1637 lines
38 KiB
Vue
1637 lines
38 KiB
Vue
<template>
|
||
<div class="matching-container">
|
||
<div class="header">
|
||
<h1>货款匹配</h1>
|
||
<!-- <p class="subtitle">智能匹配,循环增值</p> -->
|
||
</div>
|
||
|
||
<!-- 统计卡片 -->
|
||
<!-- <div class="stats-cards">
|
||
<div class="stat-card">
|
||
<div class="stat-number">{{ stats.userStats?.initiated_orders || 0 }}</div>
|
||
<div class="stat-label">发起订单</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-number">{{ stats.userStats?.participated_allocations || 0 }}</div>
|
||
<div class="stat-label">参与分配</div>
|
||
</div>
|
||
</div> -->
|
||
|
||
<!-- 操作区域 -->
|
||
<div class="action-section">
|
||
<div class="create-order-card">
|
||
<h3>货款匹配</h3>
|
||
|
||
<!-- 匹配类型选择 -->
|
||
<div class="matching-type-selector">
|
||
<div class="type-tabs">
|
||
<button
|
||
:class="['type-tab', { active: matchingType === 'small' }]"
|
||
@click="matchingType = 'small'"
|
||
>
|
||
小额匹配
|
||
</button>
|
||
<button
|
||
:class="['type-tab', { active: matchingType === 'large' }]"
|
||
@click="matchingType = 'large'"
|
||
>
|
||
大额匹配
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 小额匹配信息 -->
|
||
<div v-if="matchingType === 'small'" class="matching-info">
|
||
<div class="info-item">
|
||
<span class="label">匹配总额:</span>
|
||
<span class="value">¥5,000.00</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">分配笔数:</span>
|
||
<span class="value">3笔</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">单笔范围:</span>
|
||
<span class="value">¥1,000 - ¥5,000</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 大额匹配信息 -->
|
||
<div v-if="matchingType === 'large'" class="matching-info">
|
||
<div class="info-item">
|
||
<span class="label">自定义金额:</span>
|
||
<div class="custom-amount-input">
|
||
<el-input
|
||
v-model="customAmount"
|
||
type="number"
|
||
:min="5000"
|
||
:max="50000"
|
||
step="100"
|
||
placeholder="请输入5000-50000之间的金额"
|
||
>
|
||
<template #prepend>¥</template>
|
||
</el-input>
|
||
</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">分配规则:</span>
|
||
<span class="value">{{ getLargeMatchingRule() }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">预计笔数:</span>
|
||
<span class="value">{{ getLargeMatchingCount() }}笔</span>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
@click="createOrder"
|
||
:disabled="creating || (matchingType === 'large' && !isValidCustomAmount)"
|
||
class="create-btn"
|
||
>
|
||
{{ creating ? '匹配中...' : '开始匹配' }}
|
||
</button>
|
||
|
||
<!-- 小额匹配提示 -->
|
||
<div v-if="matchingType === 'small'" class="tips">
|
||
<p>• 系统将为您匹配3笔货款,总金额5000元</p>
|
||
<p>• 优先匹配已完成进货的用户</p>
|
||
<!-- <p>• 每笔金额随机分配,确保货款循环</p> -->
|
||
</div>
|
||
|
||
<!-- 大额匹配提示 -->
|
||
<div v-if="matchingType === 'large'" class="tips">
|
||
<p>• 金额范围:5000-50000元</p>
|
||
<p>• 15000元以下:分成3笔随机金额</p>
|
||
<p>• 15000元以上:随机分拆,每笔1000-8000元</p>
|
||
<p>• 优先匹配已完成进货的用户</p>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
<!-- 待处理分配 -->
|
||
<div class="pending-section" v-if="pendingAllocations.length > 0">
|
||
<h3>待处理分配</h3>
|
||
<div class="allocation-list">
|
||
<div
|
||
v-for="allocation in pendingAllocations"
|
||
:key="allocation.id"
|
||
class="allocation-card"
|
||
>
|
||
<div class="allocation-info">
|
||
<div class="allocation-header">
|
||
<span class="order-id">订单 #{{ allocation.matching_order_id }}</span>
|
||
<span class="cycle">第{{ allocation.cycle_number }}轮</span>
|
||
</div>
|
||
<div class="allocation-details">
|
||
<p>转账给: <strong>{{ allocation.to_user_real_name }}</strong></p>
|
||
<p>金额: <strong class="amount">¥{{ allocation.amount }}</strong></p>
|
||
<p>总金额: ¥{{ allocation.total_amount }}</p>
|
||
<p class="deadline-info">
|
||
转账时效:
|
||
<span :class="['time-left', allocation.time_status]">
|
||
{{ allocation.time_left }}
|
||
</span>
|
||
<span class="deadline-time">({{ formatDeadline(allocation.deadline) }}前)</span>
|
||
</p>
|
||
<div v-if="!allocation.can_transfer" class="timeout-warning">
|
||
<i class="el-icon-warning"></i>
|
||
<span class="warning-text">{{ allocation.timeout_reason }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="allocation-actions">
|
||
<button
|
||
@click="confirmAllocation(allocation.id, allocation.amount)"
|
||
class="confirm-btn"
|
||
:disabled="processing || !allocation.can_transfer"
|
||
:title="!allocation.can_transfer ? allocation.timeout_reason : ''"
|
||
>
|
||
{{ allocation.can_transfer ? '确认转账' : '无法转账' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 我的匹配订单 -->
|
||
<div class="orders-section">
|
||
<h3>我的匹配订单</h3>
|
||
<div class="orders-list">
|
||
<div
|
||
v-for="order in matchingOrders"
|
||
:key="order.id"
|
||
class="order-card"
|
||
@click="viewOrderDetail(order.id)"
|
||
>
|
||
<div class="order-header">
|
||
<span class="order-id">#{{ order.id }}</span>
|
||
<span v-if="order.is_system_reverse" class="system-reverse-tag">系统反向匹配</span>
|
||
<span :class="['status', order.status]">{{ getStatusText(order.status) }}</span>
|
||
</div>
|
||
<div class="order-info">
|
||
<p>金额: ¥{{ order.amount }}</p>
|
||
<p>发起人: {{ order.initiator_real_name }}</p>
|
||
<p v-if="!order.is_system_reverse">轮次: {{ order.cycle_count + 1 }}/{{ order.max_cycles }}</p>
|
||
<p v-if="order.is_system_reverse" class="system-note">系统自动发起,向负余额用户补充货款</p>
|
||
<p>创建时间: {{ formatDate(order.created_at) }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="matchingOrders.length === 0" class="empty-state">
|
||
<p>暂无匹配订单</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 订单详情弹窗 -->
|
||
<div v-if="showOrderDetail" class="modal-overlay" @click="closeOrderDetail">
|
||
<div class="modal-content" @click.stop>
|
||
<div class="modal-header">
|
||
<h3>订单详情 #{{ selectedOrder?.order?.id }}</h3>
|
||
<button @click="closeOrderDetail" class="close-btn">×</button>
|
||
</div>
|
||
<div class="modal-body" v-if="selectedOrder">
|
||
<div class="order-summary">
|
||
<p><strong>状态:</strong> {{ getStatusText(selectedOrder.order.status) }}</p>
|
||
<p><strong>金额:</strong> ¥{{ selectedOrder.order.amount }}</p>
|
||
<p><strong>发起人:</strong> {{ selectedOrder.order.initiator_real_name }}</p>
|
||
<p><strong>轮次:</strong> {{ selectedOrder.order.cycle_count + 1 }}/{{ selectedOrder.order.max_cycles }}</p>
|
||
</div>
|
||
|
||
<div class="allocations-section">
|
||
<h4>分配详情</h4>
|
||
<div class="allocation-timeline">
|
||
<div
|
||
v-for="allocation in selectedOrder.allocations"
|
||
:key="allocation.id"
|
||
class="timeline-item"
|
||
>
|
||
<div class="timeline-content">
|
||
<div class="timeline-header">
|
||
<span class="cycle">第{{ allocation.cycle_number }}轮</span>
|
||
<span :class="['status', allocation.status]">{{ getStatusText(allocation.status) }}</span>
|
||
</div>
|
||
<p>{{ allocation.from_user_real_name }} → {{ allocation.to_user_real_name }}</p>
|
||
<p class="amount">¥{{ allocation.amount }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="records-section">
|
||
<h4>操作记录</h4>
|
||
<div class="records-list">
|
||
<div
|
||
v-for="record in selectedOrder.records"
|
||
:key="record.id"
|
||
class="record-item"
|
||
>
|
||
<div class="record-info">
|
||
<span class="action">{{ getActionText(record.action) }}</span>
|
||
<span class="user">{{ record.real_name }}</span>
|
||
<span class="time">{{ formatDate(record.created_at) }}</span>
|
||
</div>
|
||
<div v-if="record.amount" class="record-amount">¥{{ record.amount }}</div>
|
||
<div v-if="record.note" class="record-note">{{ record.note }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 转账弹窗 -->
|
||
<el-dialog
|
||
v-model="transferDialog.visible"
|
||
title="确认转账"
|
||
width="90%"
|
||
:style="{ maxWidth: '600px' }"
|
||
@close="closeTransferDialog"
|
||
>
|
||
<div class="transfer-dialog-content">
|
||
<!-- 转账信息 -->
|
||
<div class="transfer-info">
|
||
<h4>转账信息</h4>
|
||
<p><strong>收款人:</strong> {{ transferDialog.toUser.to_user_real_name }}</p>
|
||
<p><strong>转账金额:</strong> ¥{{ transferDialog.amount }}</p>
|
||
</div>
|
||
|
||
<!-- 收款码展示 -->
|
||
<div class="payment-codes">
|
||
<h4>收款方式</h4>
|
||
<div class="payment-grid">
|
||
<div v-if="transferDialog.toUser.wechatQr" class="payment-item">
|
||
<h5>微信收款码</h5>
|
||
<img :src="getImageUrl(transferDialog.toUser.wechatQr)" alt="微信收款码" class="qr-code" />
|
||
</div>
|
||
<div v-if="transferDialog.toUser.alipayQr" class="payment-item">
|
||
<h5>支付宝收款码</h5>
|
||
<img :src="getImageUrl(transferDialog.toUser.alipayQr)" alt="支付宝收款码" class="qr-code" />
|
||
</div>
|
||
<div v-if="transferDialog.toUser.unionpayQr" class="payment-item">
|
||
<h5>云闪付收款码</h5>
|
||
<img :src="getImageUrl(transferDialog.toUser.unionpayQr)" alt="云闪付收款码" class="qr-code" />
|
||
</div>
|
||
<div v-if="transferDialog.toUser.bankCard" class="payment-item">
|
||
<h5>银行账号</h5>
|
||
<p class="bank-card">{{ transferDialog.toUser.bankCard }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 转账表单 -->
|
||
<div class="transfer-form">
|
||
<h4>转账确认</h4>
|
||
<el-form label-width="100px">
|
||
<el-form-item label="转账金额">
|
||
<el-input
|
||
v-model="transferDialog.actualAmount"
|
||
readonly
|
||
disabled
|
||
>
|
||
<template #prepend>¥</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="转账说明">
|
||
<el-input
|
||
v-model="transferDialog.description"
|
||
type="textarea"
|
||
placeholder="请输入转账说明(可选)"
|
||
:rows="3"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="转账凭证">
|
||
<el-upload
|
||
:action="uploadUrl"
|
||
:headers="uploadHeaders"
|
||
:before-upload="beforeUpload"
|
||
:on-success="handleUploadSuccess"
|
||
:on-error="handleUploadError"
|
||
:show-file-list="false"
|
||
accept="image/*"
|
||
>
|
||
<el-button size="small" type="primary">上传凭证</el-button>
|
||
</el-upload>
|
||
<div v-if="transferDialog.voucher" class="upload-preview">
|
||
<img :src="getImageUrl(transferDialog.voucher)" alt="转账凭证" />
|
||
</div>
|
||
<div v-else class="upload-tip">
|
||
<span class="tip-text">* 必须上传转账凭证才能确认转账</span>
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="closeTransferDialog">取消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="submitTransfer"
|
||
:loading="processing"
|
||
:disabled="!transferDialog.voucher"
|
||
>
|
||
确认转账
|
||
</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import api from '../utils/api'
|
||
import { uploadURL, getImageUrl, getUploadConfig } from '@/config'
|
||
|
||
export default {
|
||
name: 'Matching',
|
||
data() {
|
||
return {
|
||
stats: {
|
||
userStats: null
|
||
},
|
||
creating: false,
|
||
processing: false,
|
||
pendingAllocations: [],
|
||
matchingOrders: [],
|
||
showOrderDetail: false,
|
||
selectedOrder: null,
|
||
matchingType: 'small', // 匹配类型:small(小额) 或 large(大额)
|
||
customAmount: '', // 大额匹配自定义金额
|
||
transferDialog: {
|
||
visible: false,
|
||
allocationId: null,
|
||
toUser: {
|
||
id: null,
|
||
name: '',
|
||
wechatQr: '',
|
||
alipayQr: '',
|
||
unionpayQr: '',
|
||
bankCard: '',
|
||
to_user_real_name:'',
|
||
},
|
||
amount: 0,
|
||
actualAmount: 0,
|
||
voucher: '',
|
||
description: ''
|
||
}
|
||
}
|
||
},
|
||
async mounted() {
|
||
await this.loadData()
|
||
},
|
||
methods: {
|
||
async loadData() {
|
||
try {
|
||
await Promise.all([
|
||
this.loadStats(),
|
||
this.loadPendingAllocations(),
|
||
this.loadMatchingOrders()
|
||
])
|
||
} catch (error) {
|
||
console.error('加载数据失败:', error)
|
||
this.$message.error('加载数据失败')
|
||
}
|
||
},
|
||
|
||
async loadStats() {
|
||
try {
|
||
const response = await api.get('/matching/stats')
|
||
this.stats = response.data.data || {}
|
||
|
||
} catch (error) {
|
||
console.error('加载统计数据失败:', error)
|
||
}
|
||
},
|
||
|
||
async loadPendingAllocations() {
|
||
try {
|
||
const response = await api.get('/matching/pending-allocations')
|
||
this.pendingAllocations = response.data.data || []
|
||
} catch (error) {
|
||
console.error('加载待处理分配失败:', error)
|
||
}
|
||
},
|
||
|
||
async loadMatchingOrders() {
|
||
try {
|
||
const response = await api.get('/matching/my-orders')
|
||
this.matchingOrders = response.data.data || []
|
||
} catch (error) {
|
||
console.error('加载匹配订单失败:', error)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 创建匹配订单
|
||
*/
|
||
async createOrder() {
|
||
try {
|
||
// 构建确认信息
|
||
let confirmMessage = ''
|
||
|
||
if (this.matchingType === 'small') {
|
||
confirmMessage = '确定要开始小额匹配吗?\n\n匹配成功后将生成3笔转账分配。'
|
||
} else {
|
||
if (!this.isValidCustomAmount) {
|
||
this.$message.error('请输入有效的匹配金额(5000-50000元)')
|
||
return
|
||
}
|
||
const amount = parseFloat(this.customAmount)
|
||
const count = this.getLargeMatchingCount()
|
||
confirmMessage = `确定要开始大额匹配吗?\n\n匹配金额:${amount}元\n将生成${count}笔转账分配`
|
||
}
|
||
|
||
// 二次确认对话框
|
||
await this.$confirm(confirmMessage, '确认匹配', {
|
||
confirmButtonText: '确认匹配',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
dangerouslyUseHTMLString: false
|
||
})
|
||
|
||
this.creating = true
|
||
|
||
// 构建请求参数
|
||
const requestData = {
|
||
matchingType: this.matchingType
|
||
}
|
||
|
||
// 如果是大额匹配,添加自定义金额
|
||
if (this.matchingType === 'large') {
|
||
requestData.customAmount = parseFloat(this.customAmount)
|
||
}
|
||
|
||
await api.post('/matching/create', requestData)
|
||
|
||
const successMessage = this.matchingType === 'small'
|
||
? '小额匹配成功!已为您生成3笔转账分配'
|
||
: `大额匹配成功!已为您生成${this.getLargeMatchingCount()}笔转账分配`
|
||
|
||
this.$message.success(successMessage)
|
||
await this.loadData()
|
||
} catch (error) {
|
||
// 如果用户取消确认,不显示错误信息
|
||
if (error === 'cancel') {
|
||
return
|
||
}
|
||
|
||
console.error('创建匹配订单失败:', error)
|
||
|
||
const errorMessage = error.response?.data?.message || '匹配失败,请稍后重试'
|
||
|
||
// 检查是否是审核相关的错误
|
||
if (errorMessage.includes('审核') || errorMessage.includes('上传') || errorMessage.includes('完善')) {
|
||
this.$confirm(errorMessage + ',是否前往个人中心完善资料?', '提示', {
|
||
confirmButtonText: '前往完善',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
this.$router.push('/profile')
|
||
}).catch(() => {})
|
||
} else {
|
||
this.$message.error(errorMessage)
|
||
}
|
||
} finally {
|
||
this.creating = false
|
||
}
|
||
},
|
||
|
||
|
||
|
||
/**
|
||
* 确认分配并创建转账记录
|
||
* @param {number} allocationId - 分配ID
|
||
* @param {number} expectedAmount - 预期转账金额
|
||
*/
|
||
async confirmAllocation(allocationId, expectedAmount) {
|
||
try {
|
||
// 获取分配详情和收款用户信息
|
||
const allocationResponse = await api.get(`/matching/allocation/${allocationId}`)
|
||
const allocation = allocationResponse.data.data
|
||
|
||
// 获取收款用户的收款码信息
|
||
const userResponse = await api.get(`/users/payment-info/${allocation.to_user_id}`)
|
||
const userPaymentInfo = userResponse.data.data
|
||
|
||
// 设置转账弹窗数据
|
||
this.transferDialog = {
|
||
visible: true,
|
||
allocationId: allocationId,
|
||
toUser: {
|
||
id: allocation.to_user_id,
|
||
name: allocation.to_user_name,
|
||
wechatQr: userPaymentInfo.wechat_qr,
|
||
alipayQr: userPaymentInfo.alipay_qr,
|
||
unionpayQr: userPaymentInfo.unionpay_qr,
|
||
bankCard: userPaymentInfo.bank_card,
|
||
to_user_real_name: allocation.to_user_real_name,
|
||
},
|
||
amount: expectedAmount,
|
||
actualAmount: expectedAmount
|
||
}
|
||
} catch (error) {
|
||
console.error('获取转账信息失败:', error)
|
||
this.$message.error('获取转账信息失败')
|
||
}
|
||
},
|
||
|
||
|
||
|
||
async viewOrderDetail(orderId) {
|
||
try {
|
||
const response = await api.get(`/matching/order/${orderId}`)
|
||
this.selectedOrder = response.data.data
|
||
this.showOrderDetail = true
|
||
} catch (error) {
|
||
console.error('获取订单详情失败:', error)
|
||
this.$message.error('获取订单详情失败')
|
||
}
|
||
},
|
||
|
||
closeOrderDetail() {
|
||
this.showOrderDetail = false
|
||
this.selectedOrder = null
|
||
},
|
||
|
||
getStatusText(status) {
|
||
const statusMap = {
|
||
pending: '待处理',
|
||
matching: '匹配中',
|
||
completed: '已完成',
|
||
cancelled: '已取消',
|
||
confirmed: '已确认',
|
||
failed: '匹配失败'
|
||
}
|
||
return statusMap[status] || status
|
||
},
|
||
|
||
getActionText(action) {
|
||
const actionMap = {
|
||
join: '加入',
|
||
confirm: '确认',
|
||
complete: '完成'
|
||
}
|
||
return actionMap[action] || action
|
||
},
|
||
|
||
formatDate(dateString) {
|
||
return new Date(dateString).toLocaleString('zh-CN')
|
||
},
|
||
|
||
/**
|
||
* 格式化截止时间显示
|
||
* @param {string} dateString - 日期字符串
|
||
* @returns {string} 格式化后的时间字符串
|
||
*/
|
||
formatDeadline(dateString) {
|
||
if (!dateString) return ''
|
||
const date = new Date(dateString)
|
||
const now = new Date()
|
||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||
const targetDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
||
|
||
const timeStr = date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||
|
||
if (targetDate.getTime() === today.getTime()) {
|
||
return `今天${timeStr}`
|
||
} else if (targetDate.getTime() === today.getTime() + 24 * 60 * 60 * 1000) {
|
||
return `明天${timeStr}`
|
||
} else {
|
||
return date.toLocaleString('zh-CN', {
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
})
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 格式化金额显示,确保数字安全
|
||
* @param {number|string|null|undefined} amount - 金额值
|
||
* @returns {string} 格式化后的金额字符串
|
||
*/
|
||
formatAmount(amount) {
|
||
const num = parseFloat(amount)
|
||
return isNaN(num) ? '0.00' : num.toFixed(2)
|
||
},
|
||
|
||
/**
|
||
* 获取大额匹配的分配规则描述
|
||
* @returns {string} 规则描述
|
||
*/
|
||
getLargeMatchingRule() {
|
||
const amount = parseFloat(this.customAmount) || 0
|
||
if (amount <= 0) {
|
||
return '请输入金额'
|
||
} else if (amount < 5000) {
|
||
return '金额不能少于5000元'
|
||
} else if (amount > 50000) {
|
||
return '金额不能超过50000元'
|
||
} else if (amount <= 15000) {
|
||
return '分成3笔随机金额'
|
||
} else {
|
||
return '随机分拆,每笔1000-8000元'
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 获取大额匹配的预计笔数
|
||
* @returns {string} 预计笔数描述
|
||
*/
|
||
getLargeMatchingCount() {
|
||
const amount = parseFloat(this.customAmount) || 0
|
||
if (amount <= 0 || amount < 5000 || amount > 50000) {
|
||
return '0'
|
||
} else if (amount <= 15000) {
|
||
return '3'
|
||
} else {
|
||
// 15000以上随机分拆,估算笔数范围
|
||
const minCount = Math.ceil(amount / 8000) // 按最大单笔8000计算最少笔数
|
||
const maxCount = Math.floor(amount / 1000) // 按最小单笔1000计算最多笔数
|
||
return `${minCount}-${Math.min(maxCount, 10)}` // 限制最大显示笔数为10
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 关闭转账弹窗
|
||
*/
|
||
closeTransferDialog() {
|
||
this.transferDialog.visible = false
|
||
this.transferDialog.allocationId = null
|
||
this.transferDialog.toUser = {
|
||
id: null,
|
||
name: '',
|
||
wechatQr: '',
|
||
alipayQr: '',
|
||
unionpayQr: '',
|
||
bankCard: ''
|
||
}
|
||
this.transferDialog.amount = 0
|
||
this.transferDialog.actualAmount = 0
|
||
this.transferDialog.voucher = ''
|
||
this.transferDialog.description = ''
|
||
},
|
||
|
||
/**
|
||
* 获取图片URL
|
||
* @param {string} imagePath - 图片路径
|
||
* @returns {string} 完整的图片URL
|
||
*/
|
||
getImageUrl(imagePath) {
|
||
return getImageUrl(imagePath)
|
||
},
|
||
|
||
/**
|
||
* 上传前验证
|
||
* @param {File} file - 上传的文件
|
||
* @returns {boolean} 是否通过验证
|
||
*/
|
||
beforeUpload(file) {
|
||
const isImage = file.type.startsWith('image/')
|
||
const isLt5M = file.size / 1024 / 1024 < 5
|
||
|
||
if (!isImage) {
|
||
this.$message.error('只能上传图片文件!')
|
||
}
|
||
if (!isLt5M) {
|
||
this.$message.error('图片大小不能超过 5MB!')
|
||
}
|
||
return isImage && isLt5M
|
||
},
|
||
|
||
/**
|
||
* 上传成功处理
|
||
* @param {Object} response - 上传响应
|
||
*/
|
||
handleUploadSuccess(response) {
|
||
if (response.success) {
|
||
this.transferDialog.voucher = response.url
|
||
this.$message.success('凭证上传成功')
|
||
} else {
|
||
this.$message.error(response.message || '上传失败')
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 上传失败处理
|
||
* @param {Error} error - 错误信息
|
||
*/
|
||
handleUploadError(error) {
|
||
console.error('上传失败:', error)
|
||
this.$message.error('上传失败')
|
||
},
|
||
|
||
/**
|
||
* 提交转账
|
||
*/
|
||
async submitTransfer() {
|
||
// 校验是否上传了转账凭证
|
||
if (!this.transferDialog.voucher) {
|
||
this.$message.error('请先上传转账凭证')
|
||
return
|
||
}
|
||
|
||
const actualAmount = parseFloat(this.transferDialog.actualAmount)
|
||
|
||
this.processing = true
|
||
try {
|
||
await api.post(`/matching/confirm-allocation/${this.transferDialog.allocationId}`, {
|
||
transferAmount: actualAmount,
|
||
description: this.transferDialog.description,
|
||
voucher: this.transferDialog.voucher
|
||
})
|
||
this.$message.success('转账凭证已提交,转账记录已创建')
|
||
this.closeTransferDialog()
|
||
// 直接跳转到转账记录页面
|
||
this.$router.push('/transfers')
|
||
} catch (error) {
|
||
console.error('确认分配失败:', error)
|
||
this.$message.error(error.response?.data?.message || '确认分配失败')
|
||
} finally {
|
||
this.processing = false
|
||
}
|
||
}
|
||
},
|
||
|
||
computed: {
|
||
/**
|
||
* 验证自定义金额是否有效
|
||
* @returns {boolean} 金额是否有效
|
||
*/
|
||
isValidCustomAmount() {
|
||
const amount = parseFloat(this.customAmount)
|
||
return !isNaN(amount) && amount >= 5000 && amount <= 50000
|
||
},
|
||
|
||
/**
|
||
* 上传URL
|
||
*/
|
||
uploadUrl() {
|
||
return uploadURL
|
||
},
|
||
|
||
/**
|
||
* 上传请求头
|
||
*/
|
||
uploadHeaders() {
|
||
return getUploadConfig().headers
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.matching-container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
.header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.header h1 {
|
||
color: #2c3e50;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.subtitle {
|
||
color: #7f8c8d;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.stats-cards {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-number {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #3498db;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.stat-label {
|
||
color: #7f8c8d;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.action-section {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.create-order-card {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.create-order-card h3 {
|
||
margin-bottom: 15px;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.matching-info {
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
border: 1px solid #e9ecef;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.info-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.info-item .label {
|
||
color: #6c757d;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.info-item .value {
|
||
color: #2c3e50;
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
color: #34495e;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-group input {
|
||
width: 100%;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.create-btn {
|
||
width: 100%;
|
||
padding: 12px;
|
||
background: #3498db;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 5px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
transition: background 0.3s;
|
||
}
|
||
|
||
.create-btn:hover {
|
||
background: #2980b9;
|
||
}
|
||
|
||
.create-btn:disabled {
|
||
background: #bdc3c7;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.tips {
|
||
margin-top: 15px;
|
||
padding: 10px;
|
||
background: #f8f9fa;
|
||
border-radius: 5px;
|
||
font-size: 12px;
|
||
color: #6c757d;
|
||
}
|
||
|
||
.tips p {
|
||
margin: 5px 0;
|
||
}
|
||
|
||
.amount {
|
||
font-weight: bold;
|
||
color: #27ae60;
|
||
}
|
||
|
||
.status.active {
|
||
color: #27ae60;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.status.inactive {
|
||
color: #e74c3c;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.pending-section,
|
||
.orders-section {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.pending-section h3,
|
||
.orders-section h3 {
|
||
margin-bottom: 15px;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.allocation-list,
|
||
.orders-list {
|
||
display: grid;
|
||
gap: 15px;
|
||
}
|
||
|
||
.allocation-card {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 15px;
|
||
border: 1px solid #e1e8ed;
|
||
border-radius: 8px;
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.allocation-header {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.order-id,
|
||
.cycle {
|
||
background: #3498db;
|
||
color: white;
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.allocation-details p {
|
||
margin: 5px 0;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.deadline-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.time-left {
|
||
font-weight: bold;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.time-left.normal {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
}
|
||
|
||
.time-left.urgent {
|
||
background: #fff3cd;
|
||
color: #856404;
|
||
}
|
||
|
||
.time-left.expired {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
animation: blink 1s infinite;
|
||
}
|
||
|
||
.deadline-time {
|
||
color: #6c757d;
|
||
font-size: 12px;
|
||
}
|
||
|
||
@keyframes blink {
|
||
0%, 50% { opacity: 1; }
|
||
51%, 100% { opacity: 0.5; }
|
||
}
|
||
|
||
.allocation-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.confirm-btn {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.confirm-btn {
|
||
background: #27ae60;
|
||
color: white;
|
||
}
|
||
|
||
.confirm-btn:hover {
|
||
background: #229954;
|
||
}
|
||
|
||
|
||
|
||
.order-card {
|
||
padding: 15px;
|
||
border: 1px solid #e1e8ed;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.order-card:hover {
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.order-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.system-reverse-tag {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 4px 8px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.system-note {
|
||
color: #667eea;
|
||
font-style: italic;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.status {
|
||
padding: 4px 8px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.status.pending {
|
||
background: #f39c12;
|
||
color: white;
|
||
}
|
||
|
||
.status.matching {
|
||
background: #3498db;
|
||
color: white;
|
||
}
|
||
|
||
.status.completed {
|
||
background: #27ae60;
|
||
color: white;
|
||
}
|
||
|
||
.status.cancelled {
|
||
background: #e74c3c;
|
||
color: white;
|
||
}
|
||
|
||
.status.failed {
|
||
background: #e74c3c;
|
||
color: white;
|
||
}
|
||
|
||
.order-info p {
|
||
margin: 5px 0;
|
||
font-size: 14px;
|
||
color: #34495e;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #7f8c8d;
|
||
}
|
||
|
||
/* 匹配类型选择器样式 */
|
||
.matching-type-selector {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.type-tabs {
|
||
display: flex;
|
||
background: #f8f9fa;
|
||
border-radius: 12px;
|
||
padding: 4px;
|
||
gap: 4px;
|
||
border: 1px solid #e9ecef;
|
||
}
|
||
|
||
.type-tab {
|
||
flex: 1;
|
||
padding: 12px 20px;
|
||
border: none;
|
||
background: transparent;
|
||
color: #6c757d;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.type-tab:hover {
|
||
color: #495057;
|
||
background: rgba(52, 144, 220, 0.1);
|
||
}
|
||
|
||
.type-tab.active {
|
||
background: linear-gradient(135deg, #3498db, #2980b9);
|
||
color: white;
|
||
font-weight: 600;
|
||
box-shadow: 0 2px 8px rgba(52, 144, 220, 0.3);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.type-tab.active::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: linear-gradient(135deg, rgba(255,255,255,0.2), transparent);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.type-tab:focus {
|
||
outline: none;
|
||
box-shadow: 0 0 0 3px rgba(52, 144, 220, 0.2);
|
||
}
|
||
|
||
.type-tab:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
/* 自定义金额输入框样式 */
|
||
.custom-amount-input {
|
||
flex: 1;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.custom-amount-input .el-input {
|
||
width: 100%;
|
||
}
|
||
|
||
/* 转账弹窗样式 */
|
||
.transfer-dialog-content {
|
||
max-height: 70vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.transfer-info {
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.transfer-info h4 {
|
||
margin: 0 0 10px 0;
|
||
color: #2c3e50;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.transfer-info p {
|
||
margin: 5px 0;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.payment-codes {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.payment-codes h4 {
|
||
margin: 0 0 15px 0;
|
||
color: #2c3e50;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.payment-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
|
||
.payment-item {
|
||
text-align: center;
|
||
padding: 15px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
}
|
||
|
||
.payment-item h5 {
|
||
margin: 0 0 10px 0;
|
||
color: #34495e;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.qr-code {
|
||
width: 150px;
|
||
height: 150px;
|
||
object-fit: contain;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.bank-card {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #2c3e50;
|
||
background: #f0f0f0;
|
||
padding: 10px;
|
||
border-radius: 4px;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.transfer-form {
|
||
border-top: 1px solid #e0e0e0;
|
||
padding-top: 20px;
|
||
}
|
||
|
||
.transfer-form h4 {
|
||
margin: 0 0 15px 0;
|
||
color: #2c3e50;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.upload-preview {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.upload-preview img {
|
||
max-width: 200px;
|
||
max-height: 200px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.dialog-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
}
|
||
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.modal-content {
|
||
background: white;
|
||
border-radius: 10px;
|
||
max-width: 800px;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
width: 90%;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20px;
|
||
border-bottom: 1px solid #e1e8ed;
|
||
}
|
||
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
cursor: pointer;
|
||
color: #7f8c8d;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 20px;
|
||
}
|
||
|
||
.order-summary {
|
||
background: #f8f9fa;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.order-summary p {
|
||
margin: 8px 0;
|
||
}
|
||
|
||
.allocations-section,
|
||
.records-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.allocations-section h4,
|
||
.records-section h4 {
|
||
margin-bottom: 15px;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.timeline-item {
|
||
border-left: 3px solid #3498db;
|
||
padding-left: 15px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.timeline-content {
|
||
background: #f8f9fa;
|
||
padding: 10px;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.timeline-header {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.record-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 10px;
|
||
border-bottom: 1px solid #e1e8ed;
|
||
}
|
||
|
||
.record-info {
|
||
display: flex;
|
||
gap: 15px;
|
||
align-items: center;
|
||
}
|
||
|
||
.action {
|
||
font-weight: bold;
|
||
color: #3498db;
|
||
}
|
||
|
||
.user {
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.time {
|
||
color: #7f8c8d;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.record-amount {
|
||
font-weight: bold;
|
||
color: #27ae60;
|
||
}
|
||
|
||
.record-note {
|
||
font-style: italic;
|
||
color: #7f8c8d;
|
||
font-size: 12px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.action-section {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.stats-cards {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 15px;
|
||
}
|
||
|
||
.allocation-card {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 15px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.allocation-info {
|
||
width: 100%;
|
||
}
|
||
|
||
.allocation-info h4 {
|
||
font-size: 14px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.allocation-info p {
|
||
font-size: 13px;
|
||
margin: 4px 0;
|
||
}
|
||
|
||
.deadline-info {
|
||
width: 100%;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.time-left {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.deadline-time {
|
||
font-size: 11px;
|
||
}
|
||
|
||
.allocation-actions {
|
||
width: 100%;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.allocation-actions .el-button {
|
||
flex: 1;
|
||
min-width: 80px;
|
||
font-size: 12px;
|
||
padding: 8px 12px;
|
||
}
|
||
|
||
.modal-content {
|
||
width: 95%;
|
||
margin: 10px;
|
||
max-height: 90vh;
|
||
}
|
||
|
||
.modal-header {
|
||
padding: 15px;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 15px;
|
||
}
|
||
|
||
.order-summary {
|
||
padding: 12px;
|
||
}
|
||
|
||
.qr-code {
|
||
width: 120px;
|
||
height: 120px;
|
||
}
|
||
|
||
.bank-card {
|
||
font-size: 14px;
|
||
padding: 8px;
|
||
}
|
||
|
||
.record-item {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
padding: 12px 8px;
|
||
}
|
||
|
||
.record-info {
|
||
width: 100%;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.record-amount {
|
||
align-self: flex-end;
|
||
}
|
||
}
|
||
|
||
/* 上传提示样式 */
|
||
.upload-tip {
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.tip-text {
|
||
color: #f56c6c;
|
||
font-size: 12px;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* 超时警告样式 */
|
||
.timeout-warning {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-top: 10px;
|
||
padding: 8px 12px;
|
||
background-color: #fef0f0;
|
||
border: 1px solid #fbc4c4;
|
||
border-radius: 4px;
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.timeout-warning i {
|
||
font-size: 16px;
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.warning-text {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 禁用状态的确认按钮样式 */
|
||
.confirm-btn:disabled {
|
||
background-color: #f5f7fa !important;
|
||
border-color: #e4e7ed !important;
|
||
color: #c0c4cc !important;
|
||
cursor: not-allowed !important;
|
||
}
|
||
|
||
.confirm-btn:disabled:hover {
|
||
background-color: #f5f7fa !important;
|
||
border-color: #e4e7ed !important;
|
||
color: #c0c4cc !important;
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.stats-cards {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.allocation-card {
|
||
padding: 12px;
|
||
}
|
||
|
||
.allocation-info h4 {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.allocation-info p {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.time-left {
|
||
font-size: 11px;
|
||
}
|
||
|
||
.deadline-time {
|
||
font-size: 10px;
|
||
}
|
||
|
||
.allocation-actions .el-button {
|
||
font-size: 11px;
|
||
padding: 6px 10px;
|
||
}
|
||
|
||
.qr-code {
|
||
width: 100px;
|
||
height: 100px;
|
||
}
|
||
|
||
.bank-card {
|
||
font-size: 12px;
|
||
padding: 6px;
|
||
}
|
||
}
|
||
</style> |