| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- <template>
- <div class="pending-refund admin-page">
- <div class="header admin-page-header">
- <h2 class="admin-page-title">待退款列表</h2>
- <div class="flex items-center gap-sm">
- <el-select v-model="statusFilter" placeholder="退款状态" @change="loadList">
- <el-option label="全部状态" value="" />
- <el-option label="待处理" value="pending" />
- <el-option label="处理中" value="processing" />
- <el-option label="已成功" value="success" />
- <el-option label="已失败" value="failed" />
- </el-select>
- <el-select v-model="typeFilter" placeholder="订单类型" @change="loadList">
- <el-option label="全部类型" value="" />
- <el-option label="提现退款" value="withdrawal" />
- <el-option label="商品退款" value="product" />
- </el-select>
- </div>
- </div>
- <div v-if="summary.totalPendingAmount > 0" class="summary-bar">
- <el-alert
- :title="'待退款总金额:¥' + (summary.totalPendingAmount / 100).toFixed(2) + '(' + summary.pendingCount + ' 笔待处理)'"
- type="warning"
- :closable="false"
- show-icon
- />
- </div>
- <div class="adm-table-wrap">
- <el-table :data="list" v-loading="loading" border stripe height="100%">
- <el-table-column prop="id" label="ID" width="70">
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="类型" width="100">
- <template slot-scope="{ row }">
- <el-tag :type="row.orderType === 'product' ? 'primary' : 'warning'" size="small">
- {{ row.orderType === 'product' ? '商品退款' : '提现退款' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="订单号" width="180">
- <template slot-scope="{ row }">
- {{ row.orderNo || '-' }}
- </template>
- </el-table-column>
- <el-table-column label="提现申请ID" width="100">
- <template slot-scope="{ row }">
- {{ row.withdrawalRequestId || '-' }}
- </template>
- </el-table-column>
- <el-table-column label="用户" width="120">
- <template slot-scope="{ row }">
- {{ row.userName || '用户' + row.userId }}
- </template>
- </el-table-column>
- <el-table-column label="退款金额" width="120">
- <template slot-scope="{ row }">
- <span class="amount">{{ formatPriceWithSymbol(row.amount) }}</span>
- </template>
- </el-table-column>
- <el-table-column label="实退金额" width="120">
- <template slot-scope="{ row }">
- <span class="amount">{{ row.refundedAmount ? formatPriceWithSymbol(row.refundedAmount) : '-' }}</span>
- </template>
- </el-table-column>
- <el-table-column label="状态" width="100">
- <template slot-scope="{ row }">
- <el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="尝试次数" width="80">
- <template slot-scope="{ row }">
- {{ row.attemptCount }}/{{ row.maxAttempts }}
- </template>
- </el-table-column>
- <el-table-column label="失败原因" min-width="200" show-overflow-tooltip>
- <template slot-scope="{ row }">
- {{ row.failureReason || '-' }}
- </template>
- </el-table-column>
- <el-table-column label="创建时间" width="160">
- <template slot-scope="{ row }">
- {{ formatTime(row.createdAt) }}
- </template>
- <template slot="header">
- <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">创建时间{{ sortIcon('createdAt') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="退款时间" width="160">
- <template slot-scope="{ row }">
- {{ row.refundedAt ? formatTime(row.refundedAt) : '-' }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="120" fixed="right">
- <template slot-scope="{ row }">
- <el-button v-if="row.status === 'pending' || row.status === 'processing'" type="danger" size="mini" @click="handleCancel(row)">
- 中止退款
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <div class="pagination-wrap">
- <el-pagination
- background
- layout="total, prev, pager, next"
- :total="total"
- :page-size="pageSize"
- :current-page.sync="currentPage"
- @current-change="handlePageChange"
- />
- </div>
- </div>
- </template>
- <script>
- import { getPendingRefundList, getPendingRefundSummary, cancelPendingRefund, searchUsers } from '@/api/admin'
- import SortMixin from '@/mixins/SortMixin'
- export default {
- name: 'PendingRefund',
- mixins: [SortMixin],
- computed: {
- tableHeight() {
- return window.innerHeight - 300
- }
- },
- data() {
- return {
- list: [],
- sortableColumns: { id: 'ID', createdAt: '创建时间' },
- loading: false,
- statusFilter: '',
- typeFilter: '',
- currentPage: 1,
- pageSize: 20,
- total: 0,
- summary: {
- totalPendingAmount: 0,
- pendingCount: 0
- }
- }
- },
- created() {
- this.loadSummary()
- this.loadList()
- },
- methods: {
- async loadSummary() {
- try {
- const res = await getPendingRefundSummary()
- if (res.code === 200) {
- this.summary = res.data || { totalPendingAmount: 0 }
- }
- } catch (e) {
- console.error('加载待退款摘要失败', e)
- }
- },
- async loadList() {
- this.loading = true
- try {
- const reqParams = {
- status: this.statusFilter || undefined,
- orderType: this.typeFilter || undefined,
- page: this.currentPage,
- size: this.pageSize
- }
- if (this.sortState.length > 0) reqParams.sort = this.sortState
- const res = await getPendingRefundList(reqParams)
- if (res.code === 200) {
- this.list = res.data.records || []
- this.total = res.data.total || 0
- await this.resolveUserNames(this.list, 'userId')
- }
- } catch (e) {
- console.error('加载待退款列表失败', e)
- } finally {
- this.loading = false
- }
- },
- resolveUserNames(items, idField) {
- if (!items || items.length === 0) return Promise.resolve()
- var ids = []
- items.forEach(function(item) {
- if (item[idField] && ids.indexOf(item[idField]) === -1) ids.push(item[idField])
- })
- var nameMap = {}
- return Promise.all(ids.map(function(id) {
- return searchUsers({ keyword: String(id) })
- .then(function(res) {
- if (res.data && res.data.length > 0) {
- var u = res.data[0]
- nameMap[id] = u.nickname || u.realName || '用户' + id
- } else {
- nameMap[id] = '用户' + id
- }
- })
- .catch(function() {
- nameMap[id] = '用户' + id
- })
- })).then(function() {
- items.forEach(function(item) {
- item.userName = nameMap[item[idField]] || '用户' + item[idField]
- })
- })
- },
- handlePageChange(page) {
- this.currentPage = page
- this.loadList()
- },
- statusType(status) {
- const map = { pending: 'warning', processing: 'primary', success: 'success', failed: 'danger', cancelled: 'info' }
- return map[status] || 'info'
- },
- statusLabel(status) {
- const map = { pending: '待处理', processing: '处理中', success: '已成功', failed: '已失败', cancelled: '已中止' }
- return map[status] || status
- },
- async handleCancel(row) {
- try {
- await this.$confirm('确认中止该退款?订单将回到退款审核状态。', '确认中止', { type: 'warning' })
- const res = await cancelPendingRefund({ refundId: row.id })
- if (res.code === 200) {
- this.$message.success('已中止退款')
- this.loadList()
- this.loadSummary()
- } else {
- this.$message.error(res.message || '操作失败')
- }
- } catch (e) {
- if (e !== 'cancel') {
- console.error('中止退款失败', e)
- }
- }
- },
- formatTime(time) {
- if (!time) return '-'
- const d = new Date(time)
- const pad = n => n < 10 ? '0' + n : n
- return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) +
- ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds())
- },
- formatPriceWithSymbol(amount) {
- if (amount == null) return '-'
- return '¥' + (amount / 100).toFixed(2)
- }
- }
- }
- </script>
- <style scoped>
- .summary-bar {
- margin-bottom: 16px;
- }
- .sort-header {
- cursor: pointer;
- user-select: none;
- font-weight: 500;
- font-size: 12px;
- }
- .sort-header:hover {
- color: #409eff;
- }
- </style>
|