whitelist.html
6.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
{% extends "base.html" %}
{% block title %}回复白名单 - WeChat Viewer{% endblock %}
{% block body %}
<style>
body{overflow-y:auto!important;height:auto!important;}
.wl-container{max-width:760px;margin:0 auto;padding:20px;}
.wl-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;}
.wl-header h1{font-size:20px;color:#2c3e50;}
.btn-add{background:#27ae60;color:white;border:none;border-radius:6px;padding:8px 16px;font-size:14px;cursor:pointer;}
.wl-hint{font-size:12px;color:#888;margin:8px 0 16px;line-height:1.5;}
.wl-card{background:white;border-radius:8px;padding:12px 16px;margin-bottom:8px;box-shadow:0 1px 2px rgba(0,0,0,0.05);display:flex;align-items:center;gap:10px;}
.kb{font-size:10px;padding:2px 8px;border-radius:10px;font-weight:600;flex-shrink:0;}
.kb.contact{background:#d6eaf8;color:#1a5276}.kb.group{background:#fdebd0;color:#b9770e}
.wl-name{font-weight:600;font-size:14px;}
.wl-wxid{font-size:11px;color:#999;word-break:break-all;}
.btn-del{margin-left:auto;background:#e74c3c;color:white;border:none;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer;flex-shrink:0;}
.empty-state{text-align:center;padding:50px 20px;color:#999;}
/* modal */
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1000;}
.modal-mask.show{display:flex;align-items:center;justify-content:center;}
.modal{background:white;border-radius:12px;width:92%;max-width:560px;max-height:82vh;display:flex;flex-direction:column;overflow:hidden;}
.modal-hd{padding:14px 18px;border-bottom:1px solid #eee;display:flex;align-items:center;gap:10px;}
.modal-hd h3{font-size:16px;flex:1;}
.modal-hd .x{background:none;border:none;font-size:22px;color:#999;cursor:pointer;}
.modal-search{padding:10px 18px;border-bottom:1px solid #f0f0f0;}
.modal-search input{width:100%;box-sizing:border-box;border:1px solid #cfd8e3;border-radius:8px;padding:9px 12px;font-size:14px;}
.modal-body{flex:1;overflow-y:auto;padding:6px 0;}
.pick-row{display:flex;align-items:center;gap:10px;padding:9px 18px;cursor:pointer;}
.pick-row:hover{background:#f5f8fb;}
.pick-row input{width:17px;height:17px;flex-shrink:0;}
.pick-row .nm{font-size:14px;flex:1;}
.pick-row .kb2{font-size:9px;padding:1px 6px;border-radius:8px;flex-shrink:0;}
.kb2.contact{background:#d6eaf8;color:#1a5276}.kb2.group{background:#fdebd0;color:#b9770e}
.modal-ft{padding:12px 18px;border-top:1px solid #eee;display:flex;align-items:center;gap:10px;}
.modal-ft .cnt{font-size:13px;color:#666;flex:1;}
.btn-confirm{background:#27ae60;color:white;border:none;border-radius:8px;padding:9px 20px;font-size:14px;cursor:pointer;}
.btn-confirm:disabled{opacity:.5;cursor:not-allowed;}
.loading{text-align:center;padding:30px;color:#999;}
</style>
<div class="wl-container">
<div class="wl-header"><h1>⭐ 回复白名单</h1><button class="btn-add" onclick="openPicker()">➕ 添加</button></div>
<div class="wl-hint">白名单里的联系人 / 群,消息可被<b>自动回复</b>(仍受敏感词等安全规则拦截,命中转人工)。不在名单里的一律进「自动回复审批」等你手动发。</div>
<div id="list"></div>
</div>
<!-- 选人弹窗 -->
<div class="modal-mask" id="mask">
<div class="modal">
<div class="modal-hd"><h3>选择联系人 / 群</h3><button class="x" onclick="closePicker()">×</button></div>
<div class="modal-search"><input id="q" placeholder="搜索昵称 / 备注 / 群名..." oninput="onSearch()"></div>
<div class="modal-body" id="picklist"><div class="loading">加载中...</div></div>
<div class="modal-ft"><span class="cnt" id="cnt">已选 0</span>
<button class="btn-confirm" id="btnConfirm" onclick="confirmAdd()" disabled>添加所选</button></div>
</div>
</div>
<script>
function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
var selected={}; // wxid -> {wxid,name,kind}
var searchTimer=null;
function load(){
var el=document.getElementById('list');
fetch('/api/whitelist').then(function(r){return r.json()}).then(function(j){
if(!j.ok){el.innerHTML='<div class="empty-state">加载失败</div>';return;}
if(!j.data.length){el.innerHTML='<div class="empty-state"><p>白名单为空 — 当前所有回复都会进人工审批</p></div>';return;}
var h='';
for(var i=0;i<j.data.length;i++){var t=j.data[i];
h+='<div class="wl-card"><span class="kb '+t.kind+'">'+(t.kind==='group'?'群':'联系人')+'</span>';
h+='<div><div class="wl-name">'+esc(t.name||'(未命名)')+'</div><div class="wl-wxid">'+esc(t.wxid)+'</div></div>';
h+='<button class="btn-del" onclick="del('+t.id+')">删除</button></div>';
}
el.innerHTML=h;
});
}
function del(id){fetch('/api/whitelist/'+id+'/delete',{method:'POST'}).then(function(r){return r.json()}).then(function(j){if(j.ok)load();else alert('失败');});}
function openPicker(){
selected={};updateCnt();
document.getElementById('q').value='';
document.getElementById('mask').classList.add('show');
fetchList('/api/contacts');
}
function closePicker(){document.getElementById('mask').classList.remove('show');}
function onSearch(){
clearTimeout(searchTimer);
var q=document.getElementById('q').value.trim();
searchTimer=setTimeout(function(){
fetchList(q?('/api/contacts/search?q='+encodeURIComponent(q)):'/api/contacts');
},250);
}
function fetchList(url){
var el=document.getElementById('picklist');el.innerHTML='<div class="loading">加载中...</div>';
fetch(url).then(function(r){return r.json()}).then(function(j){
if(!j.ok){el.innerHTML='<div class="loading">加载失败: '+esc(j.error)+'</div>';return;}
if(!j.data.length){el.innerHTML='<div class="loading">无结果</div>';return;}
var h='';
for(var i=0;i<j.data.length;i++){var t=j.data[i];
var ck=selected[t.wxid]?'checked':'';
h+='<label class="pick-row"><input type="checkbox" '+ck+
' onchange="toggle(this,'+esc(JSON.stringify(t)).replace(/"/g,'"')+')">'+
'<span class="nm">'+esc(t.name)+'</span>'+
'<span class="kb2 '+t.kind+'">'+(t.kind==='group'?'群':'联系人')+'</span></label>';
}
el.innerHTML=h;
});
}
function toggle(cb,t){if(cb.checked)selected[t.wxid]=t;else delete selected[t.wxid];updateCnt();}
function updateCnt(){
var n=Object.keys(selected).length;
document.getElementById('cnt').textContent='已选 '+n;
document.getElementById('btnConfirm').disabled=(n===0);
}
function confirmAdd(){
var items=Object.keys(selected).map(function(k){return selected[k];});
if(!items.length)return;
fetch('/api/whitelist',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({items:items})})
.then(function(r){return r.json()}).then(function(j){
if(j.ok){closePicker();load();}else{alert('失败: '+(j.error||''));}
});
}
load();
</script>
{% endblock %}