let fetchedData = null;
let extractedSprintId = null;
let extractedSprintName = null;
let sprintMappings = [];
let currentRepoName = null;
let existingMappings = null;
//默认的Gitea标签建议
const defaultLabels = {
'High': 'high', 'Highest': 'highest', 'Medium': 'medium', 'Low': 'low', 'Lowest': 'lowest',
'Bug': 'bug', 'Story': 'story', 'Task': 'task', 'New Feature': 'feature'
};
//为Jira字段添加新的Gitea标签输入框
function addLabel(type, jiraId, jiraName) {
const containerClass = type === 'priority' ? 'priority-labels-container' : 'type-labels-container';
const dataType = type === 'priority' ? 'priority' : 'issuetype';
const removeFunc = type === 'priority' ? 'removePriorityLabelInput' : 'removeTypeLabelInput';
const placeholder = type === 'priority' ? 'urgent' : 'defect';
const container = document.querySelector(`.${containerClass}[data-jira-id="${jiraId}"]`);
if (!container) return;
const newInput = document.createElement('div');
newInput.className = 'flex items-center gap-2';
newInput.innerHTML = `
Gitea标签:
`;
container.appendChild(newInput);
//为新输入框绑定实时更新事件
const input = newInput.querySelector('input');
if (input) {
input.addEventListener('input', updatePreview);
}
updateLabelDeleteButtons(type, jiraId);
updatePreview();
}
//删除标签输入框
function removeLabelInput(type, btn) {
const containerClass = type === 'priority' ? 'priority-labels-container' : 'type-labels-container';
const container = btn.closest(`.${containerClass}`);
const jiraId = container.dataset.jiraId;
btn.closest('.flex').remove();
updateLabelDeleteButtons(type, jiraId);
updatePreview();
}
//更新标签删除按钮的可见性
function updateLabelDeleteButtons(type, jiraId) {
const containerClass = type === 'priority' ? 'priority-labels-container' : 'type-labels-container';
const removeFunc = type === 'priority' ? 'removePriorityLabelInput' : 'removeTypeLabelInput';
const container = document.querySelector(`.${containerClass}[data-jira-id="${jiraId}"]`);
if (!container) return;
const inputs = container.querySelectorAll('.flex');
inputs.forEach(input => {
const deleteBtn = input.querySelector(`button[onclick*="${removeFunc}"]`);
if (deleteBtn) {
if (inputs.length > 1) {
deleteBtn.classList.remove('opacity-0', 'pointer-events-none');
} else {
deleteBtn.classList.add('opacity-0', 'pointer-events-none');
}
}
});
}
//包装函数:保持HTML兼容性
function addPriorityLabel(jiraId, jiraName) { addLabel('priority', jiraId, jiraName); }
function addTypeLabel(jiraId, jiraName) { addLabel('type', jiraId, jiraName); }
function removePriorityLabelInput(btn) { removeLabelInput('priority', btn); }
function removeTypeLabelInput(btn) { removeLabelInput('type', btn); }
function updatePriorityLabelDeleteButtons(jiraId) { updateLabelDeleteButtons('priority', jiraId); }
function updateTypeLabelDeleteButtons(jiraId) { updateLabelDeleteButtons('type', jiraId); }
//页面加载时恢复缓存的配置
window.addEventListener('DOMContentLoaded', () => {
loadGlobalSettings();
});
//加载全局设置
function loadGlobalSettings() {
const savedConfig = localStorage.getItem('jiraGlobalConfig');
if (savedConfig) {
try {
const config = JSON.parse(savedConfig);
if (config.jiraUrl) document.getElementById('settingsJiraUrl').value = config.jiraUrl;
if (config.jiraUser) document.getElementById('settingsJiraUser').value = config.jiraUser;
if (config.jiraToken) document.getElementById('settingsJiraToken').value = config.jiraToken;
} catch (e) {
console.error('Failed to load saved config:', e);
}
}
}
//打开设置窗口
function openSettings() {
loadGlobalSettings();
document.getElementById('settingsModal').classList.remove('hidden');
}
//关闭设置窗口
function closeSettings() {
document.getElementById('settingsModal').classList.add('hidden');
}
//保存全局设置
function saveSettings() {
const config = {
jiraUrl: document.getElementById('settingsJiraUrl').value.trim(),
jiraUser: document.getElementById('settingsJiraUser').value.trim(),
jiraToken: document.getElementById('settingsJiraToken').value.trim()
};
if (!config.jiraUrl) {
alert('请填写Jira地址');
return;
}
localStorage.setItem('jiraGlobalConfig', JSON.stringify(config));
closeSettings();
alert('全局设置已保存');
}
//获取全局设置
function getGlobalSettings() {
const savedConfig = localStorage.getItem('jiraGlobalConfig');
if (savedConfig) {
try {
return JSON.parse(savedConfig);
} catch (e) {
return {};
}
}
return {};
}
//加载现有映射配置
async function loadExistingMappings() {
const btn = document.getElementById('loadBtn');
const errBox = document.getElementById('step0Error');
btn.disabled = true;
btn.innerText = '加载中...';
errBox.classList.add('hidden');
try {
const res = await fetch('/editor/api/mappings');
const data = await res.json();
if (!data.success) {
throw new Error(data.error);
}
existingMappings = data.data;
const repos = Object.keys(existingMappings.repositories || {});
if (repos.length === 0) {
throw new Error('没有找到现有配置,请新建仓库配置');
}
//显示仓库列表
const container = document.getElementById('repoListContainer');
const list = document.getElementById('repoList');
list.innerHTML = repos.map(repo => `
`).join('');
container.classList.remove('hidden');
} catch (e) {
showStep0Error(e.message);
} finally {
btn.disabled = false;
btn.innerText = '加载现有配置';
}
}
//选择要编辑的仓库
async function selectRepo(repoName) {
currentRepoName = repoName;
const config = existingMappings.repositories[repoName];
//预填充基本字段
document.getElementById('currentRepoName').innerText = repoName;
document.getElementById('projectKey').value = config.jira?.projectKey || '';
//预填充 Sprint 映射
if (config.sprints) {
sprintMappings = Object.entries(config.sprints).map(([milestone, sprintId]) => ({
milestone,
sprintId,
sprintName: ''
}));
renderSprintList();
}
//隐藏 Step 0,显示 Step 1
document.getElementById('step0').classList.add('hidden');
document.getElementById('step1').classList.remove('hidden');
document.getElementById('step1').classList.add('step-active');
//如果有项目Key,自动扫描并预填充配置
if (config.jira?.projectKey) {
const settings = getGlobalSettings();
if (settings.jiraUrl && (settings.jiraToken || settings.jiraUser)) {
await scanJiraAndLoadConfig(config);
}
}
}
//新建仓库配置
function createNewMapping() {
//清空之前的状态,避免残留数据
sprintMappings = [];
fetchedData = null;
document.getElementById('repoListContainer').classList.add('hidden');
document.getElementById('newRepoContainer').classList.remove('hidden');
}
//确认新仓库名称
function confirmNewRepo() {
const repoName = document.getElementById('newRepoName').value.trim();
if (!repoName) {
showStep0Error('请输入仓库名称');
return;
}
if (!/^[\w-]+\/[\w-]+$/.test(repoName)) {
showStep0Error('仓库名称格式不正确,应为: owner/repo');
return;
}
//清空之前的状态
sprintMappings = [];
fetchedData = null;
currentRepoName = repoName;
document.getElementById('currentRepoName').innerText = repoName;
//隐藏Step 0,显示Step 1
document.getElementById('step0').classList.add('hidden');
document.getElementById('step1').classList.remove('hidden');
document.getElementById('step1').classList.add('step-active');
}
//显示错误信息
function showErrorMsg(elementId, msg) {
const el = document.getElementById(elementId);
el.innerText = msg;
el.classList.remove('hidden');
}
function showStep0Error(msg) { showErrorMsg('step0Error', msg); }
function backToStart() {
//重置状态
currentRepoName = null;
fetchedData = null;
sprintMappings = [];
//清空UI显示
document.getElementById('sprintListContainer').classList.add('hidden');
document.getElementById('sprintList').innerHTML = '';
//隐藏所有步骤
document.getElementById('step1').classList.add('hidden');
document.getElementById('step2').classList.add('hidden');
document.getElementById('step3').classList.add('hidden');
//显示 Step 0
document.getElementById('step0').classList.remove('hidden');
document.getElementById('step0').classList.add('step-active');
//重置输入框
document.getElementById('repoListContainer').classList.add('hidden');
document.getElementById('newRepoContainer').classList.add('hidden');
document.getElementById('newRepoName').value = '';
}
//删除仓库
async function deleteRepo(repoName) {
if (!confirm(`确定要删除仓库 "${repoName}" 的配置吗?\n\n此操作不可恢复!`)) {
return;
}
try {
const res = await fetch('/editor/api/mappings/' + encodeURIComponent(repoName), {
method: 'DELETE'
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error);
}
alert('仓库配置已删除');
//重新加载仓库列表
await loadExistingMappings();
} catch (e) {
alert('删除失败: ' + e.message);
}
}
//打开改名模态窗口
function openRenameModal(repoName) {
document.getElementById('renameOldName').value = repoName;
document.getElementById('renameNewName').value = repoName;
document.getElementById('renameError').classList.add('hidden');
document.getElementById('renameModal').classList.remove('hidden');
//聚焦到新名称输入框并选中文本
setTimeout(() => {
const input = document.getElementById('renameNewName');
input.focus();
input.select();
}, 100);
}
//关闭改名模态窗口
function closeRenameModal() {
document.getElementById('renameModal').classList.add('hidden');
}
function showRenameError(msg) { showErrorMsg('renameError', msg); }
//确认改名
async function confirmRename() {
const oldName = document.getElementById('renameOldName').value.trim();
const newName = document.getElementById('renameNewName').value.trim();
const btn = document.getElementById('renameConfirmBtn');
//验证
if (!newName) {
showRenameError('请输入新名称');
return;
}
if (newName === oldName) {
showRenameError('新名称与当前名称相同');
return;
}
if (!/^[\w-]+\/[\w-]+$/.test(newName)) {
showRenameError('仓库名称格式不正确,应为: owner/repo');
return;
}
//检查新名称是否已存在
if (existingMappings && existingMappings.repositories && existingMappings.repositories[newName]) {
showRenameError('新名称已存在,请使用其他名称');
return;
}
btn.disabled = true;
btn.innerText = '改名中...';
try {
const res = await fetch('/editor/api/mappings/rename', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
oldName: oldName,
newName: newName
})
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error);
}
alert('仓库已成功改名');
closeRenameModal();
//重新加载仓库列表
await loadExistingMappings();
} catch (e) {
showRenameError('改名失败: ' + e.message);
} finally {
btn.disabled = false;
btn.innerText = '确认改名';
}
}
//scanJiraAndLoadConfig 已合并到 scanJira(existingConfig)
async function scanJiraAndLoadConfig(existingConfig) {
return scanJira(existingConfig);
}
//恢复标签配置到UI
function restoreLabelConfig(config, type) {
if (!config) return;
const containerClass = type === 'priority' ? 'priority-labels-container' : 'type-labels-container';
const dataType = type === 'priority' ? 'priority' : 'issuetype';
const removeFunc = type === 'priority' ? 'removePriorityLabelInput' : 'removeTypeLabelInput';
//按JiraID 分组Gitea标签
const byJiraId = {};
Object.entries(config).forEach(([label, jiraId]) => {
if (!byJiraId[jiraId]) byJiraId[jiraId] = [];
byJiraId[jiraId].push(label);
});
//为每个Jira字段填充标签
Object.entries(byJiraId).forEach(([jiraId, labels]) => {
const container = document.querySelector(`.${containerClass}[data-jira-id="${jiraId}"]`);
if (!container) return;
container.innerHTML = '';
labels.forEach(label => {
const inputDiv = document.createElement('div');
inputDiv.className = 'flex items-center gap-2';
inputDiv.innerHTML = `
Gitea标签:
`;
container.appendChild(inputDiv);
});
});
}
//将现有配置恢复到UI
function restoreConfigToUI(config) {
restoreLabelConfig(config.priorities, 'priority');
restoreLabelConfig(config.types, 'type');
if (config.jira?.defaultType) {
document.getElementById('defaultTypeSelect').value = config.jira.defaultType;
}
if (config.transitions) {
if (config.transitions.close) document.getElementById('transClose').value = config.transitions.close;
if (config.transitions.reopen) document.getElementById('transReopen').value = config.transitions.reopen;
if (config.transitions.in_progress) document.getElementById('transProgress').value = config.transitions.in_progress;
}
updatePreview();
}
async function fetchSprintId() {
const btn = document.getElementById('fetchSprintBtn');
const resultBox = document.getElementById('sprintResult');
const errBox = document.getElementById('sprintError');
const milestoneBox = document.getElementById('sprintMilestone');
const settings = getGlobalSettings();
const baseUrl = settings.jiraUrl;
const username = settings.jiraUser;
const token = settings.jiraToken;
const issueKey = document.getElementById('issueKey').value.trim().toUpperCase();
if (!baseUrl) {
showSprintError("请先在全局设置中配置Jira地址");
return;
}
if (!issueKey) {
showSprintError("请填写工单 Key");
return;
}
btn.disabled = true;
btn.innerText = "提取中...";
errBox.classList.add('hidden');
resultBox.innerHTML = '';
try {
//构造认证头(支持 PAT 和 Basic Auth)
let authHeader;
if (token && !username) {
//如果只有 token 没有 username,使用 Bearer 认证(PAT)
authHeader = 'Bearer ' + token;
} else if (username && token) {
//如果有 username 和 token/password,使用 Basic 认证
authHeader = 'Basic ' + btoa(username + ':' + token);
} else {
throw new Error('请填写认证信息(PAT 或 用户名+密码)');
}
//调用JiraAPI
const apiUrl = `${baseUrl}/rest/api/2/issue/${issueKey}`;
const res = await fetch('/editor/api/proxy-jira', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: apiUrl,
auth: authHeader
})
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || '无法获取工单信息');
}
const issue = data.data;
const sprintField = issue.fields?.customfield_10105;
if (!sprintField || sprintField.length === 0) {
throw new Error('该工单没有关联 Sprint (customfield_10105 为空)');
}
//解析 Sprint 字符串,提取 id
const sprintStr = sprintField[0];
const idMatch = sprintStr.match(/id=(\d+)/);
const nameMatch = sprintStr.match(/name=([^,\]]+)/);
if (!idMatch) {
throw new Error('无法从 Sprint 字段中解析 ID');
}
extractedSprintId = idMatch[1];
const sprintName = nameMatch ? nameMatch[1] : 'Unknown';
extractedSprintName = sprintName;
//显示成功结果
resultBox.innerHTML = `
Sprint ID: ${extractedSprintId}
(${sprintName})
`;
//显示里程碑名称输入框,并预填充Sprint名称
milestoneBox.classList.remove('hidden');
document.getElementById('milestoneName').value = sprintName;
} catch (e) {
showSprintError(e.message);
extractedSprintId = null;
milestoneBox.classList.add('hidden');
} finally {
btn.disabled = false;
btn.innerText = "提取 Sprint";
}
}
function showSprintError(msg) { showErrorMsg('sprintError', msg); }
function addSprintMapping() {
const milestoneName = document.getElementById('milestoneName').value.trim();
if (!milestoneName) {
showSprintError('请填写里程碑名称');
return;
}
if (!extractedSprintId) {
showSprintError('请先提取 Sprint ID');
return;
}
//检查是否已存在相同的里程碑名称
const existing = sprintMappings.find(m => m.milestone === milestoneName);
if (existing) {
if (!confirm(`里程碑 "${milestoneName}" 已存在(Sprint ID: ${existing.sprintId}),是否覆盖?`)) {
return;
}
//移除旧的
sprintMappings = sprintMappings.filter(m => m.milestone !== milestoneName);
}
//添加新映射
sprintMappings.push({
milestone: milestoneName,
sprintId: parseInt(extractedSprintId),
sprintName: extractedSprintName
});
//更新显示
renderSprintList();
//清空输入
document.getElementById('issueKey').value = '';
document.getElementById('milestoneName').value = '';
document.getElementById('sprintResult').innerHTML = '';
document.getElementById('sprintMilestone').classList.add('hidden');
document.getElementById('sprintError').classList.add('hidden');
extractedSprintId = null;
extractedSprintName = null;
//更新预览
updatePreview();
}
function renderSprintList() {
const container = document.getElementById('sprintListContainer');
const list = document.getElementById('sprintList');
if (sprintMappings.length === 0) {
container.classList.add('hidden');
return;
}
container.classList.remove('hidden');
list.innerHTML = sprintMappings.map((m, index) => `
${m.milestone}
→
Sprint ID: ${m.sprintId}
(${m.sprintName})
`).join('');
}
function removeSprintMapping(index) {
sprintMappings.splice(index, 1);
renderSprintList();
updatePreview();
}
async function saveToFile() {
if (!currentRepoName) {
alert('错误:未设置仓库名称');
return;
}
if (!fetchedData) {
alert('请先扫描Jira项目信息');
return;
}
//尝试从JSON预览读取配置
const jsonPreview = document.getElementById('jsonPreview');
const jsonError = document.getElementById('jsonError');
let config;
if (jsonPreview.value.trim()) {
try {
config = JSON.parse(jsonPreview.value);
jsonError.classList.add('hidden');
} catch (e) {
jsonError.textContent = `JSON格式错误: ${e.message}`;
jsonError.classList.remove('hidden');
return;
}
} else {
//如果JSON预览为空,从表单收集配置
config = buildConfigFromForm();
}
//更新JSON预览
updatePreview();
//保存到服务器
try {
const res = await fetch('/editor/api/mappings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
repoName: currentRepoName,
config: config
})
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error);
}
//显示成功信息
document.getElementById('saveResult').innerHTML = `
配置已成功保存!
仓库: ${currentRepoName}
文件: mappings.json
`;
document.getElementById('step2').classList.add('hidden');
document.getElementById('step3').classList.remove('hidden');
document.getElementById('step3').scrollIntoView({ behavior: "smooth" });
} catch (e) {
alert('保存失败: ' + e.message);
}
}
//从表单构建配置对象
function buildConfigFromForm() {
const projectId = fetchedData.project.id;
const projectKey = fetchedData.project.key;
const defaultType = document.getElementById('defaultTypeSelect').value;
//收集优先级(支持多个Gitea标签映射到同一个Jira优先级)
const priorities = {};
document.querySelectorAll('input[data-type="priority"]').forEach(input => {
const val = input.value.trim();
if (val) {
//检查是否已存在相同的Gitea标签
if (priorities[val] && priorities[val] !== input.dataset.jiraId) {
console.warn(`警告:Gitea标签 "${val}" 被映射到多个Jira优先级,将使用最后一个`);
}
priorities[val] = input.dataset.jiraId;
}
});
//收集类型(支持多个Gitea标签映射到同一个Jira类型)
const types = {};
document.querySelectorAll('input[data-type="issuetype"]').forEach(input => {
const val = input.value.trim();
if (val) {
//检查是否已存在相同的Gitea标签
if (types[val] && types[val] !== input.dataset.jiraId) {
console.warn(`警告:Gitea标签 "${val}" 被映射到多个Jira类型,将使用最后一个`);
}
types[val] = input.dataset.jiraId;
}
});
//收集流转
const transitions = {};
const closeId = document.getElementById('transClose').value;
const reopenId = document.getElementById('transReopen').value;
const progressId = document.getElementById('transProgress').value;
if (closeId) transitions['close'] = closeId;
if (reopenId) transitions['reopen'] = reopenId;
if (progressId) transitions['in_progress'] = progressId;
//生成 Sprint 配置对象
const sprints = {};
sprintMappings.forEach(m => {
sprints[m.milestone] = m.sprintId;
});
//构建配置对象
const jiraConfig = {
projectId: projectId,
projectKey: projectKey,
sprintField: "customfield_10105"
};
//只在有值时添加defaultType
if (defaultType) {
jiraConfig.defaultType = defaultType;
}
return {
jira: jiraConfig,
priorities: priorities,
types: types,
transitions: transitions,
sprints: sprints
};
}
//更新JSON预览
function updatePreview() {
if (!fetchedData) return;
const config = buildConfigFromForm();
const jsonPreview = document.getElementById('jsonPreview');
const jsonError = document.getElementById('jsonError');
try {
jsonPreview.value = JSON.stringify(config, null, 2);
jsonError.classList.add('hidden');
} catch (e) {
jsonError.textContent = `生成预览失败: ${e.message}`;
jsonError.classList.remove('hidden');
}
}
async function scanJira(existingConfig = null) {
const btn = document.getElementById('scanBtn');
const errBox = document.getElementById('scanError');
const step2 = document.getElementById('step2');
const settings = getGlobalSettings();
const baseUrl = settings.jiraUrl;
const username = settings.jiraUser;
const token = settings.jiraToken;
const projectKey = document.getElementById('projectKey').value.trim();
if (!baseUrl) {
showError("请先在全局设置中配置Jira连接信息");
return;
}
if (!projectKey) {
showError("请输入项目 Key");
return;
}
btn.disabled = true;
btn.classList.add('opacity-75');
document.getElementById('scanBtnText').innerText = existingConfig ? "加载中..." : "扫描中...";
errBox.classList.add('hidden');
if (!existingConfig) step2.classList.add('hidden');
try {
const res = await fetch('/editor/api/scan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
baseUrl,
projectKey,
auth: { username, password: token, token: token.length > 30 ? token : null }
})
});
const json = await res.json();
if (!json.success) {
throw new Error(json.error);
}
fetchedData = json.data;
renderMappingUI(json.data);
if (existingConfig) {
restoreConfigToUI(existingConfig);
}
step2.classList.remove('hidden');
setTimeout(() => step2.scrollIntoView({ behavior: "smooth" }), 100);
} catch (e) {
showError(e.message);
} finally {
btn.disabled = false;
btn.classList.remove('opacity-75');
document.getElementById('scanBtnText').innerText = "开始扫描";
}
}
function showError(msg) { showErrorMsg('scanError', msg); }
//手动扫描工单的 Transition
async function scanTransitions() {
const btn = document.getElementById('scanTransBtn');
const resultBox = document.getElementById('transResult');
const errBox = document.getElementById('transError');
const issueKey = document.getElementById('transIssueKey').value.trim().toUpperCase();
const settings = getGlobalSettings();
const baseUrl = settings.jiraUrl;
const username = settings.jiraUser;
const token = settings.jiraToken;
if (!baseUrl) {
showTransError("请先在全局设置中配置Jira地址");
return;
}
if (!issueKey) {
showTransError("请输入工单 Key");
return;
}
btn.disabled = true;
btn.innerText = "扫描中...";
errBox.classList.add('hidden');
resultBox.innerHTML = '';
try {
//构造认证头
let authHeader;
if (token && !username) {
authHeader = 'Bearer ' + token;
} else if (username && token) {
authHeader = 'Basic ' + btoa(username + ':' + token);
} else {
throw new Error('请填写认证信息(PAT 或 用户名+密码)');
}
//调用 Jira API 获取 transitions
const apiUrl = `${baseUrl}/rest/api/2/issue/${issueKey}/transitions`;
const res = await fetch('/editor/api/proxy-jira', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: apiUrl, auth: authHeader })
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || '无法获取流转信息');
}
const transitions = data.data.transitions || [];
if (transitions.length === 0) {
resultBox.innerHTML = '该工单当前状态没有可用的流转';
return;
}
//追加到下拉菜单(去重)
const transSelects = ['transClose', 'transReopen', 'transProgress'];
let addedCount = 0;
transitions.forEach(t => {
transSelects.forEach(selectId => {
const sel = document.getElementById(selectId);
//检查是否已存在(按 ID 去重)
const exists = Array.from(sel.options).some(opt => opt.value === t.id);
if (!exists) {
const opt = document.createElement('option');
opt.value = t.id;
opt.text = `${t.name} (To: ${t.to?.name || 'Unknown'})`;
sel.add(opt);
addedCount++;
}
});
});
//显示结果
const newTransNames = transitions.map(t => `${t.name} → ${t.to?.name || '?'}`).join(', ');
resultBox.innerHTML = `
扫描到 ${transitions.length} 个流转: ${newTransNames}
`;
//同时更新 fetchedData 以保持一致
if (fetchedData) {
transitions.forEach(t => {
const exists = fetchedData.transitions.some(existing => existing.id === t.id);
if (!exists) {
fetchedData.transitions.push({
id: t.id,
name: t.name,
to: t.to?.name || 'Unknown'
});
}
});
}
} catch (e) {
showTransError(e.message);
} finally {
btn.disabled = false;
btn.innerText = "扫描流转";
}
}
function showTransError(msg) { showErrorMsg('transError', msg); }
function renderMappingUI(data) {
//渲染优先级
const pContainer = document.getElementById('priorityContainer');
pContainer.innerHTML = '';
data.priorities.forEach(p => {
let guess = "";
for (let k in defaultLabels) {
if (p.name.includes(k)) guess = defaultLabels[k];
}
const itemDiv = document.createElement('div');
itemDiv.className = 'bg-gray-50 p-3 rounded border';
itemDiv.dataset.jiraId = p.id;
itemDiv.innerHTML = `
${p.name}
`;
pContainer.appendChild(itemDiv);
});
//渲染类型
const tContainer = document.getElementById('typeContainer');
const defaultSelect = document.getElementById('defaultTypeSelect');
tContainer.innerHTML = '';
defaultSelect.innerHTML = '';
//添加"不设置"选项
const emptyOption = document.createElement('option');
emptyOption.value = '';
emptyOption.text = '(不设置默认类型)';
defaultSelect.add(emptyOption);
data.types.forEach((t, index) => {
let guess = "";
for (let k in defaultLabels) {
if (t.name.includes(k)) guess = defaultLabels[k];
}
const itemDiv = document.createElement('div');
itemDiv.className = 'bg-gray-50 p-3 rounded border';
itemDiv.dataset.jiraId = t.id;
itemDiv.innerHTML = `
${t.name}
`;
tContainer.appendChild(itemDiv);
const option = document.createElement('option');
option.value = t.id;
option.text = t.name;
defaultSelect.add(option);
if (t.name.toLowerCase() === 'bug' || t.name.toLowerCase() === 'task') defaultSelect.value = t.id;
});
//渲染流转
const transSelects = ['transClose', 'transReopen', 'transProgress'];
const warningBox = document.getElementById('transWarning');
if (data.warning) {
warningBox.innerText = data.warning;
warningBox.classList.remove('hidden');
} else {
warningBox.classList.add('hidden');
}
transSelects.forEach(id => {
const sel = document.getElementById(id);
sel.innerHTML = '';
data.transitions.forEach(t => {
const opt = document.createElement('option');
opt.value = t.id;
opt.text = `${t.name} (To: ${t.to})`;
sel.add(opt);
const lowerName = t.name.toLowerCase();
const lowerTo = t.to.toLowerCase();
if (id === 'transClose' && (lowerName.includes('close') || lowerName.includes('done') || lowerName.includes('complete'))) sel.value = t.id;
if (id === 'transReopen' && (lowerName.includes('reopen') || lowerName.includes('open'))) sel.value = t.id;
if (id === 'transProgress' && (lowerName.includes('progress') || lowerName.includes('process') || lowerTo.includes('progress'))) sel.value = t.id;
});
});
//为所有输入框添加input事件,自动更新预览(使用input替代change以实现实时更新)
document.querySelectorAll('input[data-type]').forEach(el => {
el.addEventListener('input', updatePreview);
});
document.querySelectorAll('select').forEach(el => {
el.addEventListener('change', updatePreview);
});
//初始化预览
updatePreview();
}