完善充值代理
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m6s

This commit is contained in:
sexygoat
2026-03-18 10:47:25 +08:00
parent ff67681c29
commit 9fb3367fd7
3 changed files with 240 additions and 438 deletions

View File

@@ -1,124 +1,42 @@
<template>
<ArtDataViewer
:service="loadDetailData"
:card-title="pageTitle"
@back="handleBack"
@refresh="handleRefresh"
>
<template #default="{ data }">
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="充值单号">
{{ data.recharge_no }}
</ElDescriptionsItem>
<ElDescriptionsItem label="充值记录ID">
{{ data.id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="代理钱包ID">
{{ data.agent_wallet_id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="店铺ID">
{{ data.shop_id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="店铺名称">
{{ data.shop_name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="充值金额">
<span style="color: var(--el-color-success); font-weight: bold">
{{ formatCurrency(data.amount) }}
</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="状态">
<ElTag :type="getStatusType(data.status)">
{{ getStatusText(data.status) }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="支付方式">
{{ getPaymentMethodText(data.payment_method) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="支付通道">
{{ data.payment_channel }}
</ElDescriptionsItem>
<ElDescriptionsItem label="支付配置ID">
{{ data.payment_config_id || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="第三方支付流水号" :span="2">
{{ data.payment_transaction_id || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间">
{{ formatDateTime(data.created_at) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="支付时间">
{{ data.paid_at ? formatDateTime(data.paid_at) : '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="完成时间">
{{ data.completed_at ? formatDateTime(data.completed_at) : '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间">
{{ formatDateTime(data.updated_at) }}
</ElDescriptionsItem>
</ElDescriptions>
<!-- 操作按钮 -->
<div style="margin-top: 20px; text-align: right">
<ElButton v-if="data.status === 1 && data.payment_method === 'offline'" type="primary" @click="handleConfirmPay(data)">
确认支付
</ElButton>
<ElButton @click="handleBack">返回列表</ElButton>
</div>
<div class="agent-recharge-detail-page">
<ElCard shadow="never">
<!-- 页面头部 -->
<div class="detail-header">
<ElButton @click="handleBack">
<template #icon>
<ElIcon><ArrowLeft /></ElIcon>
</template>
</ArtDataViewer>
<!-- 确认线下支付对话框 -->
<ElDialog
v-model="confirmPayDialogVisible"
title="确认线下充值"
width="400px"
@closed="handleConfirmPayDialogClosed"
>
<ElForm
ref="confirmPayFormRef"
:model="confirmPayForm"
:rules="confirmPayRules"
label-width="100px"
>
<ElFormItem label="充值单号">
<span>{{ currentRecharge?.recharge_no }}</span>
</ElFormItem>
<ElFormItem label="充值金额">
<span>{{ formatCurrency(currentRecharge?.amount || 0) }}</span>
</ElFormItem>
<ElFormItem label="操作密码" prop="operation_password">
<ElInput
v-model="confirmPayForm.operation_password"
type="password"
placeholder="请输入操作密码"
show-password
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="confirmPayDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleConfirmPaySubmit" :loading="confirmPayLoading">
确认支付
返回
</ElButton>
<h2 class="detail-title">{{ pageTitle }}</h2>
</div>
<!-- 详情内容 -->
<DetailPage v-if="detailData" :sections="detailSections" :data="detailData" />
<!-- 加载中 -->
<div v-if="loading" class="loading-container">
<ElIcon class="is-loading"><Loading /></ElIcon>
<span>加载中...</span>
</div>
</ElCard>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, h } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
import DetailPage from '@/components/common/DetailPage.vue'
import type { DetailSection } from '@/components/common/DetailPage.vue'
import { AgentRechargeService } from '@/api/modules'
import { ElMessage, ElTag } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import type {
AgentRecharge,
AgentRechargeStatus,
AgentRechargePaymentMethod,
ConfirmOfflinePaymentRequest
AgentRechargePaymentMethod
} from '@/types/api'
import ArtDataViewer from '@/components/core/views/ArtDataViewer.vue'
import { formatDateTime } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias'
@@ -126,22 +44,13 @@
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const detailData = ref<AgentRecharge | null>(null)
const rechargeId = computed(() => Number(route.params.id))
const pageTitle = computed(() => `充值订单详情 #${rechargeId.value}`)
const confirmPayDialogVisible = ref(false)
const confirmPayLoading = ref(false)
const currentRecharge = ref<AgentRecharge | null>(null)
const confirmPayFormRef = ref<FormInstance>()
const confirmPayRules = reactive<FormRules>({
operation_password: [{ required: true, message: '请输入操作密码', trigger: 'blur' }]
})
const confirmPayForm = reactive<ConfirmOfflinePaymentRequest>({
operation_password: ''
})
// 格式化货币 - 将分转换为元
const formatCurrency = (amount: number): string => {
return `¥${(amount / 100).toFixed(2)}`
@@ -176,13 +85,97 @@
return methodMap[method] || method
}
// 详情配置
const detailSections = computed((): DetailSection[] => [
{
title: '订单信息',
fields: [
{ label: '充值单号', prop: 'recharge_no' },
{ label: '充值记录ID', prop: 'id' },
{ label: '店铺名称', prop: 'shop_name' },
{ label: '店铺ID', prop: 'shop_id' },
{
label: '充值金额',
formatter: (_, data) => formatCurrency(data.amount)
},
{
label: '状态',
render: (data) => h(ElTag, { type: getStatusType(data.status) }, () => getStatusText(data.status))
}
]
},
{
title: '支付信息',
fields: [
{
label: '支付方式',
formatter: (_, data) => getPaymentMethodText(data.payment_method)
},
{
label: '支付通道',
prop: 'payment_channel',
formatter: (value) => value || '-'
},
{
label: '支付配置ID',
prop: 'payment_config_id',
formatter: (value) => value || '-'
},
{
label: '代理钱包ID',
prop: 'agent_wallet_id'
},
{
label: '第三方支付流水号',
prop: 'payment_transaction_id',
formatter: (value) => value || '-',
fullWidth: true
}
]
},
{
title: '时间信息',
fields: [
{
label: '创建时间',
prop: 'created_at',
formatter: (value) => formatDateTime(value)
},
{
label: '支付时间',
prop: 'paid_at',
formatter: (value) => (value ? formatDateTime(value) : '-')
},
{
label: '完成时间',
prop: 'completed_at',
formatter: (value) => (value ? formatDateTime(value) : '-')
},
{
label: '更新时间',
prop: 'updated_at',
formatter: (value) => formatDateTime(value)
}
]
}
])
// 加载详情数据
const loadDetailData = async () => {
loading.value = true
try {
const res = await AgentRechargeService.getAgentRechargeById(rechargeId.value)
if (res.code === 0) {
return res.data
detailData.value = res.data
} else {
ElMessage.error(res.msg || '加载失败')
}
} catch (error) {
console.error(error)
ElMessage.error('加载失败')
} finally {
loading.value = false
}
throw new Error(res.msg || '加载失败')
}
// 返回列表
@@ -190,46 +183,41 @@
router.push(RoutesAlias.AgentRecharge)
}
// 刷新数据
const handleRefresh = () => {
// ArtDataViewer 会自动重新加载数据
}
// 显示确认支付对话框
const handleConfirmPay = (data: AgentRecharge) => {
currentRecharge.value = data
confirmPayDialogVisible.value = true
}
// 确认支付对话框关闭后的清理
const handleConfirmPayDialogClosed = () => {
confirmPayFormRef.value?.resetFields()
confirmPayForm.operation_password = ''
currentRecharge.value = null
}
// 确认线下支付
const handleConfirmPaySubmit = async () => {
if (!confirmPayFormRef.value || !currentRecharge.value) return
await confirmPayFormRef.value.validate(async (valid) => {
if (valid) {
confirmPayLoading.value = true
try {
await AgentRechargeService.confirmOfflinePayment(currentRecharge.value!.id, {
operation_password: confirmPayForm.operation_password
onMounted(() => {
loadDetailData()
})
ElMessage.success('确认支付成功')
confirmPayDialogVisible.value = false
confirmPayFormRef.value.resetFields()
// 刷新页面数据
handleRefresh()
} catch (error) {
console.error(error)
} finally {
confirmPayLoading.value = false
}
}
})
}
</script>
<style scoped lang="scss">
.agent-recharge-detail-page {
padding: 20px;
.detail-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 24px;
.detail-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 0;
gap: 12px;
color: var(--el-text-color-secondary);
.el-icon {
font-size: 32px;
}
}
}
</style>

View File

@@ -32,14 +32,29 @@
:pageSize="pagination.page_size"
:total="pagination.total"
:marginTop="10"
:row-class-name="getRowClassName"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@row-contextmenu="handleRowContextMenu"
@cell-mouse-enter="handleCellMouseEnter"
@cell-mouse-leave="handleCellMouseLeave"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 鼠标悬浮提示 -->
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
<!-- 充值订单操作右键菜单 -->
<ArtMenuRight
ref="rechargeOperationMenuRef"
:menu-items="rechargeOperationMenuItems"
:menu-width="140"
@select="handleRechargeOperationMenuSelect"
/>
<!-- 创建充值订单对话框 -->
<ElDialog
v-model="createDialogVisible"
@@ -151,7 +166,7 @@
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { AgentRechargeService, ShopService } from '@/api/modules'
import { ElMessage, ElTag, ElTreeSelect } from 'element-plus'
import { ElMessage, ElTag, ElTreeSelect, ElButton } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import type {
AgentRecharge,
@@ -165,6 +180,10 @@
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useUserStore } from '@/store/modules/user'
import { useTableContextMenu } from '@/composables/useTableContextMenu'
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
import { formatDateTime } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias'
@@ -181,6 +200,10 @@
const confirmPayDialogVisible = ref(false)
const currentRecharge = ref<AgentRecharge | null>(null)
// 右键菜单
const rechargeOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const currentOperatingRecharge = ref<AgentRecharge | null>(null)
// 搜索表单初始值
const initialSearchState: AgentRechargeQueryParams = {
shop_id: undefined,
@@ -249,7 +272,6 @@
// 列配置
const columnOptions = [
{ label: 'ID', prop: 'id' },
{ label: '充值单号', prop: 'recharge_no' },
{ label: '店铺名称', prop: 'shop_name' },
{ label: '充值金额', prop: 'amount' },
@@ -258,8 +280,7 @@
{ label: '支付通道', prop: 'payment_channel' },
{ label: '创建时间', prop: 'created_at' },
{ label: '支付时间', prop: 'paid_at' },
{ label: '完成时间', prop: 'completed_at' },
{ label: '操作', prop: 'actions' }
{ label: '完成时间', prop: 'completed_at' }
]
const createFormRef = ref<FormInstance>()
@@ -323,15 +344,10 @@
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'id',
label: 'ID',
width: 80
},
{
prop: 'recharge_no',
label: '充值单号',
minWidth: 200,
minWidth: 240,
formatter: (row: AgentRecharge) => {
return h(
'span',
@@ -374,7 +390,8 @@
{
prop: 'payment_channel',
label: '支付通道',
width: 120
width: 120,
formatter: (row: AgentRecharge) => row.payment_channel || '-'
},
{
prop: 'created_at',
@@ -393,46 +410,6 @@
label: '完成时间',
width: 180,
formatter: (row: AgentRecharge) => (row.completed_at ? formatDateTime(row.completed_at) : '-')
},
{
prop: 'actions',
label: '操作',
width: 150,
fixed: 'right',
formatter: (row: AgentRecharge) => {
const buttons: any[] = []
// 待支付且线下转账的订单可以确认支付
if (row.status === 1 && row.payment_method === 'offline') {
buttons.push(
h(
ElButton,
{
type: 'primary',
link: true,
size: 'small',
onClick: () => handleShowConfirmPay(row)
},
() => '确认支付'
)
)
}
buttons.push(
h(
ElButton,
{
type: 'primary',
link: true,
size: 'small',
onClick: () => handleViewDetail(row)
},
() => '查看详情'
)
)
return h('div', { style: 'display: flex; gap: 8px;' }, buttons)
}
}
])
@@ -502,7 +479,7 @@
}
const res = await AgentRechargeService.getAgentRecharges(params)
if (res.code === 0) {
rechargeList.value = res.data.list || []
rechargeList.value = res.data.items || []
pagination.total = res.data.total || 0
}
} catch (error) {
@@ -551,6 +528,8 @@
// 显示创建订单对话框
const showCreateDialog = async () => {
// 重新加载店铺列表,确保获取最新数据
await loadShops()
createDialogVisible.value = true
}
@@ -633,10 +612,65 @@
path: `${RoutesAlias.AgentRecharge}/detail/${row.id}`
})
}
// 充值订单操作菜单项配置
const rechargeOperationMenuItems = computed((): MenuItemType[] => {
const items: MenuItemType[] = []
// 待支付且线下转账的订单可以确认支付
if (
currentOperatingRecharge.value?.status === 1 &&
currentOperatingRecharge.value?.payment_method === 'offline'
) {
items.push({
key: 'confirm_pay',
label: '确认支付'
})
}
return items
})
// 显示充值订单操作右键菜单
const showRechargeOperationMenu = (e: MouseEvent, row: AgentRecharge) => {
e.preventDefault()
e.stopPropagation()
currentOperatingRecharge.value = row
rechargeOperationMenuRef.value?.show(e)
}
// 处理表格行右键菜单
const handleRowContextMenu = (row: AgentRecharge, column: any, event: MouseEvent) => {
showRechargeOperationMenu(event, row)
}
// 处理充值订单操作菜单选择
const handleRechargeOperationMenuSelect = (item: MenuItemType) => {
if (!currentOperatingRecharge.value) return
switch (item.key) {
case 'confirm_pay':
handleShowConfirmPay(currentOperatingRecharge.value)
break
}
}
// 使用表格右键菜单功能
const {
showContextMenuHint,
hintPosition,
getRowClassName,
handleCellMouseEnter,
handleCellMouseLeave
} = useTableContextMenu()
</script>
<style scoped lang="scss">
.agent-recharge-page {
height: 100%;
}
:deep(.el-table__row.table-row-with-context-menu) {
cursor: pointer;
}
</style>

View File

@@ -1,220 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量为表格页面添加右键菜单和悬浮提示功能
"""
import re
import os
def add_imports(content):
"""添加必要的导入"""
# 检查是否已经导入
if 'useTableContextMenu' in content:
return content
# 找到 useAuth 导入位置
import_pattern = r"(import\s+{\s+useAuth\s+}\s+from\s+'@/composables/useAuth')"
if re.search(import_pattern, content):
# 在 useAuth 后面添加 useTableContextMenu
content = re.sub(
import_pattern,
r"\1\n import { useTableContextMenu } from '@/composables/useTableContextMenu'",
content
)
# 添加 TableContextMenuHint 组件导入
arttable_pattern = r"(import\s+ArtButtonTable\s+from\s+'@/components/core/forms/ArtButtonTable\.vue')"
if re.search(arttable_pattern, content):
content = re.sub(
arttable_pattern,
r"\1\n import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'",
content
)
# 如果没有 ArtMenuRight,添加它
if 'ArtMenuRight' not in content:
content = re.sub(
arttable_pattern,
r"\1\n import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'\n import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'",
content
)
return content
def add_context_menu_usage(content):
"""添加 useTableContextMenu 的使用"""
if 'useTableContextMenu()' in content:
return content
# 查找 const currentRow = ref 或类似的变量声明
pattern = r"(const\s+currentRow\s*=\s*ref[^\n]+)"
usage_code = """
// 使用表格右键菜单功能
const {
showContextMenuHint,
hintPosition,
getRowClassName,
handleCellMouseEnter,
handleCellMouseLeave
} = useTableContextMenu()"""
if re.search(pattern, content):
content = re.sub(pattern, r"\1" + usage_code, content)
return content
def add_table_events(content):
"""为 ArtTable 添加事件监听"""
# 查找 ArtTable 标签
table_pattern = r"(<ArtTable[^>]*?)(\s*@row-contextmenu=\"[^\"]+\")?([^>]*>)"
if '@cell-mouse-enter' in content:
return content
# 添加必要的属性和事件
def replace_table(match):
prefix = match.group(1)
existing_contextmenu = match.group(2) or ''
suffix = match.group(3)
# 如果没有 row-class-name,添加它
if ':row-class-name' not in prefix and 'row-class-name' not in prefix:
prefix += '\n :row-class-name="getRowClassName"'
# 如果没有 row-contextmenu,添加它
if not existing_contextmenu and '@row-contextmenu' not in prefix:
existing_contextmenu = '\n @row-contextmenu="handleRowContextMenu"'
# 添加 cell mouse 事件
cell_events = '\n @cell-mouse-enter="handleCellMouseEnter"\n @cell-mouse-leave="handleCellMouseLeave"'
return prefix + existing_contextmenu + cell_events + suffix
content = re.sub(table_pattern, replace_table, content, flags=re.DOTALL)
return content
def add_hint_component(content):
"""添加悬浮提示组件"""
if 'TableContextMenuHint' in content and ':visible="showContextMenuHint"' in content:
return content
# 在 </ArtTable> 后添加提示组件
table_end_pattern = r"(</ArtTable>)"
hint_component = r"""\1
<!-- 鼠标悬浮提示 -->
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />"""
content = re.sub(table_end_pattern, hint_component, content)
return content
def add_context_menu_component(content):
"""添加右键菜单组件"""
if 'ArtMenuRight' in content and 'contextMenuRef' in content:
return content
# 在提示组件后添加右键菜单
hint_pattern = r"(<TableContextMenuHint[^>]+/>)"
menu_component = r"""\1
<!-- 右键菜单 -->
<ArtMenuRight
ref="contextMenuRef"
:menu-items="contextMenuItems"
:menu-width="120"
@select="handleContextMenuSelect"
/>"""
content = re.sub(hint_pattern, menu_component, content)
return content
def add_css_styles(content):
"""添加 CSS 样式"""
if 'table-row-with-context-menu' in content:
return content
# 查找 <style> 标签
style_pattern = r"(<style[^>]*>)"
css_code = r"""\1
:deep(.el-table__row.table-row-with-context-menu) {
cursor: pointer;
}
"""
content = re.sub(style_pattern, css_code, content)
return content
def process_file(file_path):
"""处理单个文件"""
print(f"Processing: {file_path}")
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
original_content = content
# 执行所有转换
content = add_imports(content)
content = add_context_menu_usage(content)
content = add_table_events(content)
content = add_hint_component(content)
content = add_context_menu_component(content)
content = add_css_styles(content)
# 如果内容有变化,写回文件
if content != original_content:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f" ✓ Updated")
return True
else:
print(f" - No changes needed")
return False
except Exception as e:
print(f" ✗ Error: {e}")
return False
def main():
"""主函数"""
# 定义需要处理的文件列表
files_to_process = [
"src/views/package-management/package-list/index.vue",
"src/views/account-management/account/index.vue",
"src/views/account-management/enterprise-customer/index.vue",
"src/views/account-management/enterprise-cards/index.vue",
"src/views/asset-management/iot-card-management/index.vue",
"src/views/asset-management/iot-card-task/index.vue",
"src/views/asset-management/device-task/index.vue",
"src/views/asset-management/asset-assign/index.vue",
"src/views/asset-management/authorization-records/index.vue",
"src/views/finance/commission/agent-commission/index.vue",
]
base_dir = os.path.dirname(os.path.abspath(__file__))
updated_count = 0
for file_rel_path in files_to_process:
file_path = os.path.join(base_dir, file_rel_path)
if os.path.exists(file_path):
if process_file(file_path):
updated_count += 1
else:
print(f"File not found: {file_path}")
print(f"\n完成! 更新了 {updated_count} 个文件")
if __name__ == '__main__':
main()