数据管理
+ {% if is_admin %}仅管理员可见。可查看、编辑、删除所有记录。
+ {% else %} +可查看本人及所管理 Key 的上传数据。
+ {% endif %}
+
+
@@ -146,6 +152,7 @@ function getCookie(name) {
// DOM元素引用
const searchQueryInput = document.getElementById('searchQuery');
+const keyFilterSelect = document.getElementById('keyFilter');
const searchResultDiv = document.getElementById('searchResult');
const searchStatus = document.getElementById('searchStatus');
const searchCount = document.getElementById('searchCount');
@@ -174,6 +181,7 @@ let allDataCache = []; // 缓存所有数据,避免重复请求
let currentSearchQuery = ''; // 记录当前搜索查询
let isFuzzySearch = false; // 记录当前是否为模糊搜索
let isDeleting = false; // 标记是否正在删除
+let currentKeyFilter = '';
// 图片缩放相关变量
let currentScale = 1;
@@ -193,6 +201,11 @@ async function performSearch(type) {
return;
}
+ if (currentKeyFilter) {
+ currentKeyFilter = '';
+ if (keyFilterSelect) keyFilterSelect.value = '';
+ }
+
currentSearchQuery = query;
isFuzzySearch = type === 'fuzzy';
showSearchLoading();
@@ -257,6 +270,17 @@ async function loadAllData() {
showSearchLoading();
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) {
displayAllData(allDataCache);
@@ -279,10 +303,10 @@ async function loadAllData() {
}
// 显示所有数据
-function displayAllData(data) {
+function displayAllData(data, key) {
searchResultDiv.style.display = 'block';
searchResultDiv.className = 'search-result';
- searchStatus.textContent = '显示全部数据';
+ searchStatus.textContent = key ? `按Key筛查:${key}` : '显示全部数据';
searchCount.textContent = `共 ${data.length} 条记录`;
renderTable(data);
@@ -294,7 +318,11 @@ function clearSearch() {
searchResultDiv.style.display = 'none';
currentSearchQuery = '';
- // 如果有缓存数据,显示全部
+ if (currentKeyFilter) {
+ loadAllData();
+ return;
+ }
+
if (allDataCache.length > 0) {
renderTable(allDataCache);
} else {
@@ -303,6 +331,34 @@ function clearSearch() {
}
}
+async function initKeyFilter() {
+ if (!keyFilterSelect) return;
+ keyFilterSelect.innerHTML = '';
+ 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) {
tableBody.innerHTML = '';
@@ -610,6 +666,7 @@ async function doDelete(id){
// 页面加载时自动加载所有数据
document.addEventListener('DOMContentLoaded', function() {
+ initKeyFilter();
loadAllData();
});
diff --git a/elastic/urls.py b/elastic/urls.py
index d89b439..c6a1589 100644
--- a/elastic/urls.py
+++ b/elastic/urls.py
@@ -17,6 +17,8 @@ urlpatterns = [
path('search/', views.search, name='search'),
path('fuzzy-search/', views.fuzzy_search, name='fuzzy_search'),
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'),
diff --git a/elastic/views.py b/elastic/views.py
index 92c45b0..0878efe 100644
--- a/elastic/views.py
+++ b/elastic/views.py
@@ -51,6 +51,26 @@ def _filter_results_for_user(request, results):
uid = str(session_user_id)
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 = []
for r in results:
@@ -59,11 +79,16 @@ def _filter_results_for_user(request, results):
filtered.append(r)
continue
- # 2. 管理的提交
- if manage_keys:
+ # 2. 管理的提交:优先按“作者的 key 与我的 manage_key 交集”判断;缺失时回退为 data 字符串包含判断
+ 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", ""))
- for mk in manage_keys:
- if mk and str(mk) in r_data:
+ for mk in manage_keys_set:
+ if mk and mk in r_data:
filtered.append(r)
break
return filtered
@@ -221,6 +246,55 @@ def get_all_data(request):
except Exception as e:
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"])
@csrf_exempt
def delete_data(request, doc_id):
@@ -1048,6 +1122,27 @@ def list_registration_codes_view(request):
data = list_registration_codes()
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"])
@csrf_protect
def revoke_registration_code_view(request):
diff --git a/main/templates/main/home.html b/main/templates/main/home.html
index 64856da..779fa34 100644
--- a/main/templates/main/home.html
+++ b/main/templates/main/home.html
@@ -41,8 +41,10 @@