Files
gitea-jira-task-bot/public/larkReminder.js

286 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 飞书提醒规则管理前端
*/
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;
}