This commit is contained in:
642
src/views/finance/agent-recharge/index.vue
Normal file
642
src/views/finance/agent-recharge/index.vue
Normal file
@@ -0,0 +1,642 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user