This commit is contained in:
2025-09-18 17:17:35 +08:00
parent 8de14edf9d
commit 01503d6862
11 changed files with 1378 additions and 1504 deletions
BIN
View File
Binary file not shown.
+31
View File
@@ -0,0 +1,31 @@
import sqlite3
# 连接数据库
conn = sqlite3.connect('car_info.db')
cursor = conn.cursor()
# 查询所有表
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = cursor.fetchall()
print('数据库中的表:', tables)
# 查询参数配置表
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='parameter_config'")
result = cursor.fetchone()
print('参数配置表SQL:', result)
# 如果参数配置表存在,查询其结构
if result:
cursor.execute("PRAGMA table_info(parameter_config)")
columns = cursor.fetchall()
print('参数配置表结构:')
for col in columns:
print(f"字段: {col[1]}, 类型: {col[2]}")
# 查询数据
cursor.execute("SELECT * FROM parameter_config")
data = cursor.fetchall()
print('参数配置数据:', data)
# 关闭连接
conn.close()
+179
View File
@@ -0,0 +1,179 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修改密码 - 车主信息查询系统</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- 配置Tailwind自定义颜色和字体 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#2563eb',
secondary: '#f3f4f6',
'primary-light': '#dbeafe',
'primary-dark': '#1d4ed8',
'table-header': '#f9fafb',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.input-focus {
@apply focus:border-primary focus:ring-2 focus:ring-primary/20 focus:outline-none;
}
.animate-fade-in {
animation: fadeIn 0.3s ease-out forwards;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body class="bg-secondary min-h-screen font-sans">
{% include 'header.html' %}
<!-- 主要内容区域 -->
<main class="container mx-auto px-4 pt-24 pb-12">
<!-- 返回按钮 -->
<div class="mb-6 flex justify-end">
<a href="{{ url_for('query') }}" class="inline-flex items-center text-primary hover:text-primary-dark transition-colors">
<i class="fa fa-arrow-left mr-2"></i> 返回主页面
</a>
</div>
<!-- 修改密码表单 -->
<div class="max-w-md mx-auto bg-white rounded-lg shadow-xl p-8 animate-fade-in">
<div class="text-center mb-6">
<div class="w-16 h-16 bg-primary-light rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fa fa-key text-primary text-2xl"></i>
</div>
<h2 class="text-2xl font-bold text-gray-800">修改密码</h2>
<p class="text-gray-500 mt-2">请填写以下信息以修改您的密码</p>
</div>
<!-- 错误信息显示区域 -->
{% if error %}
<div class="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start animate-fade-in">
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span>{{ error }}</span>
</div>
{% endif %}
<!-- 表单 -->
<form id="changePasswordForm" method="POST" class="space-y-4">
<div class="mb-4">
<label for="old_password" class="block text-gray-700 text-sm font-medium mb-2">旧密码</label>
<div class="relative">
<input type="password" id="old_password" name="old_password" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入旧密码" required>
<button type="button" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors" onclick="togglePasswordVisibility('old_password', this)">
<i class="fa fa-eye"></i>
</button>
</div>
</div>
<div class="mb-4">
<label for="new_password" class="block text-gray-700 text-sm font-medium mb-2">新密码</label>
<div class="relative">
<input type="password" id="new_password" name="new_password" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入新密码" required>
<button type="button" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors" onclick="togglePasswordVisibility('new_password', this)">
<i class="fa fa-eye"></i>
</button>
</div>
<p class="text-xs text-gray-500 mt-1">密码长度至少8位,包含字母和数字</p>
</div>
<div class="mb-6">
<label for="confirm_password" class="block text-gray-700 text-sm font-medium mb-2">确认新密码</label>
<div class="relative">
<input type="password" id="confirm_password" name="confirm_password" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请再次输入新密码" required>
<button type="button" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors" onclick="togglePasswordVisibility('confirm_password', this)">
<i class="fa fa-eye"></i>
</button>
</div>
</div>
<div class="flex space-x-3">
<button type="submit" class="flex-1 bg-primary hover:bg-primary-dark text-white py-2 px-4 rounded-md transition-colors shadow-sm hover:shadow">
确认修改
</button>
<button type="button" onclick="window.location.href='{{ url_for('query') }}'" class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 py-2 px-4 rounded-md transition-colors">
取消
</button>
</div>
</form>
</div>
</main>
{% include 'footer.html' %}
<!-- JavaScript -->
<script>
// 密码可见性切换
function togglePasswordVisibility(inputId, button) {
const input = document.getElementById(inputId);
const icon = button.querySelector('i');
if (input.type === 'password') {
input.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
input.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
// 表单提交处理
document.getElementById('changePasswordForm').addEventListener('submit', function(e) {
e.preventDefault(); // 阻止默认提交
const newPassword = document.getElementById('new_password').value;
const confirmPassword = document.getElementById('confirm_password').value;
const oldPassword = document.getElementById('old_password').value;
// 前端验证
if (!oldPassword) {
alert('请输入旧密码');
return;
}
if (newPassword.length < 8 || !/[a-zA-Z]/.test(newPassword) || !/[0-9]/.test(newPassword)) {
alert('新密码长度至少8位,且必须包含字母和数字');
return;
}
if (newPassword !== confirmPassword) {
alert('两次输入的新密码不一致');
return;
}
// 提交表单
this.submit();
});
</script>
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
<!-- 页脚 -->
<footer class="bg-white py-4 border-t border-gray-200">
<div class="container mx-auto px-4 text-center text-gray-500 text-sm">
<p>© {{ current_year }} 车主信息查询系统 - 版权所有</p>
<p>免责声明:本系统为内部测试使用,仅供学习和研究。</p>
</div>
</footer>
+42
View File
@@ -0,0 +1,42 @@
<!-- 顶部导航栏 -->
<header class="bg-white shadow-sm fixed top-0 left-0 right-0 z-10">
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fa fa-car text-primary text-2xl"></i>
<h1 class="text-xl font-bold text-gray-800">车主信息查询系统</h1>
</div>
<div class="flex items-center space-x-4">
<!-- 用户下拉菜单 -->
<div class="relative group">
<div class="flex items-center cursor-pointer hover:text-primary transition-colors">
<span class="text-gray-600">欢迎 <strong>{{ session.department }}</strong> <strong>{{ session.name }}</strong></span>
<i class="fa fa-chevron-down ml-2 text-xs transition-transform duration-300 group-hover:rotate-180"></i>
</div>
<!-- 下拉菜单 -->
<div class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300 transform origin-top-right scale-95 group-hover:scale-100 z-20">
<div class="py-1">
<a href="{{ url_for('user.user_profile_page') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-user mr-2"></i> 个人信息
</a>
<a href="{{ url_for('user.change_password_page') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-lock mr-2"></i> 修改密码
</a>
<a href="{{ url_for('user.parameter_config_page') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-cog mr-2"></i> 参数配置
</a>
{% if session.username == 'admin' %}
<a href="{{ url_for('user.users_management') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-users mr-2"></i> 用户管理
</a>
{% endif %}
<div class="border-t border-gray-200 my-1"></div>
<a href="{{ url_for('user.logout') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-sign-out mr-2"></i> 退出
</a>
</div>
</div>
</div>
</div>
</div>
</header>
+214
View File
@@ -0,0 +1,214 @@
// 通用工具函数
/**
* 显示模态框
* @param {string} modalId - 模态框的ID
*/
function showModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.remove('hidden');
}
}
/**
* 关闭模态框
* @param {string} modalId - 模态框的ID
*/
function closeModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.add('hidden');
}
}
/**
* 绑定模态框点击外部关闭事件
* @param {string} modalId - 模态框的ID
*/
function bindModalOutsideClick(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.addEventListener('click', function(e) {
if (e.target === this) {
closeModal(modalId);
}
});
}
}
/**
* 显示消息提示
* @param {string} message - 消息内容
* @param {boolean} isSuccess - 是否为成功消息
* @param {number} duration - 消息显示时长(毫秒)
*/
function showMessage(message, isSuccess = true, duration = 5000) {
const messageContainer = document.getElementById('messageContainer');
const successMessage = document.getElementById('successMessage');
const errorMessage = document.getElementById('errorMessage');
if (!messageContainer || !successMessage || !errorMessage) return;
if (isSuccess) {
successMessage.querySelector('span').textContent = message;
successMessage.classList.remove('hidden');
errorMessage.classList.add('hidden');
} else {
errorMessage.querySelector('span').textContent = message;
errorMessage.classList.remove('hidden');
successMessage.classList.add('hidden');
}
messageContainer.classList.remove('hidden');
// 自动隐藏
setTimeout(() => {
(isSuccess ? successMessage : errorMessage).classList.add('hidden');
messageContainer.classList.add('hidden');
}, duration);
}
/**
* 显示表单错误消息
* @param {string} errorId - 错误容器的ID
* @param {string} messageId - 错误消息的ID
* @param {string} message - 错误消息内容
* @param {string} successId - 成功消息容器的ID
*/
function showFormError(errorId, messageId, message, successId = '') {
const errorContainer = document.getElementById(errorId);
const errorMessage = document.getElementById(messageId);
const successContainer = successId ? document.getElementById(successId) : null;
if (!errorContainer || !errorMessage) return;
errorMessage.textContent = message;
errorContainer.classList.remove('hidden');
if (successContainer) {
successContainer.classList.add('hidden');
}
// 滚动到错误信息
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
/**
* 显示表单成功消息
* @param {string} successId - 成功容器的ID
* @param {string} messageId - 成功消息的ID
* @param {string} message - 成功消息内容
* @param {string} errorId - 错误消息容器的ID
*/
function showFormSuccess(successId, messageId, message, errorId = '') {
const successContainer = document.getElementById(successId);
const successMessage = document.getElementById(messageId);
const errorContainer = errorId ? document.getElementById(errorId) : null;
if (!successContainer || !successMessage) return;
successMessage.textContent = message;
successContainer.classList.remove('hidden');
if (errorContainer) {
errorContainer.classList.add('hidden');
}
// 滚动到成功信息
successContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
/**
* 发起API请求的通用函数
* @param {string} url - 请求URL
* @param {string} method - 请求方法
* @param {object} data - 请求数据
* @param {object} options - 附加选项
* @returns {Promise} - 返回Promise对象
*/
async function apiRequest(url, method = 'GET', data = null, options = {}) {
const defaultOptions = {
headers: {
'Content-Type': 'application/json'
},
method,
...options
};
if (data) {
// 根据Content-Type处理数据
if (defaultOptions.headers['Content-Type'] === 'application/json') {
defaultOptions.body = JSON.stringify(data);
} else if (defaultOptions.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
defaultOptions.body = new URLSearchParams(data);
}
}
try {
const response = await fetch(url, defaultOptions);
return await response.json();
} catch (error) {
console.error(`API请求错误 [${url}]:`, error);
throw error;
}
}
/**
* 重置表单
* @param {string} formId - 表单ID
*/
function resetForm(formId) {
const form = document.getElementById(formId);
if (form) {
form.reset();
}
}
/**
* 填充表单数据
* @param {string} formId - 表单ID
* @param {object} data - 表单数据对象
*/
function fillForm(formId, data) {
const form = document.getElementById(formId);
if (!form || !data) return;
Object.keys(data).forEach(key => {
const element = form.elements[key] || document.getElementById(key);
if (element) {
element.value = data[key] || '';
}
});
}
/**
* 防抖函数
* @param {Function} func - 要防抖的函数
* @param {number} delay - 延迟时间(毫秒)
* @returns {Function} - 返回防抖后的函数
*/
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
/**
* 节流函数
* @param {Function} func - 要节流的函数
* @param {number} limit - 时间限制(毫秒)
* @returns {Function} - 返回节流后的函数
*/
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
+271
View File
@@ -0,0 +1,271 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>参数配置 - 车主信息查询系统</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- 配置Tailwind自定义颜色和字体 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#2563eb',
secondary: '#f3f4f6',
'primary-light': '#dbeafe',
'primary-dark': '#1d4ed8',
'table-header': '#f9fafb',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.input-focus {
@apply focus:border-primary focus:ring-2 focus:ring-primary/20 focus:outline-none;
}
.animate-fade-in {
animation: fadeIn 0.3s ease-out forwards;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body class="bg-secondary min-h-screen font-sans">
{% include 'header.html' %}
<!-- 主要内容区域 -->
<main class="container mx-auto px-4 pt-24 pb-12">
<!-- 返回按钮 -->
<div class="mb-6 flex justify-end">
<a href="{{ url_for('query') }}" class="inline-flex items-center text-primary hover:text-primary-dark transition-colors">
<i class="fa fa-arrow-left mr-2"></i> 返回主页面
</a>
</div>
<!-- 个人信息表单 -->
<div class="max-w-md mx-auto bg-white rounded-lg shadow-xl p-8 animate-fade-in">
<div class="text-center mb-6">
<div class="w-16 h-16 bg-primary-light rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fa fa-cog text-primary text-2xl"></i>
</div>
<h2 class="text-2xl font-bold text-gray-800">参数配置</h2>
<p class="text-gray-500 mt-2">请配置系统参数</p>
</div>
<!-- 消息提示区域 -->
{% if error %}
<div class="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start animate-fade-in">
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span>{{ error }}</span>
</div>
{% endif %}
{% if success %}
<div class="mb-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md flex items-start animate-fade-in">
<i class="fa fa-check-circle mt-1 mr-3"></i>
<span>{{ success }}</span>
</div>
{% endif %}
<div class="mb-6 bg-blue-50 border border-blue-100 text-blue-700 px-4 py-3 rounded-md flex items-center" id="updateTimeAlert" style="display: none;">
<i class="fa fa-info-circle mr-2"></i>
<span id="updateTimeContent"></span>
</div>
<div class="space-y-4">
<div class="mb-4">
<label for="param1" class="block text-gray-700 text-sm font-medium mb-2">参数1 <span class="text-red-500">*</span></label>
<input type="text" id="param1" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入参数1的值">
</div>
<div class="mb-4">
<label for="param2" class="block text-gray-700 text-sm font-medium mb-2">参数2</label>
<input type="text" id="param2" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入参数2的值">
</div>
<div class="mb-4">
<label for="param3" class="block text-gray-700 text-sm font-medium mb-2">参数3</label>
<input type="text" id="param3" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入参数3的值">
</div>
<div class="mb-4">
<label for="param4" class="block text-gray-700 text-sm font-medium mb-2">参数4</label>
<input type="text" id="param4" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入参数4的值">
</div>
<div class="mb-6">
<label for="param5" class="block text-gray-700 text-sm font-medium mb-2">参数5</label>
<input type="text" id="param5" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入参数5的值">
</div>
<div class="flex space-x-3">
<button onclick="loadParameterConfig()" class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 py-2 px-4 rounded-md transition-colors flex items-center justify-center">
<i class="fa fa-refresh mr-2"></i> 重新加载
</button>
<button onclick="saveParameterConfig()" class="flex-1 bg-primary hover:bg-primary-dark text-white py-2 px-4 rounded-md transition-colors shadow-sm hover:shadow flex items-center justify-center">
<i class="fa fa-save mr-2"></i> 保存配置
</button>
</div>
</div>
</main>
<script>
// 页面加载时获取参数配置
document.addEventListener('DOMContentLoaded', function() {
loadParameterConfig();
});
// 加载参数配置
function loadParameterConfig() {
fetch('/get_parameter_config')
.then(response => response.json())
.then(data => {
if (data.success && data.parameters) {
const parameters = data.parameters;
document.getElementById('param1').value = parameters.param1 || '';
document.getElementById('param2').value = parameters.param2 || '';
document.getElementById('param3').value = parameters.param3 || '';
document.getElementById('param4').value = parameters.param4 || '';
document.getElementById('param5').value = parameters.param5 || '';
// 显示更新时间
if (parameters.update_time) {
document.getElementById('updateTimeContent').textContent = '最后更新时间:' + parameters.update_time;
document.getElementById('updateTimeAlert').style.display = 'flex';
}
} else {
showErrorMessage('获取参数配置失败:' + (data.error || ''));
}
})
.catch(error => {
showErrorMessage('获取参数配置失败,请稍后重试');
console.error('获取参数配置失败', error);
});
}
// 保存参数配置
function saveParameterConfig() {
// 表单验证
if (!document.getElementById('param1').value.trim()) {
showErrorMessage('参数1不能为空');
return;
}
const formData = {
param1: document.getElementById('param1').value.trim(),
param2: document.getElementById('param2').value.trim(),
param3: document.getElementById('param3').value.trim(),
param4: document.getElementById('param4').value.trim(),
param5: document.getElementById('param5').value.trim()
};
fetch('/save_parameter_config', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage('参数配置保存成功');
// 重新加载参数配置
loadParameterConfig();
} else {
showErrorMessage('保存参数配置失败:' + (data.error || ''));
}
})
.catch(error => {
let errorMessage = '保存参数配置失败,请稍后重试';
if (error.responseJSON && error.responseJSON.error) {
errorMessage = error.responseJSON.error;
}
showErrorMessage(errorMessage);
console.error('保存参数配置失败', error);
});
}
// 显示错误消息
function showErrorMessage(message) {
// 创建错误消息元素
const errorDiv = document.createElement('div');
errorDiv.className = 'mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start animate-fade-in';
errorDiv.innerHTML = `
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span>${message}</span>
`;
// 插入到表单前
const container = document.querySelector('.space-y-4');
container.insertBefore(errorDiv, container.firstChild);
// 3秒后自动移除
setTimeout(() => {
errorDiv.style.opacity = '0';
errorDiv.style.transform = 'translateY(-10px)';
errorDiv.style.transition = 'opacity 0.3s, transform 0.3s';
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.parentNode.removeChild(errorDiv);
}
}, 300);
}, 3000);
}
// 显示成功消息
function showSuccessMessage(message) {
// 创建成功消息元素
const successDiv = document.createElement('div');
successDiv.className = 'mb-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md flex items-start animate-fade-in';
successDiv.innerHTML = `
<i class="fa fa-check-circle mt-1 mr-3"></i>
<span>${message}</span>
`;
// 插入到表单前
const container = document.querySelector('.space-y-4');
container.insertBefore(successDiv, container.firstChild);
// 3秒后自动移除
setTimeout(() => {
successDiv.style.opacity = '0';
successDiv.style.transform = 'translateY(-10px)';
successDiv.style.transition = 'opacity 0.3s, transform 0.3s';
setTimeout(() => {
if (successDiv.parentNode) {
successDiv.parentNode.removeChild(successDiv);
}
}, 300);
}, 3000);
}
</script>
{% include 'footer.html' %}
</body>
</html>
+194 -758
View File
@@ -32,6 +32,8 @@
} }
</script> </script>
<!-- 自定义工具类 --> <!-- 自定义工具类 -->
<style type="text/tailwindcss"> <style type="text/tailwindcss">
@layer utilities { @layer utilities {
@@ -65,45 +67,7 @@
</style> </style>
</head> </head>
<body class="bg-secondary min-h-screen font-sans"> <body class="bg-secondary min-h-screen font-sans">
<!-- 顶部导航栏 --> {% include 'header.html' %}
<header class="bg-white shadow-sm fixed top-0 left-0 right-0 z-10">
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fa fa-car text-primary text-2xl"></i>
<h1 class="text-xl font-bold text-gray-800">车主信息查询系统</h1>
</div>
<div class="flex items-center space-x-4">
<!-- 用户下拉菜单 -->
<div class="relative group">
<div class="flex items-center cursor-pointer hover:text-primary transition-colors">
<span class="text-gray-600">欢迎 <strong>{{ session.department }}</strong> <strong>{{ session.name }}</strong></span>
<i class="fa fa-chevron-down ml-2 text-xs transition-transform duration-300 group-hover:rotate-180"></i>
</div>
<!-- 下拉菜单 -->
<div class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300 transform origin-top-right scale-95 group-hover:scale-100 z-20">
<div class="py-1">
<a href="#" onclick="showChangePasswordModal()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-key mr-2"></i> 修改密码
</a>
<a href="#" onclick="checkAdminAndRedirectToUserManagement()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-users mr-2"></i> 用户管理
</a>
<a href="#" onclick="showUserProfileModal()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-user mr-2"></i> 个人信息
</a>
<a href="#" onclick="showLoginInfoModal()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-sign-in mr-2"></i> 后台登录信息
</a>
<div class="border-t border-gray-200 my-1"></div>
<a href="{{ url_for('user.logout') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-sign-out mr-2"></i> 退出
</a>
</div>
</div>
</div>
</div>
</div>
</header>
<!-- 主要内容区域 --> <!-- 主要内容区域 -->
<main class="container mx-auto px-4 pt-24 pb-12"> <main class="container mx-auto px-4 pt-24 pb-12">
@@ -248,751 +212,223 @@
</div> </div>
</div> </div>
<!-- 页脚 --> {% include 'footer.html' %}
<footer class="bg-white border-t border-gray-200 py-4 mt-auto">
<div class="container mx-auto px-4 text-center text-sm text-gray-500">
<p>© 2025 车主信息查询系统 - 内部使用</p>
<p class="mt-1">免责声明:本系统数据仅供内部人员工作参考,请勿外传或用于其他用途。</p>
</div>
</footer>
<!-- JavaScript代码 --> <!-- 页面JavaScript -->
<script> <script>
// 获取DOM元素 // 等待DOM加载完成
const detailModal = document.getElementById('detailModal'); document.addEventListener('DOMContentLoaded', function() {
const closeModal = document.getElementById('closeModal'); // 获取DOM元素
const loadingState = document.getElementById('loadingState'); const addQueryBtn = document.getElementById('addQueryBtn');
const noResultState = document.getElementById('noResultState'); const addQueryModal = document.getElementById('addQueryModal');
const resultList = document.getElementById('resultList'); const closeAddModal = document.getElementById('closeAddModal');
const carOwnerCards = document.getElementById('carOwnerCards'); const cancelAddBtn = document.getElementById('cancelAddBtn');
const addQueryForm = document.getElementById('addQueryForm');
const addQueryLoading = document.getElementById('addQueryLoading');
const detailModal = document.getElementById('detailModal');
const closeModal = document.getElementById('closeModal');
const loadingState = document.getElementById('loadingState');
const noResultState = document.getElementById('noResultState');
const resultList = document.getElementById('resultList');
const carOwnerCards = document.getElementById('carOwnerCards');
// 增加查询模态框相关元素 // 为查询历史记录行添加点击事件
const addQueryBtn = document.getElementById('addQueryBtn');
const addQueryModal = document.getElementById('addQueryModal');
const closeAddModal = document.getElementById('closeAddModal');
const cancelAddBtn = document.getElementById('cancelAddBtn');
const addQueryForm = document.getElementById('addQueryForm');
const addQueryLoading = document.getElementById('addQueryLoading');
// 添加点击事件到查询历史记录行
document.querySelectorAll('tr[data-history-id]').forEach(row => {
row.addEventListener('click', function() {
const historyId = this.getAttribute('data-history-id');
loadHistoryDetail(historyId);
});
});
// 增加查询按钮点击事件
addQueryBtn.addEventListener('click', function() {
addQueryModal.classList.remove('hidden');
addQueryForm.reset();
});
// 关闭增加查询模态框
closeAddModal.addEventListener('click', function() {
addQueryModal.classList.add('hidden');
});
// 取消增加查询
cancelAddBtn.addEventListener('click', function() {
addQueryModal.classList.add('hidden');
});
// 点击增加查询模态框背景关闭
addQueryModal.addEventListener('click', function(e) {
if (e.target === addQueryModal) {
addQueryModal.classList.add('hidden');
}
});
// 关闭详情模态框
closeModal.addEventListener('click', function() {
detailModal.classList.add('hidden');
});
// 点击详情模态框背景关闭
detailModal.addEventListener('click', function(e) {
if (e.target === detailModal) {
detailModal.classList.add('hidden');
}
});
// 处理增加查询表单提交
addQueryForm.addEventListener('submit', function(e) {
e.preventDefault();
const plateNumber = document.getElementById('add_plate_number').value.trim();
const phone = document.getElementById('add_phone').value.trim();
if (!plateNumber && !phone) {
alert('请至少输入车牌号或手机号');
return;
}
// 显示加载状态
addQueryForm.classList.add('hidden');
addQueryLoading.classList.remove('hidden');
// 构建表单数据
const formData = new FormData();
formData.append('plate_number', plateNumber);
formData.append('phone', phone);
// 发送请求到查询路由
fetch('/query', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('网络错误');
}
// 重新加载页面以显示新的查询记录
window.location.reload();
})
.catch(error => {
console.error('Error submitting query:', error);
alert('提交失败,请重试');
// 恢复表单显示
addQueryForm.classList.remove('hidden');
addQueryLoading.classList.add('hidden');
});
});
// 加载查询历史详情
function loadHistoryDetail(historyId) {
// 显示模态框和加载状态
detailModal.classList.remove('hidden');
loadingState.classList.remove('hidden');
noResultState.classList.add('hidden');
resultList.classList.add('hidden');
carOwnerCards.innerHTML = '';
// 发送AJAX请求获取详情
fetch(`/query_history_detail/${historyId}`)
.then(response => response.json())
.then(data => {
// 隐藏加载状态
loadingState.classList.add('hidden');
if (data.error) {
// 显示错误信息
noResultState.querySelector('p').textContent = data.error;
noResultState.classList.remove('hidden');
} else if (data.car_owners && data.car_owners.length > 0) {
// 显示结果列表
resultList.classList.remove('hidden');
// 添加车辆信息卡片
data.car_owners.forEach(carOwner => {
const 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';
card.innerHTML = `
<h4 class="text-lg font-medium text-gray-800 mb-4 flex items-center">
<i class="fa fa-car text-primary mr-2"></i>
车辆信息
</h4>
<div class="space-y-3">
<div class="flex justify-between items-center">
<span class="text-gray-500">车牌号:</span>
<span class="font-medium text-gray-800">${carOwner.plate_number}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">手机号:</span>
<span class="font-medium text-gray-800">${carOwner.phone}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">车主姓名:</span>
<span class="font-medium text-gray-800">${carOwner.name}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">身份证号:</span>
<span class="font-medium text-gray-800">${carOwner.id_card}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">电子邮箱:</span>
<span class="font-medium text-gray-800">${carOwner.email || '未提供'}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">居住地址:</span>
<span class="font-medium text-gray-800">${carOwner.address || '未提供'}</span>
</div>
</div>
`;
carOwnerCards.appendChild(card);
});
} else {
// 显示无结果状态
noResultState.classList.remove('hidden');
}
})
.catch(error => {
// 隐藏加载状态,显示错误信息
loadingState.classList.add('hidden');
noResultState.querySelector('p').textContent = '加载详情时发生错误';
noResultState.classList.remove('hidden');
console.error('Error loading history detail:', error);
});
}
// 响应式处理:当窗口大小改变时,重新绑定事件(因为表格可能会重新渲染)
window.addEventListener('resize', function() {
// 重新绑定事件到查询历史记录行
document.querySelectorAll('tr[data-history-id]').forEach(row => { document.querySelectorAll('tr[data-history-id]').forEach(row => {
// 先移除旧的事件监听器 row.addEventListener('click', function() {
const newRow = row.cloneNode(true);
row.parentNode.replaceChild(newRow, row);
// 添加新的事件监听器
newRow.addEventListener('click', function() {
const historyId = this.getAttribute('data-history-id'); const historyId = this.getAttribute('data-history-id');
loadHistoryDetail(historyId); loadHistoryDetail(historyId);
}); });
}); });
});
</script>
<!-- 修改密码弹窗 -->
<div id="changePasswordModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden transform transition-all">
<div class="bg-primary text-white p-4 flex justify-between items-center">
<h3 class="text-lg font-medium">修改密码</h3>
<button onclick="closeChangePasswordModal()" class="text-white hover:text-gray-200 transition-colors">
<i class="fa fa-times"></i>
</button>
</div>
<div class="p-6">
<!-- 错误信息显示区域 -->
<div id="passwordError" class="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span id="errorMessage"></span>
</div>
<form id="changePasswordForm" method="POST"> // 增加查询按钮点击事件
<div class="mb-4"> addQueryBtn.addEventListener('click', function() {
<label for="old_password" class="block text-gray-700 text-sm font-medium mb-2">旧密码</label> addQueryModal.classList.remove('hidden');
<div class="relative"> addQueryForm.reset();
<input type="password" id="old_password" name="old_password" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-colors" placeholder="请输入旧密码" required>
<button type="button" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors" onclick="togglePasswordVisibility('old_password', this)">
<i class="fa fa-eye"></i>
</button>
</div>
</div>
<div class="mb-4">
<label for="new_password" class="block text-gray-700 text-sm font-medium mb-2">新密码</label>
<div class="relative">
<input type="password" id="new_password" name="new_password" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-colors" placeholder="请输入新密码" required>
<button type="button" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors" onclick="togglePasswordVisibility('new_password', this)">
<i class="fa fa-eye"></i>
</button>
</div>
<p class="text-xs text-gray-500 mt-1">密码长度至少8位,包含字母和数字</p>
</div>
<div class="mb-6">
<label for="confirm_password" class="block text-gray-700 text-sm font-medium mb-2">确认新密码</label>
<div class="relative">
<input type="password" id="confirm_password" name="confirm_password" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-colors" placeholder="请再次输入新密码" required>
<button type="button" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors" onclick="togglePasswordVisibility('confirm_password', this)">
<i class="fa fa-eye"></i>
</button>
</div>
</div>
<div class="flex space-x-3">
<button type="submit" class="flex-1 bg-primary hover:bg-primary/90 text-white py-2 px-4 rounded-md transition-colors shadow-sm hover:shadow">
确认修改
</button>
<button type="button" onclick="closeChangePasswordModal()" class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 py-2 px-4 rounded-md transition-colors">
取消
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 个人信息弹窗 -->
<div id="userProfileModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden transform transition-all">
<div class="bg-primary text-white p-4 flex justify-between items-center">
<h3 class="text-lg font-medium">个人信息</h3>
<button onclick="closeUserProfileModal()" class="text-white hover:text-gray-200 transition-colors">
<i class="fa fa-times"></i>
</button>
</div>
<div class="p-6">
<!-- 错误信息显示区域 -->
<div id="profileError" class="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span id="errorProfileMessage"></span>
</div>
<!-- 成功信息显示区域 -->
<div id="profileSuccess" class="mb-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-check-circle mt-1 mr-3"></i>
<span id="successProfileMessage"></span>
</div>
<form id="userProfileForm">
<div class="mb-4">
<label for="profile_username" class="block text-gray-700 text-sm font-medium mb-2">用户名</label>
<input type="text" id="profile_username" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-colors" disabled>
</div>
<div class="mb-4">
<label for="profile_name" class="block text-gray-700 text-sm font-medium mb-2">姓名 <span class="text-red-500">*</span></label>
<input type="text" id="profile_name" name="name" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-colors" placeholder="请输入姓名" required>
</div>
<div class="mb-4">
<label for="profile_phone" class="block text-gray-700 text-sm font-medium mb-2">联系电话 <span class="text-red-500">*</span></label>
<input type="tel" id="profile_phone" name="phone" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-colors" placeholder="请输入联系电话" required>
</div>
<div class="mb-6">
<label for="profile_department" class="block text-gray-700 text-sm font-medium mb-2">部门名称 <span class="text-red-500">*</span></label>
<input type="text" id="profile_department" name="department" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-colors" placeholder="请输入部门名称" required>
</div>
<div class="flex space-x-3">
<button type="submit" class="flex-1 bg-primary hover:bg-primary/90 text-white py-2 px-4 rounded-md transition-colors shadow-sm hover:shadow">
保存修改
</button>
<button type="button" onclick="closeUserProfileModal()" class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 py-2 px-4 rounded-md transition-colors">
取消
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 后台登录信息弹窗 -->
<div id="loginInfoModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden transform transition-all">
<div class="bg-primary text-white p-4 flex justify-between items-center">
<h3 class="text-lg font-medium">后台登录信息</h3>
<button onclick="closeLoginInfoModal()" class="text-white hover:text-gray-200 transition-colors">
<i class="fa fa-times"></i>
</button>
</div>
<div class="p-6">
<!-- 错误信息显示区域 -->
<div id="loginInfoError" class="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span id="errorLoginInfoMessage"></span>
</div>
<!-- 成功信息显示区域 -->
<div id="loginInfoSuccess" class="mb-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-check-circle mt-1 mr-3"></i>
<span id="successLoginInfoMessage"></span>
</div>
<!-- 登录信息编辑表单 -->
<form id="loginInfoForm" class="space-y-4">
<div class="bg-gray-50 p-4 rounded-md">
<div class="grid grid-cols-1 gap-2 text-sm">
<div class="text-gray-500 mb-1">登录用户名:</div>
<input type="text" id="loginUsername" value="{{ session.username }}"
{% if session.username != 'admin' %}readonly{% endif %}
class="w-full px-3 py-2 border rounded-md text-gray-800 input-focus"
{% if session.username != 'admin' %}style="background-color: #f3f4f6; cursor: not-allowed;"{% endif %}>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-md">
<div class="grid grid-cols-1 gap-2 text-sm">
<div class="text-gray-500 mb-1">用户密码:</div>
<input type="password" id="loginPassword" value="" placeholder="{% if session.username == 'admin' %}输入新密码或保持为空不修改{% else %}只读,无法修改{% endif %}"
{% if session.username != 'admin' %}readonly{% endif %}
class="w-full px-3 py-2 border rounded-md text-gray-800 input-focus"
{% if session.username != 'admin' %}style="background-color: #f3f4f6; cursor: not-allowed;"{% endif %}>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-md">
<div class="grid grid-cols-1 gap-2 text-sm">
<div class="text-gray-500 mb-1">OTP码:</div>
<input type="text" id="loginOtpCode" value="{{ session.otp_code if 'otp_code' in session else '' }}"
class="w-full px-3 py-2 border rounded-md text-gray-800 input-focus">
</div>
</div>
</form>
<div class="mt-6 flex space-x-4">
<button type="button" onclick="saveLoginInfo()" class="flex-1 bg-primary hover:bg-primary/90 text-white py-2 px-4 rounded-md transition-colors shadow-sm hover:shadow">
保存修改
</button>
<button type="button" onclick="closeLoginInfoModal()" class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 py-2 px-4 rounded-md transition-colors">
取消
</button>
</div>
</div>
</div>
</div>
<!-- 修改密码相关JavaScript -->
<script>
function showChangePasswordModal() {
document.getElementById('changePasswordModal').classList.remove('hidden');
document.getElementById('old_password').focus();
}
function closeChangePasswordModal() {
document.getElementById('changePasswordModal').classList.add('hidden');
// 重置表单
document.getElementById('changePasswordForm').reset();
}
function togglePasswordVisibility(inputId, button) {
const input = document.getElementById(inputId);
const icon = button.querySelector('i');
if (input.type === 'password') {
input.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
input.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
// 表单提交处理
document.getElementById('changePasswordForm').addEventListener('submit', function(e) {
e.preventDefault(); // 阻止默认提交
const newPassword = document.getElementById('new_password').value;
const confirmPassword = document.getElementById('confirm_password').value;
const oldPassword = document.getElementById('old_password').value;
const errorContainer = document.getElementById('passwordError');
const errorMessage = document.getElementById('errorMessage');
// 隐藏之前的错误信息
errorContainer.classList.add('hidden');
// 前端验证
if (!oldPassword) {
showPasswordError('请输入旧密码');
return;
}
if (newPassword.length < 8 || !/[a-zA-Z]/.test(newPassword) || !/[0-9]/.test(newPassword)) {
showPasswordError('新密码长度至少8位,且必须包含字母和数字');
return;
}
if (newPassword !== confirmPassword) {
showPasswordError('两次输入的新密码不一致');
return;
}
// 异步提交表单
const formData = new FormData();
formData.append('old_password', oldPassword);
formData.append('new_password', newPassword);
formData.append('confirm_password', confirmPassword);
fetch('{{ url_for('user.change_password') }}', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 密码修改成功,先执行退出功能,再跳转到登录页面
alert(data.message);
// 重定向到退出路由,系统会自动完成退出并跳转到登录页面
window.location.href = '{{ url_for('user.logout') }}';
} else if (data.error) {
// 显示服务器返回的错误信息
showPasswordError(data.error);
}
})
.catch(error => {
console.error('修改密码时发生错误:', error);
showPasswordError('网络错误,请稍后重试');
}); });
});
// 显示密码错误信息 // 关闭增加查询模态框
function showPasswordError(message) { closeAddModal.addEventListener('click', function() {
const errorContainer = document.getElementById('passwordError'); addQueryModal.classList.add('hidden');
const errorMessage = document.getElementById('errorMessage'); });
errorMessage.textContent = message;
errorContainer.classList.remove('hidden');
// 滚动到错误信息
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 点击弹窗外部关闭弹窗 // 取消增加查询
document.getElementById('changePasswordModal').addEventListener('click', function(e) { cancelAddBtn.addEventListener('click', function() {
if (e.target === this) { addQueryModal.classList.add('hidden');
closeChangePasswordModal(); });
}
});
// ESC键关闭弹窗 // 点击增加查询模态框背景关闭
document.addEventListener('keydown', function(e) { addQueryModal.addEventListener('click', function(e) {
if (e.key === 'Escape' && !document.getElementById('changePasswordModal').classList.contains('hidden')) { if (e.target === addQueryModal) {
closeChangePasswordModal(); addQueryModal.classList.add('hidden');
} }
// ESC键关闭个人信息弹窗 });
if (e.key === 'Escape' && !document.getElementById('userProfileModal').classList.contains('hidden')) {
closeUserProfileModal();
}
});
// 个人信息相关JavaScript // 关闭详情模态框
function showUserProfileModal() { closeModal.addEventListener('click', function() {
const modal = document.getElementById('userProfileModal'); detailModal.classList.add('hidden');
modal.classList.remove('hidden'); });
// 重置表单状态
document.getElementById('profileError').classList.add('hidden');
document.getElementById('profileSuccess').classList.add('hidden');
// 加载用户信息 // 点击详情模态框背景关闭
fetch('{{ url_for('user.get_user_profile') }}') detailModal.addEventListener('click', function(e) {
.then(response => response.json()) if (e.target === detailModal) {
.then(data => { detailModal.classList.add('hidden');
if (data.success) { }
document.getElementById('profile_name').value = data.user.name || ''; });
document.getElementById('profile_phone').value = data.user.phone || '';
document.getElementById('profile_department').value = data.user.department || ''; // 处理增加查询表单提交
document.getElementById('profile_username').value = data.user.username || ''; addQueryForm.addEventListener('submit', function(e) {
} else { e.preventDefault();
showProfileError(data.error || '加载个人信息失败');
const plateNumber = document.getElementById('add_plate_number').value.trim();
const phone = document.getElementById('add_phone').value.trim();
if (!plateNumber && !phone) {
alert('请至少输入车牌号或手机号');
return;
}
// 显示加载状态
addQueryForm.classList.add('hidden');
addQueryLoading.classList.remove('hidden');
// 构建表单数据
const formData = new FormData();
formData.append('plate_number', plateNumber);
formData.append('phone', phone);
// 发送请求到查询路由
fetch('/query', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('网络错误');
} }
// 重新加载页面以显示新的查询记录
window.location.reload();
}) })
.catch(error => { .catch(error => {
console.error('加载个人信息时发生错误:', error); console.error('Error submitting query:', error);
showProfileError('网络错误,请稍后重试'); alert('提交失败,请重试');
// 恢复表单显示
addQueryForm.classList.remove('hidden');
addQueryLoading.classList.add('hidden');
}); });
} });
function closeUserProfileModal() { // 加载查询历史详情
document.getElementById('userProfileModal').classList.add('hidden'); function loadHistoryDetail(historyId) {
} // 显示模态框和加载状态
detailModal.classList.remove('hidden');
loadingState.classList.remove('hidden');
noResultState.classList.add('hidden');
resultList.classList.add('hidden');
carOwnerCards.innerHTML = '';
function showProfileError(message) { // 发送AJAX请求获取详情
const errorContainer = document.getElementById('profileError'); fetch(`/query_history_detail/${historyId}`)
const errorMessage = document.getElementById('errorProfileMessage'); .then(response => response.json())
errorMessage.textContent = message; .then(data => {
errorContainer.classList.remove('hidden'); // 隐藏加载状态
document.getElementById('profileSuccess').classList.add('hidden'); loadingState.classList.add('hidden');
// 滚动到错误信息
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function showProfileSuccess(message) { if (data.error) {
const successContainer = document.getElementById('profileSuccess'); // 显示错误信息
const successMessage = document.getElementById('successProfileMessage'); noResultState.querySelector('p').textContent = data.error;
successMessage.textContent = message; noResultState.classList.remove('hidden');
successContainer.classList.remove('hidden'); } else if (data.car_owners && data.car_owners.length > 0) {
document.getElementById('profileError').classList.add('hidden'); // 显示结果列表
// 滚动到成功信息 resultList.classList.remove('hidden');
successContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 个人信息表单提交处理 // 添加车辆信息卡片
document.getElementById('userProfileForm').addEventListener('submit', function(e) { data.car_owners.forEach(carOwner => {
e.preventDefault(); // 阻止默认提交 const 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';
const name = document.getElementById('profile_name').value.trim(); card.innerHTML = `
const phone = document.getElementById('profile_phone').value.trim(); <h4 class="text-lg font-medium text-gray-800 mb-4 flex items-center">
const department = document.getElementById('profile_department').value.trim(); <i class="fa fa-car text-primary mr-2"></i>
车辆信息
// 隐藏之前的消息 </h4>
document.getElementById('profileError').classList.add('hidden'); <div class="space-y-3">
document.getElementById('profileSuccess').classList.add('hidden'); <div class="flex justify-between items-center">
<span class="text-gray-500">车牌号:</span>
// 前端验证 <span class="font-medium text-gray-800">${carOwner.plate_number}</span>
if (!name) { </div>
showProfileError('请输入姓名'); <div class="flex justify-between items-center">
return; <span class="text-gray-500">手机号:</span>
} <span class="font-medium text-gray-800">${carOwner.phone}</span>
</div>
if (!phone) { <div class="flex justify-between items-center">
showProfileError('请输入联系电话'); <span class="text-gray-500">车主姓名:</span>
return; <span class="font-medium text-gray-800">${carOwner.name}</span>
} </div>
<div class="flex justify-between items-center">
if (!department) { <span class="text-gray-500">身份证号:</span>
showProfileError('请输入部门名称'); <span class="font-medium text-gray-800">${carOwner.id_card}</span>
return; </div>
} <div class="flex justify-between items-center">
<span class="text-gray-500">电子邮箱:</span>
// 异步提交表单 <span class="font-medium text-gray-800">${carOwner.email || '未提供'}</span>
const formData = new FormData(); </div>
formData.append('name', name); <div class="flex justify-between items-center">
formData.append('phone', phone); <span class="text-gray-500">居住地址:</span>
formData.append('department', department); <span class="font-medium text-gray-800">${carOwner.address || '未提供'}</span>
</div>
fetch('{{ url_for('user.update_user_profile') }}', { </div>
method: 'POST', `;
body: formData carOwnerCards.appendChild(card);
}) });
.then(response => response.json()) } else {
.then(data => { // 显示无结果状态
if (data.success) { noResultState.classList.remove('hidden');
// 更新成功
showProfileSuccess(data.message || '个人信息更新成功');
// 可选:更新会话中的部门信息
if (data.user && data.user.department) {
sessionStorage.setItem('department', data.user.department);
// 更新页面上显示的部门信息
const welcomeText = document.querySelector('.relative.group span');
if (welcomeText) {
const username = welcomeText.textContent.match(/欢迎\s+<strong>([^<]+)<\/strong>\s+<strong>([^<]+)<\/strong>/)?.[2] || '';
welcomeText.innerHTML = `欢迎 <strong>${data.user.department}</strong> <strong>${username}</strong>`;
} }
} })
} else { .catch(error => {
// 显示服务器返回的错误信息 // 隐藏加载状态,显示错误信息
showProfileError(data.error || '个人信息更新失败'); loadingState.classList.add('hidden');
} noResultState.querySelector('p').textContent = '加载详情时发生错误';
}) noResultState.classList.remove('hidden');
.catch(error => { console.error('Error loading history detail:', error);
console.error('更新个人信息时发生错误:', error); });
showProfileError('网络错误,请稍后重试'); }
// 响应式处理:当窗口大小改变时,重新绑定事件(因为表格可能会重新渲染)
window.addEventListener('resize', function() {
// 重新绑定事件到查询历史记录行
document.querySelectorAll('tr[data-history-id]').forEach(row => {
// 先移除旧的事件监听器
const newRow = row.cloneNode(true);
row.parentNode.replaceChild(newRow, row);
// 添加新的事件监听器
newRow.addEventListener('click', function() {
const historyId = this.getAttribute('data-history-id');
loadHistoryDetail(historyId);
});
});
}); });
}); });
// 点击个人信息弹窗外部关闭弹窗
document.getElementById('userProfileModal').addEventListener('click', function(e) {
if (e.target === this) {
closeUserProfileModal();
}
});
// 后台登录信息弹窗相关JavaScript
function showLoginInfoModal() {
const modal = document.getElementById('loginInfoModal');
modal.classList.remove('hidden');
// 隐藏之前的错误和成功信息
document.getElementById('loginInfoError').classList.add('hidden');
document.getElementById('loginInfoSuccess').classList.add('hidden');
// 用户名、用户密码和OTP码已在页面加载时通过模板渲染显示
}
function closeLoginInfoModal() {
document.getElementById('loginInfoModal').classList.add('hidden');
}
function showLoginInfoError(message) {
const errorContainer = document.getElementById('loginInfoError');
const errorMessage = document.getElementById('errorLoginInfoMessage');
errorMessage.textContent = message;
errorContainer.classList.remove('hidden');
document.getElementById('loginInfoSuccess').classList.add('hidden');
// 滚动到错误信息
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function showLoginInfoSuccess(message) {
const successContainer = document.getElementById('loginInfoSuccess');
const successMessage = document.getElementById('successLoginInfoMessage');
successMessage.textContent = message;
successContainer.classList.remove('hidden');
document.getElementById('loginInfoError').classList.add('hidden');
// 滚动到成功信息
successContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 保存登录信息修改
function saveLoginInfo() {
// 获取表单数据
const username = document.getElementById('loginUsername').value.trim();
const password = document.getElementById('loginPassword').value;
const otpCode = document.getElementById('loginOtpCode').value.trim();
// 隐藏之前的消息
document.getElementById('loginInfoError').classList.add('hidden');
document.getElementById('loginInfoSuccess').classList.add('hidden');
// 前端验证
if (!otpCode) {
showLoginInfoError('OTP码不能为空');
return;
}
if (otpCode.length !== 6) {
showLoginInfoError('OTP码长度必须是6位');
return;
}
// 如果是admin用户修改密码,需要验证密码非空
{% if session.username == 'admin' %}
if (password === '') {
showLoginInfoError('密码不能为空');
return;
}
{% endif %}
// 构建请求数据
const data = {
username: username,
otp_code: otpCode
};
// 如果admin用户提供了新密码,添加到请求数据中
{% if session.username == 'admin' %}
if (password) {
data.password = password;
}
{% endif %}
// 异步提交修改
fetch('{{ url_for('user.update_login_info') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 更新成功
showLoginInfoSuccess(data.message || '登录信息更新成功');
// 更新session中的OTP码
{% if 'session' in globals %}
session['otp_code'] = otpCode;
{% endif %}
// 3秒后关闭弹窗
setTimeout(() => {
closeLoginInfoModal();
}, 1500);
} else {
// 显示服务器返回的错误信息
showLoginInfoError(data.error || '登录信息更新失败');
}
})
.catch(error => {
console.error('更新登录信息时发生错误:', error);
showLoginInfoError('网络错误,请稍后重试');
});
}
// 点击后台登录信息弹窗外部关闭弹窗
document.getElementById('loginInfoModal').addEventListener('click', function(e) {
if (e.target === this) {
closeLoginInfoModal();
}
});
// 检查是否为管理员并跳转到用户管理页面
function checkAdminAndRedirectToUserManagement() {
// 从服务器渲染的模板中获取当前登录的用户名
const currentUsername = '{{ session.username }}';
// 检查用户名是否为admin
if (currentUsername === 'admin') {
// 如果是admin用户,重定向到用户管理页面
window.location.href = '{{ url_for('user.users_management') }}';
} else {
// 如果不是admin用户,显示提示信息
alert('只有管理员才可以进行用户管理');
}
}
</script> </script>
<script>
// 全局搜索变量
let searchQuery = '';
</script>
<script>
// ESC键关闭详情弹窗
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && !document.getElementById('detailModal').classList.contains('hidden')) {
document.getElementById('detailModal').classList.add('hidden');
}
});
</script>
</body> </body>
</html> </html>
+127
View File
@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人信息 - 车主信息查询系统</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- 配置Tailwind自定义颜色和字体 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#2563eb',
secondary: '#f3f4f6',
'primary-light': '#dbeafe',
'primary-dark': '#1d4ed8',
'table-header': '#f9fafb',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.input-focus {
@apply focus:border-primary focus:ring-2 focus:ring-primary/20 focus:outline-none;
}
.animate-fade-in {
animation: fadeIn 0.3s ease-out forwards;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body class="bg-secondary min-h-screen font-sans">
{% include 'header.html' %}
<!-- 主要内容区域 -->
<main class="container mx-auto px-4 pt-24 pb-12">
<!-- 返回按钮 -->
<div class="mb-6 flex justify-end">
<a href="{{ url_for('query') }}" class="inline-flex items-center text-primary hover:text-primary-dark transition-colors">
<i class="fa fa-arrow-left mr-2"></i> 返回主页面
</a>
</div>
<!-- 个人信息表单 -->
<div class="max-w-md mx-auto bg-white rounded-lg shadow-xl p-8 animate-fade-in">
<div class="text-center mb-6">
<div class="w-16 h-16 bg-primary-light rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fa fa-user text-primary text-2xl"></i>
</div>
<h2 class="text-2xl font-bold text-gray-800">个人信息</h2>
<p class="text-gray-500 mt-2">请查看并更新您的个人信息</p>
</div>
<!-- 消息提示区域 -->
{% if error %}
<div class="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start animate-fade-in">
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span>{{ error }}</span>
</div>
{% endif %}
{% if success %}
<div class="mb-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md flex items-start animate-fade-in">
<i class="fa fa-check-circle mt-1 mr-3"></i>
<span>{{ success }}</span>
</div>
{% endif %}
<!-- 表单 -->
<form id="userProfileForm" method="POST" class="space-y-4">
<div class="mb-4">
<label for="profile_username" class="block text-gray-700 text-sm font-medium mb-2">用户名</label>
<input type="text" id="profile_username" value="{{ user.username }}" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors bg-gray-50 cursor-not-allowed" disabled>
</div>
<div class="mb-4">
<label for="profile_name" class="block text-gray-700 text-sm font-medium mb-2">姓名 <span class="text-red-500">*</span></label>
<input type="text" id="profile_name" name="name" value="{{ user.name }}" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入姓名" required>
</div>
<div class="mb-4">
<label for="profile_phone" class="block text-gray-700 text-sm font-medium mb-2">联系电话 <span class="text-red-500">*</span></label>
<input type="tel" id="profile_phone" name="phone" value="{{ user.phone }}" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入联系电话" required>
</div>
<div class="mb-6">
<label for="profile_department" class="block text-gray-700 text-sm font-medium mb-2">部门名称 <span class="text-red-500">*</span></label>
<input type="text" id="profile_department" name="department" value="{{ user.department }}" class="w-full px-4 py-2 border border-gray-300 rounded-md input-focus transition-colors" placeholder="请输入部门名称" required>
</div>
<div class="flex space-x-3">
<button type="submit" class="flex-1 bg-primary hover:bg-primary-dark text-white py-2 px-4 rounded-md transition-colors shadow-sm hover:shadow">
保存修改
</button>
<button type="button" onclick="window.location.href='{{ url_for('query') }}'" class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 py-2 px-4 rounded-md transition-colors">
取消
</button>
</div>
</form>
</div>
</main>
{% include 'footer.html' %}
</body>
</html>
+51 -615
View File
@@ -32,6 +32,9 @@
} }
</script> </script>
<!-- 引入通用工具函数 -->
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
<!-- 自定义工具类 --> <!-- 自定义工具类 -->
<style type="text/tailwindcss"> <style type="text/tailwindcss">
@layer utilities { @layer utilities {
@@ -65,48 +68,17 @@
</style> </style>
</head> </head>
<body class="bg-secondary min-h-screen font-sans"> <body class="bg-secondary min-h-screen font-sans">
<!-- 顶部导航栏 --> {% include 'header.html' %}
<header class="bg-white shadow-sm fixed top-0 left-0 right-0 z-10">
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fa fa-car text-primary text-2xl"></i>
<h1 class="text-xl font-bold text-gray-800">车主信息查询系统</h1>
</div>
<div class="flex items-center space-x-4">
<!-- 用户下拉菜单 -->
<div class="relative group">
<div class="flex items-center cursor-pointer hover:text-primary transition-colors">
<span class="text-gray-600">欢迎 <strong>{{ session.department }}</strong> <strong>{{ session.name }}</strong></span>
<i class="fa fa-chevron-down ml-2 text-xs transition-transform duration-300 group-hover:rotate-180"></i>
</div>
<!-- 下拉菜单 -->
<div class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300 transform origin-top-right scale-95 group-hover:scale-100 z-20">
<div class="py-1">
<a href="#" onclick="showChangePasswordModal()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-key mr-2"></i> 修改密码
</a>
<a href="{{ url_for('user.users_management') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors bg-primary/10">
<i class="fa fa-users mr-2"></i> 用户管理
</a>
<a href="#" onclick="showUserProfileModal()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-user mr-2"></i> 个人信息
</a>
<a href="#" onclick="showLoginInfoModal()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-sign-in mr-2"></i> 后台登录信息
</a>
<div class="border-t border-gray-200 my-1"></div>
<a href="{{ url_for('user.logout') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
<i class="fa fa-sign-out mr-2"></i> 退出
</a>
</div>
</div>
</div>
</div>
</div>
</header>
<!-- 主内容区 --> <!-- 主内容区 -->
<main class="container mx-auto px-4 pt-24 pb-12"> <main class="container mx-auto px-4 pt-24 pb-12">
<!-- 返回按钮 -->
<div class="mb-6 flex justify-end">
<a href="{{ url_for('query') }}" class="inline-flex items-center text-primary hover:text-primary-dark transition-colors">
<i class="fa fa-arrow-left mr-2"></i> 返回主页面
</a>
</div>
<!-- 页面标题 --> <!-- 页面标题 -->
<div class="mb-6"> <div class="mb-6">
<h2 class="text-2xl font-bold text-gray-800">用户管理</h2> <h2 class="text-2xl font-bold text-gray-800">用户管理</h2>
@@ -197,6 +169,8 @@
</div> </div>
</main> </main>
{% include 'footer.html' %}
<!-- 修改部门信息弹窗 --> <!-- 修改部门信息弹窗 -->
<div id="editDepartmentModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> <div id="editDepartmentModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden transform transition-all animate-fade-in"> <div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden transform transition-all animate-fade-in">
@@ -271,177 +245,6 @@
</div> </div>
</div> </div>
<!-- 修改密码弹窗 -->
<div id="changePasswordModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden transform transition-all animate-fade-in">
<div class="bg-primary text-white p-4 flex justify-between items-center">
<h3 class="text-lg font-medium">修改密码</h3>
<button onclick="closeChangePasswordModal()" class="text-white hover:text-gray-200 transition-colors">
<i class="fa fa-times"></i>
</button>
</div>
<div class="p-6">
<!-- 错误信息显示区域 -->
<div id="changePasswordError" class="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span id="errorChangePasswordMessage"></span>
</div>
<!-- 成功信息显示区域 -->
<div id="changePasswordSuccess" class="mb-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-check-circle mt-1 mr-3"></i>
<span id="successChangePasswordMessage"></span>
</div>
<form id="changePasswordForm">
<div class="mb-4">
<label for="old_password" class="block text-sm font-medium text-gray-700 mb-1">当前密码</label>
<input type="password" id="old_password" class="w-full px-3 py-2 border border-gray-300 rounded-md input-focus">
</div>
<div class="mb-4">
<label for="new_password" class="block text-sm font-medium text-gray-700 mb-1">新密码</label>
<input type="password" id="new_password" class="w-full px-3 py-2 border border-gray-300 rounded-md input-focus">
</div>
<div class="mb-6">
<label for="confirm_password" class="block text-sm font-medium text-gray-700 mb-1">确认新密码</label>
<input type="password" id="confirm_password" class="w-full px-3 py-2 border border-gray-300 rounded-md input-focus">
</div>
<div class="flex space-x-4">
<button type="submit" class="flex-1 bg-primary hover:bg-primary/90 text-white py-2 px-4 rounded-md transition-colors shadow-sm hover:shadow">
保存修改
</button>
<button type="button" onclick="closeChangePasswordModal()" class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 py-2 px-4 rounded-md transition-colors">
取消
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 个人信息弹窗 -->
<div id="userProfileModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden transform transition-all animate-fade-in">
<div class="bg-primary text-white p-4 flex justify-between items-center">
<h3 class="text-lg font-medium">个人信息</h3>
<button onclick="closeUserProfileModal()" class="text-white hover:text-gray-200 transition-colors">
<i class="fa fa-times"></i>
</button>
</div>
<div class="p-6">
<!-- 错误信息显示区域 -->
<div id="userProfileError" class="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span id="errorUserProfileMessage"></span>
</div>
<!-- 成功信息显示区域 -->
<div id="userProfileSuccess" class="mb-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-check-circle mt-1 mr-3"></i>
<span id="successUserProfileMessage"></span>
</div>
<form id="userProfileForm">
<div class="mb-4">
<label for="profile_username" class="block text-sm font-medium text-gray-700 mb-1">用户名</label>
<input type="text" id="profile_username" readonly class="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 cursor-not-allowed">
</div>
<div class="mb-4">
<label for="profile_name" class="block text-sm font-medium text-gray-700 mb-1">姓名</label>
<input type="text" id="profile_name" class="w-full px-3 py-2 border border-gray-300 rounded-md input-focus">
</div>
<div class="mb-4">
<label for="profile_phone" class="block text-sm font-medium text-gray-700 mb-1">电话</label>
<input type="text" id="profile_phone" class="w-full px-3 py-2 border border-gray-300 rounded-md input-focus">
</div>
<div class="mb-6">
<label for="profile_department" class="block text-sm font-medium text-gray-700 mb-1">部门</label>
<input type="text" id="profile_department" readonly class="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 cursor-not-allowed">
</div>
<div class="flex space-x-4">
<button type="submit" class="flex-1 bg-primary hover:bg-primary/90 text-white py-2 px-4 rounded-md transition-colors shadow-sm hover:shadow">
保存修改
</button>
<button type="button" onclick="closeUserProfileModal()" class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 py-2 px-4 rounded-md transition-colors">
取消
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 后台登录信息弹窗 -->
<div id="loginInfoModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden transform transition-all animate-fade-in">
<div class="bg-primary text-white p-4 flex justify-between items-center">
<h3 class="text-lg font-medium">后台登录信息</h3>
<button onclick="closeLoginInfoModal()" class="text-white hover:text-gray-200 transition-colors">
<i class="fa fa-times"></i>
</button>
</div>
<div class="p-6">
<!-- 错误信息显示区域 -->
<div id="loginInfoError" class="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-exclamation-circle mt-1 mr-3"></i>
<span id="errorLoginInfoMessage"></span>
</div>
<!-- 成功信息显示区域 -->
<div id="loginInfoSuccess" class="mb-4 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md flex items-start hidden">
<i class="fa fa-check-circle mt-1 mr-3"></i>
<span id="successLoginInfoMessage"></span>
</div>
<form id="loginInfoForm">
<div class="bg-gray-50 p-4 rounded-md">
<div class="grid grid-cols-1 gap-2 text-sm">
<div class="text-gray-500 mb-1">登录用户名:</div>
<input type="text" id="loginUsername" value="{{ session.username }}"
{% if session.username != 'admin' %}readonly{% endif %}
class="w-full px-3 py-2 border rounded-md text-gray-800 input-focus"
{% if session.username != 'admin' %}style="background-color: #f3f4f6; cursor: not-allowed;"{% endif %}>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-md">
<div class="grid grid-cols-1 gap-2 text-sm">
<div class="text-gray-500 mb-1">用户密码:</div>
<input type="password" id="loginPassword" value="" placeholder="{% if session.username == 'admin' %}输入新密码或保持为空不修改{% else %}只读,无法修改{% endif %}"
{% if session.username != 'admin' %}readonly{% endif %}
class="w-full px-3 py-2 border rounded-md text-gray-800 input-focus"
{% if session.username != 'admin' %}style="background-color: #f3f4f6; cursor: not-allowed;"{% endif %}>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-md">
<div class="grid grid-cols-1 gap-2 text-sm">
<div class="text-gray-500 mb-1">OTP码:</div>
<input type="text" id="loginOtpCode" value="{{ session.otp_code if 'otp_code' in session else '' }}"
class="w-full px-3 py-2 border rounded-md text-gray-800 input-focus">
</div>
</div>
</form>
<div class="mt-6 flex space-x-4">
<button type="button" onclick="saveLoginInfo()" class="flex-1 bg-primary hover:bg-primary/90 text-white py-2 px-4 rounded-md transition-colors shadow-sm hover:shadow">
保存修改
</button>
<button type="button" onclick="closeLoginInfoModal()" class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 py-2 px-4 rounded-md transition-colors">
取消
</button>
</div>
</div>
</div>
</div>
<!-- 用户管理相关JavaScript --> <!-- 用户管理相关JavaScript -->
<script> <script>
// 当前页码和每页显示数量 // 当前页码和每页显示数量
@@ -454,18 +257,6 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
loadUsers(); loadUsers();
// 绑定修改密码表单提交事件
document.getElementById('changePasswordForm').addEventListener('submit', function(e) {
e.preventDefault();
changePassword();
});
// 绑定个人信息表单提交事件
document.getElementById('userProfileForm').addEventListener('submit', function(e) {
e.preventDefault();
updateUserProfile();
});
// 绑定搜索按钮点击事件 // 绑定搜索按钮点击事件
document.getElementById('searchUsername').addEventListener('keypress', function(e) { document.getElementById('searchUsername').addEventListener('keypress', function(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
@@ -487,24 +278,27 @@
renderUsersTable(); renderUsersTable();
} }
}); });
// 绑定模态框点击外部关闭事件
bindModalOutsideClick('editDepartmentModal');
bindModalOutsideClick('resetPasswordModal');
}); });
// 加载用户列表 // 加载用户列表
function loadUsers() { function loadUsers() {
fetch('{{ url_for('user.get_users') }}') apiRequest('{{ url_for('user.get_users') }}')
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
usersData = data.users; usersData = data.users;
totalUsers = usersData.length; totalUsers = usersData.length;
renderUsersTable(); renderUsersTable();
} else { } else {
showError('加载用户列表失败:' + (data.error || '未知错误')); showMessage('加载用户列表失败:' + (data.error || '未知错误'), false);
} }
}) })
.catch(error => { .catch(error => {
console.error('加载用户列表时发生错误:', error); console.error('加载用户列表时发生错误:', error);
showError('网络错误,请稍后重试'); showMessage('网络错误,请稍后重试', false);
}); });
} }
@@ -599,12 +393,12 @@
currentPage = 1; // 重置到第一页 currentPage = 1; // 重置到第一页
renderUsersTable(); renderUsersTable();
} else { } else {
showError('搜索用户失败:' + (data.error || '未知错误')); showMessage('搜索用户失败:' + (data.error || '未知错误'), false);
} }
}) })
.catch(error => { .catch(error => {
console.error('搜索用户时发生错误:', error); console.error('搜索用户时发生错误:', error);
showError('网络错误,请稍后重试'); showMessage('网络错误,请稍后重试', false);
}); });
} }
@@ -614,28 +408,25 @@
const confirmMessage = `确定要${newStatus === 'active' ? '启用' : '禁用'}用户 ${username} 吗?`; const confirmMessage = `确定要${newStatus === 'active' ? '启用' : '禁用'}用户 ${username} 吗?`;
if (confirm(confirmMessage)) { if (confirm(confirmMessage)) {
fetch('{{ url_for('user.update_user_status') }}', { apiRequest('{{ url_for('user.update_user_status') }}', 'POST', {
method: 'POST', user_id: userId,
status: newStatus
}, {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, }
body: JSON.stringify({
user_id: userId,
status: newStatus
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
showSuccess(data.message || '用户状态更新成功'); showMessage(data.message || '用户状态更新成功', true);
loadUsers(); // 重新加载用户列表 loadUsers(); // 重新加载用户列表
} else { } else {
showError(data.error || '用户状态更新失败'); showMessage(data.error || '用户状态更新失败', false);
} }
}) })
.catch(error => { .catch(error => {
console.error('更新用户状态时发生错误:', error); console.error('更新用户状态时发生错误:', error);
showError('网络错误,请稍后重试'); showMessage('网络错误,请稍后重试', false);
}); });
} }
} }
@@ -656,7 +447,7 @@
} }
} }
document.getElementById('editDepartmentModal').classList.remove('hidden'); showModal('editDepartmentModal');
} }
// 关闭修改部门弹窗 // 关闭修改部门弹窗
@@ -682,18 +473,18 @@
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
showSuccess(data.message || '部门信息更新成功'); showMessage(data.message || '部门信息更新成功', true);
closeEditDepartmentModal(); closeModal('editDepartmentModal');
loadUsers(); // 重新加载用户列表 loadUsers(); // 重新加载用户列表
} else { } else {
showError(data.error || '部门信息更新失败'); showMessage(data.error || '部门信息更新失败', false);
} }
}) })
.catch(error => { .catch(error => {
console.error('更新部门信息时发生错误:', error); console.error('更新部门信息时发生错误:', error);
showError('网络错误,请稍后重试'); showMessage('网络错误,请稍后重试', false);
}); });
} }
// 显示初始化密码弹窗 // 显示初始化密码弹窗
@@ -727,379 +518,24 @@
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) {
showSuccess(data.message || '密码初始化成功');
closeResetPasswordModal();
} else {
showError(data.error || '密码初始化失败');
}
})
.catch(error => {
console.error('初始化密码时发生错误:', error);
showError('网络错误,请稍后重试');
});
}
// 显示成功消息
function showSuccess(message) {
const messageContainer = document.getElementById('messageContainer');
const successMessage = document.getElementById('successMessage');
const errorMessage = document.getElementById('errorMessage');
successMessage.querySelector('span').textContent = message;
successMessage.classList.remove('hidden');
errorMessage.classList.add('hidden');
messageContainer.classList.remove('hidden');
// 5秒后自动隐藏
setTimeout(() => {
successMessage.classList.add('hidden');
messageContainer.classList.add('hidden');
}, 5000);
}
// 显示错误消息
function showError(message) {
const messageContainer = document.getElementById('messageContainer');
const successMessage = document.getElementById('successMessage');
const errorMessage = document.getElementById('errorMessage');
errorMessage.querySelector('span').textContent = message;
errorMessage.classList.remove('hidden');
successMessage.classList.add('hidden');
messageContainer.classList.remove('hidden');
// 5秒后自动隐藏
setTimeout(() => {
errorMessage.classList.add('hidden');
messageContainer.classList.add('hidden');
}, 5000);
}
// 修改密码相关函数
function showChangePasswordModal() {
document.getElementById('changePasswordModal').classList.remove('hidden');
document.getElementById('old_password').focus();
}
function closeChangePasswordModal() {
document.getElementById('changePasswordModal').classList.add('hidden');
// 重置表单
document.getElementById('changePasswordForm').reset();
}
function changePassword() {
// 获取表单数据
const oldPassword = document.getElementById('old_password').value;
const newPassword = document.getElementById('new_password').value;
const confirmPassword = document.getElementById('confirm_password').value;
// 隐藏之前的消息
document.getElementById('changePasswordError').classList.add('hidden');
document.getElementById('changePasswordSuccess').classList.add('hidden');
// 前端验证
if (!oldPassword || !newPassword || !confirmPassword) {
showChangePasswordError('请填写所有必填字段');
return;
}
if (newPassword !== confirmPassword) {
showChangePasswordError('两次输入的新密码不一致');
return;
}
// 异步提交修改
fetch('{{ url_for('user.change_password') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
'old_password': oldPassword,
'new_password': newPassword,
'confirm_password': confirmPassword
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 修改成功
showChangePasswordSuccess(data.message || '修改密码成功,请重新登录');
// 3秒后跳转登录页
setTimeout(() => {
window.location.href = '{{ url_for('user.login') }}';
}, 3000);
} else {
// 显示错误信息
showChangePasswordError(data.error || '修改密码失败');
}
})
.catch(error => {
console.error('修改密码时发生错误:', error);
showChangePasswordError('网络错误,请稍后重试');
});
}
function showChangePasswordError(message) {
const errorContainer = document.getElementById('changePasswordError');
const errorMessage = document.getElementById('errorChangePasswordMessage');
errorMessage.textContent = message;
errorContainer.classList.remove('hidden');
document.getElementById('changePasswordSuccess').classList.add('hidden');
// 滚动到错误信息
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function showChangePasswordSuccess(message) {
const successContainer = document.getElementById('changePasswordSuccess');
const successMessage = document.getElementById('successChangePasswordMessage');
successMessage.textContent = message;
successContainer.classList.remove('hidden');
document.getElementById('changePasswordError').classList.add('hidden');
// 滚动到成功信息
successContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 个人信息相关函数
function showUserProfileModal() {
// 获取个人信息
fetch('{{ url_for('user.get_user_profile') }}')
.then(response => response.json())
.then(data => {
if (data.success) { if (data.success) {
const user = data.user; showMessage(data.message || '密码初始化成功', true);
document.getElementById('profile_username').value = user.username; closeModal('resetPasswordModal');
document.getElementById('profile_name').value = user.name;
document.getElementById('profile_phone').value = user.phone;
document.getElementById('profile_department').value = user.department;
document.getElementById('userProfileModal').classList.remove('hidden');
} else { } else {
// 显示错误信息 showMessage(data.error || '密码初始化失败', false);
alert('获取个人信息失败:' + (data.error || '未知错误'));
} }
}) })
.catch(error => { .catch(error => {
console.error('获取个人信息时发生错误:', error); console.error('初始化密码时发生错误:', error);
alert('网络错误,请稍后重试'); showMessage('网络错误,请稍后重试', false);
}); });
} }
function closeUserProfileModal() {
document.getElementById('userProfileModal').classList.add('hidden');
// 隐藏之前的消息
document.getElementById('userProfileError').classList.add('hidden');
document.getElementById('userProfileSuccess').classList.add('hidden');
}
function updateUserProfile() {
// 获取表单数据
const name = document.getElementById('profile_name').value.trim();
const phone = document.getElementById('profile_phone').value.trim();
// 隐藏之前的消息
document.getElementById('userProfileError').classList.add('hidden');
document.getElementById('userProfileSuccess').classList.add('hidden');
// 前端验证
if (!name || !phone) {
showUserProfileError('请填写所有必填字段');
return;
}
// 异步提交修改
fetch('{{ url_for('user.update_user_profile') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
'name': name,
'phone': phone
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 更新成功
showUserProfileSuccess(data.message || '个人信息更新成功');
// 3秒后关闭弹窗
setTimeout(() => {
closeUserProfileModal();
// 刷新页面以更新会话信息
location.reload();
}, 1500);
} else {
// 显示错误信息
showUserProfileError(data.error || '个人信息更新失败');
}
})
.catch(error => {
console.error('更新个人信息时发生错误:', error);
showUserProfileError('网络错误,请稍后重试');
});
}
function showUserProfileError(message) {
const errorContainer = document.getElementById('userProfileError');
const errorMessage = document.getElementById('errorUserProfileMessage');
errorMessage.textContent = message;
errorContainer.classList.remove('hidden');
document.getElementById('userProfileSuccess').classList.add('hidden');
// 滚动到错误信息
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function showUserProfileSuccess(message) {
const successContainer = document.getElementById('userProfileSuccess');
const successMessage = document.getElementById('successUserProfileMessage');
successMessage.textContent = message;
successContainer.classList.remove('hidden');
document.getElementById('userProfileError').classList.add('hidden');
// 滚动到成功信息
successContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 后台登录信息相关函数
function showLoginInfoModal() {
document.getElementById('loginInfoModal').classList.remove('hidden');
}
function closeLoginInfoModal() {
document.getElementById('loginInfoModal').classList.add('hidden');
// 隐藏之前的消息
document.getElementById('loginInfoError').classList.add('hidden');
document.getElementById('loginInfoSuccess').classList.add('hidden');
}
function saveLoginInfo() {
// 获取表单数据
const username = document.getElementById('loginUsername').value.trim();
const password = document.getElementById('loginPassword').value;
const otpCode = document.getElementById('loginOtpCode').value.trim();
// 隐藏之前的消息
document.getElementById('loginInfoError').classList.add('hidden');
document.getElementById('loginInfoSuccess').classList.add('hidden');
// 前端验证
if (!otpCode) {
showLoginInfoError('OTP码不能为空');
return;
}
if (otpCode.length !== 6) {
showLoginInfoError('OTP码长度必须是6位');
return;
}
// 如果是admin用户修改密码,需要验证密码非空
{% if session.username == 'admin' %}
if (password === '') {
showLoginInfoError('密码不能为空');
return;
}
{% endif %}
// 构建请求数据
const data = {
username: username,
otp_code: otpCode
};
// 如果admin用户提供了新密码,添加到请求数据中
{% if session.username == 'admin' %}
if (password) {
data.password = password;
}
{% endif %}
// 异步提交修改
fetch('{{ url_for('user.update_login_info') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 更新成功
showLoginInfoSuccess(data.message || '登录信息更新成功');
// 更新session中的OTP码
{% if 'session' in globals %}
session['otp_code'] = otpCode;
{% endif %}
// 3秒后关闭弹窗
setTimeout(() => {
closeLoginInfoModal();
}, 1500);
} else {
// 显示服务器返回的错误信息
showLoginInfoError(data.error || '登录信息更新失败');
}
})
.catch(error => {
console.error('更新登录信息时发生错误:', error);
showLoginInfoError('网络错误,请稍后重试');
});
}
function showLoginInfoError(message) {
const errorContainer = document.getElementById('loginInfoError');
const errorMessage = document.getElementById('errorLoginInfoMessage');
errorMessage.textContent = message;
errorContainer.classList.remove('hidden');
document.getElementById('loginInfoSuccess').classList.add('hidden');
// 滚动到错误信息
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function showLoginInfoSuccess(message) {
const successContainer = document.getElementById('loginInfoSuccess');
const successMessage = document.getElementById('successLoginInfoMessage');
successMessage.textContent = message;
successContainer.classList.remove('hidden');
document.getElementById('loginInfoError').classList.add('hidden');
// 滚动到成功信息
successContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 点击弹窗外部关闭弹窗
document.getElementById('editDepartmentModal').addEventListener('click', function(e) {
if (e.target === this) {
closeEditDepartmentModal();
}
});
document.getElementById('resetPasswordModal').addEventListener('click', function(e) {
if (e.target === this) {
closeResetPasswordModal();
}
});
document.getElementById('changePasswordModal').addEventListener('click', function(e) {
if (e.target === this) {
closeChangePasswordModal();
}
});
document.getElementById('userProfileModal').addEventListener('click', function(e) {
if (e.target === this) {
closeUserProfileModal();
}
});
document.getElementById('loginInfoModal').addEventListener('click', function(e) {
if (e.target === this) {
closeLoginInfoModal();
}
});
</script> </script>
</body> </body>
</html> </html>
+253 -122
View File
@@ -23,16 +23,6 @@ def init_user_db():
) )
''') ''')
# 创建登录信息表
cursor.execute('''
CREATE TABLE IF NOT EXISTS login_info (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
otp_code TEXT
)
''')
# 检查是否存在admin用户,如果不存在则创建 # 检查是否存在admin用户,如果不存在则创建
cursor.execute("SELECT * FROM users WHERE username = 'admin'") cursor.execute("SELECT * FROM users WHERE username = 'admin'")
if not cursor.fetchone(): if not cursor.fetchone():
@@ -41,9 +31,6 @@ def init_user_db():
"INSERT INTO users (username, password, name, phone, department) VALUES (?, ?, ?, ?, ?)", "INSERT INTO users (username, password, name, phone, department) VALUES (?, ?, ?, ?, ?)",
('admin', 'admin123', '管理员', '13800138000', '管理部') ('admin', 'admin123', '管理员', '13800138000', '管理部')
) )
# 同时插入到登录信息表
cursor.execute("INSERT INTO login_info (username, password, otp_code) VALUES (?, ?, ?)",
('admin', 'admin123', ''))
# 插入一些测试数据 # 插入一些测试数据
cursor.execute("SELECT COUNT(*) FROM users") cursor.execute("SELECT COUNT(*) FROM users")
@@ -60,8 +47,6 @@ def init_user_db():
"INSERT INTO users (username, password, name, phone, department) VALUES (?, ?, ?, ?, ?)", "INSERT INTO users (username, password, name, phone, department) VALUES (?, ?, ?, ?, ?)",
user user
) )
cursor.execute("INSERT INTO login_info (username, password, otp_code) VALUES (?, ?, ?)",
(user[0], user[1], ''))
conn.commit() conn.commit()
conn.close() conn.close()
@@ -93,19 +78,7 @@ def login():
session['department'] = user[5] # 部门 session['department'] = user[5] # 部门
session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 检查是否有login_info记录,如果没有则创建
cursor.execute("SELECT * FROM login_info WHERE username = ?", (username,))
login_info = cursor.fetchone()
if not login_info:
cursor.execute("INSERT INTO login_info (username, password, otp_code) VALUES (?, ?, ?)",
(username, password, ""))
conn.commit()
# 设置session中的OTP码为空
session['otp_code'] = ""
else:
# 设置session中的OTP码
session['otp_code'] = login_info[3] if login_info[3] else ""
conn.close() conn.close()
@@ -154,9 +127,7 @@ def register():
"INSERT INTO users (username, password, name, phone, department) VALUES (?, ?, ?, ?, ?)", "INSERT INTO users (username, password, name, phone, department) VALUES (?, ?, ?, ?, ?)",
(username, password, name, phone, department) (username, password, name, phone, department)
) )
# 同时插入到登录信息表,不自动生成OTP码
cursor.execute("INSERT INTO login_info (username, password, otp_code) VALUES (?, ?, ?)",
(username, password, ""))
conn.commit() conn.commit()
conn.close() conn.close()
# 注册成功,跳转到登录页并显示成功消息 # 注册成功,跳转到登录页并显示成功消息
@@ -203,8 +174,7 @@ def change_password():
# 更新密码 # 更新密码
cursor.execute("UPDATE users SET password = ? WHERE username = ?", (new_password, session['username'])) cursor.execute("UPDATE users SET password = ? WHERE username = ?", (new_password, session['username']))
# 同时更新login_info表中的密码
cursor.execute("UPDATE login_info SET password = ? WHERE username = ?", (new_password, session['username']))
conn.commit() conn.commit()
conn.close() conn.close()
@@ -283,91 +253,7 @@ def update_user_profile():
conn.close() conn.close()
return jsonify({'success': False, 'error': f'更新失败,请重试。错误:{str(e)}'}) return jsonify({'success': False, 'error': f'更新失败,请重试。错误:{str(e)}'})
# 获取登录信息路由
@bp.route('/get_login_info', methods=['GET'])
def get_login_info():
if 'username' not in session:
return jsonify({'success': False, 'error': '未登录'}), 401
# 获取登录时间 - 如果会话中没有存储登录时间,可以使用当前时间
login_time = session.get('login_time', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
# 获取登录IP地址
login_ip = request.remote_addr
# 构造返回数据
return jsonify({
'success': True,
'login_time': login_time,
'login_ip': login_ip
})
# 登录信息更新路由
@bp.route('/update_login_info', methods=['POST'])
def update_login_info():
import re
if 'username' not in session:
return jsonify({'success': False, 'error': '请先登录'}), 401
data = request.get_json()
username = data.get('username')
password = data.get('password')
otp_code = data.get('otp_code')
# 验证输入
if not username or not otp_code:
return jsonify({'success': False, 'error': '用户名和OTP码不能为空'})
# 验证OTP码长度必须是6位
if len(otp_code) != 6:
return jsonify({'success': False, 'error': 'OTP码长度必须是6位'})
# 确保用户只能更新自己的信息
if username != session['username'] and session['username'] != 'admin':
return jsonify({'success': False, 'error': '无权更新其他用户的信息'})
# 连接数据库
conn = sqlite3.connect('car_info.db')
cursor = conn.cursor()
try:
# 构建更新语句
if session['username'] == 'admin':
# admin用户可以更新所有字段
if password:
# 只验证密码不为空,不限制格式
cursor.execute("UPDATE login_info SET password = ?, otp_code = ? WHERE username = ?",
(password, otp_code, username))
else:
cursor.execute("UPDATE login_info SET otp_code = ? WHERE username = ?", (otp_code, username))
else:
# 普通用户只能更新自己的OTP码
cursor.execute("UPDATE login_info SET otp_code = ? WHERE username = ?", (otp_code, session['username']))
# 检查是否有行被更新
if cursor.rowcount == 0:
# 如果没有找到记录,则插入新记录
if session['username'] == 'admin':
# admin用户可以为任何用户创建登录信息
cursor.execute("INSERT INTO login_info (username, password, otp_code) VALUES (?, ?, ?)",
(username, password or 'default_password', otp_code))
else:
# 普通用户只能创建自己的登录信息
cursor.execute("INSERT INTO login_info (username, password, otp_code) VALUES (?, ?, ?)",
(session['username'], 'default_password', otp_code))
# 提交更改
conn.commit()
# 如果更新的是当前登录用户的信息,更新session中的OTP码
if username == session['username']:
session['otp_code'] = otp_code
conn.close()
return jsonify({'success': True, 'message': '登录信息更新成功'})
except Exception as e:
conn.close()
return jsonify({'success': False, 'error': f'更新失败:{str(e)}'})
# 登出路由 # 登出路由
@bp.route('/logout') @bp.route('/logout')
@@ -535,13 +421,7 @@ def reset_user_password():
# 更新users表中的密码 # 更新users表中的密码
cursor.execute("UPDATE users SET password = ? WHERE id = ?", (default_password, user_id)) cursor.execute("UPDATE users SET password = ? WHERE id = ?", (default_password, user_id))
# 更新login_info表中的密码
cursor.execute("UPDATE login_info SET password = ? WHERE username = ?", (default_password, username))
# 如果login_info表中没有该用户的记录,则插入
if cursor.rowcount == 0:
cursor.execute("INSERT INTO login_info (username, password, otp_code) VALUES (?, ?, ?)",
(username, default_password, ""))
conn.commit() conn.commit()
conn.close() conn.close()
@@ -550,3 +430,254 @@ def reset_user_password():
except Exception as e: except Exception as e:
conn.close() conn.close()
return jsonify({'success': False, 'error': f'密码重置失败:{str(e)}'}) return jsonify({'success': False, 'error': f'密码重置失败:{str(e)}'})
# 创建参数配置表(如果不存在)
def create_parameter_config_table():
conn = sqlite3.connect('car_info.db')
cursor = conn.cursor()
# 创建参数配置表
cursor.execute('''
CREATE TABLE IF NOT EXISTS parameter_config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
param1 TEXT,
param2 TEXT,
param3 TEXT,
param4 TEXT,
param5 TEXT,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# 检查是否存在参数配置记录,如果不存在则插入第一条记录
cursor.execute("SELECT COUNT(*) FROM parameter_config")
count = cursor.fetchone()[0]
if count == 0:
cursor.execute(
"INSERT INTO parameter_config (param1, param2, param3, param4, param5, update_time) VALUES (?, ?, ?, ?, ?, ?)",
('默认参数1', '默认参数2', '默认参数3', '默认参数4', '默认参数5', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
)
conn.commit()
conn.close()
# 获取参数配置路由
@bp.route('/get_parameter_config', methods=['GET'])
def get_parameter_config():
if 'username' not in session:
return jsonify({'success': False, 'error': '未登录'}), 401
try:
conn = sqlite3.connect('car_info.db')
cursor = conn.cursor()
# 查询参数配置
cursor.execute("SELECT param1, param2, param3, param4, param5, update_time FROM parameter_config ORDER BY update_time DESC LIMIT 1")
config = cursor.fetchone()
conn.close()
if not config:
return jsonify({'success': False, 'error': '未找到参数配置'})
# 构造返回数据
parameters = {
'param1': config[0],
'param2': config[1],
'param3': config[2],
'param4': config[3],
'param5': config[4],
'update_time': config[5]
}
return jsonify({'success': True, 'parameters': parameters})
except Exception as e:
return jsonify({'success': False, 'error': f'获取参数配置失败:{str(e)}'})
# 保存参数配置路由
@bp.route('/save_parameter_config', methods=['POST'])
def save_parameter_config():
if 'username' not in session:
return jsonify({'success': False, 'error': '未登录'}), 401
# 只有管理员可以修改参数配置
if session['username'] != 'admin':
return jsonify({'success': False, 'error': '无权限修改参数配置'}), 403
# 获取请求数据
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': '请求数据格式错误'}), 400
param1 = data.get('param1', '').strip()
param2 = data.get('param2', '').strip()
param3 = data.get('param3', '').strip()
param4 = data.get('param4', '').strip()
param5 = data.get('param5', '').strip()
# 验证参数
if not param1:
return jsonify({'success': False, 'error': '参数1不能为空'})
try:
conn = sqlite3.connect('car_info.db')
cursor = conn.cursor()
# 更新参数配置
cursor.execute(
"UPDATE parameter_config SET param1 = ?, param2 = ?, param3 = ?, param4 = ?, param5 = ?, update_time = ? WHERE id = 1",
(param1, param2, param3, param4, param5, datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
)
# 如果没有更新任何记录,插入新记录
if cursor.rowcount == 0:
cursor.execute(
"INSERT INTO parameter_config (param1, param2, param3, param4, param5, update_time) VALUES (?, ?, ?, ?, ?, ?)",
(param1, param2, param3, param4, param5, datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
)
conn.commit()
conn.close()
return jsonify({'success': True, 'message': '参数配置保存成功'})
except Exception as e:
return jsonify({'success': False, 'error': f'保存参数配置失败:{str(e)}'})
# 参数配置页面路由
@bp.route('/parameter_config_page', methods=['GET', 'POST'])
def parameter_config_page():
if 'username' not in session:
return redirect(url_for('user.login'))
# 只有管理员可以访问参数配置页面
if session['username'] != 'admin':
return redirect(url_for('user.user_profile_page', error='无权限访问参数配置页面'))
# 渲染参数配置页面
return render_template('parameter_config.html', current_year=datetime.now().year)
# 修改密码页面路由
@bp.route('/change_password_page', methods=['GET', 'POST'])
def change_password_page():
if 'username' not in session:
return redirect(url_for('user.login'))
if request.method == 'POST':
old_password = request.form.get('old_password')
new_password = request.form.get('new_password')
confirm_password = request.form.get('confirm_password')
# 验证参数
if not old_password or not new_password or not confirm_password:
return render_template('change_password.html', error='请填写所有必填字段')
# 验证新密码和确认密码是否一致
if new_password != confirm_password:
return render_template('change_password.html', error='两次输入的新密码不一致')
# 验证新密码复杂度
if len(new_password) < 8 or not any(c.isalpha() for c in new_password) or not any(c.isdigit() for c in new_password):
return render_template('change_password.html', error='新密码长度至少8位,且必须包含字母和数字')
# 连接数据库,验证旧密码并更新新密码
conn = sqlite3.connect('car_info.db')
cursor = conn.cursor()
# 验证旧密码是否正确
cursor.execute("SELECT password FROM users WHERE username = ?", (session['username'],))
result = cursor.fetchone()
if not result or result[0] != old_password:
conn.close()
return render_template('change_password.html', error='旧密码不正确')
# 更新密码
cursor.execute("UPDATE users SET password = ? WHERE username = ?", (new_password, session['username']))
conn.commit()
conn.close()
# 密码修改成功,先执行退出功能,再跳转到登录页面
session.clear()
return redirect(url_for('user.login', success='修改密码成功,请重新登录'))
# GET请求,渲染修改密码页面
return render_template('change_password.html', current_year=datetime.now().year)
# 个人信息页面路由
@bp.route('/user_profile_page', methods=['GET', 'POST'])
def user_profile_page():
if 'username' not in session:
return redirect(url_for('user.login'))
# 连接数据库,获取用户信息
conn = sqlite3.connect('car_info.db')
cursor = conn.cursor()
cursor.execute("SELECT username, name, phone, department FROM users WHERE username = ?", (session['username'],))
user_tuple = cursor.fetchone()
conn.close()
if not user_tuple:
return redirect(url_for('user.login', error='用户不存在'))
# 将元组转换为字典,以便在模板中使用属性访问
user = {
'username': user_tuple[0],
'name': user_tuple[1],
'phone': user_tuple[2],
'department': user_tuple[3]
}
if request.method == 'POST':
name = request.form.get('name', '').strip()
phone = request.form.get('phone', '').strip()
department = request.form.get('department', '').strip()
# 验证参数
if not name or not phone or not department:
return render_template('user_profile.html', user=user, error='请填写所有必填字段', current_year=datetime.now().year)
# 连接数据库,更新用户信息
conn = sqlite3.connect('car_info.db')
cursor = conn.cursor()
try:
cursor.execute(
"UPDATE users SET name = ?, phone = ?, department = ? WHERE username = ?",
(name, phone, department, session['username'])
)
conn.commit()
# 更新会话中的部门信息
session['department'] = department
session['name'] = name
conn.close()
# 重新获取用户信息
conn = sqlite3.connect('car_info.db')
cursor = conn.cursor()
cursor.execute("SELECT username, name, phone, department FROM users WHERE username = ?", (session['username'],))
updated_user_tuple = cursor.fetchone()
conn.close()
# 将元组转换为字典
updated_user = {
'username': updated_user_tuple[0],
'name': updated_user_tuple[1],
'phone': updated_user_tuple[2],
'department': updated_user_tuple[3]
}
return render_template('user_profile.html', user=updated_user, success='个人信息更新成功', current_year=datetime.now().year)
except Exception as e:
conn.close()
return render_template('user_profile.html', user=user, error=f'更新失败,请重试。错误:{str(e)}', current_year=datetime.now().year)
# GET请求,渲染个人信息页面
return render_template('user_profile.html', user=user, current_year=datetime.now().year)