Signed-off-by: sairate <sairate@sina.cn>
|
@ -5,8 +5,21 @@
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="c512d54a-7f5f-4cfb-af24-29d5821a33bf" name="更改" comment="Signed-off-by: sairate <sairate@sina.cn>">
|
<list default="true" id="c512d54a-7f5f-4cfb-af24-29d5821a33bf" name="更改" comment="Signed-off-by: sairate <sairate@sina.cn>">
|
||||||
|
<change afterPath="$PROJECT_DIR$/templates/info_person.html" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/scanf_face.py" beforeDir="false" afterPath="$PROJECT_DIR$/scanf_face.py" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/app.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_1.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_1.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_10.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_10.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_2.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_2.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_3.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_3.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_4.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_4.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_5.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_5.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_6.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_6.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_7.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_7.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_8.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_8.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/captured_faces/face_9.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_9.jpg" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/face_database.db" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/match_log.txt" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/templates/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/index.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/templates/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/index.html" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
@ -17,8 +30,8 @@
|
||||||
<component name="FileTemplateManagerImpl">
|
<component name="FileTemplateManagerImpl">
|
||||||
<option name="RECENT_TEMPLATES">
|
<option name="RECENT_TEMPLATES">
|
||||||
<list>
|
<list>
|
||||||
<option value="HTML File" />
|
|
||||||
<option value="Python Script" />
|
<option value="Python Script" />
|
||||||
|
<option value="HTML File" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
@ -171,9 +184,9 @@
|
||||||
<recent_temporary>
|
<recent_temporary>
|
||||||
<list>
|
<list>
|
||||||
<item itemvalue="Python.app" />
|
<item itemvalue="Python.app" />
|
||||||
|
<item itemvalue="Python.run_programs" />
|
||||||
<item itemvalue="JavaScript 调试.index.html" />
|
<item itemvalue="JavaScript 调试.index.html" />
|
||||||
<item itemvalue="Python.scanf_face" />
|
<item itemvalue="Python.scanf_face" />
|
||||||
<item itemvalue="Python.run_programs" />
|
|
||||||
<item itemvalue="Python.match_face" />
|
<item itemvalue="Python.match_face" />
|
||||||
</list>
|
</list>
|
||||||
</recent_temporary>
|
</recent_temporary>
|
||||||
|
@ -218,7 +231,13 @@
|
||||||
<workItem from="1724575613580" duration="168000" />
|
<workItem from="1724575613580" duration="168000" />
|
||||||
<workItem from="1724718988706" duration="21000" />
|
<workItem from="1724718988706" duration="21000" />
|
||||||
<workItem from="1724750178311" duration="717000" />
|
<workItem from="1724750178311" duration="717000" />
|
||||||
<workItem from="1724805879844" duration="2788000" />
|
<workItem from="1724805879844" duration="2896000" />
|
||||||
|
<workItem from="1724814757252" duration="135000" />
|
||||||
|
<workItem from="1724815878356" duration="1035000" />
|
||||||
|
<workItem from="1724817321005" duration="346000" />
|
||||||
|
<workItem from="1724820717241" duration="895000" />
|
||||||
|
<workItem from="1724821991134" duration="19000" />
|
||||||
|
<workItem from="1724822043361" duration="2284000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="sairate">
|
<task id="LOCAL-00001" summary="sairate">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
|
@ -273,10 +292,10 @@
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="Signed-off-by: sairate <sairate@sina.cn>" />
|
<option name="LAST_COMMIT_MESSAGE" value="Signed-off-by: sairate <sairate@sina.cn>" />
|
||||||
</component>
|
</component>
|
||||||
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||||
<SUITE FILE_PATH="coverage/face$run_programs.coverage" NAME="run_programs 覆盖结果" MODIFIED="1724750462326" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
<SUITE FILE_PATH="coverage/face$run_programs.coverage" NAME="run_programs 覆盖结果" MODIFIED="1724824221428" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
<SUITE FILE_PATH="coverage/face$sqlite.coverage" NAME="sqlite 覆盖结果" MODIFIED="1724310603865" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
<SUITE FILE_PATH="coverage/face$sqlite.coverage" NAME="sqlite 覆盖结果" MODIFIED="1724310603865" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
<SUITE FILE_PATH="coverage/face$match_face.coverage" NAME="match_face 覆盖结果" MODIFIED="1724314395402" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
<SUITE FILE_PATH="coverage/face$match_face.coverage" NAME="match_face 覆盖结果" MODIFIED="1724314395402" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
<SUITE FILE_PATH="coverage/face$app.coverage" NAME="app 覆盖结果" MODIFIED="1724807921894" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
<SUITE FILE_PATH="coverage/face$app.coverage" NAME="app 覆盖结果" MODIFIED="1724824238878" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
<SUITE FILE_PATH="coverage/face$scanf_face.coverage" NAME="scanf_face 覆盖结果" MODIFIED="1724807152252" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
<SUITE FILE_PATH="coverage/face$scanf_face.coverage" NAME="scanf_face 覆盖结果" MODIFIED="1724807152252" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
<SUITE FILE_PATH="coverage/face$add_face.coverage" NAME="add_face 覆盖结果" MODIFIED="1724311005030" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
<SUITE FILE_PATH="coverage/face$add_face.coverage" NAME="add_face 覆盖结果" MODIFIED="1724311005030" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
|
|
82
app.py
|
@ -1,16 +1,19 @@
|
||||||
from flask import Flask, render_template
|
from flask import Flask, render_template, request, redirect, url_for
|
||||||
from flask_socketio import SocketIO, emit
|
from flask_socketio import SocketIO, emit
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import os
|
||||||
import eventlet
|
import eventlet
|
||||||
|
import face_recognition
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.config['UPLOAD_FOLDER'] = './static/db_image' # 设置文件上传路径
|
||||||
socketio = SocketIO(app, async_mode='eventlet')
|
socketio = SocketIO(app, async_mode='eventlet')
|
||||||
|
|
||||||
# 从数据库中获取匹配日志记录
|
# 从数据库中获取匹配日志记录
|
||||||
def get_match_logs(db_name="face_database.db"):
|
def get_match_logs(db_name="face_database.db"):
|
||||||
conn = sqlite3.connect(db_name)
|
conn = sqlite3.connect(db_name)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
c.execute("SELECT name, identity,image_path,match_time FROM match_logs") # 去掉了 image_path
|
c.execute("SELECT name, identity, image_path, match_time FROM match_logs")
|
||||||
logs = c.fetchall()
|
logs = c.fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
return logs
|
return logs
|
||||||
|
@ -21,6 +24,81 @@ def index():
|
||||||
logs = get_match_logs()
|
logs = get_match_logs()
|
||||||
return render_template('index.html', logs=logs)
|
return render_template('index.html', logs=logs)
|
||||||
|
|
||||||
|
# 人员信息页面,展示人员信息并进行CRUD操作
|
||||||
|
@app.route('/info_person', methods=['GET', 'POST'])
|
||||||
|
def info_person():
|
||||||
|
conn = sqlite3.connect('face_database.db')
|
||||||
|
c = conn.cursor()
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
# 添加新人员
|
||||||
|
if 'add' in request.form:
|
||||||
|
name = request.form['name']
|
||||||
|
identity = request.form['identity']
|
||||||
|
image = request.files['image_path']
|
||||||
|
|
||||||
|
# 保存上传的图片并生成编码
|
||||||
|
if image:
|
||||||
|
image_path = os.path.join(app.config['UPLOAD_FOLDER'], image.filename)
|
||||||
|
image.save(image_path)
|
||||||
|
|
||||||
|
# 使用face_recognition生成面部编码
|
||||||
|
loaded_image = face_recognition.load_image_file(image_path)
|
||||||
|
face_encodings = face_recognition.face_encodings(loaded_image)
|
||||||
|
|
||||||
|
if face_encodings:
|
||||||
|
encoding = ','.join(map(str, face_encodings[0])) # 将编码转换为字符串存储
|
||||||
|
image_path = "db_image/"+image.filename # 仅保存文件名以便后续使用
|
||||||
|
else:
|
||||||
|
return "No face detected in the uploaded image."
|
||||||
|
else:
|
||||||
|
image_path = ""
|
||||||
|
encoding = ""
|
||||||
|
|
||||||
|
c.execute("INSERT INTO faces (name, identity, image_path, encoding) VALUES (?, ?, ?, ?)",
|
||||||
|
(name, identity, image_path, encoding))
|
||||||
|
|
||||||
|
# 更新人员信息
|
||||||
|
elif 'update' in request.form:
|
||||||
|
id = request.form['id']
|
||||||
|
name = request.form['name']
|
||||||
|
identity = request.form['identity']
|
||||||
|
image = request.files.get('image_path')
|
||||||
|
|
||||||
|
if image:
|
||||||
|
image_path = os.path.join(app.config['UPLOAD_FOLDER'], image.filename)
|
||||||
|
image.save(image_path)
|
||||||
|
|
||||||
|
# 使用face_recognition生成新的面部编码
|
||||||
|
loaded_image = face_recognition.load_image_file(image_path)
|
||||||
|
face_encodings = face_recognition.face_encodings(loaded_image)
|
||||||
|
|
||||||
|
if face_encodings:
|
||||||
|
encoding = ','.join(map(str, face_encodings[0])) # 将编码转换为字符串存储
|
||||||
|
image_path = image.filename # 仅保存文件名
|
||||||
|
else:
|
||||||
|
return "No face detected in the uploaded image."
|
||||||
|
else:
|
||||||
|
image_path = request.form['current_image_path'] # 使用当前的图片路径
|
||||||
|
encoding = request.form['encoding'] # 使用现有的编码
|
||||||
|
|
||||||
|
c.execute("UPDATE faces SET name=?, identity=?, image_path=?, encoding=? WHERE id=?",
|
||||||
|
(name, identity, image_path, encoding, id))
|
||||||
|
|
||||||
|
# 删除人员信息
|
||||||
|
elif 'delete' in request.form:
|
||||||
|
id = request.form['id']
|
||||||
|
c.execute("DELETE FROM faces WHERE id=?", (id,))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
# 获取所有人员记录
|
||||||
|
c.execute("SELECT * FROM faces")
|
||||||
|
persons = c.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render_template('info_person.html', persons=persons)
|
||||||
|
|
||||||
# 处理 WebSocket 连接
|
# 处理 WebSocket 连接
|
||||||
@socketio.on('connect')
|
@socketio.on('connect')
|
||||||
def handle_connect():
|
def handle_connect():
|
||||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 12 KiB |
BIN
face_database.db
|
@ -1 +0,0 @@
|
||||||
ÀîËÄ,¾ÓÃñ,db_image\test1.jpg
|
|
After Width: | Height: | Size: 352 KiB |
|
@ -1,22 +1,45 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Face Match Logs</title>
|
<title>后台管理系统</title>
|
||||||
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
background-color: #f4f4f4;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
text-align: center;
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
width: 200px;
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding-top: 20px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.sidebar a {
|
||||||
|
display: block;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.sidebar a:hover {
|
||||||
|
background-color: #575757;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
margin-left: 200px;
|
||||||
|
padding: 20px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
color: #333;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 0;
|
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
@ -36,49 +59,52 @@
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
max-width: 100px; /* 照片最大宽度 */
|
max-width: 100px;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="sidebar">
|
||||||
|
<a href="/">进入记录</a>
|
||||||
|
<a href="/info_person">人员信息</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h1>进入记录</h1>
|
||||||
|
<table border="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>姓名</th>
|
||||||
|
<th>身份</th>
|
||||||
|
<th>进入时间</th>
|
||||||
|
<th>照片</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="log-table-body">
|
||||||
|
<!-- 日志行将在 JavaScript 中插入 -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
// 连接到 Socket.IO 服务器
|
|
||||||
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
||||||
|
|
||||||
// 监听 'update' 事件
|
|
||||||
socket.on('update', function(data) {
|
socket.on('update', function(data) {
|
||||||
var tableBody = document.getElementById('log-table-body');
|
var tableBody = document.getElementById('log-table-body');
|
||||||
tableBody.innerHTML = ''; // 清空现有的表格行
|
tableBody.innerHTML = '';
|
||||||
|
|
||||||
// 遍历日志数据并更新表格
|
|
||||||
data.logs.forEach(function(log) {
|
data.logs.forEach(function(log) {
|
||||||
var row = document.createElement('tr');
|
var row = document.createElement('tr');
|
||||||
row.innerHTML = '<td>' + log[0] + '</td>' +
|
row.innerHTML = '<td>' + log[0] + '</td>' +
|
||||||
'<td>' + log[1] + '</td>' +
|
'<td>' + log[1] + '</td>' +
|
||||||
'<td>' + log[3] + '</td>' +
|
'<td>' + log[3] + '</td>' +
|
||||||
'<td><img src="' + '../static/' + log[2] + '" alt="Photo of ' + log[0] + '"></td>';
|
'<td><img src="' + '../static/' + log[2] + '" alt="Photo of ' + log[0] + '"></td>';
|
||||||
// log[4] 假设是照片的路径
|
|
||||||
tableBody.appendChild(row);
|
tableBody.appendChild(row);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>进入记录</h1>
|
|
||||||
<table border="0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>姓名</th>
|
|
||||||
<th>身份</th>
|
|
||||||
<th>进入时间</th>
|
|
||||||
<th>照片</th> <!-- 新增照片列 -->
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="log-table-body">
|
|
||||||
<!-- 日志行将在 JavaScript 中插入 -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>人员信息管理</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
width: 200px;
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding-top: 20px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.sidebar a {
|
||||||
|
display: block;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.sidebar a:hover, .sidebar a.active {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
margin-left: 200px;
|
||||||
|
padding: 20px;
|
||||||
|
width: calc(100% - 200px);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 20px auto;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.form-inline input[type="text"],
|
||||||
|
.form-inline input[type="file"] {
|
||||||
|
width: auto;
|
||||||
|
padding: 5px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.form-inline button {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.form-inline button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
.form-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.form-container h2 {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 0;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
}
|
||||||
|
.form-container .form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.form-container .form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.form-container .form-group input[type="text"],
|
||||||
|
.form-container .form-group input[type="file"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.form-container button {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.form-container button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="sidebar">
|
||||||
|
<a href="/">进入记录</a>
|
||||||
|
<a href="/info_person" class="active">人员信息</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h1>人员信息管理</h1>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>姓名</th>
|
||||||
|
<th>身份</th>
|
||||||
|
<th>照片</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for person in persons %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ person[1] }}</td>
|
||||||
|
<td>{{ person[2] }}</td>
|
||||||
|
<td><img src="../static/{{ person[3] }}" alt="Photo of {{ person[1] }}"></td>
|
||||||
|
<td>
|
||||||
|
<form method="POST" enctype="multipart/form-data" class="form-inline">
|
||||||
|
<input type="hidden" name="id" value="{{ person[0] }}">
|
||||||
|
<input type="text" name="name" value="{{ person[1] }}" required>
|
||||||
|
<input type="text" name="identity" value="{{ person[2] }}" required>
|
||||||
|
<input type="hidden" name="current_image_path" value="{{ person[3] }}">
|
||||||
|
<input type="file" name="image_path" onchange="previewImage(event, {{ person[0] }})">
|
||||||
|
<input type="text" name="encoding" value="{{ person[4] }}" required>
|
||||||
|
<button type="submit" name="update">更新</button>
|
||||||
|
</form>
|
||||||
|
<form method="POST" class="form-inline">
|
||||||
|
<input type="hidden" name="id" value="{{ person[0] }}">
|
||||||
|
<button type="submit" name="delete">删除</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="form-container">
|
||||||
|
<h2>添加新人员</h2>
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">姓名</label>
|
||||||
|
<input type="text" name="name" id="name" placeholder="姓名" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="identity">身份</label>
|
||||||
|
<input type="text" name="identity" id="identity" placeholder="身份" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="image_path">照片</label>
|
||||||
|
<input type="file" name="image_path" id="image_path" onchange="previewNewImage(event)" required>
|
||||||
|
<img id="new-image-preview" src="#" alt="New Image Preview" style="max-width: 100px; height: auto; border-radius: 5px; display: none;">
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="add">添加</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 预览现有记录的图像
|
||||||
|
function previewImage(event, personId) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function(){
|
||||||
|
var output = document.getElementById('image-preview-' + personId);
|
||||||
|
output.src = reader.result;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(event.target.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览添加新人员时的图像
|
||||||
|
function previewNewImage(event) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function(){
|
||||||
|
var output = document.getElementById('new-image-preview');
|
||||||
|
output.src = reader.result;
|
||||||
|
output.style.display = 'block';
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(event.target.files[0]);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|