数据管理

+ {% 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 @@