Files
one-pipe-system/src/views/finance/agent-recharge/index.vue
sexygoat e975e6af4b
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m58s
新增: 微信配置-代理充值
2026-03-17 14:06:38 +08:00

643 lines
18 KiB
Vue

<template>
<ArtTableFullScreen>
<div class="agent-recharge-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="searchForm"
:items="searchFormItems"
:show-expand="false"
@reset="handleReset"
@search="handleSearch"
></ArtSearchBar>
<ElCard shadow="never" class="art-table-card">
<!-- 表格头部 -->
<ArtTableHeader
:columnList="columnOptions"
v-model:columns="columnChecks"
@refresh="handleRefresh"
>
<template #left>
<ElButton type="primary" @click="showCreateDialog">创建充值订单</ElButton>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="rechargeList"
:currentPage="pagination.page"
:pageSize="pagination.page_size"
:total="pagination.total"
:marginTop="10"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 创建充值订单对话框 -->
<ElDialog
v-model="createDialogVisible"
title="创建充值订单"
width="500px"
@closed="handleCreateDialogClosed"
>
<ElForm ref="createFormRef" :model="createForm" :rules="createRules" label-width="100px">
<ElFormItem label="充值金额" prop="amount">
<ElInputNumber
v-model="createForm.amount"
:min="100"
:max="1000000"
:precision="2"
:step="100"
style="width: 100%"
placeholder="请输入充值金额(元)"
/>
<div style="margin-top: 8px; font-size: 12px; color: var(--el-text-color-secondary)">
充值范围: ¥100 ~ ¥1,000,000
</div>
</ElFormItem>
<ElFormItem label="支付方式" prop="payment_method">
<ElSelect
v-model="createForm.payment_method"
placeholder="请选择支付方式"
style="width: 100%"
>
<ElOption label="微信在线支付" value="wechat" />
<!-- 只有平台用户才显示线下转账选项 -->
<ElOption
v-if="userStore.info.user_type === 1 || userStore.info.user_type === 2"
label="线下转账"
value="offline"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="目标店铺" prop="shop_id">
<ElTreeSelect
v-model="createForm.shop_id"
:data="shopTreeData"
placeholder="请选择店铺"
filterable
clearable
check-strictly
:render-after-expand="false"
:props="{
label: 'shop_name',
value: 'id',
children: 'children'
}"
style="width: 100%"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="createDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleCreateRecharge" :loading="createLoading">
确认创建
</ElButton>
</div>
</template>
</ElDialog>
<!-- 确认线下支付对话框 -->
<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="handleConfirmPay" :loading="confirmPayLoading">
确认支付
</ElButton>
</div>
</template>
</ElDialog>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { AgentRechargeService, ShopService } from '@/api/modules'
import { ElMessage, ElTag, ElTreeSelect } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import type {
AgentRecharge,
AgentRechargeQueryParams,
AgentRechargeStatus,
AgentRechargePaymentMethod,
CreateAgentRechargeRequest,
ConfirmOfflinePaymentRequest,
ShopResponse
} from '@/types/api'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useUserStore } from '@/store/modules/user'
import { formatDateTime } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias'
defineOptions({ name: 'AgentRechargeList' })
const router = useRouter()
const userStore = useUserStore()
const loading = ref(false)
const createLoading = ref(false)
const confirmPayLoading = ref(false)
const tableRef = ref()
const createDialogVisible = ref(false)
const confirmPayDialogVisible = ref(false)
const currentRecharge = ref<AgentRecharge | null>(null)
// 搜索表单初始值
const initialSearchState: AgentRechargeQueryParams = {
shop_id: undefined,
status: undefined,
start_date: '',
end_date: ''
}
// 搜索表单
const searchForm = reactive<AgentRechargeQueryParams>({ ...initialSearchState })
// 店铺选项
const shopOptions = ref<any[]>([])
const shopTreeData = ref<ShopResponse[]>([])
// 搜索表单配置
const searchFormItems: SearchFormItem[] = [
{
label: '店铺',
prop: 'shop_id',
type: 'select',
placeholder: '请选择店铺',
options: () =>
shopOptions.value.map((shop) => ({
label: shop.shop_name,
value: shop.id
})),
config: {
clearable: true,
filterable: true
}
},
{
label: '状态',
prop: 'status',
type: 'select',
placeholder: '请选择状态',
options: [
{ label: '待支付', value: 1 },
{ label: '已完成', value: 2 },
{ label: '已取消', value: 3 }
],
config: {
clearable: true
}
},
{
label: '创建时间',
prop: 'dateRange',
type: 'daterange',
config: {
clearable: true,
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
valueFormat: 'YYYY-MM-DD'
}
}
]
// 分页
const pagination = reactive({
page: 1,
page_size: 20,
total: 0
})
// 列配置
const columnOptions = [
{ label: 'ID', prop: 'id' },
{ label: '充值单号', prop: 'recharge_no' },
{ label: '店铺名称', prop: 'shop_name' },
{ label: '充值金额', prop: 'amount' },
{ label: '状态', prop: 'status' },
{ label: '支付方式', prop: 'payment_method' },
{ label: '支付通道', prop: 'payment_channel' },
{ label: '创建时间', prop: 'created_at' },
{ label: '支付时间', prop: 'paid_at' },
{ label: '完成时间', prop: 'completed_at' },
{ label: '操作', prop: 'actions' }
]
const createFormRef = ref<FormInstance>()
const confirmPayFormRef = ref<FormInstance>()
const createRules = reactive<FormRules>({
amount: [{ required: true, message: '请输入充值金额', trigger: 'blur' }],
payment_method: [{ required: true, message: '请选择支付方式', trigger: 'change' }],
shop_id: [{ required: true, message: '请选择目标店铺', trigger: 'change' }]
})
const confirmPayRules = reactive<FormRules>({
operation_password: [{ required: true, message: '请输入操作密码', trigger: 'blur' }]
})
const createForm = reactive<{ amount: number; payment_method: string; shop_id: number | null }>({
amount: 100,
payment_method: 'wechat',
shop_id: null
})
const confirmPayForm = reactive<ConfirmOfflinePaymentRequest>({
operation_password: ''
})
const rechargeList = ref<AgentRecharge[]>([])
// 格式化货币 - 将分转换为元
const formatCurrency = (amount: number): string => {
return `¥${(amount / 100).toFixed(2)}`
}
// 获取状态标签类型
const getStatusType = (status: AgentRechargeStatus): 'warning' | 'success' | 'info' => {
const statusMap: Record<AgentRechargeStatus, 'warning' | 'success' | 'info'> = {
1: 'warning', // 待支付
2: 'success', // 已完成
3: 'info' // 已取消
}
return statusMap[status] || 'info'
}
// 获取状态文本
const getStatusText = (status: AgentRechargeStatus): string => {
const statusMap: Record<AgentRechargeStatus, string> = {
1: '待支付',
2: '已完成',
3: '已取消'
}
return statusMap[status] || '-'
}
// 获取支付方式文本
const getPaymentMethodText = (method: AgentRechargePaymentMethod): string => {
const methodMap: Record<AgentRechargePaymentMethod, string> = {
wechat: '微信在线支付',
offline: '线下转账'
}
return methodMap[method] || method
}
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'id',
label: 'ID',
width: 80
},
{
prop: 'recharge_no',
label: '充值单号',
minWidth: 200,
formatter: (row: AgentRecharge) => {
return h(
'span',
{
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
onClick: (e: MouseEvent) => {
e.stopPropagation()
handleViewDetail(row)
}
},
row.recharge_no
)
}
},
{
prop: 'shop_name',
label: '店铺名称',
minWidth: 150
},
{
prop: 'amount',
label: '充值金额',
width: 120,
formatter: (row: AgentRecharge) => formatCurrency(row.amount)
},
{
prop: 'status',
label: '状态',
width: 100,
formatter: (row: AgentRecharge) => {
return h(ElTag, { type: getStatusType(row.status) }, () => getStatusText(row.status))
}
},
{
prop: 'payment_method',
label: '支付方式',
width: 120,
formatter: (row: AgentRecharge) => getPaymentMethodText(row.payment_method)
},
{
prop: 'payment_channel',
label: '支付通道',
width: 120
},
{
prop: 'created_at',
label: '创建时间',
width: 180,
formatter: (row: AgentRecharge) => formatDateTime(row.created_at)
},
{
prop: 'paid_at',
label: '支付时间',
width: 180,
formatter: (row: AgentRecharge) => (row.paid_at ? formatDateTime(row.paid_at) : '-')
},
{
prop: 'completed_at',
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)
}
}
])
onMounted(() => {
getTableData()
loadShops()
})
// 构建树形数据
const buildTreeData = (items: ShopResponse[]) => {
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
const tree: ShopResponse[] = []
// 先将所有项放入 map
items.forEach((item) => {
map.set(item.id, { ...item, children: [] })
})
// 构建树形结构
items.forEach((item) => {
const node = map.get(item.id)!
if (item.parent_id && map.has(item.parent_id)) {
// 有父节点,添加到父节点的 children 中
const parent = map.get(item.parent_id)!
if (!parent.children) parent.children = []
parent.children.push(node)
} else {
// 没有父节点或父节点不存在,作为根节点
tree.push(node)
}
})
return tree
}
// 加载店铺列表
const loadShops = async () => {
try {
const params: any = {
page: 1,
page_size: 9999 // 获取所有数据用于构建树形结构
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
const items = res.data.items || []
// 保留平铺列表用于搜索
shopOptions.value = items
// 构建树形数据用于创建对话框
shopTreeData.value = buildTreeData(items)
}
} catch (error) {
console.error('Load shops failed:', error)
}
}
// 获取充值订单列表
const getTableData = async () => {
loading.value = true
try {
const params: AgentRechargeQueryParams = {
page: pagination.page,
page_size: pagination.page_size,
shop_id: searchForm.shop_id,
status: searchForm.status,
start_date: searchForm.start_date || undefined,
end_date: searchForm.end_date || undefined
}
const res = await AgentRechargeService.getAgentRecharges(params)
if (res.code === 0) {
rechargeList.value = res.data.list || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
// 重置搜索
const handleReset = () => {
Object.assign(searchForm, { ...initialSearchState })
pagination.page = 1
getTableData()
}
// 搜索
const handleSearch = () => {
// 处理日期范围
if (searchForm.dateRange && Array.isArray(searchForm.dateRange)) {
searchForm.start_date = searchForm.dateRange[0]
searchForm.end_date = searchForm.dateRange[1]
} else {
searchForm.start_date = ''
searchForm.end_date = ''
}
pagination.page = 1
getTableData()
}
// 刷新表格
const handleRefresh = () => {
getTableData()
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.page_size = newPageSize
getTableData()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.page = newCurrentPage
getTableData()
}
// 显示创建订单对话框
const showCreateDialog = async () => {
createDialogVisible.value = true
}
// 对话框关闭后的清理
const handleCreateDialogClosed = () => {
createFormRef.value?.resetFields()
createForm.amount = 100
createForm.payment_method = 'wechat'
createForm.shop_id = null
}
// 创建充值订单
const handleCreateRecharge = async () => {
if (!createFormRef.value) return
await createFormRef.value.validate(async (valid) => {
if (valid) {
createLoading.value = true
try {
const data: CreateAgentRechargeRequest = {
amount: createForm.amount * 100, // 元转分
payment_method: createForm.payment_method as AgentRechargePaymentMethod,
shop_id: createForm.shop_id!
}
await AgentRechargeService.createAgentRecharge(data)
ElMessage.success('充值订单创建成功')
createDialogVisible.value = false
createFormRef.value.resetFields()
await getTableData()
} catch (error) {
console.error(error)
} finally {
createLoading.value = false
}
}
})
}
// 显示确认支付对话框
const handleShowConfirmPay = (row: AgentRecharge) => {
currentRecharge.value = row
confirmPayDialogVisible.value = true
}
// 确认支付对话框关闭后的清理
const handleConfirmPayDialogClosed = () => {
confirmPayFormRef.value?.resetFields()
confirmPayForm.operation_password = ''
currentRecharge.value = null
}
// 确认线下支付
const handleConfirmPay = 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
})
ElMessage.success('确认支付成功')
confirmPayDialogVisible.value = false
confirmPayFormRef.value.resetFields()
await getTableData()
} catch (error) {
console.error(error)
} finally {
confirmPayLoading.value = false
}
}
})
}
// 查看详情
const handleViewDetail = (row: AgentRecharge) => {
router.push({
path: `${RoutesAlias.AgentRecharge}/detail/${row.id}`
})
}
</script>
<style scoped lang="scss">
.agent-recharge-page {
height: 100%;
}
</style>