From df4cdb24b3d9929c4393890453f465948c08a68a Mon Sep 17 00:00:00 2001
From: 18278715334 <18278715334@163.com>
Date: Mon, 22 Dec 2025 18:32:34 +0800
Subject: [PATCH] =?UTF-8?q?=E8=B4=AD=E4=B9=B0=E5=8A=9F=E8=83=BDfix?=
=?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=A0=B7=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
auto-imports.d.ts | 2 +-
src/api/index.js | 24 +-
src/components/admin/ProductFormDialog.vue | 71 +-
src/constants/menu.js | 7 +
src/pages/admin/purchase-records/index.vue | 857 +++++++++++++++++++
src/pages/admin/ui-components/index.vue | 680 ++++++++++++---
src/pages/admin/users/index.vue | 16 +-
src/pages/finance/purchase-records/index.vue | 555 ++++++++++++
src/pages/products/detail.vue | 832 +++++++++++++++++-
src/router/index.js | 12 +
src/utils/request.js | 10 +-
11 files changed, 2917 insertions(+), 149 deletions(-)
create mode 100644 src/pages/admin/purchase-records/index.vue
create mode 100644 src/pages/finance/purchase-records/index.vue
diff --git a/auto-imports.d.ts b/auto-imports.d.ts
index 0d9fc22..6fdca1a 100644
--- a/auto-imports.d.ts
+++ b/auto-imports.d.ts
@@ -7,7 +7,7 @@
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
- const ElLoading: typeof import('element-plus')['ElLoading']
+ const ElLoading: typeof import('element-plus/es')['ElLoading']
const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const ElNotification: typeof import('element-plus')['ElNotification']
diff --git a/src/api/index.js b/src/api/index.js
index d5d23b8..1b98333 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -63,7 +63,19 @@ export const productApi = {
// 下载接口文档(支持PDF和Markdown)
downloadProductDocumentation: (productId) => request.get(`/products/${productId}/documentation/download`, {
responseType: 'blob'
- })
+ }),
+
+ // 组件报告下载相关API
+ // 检查产品是否可以下载示例报告
+ checkComponentReportAvailability: (productId) => request.get(`/products/${productId}/component-report/check`),
+ // 获取产品示例报告下载信息和价格计算
+ getComponentReportInfo: (productId) => request.get(`/products/${productId}/component-report/info`),
+ // 创建示例报告下载支付订单
+ createComponentReportPaymentOrder: (productId, data) => request.post(`/products/${productId}/component-report/create-order`, data),
+ // 检查示例报告下载支付状态
+ checkComponentReportPaymentStatus: (orderId) => request.get(`/component-report/check-payment/${orderId}`),
+ // 生成并下载示例报告ZIP文件
+ generateAndDownloadComponentReport: (data) => request.post('/component-report/generate-and-download', data, { responseType: 'blob' })
}
// 分类相关接口 - 数据大厅
@@ -130,7 +142,15 @@ export const financeApi = {
// 充值记录相关接口
getUserRechargeRecords: (params) => request.get('/finance/wallet/recharge-records', { params }),
- getAdminRechargeRecords: (params) => request.get('/admin/finance/recharge-records', { params })
+ getAdminRechargeRecords: (params) => request.get('/admin/finance/recharge-records', { params }),
+
+ // 购买记录相关接口
+ getUserPurchaseRecords: (params) => request.get('/finance/purchase-records', { params }),
+ getAdminPurchaseRecords: (params) => request.get('/admin/finance/purchase-records', { params }),
+ exportAdminPurchaseRecords: (params) => request.get('/admin/finance/purchase-records/export', {
+ params,
+ responseType: 'blob'
+ })
}
// 认证相关接口
diff --git a/src/components/admin/ProductFormDialog.vue b/src/components/admin/ProductFormDialog.vue
index a6e2deb..e73e8cb 100644
--- a/src/components/admin/ProductFormDialog.vue
+++ b/src/components/admin/ProductFormDialog.vue
@@ -114,6 +114,45 @@
+
+
+
+
UI组件配置
+
+
+ 配置此组合包的UI组件销售选项,用户可以单独购买此组合包的UI组件示例。
+
+
+
+
+
+
+
+
+
+
+
@@ -375,6 +414,9 @@ const form = reactive({
is_enabled: true,
is_visible: true,
is_package: false,
+ // UI组件相关字段
+ sell_ui_component: false,
+ ui_component_price: 0,
seo_title: '',
seo_description: '',
seo_keywords: ''
@@ -399,6 +441,22 @@ const rules = {
cost_price: [
{ type: 'number', min: 0, message: '成本价不能小于0', trigger: 'blur' }
],
+ ui_component_price: [
+ {
+ type: 'number',
+ min: 0,
+ message: 'UI组件价格不能小于0',
+ trigger: 'blur',
+ validator: (rule, value, callback) => {
+ // 如果是组合包且出售UI组件,则价格必须大于0
+ if (form.is_package && form.sell_ui_component && (!value || value < 0)) {
+ callback(new Error('出售UI组件时,价格不能小于0'))
+ } else {
+ callback()
+ }
+ }
+ }
+ ],
remark: [
{ max: 1000, message: '备注长度不能超过1000个字符', trigger: 'blur' }
],
@@ -467,6 +525,15 @@ const handleEditMode = async () => {
form[key] = props.product[key]
}
})
+
+ // 确保UI组件相关字段正确加载
+ // 如果后端返回的字段名不同,这里可以添加映射
+ if (props.product.sell_ui_component !== undefined) {
+ form.sell_ui_component = props.product.sell_ui_component
+ }
+ if (props.product.ui_component_price !== undefined) {
+ form.ui_component_price = props.product.ui_component_price
+ }
// 如果是组合包,处理子产品数据
if (props.product.is_package) {
@@ -502,8 +569,10 @@ const handleCreateMode = () => {
// 保持默认值 true,但允许用户在界面上切换
// 注意:这里不强制设置,让用户可以在界面上自由切换
// 默认值已经在 form 初始化时设置为 true(第375-376行)
- } else if (key === 'price' || key === 'cost_price') {
+ } else if (key === 'price' || key === 'cost_price' || key === 'ui_component_price') {
form[key] = 0
+ } else if (key === 'sell_ui_component') {
+ form[key] = false
} else {
form[key] = ''
}
diff --git a/src/constants/menu.js b/src/constants/menu.js
index b451b12..1c8ba00 100644
--- a/src/constants/menu.js
+++ b/src/constants/menu.js
@@ -41,6 +41,7 @@ export const userMenuItems = [
children: [
{ name: '余额充值', path: '/finance/wallet', icon: CreditCard, requiresCertification: true },
{ name: '充值记录', path: '/finance/recharge-records', icon: CreditCard, requiresCertification: true },
+ { name: '购买记录', path: '/finance/purchase-records', icon: ShoppingCart, requiresCertification: true },
{ name: '消费记录', path: '/finance/transactions', icon: Clipboard, requiresCertification: true },
{ name: '发票申请', path: '/finance/invoice', icon: Wallet, requiresCertification: true }
]
@@ -124,6 +125,7 @@ export const getUserAccessibleMenuItems = (userType = 'user') => {
{ name: '调用记录', path: '/admin/usage', icon: Clipboard },
{ name: '消费记录', path: '/admin/transactions', icon: Clipboard },
{ name: '充值记录', path: '/admin/recharge-records', icon: CreditCard },
+ { name: '购买记录', path: '/admin/purchase-records', icon: ShoppingCart },
{ name: '发票管理', path: '/admin/invoices', icon: Wallet },
{ name: '组件管理', path: '/admin/ui-components', icon: Cube }
]
@@ -137,6 +139,7 @@ export const getUserAccessibleMenuItems = (userType = 'user') => {
export const requiresCertificationPaths = [
'/finance/wallet',
'/finance/recharge-records',
+ '/finance/purchase-records',
'/finance/transactions',
'/apis/usage',
'/apis/whitelist'
@@ -166,6 +169,10 @@ export const getCurrentPageCertificationConfig = (path) => {
title: '充值记录',
description: '为了查看完整的充值记录,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!'
},
+ '/finance/purchase-records': {
+ title: '购买记录',
+ description: '为了查看完整的购买记录,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!'
+ },
'/finance/transactions': {
title: '消费记录',
description: '为了查看完整的消费记录,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!'
diff --git a/src/pages/admin/purchase-records/index.vue b/src/pages/admin/purchase-records/index.vue
new file mode 100644
index 0000000..0a173ca
--- /dev/null
+++ b/src/pages/admin/purchase-records/index.vue
@@ -0,0 +1,857 @@
+
+
+
+
+
+
+ 当前用户:{{ currentUser?.company_name || currentUser?.phone }}
+ (仅显示当前用户)
+
+
+
+
+
+
+
+
+ {{ currentUser?.enterprise_info?.company_name || currentUser?.phone }}
+
+
+
+ 取消
+
+
+
+ 返回用户管理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+ 共找到 {{ total }} 条购买记录
+
+
+ 重置筛选
+ 应用筛选
+
+
+ 导出数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
商户订单:
+
{{ row.order_no }}
+
交易号:
+
{{ row.trade_no }}
+
+
+
+
+
+
+
+
{{ row.company_name || '未知企业' }}
+
{{ row.user?.phone }}
+
+
+
+
+
+
+
+
{{ row.product_name }}
+
{{ row.product_code }}
+
+
+
+
+
+
+ ¥{{ formatPrice(row.amount) }}
+
+
+
+
+
+
+ {{ getPaymentTypeText(row.payment_type) }}
+
+
+
+
+
+
+
+ {{ getPayChannelText(row.pay_channel) }}
+
+
+
+
+
+
+
+ {{ getStatusText(row.status) }}
+
+
+
+
+
+
+
+
{{ formatDate(row.pay_time) }}
+
{{ formatTime(row.pay_time) }}
+
-
+
+
+
+
+
+
+
+
+ 查看详情
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
基本信息
+
+
+ 订单号
+ {{ selectedPurchaseRecord?.order_no || '-' }}
+
+
+ 交易号
+ {{ selectedPurchaseRecord?.trade_no || '-' }}
+
+
+ 产品名称
+ {{ selectedPurchaseRecord?.product_name || '-' }}
+
+
+ 产品代码
+ {{ selectedPurchaseRecord?.product_code || '-' }}
+
+
+ 订单金额
+ ¥{{ formatPrice(selectedPurchaseRecord?.amount) }}
+
+
+ 支付类型
+
+
+ {{ getPaymentTypeText(selectedPurchaseRecord?.payment_type) }}
+
+
+
+
+ 支付渠道
+
+
+ {{ getPayChannelText(selectedPurchaseRecord?.pay_channel) }}
+
+
+
+
+ 订单状态
+
+
+ {{ getStatusText(selectedPurchaseRecord?.status) }}
+
+
+
+
+ 企业名称
+ {{
+ selectedPurchaseRecord?.company_name || '未知企业'
+ }}
+
+
+
+
+
+
+
时间信息
+
+
+ 创建时间
+ {{
+ formatDateTime(selectedPurchaseRecord?.created_at)
+ }}
+
+
+ 支付时间
+ {{
+ formatDateTime(selectedPurchaseRecord?.pay_time)
+ }}
+
+
+ 更新时间
+ {{
+ formatDateTime(selectedPurchaseRecord?.updated_at)
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/admin/ui-components/index.vue b/src/pages/admin/ui-components/index.vue
index ac8081c..90872ac 100644
--- a/src/pages/admin/ui-components/index.vue
+++ b/src/pages/admin/ui-components/index.vue
@@ -1,126 +1,328 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- 搜索
- 重置
- 新增UI组件
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- 已解压
- 已上传
- 未上传
-
-
-
-
- {{ formatFileSize(row.file_size) }}
- -
-
-
-
-
-
- {{ row.is_active ? '启用' : '禁用' }}
-
-
-
-
-
-
- {{ formatDateTime(row.created_at) }}
-
-
-
-
- 编辑
-
+
- 上传文件
-
-
- 上传并解压
-
-
- 查看文件夹
-
-
- 下载文件
-
-
- 删除文件夹
-
- 删除
-
-
-
+
+
+
+
+
-
\ No newline at end of file
diff --git a/src/pages/admin/users/index.vue b/src/pages/admin/users/index.vue
index f3acdfd..9c57036 100644
--- a/src/pages/admin/users/index.vue
+++ b/src/pages/admin/users/index.vue
@@ -178,6 +178,10 @@
充值记录
+
+
+ 购买记录
+
@@ -367,6 +371,10 @@
充值记录
+
+
+ 购买记录
+
@@ -643,7 +651,7 @@ import FilterItem from '@/components/common/FilterItem.vue'
import FilterSection from '@/components/common/FilterSection.vue'
import ListPageLayout from '@/components/common/ListPageLayout.vue'
import { useMobileTable } from '@/composables/useMobileTable'
-import { ArrowDown, Document, Money, Tickets, Wallet, Warning } from '@element-plus/icons-vue'
+import { ArrowDown, Document, Money, ShoppingCart, Tickets, Wallet, Warning } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
@@ -950,6 +958,12 @@ const handleMoreAction = (command) => {
query: { user_id: user.id }
})
break
+ case 'purchase_records':
+ router.push({
+ name: 'AdminPurchaseRecords',
+ query: { user_id: user.id }
+ })
+ break
default:
ElMessage.warning('未知操作')
}
diff --git a/src/pages/finance/purchase-records/index.vue b/src/pages/finance/purchase-records/index.vue
new file mode 100644
index 0000000..891e055
--- /dev/null
+++ b/src/pages/finance/purchase-records/index.vue
@@ -0,0 +1,555 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 共找到 {{ total }} 条购买记录
+
+
+
+ 重置筛选
+ 应用筛选
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 商户订单:
+ {{ row.order_no }}
+
+
+ 交易号:
+ {{ row.trade_no }}
+
+
+
+
+
+
+
+
+
{{ row.product_name }}
+
{{ row.product_code }}
+
+
+
+
+
+
+
+ ¥{{ formatMoney(row.amount) }}
+
+
+
+
+
+
+
+ {{ getPaymentTypeText(row.payment_type) }}
+
+
+
+
+
+
+
+ {{ getPayChannelText(row.pay_channel) }}
+
+
+
+
+
+
+
+ {{ getStatusText(row.status) }}
+
+
+
+
+
+
+
+
{{ formatDate(row.pay_time) }}
+
{{ formatTime(row.pay_time) }}
+
-
+
+
+
+
+
+
+
+
{{ formatDate(row.created_at) }}
+
{{ formatTime(row.created_at) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/products/detail.vue b/src/pages/products/detail.vue
index 68a3808..6ceeeb2 100644
--- a/src/pages/products/detail.vue
+++ b/src/pages/products/detail.vue
@@ -51,6 +51,28 @@
>
前往在线调试
+
+
+
+ {{ canDownloadReport ? '下载示例报告' : '购买示例报告' }}
+
+
+
+
+ 检查支付状态
+
@@ -274,6 +296,82 @@
+
+
+
+
+
+
+ 您正在购买{{ reportDownloadInfo.product_name }}的示例报告
+
+
+
+
+
包含的组件
+
+
+ {{ item.product_code }}
+ ✓
+
+
+
+ 暂无组件信息
+
+
+
+
+
价格信息
+
+
+ 价格:
+ ¥{{ formatPrice(product.ui_component_price) }}
+
+
+
+
+
+
+
+
+ 取消
+
+
@@ -281,8 +379,9 @@
import { productApi, subscriptionApi } from '@/api'
import { useMobileTable } from '@/composables/useMobileTable'
import { DocumentCopy, Download } from '@element-plus/icons-vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
+import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
import { marked } from 'marked'
+import { h } from 'vue'
const route = useRoute()
const router = useRouter()
@@ -297,8 +396,16 @@ const userSubscriptions = ref([])
const subscribing = ref(false)
const cancelling = ref(false)
const downloading = ref(false)
+const downloadingReport = ref(false)
const activeTab = ref('content')
const currentTimestamp = ref('')
+const showDownloadReportButton = ref(false)
+const canDownloadReport = ref(false)
+const reportDownloadInfo = ref(null)
+const reportPaymentDialogVisible = ref(false)
+const reportPaymentStatus = ref('pending')
+const currentReportOrderId = ref('')
+const checkingPaymentStatus = ref(false)
// DOM 引用
const basicInfoRef = ref(null)
@@ -324,15 +431,35 @@ onMounted(() => {
loadUserSubscriptions()
loadProductDetail().then(() => {
addCollapsibleFeatures()
+ // 检查是否可以下载示例报告
+ if (product.value?.is_package) {
+ checkReportDownloadAvailability()
+ }
})
startTimestampUpdate()
})
// 组件卸载时清理定时器
onUnmounted(() => {
+ // 清理时间戳更新定时器
if (timestampTimer.value) {
clearInterval(timestampTimer.value)
}
+
+ // 清理支付状态检查定时器
+ if (window.paymentCheckIntervals) {
+ Object.keys(window.paymentCheckIntervals).forEach(orderId => {
+ clearInterval(window.paymentCheckIntervals[orderId])
+ })
+ window.paymentCheckIntervals = {}
+ }
+
+ // 关闭所有消息框
+ ElMessageBox.close()
+
+ // 关闭所有加载提示
+ const loadingInstance = ElLoading.service()
+ loadingInstance.close()
})
// 时间戳更新定时器
@@ -654,6 +781,29 @@ const openProductDetail = (productId) => {
window.open(url, '_blank')
}
+/*
+ * 示例报告下载功能说明
+ *
+ * 功能概述:
+ * - 只有组合包产品才能下载示例报告
+ * - 根据用户是否已下载过子产品,计算价格(已下载的产品不需要再次付费)
+ * - 支持微信和支付宝两种支付方式
+ * - 支付成功后可以下载包含所有子产品UI组件和示例数据的ZIP文件
+ *
+ * 流程说明:
+ * 1. 检查产品是否为组合包,以及是否可以下载示例报告
+ * 2. 如果可以直接下载(免费或已付费),直接提供下载
+ * 3. 如果需要付费,显示支付对话框,包含价格明细和支付方式选择
+ * 4. 用户选择支付方式后,创建支付订单
+ * 5. 定期检查支付状态,成功后自动下载示例报告
+ *
+ * 注意事项:
+ * - 微信支付显示二维码,需要用户扫码支付
+ * - 支付宝支付会跳转到支付宝页面
+ * - 支付状态检查会在后台持续进行,直到成功或超时
+ * - 下载的ZIP文件包含UI组件和示例数据
+ */
+
// 获取默认的请求方式内容
const getDefaultBasicInfo = () => {
return `## 请求头
@@ -825,6 +975,532 @@ const downloadMarkdown = (type) => {
ElMessage.success('文档下载成功')
}
+
+// 检查报告下载可用性
+const checkReportDownloadAvailability = async () => {
+ console.log('[checkReportDownloadAvailability] 开始检查下载可用性')
+ console.log('[checkReportDownloadAvailability] 产品信息:', product.value)
+ console.log('[checkReportDownloadAvailability] sell_ui_component:', product.value?.sell_ui_component)
+
+ if (!product.value?.id || !product.value?.is_package) {
+ console.log('[checkReportDownloadAvailability] 产品不存在或不是组合包,隐藏下载按钮')
+ showDownloadReportButton.value = false
+ return
+ }
+
+ if (!product.value?.sell_ui_component) {
+ console.log('[checkReportDownloadAvailability] 产品不出售UI组件,隐藏下载按钮')
+ showDownloadReportButton.value = false
+ return
+ }
+
+ try {
+ console.log('[checkReportDownloadAvailability] 检查组件报告可用性,产品ID:', product.value.id)
+ const response = await productApi.checkComponentReportAvailability(product.value.id)
+ console.log('[checkReportDownloadAvailability] 检查可用性响应:', response)
+ const { can_download } = response.data
+
+ showDownloadReportButton.value = true
+
+ console.log('[checkReportDownloadAvailability] 获取组件报告信息,产品ID:', product.value.id)
+ // 获取下载信息和价格
+ const infoResponse = await productApi.getComponentReportInfo(product.value.id)
+ console.log('[checkReportDownloadAvailability] 组件报告信息响应:', infoResponse)
+
+ // 检查响应数据结构
+ if (infoResponse.data && infoResponse.data.data) {
+ // 如果响应是嵌套结构,提取实际数据
+ reportDownloadInfo.value = infoResponse.data.data
+ } else if (infoResponse.data) {
+ // 直接使用响应数据
+ reportDownloadInfo.value = infoResponse.data
+ } else {
+ console.error('[checkReportDownloadAvailability] 响应数据格式不正确:', infoResponse)
+ showDownloadReportButton.value = false
+ return
+ }
+
+ canDownloadReport.value = reportDownloadInfo.value.can_download
+ } catch (error) {
+ console.error('[checkReportDownloadAvailability] 检查报告下载可用性失败:', error)
+ console.error('[checkReportDownloadAvailability] 错误详情:', {
+ message: error.message,
+ response: error.response?.data,
+ status: error.response?.status
+ })
+ showDownloadReportButton.value = false
+ }
+}
+
+// 处理下载报告按钮点击
+const handleDownloadReport = async () => {
+ console.log('[handleDownloadReport] 开始处理下载报告按钮点击')
+ console.log('[handleDownloadReport] 产品信息:', product.value)
+
+ if (!product.value?.id) {
+ console.log('[handleDownloadReport] 产品信息不存在')
+ ElMessage.warning('产品信息不存在')
+ return
+ }
+
+ // 如果可以直接下载
+ if (canDownloadReport.value) {
+ console.log('[handleDownloadReport] 可以直接下载,开始下载')
+ downloadComponentReport()
+ return
+ }
+
+ console.log('[handleDownloadReport] 需要购买,检查下载信息是否存在')
+ // 需要购买,显示支付对话框
+ if (!reportDownloadInfo.value || !reportDownloadInfo.value.product_id) {
+ console.log('[handleDownloadReport] 下载信息不存在或不完整,重新获取')
+ try {
+ const response = await productApi.getComponentReportInfo(product.value.id)
+ console.log('[handleDownloadReport] 获取到的下载信息:', response)
+
+ // 检查响应数据结构
+ if (response.data && response.data.data) {
+ // 如果响应是嵌套结构,提取实际数据
+ reportDownloadInfo.value = response.data.data
+ } else if (response.data) {
+ // 直接使用响应数据
+ reportDownloadInfo.value = response.data
+ } else {
+ console.error('[handleDownloadReport] 响应数据格式不正确:', response)
+ ElMessage.error('获取报告下载信息失败')
+ return
+ }
+
+ // 详细日志记录数据结构
+ console.log('[handleDownloadReport] 下载信息详情:', {
+ 产品ID: reportDownloadInfo.value.product_id,
+ 产品名称: reportDownloadInfo.value.product_name,
+ 是否组合包: reportDownloadInfo.value.is_package,
+ 子产品数量: reportDownloadInfo.value.sub_products?.length || 0,
+ 子产品列表: reportDownloadInfo.value.sub_products,
+ 价格: product.value.ui_component_price,
+ 可下载: reportDownloadInfo.value.can_download
+ })
+ } catch (error) {
+ console.error('[handleDownloadReport] 获取报告下载信息失败:', error)
+ console.error('[handleDownloadReport] 错误详情:', {
+ message: error.message,
+ response: error.response?.data,
+ status: error.response?.status
+ })
+ ElMessage.error('获取报告下载信息失败')
+ return
+ }
+ } else {
+ console.log('[handleDownloadReport] 下载信息已存在,使用缓存数据')
+ // 详细日志记录数据结构
+ console.log('[handleDownloadReport] 缓存的下载信息详情:', {
+ 产品ID: reportDownloadInfo.value.product_id,
+ 产品名称: reportDownloadInfo.value.product_name,
+ 是否组合包: reportDownloadInfo.value.is_package,
+ 子产品数量: reportDownloadInfo.value.sub_products?.length || 0,
+ 子产品列表: reportDownloadInfo.value.sub_products,
+ 价格: product.value.ui_component_price,
+ 可下载: reportDownloadInfo.value.can_download
+ })
+ }
+
+ console.log('[handleDownloadReport] 显示支付对话框')
+ reportPaymentDialogVisible.value = true
+}
+
+// 下载组件报告
+const downloadComponentReport = async () => {
+ if (!product.value?.id) {
+ ElMessage.warning('产品信息不存在')
+ return
+ }
+
+ downloadingReport.value = true
+ try {
+ // 获取子产品代码列表
+ let subProductCodes = []
+ if (reportDownloadInfo.value && reportDownloadInfo.value.sub_products && reportDownloadInfo.value.sub_products.length > 0) {
+ subProductCodes = reportDownloadInfo.value.sub_products.map(item => item.product_code)
+ console.log('[downloadComponentReport] 提取子产品代码列表:', subProductCodes)
+ } else {
+ console.log('[downloadComponentReport] 未找到子产品信息,使用空列表')
+ }
+
+ const response = await productApi.generateAndDownloadComponentReport({
+ product_id: product.value.id,
+ sub_product_codes: subProductCodes
+ })
+
+ // 创建下载链接
+ // 直接从response中获取数据,不再检查Blob类型
+ const url = URL.createObjectURL(response.data)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = `${product.value.name || '产品'}_示例报告.zip`
+
+ // 触发下载
+ document.body.appendChild(link)
+ link.click()
+
+ // 清理
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+
+ ElMessage.success('示例报告下载成功')
+ } catch (error) {
+ console.error('下载示例报告失败:', error)
+ ElMessage.error('下载示例报告失败')
+ } finally {
+ downloadingReport.value = false
+ }
+}
+
+// 创建支付订单
+const createReportPaymentOrder = async (paymentType) => {
+ console.log('[createReportPaymentOrder] 开始创建支付订单')
+ console.log('[createReportPaymentOrder] 支付类型:', paymentType)
+ console.log('[createReportPaymentOrder] 产品信息:', product.value)
+
+ if (!product.value?.id) {
+ console.log('[createReportPaymentOrder] 产品信息不存在')
+ ElMessage.warning('产品信息不存在')
+ return
+ }
+
+ try {
+ // 显示加载状态
+ const loadingInstance = ElLoading.service({
+ lock: true,
+ text: '正在创建支付订单,请稍候...',
+ background: 'rgba(0, 0, 0, 0.7)'
+ })
+
+ console.log('[createReportPaymentOrder] 调用创建支付订单API')
+ const response = await productApi.createComponentReportPaymentOrder(product.value.id, {
+ payment_type: paymentType
+ })
+ console.log('[createReportPaymentOrder] 支付订单创建响应:', response)
+
+ // 关闭加载状态
+ loadingInstance.close()
+
+ // 根据响应拦截器的处理方式,正确访问嵌套的 data 对象
+ // 响应格式为 {code: 200, data: {...}}
+ const responseData = response.data.data || response.data
+ console.log('[createReportPaymentOrder] 解析响应数据:', responseData)
+
+ const { order_id, code_url, pay_url, payment_type } = responseData
+
+ currentReportOrderId.value = order_id
+ reportPaymentStatus.value = 'pending'
+ reportPaymentDialogVisible.value = false
+
+ // 根据支付类型处理
+ if (payment_type === 'wechat' && code_url) {
+ console.log('[createReportPaymentOrder] 微信支付,显示二维码')
+ // 显示微信支付二维码
+ showWechatPaymentQRCode(code_url, order_id)
+ } else if (payment_type === 'alipay' && pay_url) {
+ console.log('[createReportPaymentOrder] 支付宝支付,跳转到支付页面')
+ // 跳转到支付宝支付页面
+ window.open(pay_url, '_blank')
+ // 显示支付状态检查提示
+ ElMessage.info('已打开支付页面,请在支付完成后返回此页面')
+ // 开始检查支付状态
+ startPaymentStatusCheck(order_id)
+ } else {
+ console.error('[createReportPaymentOrder] 支付类型或支付链接不匹配', {
+ payment_type,
+ code_url: !!code_url,
+ pay_url: !!pay_url
+ })
+ ElMessage.error('支付方式不支持或支付链接获取失败')
+ }
+ } catch (error) {
+ console.error('[createReportPaymentOrder] 创建支付订单失败:', error)
+ console.error('[createReportPaymentOrder] 错误详情:', {
+ message: error.message,
+ response: error.response?.data,
+ status: error.response?.status
+ })
+
+ // 显示具体错误信息
+ const errorMessage = error.response?.data?.message || error.message || '创建支付订单失败'
+ ElMessage.error(errorMessage)
+ }
+}
+
+// 显示微信支付二维码
+const showWechatPaymentQRCode = (codeUrl, orderId) => {
+ // 验证二维码URL是否有效
+ if (!codeUrl || typeof codeUrl !== 'string') {
+ ElMessage.error('微信支付二维码获取失败')
+ return
+ }
+
+ console.log('[showWechatPaymentQRCode] 准备显示二维码,URL:', codeUrl)
+
+ // 使用第三方二维码生成服务将微信支付URL转换为二维码图片
+ const qrCodeImageUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(codeUrl)}`
+
+ // 使用自定义对话框而不是ElMessageBox.alert,以确保HTML内容正确渲染
+ const qrDialog = ElMessageBox({
+ title: '微信扫码支付',
+ message: h('div', { style: 'text-align: center; padding: 20px;' }, [
+ h('h3', { style: 'margin-bottom: 20px;' }, '微信扫码支付'),
+ h('div', { style: 'margin-bottom: 20px;' }, '请使用微信扫描下方二维码完成支付'),
+ h('div', { style: 'display: flex; justify-content: center; margin-bottom: 20px;' }, [
+ h('img', {
+ src: qrCodeImageUrl,
+ alt: '微信支付二维码',
+ style: 'width: 200px; height: 200px; border: 1px solid #eee;',
+ onError: (e) => {
+ console.error('[showWechatPaymentQRCode] 二维码图片加载失败:', e)
+ // 尝试使用备用方案:显示一个链接,让用户点击打开微信
+ ElMessage({
+ message: '二维码加载失败,请尝试直接打开微信支付',
+ type: 'warning',
+ duration: 5000,
+ showClose: true
+ })
+
+ // 更新对话框内容,显示支付链接
+ instance.message = h('div', { style: 'text-align: center; padding: 20px;' }, [
+ h('h3', { style: 'margin-bottom: 20px;' }, '微信扫码支付'),
+ h('div', { style: 'margin-bottom: 20px;' }, '二维码生成失败,请点击下方链接打开微信支付'),
+ h('div', { style: 'margin-bottom: 20px; padding: 10px; border: 1px dashed #ccc; border-radius: 4px;' }, [
+ h('a', {
+ href: codeUrl,
+ onClick: (e) => {
+ e.preventDefault()
+ // 尝试打开微信支付链接
+ window.open(codeUrl, '_self')
+ },
+ style: 'color: #409EFF; text-decoration: underline; cursor: pointer;'
+ }, '点击打开微信支付')
+ ]),
+ h('div', { style: 'font-size: 14px; color: #666;' }, [
+ h('p', `支付金额:¥${formatPrice(product.value?.ui_component_price || 0)}`),
+ h('p', '支付完成后,请点击"我已支付"按钮')
+ ])
+ ])
+ }
+ })
+ ]),
+ h('div', { style: 'font-size: 14px; color: #666;' }, [
+ h('p', `支付金额:¥${formatPrice(product.value?.ui_component_price || 0)}`),
+ h('p', '支付完成后,系统将自动检测支付状态')
+ ])
+ ]),
+ confirmButtonText: '我已支付',
+ cancelButtonText: '支付遇到问题',
+ type: 'info',
+ showClose: false,
+ closeOnClickModal: false,
+ closeOnPressEscape: false,
+ beforeClose: (action, instance, done) => {
+ if (action === 'confirm') {
+ // 用户点击了"我已支付"按钮,开始检查支付状态
+ startPaymentStatusCheck(orderId)
+ } else {
+ // 用户点击了"支付遇到问题"按钮
+ ElMessage.warning('如果支付遇到问题,请稍后重试或联系客服')
+ }
+ done()
+ }
+ }).catch(() => {
+ // 对话框被关闭(通过取消按钮或其他方式)
+ })
+
+ // 不立即开始检查支付状态,等待用户确认支付后再开始
+ // 这样可以避免过早开始状态检查导致用户体验不佳
+}
+
+// 开始检查支付状态
+const startPaymentStatusCheck = (orderId) => {
+ if (!orderId) {
+ ElMessage.error('订单ID不存在')
+ return
+ }
+
+ // 显示支付状态检查提示
+ const loadingInstance = ElLoading.service({
+ lock: false, // 不锁定屏幕,允许用户操作
+ text: '正在检查支付状态,请完成支付后等待(检查超过1分钟将自动刷新页面)...',
+ background: 'rgba(0, 0, 0, 0.5)'
+ })
+
+ // 显示一个提示对话框,告知用户正在检查支付状态
+ ElMessageBox.alert(
+ '我们正在检测您的支付状态,支付完成后会自动为您下载示例报告。检查超过1分钟页面将自动刷新以获取最新状态。请勿关闭此页面。',
+ '支付状态检测',
+ {
+ confirmButtonText: '我知道了',
+ type: 'info',
+ showClose: false,
+ closeOnClickModal: false,
+ closeOnPressEscape: false
+ }
+ ).catch(() => {
+ // 用户点击了确定按钮,继续检查
+ })
+
+ let checkCount = 0
+ const maxCheckCount = 60 // 最多检查3分钟(60次 * 3秒)
+
+ // 设置定时器检查支付状态
+ const checkInterval = setInterval(async () => {
+ checkCount++
+
+ // 更新提示文本
+ loadingInstance.text = `正在检查支付状态... (${Math.ceil(checkCount/20)}分钟/${Math.ceil(maxCheckCount/20)}分钟)`
+
+ try {
+ const response = await productApi.checkComponentReportPaymentStatus(orderId)
+ // 正确处理响应数据格式
+ const responseData = response.data.data || response.data
+ const { payment_status, can_download } = responseData
+
+ console.log('[startPaymentStatusCheck] 支付状态检查结果:', {
+ payment_status,
+ can_download,
+ check_count: checkCount
+ })
+
+ if (payment_status === 'success' && can_download) {
+ // 支付成功
+ clearInterval(checkInterval)
+ loadingInstance.close()
+
+ canDownloadReport.value = true
+ ElMessage.success('支付成功,现在可以下载示例报告了')
+
+ // 关闭之前的提示框
+ ElMessageBox.close()
+
+ // 自动下载
+ downloadComponentReport()
+ } else if (payment_status === 'failed') {
+ // 支付失败
+ clearInterval(checkInterval)
+ loadingInstance.close()
+
+ // 关闭之前的提示框
+ ElMessageBox.close()
+
+ ElMessage.error('支付失败,请重试')
+ } else if (payment_status === 'cancelled') {
+ // 支付取消
+ clearInterval(checkInterval)
+ loadingInstance.close()
+
+ // 关闭之前的提示框
+ ElMessageBox.close()
+
+ ElMessage.info('支付已取消')
+ } else if (checkCount >= 20) {
+ // 检查超过1分钟(20次 * 3秒)
+ clearInterval(checkInterval)
+ loadingInstance.close()
+
+ // 关闭之前的提示框
+ ElMessageBox.close()
+
+ ElMessage.warning('支付状态检查超过1分钟,页面将自动刷新以获取最新状态')
+
+ // 1秒后刷新页面
+ setTimeout(() => {
+ window.location.reload()
+ }, 1000)
+ } else if (checkCount >= maxCheckCount) {
+ // 超时(3分钟)
+ clearInterval(checkInterval)
+ loadingInstance.close()
+
+ // 关闭之前的提示框
+ ElMessageBox.close()
+
+ ElMessage.warning('支付状态检查超时,请刷新页面后检查')
+
+ // 显示手动检查选项
+ ElMessageBox.confirm(
+ '支付状态检查已超时,您可以选择继续等待或稍后手动检查支付状态。',
+ '支付状态检查',
+ {
+ confirmButtonText: '继续等待',
+ cancelButtonText: '稍后手动检查',
+ type: 'warning'
+ }
+ ).then(() => {
+ // 继续等待,重置计数器
+ checkCount = 0
+ // 继续检查,不再设置超时
+ }).catch(() => {
+ // 用户选择稍后手动检查
+ ElMessage.info('您可以在页面中点击"下载示例报告"按钮来手动检查支付状态')
+ })
+ }
+ } catch (error) {
+ console.error('[startPaymentStatusCheck] 检查支付状态失败:', error)
+ console.error('[startPaymentStatusCheck] 错误详情:', {
+ message: error.message,
+ response: error.response?.data,
+ status: error.response?.status
+ })
+
+ // 不显示错误消息,避免打扰用户,继续检查
+ }
+ }, 3000) // 每3秒检查一次,减少服务器压力
+
+ // 将定时器ID存储在组件实例中,以便在组件卸载时清理
+ if (!window.paymentCheckIntervals) {
+ window.paymentCheckIntervals = {}
+ }
+ window.paymentCheckIntervals[orderId] = checkInterval
+}
+
+// 手动检查支付状态
+const checkPaymentStatusManually = async () => {
+ if (!currentReportOrderId.value) {
+ ElMessage.warning('没有正在处理的支付订单')
+ return
+ }
+
+ checkingPaymentStatus.value = true
+ try {
+ const response = await productApi.checkComponentReportPaymentStatus(currentReportOrderId.value)
+ const { payment_status, can_download } = response.data
+
+ console.log('[checkPaymentStatusManually] 支付状态检查结果:', {
+ payment_status,
+ can_download
+ })
+
+ if (payment_status === 'success' && can_download) {
+ // 支付成功
+ canDownloadReport.value = true
+ ElMessage.success('支付成功,现在可以下载示例报告了')
+
+ // 自动下载
+ downloadComponentReport()
+ } else if (payment_status === 'failed') {
+ // 支付失败
+ ElMessage.error('支付失败,请重试')
+ } else if (payment_status === 'pending') {
+ // 支付中
+ ElMessage.info('支付仍在处理中,请稍后再次检查')
+ } else {
+ // 其他状态
+ ElMessage.warning(`支付状态: ${payment_status}`)
+ }
+ } catch (error) {
+ console.error('[checkPaymentStatusManually] 检查支付状态失败:', error)
+ ElMessage.error('检查支付状态失败,请稍后重试')
+ } finally {
+ checkingPaymentStatus.value = false
+ }
+}
diff --git a/src/router/index.js b/src/router/index.js
index 447de42..ff20bee 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -136,6 +136,12 @@ const routes = [
name: 'FinanceRechargeRecords',
component: () => import('@/pages/finance/recharge-records/index.vue'),
meta: { title: '充值记录' }
+ },
+ {
+ path: 'purchase-records',
+ name: 'FinancePurchaseRecords',
+ component: () => import('@/pages/finance/purchase-records/index.vue'),
+ meta: { title: '购买记录' }
}
]
},
@@ -292,6 +298,12 @@ const routes = [
name: 'AdminUIComponents',
component: () => import('@/pages/admin/ui-components/index.vue'),
meta: { title: '组件管理' }
+ },
+ {
+ path: 'purchase-records',
+ name: 'AdminPurchaseRecords',
+ component: () => import('@/pages/admin/purchase-records/index.vue'),
+ meta: { title: '购买记录管理' }
}
]
},
diff --git a/src/utils/request.js b/src/utils/request.js
index 3c812b6..4a4519c 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -52,14 +52,16 @@ request.interceptors.request.use(
// 响应拦截器
request.interceptors.response.use(
response => {
- const { data, status } = response
+ const { data, status, config } = response
// 检查HTTP状态码
if (status >= 200 && status < 300) {
- // 如果是blob响应(文件下载),直接返回data
- if (data instanceof Blob) {
- return data
+ // 检查是否是文件下载API(通过URL判断)
+ if (config.url && config.url.includes('/generate-and-download')) {
+ // 直接返回原始响应,让前端处理文件下载
+ return response
}
+
// 严格按照后端响应格式处理
if (data.success === true) {
// 成功响应,返回data字段