0.使用模拟数据进行充值和提现
1.优化提现接口/api/finance/withdrawCreate 2.优化充值接口/api/finance/depositCreate
This commit is contained in:
204
web/public/mock-deposit.html
Normal file
204
web/public/mock-deposit.html
Normal file
@@ -0,0 +1,204 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>模拟充值收银台</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; background: #f5f7fa; margin: 0; padding: 24px; }
|
||||
.card { max-width: 420px; margin: 40px auto; background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 4px 24px rgba(0,0,0,.08); }
|
||||
h1 { font-size: 20px; margin: 0 0 8px; }
|
||||
p { color: #666; line-height: 1.6; margin: 8px 0; }
|
||||
.amt { font-size: 28px; color: #1677ff; font-weight: 700; margin: 16px 0; }
|
||||
.countdown { font-size: 15px; color: #fa8c16; font-weight: 600; margin: 12px 0; }
|
||||
.countdown.expired { color: #cf1322; }
|
||||
button { width: 100%; padding: 14px; font-size: 16px; border: 0; border-radius: 8px; background: #1677ff; color: #fff; cursor: pointer; margin-top: 12px; }
|
||||
button:disabled { background: #ccc; cursor: not-allowed; }
|
||||
.hint { font-size: 13px; color: #999; }
|
||||
.ok { display: none; margin-top: 16px; padding: 12px; background: #f6ffed; border: 1px solid #b7eb8f; border-radius: 8px; color: #389e0d; line-height: 1.6; }
|
||||
.info { margin-top: 12px; padding: 12px; background: #e6f4ff; border: 1px solid #91caff; border-radius: 8px; color: #0958d9; font-size: 14px; line-height: 1.6; }
|
||||
.err { margin-top: 12px; padding: 12px; background: #fff2f0; border: 1px solid #ffccc7; border-radius: 8px; color: #cf1322; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>模拟充值收银台</h1>
|
||||
<p class="hint">订单号:<span id="orderNo">-</span></p>
|
||||
<div id="amountBlock" style="display:none;">
|
||||
<p>充值金额 <strong id="amount">-</strong>,赠送 <strong id="bonus">-</strong></p>
|
||||
<div class="amt">预计到账 <span id="total">-</span></div>
|
||||
</div>
|
||||
<p class="countdown" id="countdown">正在加载…</p>
|
||||
<p class="hint" id="expireLine"></p>
|
||||
<button type="button" id="btnPay">确认支付</button>
|
||||
<div class="ok" id="okBox"><strong>支付成功(模拟)</strong><br />订单已提交,需管理员在后台审核通过后才会入账。</div>
|
||||
<div id="msg"></div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var orderNo = params.get('order_no') || '';
|
||||
var apiBase = (params.get('api_base') || '').replace(/\/$/, '');
|
||||
var expireAt = parseInt(params.get('expire_at') || '0', 10);
|
||||
var sign = params.get('sign') || '';
|
||||
if (!apiBase) {
|
||||
apiBase = window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
|
||||
}
|
||||
document.getElementById('orderNo').textContent = orderNo || '(缺少 order_no)';
|
||||
|
||||
var countdownEl = document.getElementById('countdown');
|
||||
var expireLineEl = document.getElementById('expireLine');
|
||||
var btnPay = document.getElementById('btnPay');
|
||||
var countdownTimer = null;
|
||||
|
||||
function showErr(text) {
|
||||
document.getElementById('msg').innerHTML = '<div class="err">' + text + '</div>';
|
||||
}
|
||||
function showInfo(text) {
|
||||
document.getElementById('msg').innerHTML = '<div class="info">' + text + '</div>';
|
||||
}
|
||||
function pad(n) { return n < 10 ? '0' + n : String(n); }
|
||||
function formatTs(ts) {
|
||||
var d = new Date(ts * 1000);
|
||||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' '
|
||||
+ pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
||||
}
|
||||
function formatRemain(sec) {
|
||||
if (sec <= 0) return '00:00';
|
||||
var m = Math.floor(sec / 60);
|
||||
var s = sec % 60;
|
||||
return pad(m) + ':' + pad(s);
|
||||
}
|
||||
function updateCountdown(remaining) {
|
||||
if (remaining <= 0) {
|
||||
countdownEl.textContent = '支付链接已过期,请返回应用重新发起充值';
|
||||
countdownEl.className = 'countdown expired';
|
||||
btnPay.disabled = true;
|
||||
return;
|
||||
}
|
||||
countdownEl.textContent = '剩余支付时间 ' + formatRemain(remaining);
|
||||
countdownEl.className = 'countdown';
|
||||
if (expireAt > 0) {
|
||||
expireLineEl.textContent = '链接有效期至 ' + formatTs(expireAt);
|
||||
}
|
||||
}
|
||||
function startCountdown(remaining) {
|
||||
if (countdownTimer) clearInterval(countdownTimer);
|
||||
var left = remaining;
|
||||
updateCountdown(left);
|
||||
countdownTimer = setInterval(function () {
|
||||
left -= 1;
|
||||
updateCountdown(left);
|
||||
if (left <= 0) clearInterval(countdownTimer);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function applyStatus(data) {
|
||||
var amount = data.amount;
|
||||
var bonus = data.bonus_amount;
|
||||
var total = data.total_amount;
|
||||
if (amount !== undefined) {
|
||||
document.getElementById('amount').textContent = Number(amount).toFixed(2);
|
||||
document.getElementById('bonus').textContent = Number(bonus || 0).toFixed(2);
|
||||
document.getElementById('total').textContent = Number(total || 0).toFixed(2);
|
||||
document.getElementById('amountBlock').style.display = 'block';
|
||||
}
|
||||
if (data.expire_at) expireAt = data.expire_at;
|
||||
var remaining = data.remaining_seconds !== undefined ? data.remaining_seconds : Math.max(0, expireAt - Math.floor(Date.now() / 1000));
|
||||
startCountdown(remaining);
|
||||
|
||||
if (data.status === 'paid') {
|
||||
showInfo('该订单已入账。');
|
||||
btnPay.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
if (data.status === 'pending_review') {
|
||||
document.getElementById('okBox').style.display = 'block';
|
||||
btnPay.style.display = 'none';
|
||||
showInfo('您已提交支付,请等待管理员审核通过后到账。');
|
||||
return;
|
||||
}
|
||||
if (data.status === 'failed') {
|
||||
showErr(data.reject_reason || '订单已失效或超时,请重新发起充值。');
|
||||
btnPay.disabled = true;
|
||||
return;
|
||||
}
|
||||
if (!data.can_pay) {
|
||||
btnPay.disabled = true;
|
||||
if (remaining <= 0) {
|
||||
showErr('支付链接已过期,请返回应用重新发起充值。');
|
||||
}
|
||||
} else {
|
||||
btnPay.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadStatus() {
|
||||
if (!orderNo || !sign || expireAt <= 0) {
|
||||
showErr('链接无效或缺少签名参数,请通过 App 内「去支付」打开完整链接。');
|
||||
btnPay.disabled = true;
|
||||
countdownEl.textContent = '';
|
||||
return;
|
||||
}
|
||||
var statusUrl = apiBase + '/api/finance/mockDepositStatus?' + new URLSearchParams({
|
||||
order_no: orderNo,
|
||||
expire_at: String(expireAt),
|
||||
sign: sign
|
||||
}).toString();
|
||||
fetch(statusUrl, { method: 'GET', credentials: 'omit' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
if (res && res.code === 1 && res.data) {
|
||||
applyStatus(res.data);
|
||||
} else {
|
||||
showErr((res && res.message) ? res.message : '无法加载订单信息');
|
||||
btnPay.disabled = true;
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
showErr('无法连接支付服务,请检查网络或 api_base 配置。');
|
||||
btnPay.disabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
btnPay.onclick = function () {
|
||||
if (!orderNo || !sign || expireAt <= 0) return;
|
||||
btnPay.disabled = true;
|
||||
btnPay.textContent = '处理中...';
|
||||
var confirmUrl = apiBase + '/api/finance/mockDepositConfirm';
|
||||
fetch(confirmUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
order_no: orderNo,
|
||||
expire_at: String(expireAt),
|
||||
sign: sign
|
||||
}).toString()
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
if (res && res.code === 1) {
|
||||
document.getElementById('okBox').style.display = 'block';
|
||||
btnPay.style.display = 'none';
|
||||
document.getElementById('msg').innerHTML = '';
|
||||
if (countdownTimer) clearInterval(countdownTimer);
|
||||
countdownEl.textContent = '已提交,等待后台审核';
|
||||
countdownEl.className = 'countdown';
|
||||
} else {
|
||||
showErr((res && res.message) ? res.message : '提交失败');
|
||||
btnPay.disabled = false;
|
||||
btnPay.textContent = '确认支付';
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
showErr('网络错误,请稍后重试。');
|
||||
btnPay.disabled = false;
|
||||
btnPay.textContent = '确认支付';
|
||||
});
|
||||
};
|
||||
|
||||
loadStatus();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
'status 0': 'Pending',
|
||||
'status 1': 'Success',
|
||||
'status 2': 'Failed',
|
||||
'status 3': 'Canceled',
|
||||
'status 3': 'Pending review',
|
||||
pay_channel: 'Pay channel',
|
||||
pay_time: 'Pay time',
|
||||
deposit_tier_id: 'Deposit tier',
|
||||
@@ -23,4 +23,18 @@ export default {
|
||||
channel_name: 'Channel',
|
||||
detail_title: 'Deposit Order Detail',
|
||||
close_btn: 'Close',
|
||||
reject_reason: 'Reject reason',
|
||||
review_admin_username: 'Reviewer',
|
||||
review_time: 'Review time',
|
||||
review_title: 'Deposit review',
|
||||
review_reject_title: 'Reject deposit',
|
||||
review_btn: 'Review',
|
||||
review_btn_approve: 'Approve',
|
||||
review_btn_reject: 'Reject',
|
||||
review_btn_back: 'Back',
|
||||
review_btn_confirm_reject: 'Confirm reject',
|
||||
review_reject_tip: 'After rejection, the order is marked failed and no funds will be credited.',
|
||||
review_reject_placeholder: 'Enter reject reason',
|
||||
reject_reason_required: 'Please enter reject reason',
|
||||
already_reviewed: 'This order has already been reviewed',
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export default {
|
||||
channel_name: 'Channel',
|
||||
review_admin_username: 'Reviewer',
|
||||
review_title: 'Withdraw review',
|
||||
review_btn: 'Review',
|
||||
review_reject_title: 'Reject withdraw',
|
||||
review_btn_approve: 'Approve',
|
||||
review_btn_reject: 'Reject',
|
||||
|
||||
@@ -39,7 +39,7 @@ export default {
|
||||
ddpay_spec_intro:
|
||||
'当前提现走 DDPay 出金(Payout),移动端调用 withdrawCreate;充值渠道为 ddpay 时调用 depositCreate。下列为字段约定摘要,详细以仓库内 DDPay 文档与《36字花-移动端接口设计草案》为准。',
|
||||
ddpay_spec_li_withdraw:
|
||||
'提现必填:channel_code=ddpay(支付渠道)、withdraw_coin、receive_type=bank、receive_account(收款账号)、receiver_name(与银行登记一致)、receiver_email、receiver_mobile、bank_code(须与本页「提现支持银行(按币种)」中 code 一致)、idempotency_key;bank_branch 选填,不传则服务端按 N/A 提交。',
|
||||
'充值联调可用 channel_code=mock(模拟支付,无需 DDPay 配置);生产请用 ddpay。提现必填:channel_code=ddpay 或 mock(模拟)、withdraw_coin、receive_type=bank、receive_account、receiver_name、receiver_email、receiver_mobile、bank_code、idempotency_key;bank_branch 选填。',
|
||||
ddpay_spec_li_bank_table:
|
||||
'「银行名(英文)」将映射为 DDPay 的 bank[name],请与 DDPay 官方银行全称列表一致,否则出金可能被拒。',
|
||||
ddpay_spec_li_deposit:
|
||||
|
||||
@@ -14,7 +14,7 @@ export default {
|
||||
'status 0': '待支付',
|
||||
'status 1': '成功',
|
||||
'status 2': '失败',
|
||||
'status 3': '已取消',
|
||||
'status 3': '待审核',
|
||||
pay_channel: '支付通道',
|
||||
pay_time: '支付时间',
|
||||
deposit_tier_id: '充值档位',
|
||||
@@ -23,4 +23,18 @@ export default {
|
||||
update_time: '更新时间',
|
||||
detail_title: '充值订单详情',
|
||||
close_btn: '关闭',
|
||||
reject_reason: '驳回原因',
|
||||
review_admin_username: '审核人',
|
||||
review_time: '审核时间',
|
||||
review_title: '充值审核',
|
||||
review_reject_title: '拒绝充值',
|
||||
review_btn: '审核',
|
||||
review_btn_approve: '通过',
|
||||
review_btn_reject: '拒绝',
|
||||
review_btn_back: '返回',
|
||||
review_btn_confirm_reject: '确认拒绝',
|
||||
review_reject_tip: '拒绝后订单将标记为失败,资金不会入账。',
|
||||
review_reject_placeholder: '请输入驳回原因',
|
||||
reject_reason_required: '请输入驳回原因',
|
||||
already_reviewed: '该订单已审核,无需重复操作',
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export default {
|
||||
channel_name: '渠道',
|
||||
review_admin_username: '审核人',
|
||||
review_title: '提现审核',
|
||||
review_btn: '审核',
|
||||
review_reject_title: '提现拒绝',
|
||||
review_btn_approve: '通过',
|
||||
review_btn_reject: '拒绝',
|
||||
|
||||
@@ -30,6 +30,22 @@ defineOptions({
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit'])
|
||||
const depositEditBtn = optButtons.find((b) => b.name === 'edit')
|
||||
if (depositEditBtn) {
|
||||
depositEditBtn.display = (row: TableRow) => Number(row.status) !== 3
|
||||
}
|
||||
optButtons.push({
|
||||
render: 'tipButton',
|
||||
name: 'depositReview',
|
||||
title: 'order.depositOrder.review_btn',
|
||||
text: '',
|
||||
type: 'warning',
|
||||
icon: 'fa fa-check-square-o',
|
||||
display: (row: TableRow) => Number(row.status) === 3,
|
||||
click: (row: TableRow) => {
|
||||
baTable.toggleForm('Edit', [row[baTable.table.pk!]])
|
||||
},
|
||||
})
|
||||
|
||||
function formatAmount(_row: anyObj, _column: any, cellValue: unknown) {
|
||||
if (cellValue === null || cellValue === undefined || cellValue === '') {
|
||||
@@ -186,7 +202,7 @@ const baTable = new baTableClass(
|
||||
{
|
||||
label: t('Operate'),
|
||||
align: 'center',
|
||||
width: 90,
|
||||
width: 120,
|
||||
render: 'buttons',
|
||||
buttons: optButtons,
|
||||
operator: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="ba-operate-dialog deposit-detail-dialog"
|
||||
class="ba-operate-dialog deposit-review-dialog"
|
||||
:close-on-click-modal="false"
|
||||
:model-value="isOpen"
|
||||
width="640px"
|
||||
@@ -8,7 +8,13 @@
|
||||
>
|
||||
<template #header>
|
||||
<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
|
||||
{{ t('order.depositOrder.detail_title') }}
|
||||
{{
|
||||
step === 'reject'
|
||||
? t('order.depositOrder.review_reject_title')
|
||||
: isPendingReview
|
||||
? t('order.depositOrder.review_title')
|
||||
: t('order.depositOrder.detail_title')
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -18,7 +24,7 @@
|
||||
:style="config.layout.shrink ? '' : 'width: calc(100% - ' + (baTable.form.labelWidth ?? 120) / 2 + 'px)'"
|
||||
>
|
||||
<el-form
|
||||
v-if="!loading"
|
||||
v-if="!loading && step === 'review'"
|
||||
:label-position="config.layout.shrink ? 'top' : 'right'"
|
||||
:label-width="(baTable.form.labelWidth ?? 120) + 'px'"
|
||||
@submit.prevent=""
|
||||
@@ -38,7 +44,6 @@
|
||||
<el-form-item :label="t('order.depositOrder.status')">
|
||||
<el-tag :type="statusTagType" effect="dark" size="small">{{ statusLabel }}</el-tag>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('order.depositOrder.amount')">
|
||||
<el-input :model-value="amountText" readonly />
|
||||
</el-form-item>
|
||||
@@ -48,7 +53,6 @@
|
||||
<el-form-item :label="t('order.depositOrder.total_credit')">
|
||||
<el-input :model-value="totalCreditText" readonly />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('order.depositOrder.pay_channel')">
|
||||
<el-input :model-value="form.pay_channel || '-'" readonly />
|
||||
</el-form-item>
|
||||
@@ -58,6 +62,15 @@
|
||||
<el-form-item :label="t('order.depositOrder.deposit_tier_id')">
|
||||
<el-input :model-value="form.deposit_tier_id || '-'" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.reject_reason" :label="t('order.depositOrder.reject_reason')">
|
||||
<el-input :model-value="form.reject_reason" type="textarea" :rows="2" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!isPendingReview" :label="t('order.depositOrder.review_admin_username')">
|
||||
<el-input :model-value="form.review_admin_text" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!isPendingReview" :label="t('order.depositOrder.review_time')">
|
||||
<el-input :model-value="form.review_time_text" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('order.depositOrder.remark')">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" readonly />
|
||||
</el-form-item>
|
||||
@@ -65,20 +78,67 @@
|
||||
<el-input :model-value="form.create_time_text" readonly />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form
|
||||
v-if="!loading && step === 'reject'"
|
||||
ref="rejectFormRef"
|
||||
:model="rejectForm"
|
||||
:rules="rejectRules"
|
||||
:label-position="config.layout.shrink ? 'top' : 'right'"
|
||||
:label-width="(baTable.form.labelWidth ?? 120) + 'px'"
|
||||
@submit.prevent=""
|
||||
>
|
||||
<el-alert
|
||||
class="review-reject-hint"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
:title="t('order.depositOrder.review_reject_tip')"
|
||||
/>
|
||||
<el-form-item :label="t('order.depositOrder.order_no')">
|
||||
<el-input :model-value="form.order_no" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('order.depositOrder.total_credit')">
|
||||
<el-input :model-value="totalCreditText" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('order.depositOrder.reject_reason')" prop="remark">
|
||||
<el-input
|
||||
v-model="rejectForm.remark"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="255"
|
||||
show-word-limit
|
||||
:placeholder="t('order.depositOrder.review_reject_placeholder')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<template #footer>
|
||||
<div class="detail-footer">
|
||||
<el-button type="primary" v-blur @click="onDialogClose">{{ t('order.depositOrder.close_btn') }}</el-button>
|
||||
<div class="review-footer">
|
||||
<template v-if="step === 'review'">
|
||||
<el-button @click="onDialogClose">{{ isPendingReview ? t('Cancel') : t('order.depositOrder.close_btn') }}</el-button>
|
||||
<template v-if="isPendingReview">
|
||||
<el-button type="danger" :loading="submitting" @click="gotoReject">{{ t('order.depositOrder.review_btn_reject') }}</el-button>
|
||||
<el-button type="primary" v-blur :loading="submitting" @click="submitApprove">{{ t('order.depositOrder.review_btn_approve') }}</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button @click="backToReview">{{ t('order.depositOrder.review_btn_back') }}</el-button>
|
||||
<el-button type="danger" v-blur :loading="submitting" @click="submitReject">{{ t('order.depositOrder.review_btn_confirm_reject') }}</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, ref, watch } from 'vue'
|
||||
import type { FormInstance, FormItemRule } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, inject, reactive, ref, useTemplateRef, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import createAxios from '/@/utils/axios'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
|
||||
@@ -86,7 +146,12 @@ const config = useConfig()
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
const { t } = useI18n()
|
||||
|
||||
const rejectFormRef = useTemplateRef<FormInstance>('rejectFormRef')
|
||||
|
||||
type Step = 'review' | 'reject'
|
||||
const step = ref<Step>('review')
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
id: 0,
|
||||
@@ -98,13 +163,29 @@ const form = reactive({
|
||||
pay_time_text: '-',
|
||||
deposit_tier_id: '',
|
||||
remark: '',
|
||||
reject_reason: '',
|
||||
create_time_text: '-',
|
||||
review_time_text: '-',
|
||||
review_admin_text: '-',
|
||||
amount: 0,
|
||||
bonus_amount: 0,
|
||||
status: 0,
|
||||
})
|
||||
|
||||
const rejectForm = reactive({
|
||||
remark: '',
|
||||
})
|
||||
|
||||
const isOpen = computed(() => ['Edit'].includes(baTable.form.operate ?? ''))
|
||||
const isPendingReview = computed(() => form.status === 3)
|
||||
|
||||
watch(isOpen, (visible) => {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
step.value = 'review'
|
||||
rejectForm.remark = ''
|
||||
})
|
||||
|
||||
watch(
|
||||
() => ({ visible: isOpen.value, loadingState: baTable.form.loading, items: baTable.form.items }),
|
||||
@@ -128,13 +209,16 @@ const hydrate = () => {
|
||||
form.pay_channel = String(row['pay_channel'] ?? '')
|
||||
form.deposit_tier_id = String(row['deposit_tier_id'] ?? '')
|
||||
form.remark = String(row['remark'] ?? '')
|
||||
form.reject_reason = String(row['reject_reason'] ?? '')
|
||||
form.amount = parseNumber(row['amount'])
|
||||
form.bonus_amount = parseNumber(row['bonus_amount'])
|
||||
form.status = Number(row['status'] ?? 0)
|
||||
form.create_time_text = formatTime(row['create_time'])
|
||||
form.pay_time_text = formatTime(row['pay_time'])
|
||||
form.review_time_text = formatTime(row['review_time'])
|
||||
form.user_text = resolveRelationText(row, 'user', row['user_id'])
|
||||
form.channel_text = resolveRelationText(row, 'channel', row['channel_id'])
|
||||
form.review_admin_text = resolveRelationText(row, 'reviewAdmin', row['review_admin_id'])
|
||||
}
|
||||
|
||||
const statusLabel = computed(() => t('order.depositOrder.status ' + form.status))
|
||||
@@ -155,10 +239,91 @@ const amountText = computed(() => formatAmount(form.amount))
|
||||
const bonusText = computed(() => formatAmount(form.bonus_amount))
|
||||
const totalCreditText = computed(() => formatAmount(Number((form.amount + form.bonus_amount).toFixed(2))))
|
||||
|
||||
const rejectRules: Record<string, FormItemRule[]> = {
|
||||
remark: [
|
||||
{
|
||||
required: true,
|
||||
validator: (_r, value, cb) => {
|
||||
const text = typeof value === 'string' ? value.trim() : ''
|
||||
if (text === '') {
|
||||
cb(new Error(t('order.depositOrder.reject_reason_required')))
|
||||
return
|
||||
}
|
||||
cb()
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const onDialogClose = () => {
|
||||
if (submitting.value) {
|
||||
return
|
||||
}
|
||||
step.value = 'review'
|
||||
baTable.toggleForm()
|
||||
}
|
||||
|
||||
const gotoReject = () => {
|
||||
step.value = 'reject'
|
||||
rejectForm.remark = ''
|
||||
}
|
||||
|
||||
const backToReview = () => {
|
||||
step.value = 'review'
|
||||
}
|
||||
|
||||
const submitApprove = async () => {
|
||||
if (!isPendingReview.value) {
|
||||
ElMessage.warning(t('order.depositOrder.already_reviewed'))
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
await createAxios(
|
||||
{
|
||||
url: '/admin/order.DepositOrder/approve',
|
||||
method: 'POST',
|
||||
data: { id: form.id },
|
||||
},
|
||||
{ showSuccessMessage: true }
|
||||
)
|
||||
baTable.onTableHeaderAction('refresh', {})
|
||||
baTable.toggleForm()
|
||||
} catch (_e) {
|
||||
// axios interceptor
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const submitReject = async () => {
|
||||
const formEl = rejectFormRef.value
|
||||
if (!formEl) return
|
||||
const valid = await formEl.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
submitting.value = true
|
||||
try {
|
||||
await createAxios(
|
||||
{
|
||||
url: '/admin/order.DepositOrder/reject',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id: form.id,
|
||||
remark: rejectForm.remark.trim(),
|
||||
},
|
||||
},
|
||||
{ showSuccessMessage: true }
|
||||
)
|
||||
baTable.onTableHeaderAction('refresh', {})
|
||||
baTable.toggleForm()
|
||||
} catch (_e) {
|
||||
// axios interceptor
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function parseNumber(raw: unknown): number {
|
||||
if (raw === null || raw === undefined || raw === '') return 0
|
||||
const n = Number(raw)
|
||||
@@ -210,22 +375,19 @@ function resolveRelationText(row: Record<string, unknown>, relationKey: string,
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.deposit-detail-dialog {
|
||||
.deposit-review-dialog {
|
||||
:deep(.el-dialog__body) {
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-footer {
|
||||
.review-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
:deep(.deposit-detail-dialog) {
|
||||
width: calc(100vw - 24px) !important;
|
||||
max-width: 100vw;
|
||||
}
|
||||
.review-reject-hint {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -30,6 +30,22 @@ defineOptions({
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit'])
|
||||
const editBtn = optButtons.find((b) => b.name === 'edit')
|
||||
if (editBtn) {
|
||||
editBtn.display = (row: TableRow) => Number(row.status) !== 0
|
||||
}
|
||||
optButtons.push({
|
||||
render: 'tipButton',
|
||||
name: 'review',
|
||||
title: 'order.withdrawOrder.review_btn',
|
||||
text: '',
|
||||
type: 'warning',
|
||||
icon: 'fa fa-check-square-o',
|
||||
display: (row: TableRow) => Number(row.status) === 0,
|
||||
click: (row: TableRow) => {
|
||||
baTable.toggleForm('Edit', [row[baTable.table.pk!]])
|
||||
},
|
||||
})
|
||||
|
||||
function formatAmount(_row: anyObj, _column: any, cellValue: unknown) {
|
||||
if (cellValue === null || cellValue === undefined || cellValue === '') {
|
||||
@@ -151,9 +167,9 @@ const baTable = new baTableClass(
|
||||
effect: 'dark',
|
||||
custom: {
|
||||
'0': 'info',
|
||||
'1': 'warning',
|
||||
'2': 'success',
|
||||
'3': 'danger',
|
||||
'1': 'success',
|
||||
'2': 'danger',
|
||||
'3': 'success',
|
||||
},
|
||||
replaceValue: {
|
||||
'0': t('order.withdrawOrder.status 0'),
|
||||
@@ -213,7 +229,7 @@ const baTable = new baTableClass(
|
||||
width: 170,
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{ label: t('Operate'), align: 'center', width: 90, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
|
||||
{ label: t('Operate'), align: 'center', width: 120, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user