feat: 展示项目最近发布状态

- store: 将真实 deploy-runs 回填到项目环境最近发布摘要
- ProjectsView: 展示最近 run 状态、ref、操作者和跳转入口
- RunsView: 支持通过 runId query 定位运行记录
This commit is contained in:
湛兮
2026-06-12 04:26:39 +08:00
parent 806b210739
commit 4e15547662
3 changed files with 115 additions and 4 deletions
+51
View File
@@ -55,6 +55,7 @@ export const usePlatformStore = defineStore('platform', () => {
const summary = await devopsApi.getDashboard({ includeAdminConfig: includeAdminData })
projects.value = summary.projects
runs.value = summary.runs
applyProjectRunSummaries()
integrations.value = summary.integrations
agentCards.value = summary.agentCards
settings.value = summary.settings
@@ -200,6 +201,49 @@ export const usePlatformStore = defineStore('platform', () => {
function upsertRun(run: ReleaseRun) {
runs.value = [run, ...runs.value.filter((item) => item.id !== run.id)]
applyProjectRunSummaries()
}
function applyProjectRunSummaries() {
const latestRunByEnvironment = new Map<string, ReleaseRun>()
// 后端 /deploy-runs 按最近记录返回;首次出现的项目环境即当前展示摘要。
runs.value.forEach((run) => {
const key = projectEnvironmentKey(run.projectKey, run.environment)
if (!latestRunByEnvironment.has(key)) {
latestRunByEnvironment.set(key, run)
}
})
projects.value = projects.value.map((project) => ({
...project,
environments: project.environments.map((environment) => {
const run = latestRunByEnvironment.get(
projectEnvironmentKey(project.key, environment.name),
)
if (!run) {
return {
...environment,
status: 'idle',
version: '-',
lastRunId: '-',
lastReleasedAt: '-',
lastActor: '-',
}
}
return {
...environment,
status: run.status,
version: run.ref,
lastRunId: run.id,
lastReleasedAt: run.startedAt,
lastActor: run.actor,
}
}),
}))
}
return {
@@ -263,3 +307,10 @@ function jenkinsDiagnosticMessage(
return 'Jenkins job 不可用'
}
function projectEnvironmentKey(
projectKey: string,
environment: ReleaseRun['environment'],
): string {
return `${projectKey}:${environment}`
}
+42 -1
View File
@@ -94,7 +94,23 @@ function lastBuildText(env: EnvironmentStatus): string {
<dt>通知通道</dt>
<dd>{{ env.wecomRobot }}</dd>
<dt>最近发布</dt>
<dd>{{ env.lastRunId }} · {{ env.version }}</dd>
<dd>
<div v-if="env.lastRunId !== '-'" class="latest-release">
<div class="latest-release-head">
<StatusPill :state="env.status" />
<RouterLink
class="run-link mono"
:to="{ name: 'runs', query: { runId: env.lastRunId } }"
>
{{ env.lastRunId }}
</RouterLink>
</div>
<small>
{{ env.version }} · {{ env.lastActor }} · {{ env.lastReleasedAt }}
</small>
</div>
<span v-else>暂无真实发布记录</span>
</dd>
</dl>
</div>
</div>
@@ -178,6 +194,31 @@ dd {
font-size: 12px;
}
.latest-release {
display: grid;
gap: 6px;
}
.latest-release-head {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.latest-release small {
color: #7b8798;
}
.run-link {
color: #2563eb;
text-decoration: none;
}
.run-link:hover {
text-decoration: underline;
}
@media (max-width: 760px) {
.env-config {
grid-template-columns: 1fr;
+20 -1
View File
@@ -4,7 +4,8 @@
*/
import { CircleClose, RefreshRight } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { computed, ref } from 'vue'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import BpmnStatusViewer from '../components/BpmnStatusViewer.vue'
import RunExecutionDetails from '../components/RunExecutionDetails.vue'
import StatusPill from '../components/StatusPill.vue'
@@ -12,6 +13,7 @@ import { usePlatformStore } from '../stores/platform'
import type { ReleaseRun } from '../types/devops'
const platform = usePlatformStore()
const route = useRoute()
const selectedRunId = ref(platform.runs[0]?.id || '')
const syncingAll = ref(false)
@@ -29,6 +31,23 @@ const canRetrySelectedRun = computed(() => {
const canSyncAnyRun = computed(() => platform.runs.some((run) => canSyncRun(run)))
watch(
() => [route.query.runId, platform.runs.map((run) => run.id).join('|')],
() => {
const queryRunId = typeof route.query.runId === 'string' ? route.query.runId : ''
if (queryRunId && platform.runs.some((run) => run.id === queryRunId)) {
selectedRunId.value = queryRunId
return
}
if (!selectedRunId.value && platform.runs[0]) {
selectedRunId.value = platform.runs[0].id
}
},
{ immediate: true },
)
function canSyncRun(run?: ReleaseRun): boolean {
return (
Boolean(run?.jenkinsQueueId || run?.jenkinsBuildNumber) &&