cleanup: remove preset builder, restore simple preset fields

- Revert preset panel to original cnrs/isrs/cnam/snam simple fields
- Remove applyPresetFilter, addPresetGroup, addPresetCondition,
  resetPreset, populatePresetBuilder, applyFilterPreset functions
- Remove mode='preset' backend code
- Remove root-format support from upload preset (no longer needed)
- Remove preset template buttons from filter action bar
- Keep: value dropdown with top-50, filter groups, tree-format advanced filter

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
TianXuan Developer
2026-07-23 13:24:45 +08:00
parent 6f6581d38d
commit 5423a360ec
2 changed files with 64 additions and 148 deletions
@@ -184,6 +184,11 @@
.sa-group-actions { display: flex; gap: 0.3rem; margin: 0.3rem 0; } .sa-group-actions { display: flex; gap: 0.3rem; margin: 0.3rem 0; }
.sa-group-actions button { font-size: 0.75rem; padding: 0.15rem 0.5rem; } .sa-group-actions button { font-size: 0.75rem; padding: 0.15rem 0.5rem; }
.sa-filter-actions { display: flex; gap: 0.5rem; margin: 0.5rem 0; flex-wrap: wrap; } .sa-filter-actions { display: flex; gap: 0.5rem; margin: 0.5rem 0; flex-wrap: wrap; }
.sa-preset-row { display: flex; gap: 0.5rem; margin: 0.35rem 0; align-items: center; flex-wrap: wrap; }
.sa-preset-row label { font-size: 0.8rem; min-width: 60px; color: #555; }
.sa-preset-row select, .sa-preset-row input {
padding: 0.25rem 0.5rem; border: 1px solid #ccc; border-radius: 4px; font-size: 0.8rem;
}
/* ── Debug panel ───────────────────────────── */ /* ── Debug panel ───────────────────────────── */
.sa-debug-panel { .sa-debug-panel {
@@ -274,18 +279,29 @@
<span style="color:#999;font-size:0.75rem;">展开 ▼</span> <span style="color:#999;font-size:0.75rem;">展开 ▼</span>
</div> </div>
<div class="sa-preset-body" id="saPresetBody"> <div class="sa-preset-body" id="saPresetBody">
<div style="font-size:0.85rem;margin-bottom:0.5rem;">上传后预筛选(与高级筛选格式一致,上传后生效)</div> <div class="sa-preset-row">
<div id="saPresetConditions"> <label>cnrs</label>
<div class="empty-state" style="padding:0.5rem;color:#999;font-size:0.85rem;text-align:center;">上传 CSV 后可用条件构建器设置预筛选</div> <select id="saPresetCnrs">
<option value="">不限</option>
<option value="+">等于 +</option>
<option value="-">等于 -</option>
</select>
<label>isrs</label>
<select id="saPresetIsrs">
<option value="">不限</option>
<option value="+">等于 +</option>
<option value="-">等于 -</option>
</select>
</div> </div>
<div class="sa-group-actions" style="margin-top:0.5rem;"> <div class="sa-preset-row">
<button class="btn" style="font-size:0.78rem;padding:0.2rem 0.5rem;" id="saBtnPresetAddCond" onclick="addPresetCondition()" disabled>+ 条件</button> <label>cnam</label>
<button class="btn" style="font-size:0.78rem;padding:0.2rem 0.5rem;" id="saBtnPresetAddGroup" onclick="addPresetGroup()" disabled>( ) 组</button> <input type="text" id="saPresetCnam" placeholder="留空不限" style="width:160px;">
<button class="btn btn-primary" style="font-size:0.78rem;padding:0.2rem 0.6rem;" id="saBtnApplyPreset" onclick="applyPresetFilter()" disabled>应用预设筛选</button> <label>snam</label>
<input type="text" id="saPresetSnam" placeholder="留空不限" style="width:160px;">
</div> </div>
<div style="font-size:0.75rem;color:#999;margin-top:0.35rem;"> <div style="font-size:0.75rem;color:#999;margin-top:0.35rem;">
条件上传生效,可缩小分析范围 条件上传时即生效,未选中的行直接丢弃
<span style="color:#4361ee;cursor:pointer;margin-left:0.5rem;" onclick="resetPreset()">清除预设</span> <span style="color:#4361ee;cursor:pointer;margin-left:0.5rem;" onclick="localStorage.removeItem('sa_preset');showSuccess('已清除记忆')">清除记忆</span>
</div> </div>
</div> </div>
</div> </div>
@@ -339,9 +355,6 @@
<div class="sa-filter-actions"> <div class="sa-filter-actions">
<button class="btn" style="font-size:0.78rem;padding:0.3rem 0.6rem;" onclick="addFilterCondition()">+ 条件</button> <button class="btn" style="font-size:0.78rem;padding:0.3rem 0.6rem;" onclick="addFilterCondition()">+ 条件</button>
<button class="btn" style="font-size:0.78rem;padding:0.3rem 0.6rem;" onclick="addFilterGroup()">( ) 组</button> <button class="btn" style="font-size:0.78rem;padding:0.3rem 0.6rem;" onclick="addFilterGroup()">( ) 组</button>
<span style="font-size:0.75rem;color:#999;">预设:</span>
<button class="btn" style="font-size:0.72rem;padding:0.2rem 0.4rem;" onclick="applyFilterPreset('cnrs_isrs')">cnrs=+ OR isrs=+</button>
<button class="btn" style="font-size:0.72rem;padding:0.2rem 0.4rem;" onclick="applyFilterPreset('cnam_empty_cnrs_isrs')">cnam=(空) AND (cnrs=+ OR isrs=+)</button>
<select id="saFilterGlobalLogic" style="padding:0.25rem;border:1px solid #ccc;border-radius:4px;font-size:0.8rem;"> <select id="saFilterGlobalLogic" style="padding:0.25rem;border:1px solid #ccc;border-radius:4px;font-size:0.8rem;">
<option value="AND">根逻辑:AND</option> <option value="AND">根逻辑:AND</option>
<option value="OR">根逻辑:OR</option> <option value="OR">根逻辑:OR</option>
@@ -620,10 +633,10 @@ function restorePreset() {
const saved = localStorage.getItem('sa_preset'); const saved = localStorage.getItem('sa_preset');
if (saved) { if (saved) {
const p = JSON.parse(saved); const p = JSON.parse(saved);
const rootEl = document.getElementById('saPresetRoot'); if (p.cnrs) document.getElementById('saPresetCnrs').value = p.cnrs;
if (rootEl && p.root) { if (p.isrs) document.getElementById('saPresetIsrs').value = p.isrs;
rootEl.value = JSON.stringify(p.root, null, 2); if (p.cnam) document.getElementById('saPresetCnam').value = p.cnam;
} if (p.snam) document.getElementById('saPresetSnam').value = p.snam;
} }
} catch(e) {} } catch(e) {}
} }
@@ -746,51 +759,6 @@ function updateFilterLogicTags() {
} }
document.getElementById('saFilterGlobalLogic')?.addEventListener('change', updateFilterLogicTags); document.getElementById('saFilterGlobalLogic')?.addEventListener('change', updateFilterLogicTags);
function applyFilterPreset(val) {
if (!val) return;
const container = document.getElementById('saFilterConditions');
if (val === 'cnrs_isrs') {
// cnrs=+ OR isrs=+
const g = document.createElement('div'); g.className = 'sa-filter-group';
g.innerHTML = '<div class="sa-group-header"><span class="paren">(</span><select class="sa-group-logic" style="padding:0.2rem;border:1px solid #ccc;border-radius:3px;font-size:0.78rem;"><option value="AND">AND</option><option value="OR" selected>OR</option></select></div><div class="sa-group-inner"></div><div class="sa-group-actions"><button class="btn" onclick="addFilterCondition(this.closest(\'.sa-filter-group\').querySelector(\'.sa-group-inner\'))">+ 条件</button></div><div class="sa-group-footer"><span class="paren">)</span></div>';
container.appendChild(g);
const inner = g.querySelector('.sa-group-inner');
addFilterCondition(inner);
const r1 = inner.lastElementChild;
r1.querySelector('.sa-filter-col-select').value = 'cnrs';
r1.querySelector('.sa-filter-op').value = 'eq';
r1.querySelector('.sa-filter-val').value = '+';
addFilterCondition(inner);
const r2 = inner.lastElementChild;
r2.querySelector('.sa-filter-col-select').value = 'isrs';
r2.querySelector('.sa-filter-op').value = 'eq';
r2.querySelector('.sa-filter-val').value = '+';
} else if (val === 'cnam_empty_cnrs_isrs') {
// cnam=(空) AND (cnrs=+ OR isrs=+)
addFilterCondition(container);
const r0 = container.lastElementChild;
r0.querySelector('.sa-filter-col-select').value = 'cnam';
r0.querySelector('.sa-filter-op').value = 'is_empty';
r0.querySelector('.sa-filter-val').value = '';
r0.querySelector('.sa-filter-val').placeholder = '(空)';
const g = document.createElement('div'); g.className = 'sa-filter-group';
g.innerHTML = '<div class="sa-group-header"><span class="paren">(</span><select class="sa-group-logic" style="padding:0.2rem;border:1px solid #ccc;border-radius:3px;font-size:0.78rem;"><option value="AND">AND</option><option value="OR" selected>OR</option></select></div><div class="sa-group-inner"></div><div class="sa-group-actions"><button class="btn" onclick="addFilterCondition(this.closest(\'.sa-filter-group\').querySelector(\'.sa-group-inner\'))">+ 条件</button></div><div class="sa-group-footer"><span class="paren">)</span></div>';
container.appendChild(g);
const inner = g.querySelector('.sa-group-inner');
addFilterCondition(inner);
const r1 = inner.lastElementChild;
r1.querySelector('.sa-filter-col-select').value = 'cnrs';
r1.querySelector('.sa-filter-op').value = 'eq';
r1.querySelector('.sa-filter-val').value = '+';
addFilterCondition(inner);
const r2 = inner.lastElementChild;
r2.querySelector('.sa-filter-col-select').value = 'isrs';
r2.querySelector('.sa-filter-op').value = 'eq';
r2.querySelector('.sa-filter-val').value = '+';
}
updateFilterLogicTags();
}
function wrapRowInGroup(rowEl) { function wrapRowInGroup(rowEl) {
if (!rowEl || !rowEl.parentElement) return; if (!rowEl || !rowEl.parentElement) return;
const parent = rowEl.parentElement; const parent = rowEl.parentElement;
@@ -909,8 +877,22 @@ function uploadFiles(files) {
const formData = new FormData(); const formData = new FormData();
Array.from(files).forEach(f => formData.append('files', f)); Array.from(files).forEach(f => formData.append('files', f));
// Preset filter is now applied post-upload via the condition builder // Collect preset filters (optional - null-safe)
// (no longer sent during upload) try {
const preset = {};
const cnrsEl = document.getElementById('saPresetCnrs');
const isrsEl = document.getElementById('saPresetIsrs');
const cnamEl = document.getElementById('saPresetCnam');
const snamEl = document.getElementById('saPresetSnam');
if (cnrsEl) { const v = cnrsEl.value; if (v) preset.cnrs = v; }
if (isrsEl) { const v = isrsEl.value; if (v) preset.isrs = v; }
if (cnamEl) { const v = cnamEl.value.trim(); if (v) preset.cnam = v; }
if (snamEl) { const v = snamEl.value.trim(); if (v) preset.snam = v; }
if (Object.keys(preset).length) {
formData.append('preset_filters', JSON.stringify(preset));
localStorage.setItem('sa_preset', JSON.stringify(preset));
}
} catch(e) { /* preset filters are optional */ }
setBusy(true, '正在上传和解析CSV...'); setBusy(true, '正在上传和解析CSV...');
fetch('/simple/upload/', { fetch('/simple/upload/', {
@@ -959,63 +941,11 @@ function uploadFiles(files) {
} }
document.getElementById('saUploadResult').style.display = 'block'; document.getElementById('saUploadResult').style.display = 'block';
populatePresetBuilder();
showSuccess(`上传成功!共 ${data.total_rows.toLocaleString()} 条记录`); showSuccess(`上传成功!共 ${data.total_rows.toLocaleString()} 条记录`);
}) })
.catch(err => { hideLoading(); showError('上传失败: ' + err.message); }); .catch(err => { hideLoading(); showError('上传失败: ' + err.message); });
} }
/* ── Preset filter (upload step) ──────────────────────── */
function addPresetCondition() { addFilterCondition(document.getElementById('saPresetConditions')); }
function addPresetGroup() { addFilterGroup(document.getElementById('saPresetConditions')); }
function applyPresetFilter() {
const container = document.getElementById('saPresetConditions');
const items = serializeFilterTree(container);
if (!items.length) { showError('请至少添加一个筛选条件'); return; }
if (SA._loading || !SA.sessionId) return;
setBusy(true, '正在应用预设筛选...');
fetch('/simple/filter/advanced/', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRFToken': getCSRF()},
body: JSON.stringify({
session_id: SA.sessionId,
mode: 'preset',
root: {type: 'group', logic: 'AND', items: items},
}),
})
.then(r => r.json())
.then(data => {
setBusy(false);
if (data.error) { showError(data.error); return; }
setText('saTotalRows', (data.filtered_rows || 0).toLocaleString());
updateDebug({status: '已上传+预设', preset: data.filtered_rows, total: data.total_before_filter});
showSuccess('预设筛选完成,' + data.filtered_rows + ' 条记录');
const anyCol = container.querySelector('.sa-filter-col-select');
if (anyCol && anyCol.value) onFilterColChange(anyCol);
})
.catch(err => { setBusy(false); showError('预设筛选失败: ' + err.message); });
}
function resetPreset() {
const container = document.getElementById('saPresetConditions');
container.innerHTML = '<div class="empty-state" style="padding:0.5rem;color:#999;font-size:0.85rem;text-align:center;">上传 CSV 后可用条件构建器设置预筛选</div>';
document.getElementById('saBtnPresetAddCond').disabled = true;
document.getElementById('saBtnPresetAddGroup').disabled = true;
document.getElementById('saBtnApplyPreset').disabled = true;
localStorage.removeItem('sa_preset');
showSuccess('预设已清除');
}
function populatePresetBuilder() {
const container = document.getElementById('saPresetConditions');
container.innerHTML = '';
document.getElementById('saBtnPresetAddCond').disabled = false;
document.getElementById('saBtnPresetAddGroup').disabled = false;
document.getElementById('saBtnApplyPreset').disabled = false;
addFilterCondition(container);
}
/* ── Step 2: Filter ───────────────────────────────────── */ /* ── Step 2: Filter ───────────────────────────────────── */
function renderPreview(cols, rows) { function renderPreview(cols, rows) {
+19 -33
View File
@@ -287,34 +287,25 @@ def upload_csv(request):
# ── 预设筛选 ──────────────────────────────────────────────────────────── # ── 预设筛选 ────────────────────────────────────────────────────────────
preset_filtered = total_rows preset_filtered = total_rows
try: try:
# 新格式:树状条件 root(与高级筛选格式一致) cond = None
root_filter = preset_filters.get('root') if preset_filters.get('cnrs') in ('+', '-'):
if root_filter: if cnrs_col:
combined_cond = _build_tree_condition(root_filter, df) c = (pl.col(cnrs_col).cast(pl.Utf8).str.strip_chars() == '+')
if combined_cond is not None: if preset_filters['cnrs'] == '-':
df = df.filter(combined_cond) c = ~c
preset_filtered = len(df) cond = c if cond is None else (cond & c)
else: if preset_filters.get('isrs') in ('+', '-'):
# 旧格式:简单字段 AND if isrs_col:
cond = None c = (pl.col(isrs_col).cast(pl.Utf8).str.strip_chars() == '+')
if preset_filters.get('cnrs') in ('+', '-'): if preset_filters['isrs'] == '-':
if cnrs_col: c = ~c
c = (pl.col(cnrs_col).cast(pl.Utf8).str.strip_chars() == '+') cond = c if cond is None else (cond & c)
if preset_filters['cnrs'] == '-': if preset_filters.get('cnam'):
c = ~c if cnam_col:
cond = c if cond is None else (cond & c) c = (pl.col(cnam_col).cast(pl.Utf8) == preset_filters['cnam'])
if preset_filters.get('isrs') in ('+', '-'): cond = c if cond is None else (cond & c)
if isrs_col: if preset_filters.get('snam'):
c = (pl.col(isrs_col).cast(pl.Utf8).str.strip_chars() == '+') if snam_col:
if preset_filters['isrs'] == '-':
c = ~c
cond = c if cond is None else (cond & c)
if preset_filters.get('cnam'):
if cnam_col:
c = (pl.col(cnam_col).cast(pl.Utf8) == preset_filters['cnam'])
cond = c if cond is None else (cond & c)
if preset_filters.get('snam'):
if snam_col:
c = (pl.col(snam_col).cast(pl.Utf8) == preset_filters['snam']) c = (pl.col(snam_col).cast(pl.Utf8) == preset_filters['snam'])
cond = c if cond is None else (cond & c) cond = c if cond is None else (cond & c)
if cond is not None: if cond is not None:
@@ -551,11 +542,6 @@ def advanced_filter(request):
return JsonResponse({'error': f'筛选构建失败: {e}'}, status=400) return JsonResponse({'error': f'筛选构建失败: {e}'}, status=400)
filtered_rows = len(filtered) filtered_rows = len(filtered)
mode = body.get('mode', '')
if mode == 'preset':
# 预设模式:覆写原始 df,后续步骤基于预设筛选后的数据
entry['df'] = filtered
entry['meta']['preset_filtered'] = filtered_rows
entry['meta']['filtered_rows'] = filtered_rows entry['meta']['filtered_rows'] = filtered_rows
entry['filtered_df'] = filtered entry['filtered_df'] = filtered