834 lines
27 KiB
Vue
834 lines
27 KiB
Vue
<template>
|
||
<div class="payment-orders-container">
|
||
<!-- Search and Filter Bar -->
|
||
<el-card class="filter-card">
|
||
<el-form :inline="true" :model="filters" class="filter-form">
|
||
<el-form-item label="用户ID">
|
||
<el-input v-model="filters.userId" placeholder="输入用户ID" clearable />
|
||
</el-form-item>
|
||
<el-form-item label="状态">
|
||
<el-select v-model="filters.status" placeholder="全部" clearable style="width: 120px">
|
||
<el-option label="全部" value="" />
|
||
<el-option label="有效" value="active" />
|
||
<el-option label="已取消" value="cancelled" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="货币类型">
|
||
<el-select v-model="filters.currency" placeholder="全部" clearable style="width: 120px">
|
||
<el-option label="全部" value="" />
|
||
<el-option label="人民币" value="rmb" />
|
||
<el-option label="比索" value="peso" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="支付时间">
|
||
<el-date-picker
|
||
v-model="dateRange"
|
||
type="daterange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||
<el-button @click="handleReset">重置</el-button>
|
||
<el-button type="success" @click="showCreateDialog">创建支付订单</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- Amount Statistics -->
|
||
<el-card class="statistics-card">
|
||
<div class="statistics-wrapper">
|
||
<div class="statistics-item">
|
||
<span class="statistics-label">人民币总额</span>
|
||
<span class="statistics-value rmb">¥{{ statistics.totalRmb }}</span>
|
||
</div>
|
||
<div class="statistics-item">
|
||
<span class="statistics-label">比索总额</span>
|
||
<span class="statistics-value peso">₱{{ statistics.totalPeso }}</span>
|
||
</div>
|
||
<div class="statistics-item">
|
||
<span class="statistics-label">人民币成本</span>
|
||
<span class="statistics-value cost">¥{{ statistics.totalCostRmb }}</span>
|
||
</div>
|
||
<div class="statistics-item">
|
||
<span class="statistics-label">比索成本</span>
|
||
<span class="statistics-value cost">₱{{ statistics.totalCostPeso }}</span>
|
||
</div>
|
||
<div class="statistics-item">
|
||
<span class="statistics-label">人民币营利</span>
|
||
<span class="statistics-value profit">¥{{ statistics.profitRmb }}</span>
|
||
</div>
|
||
<div class="statistics-item">
|
||
<span class="statistics-label">比索营利</span>
|
||
<span class="statistics-value profit">₱{{ statistics.profitPeso }}</span>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- Orders Table -->
|
||
<el-card class="table-card">
|
||
<el-table v-loading="loading" :data="orders" stripe border>
|
||
<el-table-column prop="orderNo" label="订单编号" width="180" />
|
||
<el-table-column label="用户" width="150">
|
||
<template #default="{ row }">
|
||
<span>{{ row.user?.nickname || '-' }}</span>
|
||
<br />
|
||
<small class="text-muted">UID: {{ row.user?.uid || '-' }}</small>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="serviceContent" label="服务内容" min-width="180" show-overflow-tooltip />
|
||
<el-table-column prop="paymentTime" label="支付时间" width="160">
|
||
<template #default="{ row }">
|
||
{{ formatDate(row.paymentTime) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="支付金额" width="140">
|
||
<template #default="{ row }">
|
||
<div class="amount-info">
|
||
<span v-if="row.amountPeso" class="amount peso">₱{{ row.amountPeso }}</span>
|
||
<span v-if="row.amountRmb" class="amount rmb">¥{{ row.amountRmb }}</span>
|
||
<span v-if="!row.amountPeso && !row.amountRmb && row.amount" class="amount">¥{{ row.amount }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="成本金额" width="140">
|
||
<template #default="{ row }">
|
||
<div class="amount-info">
|
||
<span v-if="row.costPeso" class="amount cost">₱{{ row.costPeso }}</span>
|
||
<span v-if="row.costRmb" class="amount cost">¥{{ row.costRmb }}</span>
|
||
<span v-if="!row.costPeso && !row.costRmb" class="no-data">-</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="营利金额" width="140">
|
||
<template #default="{ row }">
|
||
<div class="amount-info">
|
||
<span v-if="row.amountPeso" class="amount profit">₱{{ (parseFloat(row.amountPeso || 0) - parseFloat(row.costPeso || 0)).toFixed(2) }}</span>
|
||
<span v-if="row.amountRmb" class="amount profit">¥{{ (parseFloat(row.amountRmb || 0) - parseFloat(row.costRmb || 0)).toFixed(2) }}</span>
|
||
<span v-if="!row.amountPeso && !row.amountRmb && row.amount" class="amount profit">¥{{ (parseFloat(row.amount || 0)).toFixed(2) }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="佣金" width="120">
|
||
<template #default="{ row }">
|
||
<div v-if="row.commissions && row.commissions.length > 0" class="commissions-info">
|
||
<span
|
||
v-for="(comm, index) in row.commissions"
|
||
:key="index"
|
||
class="commission-item"
|
||
:class="{ 'peso': comm.currency === 'PHP', 'rmb': comm.currency !== 'PHP' }"
|
||
>
|
||
{{ comm.currency === 'PHP' ? '₱' : '¥' }}{{ comm.amount }}
|
||
</span>
|
||
</div>
|
||
<span v-else class="no-commission">无</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" width="80">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.status === 'active' ? 'success' : 'danger'" size="small">
|
||
{{ row.status === 'active' ? '有效' : '已取消' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="120" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link @click="showDetails(row)">详情</el-button>
|
||
<el-button
|
||
v-if="row.status === 'active'"
|
||
type="danger"
|
||
link
|
||
@click="handleCancel(row)"
|
||
>取消</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- Pagination -->
|
||
<div class="pagination-wrapper">
|
||
<el-pagination
|
||
v-model:current-page="pagination.page"
|
||
v-model:page-size="pagination.limit"
|
||
:total="pagination.total"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handlePageChange"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- Create Dialog -->
|
||
<el-dialog v-model="createDialogVisible" title="创建支付订单" width="550px">
|
||
<el-form ref="createFormRef" :model="createForm" :rules="createRules" label-width="100px">
|
||
<el-form-item label="用户ID" prop="userId">
|
||
<el-input v-model="createForm.userId" placeholder="输入用户UID(6位数字)" />
|
||
</el-form-item>
|
||
<el-form-item label="比索金额">
|
||
<el-input-number v-model="createForm.amountPeso" :min="0" :precision="2" placeholder="比索" />
|
||
<span class="currency-hint">₱ 比索(可选)</span>
|
||
</el-form-item>
|
||
<el-form-item label="人民币金额">
|
||
<el-input-number v-model="createForm.amountRmb" :min="0" :precision="2" placeholder="人民币" />
|
||
<span class="currency-hint">¥ 人民币(可选)</span>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-alert type="info" :closable="false" show-icon>
|
||
比索和人民币至少填写一项,如果两个都填写表示客户同时支付了两种货币
|
||
</el-alert>
|
||
</el-form-item>
|
||
<el-form-item label="服务内容" prop="serviceContent">
|
||
<el-input v-model="createForm.serviceContent" type="textarea" :rows="3" placeholder="输入服务内容" />
|
||
</el-form-item>
|
||
<el-form-item label="比索成本">
|
||
<el-input-number v-model="createForm.costPeso" :min="0" :precision="2" placeholder="比索成本" />
|
||
<span class="currency-hint">₱ 比索(可选)</span>
|
||
</el-form-item>
|
||
<el-form-item label="人民币成本">
|
||
<el-input-number v-model="createForm.costRmb" :min="0" :precision="2" placeholder="人民币成本" />
|
||
<span class="currency-hint">¥ 人民币(可选)</span>
|
||
</el-form-item>
|
||
<el-form-item label="支付时间" prop="paymentTime">
|
||
<el-date-picker
|
||
v-model="createForm.paymentTime"
|
||
type="datetime"
|
||
placeholder="选择支付时间"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="支付凭证" prop="paymentProof" required>
|
||
<el-upload
|
||
class="payment-proof-uploader"
|
||
:action="uploadUrl"
|
||
:headers="uploadHeaders"
|
||
:show-file-list="false"
|
||
:on-success="handleUploadSuccess"
|
||
:on-error="handleUploadError"
|
||
:before-upload="beforeUpload"
|
||
accept="image/*"
|
||
>
|
||
<img v-if="createForm.paymentProof" :src="getImageUrl(createForm.paymentProof)" class="payment-proof-image" />
|
||
<el-icon v-else class="payment-proof-uploader-icon"><Plus /></el-icon>
|
||
</el-upload>
|
||
<div class="upload-tip">点击上传支付凭证截图(必填)</div>
|
||
</el-form-item>
|
||
<el-form-item label="关联预约单">
|
||
<el-input v-model="createForm.appointmentId" placeholder="可选,输入预约单ID" />
|
||
</el-form-item>
|
||
<el-form-item label="备注">
|
||
<el-input v-model="createForm.notes" type="textarea" :rows="2" placeholder="可选备注" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="createDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="creating" @click="handleCreate">创建</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- Details Dialog -->
|
||
<el-dialog v-model="detailsDialogVisible" title="订单详情" width="600px">
|
||
<el-descriptions v-if="currentOrder" :column="2" border>
|
||
<el-descriptions-item label="订单编号">{{ currentOrder.orderNo }}</el-descriptions-item>
|
||
<el-descriptions-item label="状态">
|
||
<el-tag :type="currentOrder.status === 'active' ? 'success' : 'danger'">
|
||
{{ currentOrder.status === 'active' ? '有效' : '已取消' }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="用户">{{ currentOrder.user?.nickname }} (UID: {{ currentOrder.user?.uid }})</el-descriptions-item>
|
||
<el-descriptions-item label="支付金额">
|
||
<div class="amount-detail">
|
||
<span v-if="currentOrder.amountPeso" class="peso">₱{{ currentOrder.amountPeso }} (比索)</span>
|
||
<span v-if="currentOrder.amountRmb" class="rmb">¥{{ currentOrder.amountRmb }} (人民币)</span>
|
||
<span v-if="!currentOrder.amountPeso && !currentOrder.amountRmb && currentOrder.amount">¥{{ currentOrder.amount }}</span>
|
||
</div>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="成本金额">
|
||
<div class="amount-detail">
|
||
<span v-if="currentOrder.costPeso" class="peso">₱{{ currentOrder.costPeso }} (比索)</span>
|
||
<span v-if="currentOrder.costRmb" class="rmb">¥{{ currentOrder.costRmb }} (人民币)</span>
|
||
<span v-if="!currentOrder.costPeso && !currentOrder.costRmb">-</span>
|
||
</div>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="服务内容" :span="2">{{ currentOrder.serviceContent }}</el-descriptions-item>
|
||
<el-descriptions-item label="支付时间">{{ formatDate(currentOrder.paymentTime) }}</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">{{ formatDate(currentOrder.createdAt) }}</el-descriptions-item>
|
||
<el-descriptions-item label="支付凭证" :span="2">
|
||
<el-image
|
||
v-if="currentOrder.paymentProof"
|
||
:src="getImageUrl(currentOrder.paymentProof)"
|
||
:preview-src-list="[getImageUrl(currentOrder.paymentProof)]"
|
||
fit="contain"
|
||
style="width: 120px; height: 120px;"
|
||
/>
|
||
<span v-else>-</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="备注" :span="2">{{ currentOrder.notes || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="创建人">{{ currentOrder.creator?.realName || currentOrder.creator?.username }}</el-descriptions-item>
|
||
<el-descriptions-item label="关联预约单">{{ currentOrder.appointment?.appointmentNo || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item v-if="currentOrder.status === 'cancelled'" label="取消原因" :span="2">
|
||
<span class="cancel-reason">{{ currentOrder.cancelReason || '-' }}</span>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<el-divider v-if="currentOrder?.commissions && currentOrder.commissions.length > 0">佣金信息</el-divider>
|
||
<div v-if="currentOrder?.commissions && currentOrder.commissions.length > 0">
|
||
<el-descriptions
|
||
v-for="(comm, index) in currentOrder.commissions"
|
||
:key="comm.id || index"
|
||
:column="2"
|
||
border
|
||
:class="{ 'commission-section': index > 0 }"
|
||
>
|
||
<el-descriptions-item label="货币类型">
|
||
<el-tag :type="comm.currency === 'PHP' ? 'primary' : 'warning'" size="small">
|
||
{{ comm.currency === 'PHP' ? '比索 (PHP)' : '人民币 (RMB)' }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="佣金状态">
|
||
<el-tag :type="comm.status === 'credited' ? 'success' : 'danger'">
|
||
{{ comm.status === 'credited' ? '已入账' : '已取消' }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="邀请人">{{ comm.inviter?.nickname || '-' }} (UID: {{ comm.inviter?.uid || '-' }})</el-descriptions-item>
|
||
<el-descriptions-item label="佣金比例">{{ comm.commissionRate }}</el-descriptions-item>
|
||
<el-descriptions-item label="佣金金额">
|
||
<span :class="{ 'peso-amount': comm.currency === 'PHP', 'rmb-amount': comm.currency !== 'PHP' }">
|
||
{{ comm.currency === 'PHP' ? '₱' : '¥' }}{{ comm.commissionAmount }}
|
||
</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">{{ formatDate(comm.createdAt) }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, onMounted } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { Plus } from '@element-plus/icons-vue'
|
||
import api from '@/utils/api'
|
||
|
||
// State
|
||
const loading = ref(false)
|
||
const creating = ref(false)
|
||
const orders = ref([])
|
||
const dateRange = ref([])
|
||
const createDialogVisible = ref(false)
|
||
const detailsDialogVisible = ref(false)
|
||
const currentOrder = ref(null)
|
||
const createFormRef = ref(null)
|
||
|
||
// Upload config
|
||
const uploadUrl = computed(() => {
|
||
const baseUrl = import.meta.env.VITE_API_BASE_URL || ''
|
||
return `${baseUrl}/api/v1/admin/upload/image`
|
||
})
|
||
|
||
const uploadHeaders = computed(() => {
|
||
const token = localStorage.getItem('admin_token')
|
||
return {
|
||
Authorization: token ? `Bearer ${token}` : ''
|
||
}
|
||
})
|
||
|
||
const filters = reactive({
|
||
userId: '',
|
||
status: '',
|
||
currency: '',
|
||
})
|
||
|
||
const statistics = reactive({
|
||
totalRmb: '0.00',
|
||
totalPeso: '0.00',
|
||
totalCostRmb: '0.00',
|
||
totalCostPeso: '0.00',
|
||
profitRmb: '0.00',
|
||
profitPeso: '0.00',
|
||
})
|
||
|
||
const pagination = reactive({
|
||
page: 1,
|
||
limit: 20,
|
||
total: 0,
|
||
})
|
||
|
||
const createForm = reactive({
|
||
userId: '',
|
||
amountPeso: null,
|
||
amountRmb: null,
|
||
costPeso: null,
|
||
costRmb: null,
|
||
serviceContent: '',
|
||
paymentTime: '',
|
||
paymentProof: '',
|
||
appointmentId: '',
|
||
notes: '',
|
||
})
|
||
|
||
const createRules = {
|
||
userId: [{ required: true, message: '请输入用户ID', trigger: 'blur' }],
|
||
serviceContent: [{ required: true, message: '请输入服务内容', trigger: 'blur' }],
|
||
paymentTime: [{ required: true, message: '请选择支付时间', trigger: 'change' }],
|
||
paymentProof: [{ required: true, message: '请上传支付凭证', trigger: 'change' }],
|
||
}
|
||
|
||
// Methods
|
||
async function fetchOrders() {
|
||
loading.value = true
|
||
try {
|
||
const params = {
|
||
page: pagination.page,
|
||
limit: pagination.limit,
|
||
}
|
||
if (filters.userId) params.userId = filters.userId
|
||
if (filters.status) params.status = filters.status
|
||
if (filters.currency) params.currency = filters.currency
|
||
if (dateRange.value?.length === 2) {
|
||
params.startDate = dateRange.value[0]
|
||
params.endDate = dateRange.value[1]
|
||
}
|
||
|
||
const response = await api.get('/api/v1/admin/payment-orders', { params })
|
||
if (response.data.code === 0) {
|
||
orders.value = response.data.data.records
|
||
pagination.total = response.data.data.pagination.total
|
||
|
||
// Update statistics from API response
|
||
if (response.data.data.statistics) {
|
||
statistics.totalRmb = response.data.data.statistics.totalRmb || '0.00'
|
||
statistics.totalPeso = response.data.data.statistics.totalPeso || '0.00'
|
||
statistics.totalCostRmb = response.data.data.statistics.totalCostRmb || '0.00'
|
||
statistics.totalCostPeso = response.data.data.statistics.totalCostPeso || '0.00'
|
||
statistics.profitRmb = response.data.data.statistics.profitRmb || '0.00'
|
||
statistics.profitPeso = response.data.data.statistics.profitPeso || '0.00'
|
||
} else {
|
||
statistics.totalRmb = '0.00'
|
||
statistics.totalPeso = '0.00'
|
||
statistics.totalCostRmb = '0.00'
|
||
statistics.totalCostPeso = '0.00'
|
||
statistics.profitRmb = '0.00'
|
||
statistics.profitPeso = '0.00'
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to fetch orders:', error)
|
||
ElMessage.error('获取订单列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
function handleSearch() {
|
||
pagination.page = 1
|
||
fetchOrders()
|
||
}
|
||
|
||
function handleReset() {
|
||
filters.userId = ''
|
||
filters.status = ''
|
||
filters.currency = ''
|
||
dateRange.value = []
|
||
pagination.page = 1
|
||
fetchOrders()
|
||
}
|
||
|
||
function handlePageChange(page) {
|
||
pagination.page = page
|
||
fetchOrders()
|
||
}
|
||
|
||
function handleSizeChange(size) {
|
||
pagination.limit = size
|
||
pagination.page = 1
|
||
fetchOrders()
|
||
}
|
||
|
||
function showCreateDialog() {
|
||
// 生成当前本地时间
|
||
const now = new Date()
|
||
const year = now.getFullYear()
|
||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||
const day = String(now.getDate()).padStart(2, '0')
|
||
const hours = String(now.getHours()).padStart(2, '0')
|
||
const minutes = String(now.getMinutes()).padStart(2, '0')
|
||
const seconds = String(now.getSeconds()).padStart(2, '0')
|
||
const currentTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||
|
||
Object.assign(createForm, {
|
||
userId: '',
|
||
amountPeso: null,
|
||
amountRmb: null,
|
||
costPeso: null,
|
||
costRmb: null,
|
||
serviceContent: '',
|
||
paymentTime: currentTime,
|
||
paymentProof: '',
|
||
appointmentId: '',
|
||
notes: '',
|
||
})
|
||
createDialogVisible.value = true
|
||
}
|
||
|
||
// Upload handlers
|
||
function getImageUrl(path) {
|
||
if (!path) return ''
|
||
if (path.startsWith('http')) return path
|
||
const baseUrl = import.meta.env.VITE_API_BASE_URL || ''
|
||
return `${baseUrl}${path}`
|
||
}
|
||
|
||
function handleUploadSuccess(response) {
|
||
if (response.code === 0 && response.data?.url) {
|
||
createForm.paymentProof = response.data.url
|
||
ElMessage.success('上传成功')
|
||
} else {
|
||
ElMessage.error(response.error?.message || '上传失败')
|
||
}
|
||
}
|
||
|
||
function handleUploadError(error) {
|
||
console.error('Upload error:', error)
|
||
ElMessage.error('上传失败,请重试')
|
||
}
|
||
|
||
function beforeUpload(file) {
|
||
const isImage = file.type.startsWith('image/')
|
||
const isLt5M = file.size / 1024 / 1024 < 5
|
||
|
||
if (!isImage) {
|
||
ElMessage.error('只能上传图片文件')
|
||
return false
|
||
}
|
||
if (!isLt5M) {
|
||
ElMessage.error('图片大小不能超过 5MB')
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
async function handleCreate() {
|
||
if (!createFormRef.value) return
|
||
|
||
// Custom validation for amounts - check for null/undefined, not just falsy
|
||
const hasPeso = createForm.amountPeso !== null && createForm.amountPeso !== undefined && createForm.amountPeso > 0
|
||
const hasRmb = createForm.amountRmb !== null && createForm.amountRmb !== undefined && createForm.amountRmb > 0
|
||
|
||
if (!hasPeso && !hasRmb) {
|
||
ElMessage.warning('请至少填写一种货币金额(比索或人民币)')
|
||
return
|
||
}
|
||
|
||
if (!createForm.paymentProof) {
|
||
ElMessage.warning('请上传支付凭证图片')
|
||
return
|
||
}
|
||
|
||
await createFormRef.value.validate(async (valid) => {
|
||
if (!valid) return
|
||
|
||
creating.value = true
|
||
try {
|
||
const response = await api.post('/api/v1/admin/payment-orders', {
|
||
userId: createForm.userId,
|
||
amountPeso: hasPeso ? createForm.amountPeso : undefined,
|
||
amountRmb: hasRmb ? createForm.amountRmb : undefined,
|
||
costPeso: createForm.costPeso > 0 ? createForm.costPeso : undefined,
|
||
costRmb: createForm.costRmb > 0 ? createForm.costRmb : undefined,
|
||
serviceContent: createForm.serviceContent,
|
||
paymentTime: createForm.paymentTime,
|
||
paymentProof: createForm.paymentProof,
|
||
appointmentId: createForm.appointmentId || undefined,
|
||
notes: createForm.notes || undefined,
|
||
})
|
||
|
||
if (response.data.code === 0) {
|
||
ElMessage.success('创建成功')
|
||
createDialogVisible.value = false
|
||
fetchOrders()
|
||
|
||
// Show commission info for all created commissions
|
||
const commissions = response.data.data.commissions
|
||
if (commissions && commissions.length > 0) {
|
||
const commissionMessages = commissions.map(c => {
|
||
const currencySymbol = c.currency === 'PHP' ? '₱' : '¥'
|
||
return `${currencySymbol}${c.commissionAmount}`
|
||
})
|
||
ElMessage.info(`已生成佣金: ${commissionMessages.join(', ')}`)
|
||
}
|
||
} else {
|
||
ElMessage.error(response.data.error?.message || '创建失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to create order:', error)
|
||
const errorMsg = error.response?.data?.error?.message || '创建失败'
|
||
ElMessage.error(errorMsg)
|
||
} finally {
|
||
creating.value = false
|
||
}
|
||
})
|
||
}
|
||
|
||
async function showDetails(row) {
|
||
try {
|
||
const response = await api.get(`/api/v1/admin/payment-orders/${row.id}`)
|
||
if (response.data.code === 0) {
|
||
currentOrder.value = response.data.data
|
||
detailsDialogVisible.value = true
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to get order details:', error)
|
||
ElMessage.error('获取订单详情失败')
|
||
}
|
||
}
|
||
|
||
async function handleCancel(row) {
|
||
try {
|
||
const { value: cancelReason } = await ElMessageBox.prompt(
|
||
'请输入取消原因,相关佣金也将被取消。',
|
||
'取消订单',
|
||
{
|
||
confirmButtonText: '确认取消',
|
||
cancelButtonText: '返回',
|
||
inputPlaceholder: '请输入取消原因',
|
||
inputType: 'textarea',
|
||
inputValidator: (value) => {
|
||
if (!value || !value.trim()) {
|
||
return '请输入取消原因'
|
||
}
|
||
return true
|
||
},
|
||
type: 'warning',
|
||
}
|
||
)
|
||
|
||
const response = await api.put(`/api/v1/admin/payment-orders/${row.id}/cancel`, {
|
||
cancelReason
|
||
})
|
||
if (response.data.code === 0) {
|
||
ElMessage.success('订单已取消')
|
||
fetchOrders()
|
||
} else {
|
||
ElMessage.error(response.data.error?.message || '取消失败')
|
||
}
|
||
} catch (error) {
|
||
if (error !== 'cancel') {
|
||
console.error('Failed to cancel order:', error)
|
||
ElMessage.error(error.response?.data?.error?.message || '取消失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
function formatDate(date) {
|
||
if (!date) return '-'
|
||
return new Date(date).toLocaleString('zh-CN')
|
||
}
|
||
|
||
// Initialize
|
||
onMounted(() => {
|
||
fetchOrders()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.payment-orders-container {
|
||
.filter-card {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.statistics-card {
|
||
margin-bottom: 20px;
|
||
|
||
.statistics-wrapper {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 40px;
|
||
}
|
||
|
||
.statistics-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.statistics-label {
|
||
color: #606266;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.statistics-value {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
|
||
&.rmb {
|
||
color: #e6a23c;
|
||
}
|
||
|
||
&.peso {
|
||
color: #409eff;
|
||
}
|
||
|
||
&.cost {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
&.profit {
|
||
color: #67c23a;
|
||
}
|
||
}
|
||
}
|
||
|
||
.table-card {
|
||
.amount-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.amount {
|
||
font-weight: bold;
|
||
|
||
&.peso {
|
||
color: #409eff;
|
||
}
|
||
|
||
&.rmb {
|
||
color: #e6a23c;
|
||
}
|
||
|
||
&.cost {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
&.profit {
|
||
color: #67c23a;
|
||
}
|
||
}
|
||
|
||
.no-data {
|
||
color: #909399;
|
||
}
|
||
|
||
.commission {
|
||
color: #67c23a;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.commissions-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.commission-item {
|
||
font-weight: bold;
|
||
|
||
&.peso {
|
||
color: #409eff;
|
||
}
|
||
|
||
&.rmb {
|
||
color: #e6a23c;
|
||
}
|
||
}
|
||
|
||
.no-commission {
|
||
color: #909399;
|
||
}
|
||
|
||
.text-muted {
|
||
color: #909399;
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
.pagination-wrapper {
|
||
margin-top: 20px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.currency-hint {
|
||
margin-left: 10px;
|
||
color: #909399;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.amount-detail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
|
||
.peso {
|
||
color: #409eff;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.rmb {
|
||
color: #e6a23c;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.cancel-reason {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.commission-section {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.peso-amount {
|
||
color: #409eff;
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.rmb-amount {
|
||
color: #e6a23c;
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.payment-proof-uploader {
|
||
:deep(.el-upload) {
|
||
border: 1px dashed #d9d9d9;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
transition: border-color 0.3s;
|
||
width: 148px;
|
||
height: 148px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
&:hover {
|
||
border-color: #409eff;
|
||
}
|
||
}
|
||
}
|
||
|
||
.payment-proof-uploader-icon {
|
||
font-size: 28px;
|
||
color: #8c939d;
|
||
}
|
||
|
||
.payment-proof-image {
|
||
width: 146px;
|
||
height: 146px;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.upload-tip {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-top: 8px;
|
||
}
|
||
}
|
||
</style>
|