Compare commits

...

2 Commits

Author SHA1 Message Date
sairate 6fffcceb24 Signed-off-by: sairate <sairate@sina.cn> 2024-08-28 13:53:09 +08:00
sairate 3d188b2f30 Signed-off-by: sairate <sairate@sina.cn> 2024-08-28 13:53:05 +08:00
17 changed files with 397 additions and 43 deletions

View File

@ -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 &lt;sairate@sina.cn&gt;"> <list default="true" id="c512d54a-7f5f-4cfb-af24-29d5821a33bf" name="更改" comment="Signed-off-by: sairate &lt;sairate@sina.cn&gt;">
<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 &lt;sairate@sina.cn&gt;" /> <option name="LAST_COMMIT_MESSAGE" value="Signed-off-by: sairate &lt;sairate@sina.cn&gt;" />
</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
View File

@ -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():

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

View File

@ -1 +0,0 @@
ÀîËÄ,¾ÓÃñ,db_image\test1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

View File

@ -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>

232
templates/info_person.html Normal file
View File

@ -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>