#feature: some update
This commit is contained in:
@@ -54,14 +54,15 @@
|
||||
</div>
|
||||
<div class="max-h-[360px] overflow-auto rounded border">
|
||||
<table class="dense-table min-w-[1180px]">
|
||||
<thead><tr><th v-for="h in issueHeaders" :key="h">{{ h }}</th></tr></thead>
|
||||
<thead><tr><th v-for="h in issueHeaders" :key="h">{{ h }}</th><th class="w-14">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="issue in issues" :key="issue.key">
|
||||
<td><a :href="issue.url" target="_blank" class="text-blue-600">{{ issue.key }}</a></td>
|
||||
<td class="min-w-72">{{ issue.summary }}</td>
|
||||
<td>{{ issue.reporter || '-' }}</td><td>{{ issue.status }}</td><td>{{ issue.developer || '-' }}</td><td>{{ issue.assignee || '-' }}</td><td>{{ issue.sprint || '-' }}</td><td>{{ issue.estimated_test_at || '' }}</td><td>{{ issue.estimated_release_at || '' }}</td>
|
||||
<td><button @click="removeIssue(issue.key)" class="text-xs text-red-600 hover:underline">删除</button></td>
|
||||
</tr>
|
||||
<tr v-if="!issues.length"><td colspan="9" class="text-center text-gray-400">请选择 Sprint 后拉取 Jira 数据</td></tr>
|
||||
<tr v-if="!issues.length"><td colspan="10" class="text-center text-gray-400">请选择 Sprint 后拉取 Jira 数据</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -85,7 +86,7 @@
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-between gap-2">
|
||||
<button @click="refreshDatabases" class="rounded bg-blue-100 px-3 py-1.5 text-sm text-blue-700 hover:bg-blue-200">刷新数据库分支</button>
|
||||
<span class="text-xs text-gray-500">agent / portal / portal-ticket 版本号可各自调整</span>
|
||||
<span class="text-xs text-gray-500">agent / portal / portal-ticket / mono 版本号可各自调整</span>
|
||||
</div>
|
||||
<div class="mt-3 overflow-auto rounded border">
|
||||
<table class="dense-table min-w-[520px]"><thead><tr><th>系统</th><th>是否有数据库</th><th>分支</th></tr></thead><tbody><tr v-for="row in databases" :key="row.group"><td>{{ row.system }}</td><td>{{ row.has_database }}</td><td>{{ row.branch || '-' }}</td></tr></tbody></table>
|
||||
@@ -156,6 +157,7 @@ function normalizeTestMailSprintPeriod(value) {
|
||||
}
|
||||
|
||||
const LAST_SPRINT_STORAGE_KEY = 'toolbox.testMail.lastSprint';
|
||||
const RECIPIENTS_STORAGE_KEY = 'toolbox.testMail.recipients';
|
||||
|
||||
const EditableDenseTable = {
|
||||
props: ['headers', 'rows', 'columns'], emits: ['remove'],
|
||||
@@ -171,7 +173,7 @@ export default {
|
||||
},
|
||||
data() { return {
|
||||
steps: ['1 Sprint','2 收件人','3 Jira 表格','4 容器/数据库','5 八截图+九/十表格','6 下载'],
|
||||
sprint: '', sprintOptions: [], loading: false, downloading: false, draftLoading: false, draftOpening: false, draftSource: '', error: '', jql: '', issues: [], defaults: {}, images: [],
|
||||
sprint: '', sprintOptions: [], loading: false, downloading: false, draftLoading: false, draftOpening: false, draftRequestId: 0, draftSource: '', error: '', jql: '', issues: [], defaults: {}, images: [],
|
||||
from: '万文山 <wanwenshan@angelalign.com>',
|
||||
to: '"ouyangxiaowen@angelalign.com" <ouyangxiaowen@angelalign.com>, "yaowenying@angelalign.com" <yaowenying@angelalign.com>, "guoziliang@angelalign.com" <guoziliang@angelalign.com>, chenhui7@angelalign.com, leyunpeng@angelalign.com',
|
||||
cc: '黄宇 <huangyu@angelalign.com>, "yujie2@angelalign.com" <yujie2@angelalign.com>, "lizhongyuan@angelalign.com" <lizhongyuan@angelalign.com>, "huangfang2@angelalign.com" <huangfang2@angelalign.com>, 周国辉 <zhouguohui@angelalign.com>, "yuxinli@angelalign.com" <yuxinli@angelalign.com>, "zhangzhen3@angelalign.com" <zhangzhen3@angelalign.com>, "renzhaochun@angelalign.com" <renzhaochun@angelalign.com>, "yangyunhao@angelalign.com" <yangyunhao@angelalign.com>, "xiangshang@angelalign.com" <xiangshang@angelalign.com>, "liuyuan1@angelalign.com" <liuyuan1@angelalign.com>, zhangyuan1@angelalign.com, yangjuan1@angelalign.com, wanghe2@angelalign.com',
|
||||
@@ -192,8 +194,12 @@ export default {
|
||||
draftSourceLabel() { return this.draftSource === 'ai' ? 'AI' : '规则默认'; },
|
||||
mailHtml() { return `<div style="font-family:'Microsoft YaHei UI',Arial,sans-serif;font-size:14px;color:#000;line-height:1.5">${this.section('一、需求内容')}${this.issueTableHtml()}${this.section('二、技术文档')}${this.multiline(this.techDocs)}${this.section('三、冒烟测试情况:')}${this.paragraph(`冒烟通过率:${this.escape(this.smokeRate)}`)}${this.smokeLinksHtml()}${this.section('四、计划异常情况')}${this.paragraph('紧急需求:<br> ' + this.escape(this.urgentItems).replace(/\n/g, '<br> '))}${this.paragraph('延期需求:<br> ' + this.escape(this.delayedItems).replace(/\n/g, '<br> '))}${this.section('五、容器部署和版本')}${this.simpleTable(['容器','版本号','服务器所在地'], this.selectedContainerRows, ['name','version','location'])}${this.section('六、数据库')}${this.simpleTable(['系统','是否有数据库','分支'], this.databases, ['system','has_database','branch'])}${this.section('七、是否涉及合规')}${this.paragraph(' 不涉及')}${this.section('八、环境部署准备清单:')}${this.screenshotHtml('environment')}${this.section('九、测试注意事项/其他依赖项')}${this.noteTableHtml()}${this.section('十、已知问题与风险')}${this.riskTableHtml()}</div>`; }
|
||||
},
|
||||
watch: { sprint(value) { this.rememberSprint(value); this.updateSubjectFromSprint(); } },
|
||||
async mounted() { await this.loadSprints(); await this.loadData(); },
|
||||
watch: {
|
||||
sprint(value) { this.rememberSprint(value); this.updateSubjectFromSprint(); },
|
||||
to() { this.rememberRecipients(); },
|
||||
cc() { this.rememberRecipients(); },
|
||||
},
|
||||
async mounted() { this.restoreRecipients(); await this.loadSprints(); await this.loadData(); },
|
||||
methods: {
|
||||
csrf() { return document.querySelector('meta[name="csrf-token"]').getAttribute('content'); },
|
||||
async loadSprints() { try { const data = await (await fetch('/api/test-mail/sprints')).json(); if (data.success) { this.sprintOptions = data.data.sprints || []; const savedSprint = this.restoreSprint(); if (!this.sprint && savedSprint) this.sprint = savedSprint; if (!this.sprint && this.sprintOptions.length) this.sprint = this.sprintOptions[0].id || this.sprintOptions[0].name || ''; this.defaults = data.data.defaults || {}; this.initializeContainers(); this.updateSubjectFromSprint(); } } catch (e) { console.error(e); } },
|
||||
@@ -202,6 +208,9 @@ export default {
|
||||
handleSprintSelection() { this.updateSubjectFromSprint(); this.loadData(); },
|
||||
rememberSprint(value) { try { const sprint = String(value || '').trim(); if (sprint) localStorage.setItem(LAST_SPRINT_STORAGE_KEY, sprint); else localStorage.removeItem(LAST_SPRINT_STORAGE_KEY); } catch (e) { console.error(e); } },
|
||||
restoreSprint() { try { return localStorage.getItem(LAST_SPRINT_STORAGE_KEY) || ''; } catch (e) { console.error(e); return ''; } },
|
||||
rememberRecipients() { try { localStorage.setItem(RECIPIENTS_STORAGE_KEY, JSON.stringify({to:this.to,cc:this.cc})); } catch (e) { console.error(e); } },
|
||||
restoreRecipients() { try { const saved = JSON.parse(localStorage.getItem(RECIPIENTS_STORAGE_KEY) || 'null'); if (saved && typeof saved.to === 'string' && typeof saved.cc === 'string') { this.to = saved.to; this.cc = saved.cc; } } catch (e) { console.error(e); } },
|
||||
removeIssue(key) { this.draftRequestId++; this.draftLoading = false; this.issues = this.issues.filter(issue => issue.key !== key); this.testNoteRows = this.testNoteRows.filter(row => row.issue !== key); if (!this.testNoteRows.length) this.addNoteRow(); },
|
||||
async refreshDatabases() { try { const data = await (await fetch('/api/test-mail/databases', { method: 'POST', headers: {'Content-Type':'application/json','X-CSRF-TOKEN':this.csrf()}, body: JSON.stringify({ selected_groups: this.selectedGroups, versions: this.versions }) })).json(); if (data.success) this.databases = data.data.databases || []; } catch (e) { console.error(e); } },
|
||||
isGroupSelected(key) { return this.selectedGroups.includes(key); },
|
||||
toggleGroup(key, checked) { const group = this.containerGroups.find(g => g.key === key); const names = (group?.containers || []).map(c => c.name); if (checked) { if (!this.selectedGroups.includes(key)) this.selectedGroups.push(key); this.selectedContainers = Array.from(new Set([...this.selectedContainers, ...names])); } else { this.selectedGroups = this.selectedGroups.filter(k => k !== key); this.selectedContainers = this.selectedContainers.filter(n => !names.includes(n)); } this.refreshDatabases(); },
|
||||
@@ -209,7 +218,7 @@ export default {
|
||||
clearEnvironmentScreenshots() { this.images = this.images.filter(i => i.section !== 'environment'); this.$refs.environmentPasteBox.innerHTML = '<p class="text-gray-500">点击这里后直接粘贴「八、环境部署准备清单」截图</p>'; },
|
||||
addNoteRow() { this.testNoteRows.push({_id:Date.now()+Math.random(),type:'其他依赖项',issue:'',system:'',content:'',owner:''}); }, removeNoteRow(i) { this.testNoteRows.splice(i,1); if (!this.testNoteRows.length) this.addNoteRow(); },
|
||||
addRiskRow() { this.riskRows.push({_id:Date.now()+Math.random(),problem:'',impact:'',action:'',owner:''}); }, removeRiskRow(i) { this.riskRows.splice(i,1); if (!this.riskRows.length) this.addRiskRow(); },
|
||||
async generateDraftSections(showErrors = true) { this.draftLoading = true; if (showErrors) this.error = ''; try { const res = await fetch('/api/test-mail/draft-sections', { method:'POST', headers:{'Content-Type':'application/json','X-CSRF-TOKEN':this.csrf()}, body:JSON.stringify({ issues:this.issues, tech_docs:this.techDocs, selected_containers:this.selectedContainers, databases:this.databases }) }); const data = await res.json(); if (!data.success) throw new Error(data.message || '生成草稿失败'); this.testNoteRows = (data.data.test_notes || []).map((row, index) => ({_id:Date.now()+index, type:row.type || '测试注意事项', issue:row.issue || '', system:row.system || '', content:row.content || '', owner:row.owner || ''})); this.riskRows = (data.data.risks || []).map((row, index) => ({_id:Date.now()+100+index, problem:row.problem || '', impact:row.impact || '', action:row.action || '', owner:row.owner || ''})); if (!this.testNoteRows.length) this.addNoteRow(); if (!this.riskRows.length) this.addRiskRow(); this.draftSource = data.data.source || 'rules'; } catch(e) { if (showErrors) this.error = e.message; else console.error(e); } finally { this.draftLoading = false; } },
|
||||
async generateDraftSections(showErrors = true) { const requestId = ++this.draftRequestId; this.draftLoading = true; if (showErrors) this.error = ''; try { const res = await fetch('/api/test-mail/draft-sections', { method:'POST', headers:{'Content-Type':'application/json','X-CSRF-TOKEN':this.csrf()}, body:JSON.stringify({ issues:this.issues, tech_docs:this.techDocs, selected_containers:this.selectedContainers, databases:this.databases }) }); const data = await res.json(); if (requestId !== this.draftRequestId) return; if (!data.success) throw new Error(data.message || '生成草稿失败'); this.testNoteRows = (data.data.test_notes || []).map((row, index) => ({_id:Date.now()+index, type:row.type || '测试注意事项', issue:row.issue || '', system:row.system || '', content:row.content || '', owner:row.owner || ''})); this.riskRows = (data.data.risks || []).map((row, index) => ({_id:Date.now()+100+index, problem:row.problem || '', impact:row.impact || '', action:row.action || '', owner:row.owner || ''})); if (!this.testNoteRows.length) this.addNoteRow(); if (!this.riskRows.length) this.addRiskRow(); this.draftSource = data.data.source || 'rules'; } catch(e) { if (showErrors) this.error = e.message; else console.error(e); } finally { if (requestId === this.draftRequestId) this.draftLoading = false; } },
|
||||
updateSubjectFromSprint() { const sprint = this.sprint.trim(); this.subject = this.buildSubject(this.resolveSprintPeriod() || (sprint ? `Sprint${sprint}` : '')); },
|
||||
buildSubject(period) { return `【提测】${period ? period : ''}需求提测(SP、PP、TP)`; },
|
||||
resolveSprintPeriod() { const option = this.selectedSprintOption(); if (option?.period) return option.period; const candidates = [this.sprint, option?.name, option?.label, ...this.issues.map(i => i.sprint || '')]; for (const candidate of candidates) { const period = normalizeTestMailSprintPeriod(candidate); if (period) return period; } return ''; },
|
||||
|
||||
Reference in New Issue
Block a user