feat: 新增虚拟支付订单与网盘链接检测功能

This commit is contained in:
王鹏
2026-07-28 13:42:30 +08:00
parent 4070179f46
commit 2250f744d1
60 changed files with 3668 additions and 106 deletions

View File

@@ -0,0 +1,18 @@
import request from '@/utils/request'
// 查询虚拟支付订单列表
export function listVirtualOrder(query) {
return request({
url: '/app/virtualOrder/list',
method: 'get',
params: query
})
}
// 查询虚拟支付订单详情
export function getVirtualOrder(id) {
return request({
url: '/app/virtualOrder/' + id,
method: 'get'
})
}

View File

@@ -43,6 +43,23 @@ export function delProject(id) {
})
}
// 检测单个项目的网盘链接
export function checkProjectLinks(id) {
return request({
url: '/office/project/link-check/single/' + id,
method: 'post'
})
}
// 检测选中项目的网盘链接
export function checkSelectedProjectLinks(ids) {
return request({
url: '/office/project/link-check/batch',
method: 'post',
data: ids
})
}
export function matchCode(id) {
return request({
url: '/office/project/matchCode/' + id,

View File

@@ -76,6 +76,12 @@
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="付费价格" align="center" prop="priceFen">
<template slot-scope="scope">
<span v-if="scope.row.isAd == 3">¥{{ (scope.row.priceFen / 100).toFixed(2) }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="下载次数" align="center" prop="downNum" />
<el-table-column label="权重" align="center" prop="weight" />
<el-table-column label="创建时间" align="center" prop="createTime" width="160px" />
@@ -134,8 +140,9 @@
<el-form-item v-if="form.isAd == 2" label="兑换积分" prop="adNumber">
<el-input v-model="form.adNumber" placeholder="请输入需要兑换多少积分解锁" />
</el-form-item>
<el-form-item v-if="form.isAd == 3" label="付费金额" prop="adNumber">
<el-input v-model="form.adNumber" placeholder="请输入需要付费多少金额解锁" />
<el-form-item v-if="form.isAd == 3" label="价格(分)" prop="priceFen">
<el-input-number v-model="form.priceFen" :min="1" :step="100" controls-position="right" />
<span class="form-tip">系统将按价格自动匹配已配置的微信道具</span>
</el-form-item>
<el-form-item label="权重" prop="weight">
<el-input v-model="form.weight" placeholder="请输入权重" />
@@ -260,6 +267,11 @@
message: "需要广告不能为空",
trigger: "change"
}],
priceFen: [{
required: true,
message: "付费价格不能为空",
trigger: "blur"
}],
},
directory:"appimg/fengmian/"
};
@@ -303,6 +315,8 @@
isShow: null,
isAd: null,
adNumber: null,
priceFen: null,
virtualProductId: null,
delFlag: null,
createBy: null,
createTime: null,

View File

