286 lines
9.9 KiB
JavaScript
286 lines
9.9 KiB
JavaScript
/**
|
||
* 飞书提醒规则管理前端
|
||
*/
|
||
|
||
let eventTypes = [];
|
||
let currentRules = [];
|
||
|
||
//页面加载
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
await loadEventTypes();
|
||
await loadRules();
|
||
});
|
||
|
||
//加载事件类型
|
||
async function loadEventTypes() {
|
||
try {
|
||
const res = await fetch('/api/lark/events');
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
eventTypes = data.events;
|
||
const select = document.getElementById('ruleEvent');
|
||
eventTypes.forEach(e => {
|
||
const opt = document.createElement('option');
|
||
opt.value = e.value;
|
||
opt.text = e.label;
|
||
select.add(opt);
|
||
});
|
||
}
|
||
} catch (e) {
|
||
console.error('Failed to load event types:', e);
|
||
}
|
||
}
|
||
|
||
//加载规则列表
|
||
async function loadRules() {
|
||
const container = document.getElementById('rulesList');
|
||
try {
|
||
const res = await fetch('/api/lark/rules');
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
currentRules = data.rules;
|
||
renderRules();
|
||
} else {
|
||
container.innerHTML = `<div class="p-4 text-red-600">${data.error}</div>`;
|
||
}
|
||
} catch (e) {
|
||
container.innerHTML = `<div class="p-4 text-red-600">加载失败: ${e.message}</div>`;
|
||
}
|
||
}
|
||
|
||
//渲染规则列表
|
||
function renderRules() {
|
||
const container = document.getElementById('rulesList');
|
||
|
||
if (currentRules.length === 0) {
|
||
container.innerHTML = `<div class="p-8 text-center text-gray-400">暂无规则,点击上方按钮新建</div>`;
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = currentRules.map(rule => {
|
||
const eventLabel = eventTypes.find(e => e.value === rule.event)?.label || rule.event;
|
||
const channelLabel = rule.channel === 'group' ? '群聊' : '私聊';
|
||
const statusClass = rule.enabled ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-500';
|
||
const statusLabel = rule.enabled ? '启用' : '禁用';
|
||
const targetInfo = rule.channel === 'private' && rule.target ? `→ ${rule.target}` : '';
|
||
const filterInfo = rule.filterValue ?
|
||
`<span class="mr-3 text-xs bg-yellow-100 text-yellow-800 px-2 py-0.5 rounded">过滤: ${escapeHtml(rule.filterValue)}</span>` : '';
|
||
|
||
return `
|
||
<div class="p-4 flex items-center justify-between hover:bg-gray-50">
|
||
<div class="flex-1">
|
||
<div class="flex items-center gap-2">
|
||
<span class="font-medium text-gray-900">${escapeHtml(rule.name)}</span>
|
||
<span class="text-xs px-2 py-0.5 rounded ${statusClass}">${statusLabel}</span>
|
||
</div>
|
||
<div class="text-sm text-gray-500 mt-1">
|
||
<span class="mr-3">事件: ${eventLabel}</span>
|
||
${filterInfo}
|
||
<span class="mr-3">渠道: ${channelLabel} ${targetInfo}</span>
|
||
${rule.atUsers?.length ? `<span>@ ${rule.atUsers.length}人</span>` : ''}
|
||
</div>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button onclick="editRule('${rule.id}')"
|
||
class="text-blue-600 hover:text-blue-800 text-sm px-2 py-1">编辑</button>
|
||
<button onclick="deleteRule('${rule.id}')"
|
||
class="text-red-600 hover:text-red-800 text-sm px-2 py-1">删除</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
//显示规则编辑窗口
|
||
function showRuleModal(rule = null) {
|
||
document.getElementById('modalTitle').textContent = rule ? '编辑规则' : '新建规则';
|
||
document.getElementById('ruleId').value = rule?.id || '';
|
||
document.getElementById('ruleName').value = rule?.name || '';
|
||
document.getElementById('ruleEvent').value = rule?.event || '';
|
||
document.getElementById('ruleChannel').value = rule?.channel || 'group';
|
||
document.getElementById('targetContact').value = rule?.target || '';
|
||
document.getElementById('atUsers').value = rule?.atUsers?.join(',') || '';
|
||
document.getElementById('ruleEnabled').checked = rule?.enabled !== false;
|
||
document.getElementById('ruleFilterValue').value = rule?.filterValue || '';
|
||
//不需要设置 filterKey,因为它由 event 决定
|
||
|
||
toggleChannelFields();
|
||
handleEventChange(); // 更新过滤字段显示
|
||
hideModalError();
|
||
document.getElementById('ruleModal').classList.remove('hidden');
|
||
}
|
||
|
||
//关闭模态窗口
|
||
function closeRuleModal() {
|
||
document.getElementById('ruleModal').classList.add('hidden');
|
||
}
|
||
|
||
//切换渠道相关字段
|
||
function toggleChannelFields() {
|
||
const channel = document.getElementById('ruleChannel').value;
|
||
document.getElementById('privateFields').classList.toggle('hidden', channel !== 'private');
|
||
document.getElementById('atUsersField').classList.toggle('hidden', channel !== 'group');
|
||
}
|
||
|
||
//处理事件变更,显示/隐藏过滤字段
|
||
function handleEventChange() {
|
||
const event = document.getElementById('ruleEvent').value;
|
||
const container = document.getElementById('filterField');
|
||
const label = document.getElementById('filterLabel');
|
||
const input = document.getElementById('ruleFilterValue');
|
||
const keyInput = document.getElementById('ruleFilterKey');
|
||
const help = document.getElementById('filterHelp');
|
||
|
||
// 默认隐藏
|
||
container.classList.add('hidden');
|
||
keyInput.value = '';
|
||
|
||
if (event === 'issue.assigned') {
|
||
container.classList.remove('hidden');
|
||
label.textContent = '指定被指派人 (用户名)';
|
||
input.placeholder = '例如: zhangsan';
|
||
keyInput.value = 'assignee';
|
||
help.textContent = '留空则通知所有指派事件,填写用户名则仅当指派给该用户时通知';
|
||
} else if (event === 'issue.label_updated') {
|
||
container.classList.remove('hidden');
|
||
label.textContent = '指定标签 (名称)';
|
||
input.placeholder = '例如: bug';
|
||
keyInput.value = 'label';
|
||
help.textContent = '留空则通知所有标签变更,填写标签名则仅当该标签变更时通知';
|
||
}
|
||
}
|
||
|
||
//保存规则
|
||
async function saveRule() {
|
||
const id = document.getElementById('ruleId').value;
|
||
const channel = document.getElementById('ruleChannel').value;
|
||
const filterKey = document.getElementById('ruleFilterKey').value;
|
||
const filterValue = document.getElementById('ruleFilterValue').value.trim();
|
||
|
||
const rule = {
|
||
name: document.getElementById('ruleName').value.trim(),
|
||
event: document.getElementById('ruleEvent').value,
|
||
channel,
|
||
enabled: document.getElementById('ruleEnabled').checked,
|
||
atUsers: [],
|
||
filterKey: filterKey || null,
|
||
filterValue: filterValue || null
|
||
};
|
||
|
||
if (channel === 'private') {
|
||
rule.target = document.getElementById('targetContact').value.trim();
|
||
} else {
|
||
rule.atUsers = document.getElementById('atUsers').value.split(',').map(s => s.trim()).filter(Boolean);
|
||
}
|
||
|
||
if (!rule.name || !rule.event) {
|
||
showModalError('请填写规则名称和选择事件');
|
||
return;
|
||
}
|
||
|
||
if (channel === 'private' && !rule.target) {
|
||
showModalError('请填写接收者手机号或邮箱');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const url = id ? `/api/lark/rules/${id}` : '/api/lark/rules';
|
||
const method = id ? 'PUT' : 'POST';
|
||
|
||
const res = await fetch(url, {
|
||
method,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(rule)
|
||
});
|
||
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
closeRuleModal();
|
||
await loadRules();
|
||
} else {
|
||
showModalError(data.error);
|
||
}
|
||
} catch (e) {
|
||
showModalError('保存失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
//编辑规则
|
||
function editRule(id) {
|
||
const rule = currentRules.find(r => r.id === id);
|
||
if (rule) showRuleModal(rule);
|
||
}
|
||
|
||
//删除规则
|
||
async function deleteRule(id) {
|
||
if (!confirm('确定要删除这条规则吗?')) return;
|
||
|
||
try {
|
||
const res = await fetch(`/api/lark/rules/${id}`, { method: 'DELETE' });
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
await loadRules();
|
||
} else {
|
||
alert('删除失败: ' + data.error);
|
||
}
|
||
} catch (e) {
|
||
alert('删除失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
//测试发送
|
||
async function testSend() {
|
||
const channel = document.getElementById('ruleChannel').value;
|
||
|
||
const payload = {
|
||
channel,
|
||
atUsers: [],
|
||
message: '这是一条测试消息\n来自 Gitea-Jira 同步机器人'
|
||
};
|
||
|
||
if (channel === 'private') {
|
||
payload.target = document.getElementById('targetContact').value.trim();
|
||
if (!payload.target) {
|
||
showModalError('请先填写接收者手机号或邮箱');
|
||
return;
|
||
}
|
||
} else {
|
||
payload.atUsers = document.getElementById('atUsers').value.split(',').map(s => s.trim()).filter(Boolean);
|
||
}
|
||
|
||
try {
|
||
const res = await fetch('/api/lark/test', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
});
|
||
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
alert('测试消息发送成功!请检查飞书。');
|
||
} else {
|
||
showModalError('发送失败: ' + data.error);
|
||
}
|
||
} catch (e) {
|
||
showModalError('发送失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
//显示/隐藏错误
|
||
function showModalError(msg) {
|
||
const el = document.getElementById('modalError');
|
||
el.textContent = msg;
|
||
el.classList.remove('hidden');
|
||
}
|
||
|
||
function hideModalError() {
|
||
document.getElementById('modalError').classList.add('hidden');
|
||
}
|
||
|
||
//HTML 转义
|
||
function escapeHtml(str) {
|
||
const div = document.createElement('div');
|
||
div.textContent = str;
|
||
return div.innerHTML;
|
||
}
|