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

@@ -57,6 +57,11 @@ export const productApi = {
getProductApiConfigByCode: (productCode) => request.get(`/products/code/${productCode}/api-config`),
getProductApiConfigsByProductIDs: (productIds) => request.get('/products/api-configs', {
params: { product_ids: productIds.join(',') }
}),
// 下载接口文档支持PDF和Markdown
downloadProductDocumentation: (productId) => request.get(`/products/${productId}/documentation/download`, {
responseType: 'blob'
})
}
@@ -81,7 +86,10 @@ export const subscriptionApi = {
getMySubscriptionDetail: (id) => request.get(`/my/subscriptions/${id}`),
// 获取我的订阅使用情况 (需认证)
getMySubscriptionUsage: (id) => request.get(`/my/subscriptions/${id}/usage`)
getMySubscriptionUsage: (id) => request.get(`/my/subscriptions/${id}/usage`),
// 取消我的订阅 (需认证)
cancelMySubscription: (id) => request.post(`/my/subscriptions/${id}/cancel`)
}
// 财务相关接口

View File

@@ -73,12 +73,12 @@
<!-- 已订阅的产品 -->
<el-button
v-else-if="isSubscribed"
type="info"
disabled
class="action-btn subscribed-btn"
type="danger"
@click="handleCancelSubscribe"
class="action-btn cancel-subscribe-btn"
size="small"
>
订阅
取消订阅
</el-button>
<!-- 可订阅的产品 -->
@@ -104,10 +104,14 @@ const props = defineProps({
isSubscribed: {
type: Boolean,
default: false
},
subscription: {
type: Object,
default: null
}
})
const emit = defineEmits(['view-detail', 'subscribe'])
const emit = defineEmits(['view-detail', 'subscribe', 'cancel-subscribe'])
// 格式化价格
const formatPrice = (price) => {
@@ -134,6 +138,11 @@ const handleViewDetail = () => {
const handleSubscribe = () => {
emit('subscribe', props.product)
}
// 取消订阅
const handleCancelSubscribe = () => {
emit('cancel-subscribe', props.product)
}
</script>
<style scoped>
@@ -357,19 +366,17 @@ const handleSubscribe = () => {
box-shadow: 0 4px 8px rgba(16, 185, 129, 0.3);
}
.subscribed-btn {
background: rgba(100, 116, 139, 0.1);
border-color: rgba(100, 116, 139, 0.2);
color: #64748b;
cursor: not-allowed;
.cancel-subscribe-btn {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
border-color: #ef4444;
color: white;
box-shadow: 0 2px 4px rgba(239, 68, 68, 0.2);
}
.subscribed-btn:hover {
background: rgba(100, 116, 139, 0.1);
border-color: rgba(100, 116, 139, 0.2);
color: #64748b;
transform: none;
box-shadow: none;
.cancel-subscribe-btn:hover {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
border-color: #dc2626;
box-shadow: 0 4px 8px rgba(239, 68, 68, 0.3);
}
.disabled-btn {

View File

@@ -414,13 +414,25 @@
<div v-if="decryptedData" class="mt-3 pt-3 border-t border-gray-200">
<h5 class="text-xs font-semibold text-gray-700 mb-1">解密后的内容</h5>
<pre
class="bg-green-50 p-2 rounded text-xs overflow-x-auto m-0 mb-1.5 max-h-45 border border-green-200 font-mono text-green-800">
class="bg-green-50 p-2 rounded text-xs overflow-x-auto m-0 mb-1.5 max-h-45 border border-green-200 font-mono text-green-800"
:key="`decrypted-${Date.now()}`">
{{ JSON.stringify(decryptedData, null, 2) }}</pre>
<el-button type="success" size="default"
@click="copyToClipboard(JSON.stringify(decryptedData, null, 2))" class="w-full">
复制解密内容
</el-button>
</div>
<!-- 解密加载状态 -->
<div v-else-if="debugging && debugResult && debugResult.success && debugResult.response?.data?.data"
class="mt-3 pt-3 border-t border-gray-200">
<div class="flex items-center gap-2 text-sm text-gray-500">
<svg class="animate-spin h-4 w-4 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>正在解密响应数据...</span>
</div>
</div>
</div>
</div>
</div>
@@ -545,6 +557,8 @@ const productsLoading = ref(false)
const debugging = ref(false)
const encrypting = ref(false)
const autoSelecting = ref(false) // 新增:自动选择状态
const isSelectingProduct = ref(false) // 防止重复选择产品的标志
const lastSelectedProductId = ref(null) // 记录最后选择的产品ID
const userProducts = ref([])
const apiConfig = ref(null)
const selectedProduct = ref(null)
@@ -650,12 +664,22 @@ onMounted(async () => {
// 监听路由参数变化,自动选择产品
watch(
() => route.params.productId,
async (newProductId) => {
() => route.query.productId || route.params.productId,
async (newProductId, oldProductId) => {
// 防止重复触发如果产品ID没有变化不执行
if (newProductId === oldProductId) {
return
}
// 如果正在选择产品,不重复执行
if (isSelectingProduct.value) {
return
}
if (newProductId && userProducts.value.length > 0) {
await autoSelectProduct(newProductId)
} else if (!newProductId && userProducts.value.length > 0) {
// 如果没有指定产品,选择第一个
} else if (!newProductId && userProducts.value.length > 0 && !selectedProduct.value) {
// 如果没有指定产品且当前没有选中产品,选择第一个
await selectProduct(userProducts.value[0])
}
}
@@ -663,6 +687,18 @@ watch(
// 自动选择产品
const autoSelectProduct = async (productId) => {
// 防止重复执行
if (isSelectingProduct.value) {
console.log('正在选择产品,跳过重复请求')
return
}
// 如果已经选择了相同的产品,不重复选择
if (lastSelectedProductId.value === productId && selectedProduct.value) {
console.log('产品已选择,跳过重复选择:', productId)
return
}
// 如果用户产品列表为空,等待加载完成
if (!userProducts.value.length) {
console.log('等待用户产品列表加载完成...')
@@ -686,7 +722,13 @@ const autoSelectProduct = async (productId) => {
if (targetProduct) {
console.log('自动选择产品:', targetProduct)
await selectProduct(targetProduct)
isSelectingProduct.value = true
try {
await selectProduct(targetProduct)
lastSelectedProductId.value = productId
} finally {
isSelectingProduct.value = false
}
} else {
console.warn('未找到指定的产品:', productId)
ElMessage.warning(`未找到产品ID为 ${productId} 的产品,请手动选择`)
@@ -699,7 +741,7 @@ const loadUserProducts = async () => {
try {
const response = await subscriptionApi.getMySubscriptions({
page: 1,
page_size: 100
page_size: 1000
})
if (response.success && response.data?.items) {
@@ -712,16 +754,23 @@ const loadUserProducts = async () => {
}))
console.log("route.params", route)
// 检查是否有params参数指定产品
if (route.params.productId && userProducts.value.length > 0) {
// 检查是否有query或params参数指定产品
const productId = route.query.productId || route.params.productId
if (productId && userProducts.value.length > 0) {
await nextTick()
await autoSelectProduct(route.params.productId)
} else if (userProducts.value.length > 0) {
// 没有指定产品时,默认选择第一个
// 使用 autoSelectProduct 会自动处理防重复逻辑
await autoSelectProduct(productId)
} else if (userProducts.value.length > 0 && !selectedProduct.value) {
// 没有指定产品时,默认选择第一个(仅在未选择产品时)
autoSelecting.value = true
await selectProduct(userProducts.value[0])
autoSelecting.value = false
isSelectingProduct.value = true
try {
await selectProduct(userProducts.value[0])
lastSelectedProductId.value = userProducts.value[0].id || userProducts.value[0].product_id
} finally {
isSelectingProduct.value = false
autoSelecting.value = false
}
}
} else {
// 如果没有订阅产品,显示提示信息
@@ -753,6 +802,15 @@ const loadApiKeys = async () => {
// 选择产品
const selectProduct = async (product) => {
// 防止重复选择相同产品
const productId = product.product_id || product.id
if (selectedProduct.value &&
(selectedProduct.value.id === productId || selectedProduct.value.product_id === productId) &&
!isSelectingProduct.value) {
console.log('产品已选择,跳过重复加载:', productId)
return
}
// 确保API密钥已经加载
if (!debugForm.accessId || !debugForm.secretKey) {
ElMessage.warning('正在加载API密钥请稍候...')
@@ -764,6 +822,7 @@ const selectProduct = async (product) => {
debugForm.params = {}
debugResult.value = null
encryptedData.value = null
decryptedData.value = null
activeTab.value = 'basic_info' // 重置Tab
productDocumentation.value = null // 重置文档
@@ -1106,6 +1165,11 @@ const handleDebug = async () => {
}
debugging.value = true
// 清空之前的调试结果确保UI实时更新
debugResult.value = null
decryptedData.value = null
await nextTick() // 确保DOM更新
const startTime = new Date()
try {
@@ -1118,22 +1182,6 @@ const handleDebug = async () => {
return
}
// 1.5. 类型转换:将 page_size 和 page_num 从字符串转换为数字
if (parsedParams && typeof parsedParams === 'object') {
if (parsedParams.page_size !== undefined && typeof parsedParams.page_size === 'string') {
const pageSize = parseInt(parsedParams.page_size, 10)
if (!isNaN(pageSize)) {
parsedParams.page_size = pageSize
}
}
if (parsedParams.page_num !== undefined && typeof parsedParams.page_num === 'string') {
const pageNum = parseInt(parsedParams.page_num, 10)
if (!isNaN(pageNum)) {
parsedParams.page_num = pageNum
}
}
}
// 2. 加密参数
const encryptedParams = await encryptWithAES(parsedParams, debugForm.secretKey)
if (!encryptedParams) {
@@ -1159,7 +1207,7 @@ const handleDebug = async () => {
console.log('产品API调用成功:', responseData)
const endTime = new Date()
// 5. 保存调试结果
// 5. 保存调试结果 - 立即更新确保UI实时显示
debugResult.value = createDebugResult(
selectedProduct.value,
requestBody,
@@ -1170,6 +1218,9 @@ const handleDebug = async () => {
responseData.success && responseData.data?.code === 0
)
// 确保DOM更新后再进行解密操作
await nextTick()
// 6. 如果响应成功且包含加密数据,自动解密
console.log('responseData', responseData)
if (responseData.success && responseData.data?.code === 0 && responseData.data?.data && typeof responseData.data.data === 'string') {
@@ -1180,7 +1231,9 @@ const handleDebug = async () => {
)
if (decryptResult.success) {
// 使用 nextTick 确保响应式更新
decryptedData.value = decryptResult.data
await nextTick()
ElMessage.success('调试完成,数据已自动解密')
} else {
ElMessage.warning('调试完成,但数据解密失败:' + (decryptResult.message || '未知错误'))
@@ -1223,6 +1276,9 @@ const handleDebug = async () => {
false
)
// 确保DOM更新
await nextTick()
ElMessage.error('API调用失败' + apiError.message)
}
} catch (error) {
@@ -1239,6 +1295,9 @@ const handleDebug = async () => {
endTime,
false
)
// 确保DOM更新
await nextTick()
} finally {
debugging.value = false
}

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)
}
}
}

View File

@@ -131,7 +131,7 @@
</template>
</el-table-column>
<el-table-column label="操作" width="320" fixed="right">
<el-table-column label="操作" width="400" fixed="right">
<template #default="{ row }">
<div class="flex items-center space-x-2">
<el-button
@@ -155,6 +155,13 @@
>
在线调试
</el-button>
<el-button
size="small"
type="danger"
@click="handleCancelSubscription(row)"
>
取消订阅
</el-button>
</div>
</template>
</el-table-column>
@@ -216,7 +223,7 @@ import { subscriptionApi } from '@/api'
import FilterItem from '@/components/common/FilterItem.vue'
import FilterSection from '@/components/common/FilterSection.vue'
import ListPageLayout from '@/components/common/ListPageLayout.vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter()
@@ -383,6 +390,50 @@ const goToApiDebugger = (product) => {
})
}
}
// 取消订阅
const handleCancelSubscription = async (subscription) => {
if (!subscription || !subscription.id) {
ElMessage.error('订阅信息不完整')
return
}
try {
// 显示确认对话框
await ElMessageBox.confirm(
`确定要取消订阅 "${subscription.product?.name || '该产品'}" 吗取消后将无法继续使用该产品的API服务。`,
'取消订阅确认',
{
confirmButtonText: '确定取消',
cancelButtonText: '我再想想',
type: 'warning',
dangerouslyUseHTMLString: false
}
)
// 用户确认后执行取消操作
loading.value = true
try {
await subscriptionApi.cancelMySubscription(subscription.id)
ElMessage.success('取消订阅成功')
// 重新加载订阅列表
await loadSubscriptions()
// 重新加载统计数据
await loadStats()
} catch (error) {
console.error('取消订阅失败:', error)
const errorMessage = error.response?.data?.message || error.message || '取消订阅失败'
ElMessage.error(errorMessage)
} finally {
loading.value = false
}
} catch (error) {
// 用户取消操作,不做任何处理
if (error !== 'cancel') {
console.error('取消订阅操作异常:', error)
}
}
}
</script>
<style scoped>

View File

@@ -315,9 +315,18 @@ const router = createRouter({
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
// 等待userStore初始化完成
if (!userStore.initialized) {
// 对于不需要认证的路由(如登录页),不等待初始化,直接放行
const isAuthRoute = to.path.startsWith('/auth')
const requiresAuth = to.meta.requiresAuth
// 只有在需要认证的路由上才等待初始化
if (requiresAuth && !userStore.initialized) {
await userStore.init()
} else if (!userStore.initialized) {
// 对于不需要认证的路由,异步初始化但不阻塞
userStore.init().catch(err => {
console.warn('UserStore初始化失败:', err)
})
}
// 设置页面标题
@@ -326,7 +335,7 @@ router.beforeEach(async (to, from, next) => {
}
// 检查是否需要认证
if (to.meta.requiresAuth && !userStore.isLoggedIn) {
if (requiresAuth && !userStore.isLoggedIn) {
next('/auth/login')
return
}
@@ -338,7 +347,7 @@ router.beforeEach(async (to, from, next) => {
}
// 已登录用户访问认证页面,重定向到数据大厅
if (to.path.startsWith('/auth') && userStore.isLoggedIn) {
if (isAuthRoute && userStore.isLoggedIn) {
next('/products')
return
}

View File

@@ -135,9 +135,9 @@ export const useUserStore = defineStore('user', () => {
// 检查用户信息是否完整
const isUserInfoComplete = computed(() => {
return user.value &&
user.value.id &&
user.value.phone &&
user.value.user_type !== undefined
user.value.id &&
user.value.phone &&
user.value.user_type !== undefined
})
// 强制刷新用户信息
@@ -416,44 +416,73 @@ export const useUserStore = defineStore('user', () => {
}
}
// 初始化标志,防止重复初始化
let isInitializing = false
// 初始化
const init = async () => {
// 监听认证错误事件
authEventBus.onAuthError(handleAuthError)
// 监听版本更新事件
window.addEventListener('version:logout', handleVersionLogout)
window.addEventListener('version:refresh', handleVersionRefresh)
// 进行版本检查
if (!checkVersions()) {
// 如果已经初始化完成,直接返回
if (initialized.value) {
return
}
if (accessToken.value && !user.value) {
// 有token但无用户信息自动拉取
loading.value = true
try {
const result = await fetchUserProfile()
isAuthenticated.value = result.success
// 如果正在初始化,等待完成
if (isInitializing) {
// 等待初始化完成
while (isInitializing) {
await new Promise(resolve => setTimeout(resolve, 50))
}
return
}
// 如果认证成功,启动版本检查器
if (result.success) {
isInitializing = true
try {
// 监听认证错误事件(只注册一次)
if (!authEventBus.listeners.includes(handleAuthError)) {
authEventBus.onAuthError(handleAuthError)
}
// 监听版本更新事件(只注册一次)
if (!window.hasVersionListeners) {
window.addEventListener('version:logout', handleVersionLogout)
window.addEventListener('version:refresh', handleVersionRefresh)
window.hasVersionListeners = true
}
// 进行版本检查
if (!checkVersions()) {
initialized.value = true
return
}
if (accessToken.value && !user.value) {
// 有token但无用户信息自动拉取
loading.value = true
try {
const result = await fetchUserProfile()
isAuthenticated.value = result.success
// 如果认证成功,启动版本检查器
if (result.success) {
versionChecker.startAutoCheck()
}
} catch {
isAuthenticated.value = false
logout()
} finally {
loading.value = false
initialized.value = true
}
} else {
// 如果已经认证,启动版本检查器
if (isAuthenticated.value) {
versionChecker.startAutoCheck()
}
} catch {
isAuthenticated.value = false
logout()
} finally {
loading.value = false
initialized.value = true
}
} else {
// 如果已经认证,启动版本检查器
if (isAuthenticated.value) {
versionChecker.startAutoCheck()
}
initialized.value = true
} finally {
isInitializing = false
}
}