Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,4 @@ docs/

# NestJS
uploads/
.omx/
3 changes: 3 additions & 0 deletions apps/backend/src/api/dto/api.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export class ApiDetailDto extends ApiBriefDto {
@Expose()
updatedAt: Date

@Expose()
currentVersionId?: string

@Expose()
@Transform(({ obj }) => {
const description = obj.currentVersion?.snapshot?.description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ function getEnvColor(envName: string): string {
<Import class="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
<TooltipContent side="right">
导入 OpenAPI
</TooltipContent>
</Tooltip>
Expand Down
47 changes: 30 additions & 17 deletions apps/frontend/src/components/workbench/api-editor/ApiEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import type { ApiDetail } from '@/types/api'
import { useRouteParams, useRouteQuery } from '@vueuse/router'
import { AlertCircle, FileText, GitBranch, Loader2, Pencil, Play, Settings2 } from 'lucide-vue-next'
import { computed, ref, watch } from 'vue'
import { apiApi } from '@/api/api'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { useWorkbenchResourceStore } from '@/stores/useWorkbenchResourceStore'
import ApiRunnerView from '../api-runner/ApiRunnerView.vue'
import VersionHistory from '../version/VersionHistory.vue'
import ApiDocView from './ApiDocView.vue'
Expand Down Expand Up @@ -34,36 +34,49 @@ watch(activeTab, (newV, oldV) => {
}
})

const resourceStore = useWorkbenchResourceStore()

const apiDetail = ref<ApiDetail | null>(null)
const isLoading = ref(false)
const loadError = ref<string | null>(null)

const isLoaded = computed(() => apiDetail.value !== null)

async function fetchApiDetail() {
function getCurrentApiRequestKey() {
return `${projectId.value}:${apiId.value}`
}

async function fetchApiDetail(options: { force?: boolean } = {}) {
const currentProjectId = projectId.value
const currentApiId = apiId.value
if (!currentProjectId || !currentApiId)
return

const requestKey = `${currentProjectId}:${currentApiId}`
isLoading.value = true
loadError.value = null

try {
apiDetail.value = await apiApi.getApiDetail(projectId.value, apiId.value)
const detail = await resourceStore.getApiDetail(currentProjectId, currentApiId, options)
if (getCurrentApiRequestKey() !== requestKey)
return
apiDetail.value = detail
}
catch (error) {
if (getCurrentApiRequestKey() !== requestKey)
return
console.error('获取 API 详情失败:', error)
loadError.value = `获取 API 详情失败: ${error}`
}
finally {
isLoading.value = false
if (getCurrentApiRequestKey() === requestKey) {
isLoading.value = false
}
}
}

async function refreshApiDetail() {
loadError.value = null
try {
apiDetail.value = await apiApi.getApiDetail(projectId.value, apiId.value)
}
catch (error) {
console.error('获取 API 详情失败:', error)
loadError.value = `获取 API 详情失败: ${error}`
}
await fetchApiDetail({ force: true })
}

// apiId 和 projectId 变化,刷新 API 详情
Expand Down Expand Up @@ -117,19 +130,19 @@ watch([apiId, projectId], ([curApiId, curProjectId]) => {
</div>

<ScrollArea class="flex-1 overflow-y-auto">
<TabsContent value="doc">
<TabsContent value="doc" class="2xl:w-[75%] w-full m-auto">
<ApiDocView :api="apiDetail" />
</TabsContent>

<TabsContent value="edit">
<TabsContent value="edit" class="2xl:w-[75%] w-full m-auto">
<ApiEditView :api="apiDetail" @updated="refreshApiDetail" />
</TabsContent>

<TabsContent value="run">
<TabsContent value="run" class="2xl:w-[75%] w-full m-auto">
<ApiRunnerView :api="apiDetail" />
</TabsContent>

<TabsContent value="versions">
<TabsContent value="versions" class="2xl:w-[75%] w-full m-auto">
<VersionHistory
:project-id="projectId"
:api-id="apiId"
Expand All @@ -138,7 +151,7 @@ watch([apiId, projectId], ([curApiId, curProjectId]) => {
/>
</TabsContent>

<TabsContent value="settings">
<TabsContent value="settings" class="2xl:w-[75%] w-full m-auto">
<ApiSettingsView :api="apiDetail" />
</TabsContent>
</ScrollArea>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
X,
} from 'lucide-vue-next'
import { computed, ref, toRaw, watch } from 'vue'
import { versionApi } from '@/api/version'
import CodeBlock from '@/components/common/CodeBlock.vue'
import { Badge } from '@/components/ui/badge'
import { ScrollArea } from '@/components/ui/scroll-area'
Expand All @@ -25,6 +24,7 @@ import {
import { methodBadgeColors } from '@/constants/api'
import { versionDiffFieldLabels, versionStatusColors, versionStatusLabels } from '@/constants/version'
import { cn } from '@/lib/utils'
import { useWorkbenchResourceStore } from '@/stores/useWorkbenchResourceStore'

const props = defineProps<{
projectId: string
Expand All @@ -48,6 +48,7 @@ interface ComparisonResult {
}

const isOpen = defineModel<boolean>('open', { required: true })
const resourceStore = useWorkbenchResourceStore()

const comparison = ref<ApiVersionComparison | null>(null)
const isLoading = ref(false)
Expand Down Expand Up @@ -124,47 +125,50 @@ function isComplexValue(value: unknown): boolean {
return typeof value === 'object' && value !== null
}

async function fetchComparison() {
if (!props.fromVersionId || !props.toVersionId)
return
function getComparisonRequestKey(
projectId = props.projectId,
apiId = props.apiId,
fromVersionId = props.fromVersionId,
toVersionId = props.toVersionId,
) {
return `${projectId}:${apiId}:${fromVersionId ?? ''}:${toVersionId ?? ''}`
}

async function fetchComparison(projectId: string, apiId: string, fromVersionId: string, toVersionId: string) {
const requestKey = getComparisonRequestKey(projectId, apiId, fromVersionId, toVersionId)
isLoading.value = true
loadError.value = null

try {
comparison.value = await versionApi.compareVersions(
props.projectId,
props.apiId,
props.fromVersionId,
props.toVersionId,
)
const result = await resourceStore.getVersionComparison(projectId, apiId, fromVersionId, toVersionId)
if (!isOpen.value || getComparisonRequestKey() !== requestKey)
return
comparison.value = result
}
catch (error) {
if (!isOpen.value || getComparisonRequestKey() !== requestKey)
return
loadError.value = `获取比较数据失败: ${error}`
console.error('Failed to fetch comparison:', error)
}
finally {
isLoading.value = false
if (!isOpen.value || getComparisonRequestKey() === requestKey) {
isLoading.value = false
}
}
}

watch(
() => [props.fromVersionId, props.toVersionId],
([from, to]) => {
if (from && to && isOpen.value) {
fetchComparison()
}
else {
() => [isOpen.value, props.projectId, props.apiId, props.fromVersionId, props.toVersionId] as const,
([open, projectId, apiId, from, to]) => {
if (!open || !from || !to) {
comparison.value = null
return
}
fetchComparison(projectId, apiId, from, to)
},
{ immediate: true },
)

watch(isOpen, (open) => {
if (open && props.fromVersionId && props.toVersionId) {
fetchComparison()
}
})
</script>

<template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
Tag,
} from 'lucide-vue-next'
import { computed, ref, watch } from 'vue'
import { versionApi } from '@/api/version'
import CodeBlock from '@/components/common/CodeBlock.vue'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
Expand All @@ -33,6 +32,7 @@ import {
} from '@/constants/version'
import dayjs from '@/lib/dayjs'
import { cn } from '@/lib/utils'
import { useWorkbenchResourceStore } from '@/stores/useWorkbenchResourceStore'

const props = defineProps<{
projectId: string
Expand All @@ -50,6 +50,7 @@ const emits = defineEmits<{
}>()

const isOpen = defineModel<boolean>('open', { required: true })
const resourceStore = useWorkbenchResourceStore()

const versionDetail = ref<ApiVersionDetail | null>(null)
const isLoading = ref(false)
Expand Down Expand Up @@ -90,48 +91,47 @@ const responsesPreview = computed(() => {
return JSON.stringify(versionDetail.value.responses, null, 2)
})

async function fetchVersionDetail() {
if (!props.versionId)
return
function getVersionDetailRequestKey(projectId = props.projectId, apiId = props.apiId, versionId = props.versionId) {
return `${projectId}:${apiId}:${versionId ?? ''}`
}

async function fetchVersionDetail(projectId: string, apiId: string, versionId: string) {
const requestKey = getVersionDetailRequestKey(projectId, apiId, versionId)
isLoading.value = true
loadError.value = null

try {
const detail = await versionApi.getVersionDetail(
props.projectId,
props.apiId,
props.versionId,
)
const detail = await resourceStore.getVersionDetail(projectId, apiId, versionId)
if (!isOpen.value || getVersionDetailRequestKey() !== requestKey)
return
versionDetail.value = detail
emits('update:versionData', detail)
}
catch (error) {
if (!isOpen.value || getVersionDetailRequestKey() !== requestKey)
return
loadError.value = `获取版本详情失败: ${error}`
console.error('Failed to fetch version detail:', error)
}
finally {
isLoading.value = false
if (!isOpen.value || getVersionDetailRequestKey() === requestKey) {
isLoading.value = false
}
}
}

watch(
() => props.versionId,
(newId) => {
if (newId && isOpen.value) {
fetchVersionDetail()
}
else {
() => [isOpen.value, props.projectId, props.apiId, props.versionId] as const,
([open, projectId, apiId, versionId]) => {
if (!open || !versionId) {
versionDetail.value = null
emits('update:versionData', null)
return
}
fetchVersionDetail(projectId, apiId, versionId)
},
{ immediate: true },
)

watch(isOpen, (open) => {
if (open && props.versionId) {
fetchVersionDetail()
}
})
</script>

<template>
Expand Down
Loading
Loading