appointment_system/admin/src/views/payment-orders/index.vue
2026-03-04 01:14:52 +08:00

834 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="输入用户UID6位数字" />
</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>