| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793 |
- <template>
- <div class="me-withdraw-list">
- <div class="withdraw-header">
- <div class="header-text">
- <h2 class="withdraw-title">提现记录</h2>
- <p class="withdraw-subtitle">查看您的提现申请记录和处理状态</p>
- </div>
- <el-button type="primary" :icon="Plus" @click="handleApplyWithdraw">申请提现</el-button>
- </div>
- <div class="stats-section">
- <div class="stat-card stat-total">
- <div class="stat-icon">
- <el-icon>
- <Money />
- </el-icon>
- </div>
- <div class="stat-content">
- <div class="stat-label">累计提现金额</div>
- <div class="stat-value">¥{{ formatMoney(stats.total_amount) }}</div>
- <div class="stat-desc">所有已完成提现的总金额</div>
- </div>
- </div>
- <div class="stat-card stat-pending">
- <div class="stat-icon">
- <el-icon>
- <Clock />
- </el-icon>
- </div>
- <div class="stat-content">
- <div class="stat-label">待审核金额</div>
- <div class="stat-value">¥{{ formatMoney(stats.pending_amount) }}</div>
- <div class="stat-desc">正在审核中的提现金额</div>
- </div>
- </div>
- <div class="stat-card stat-success">
- <div class="stat-icon">
- <el-icon>
- <CircleCheck />
- </el-icon>
- </div>
- <div class="stat-content">
- <div class="stat-label">成功提现次数</div>
- <div class="stat-value">{{ stats.success_count }}</div>
- <div class="stat-desc">已成功完成的提现次数</div>
- </div>
- </div>
- </div>
- <div class="withdraw-filters">
- <el-input v-model="searchForm.withdrawNo" placeholder="请输入提现单号" clearable
- class="filter-item withdraw-input">
- <template #prepend>提现单号</template>
- </el-input>
- <el-date-picker v-model="searchForm.dateRange" class="filter-item date-picker" type="daterange"
- unlink-panels start-placeholder="申请开始时间" end-placeholder="申请结束时间" range-separator="~"
- value-format="YYYY-MM-DD" clearable />
- <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
- <el-button @click="handleReset">重置</el-button>
- </div>
- <div class="withdraw-tabs">
- <el-tabs v-model="activeStatus" @tab-click="handleTabClick">
- <el-tab-pane v-for="tab in statusTabs" :key="tab.value"
- :label="`${tab.label} (${getWithdrawCount(tab.value)})`" :name="tab.value" />
- </el-tabs>
- </div>
- <div v-loading="loading" class="withdraw-list-wrapper">
- <template v-if="filteredWithdraws.length">
- <div class="withdraw-list">
- <div v-for="item in filteredWithdraws" :key="item.id" class="withdraw-card">
- <div class="withdraw-card__header">
- <div class="header-left">
- <span class="withdraw-no">提现单号:{{ item.withdraw_no }}</span>
- <el-tag size="small" :type="getStatusType(item.status)" class="status-tag">
- {{ item.status_text }}
- </el-tag>
- </div>
- <div class="header-right">
- <span class="withdraw-time">申请时间:{{ item.create_time }}</span>
- </div>
- </div>
- <div class="withdraw-card__body">
- <div class="withdraw-item">
- <div class="item-label">提现金额</div>
- <div class="item-value amount">¥{{ formatMoney(item.amount) }}</div>
- </div>
- <div class="withdraw-item">
- <div class="item-label">手续费</div>
- <div class="item-value fee">¥{{ formatMoney(item.fee) }}</div>
- </div>
- <div class="withdraw-item">
- <div class="item-label">实际到账</div>
- <div class="item-value actual">¥{{ formatMoney(item.actual_amount) }}</div>
- </div>
- <div class="withdraw-item">
- <div class="item-label">提现方式</div>
- <div class="item-value">{{ item.withdraw_type_text }}</div>
- </div>
- <div class="withdraw-item">
- <div class="item-label">收款账户</div>
- <div class="item-value account">{{ item.account }}</div>
- </div>
- <div class="withdraw-item">
- <div class="item-label">处理时间</div>
- <div class="item-value">{{ item.process_time || '-' }}</div>
- </div>
- <div v-if="item.remark" class="withdraw-item full-width">
- <div class="item-label">备注说明</div>
- <div class="item-value note">{{ item.remark }}</div>
- </div>
- <div v-if="item.reject_reason" class="withdraw-item full-width">
- <div class="item-label">拒绝原因</div>
- <div class="item-value reject-reason">{{ item.reject_reason }}</div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <template v-else>
- <el-empty class="withdraw-empty" description="暂无提现记录" />
- </template>
- </div>
- <el-dialog v-model="applyDialogVisible" title="申请提现" width="500px" :close-on-click-modal="false">
- <el-form ref="applyFormRef" :model="applyForm" :rules="applyRules" label-width="100px">
- <el-form-item label="可提现金额">
- <div class="available-amount">¥{{ formatMoney(availableAmount) }}</div>
- </el-form-item>
- <el-form-item label="提现金额" prop="amount">
- <el-input v-model="applyForm.amount" placeholder="请输入提现金额" type="number">
- <template #append>元</template>
- </el-input>
- <div class="form-tip">最低提现金额:¥100,手续费率:2%</div>
- </el-form-item>
- <el-form-item label="提现方式" prop="withdraw_type">
- <el-select v-model="applyForm.withdraw_type" placeholder="请选择提现方式">
- <el-option label="支付宝" value="alipay" />
- <el-option label="微信" value="wechat" />
- <el-option label="银行卡" value="bank" />
- </el-select>
- </el-form-item>
- <el-form-item label="收款账户" prop="account">
- <el-input v-model="applyForm.account" placeholder="请输入收款账户" />
- </el-form-item>
- <el-form-item label="账户姓名" prop="account_name">
- <el-input v-model="applyForm.account_name" placeholder="请输入账户姓名" />
- </el-form-item>
- <el-form-item label="备注" prop="remark">
- <el-input v-model="applyForm.remark" type="textarea" :rows="3" placeholder="请输入备注信息(选填)" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="applyDialogVisible = false">取消</el-button>
- <el-button type="primary" :loading="submitting" @click="handleSubmitApply">
- 提交申请
- </el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, onMounted, reactive, ref } from 'vue'
- import { Search, Plus, Money, Clock, CircleCheck } from '@element-plus/icons-vue'
- import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
- defineOptions({ name: 'MeWithdrawList' })
- type WithdrawStatus = 'pending' | 'approved' | 'rejected' | 'processing' | 'completed'
- type WithdrawTabStatus = 'all' | WithdrawStatus
- type WithdrawRecord = {
- id: number
- withdraw_no: string
- amount: number
- fee: number
- actual_amount: number
- status: WithdrawStatus
- status_text: string
- withdraw_type: string
- withdraw_type_text: string
- account: string
- account_name: string
- create_time: string
- process_time: string
- remark: string
- reject_reason: string
- }
- type WithdrawStats = {
- total_amount: number
- pending_amount: number
- success_count: number
- }
- const activeStatus = ref<WithdrawTabStatus>('all')
- const loading = ref(false)
- const submitting = ref(false)
- const applyDialogVisible = ref(false)
- const applyFormRef = ref<FormInstance>()
- const searchForm = reactive<{ withdrawNo: string; dateRange: string[] }>({
- withdrawNo: '',
- dateRange: []
- })
- const appliedFilters = reactive<{ withdrawNo: string; dateRange: string[] }>({
- withdrawNo: '',
- dateRange: []
- })
- const applyForm = reactive({
- amount: '',
- withdraw_type: '',
- account: '',
- account_name: '',
- remark: ''
- })
- const availableAmount = ref(0)
- const applyRules: FormRules = {
- amount: [
- { required: true, message: '请输入提现金额', trigger: 'blur' },
- {
- validator: (rule, value, callback) => {
- if (value < 100) {
- callback(new Error('最低提现金额为100元'))
- } else if (value > availableAmount.value) {
- callback(new Error('提现金额不能超过可提现金额'))
- } else {
- callback()
- }
- },
- trigger: 'blur'
- }
- ],
- withdraw_type: [{ required: true, message: '请选择提现方式', trigger: 'change' }],
- account: [{ required: true, message: '请输入收款账户', trigger: 'blur' }],
- account_name: [{ required: true, message: '请输入账户姓名', trigger: 'blur' }]
- }
- const statusTabs: { label: string; value: WithdrawTabStatus }[] = [
- { label: '全部', value: 'all' },
- { label: '待审核', value: 'pending' },
- { label: '已通过', value: 'approved' },
- { label: '处理中', value: 'processing' },
- { label: '已完成', value: 'completed' },
- { label: '已拒绝', value: 'rejected' }
- ]
- const withdraws = ref<WithdrawRecord[]>([])
- const stats = ref<WithdrawStats>({
- total_amount: 0,
- pending_amount: 0,
- success_count: 0
- })
- const loadWithdrawRecords = async () => {
- try {
- loading.value = true
- await new Promise(resolve => setTimeout(resolve, 500))
- withdraws.value = [
- {
- id: 1,
- withdraw_no: 'WD202501200001',
- amount: 1000,
- fee: 20,
- actual_amount: 980,
- status: 'completed',
- status_text: '已完成',
- withdraw_type: 'alipay',
- withdraw_type_text: '支付宝',
- account: '138****8888',
- account_name: '张三',
- create_time: '2025-01-15 10:30:00',
- process_time: '2025-01-15 14:20:00',
- remark: '正常提现',
- reject_reason: ''
- },
- {
- id: 2,
- withdraw_no: 'WD202501200002',
- amount: 500,
- fee: 10,
- actual_amount: 490,
- status: 'pending',
- status_text: '待审核',
- withdraw_type: 'wechat',
- withdraw_type_text: '微信',
- account: 'wx_test123',
- account_name: '李四',
- create_time: '2025-01-18 09:15:00',
- process_time: '',
- remark: '',
- reject_reason: ''
- },
- {
- id: 3,
- withdraw_no: 'WD202501200003',
- amount: 2000,
- fee: 40,
- actual_amount: 1960,
- status: 'rejected',
- status_text: '已拒绝',
- withdraw_type: 'bank',
- withdraw_type_text: '银行卡',
- account: '6222****1234',
- account_name: '王五',
- create_time: '2025-01-10 16:45:00',
- process_time: '2025-01-11 10:00:00',
- remark: '',
- reject_reason: '账户信息不符,请核对后重新申请'
- }
- ]
- stats.value = {
- total_amount: 15680,
- pending_amount: 500,
- success_count: 12
- }
- availableAmount.value = 3580.50
- } catch (error) {
- console.error('加载提现记录失败:', error)
- ElMessage.error('加载提现记录失败,请稍后重试')
- } finally {
- loading.value = false
- }
- }
- const baseFilteredWithdraws = computed(() => {
- let filtered = withdraws.value
- if (appliedFilters.withdrawNo) {
- const withdrawNo = appliedFilters.withdrawNo.trim()
- filtered = filtered.filter(w => w.withdraw_no.includes(withdrawNo))
- }
- if (appliedFilters.dateRange && appliedFilters.dateRange.length === 2) {
- filtered = filtered.filter(w => {
- return isWithinRange(w.create_time, appliedFilters.dateRange)
- })
- }
- return filtered
- })
- const filteredWithdraws = computed(() => {
- let filtered = baseFilteredWithdraws.value
- if (activeStatus.value !== 'all') {
- filtered = filtered.filter(w => w.status === activeStatus.value)
- }
- return filtered
- })
- const filteredStats = computed(() => {
- const base = baseFilteredWithdraws.value
- return {
- all: base.length,
- pending: base.filter(w => w.status === 'pending').length,
- approved: base.filter(w => w.status === 'approved').length,
- processing: base.filter(w => w.status === 'processing').length,
- completed: base.filter(w => w.status === 'completed').length,
- rejected: base.filter(w => w.status === 'rejected').length
- }
- })
- const formatMoney = (amount: number | undefined) => {
- if (amount === undefined || amount === null) return '0.00'
- return Number(amount).toFixed(2)
- }
- const getStatusType = (status: WithdrawStatus) => {
- const typeMap: Record<WithdrawStatus, 'success' | 'warning' | 'danger' | 'info' | 'primary'> = {
- pending: 'warning',
- approved: 'success',
- processing: 'primary',
- completed: 'success',
- rejected: 'danger'
- }
- return typeMap[status] || 'info'
- }
- const handleSearch = () => {
- appliedFilters.withdrawNo = searchForm.withdrawNo || ''
- if (searchForm.dateRange && Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) {
- appliedFilters.dateRange = [...searchForm.dateRange]
- } else {
- appliedFilters.dateRange = []
- }
- }
- const handleReset = () => {
- searchForm.withdrawNo = ''
- searchForm.dateRange = []
- appliedFilters.withdrawNo = ''
- appliedFilters.dateRange = []
- }
- const handleTabClick = (tab: { paneName?: string | number; name?: string | number }) => {
- activeStatus.value = (tab.paneName || tab.name || 'all') as WithdrawTabStatus
- }
- const getWithdrawCount = (status: WithdrawTabStatus) => {
- if (status === 'all') return filteredStats.value.all || 0
- return filteredStats.value[status] || 0
- }
- const isWithinRange = (time: string, range: string[]) => {
- if (!range || range.length !== 2 || !range[0] || !range[1]) return true
- const [start, end] = range
- const startDate = new Date(start + ' 00:00:00')
- const endDate = new Date(end + ' 23:59:59')
- const targetDate = new Date(time.replace(/-/g, '/'))
- if (isNaN(startDate.getTime()) || isNaN(endDate.getTime()) || isNaN(targetDate.getTime())) {
- return true
- }
- return targetDate >= startDate && targetDate <= endDate
- }
- const handleApplyWithdraw = () => {
- applyDialogVisible.value = true
- applyForm.amount = ''
- applyForm.withdraw_type = ''
- applyForm.account = ''
- applyForm.account_name = ''
- applyForm.remark = ''
- }
- const handleSubmitApply = async () => {
- if (!applyFormRef.value) return
- await applyFormRef.value.validate((valid) => {
- if (valid) {
- submitting.value = true
- setTimeout(() => {
- submitting.value = false
- applyDialogVisible.value = false
- ElMessage.success('提现申请已提交,请等待审核')
- loadWithdrawRecords()
- }, 1000)
- }
- })
- }
- onMounted(() => {
- loadWithdrawRecords()
- })
- </script>
- <style scoped lang="scss">
- .me-withdraw-list {
- width: 100%;
- display: flex;
- flex-direction: column;
- gap: 20px;
- }
- .withdraw-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-wrap: wrap;
- gap: 12px;
- .header-text {
- .withdraw-title {
- margin: 0;
- font-size: 24px;
- font-weight: 600;
- color: #1f2f3d;
- }
- .withdraw-subtitle {
- margin: 6px 0 0;
- font-size: 14px;
- color: #909399;
- }
- }
- }
- .stats-section {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 20px;
- @media (max-width: 1024px) {
- grid-template-columns: repeat(2, 1fr);
- }
- @media (max-width: 768px) {
- grid-template-columns: 1fr;
- }
- .stat-card {
- display: flex;
- align-items: flex-start;
- padding: 20px;
- background: #ffffff;
- border-radius: 12px;
- box-shadow: 0 4px 16px rgba(64, 158, 255, 0.12);
- transition: all 0.3s ease;
- border: 1px solid #d9ecff;
- &:hover {
- box-shadow: 0 6px 20px rgba(64, 158, 255, 0.18);
- transform: translateY(-3px);
- }
- .stat-icon {
- width: 56px;
- height: 56px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 12px;
- margin-right: 16px;
- flex-shrink: 0;
- font-size: 28px;
- color: #fff;
- }
- &.stat-total .stat-icon {
- background: linear-gradient(135deg, #409EFF 0%, #66b1ff 100%);
- }
- &.stat-pending .stat-icon {
- background: linear-gradient(135deg, #E6A23C 0%, #ebb563 100%);
- }
- &.stat-success .stat-icon {
- background: linear-gradient(135deg, #67C23A 0%, #85ce61 100%);
- }
- .stat-content {
- flex: 1;
- .stat-label {
- font-size: 14px;
- color: #909399;
- margin-bottom: 8px;
- font-weight: 500;
- }
- .stat-value {
- font-size: 26px;
- font-weight: 700;
- color: #303133;
- margin-bottom: 6px;
- }
- .stat-desc {
- font-size: 12px;
- color: #C0C4CC;
- line-height: 1.4;
- }
- }
- }
- }
- .withdraw-filters {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 12px;
- padding: 16px;
- background: linear-gradient(135deg, #ecf5ff 0%, #f5f9ff 100%);
- border-radius: 8px;
- border: 1px solid #c6e2ff;
- .filter-item {
- flex: 1;
- min-width: 200px;
- }
- .withdraw-input {
- max-width: 320px;
- }
- .date-picker {
- max-width: 360px;
- }
- }
- .withdraw-tabs {
- background: #fff;
- border-radius: 8px;
- padding: 0 16px;
- box-shadow: 0 4px 16px rgba(64, 158, 255, 0.12);
- ::v-deep .el-tabs__item {
- font-weight: 500;
- }
- ::v-deep .el-tabs__active-bar {
- height: 3px;
- background: linear-gradient(90deg, #409EFF, #66b1ff);
- }
- ::v-deep .el-tabs__item.is-active {
- color: #409EFF;
- }
- }
- .withdraw-list-wrapper {
- min-height: 200px;
- }
- .withdraw-list {
- display: flex;
- flex-direction: column;
- gap: 16px;
- }
- .withdraw-card {
- background: #fff;
- border-radius: 12px;
- border: 1px solid #d9ecff;
- box-shadow: 0 6px 18px rgba(64, 158, 255, 0.12);
- overflow: hidden;
- transition: all 0.3s ease;
- &:hover {
- box-shadow: 0 8px 24px rgba(64, 158, 255, 0.18);
- transform: translateY(-2px);
- }
- &__header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-wrap: wrap;
- gap: 10px;
- padding: 16px 20px;
- background: linear-gradient(135deg, #ecf5ff, #ffffff);
- }
- .header-left {
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .withdraw-no {
- font-weight: 600;
- color: #1f2d3d;
- font-size: 15px;
- }
- .status-tag {
- font-weight: 500;
- }
- .header-right {
- color: #606266;
- font-size: 13px;
- }
- .withdraw-time {
- white-space: nowrap;
- }
- .withdraw-card__body {
- padding: 20px;
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(180px, max-content));
- gap: 16px 24px;
- justify-content: flex-start;
- }
- }
- .withdraw-item {
- display: inline-flex;
- flex-direction: column;
- gap: 6px;
- font-size: 14px;
- width: max-content;
- min-width: 0;
- .item-label {
- color: #909399;
- }
- .item-value {
- color: #303133;
- font-weight: 500;
- &.amount {
- font-size: 20px;
- color: #409EFF;
- font-weight: 700;
- }
- &.fee {
- color: #E6A23C;
- font-weight: 600;
- }
- &.actual {
- font-size: 18px;
- color: #67C23A;
- font-weight: 700;
- }
- &.account {
- font-family: 'Courier New', Courier, monospace;
- }
- &.note {
- padding: 10px;
- background: #f0f5ff;
- border-radius: 6px;
- color: #606266;
- line-height: 1.6;
- }
- &.reject-reason {
- padding: 10px;
- background: #fef0f0;
- border-radius: 6px;
- color: #F56C6C;
- line-height: 1.6;
- }
- }
- &.full-width {
- grid-column: 1 / -1;
- width: 100%;
- }
- }
- .withdraw-empty {
- padding: 60px 0;
- background: #fff;
- border-radius: 12px;
- border: 1px dashed #c6e2ff;
- }
- .available-amount {
- font-size: 24px;
- font-weight: 700;
- color: #409EFF;
- }
- .form-tip {
- font-size: 12px;
- color: #909399;
- margin-top: 4px;
- }
- @media (max-width: 768px) {
- .withdraw-header {
- flex-direction: column;
- align-items: flex-start;
- }
- .withdraw-filters {
- flex-direction: column;
- .filter-item,
- .withdraw-input,
- .date-picker {
- width: 100%;
- max-width: none;
- }
- }
- .withdraw-card__body {
- grid-template-columns: 1fr;
- }
- }
- </style>
|