完善老师页面,数据管理增加按key筛查
This commit is contained in:
@@ -60,6 +60,7 @@
|
|||||||
<p><span class="label">用户名:</span> {{ profile_user.username }}</p>
|
<p><span class="label">用户名:</span> {{ profile_user.username }}</p>
|
||||||
<p><span class="label">用户ID:</span> {{ profile_user.user_id }}</p>
|
<p><span class="label">用户ID:</span> {{ profile_user.user_id }}</p>
|
||||||
<p><span class="label">所属:</span> {{ profile_user.key|join:"、"|default:"未填写" }}</p>
|
<p><span class="label">所属:</span> {{ profile_user.key|join:"、"|default:"未填写" }}</p>
|
||||||
|
<p><span class="label">可管理级别:</span> {{ profile_user.manage_key|join:"、"|default:"无" }}</p>
|
||||||
<p><span class="label">权限级别:</span> {{ permission_name }}</p>
|
<p><span class="label">权限级别:</span> {{ permission_name }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,12 +69,18 @@
|
|||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>数据管理</h2>
|
<h2>数据管理</h2>
|
||||||
|
{% if is_admin %}
|
||||||
<p class="muted">仅管理员可见。可查看、编辑、删除所有记录。</p>
|
<p class="muted">仅管理员可见。可查看、编辑、删除所有记录。</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="muted">可查看本人及所管理 Key 的上传数据。</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- 搜索功能区域 -->
|
<!-- 搜索功能区域 -->
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<div class="search-controls">
|
<div class="search-controls">
|
||||||
<input type="text" id="searchQuery" class="search-input" placeholder="请输入搜索关键词...">
|
<input type="text" id="searchQuery" class="search-input" placeholder="请输入搜索关键词...">
|
||||||
|
<select id="keyFilter" class="search-input"></select>
|
||||||
|
<button class="btn" onclick="clearKeyFilter()">清空Key筛查</button>
|
||||||
<button class="btn btn-primary" onclick="performSearch('exact')">关键词搜索</button>
|
<button class="btn btn-primary" onclick="performSearch('exact')">关键词搜索</button>
|
||||||
<button class="btn btn-secondary" onclick="performSearch('fuzzy')">模糊搜索</button>
|
<button class="btn btn-secondary" onclick="performSearch('fuzzy')">模糊搜索</button>
|
||||||
<button class="btn" onclick="loadAllData()">显示全部</button>
|
<button class="btn" onclick="loadAllData()">显示全部</button>
|
||||||
@@ -146,6 +152,7 @@ function getCookie(name) {
|
|||||||
|
|
||||||
// DOM元素引用
|
// DOM元素引用
|
||||||
const searchQueryInput = document.getElementById('searchQuery');
|
const searchQueryInput = document.getElementById('searchQuery');
|
||||||
|
const keyFilterSelect = document.getElementById('keyFilter');
|
||||||
const searchResultDiv = document.getElementById('searchResult');
|
const searchResultDiv = document.getElementById('searchResult');
|
||||||
const searchStatus = document.getElementById('searchStatus');
|
const searchStatus = document.getElementById('searchStatus');
|
||||||
const searchCount = document.getElementById('searchCount');
|
const searchCount = document.getElementById('searchCount');
|
||||||
@@ -174,6 +181,7 @@ let allDataCache = []; // 缓存所有数据,避免重复请求
|
|||||||
let currentSearchQuery = ''; // 记录当前搜索查询
|
let currentSearchQuery = ''; // 记录当前搜索查询
|
||||||
let isFuzzySearch = false; // 记录当前是否为模糊搜索
|
let isFuzzySearch = false; // 记录当前是否为模糊搜索
|
||||||
let isDeleting = false; // 标记是否正在删除
|
let isDeleting = false; // 标记是否正在删除
|
||||||
|
let currentKeyFilter = '';
|
||||||
|
|
||||||
// 图片缩放相关变量
|
// 图片缩放相关变量
|
||||||
let currentScale = 1;
|
let currentScale = 1;
|
||||||
@@ -193,6 +201,11 @@ async function performSearch(type) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentKeyFilter) {
|
||||||
|
currentKeyFilter = '';
|
||||||
|
if (keyFilterSelect) keyFilterSelect.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
currentSearchQuery = query;
|
currentSearchQuery = query;
|
||||||
isFuzzySearch = type === 'fuzzy';
|
isFuzzySearch = type === 'fuzzy';
|
||||||
showSearchLoading();
|
showSearchLoading();
|
||||||
@@ -257,6 +270,17 @@ async function loadAllData() {
|
|||||||
showSearchLoading();
|
showSearchLoading();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (currentKeyFilter) {
|
||||||
|
const response = await fetch(`/elastic/filter-by-key/?key=${encodeURIComponent(currentKeyFilter)}`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
displayAllData(data.data || [], currentKeyFilter);
|
||||||
|
} else {
|
||||||
|
showSearchMessage(`加载数据失败: ${data.message || '未知错误'}`, 'error');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果已有缓存,直接使用
|
// 如果已有缓存,直接使用
|
||||||
if (allDataCache.length > 0) {
|
if (allDataCache.length > 0) {
|
||||||
displayAllData(allDataCache);
|
displayAllData(allDataCache);
|
||||||
@@ -279,10 +303,10 @@ async function loadAllData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 显示所有数据
|
// 显示所有数据
|
||||||
function displayAllData(data) {
|
function displayAllData(data, key) {
|
||||||
searchResultDiv.style.display = 'block';
|
searchResultDiv.style.display = 'block';
|
||||||
searchResultDiv.className = 'search-result';
|
searchResultDiv.className = 'search-result';
|
||||||
searchStatus.textContent = '显示全部数据';
|
searchStatus.textContent = key ? `按Key筛查:${key}` : '显示全部数据';
|
||||||
searchCount.textContent = `共 ${data.length} 条记录`;
|
searchCount.textContent = `共 ${data.length} 条记录`;
|
||||||
|
|
||||||
renderTable(data);
|
renderTable(data);
|
||||||
@@ -294,7 +318,11 @@ function clearSearch() {
|
|||||||
searchResultDiv.style.display = 'none';
|
searchResultDiv.style.display = 'none';
|
||||||
currentSearchQuery = '';
|
currentSearchQuery = '';
|
||||||
|
|
||||||
// 如果有缓存数据,显示全部
|
if (currentKeyFilter) {
|
||||||
|
loadAllData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (allDataCache.length > 0) {
|
if (allDataCache.length > 0) {
|
||||||
renderTable(allDataCache);
|
renderTable(allDataCache);
|
||||||
} else {
|
} else {
|
||||||
@@ -303,6 +331,34 @@ function clearSearch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function initKeyFilter() {
|
||||||
|
if (!keyFilterSelect) return;
|
||||||
|
keyFilterSelect.innerHTML = '<option value="">全部Key</option>';
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/elastic/keys-for-filter/', { credentials: 'same-origin' });
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.status !== 'success') return;
|
||||||
|
const keys = data.data || [];
|
||||||
|
keys.forEach(k => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = String(k || '');
|
||||||
|
opt.textContent = String(k || '');
|
||||||
|
keyFilterSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
keyFilterSelect.addEventListener('change', () => {
|
||||||
|
currentKeyFilter = (keyFilterSelect.value || '').trim();
|
||||||
|
loadAllData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearKeyFilter() {
|
||||||
|
currentKeyFilter = '';
|
||||||
|
if (keyFilterSelect) keyFilterSelect.value = '';
|
||||||
|
loadAllData();
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染表格
|
// 渲染表格
|
||||||
function renderTable(data) {
|
function renderTable(data) {
|
||||||
tableBody.innerHTML = '';
|
tableBody.innerHTML = '';
|
||||||
@@ -610,6 +666,7 @@ async function doDelete(id){
|
|||||||
|
|
||||||
// 页面加载时自动加载所有数据
|
// 页面加载时自动加载所有数据
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initKeyFilter();
|
||||||
loadAllData();
|
loadAllData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ urlpatterns = [
|
|||||||
path('search/', views.search, name='search'),
|
path('search/', views.search, name='search'),
|
||||||
path('fuzzy-search/', views.fuzzy_search, name='fuzzy_search'),
|
path('fuzzy-search/', views.fuzzy_search, name='fuzzy_search'),
|
||||||
path('all-data/', views.get_all_data, name='get_all_data'),
|
path('all-data/', views.get_all_data, name='get_all_data'),
|
||||||
|
path('filter-by-key/', views.filter_by_key, name='filter_by_key'),
|
||||||
|
path('keys-for-filter/', views.keys_for_filter_view, name='keys_for_filter'),
|
||||||
|
|
||||||
# 用户管理
|
# 用户管理
|
||||||
path('users/', views.get_users, name='get_users'),
|
path('users/', views.get_users, name='get_users'),
|
||||||
|
|||||||
103
elastic/views.py
103
elastic/views.py
@@ -51,6 +51,26 @@ def _filter_results_for_user(request, results):
|
|||||||
|
|
||||||
uid = str(session_user_id)
|
uid = str(session_user_id)
|
||||||
manage_keys = me.get("manage_key", []) or []
|
manage_keys = me.get("manage_key", []) or []
|
||||||
|
manage_keys_set = {str(k).strip() for k in manage_keys if str(k).strip()}
|
||||||
|
writer_keys_by_id = None
|
||||||
|
if manage_keys_set:
|
||||||
|
try:
|
||||||
|
users = get_all_users() or []
|
||||||
|
except Exception:
|
||||||
|
users = []
|
||||||
|
writer_keys_by_id = {}
|
||||||
|
for u in users:
|
||||||
|
try:
|
||||||
|
u_id = str(u.get("user_id", "")).strip()
|
||||||
|
except Exception:
|
||||||
|
u_id = ""
|
||||||
|
if not u_id:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
u_keys = {str(k).strip() for k in (u.get("key") or []) if str(k).strip()}
|
||||||
|
except Exception:
|
||||||
|
u_keys = set()
|
||||||
|
writer_keys_by_id[u_id] = u_keys
|
||||||
|
|
||||||
filtered = []
|
filtered = []
|
||||||
for r in results:
|
for r in results:
|
||||||
@@ -59,11 +79,16 @@ def _filter_results_for_user(request, results):
|
|||||||
filtered.append(r)
|
filtered.append(r)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 2. 管理的提交
|
# 2. 管理的提交:优先按“作者的 key 与我的 manage_key 交集”判断;缺失时回退为 data 字符串包含判断
|
||||||
if manage_keys:
|
if manage_keys_set:
|
||||||
|
writer_id = str(r.get("writer_id", "")).strip()
|
||||||
|
writer_keys = (writer_keys_by_id or {}).get(writer_id)
|
||||||
|
if writer_keys and (writer_keys & manage_keys_set):
|
||||||
|
filtered.append(r)
|
||||||
|
continue
|
||||||
r_data = str(r.get("data", ""))
|
r_data = str(r.get("data", ""))
|
||||||
for mk in manage_keys:
|
for mk in manage_keys_set:
|
||||||
if mk and str(mk) in r_data:
|
if mk and mk in r_data:
|
||||||
filtered.append(r)
|
filtered.append(r)
|
||||||
break
|
break
|
||||||
return filtered
|
return filtered
|
||||||
@@ -221,6 +246,55 @@ def get_all_data(request):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
@require_http_methods(["GET"])
|
||||||
|
def filter_by_key(request):
|
||||||
|
try:
|
||||||
|
session_user_id = request.session.get("user_id")
|
||||||
|
if session_user_id is None:
|
||||||
|
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
|
||||||
|
|
||||||
|
key = (request.GET.get("key") or "").strip()
|
||||||
|
results = search_all()
|
||||||
|
results = _filter_results_for_user(request, results)
|
||||||
|
if not key:
|
||||||
|
data = _attach_writer_names(_attach_image_urls(request, results))
|
||||||
|
return JsonResponse({"status": "success", "data": data})
|
||||||
|
|
||||||
|
selected = str(key).strip()
|
||||||
|
try:
|
||||||
|
users = get_all_users() or []
|
||||||
|
except Exception:
|
||||||
|
users = []
|
||||||
|
writer_keys_by_id = {}
|
||||||
|
for u in users:
|
||||||
|
try:
|
||||||
|
u_id = str(u.get("user_id", "")).strip()
|
||||||
|
except Exception:
|
||||||
|
u_id = ""
|
||||||
|
if not u_id:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
u_keys = {str(k).strip() for k in (u.get("key") or []) if str(k).strip()}
|
||||||
|
except Exception:
|
||||||
|
u_keys = set()
|
||||||
|
writer_keys_by_id[u_id] = u_keys
|
||||||
|
|
||||||
|
filtered = []
|
||||||
|
for r in results:
|
||||||
|
writer_id = str(r.get("writer_id", "")).strip()
|
||||||
|
writer_keys = writer_keys_by_id.get(writer_id)
|
||||||
|
if writer_keys and selected in writer_keys:
|
||||||
|
filtered.append(r)
|
||||||
|
continue
|
||||||
|
if selected and selected in str(r.get("data", "")):
|
||||||
|
filtered.append(r)
|
||||||
|
|
||||||
|
data = _attach_writer_names(_attach_image_urls(request, filtered))
|
||||||
|
return JsonResponse({"status": "success", "data": data})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||||
|
|
||||||
@require_http_methods(["DELETE"])
|
@require_http_methods(["DELETE"])
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def delete_data(request, doc_id):
|
def delete_data(request, doc_id):
|
||||||
@@ -1048,6 +1122,27 @@ def list_registration_codes_view(request):
|
|||||||
data = list_registration_codes()
|
data = list_registration_codes()
|
||||||
return JsonResponse({"status": "success", "data": data})
|
return JsonResponse({"status": "success", "data": data})
|
||||||
|
|
||||||
|
|
||||||
|
@require_http_methods(["GET"])
|
||||||
|
def keys_for_filter_view(request):
|
||||||
|
uid = request.session.get("user_id")
|
||||||
|
if uid is None:
|
||||||
|
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
|
||||||
|
is_admin = int(request.session.get("permission", 1)) == 0
|
||||||
|
if is_admin:
|
||||||
|
lst = get_keys_list()
|
||||||
|
return JsonResponse({"status": "success", "data": lst})
|
||||||
|
me = get_user_by_id(uid) or {}
|
||||||
|
seen = set()
|
||||||
|
out = []
|
||||||
|
for v in list(me.get("manage_key") or []) + list(me.get("key") or []):
|
||||||
|
s = str(v).strip()
|
||||||
|
if not s or s in seen:
|
||||||
|
continue
|
||||||
|
seen.add(s)
|
||||||
|
out.append(s)
|
||||||
|
return JsonResponse({"status": "success", "data": out})
|
||||||
|
|
||||||
@require_http_methods(["POST"])
|
@require_http_methods(["POST"])
|
||||||
@csrf_protect
|
@csrf_protect
|
||||||
def revoke_registration_code_view(request):
|
def revoke_registration_code_view(request):
|
||||||
|
|||||||
@@ -41,8 +41,10 @@
|
|||||||
<div class="navigation-links">
|
<div class="navigation-links">
|
||||||
<a href="{% url 'main:home' %}" onclick="return handleNavClick(this, '/');">主页</a>
|
<a href="{% url 'main:home' %}" onclick="return handleNavClick(this, '/');">主页</a>
|
||||||
<a href="{% url 'elastic:upload_page' %}" onclick="return handleNavClick(this, '/elastic/upload/');">图片上传与识别</a>
|
<a href="{% url 'elastic:upload_page' %}" onclick="return handleNavClick(this, '/elastic/upload/');">图片上传与识别</a>
|
||||||
{% if is_admin %}
|
{% if is_admin or has_manage_key %}
|
||||||
<a href="{% url 'elastic:manage_page' %}" onclick="return handleNavClick(this, '/elastic/manage/');">数据管理</a>
|
<a href="{% url 'elastic:manage_page' %}" onclick="return handleNavClick(this, '/elastic/manage/');">数据管理</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_admin %}
|
||||||
<a href="{% url 'elastic:user_manage' %}" onclick="return handleNavClick(this, '/elastic/user_manage/');">用户管理</a>
|
<a href="{% url 'elastic:user_manage' %}" onclick="return handleNavClick(this, '/elastic/user_manage/');">用户管理</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/accounts/profile/">个人中心</a>
|
<a href="/accounts/profile/">个人中心</a>
|
||||||
@@ -306,4 +308,4 @@
|
|||||||
loadRecent();
|
loadRecent();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ def home(request):
|
|||||||
if session_user_id is None:
|
if session_user_id is None:
|
||||||
return redirect("/accounts/login/")
|
return redirect("/accounts/login/")
|
||||||
|
|
||||||
# Show user_id (prefer query param if present, but don't trust it)
|
uid = session_user_id
|
||||||
user_id_qs = request.GET.get("user_id")
|
|
||||||
uid = user_id_qs or session_user_id
|
|
||||||
perm = request.session.get("permission")
|
perm = request.session.get("permission")
|
||||||
u = get_user_by_id(uid) if uid is not None else None
|
u = get_user_by_id(uid) if uid is not None else None
|
||||||
if perm is None and uid is not None:
|
if perm is None and uid is not None:
|
||||||
@@ -26,9 +24,11 @@ def home(request):
|
|||||||
perm = int(perm)
|
perm = int(perm)
|
||||||
except Exception:
|
except Exception:
|
||||||
perm = 1
|
perm = 1
|
||||||
|
has_manage_key = bool((u or {}).get("manage_key") or [])
|
||||||
context = {
|
context = {
|
||||||
"user_id": uid,
|
"user_id": uid,
|
||||||
"username": (u or {}).get("username"),
|
"username": (u or {}).get("username"),
|
||||||
"is_admin": (int(perm) == 0),
|
"is_admin": (int(perm) == 0),
|
||||||
|
"has_manage_key": has_manage_key,
|
||||||
}
|
}
|
||||||
return render(request, "main/home.html", context)
|
return render(request, "main/home.html", context)
|
||||||
|
|||||||
Reference in New Issue
Block a user