@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<el-form
v-show="showSearch"
ref="queryForm"
:model="queryParams"
size="small"
:inline="true"
label-width="84px"
>
<el-form-item label="业务订单号" prop="orderNo">
<el-input
v-model="queryParams.orderNo"
placeholder="请输入业务订单号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="微信交易号" prop="transactionId">
<el-input
v-model="queryParams.transactionId"
placeholder="请输入微信交易号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="用户" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="账号或昵称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="资源ID" prop="resourceId">
<el-input
v-model="queryParams.resourceId"
placeholder="请输入资源ID"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="订单状态" prop="status">
<el-select v-model="queryParams.status" placeholder="全部状态" clearable>
<el-option
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
v-hasPermi="['app:virtualOrder:export']"
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
>导出</el-button>
</el-col>
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="orderList">
<el-table-column label="ID" align="center" prop="id" width="80" />
<el-table-column label="业务订单号" prop="orderNo" min-width="210" show-overflow-tooltip />
<el-table-column label="用户" min-width="150">
<template slot-scope="scope">
<div>{{ scope.row.nickName || scope.row.userName || '-' }}</div>
<div class="secondary-text">{{ scope.row.userName || '-' }} / ID: {{ scope.row.userId }}</div>
</template>
</el-table-column>
<el-table-column label="资源" min-width="180" show-overflow-tooltip>
<template slot-scope="scope">
<div>{{ scope.row.resourceTitle || '-' }}</div>
<div class="secondary-text">ID: {{ scope.row.resourceId }}</div>
</template>
</el-table-column>
<el-table-column label="金额" align="right" width="100">
<template slot-scope="scope">{{ formatPrice(scope.row.priceFen) }}</template>
</el-table-column>
<el-table-column label="状态" align="center" width="120">
<template slot-scope="scope">
<el-tag :type="statusType(scope.row.status)" size="small">
{{ statusLabel(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="微信道具ID" prop="productId" min-width="140" show-overflow-tooltip />
<el-table-column label="微信交易号" prop="transactionId" min-width="190" show-overflow-tooltip />
<el-table-column label="创建时间" prop="createTime" width="165" />
<el-table-column label="支付时间" prop="payTime" width="165" />
<el-table-column label="操作" align="center" width="80" fixed="right">
<template slot-scope="scope">
<el-button
v-hasPermi="['app:virtualOrder:query']"
type="text"
icon="el-icon-view"
size="mini"
@click="handleView(scope.row)"
>详情</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog title="虚拟支付订单详情" :visible.sync="detailOpen" width="820px" append-to-body>
<el-descriptions v-if="detail.id" :column="2" border>
<el-descriptions-item label="业务订单号" :span="2">{{ detail.orderNo }}</el-descriptions-item>
<el-descriptions-item label="订单状态">
<el-tag :type="statusType(detail.status)" size="small">{{ statusLabel(detail.status) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="支付金额">{{ formatPrice(detail.priceFen) }}{{ detail.priceFen }} </el-descriptions-item>
<el-descriptions-item label="用户账号">{{ detail.userName || '-' }}</el-descriptions-item>
<el-descriptions-item label="用户昵称">{{ detail.nickName || '-' }}</el-descriptions-item>
<el-descriptions-item label="用户ID">{{ detail.userId }}</el-descriptions-item>
<el-descriptions-item label="OpenID">{{ detail.openId || '-' }}</el-descriptions-item>
<el-descriptions-item label="资源ID">{{ detail.resourceId }}</el-descriptions-item>
<el-descriptions-item label="资源标题">{{ detail.resourceTitle || '-' }}</el-descriptions-item>
<el-descriptions-item label="微信道具ID">{{ detail.productId || '-' }}</el-descriptions-item>
<el-descriptions-item label="微信内部订单号">{{ detail.wxOrderNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="微信交易号" :span="2">{{ detail.transactionId || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detail.createTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="支付时间">{{ detail.payTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="发货时间">{{ detail.provideTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="退款时间">{{ detail.refundTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="最近同步时间" :span="2">{{ detail.lastQueryTime || '-' }}</el-descriptions-item>
</el-descriptions>
<div slot="footer" class="dialog-footer">
<el-button @click="detailOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getVirtualOrder, listVirtualOrder } from '@/api/app/virtualOrder'
export default {
name: 'VirtualOrder',
data() {
return {
loading: true,
showSearch: true,
total: 0,
orderList: [],
dateRange: [],
detailOpen: false,
detail: {},
statusOptions: [
{ value: 0, label: '待支付' },
{ value: 1, label: '已支付发货' },
{ value: 2, label: '已退款' },
{ value: 3, label: '已关闭' }
],
queryParams: {
pageNum: 1,
pageSize: 10,
orderNo: undefined,
transactionId: undefined,
userName: undefined,
resourceId: undefined,
status: undefined
}
}
},
created() {
this.getList()
},
methods: {
getList() {
this.loading = true
listVirtualOrder(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.orderList = response.rows
this.total = response.total
}).finally(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.dateRange = []
this.resetForm('queryForm')
this.handleQuery()
},
handleView(row) {
getVirtualOrder(row.id).then(response => {
this.detail = response.data
this.detailOpen = true
})
},
handleExport() {
const query = this.addDateRange({ ...this.queryParams, params: {}}, this.dateRange)
this.download('app/virtualOrder/export', query, `virtual_order_${new Date().getTime()}.xlsx`)
},
formatPrice(priceFen) {
if (priceFen === null || priceFen === undefined) {
return '0.00'
}
return (Number(priceFen) / 100).toFixed(2)
},
statusLabel(status) {
const option = this.statusOptions.find(item => item.value === status)
return option ? option.label : '未知状态'
},
statusType(status) {
return {
0: 'warning',
1: 'success',
2: 'danger',
3: 'info'
}[status] || 'info'
}
}
}
</script>
<style scoped>
.secondary-text {
margin-top: 2px;
color: #909399;
font-size: 12px;
}
</style>

View File

@@ -82,6 +82,18 @@
v-hasPermi="['office:project:export']"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-connection"
size="mini"
:disabled="multiple"
:loading="batchChecking"
@click="handleCheckSelected"
v-hasPermi="['office:project:edit']"
>检测选中</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -93,8 +105,38 @@
<el-table-column label="源码名称" align="center" prop="projectName1" />
<el-table-column label="项目技术" align="center" prop="projectNum1" />
<el-table-column label="项目描述" align="center" prop="projectDesc" />
<el-table-column label="项目链接" align="center" prop="projectUrl" />
<el-table-column label="夸克网盘链接" align="center" prop="projectUrl" />
<el-table-column label="项目百度链接" align="center" prop="projectBaiduUrl" />
<el-table-column label="网盘状态" align="center" width="175">
<template slot-scope="scope">
<div class="link-check-status">
<span class="disk-name">夸克</span>
<el-tooltip
:content="checkTooltip(scope.row.quarkCheckStatus, scope.row.quarkCheckMessage, scope.row.quarkCheckedAt, scope.row.projectUrl)"
placement="top"
>
<el-tag
size="mini"
effect="plain"
:type="statusType(scope.row.quarkCheckStatus)"
>{{ statusText(scope.row.quarkCheckStatus, scope.row.projectUrl) }}</el-tag>
</el-tooltip>
</div>
<div class="link-check-status">
<span class="disk-name">百度</span>
<el-tooltip
:content="checkTooltip(scope.row.baiduCheckStatus, scope.row.baiduCheckMessage, scope.row.baiduCheckedAt, scope.row.projectBaiduUrl)"
placement="top"
>
<el-tag
size="mini"
effect="plain"
:type="statusType(scope.row.baiduCheckStatus)"
>{{ statusText(scope.row.baiduCheckStatus, scope.row.projectBaiduUrl) }}</el-tag>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column label="演示视频链接" align="center" prop="projectVurl" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
@@ -131,6 +173,14 @@
<!-- v-hasPermi="['office:code:edit']"-->
<!-- >匹配源码-->
<!-- </el-button>-->
<el-button
size="mini"
type="text"
icon="el-icon-connection"
:loading="isChecking(scope.row.id)"
@click="handleCheckRow(scope.row)"
v-hasPermi="['office:project:edit']"
>检测链接</el-button>
<el-button
size="mini"
type="text"
@@ -178,7 +228,7 @@
<el-form-item label="夸克网盘链接" prop="projectUrl">
<el-input v-model="form.projectUrl" placeholder="请输入项目链接" />
</el-form-item>
<el-form-item label="百度网盘链接" prop="projectUrl">
<el-form-item label="百度网盘链接" prop="projectBaiduUrl">
<el-input v-model="form.projectBaiduUrl" placeholder="请输入项目链接" />
</el-form-item>
<el-form-item label="演示视频链接" prop="projectVurl">
@@ -205,7 +255,16 @@
</template>
<script>
import { listProject, getProject, delProject, addProject, updateProject, matchCode } from '@/api/office/project'
import {
listProject,
getProject,
delProject,
addProject,
updateProject,
matchCode,
checkProjectLinks,
checkSelectedProjectLinks
} from '@/api/office/project'
import { getToken } from '@/utils/auth'
export default {
@@ -214,6 +273,10 @@ export default {
return {
// 遮罩层
loading: true,
// 批量检测状态
batchChecking: false,
// 正在进行单条检测的项目
checkingIds: [],
// 选中数组
ids: [],
// 非单个禁用
@@ -302,6 +365,99 @@ export default {
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 检测单个项目的网盘链接 */
handleCheckRow(row) {
if (this.isChecking(row.id)) {
return
}
this.checkingIds.push(row.id)
checkProjectLinks(row.id).then(response => {
this.showCheckSummary(response.data)
this.getList()
}).then(() => {
this.removeCheckingId(row.id)
}).catch(() => {
this.removeCheckingId(row.id)
})
},
/** 检测选中的项目网盘链接 */
handleCheckSelected() {
if (!this.ids.length) {
this.$modal.msgWarning('请至少选择一个项目')
return
}
if (this.ids.length > 20) {
this.$modal.msgWarning('一次最多检测 20 个项目')
return
}
const ids = this.ids.slice()
this.$modal.confirm('确认检测选中的 ' + ids.length + ' 个项目吗?').then(() => {
this.batchChecking = true
return checkSelectedProjectLinks(ids)
}).then(response => {
this.showCheckSummary(response.data)
this.getList()
this.batchChecking = false
}).catch(() => {
this.batchChecking = false
})
},
isChecking(id) {
return this.checkingIds.indexOf(id) !== -1
},
removeCheckingId(id) {
this.checkingIds = this.checkingIds.filter(item => item !== id)
},
showCheckSummary(summary) {
if (!summary || !summary.linkCount) {
this.$modal.msgWarning('所选项目没有填写百度或夸克网盘链接')
return
}
const message = '检测完成:有效 ' + summary.validCount +
',失效 ' + summary.invalidCount +
',需处理 ' + summary.warningCount +
',异常 ' + summary.unknownCount
if (summary.invalidCount > 0 || summary.warningCount > 0) {
this.$modal.msgWarning(message)
} else {
this.$modal.msgSuccess(message)
}
},
statusText(status, linkUrl) {
if (!linkUrl) {
return '无链接'
}
const statusMap = {
VALID: '有效',
INVALID: '已失效',
NEED_CODE: '缺提取码',
CODE_ERROR: '提取码错误',
FORMAT_ERROR: '格式错误',
UNKNOWN: '检测异常'
}
return statusMap[status] || '未检测'
},
statusType(status) {
const typeMap = {
VALID: 'success',
INVALID: 'danger',
NEED_CODE: 'warning',
CODE_ERROR: 'warning',
FORMAT_ERROR: 'warning',
UNKNOWN: 'info'
}
return typeMap[status] || 'info'
},
checkTooltip(status, message, checkedAt, linkUrl) {
if (!linkUrl) {
return '未填写链接'
}
if (!status) {
return '尚未检测'
}
const timeText = checkedAt ? ';检测时间:' + this.parseTime(checkedAt) : ''
return (message || this.statusText(status, linkUrl)) + timeText
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
@@ -484,3 +640,19 @@ export default {
}
}
</script>
<style scoped>
.link-check-status {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 28px;
}
.disk-name {
width: 32px;
color: #606266;
text-align: right;
}
</style>