You've already forked agentic-coding-workflow
feat(web): workspace selector on issue/requirement detail
This commit is contained in:
+11
-1
@@ -25,6 +25,7 @@ export interface Requirement {
|
||||
phase: Phase
|
||||
status: Status
|
||||
owner_id: string
|
||||
workspace_id: string | null
|
||||
closed_at?: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
@@ -34,6 +35,7 @@ export interface Issue {
|
||||
id: string
|
||||
project_id: string
|
||||
requirement_id: string | null
|
||||
workspace_id: string | null
|
||||
number: number
|
||||
title: string
|
||||
description: string
|
||||
@@ -92,7 +94,13 @@ export const projectsApi = {
|
||||
updateRequirement: (
|
||||
slug: string,
|
||||
number: number,
|
||||
body: Partial<{ title: string; description: string; owner_id: string }>,
|
||||
body: Partial<{
|
||||
title: string
|
||||
description: string
|
||||
owner_id: string
|
||||
// 三态:未传字段 / 显式 null(detach) / "<uuid>"(attach)
|
||||
workspace_id: string | null
|
||||
}>,
|
||||
) =>
|
||||
request<Requirement>(`/api/v1/projects/${enc(slug)}/requirements/${number}`, {
|
||||
method: 'PATCH',
|
||||
@@ -141,6 +149,8 @@ export const projectsApi = {
|
||||
description: string
|
||||
// 三态:未传字段 / 显式 null(detach) / number(attach/move)
|
||||
requirement_number: number | null
|
||||
// 三态:未传字段 / 显式 null(detach) / "<uuid>"(attach)
|
||||
workspace_id: string | null
|
||||
}>,
|
||||
) =>
|
||||
request<Issue>(`/api/v1/projects/${enc(slug)}/issues/${number}`, {
|
||||
|
||||
@@ -58,6 +58,17 @@
|
||||
/>
|
||||
</NCard>
|
||||
|
||||
<!-- Workspace card -->
|
||||
<NCard title="工作区">
|
||||
<NSelect
|
||||
:value="issue.workspace_id"
|
||||
:options="workspaceOptions"
|
||||
placeholder="未关联"
|
||||
clearable
|
||||
@update:value="onWorkspaceChange"
|
||||
/>
|
||||
</NCard>
|
||||
|
||||
<!-- Assignee card -->
|
||||
<NCard title="指派">
|
||||
<div class="flex items-center gap-4">
|
||||
@@ -118,11 +129,13 @@ import { NCard, NButton, NTag, NSpin, NSelect } from 'naive-ui'
|
||||
import AppShell from '@/layouts/AppShell.vue'
|
||||
import { projectsApi } from '@/api/projects'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useWorkspacesStore } from '@/stores/workspaces'
|
||||
import type { Issue, Requirement } from '@/api/projects'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const wsStore = useWorkspacesStore()
|
||||
|
||||
const slug = route.params.slug as string
|
||||
const number = Number(route.params.number)
|
||||
@@ -138,6 +151,10 @@ const requirementOptions = computed(() =>
|
||||
requirements.value.map((r) => ({ label: `R-${r.number}: ${r.title}`, value: r.number })),
|
||||
)
|
||||
|
||||
const workspaceOptions = computed(() =>
|
||||
wsStore.list.map((w) => ({ label: `${w.name} (${w.slug})`, value: w.id })),
|
||||
)
|
||||
|
||||
const mountedRequirementNumber = computed<number | null>(() => {
|
||||
if (!issue.value?.requirement_id) return null
|
||||
const req = requirements.value.find((r) => r.id === issue.value!.requirement_id)
|
||||
@@ -150,6 +167,7 @@ async function fetchData() {
|
||||
const [fetchedIssue, reqs] = await Promise.all([
|
||||
projectsApi.getIssue(slug, number),
|
||||
projectsApi.listRequirements(slug),
|
||||
wsStore.fetchByProject(slug),
|
||||
])
|
||||
issue.value = fetchedIssue
|
||||
requirements.value = reqs.items
|
||||
@@ -163,6 +181,11 @@ async function onMountChange(v: number | null) {
|
||||
issue.value = await projectsApi.updateIssue(slug, number, { requirement_number: v })
|
||||
}
|
||||
|
||||
async function onWorkspaceChange(v: string | null) {
|
||||
if (!issue.value) return
|
||||
issue.value = await projectsApi.updateIssue(slug, number, { workspace_id: v })
|
||||
}
|
||||
|
||||
async function onClose() {
|
||||
toggling.value = true
|
||||
try {
|
||||
|
||||
@@ -85,6 +85,17 @@
|
||||
</p>
|
||||
</NCard>
|
||||
|
||||
<!-- Workspace card -->
|
||||
<NCard title="工作区">
|
||||
<NSelect
|
||||
:value="data.requirement.workspace_id"
|
||||
:options="workspaceOptions"
|
||||
placeholder="未关联"
|
||||
clearable
|
||||
@update:value="onWorkspaceChange"
|
||||
/>
|
||||
</NCard>
|
||||
|
||||
<!-- Linked Issues -->
|
||||
<NCard title="关联 Issue">
|
||||
<NList
|
||||
@@ -129,7 +140,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useDialog } from 'naive-ui'
|
||||
import {
|
||||
@@ -137,6 +148,7 @@ import {
|
||||
NButton,
|
||||
NTag,
|
||||
NSpin,
|
||||
NSelect,
|
||||
NList,
|
||||
NListItem,
|
||||
NThing,
|
||||
@@ -145,6 +157,7 @@ import {
|
||||
import AppShell from '@/layouts/AppShell.vue'
|
||||
import { projectsApi } from '@/api/projects'
|
||||
import { useRequirementsStore } from '@/stores/requirements'
|
||||
import { useWorkspacesStore } from '@/stores/workspaces'
|
||||
import type { Phase, RequirementDetail } from '@/api/projects'
|
||||
|
||||
const PHASES: Phase[] = ['planning', 'auditing', 'implementing', 'reviewing', 'done']
|
||||
@@ -161,6 +174,7 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const dialog = useDialog()
|
||||
const store = useRequirementsStore()
|
||||
const wsStore = useWorkspacesStore()
|
||||
|
||||
const slug = route.params.slug as string
|
||||
const number = Number(route.params.number)
|
||||
@@ -169,15 +183,30 @@ const loading = ref(false)
|
||||
const toggling = ref(false)
|
||||
const data = ref<RequirementDetail | null>(null)
|
||||
|
||||
const workspaceOptions = computed(() =>
|
||||
wsStore.list.map((w) => ({ label: `${w.name} (${w.slug})`, value: w.id })),
|
||||
)
|
||||
|
||||
async function fetchData() {
|
||||
loading.value = true
|
||||
try {
|
||||
data.value = await projectsApi.getRequirement(slug, number)
|
||||
const [detail] = await Promise.all([
|
||||
projectsApi.getRequirement(slug, number),
|
||||
wsStore.fetchByProject(slug),
|
||||
])
|
||||
data.value = detail
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onWorkspaceChange(v: string | null) {
|
||||
if (!data.value) return
|
||||
await projectsApi.updateRequirement(slug, number, { workspace_id: v })
|
||||
// Re-fetch to keep data.requirement and data.issues consistent (mirrors onPhaseClick)
|
||||
data.value = await projectsApi.getRequirement(slug, number)
|
||||
}
|
||||
|
||||
function onPhaseClick(target: Phase) {
|
||||
if (!data.value || data.value.requirement.phase === target) return
|
||||
const targetLabel = PHASE_LABELS[target]
|
||||
|
||||
Reference in New Issue
Block a user