This commit is contained in:
2025-12-04 12:44:54 +08:00
parent d687bf67b1
commit 3f33e5c2f1
8 changed files with 447 additions and 99 deletions

View File

@@ -10,6 +10,15 @@
</div>
<div class="list-page-actions">
<el-button @click="$router.back()">返回</el-button>
<el-button
v-if="product?.documentation"
type="info"
@click="downloadDocumentation"
:loading="downloading"
>
<el-icon><Download /></el-icon>
{{ product?.documentation?.pdf_file_path ? '下载PDF文档' : '下载接口文档' }}
</el-button>
<el-button
v-if="!isSubscribed"
type="primary"
@@ -20,10 +29,11 @@
</el-button>
<el-button
v-else
type="success"
disabled
type="danger"
@click="handleCancelSubscription"
:loading="cancelling"
>
订阅
取消订阅
</el-button>
<el-button
v-if="isSubscribed"
@@ -260,7 +270,7 @@
<script setup>
import { productApi, subscriptionApi } from '@/api'
import { DocumentCopy } from '@element-plus/icons-vue'
import { DocumentCopy, Download } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { marked } from 'marked'
@@ -272,6 +282,8 @@ const loading = ref(false)
const product = ref(null)
const userSubscriptions = ref([])
const subscribing = ref(false)
const cancelling = ref(false)
const downloading = ref(false)
const activeTab = ref('content')
const currentTimestamp = ref('')
@@ -288,6 +300,12 @@ const isSubscribed = computed(() => {
return userSubscriptions.value.some(sub => sub.product_id === product.value.id)
})
// 获取当前产品的订阅信息
const currentSubscription = computed(() => {
if (!product.value || !userSubscriptions.value.length) return null
return userSubscriptions.value.find(sub => sub.product_id === product.value.id)
})
// 初始化
onMounted(() => {
loadUserSubscriptions()
@@ -321,7 +339,7 @@ const startTimestampUpdate = () => {
// 加载用户订阅
const loadUserSubscriptions = async () => {
try {
const response = await subscriptionApi.getMySubscriptions({ page: 1, page_size: 100 })
const response = await subscriptionApi.getMySubscriptions({ page: 1, page_size: 1000 })
userSubscriptions.value = response.data?.items || []
} catch (error) {
console.error('加载用户订阅失败:', error)
@@ -528,19 +546,53 @@ const handleSubscribe = async () => {
} catch (error) {
if (error !== 'cancel') {
console.error('订阅失败:', error)
ElMessage.error('订阅失败')
const errorMessage = error.response?.data?.message || error.message || '订阅失败'
ElMessage.error(errorMessage)
}
} finally {
subscribing.value = false
}
}
// 取消订阅
const handleCancelSubscription = async () => {
if (!product.value || !currentSubscription.value) return
try {
await ElMessageBox.confirm(
`确定要取消订阅产品"${product.value.name}"吗取消后将无法继续使用该产品的API服务。`,
'取消订阅确认',
{
confirmButtonText: '确定取消',
cancelButtonText: '我再想想',
type: 'warning'
}
)
cancelling.value = true
await subscriptionApi.cancelMySubscription(currentSubscription.value.id)
ElMessage.success('取消订阅成功')
// 重新加载用户订阅
await loadUserSubscriptions()
} catch (error) {
if (error !== 'cancel') {
console.error('取消订阅失败:', error)
const errorMessage = error.response?.data?.message || error.message || '取消订阅失败'
ElMessage.error(errorMessage)
}
} finally {
cancelling.value = false
}
}
// 前往在线调试
const goToApiDebugger = () => {
if (!product.value) return
router.push({
name: 'ApiDebugger',
params: { productId: product.value.id }
query: { productId: product.value.id }
})
}
@@ -659,6 +711,65 @@ const getDefaultErrorCodes = () => {
| 2001 | 业务失败 |`
}
// 下载接口文档
const downloadDocumentation = async () => {
if (!product.value) {
ElMessage.warning('产品信息不存在')
return
}
downloading.value = true
try {
// 根据是否有PDF文件路径判断文件类型
const hasPDF = product.value.documentation?.pdf_file_path
// 使用原生fetch以获取完整的响应信息包括headers
const token = localStorage.getItem('access_token')
const tokenType = localStorage.getItem('token_type') || 'Bearer'
const headers = {
'Authorization': token ? `${tokenType} ${token}` : ''
}
const response = await fetch(`/api/v1/products/${product.value.id}/documentation/download`, {
headers
})
if (!response.ok) {
throw new Error('下载失败')
}
// 获取Content-Type
const contentType = response.headers.get('content-type') || ''
const isPDF = contentType.includes('application/pdf') || hasPDF
// 获取文件内容
const blob = await response.blob()
// 创建下载链接
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
const extension = isPDF ? 'pdf' : 'md'
const filename = `${product.value.name || '产品'}_接口文档.${extension}`
link.download = filename
// 触发下载
document.body.appendChild(link)
link.click()
// 清理
document.body.removeChild(link)
URL.revokeObjectURL(url)
ElMessage.success('文档下载成功')
} catch (error) {
console.error('下载接口文档失败:', error)
ElMessage.error('下载接口文档失败')
} finally {
downloading.value = false
}
}
// 下载 Markdown 文档
const downloadMarkdown = (type) => {
if (!product.value?.documentation) {

View File

@@ -50,9 +50,15 @@
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 p-4">
<ProductCard v-for="product in products" :key="product.id" :product="product"
:is-subscribed="product.is_subscribed" @view-detail="handleViewDetail"
@subscribe="handleSubscribe" />
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
:is-subscribed="getProductSubscriptionStatus(product.id)"
:subscription="getProductSubscription(product.id)"
@view-detail="handleViewDetail"
@subscribe="handleSubscribe"
@cancel-subscribe="handleCancelSubscribe" />
</div>
</template>
@@ -65,7 +71,7 @@
</template>
<script setup>
import { categoryApi, productApi } from '@/api'
import { categoryApi, productApi, subscriptionApi } from '@/api'
import FilterItem from '@/components/common/FilterItem.vue'
import FilterSection from '@/components/common/FilterSection.vue'
import ListPageLayout from '@/components/common/ListPageLayout.vue'
@@ -81,6 +87,7 @@ const categories = ref([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(12)
const userSubscriptions = ref([]) // 用户订阅列表
// 筛选条件
const filters = reactive({
@@ -96,6 +103,7 @@ let searchTimer = null
// 初始化
onMounted(() => {
loadCategories()
loadUserSubscriptions()
loadProducts()
})
@@ -109,6 +117,30 @@ const loadCategories = async () => {
}
}
// 加载用户订阅列表
const loadUserSubscriptions = async () => {
try {
const response = await subscriptionApi.getMySubscriptions({ page: 1, page_size: 1000 })
userSubscriptions.value = response.data?.items || []
} catch (error) {
// 如果未登录或获取失败,清空订阅列表
console.error('加载用户订阅失败:', error)
userSubscriptions.value = []
}
}
// 获取产品的订阅状态
const getProductSubscriptionStatus = (productId) => {
if (!productId || !userSubscriptions.value.length) return false
return userSubscriptions.value.some(sub => sub.product_id === productId)
}
// 获取产品的订阅信息
const getProductSubscription = (productId) => {
if (!productId || !userSubscriptions.value.length) return null
return userSubscriptions.value.find(sub => sub.product_id === productId) || null
}
// 加载产品列表
const loadProducts = async () => {
loading.value = true
@@ -122,6 +154,9 @@ const loadProducts = async () => {
const response = await productApi.getProducts(params)
products.value = response.data?.items || []
total.value = response.data?.total || 0
// 重新加载订阅列表以确保状态同步
await loadUserSubscriptions()
} catch (error) {
console.error('加载产品失败:', error)
ElMessage.error('加载产品失败')
@@ -191,12 +226,51 @@ const handleSubscribe = async (product) => {
await productApi.subscribeProduct(product.id)
ElMessage.success('订阅成功')
// 重新加载产品列表以更新订阅状态
// 重新加载订阅列表和产品列表以更新订阅状态
await loadUserSubscriptions()
await loadProducts()
} catch (error) {
if (error !== 'cancel') {
console.error('订阅失败:', error)
ElMessage.error('订阅失败')
const errorMessage = error.response?.data?.message || error.message || '订阅失败'
ElMessage.error(errorMessage)
}
}
}
// 取消订阅
const handleCancelSubscribe = async (product) => {
if (!product) return
// 获取该产品的订阅信息
const subscription = getProductSubscription(product.id)
if (!subscription || !subscription.id) {
ElMessage.error('订阅信息不完整')
return
}
try {
await ElMessageBox.confirm(
`确定要取消订阅产品"${product.name}"吗取消后将无法继续使用该产品的API服务。`,
'取消订阅确认',
{
confirmButtonText: '确定取消',
cancelButtonText: '我再想想',
type: 'warning'
}
)
await subscriptionApi.cancelMySubscription(subscription.id)
ElMessage.success('取消订阅成功')
// 重新加载订阅列表和产品列表以更新订阅状态
await loadUserSubscriptions()
await loadProducts()
} catch (error) {
if (error !== 'cancel') {
console.error('取消订阅失败:', error)
const errorMessage = error.response?.data?.message || error.message || '取消订阅失败'
ElMessage.error(errorMessage)
}
}
}