Compare commits
No commits in common. "2e816ed681878aa32b01021f9e0b04129aa12f81" and "7d33d8a5e49c96cd6e0eee5c26c641eb33f97fec" have entirely different histories.
2e816ed681
...
7d33d8a5e4
@ -1,24 +1,6 @@
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
import type { LegacyPageResult } from '#/api/legacy/system';
|
import type { LegacyPageResult } from '#/api/legacy/system';
|
||||||
|
|
||||||
const BINANCE_RECHARGE_BASE = '/go/payment/binance';
|
|
||||||
|
|
||||||
export async function getBinanceRechargeConfig(sysOrigin: string) {
|
|
||||||
return requestClient.get<Record<string, any>>(
|
|
||||||
`${BINANCE_RECHARGE_BASE}/config`,
|
|
||||||
{
|
|
||||||
params: { sysOrigin },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveBinanceRechargeConfig(data: Record<string, any>) {
|
|
||||||
return requestClient.post<Record<string, any>>(
|
|
||||||
`${BINANCE_RECHARGE_BASE}/config/save`,
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listPayOpenCountry() {
|
export async function listPayOpenCountry() {
|
||||||
return requestClient.get<Array<Record<string, any>>>(
|
return requestClient.get<Array<Record<string, any>>>(
|
||||||
'/sys-pay-open-country/list',
|
'/sys-pay-open-country/list',
|
||||||
|
|||||||
@ -25,12 +25,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('#/views/operate/pay-country.vue'),
|
component: () => import('#/views/operate/pay-country.vue'),
|
||||||
meta: { title: '开通支付国家' },
|
meta: { title: '开通支付国家' },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'BinanceRechargeConfig',
|
|
||||||
path: 'binance/config',
|
|
||||||
component: () => import('#/views/operate/binance-recharge-config.vue'),
|
|
||||||
meta: { title: '币安配置' },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'RegionRelation',
|
name: 'RegionRelation',
|
||||||
path: 'region/relation',
|
path: 'region/relation',
|
||||||
|
|||||||
@ -1,368 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
|
||||||
import { useAccessStore } from '@vben/stores';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getBinanceRechargeConfig,
|
|
||||||
saveBinanceRechargeConfig,
|
|
||||||
} from '#/api/legacy/pay';
|
|
||||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Switch,
|
|
||||||
Table,
|
|
||||||
Tag,
|
|
||||||
message,
|
|
||||||
} from 'antdv-next';
|
|
||||||
|
|
||||||
defineOptions({ name: 'OperateBinanceRechargeConfig' });
|
|
||||||
|
|
||||||
let localRowId = 0;
|
|
||||||
|
|
||||||
function createRateRow(
|
|
||||||
minAmount = '0',
|
|
||||||
maxAmount = '0',
|
|
||||||
goldPerUsd = '80000',
|
|
||||||
) {
|
|
||||||
localRowId += 1;
|
|
||||||
return {
|
|
||||||
goldPerUsd,
|
|
||||||
maxAmount,
|
|
||||||
minAmount,
|
|
||||||
rowKey: `binance-rate-${localRowId}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessStore = useAccessStore();
|
|
||||||
const sysOriginOptions = computed(() => {
|
|
||||||
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
|
|
||||||
return options.length > 0 ? options : getAllowedSysOrigins([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedSysOrigin = ref('');
|
|
||||||
const loading = ref(false);
|
|
||||||
const saving = ref(false);
|
|
||||||
const form = reactive<Record<string, any>>({
|
|
||||||
apiKey: '',
|
|
||||||
apiSecret: '',
|
|
||||||
apiSecretConfigured: false,
|
|
||||||
enabled: true,
|
|
||||||
rates: [createRateRow()],
|
|
||||||
sysOrigin: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const rateColumns = [
|
|
||||||
{ dataIndex: 'minAmount', key: 'minAmount', title: '最小金额(含)', width: 180 },
|
|
||||||
{ dataIndex: 'maxAmount', key: 'maxAmount', title: '最大金额(不含)', width: 180 },
|
|
||||||
{ dataIndex: 'goldPerUsd', key: 'goldPerUsd', title: '1 USDT 对应金币', width: 200 },
|
|
||||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 100 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const statusMeta = computed(() => {
|
|
||||||
if (!form.apiKey && !form.apiSecretConfigured) {
|
|
||||||
return { color: 'default', text: '未配置' };
|
|
||||||
}
|
|
||||||
if (form.enabled) {
|
|
||||||
return { color: 'success', text: '已启用' };
|
|
||||||
}
|
|
||||||
return { color: 'default', text: '已关闭' };
|
|
||||||
});
|
|
||||||
|
|
||||||
function normalizeDecimalText(value: unknown) {
|
|
||||||
return String(value ?? '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeConfig(result: null | Record<string, any> | undefined) {
|
|
||||||
const value = result || {};
|
|
||||||
form.sysOrigin = String(value.sysOrigin || selectedSysOrigin.value || '');
|
|
||||||
form.apiKey = String(value.apiKey || '');
|
|
||||||
form.apiSecret = '';
|
|
||||||
form.apiSecretConfigured = Boolean(value.apiSecretConfigured);
|
|
||||||
form.enabled = value.enabled !== false;
|
|
||||||
const rates = Array.isArray(value.rates) && value.rates.length > 0
|
|
||||||
? value.rates
|
|
||||||
: [createRateRow('0', '100', '80000'), createRateRow('100', '0', '82000')];
|
|
||||||
form.rates = rates.map((item: Record<string, any>) => ({
|
|
||||||
goldPerUsd: String(item.goldPerUsd ?? '80000'),
|
|
||||||
maxAmount: String(item.maxAmount ?? '0'),
|
|
||||||
minAmount: String(item.minAmount ?? '0'),
|
|
||||||
rowKey: item.rowKey || createRateRow().rowKey,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadConfig() {
|
|
||||||
if (!selectedSysOrigin.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await getBinanceRechargeConfig(selectedSysOrigin.value);
|
|
||||||
normalizeConfig(result);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRateRow() {
|
|
||||||
const rows = form.rates as Array<Record<string, any>>;
|
|
||||||
const last = rows.at(-1);
|
|
||||||
const nextMin = Number(last?.maxAmount || last?.minAmount || 0);
|
|
||||||
rows.push(createRateRow(String(Number.isFinite(nextMin) ? nextMin : 0), '0', '80000'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeRateRow(index: number) {
|
|
||||||
(form.rates as Array<Record<string, any>>).splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateDecimal(value: unknown) {
|
|
||||||
const text = normalizeDecimalText(value);
|
|
||||||
if (!text) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const numeric = Number(text);
|
|
||||||
return Number.isFinite(numeric);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateBeforeSave() {
|
|
||||||
if (!selectedSysOrigin.value) {
|
|
||||||
message.warning('请选择系统');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!String(form.apiKey || '').trim()) {
|
|
||||||
message.warning('请输入 API Key');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!form.apiSecretConfigured && !String(form.apiSecret || '').trim()) {
|
|
||||||
message.warning('请输入 API 秘钥');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const rows = form.rates as Array<Record<string, any>>;
|
|
||||||
if (rows.length === 0) {
|
|
||||||
message.warning('请至少配置一个发金币区间');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const normalized = rows.map((item) => ({
|
|
||||||
goldPerUsd: normalizeDecimalText(item.goldPerUsd),
|
|
||||||
maxAmount: normalizeDecimalText(item.maxAmount || '0'),
|
|
||||||
minAmount: normalizeDecimalText(item.minAmount),
|
|
||||||
}));
|
|
||||||
for (const item of normalized) {
|
|
||||||
if (
|
|
||||||
!validateDecimal(item.minAmount) ||
|
|
||||||
!validateDecimal(item.maxAmount) ||
|
|
||||||
!validateDecimal(item.goldPerUsd)
|
|
||||||
) {
|
|
||||||
message.warning('金额和金币比例必须是数字');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const minAmount = Number(item.minAmount);
|
|
||||||
const maxAmount = Number(item.maxAmount);
|
|
||||||
const goldPerUsd = Number(item.goldPerUsd);
|
|
||||||
if (minAmount < 0 || maxAmount < 0) {
|
|
||||||
message.warning('金额区间不能小于 0');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (maxAmount > 0 && maxAmount <= minAmount) {
|
|
||||||
message.warning('最大金额必须大于最小金额,或填 0 表示不限制');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (goldPerUsd <= 0) {
|
|
||||||
message.warning('金币比例必须大于 0');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const sorted = [...normalized].sort(
|
|
||||||
(left, right) => Number(left.minAmount) - Number(right.minAmount),
|
|
||||||
);
|
|
||||||
for (let index = 0; index < sorted.length; index += 1) {
|
|
||||||
const current = sorted[index];
|
|
||||||
const next = sorted[index + 1];
|
|
||||||
const currentMax = Number(current?.maxAmount || 0);
|
|
||||||
if (currentMax === 0 && index !== sorted.length - 1) {
|
|
||||||
message.warning('不限制最大金额的区间必须放在最后');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (next && currentMax > Number(next.minAmount)) {
|
|
||||||
message.warning('发金币区间不能重叠');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submitForm() {
|
|
||||||
if (!validateBeforeSave()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
saving.value = true;
|
|
||||||
try {
|
|
||||||
await saveBinanceRechargeConfig({
|
|
||||||
apiKey: String(form.apiKey || '').trim(),
|
|
||||||
apiSecret: String(form.apiSecret || '').trim(),
|
|
||||||
enabled: Boolean(form.enabled),
|
|
||||||
rates: (form.rates as Array<Record<string, any>>).map((item) => ({
|
|
||||||
goldPerUsd: normalizeDecimalText(item.goldPerUsd),
|
|
||||||
maxAmount: normalizeDecimalText(item.maxAmount || '0'),
|
|
||||||
minAmount: normalizeDecimalText(item.minAmount),
|
|
||||||
})),
|
|
||||||
sysOrigin: selectedSysOrigin.value,
|
|
||||||
});
|
|
||||||
message.success('保存成功');
|
|
||||||
await loadConfig();
|
|
||||||
} finally {
|
|
||||||
saving.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
sysOriginOptions,
|
|
||||||
(options) => {
|
|
||||||
if (!selectedSysOrigin.value) {
|
|
||||||
selectedSysOrigin.value = String(options[0]?.value || '');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
selectedSysOrigin,
|
|
||||||
(value) => {
|
|
||||||
if (value) {
|
|
||||||
void loadConfig();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Page auto-content-height>
|
|
||||||
<Card :bordered="false" title="币安配置">
|
|
||||||
<div class="toolbar">
|
|
||||||
<Select
|
|
||||||
v-model:value="selectedSysOrigin"
|
|
||||||
:options="sysOriginOptions"
|
|
||||||
option-filter-prop="label"
|
|
||||||
option-label-prop="label"
|
|
||||||
placeholder="请选择系统"
|
|
||||||
show-search
|
|
||||||
style="width: 180px"
|
|
||||||
/>
|
|
||||||
<Tag :color="statusMeta.color">{{ statusMeta.text }}</Tag>
|
|
||||||
<Button :loading="loading" @click="loadConfig">刷新</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Form layout="vertical">
|
|
||||||
<div class="form-grid">
|
|
||||||
<FormItem label="启用自动充值验证">
|
|
||||||
<Switch v-model:checked="form.enabled" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem label="API Key">
|
|
||||||
<Input
|
|
||||||
v-model:value="form.apiKey"
|
|
||||||
allow-clear
|
|
||||||
placeholder="请输入 Binance API Key"
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
<FormItem label="API 秘钥">
|
|
||||||
<Input
|
|
||||||
v-model:value="form.apiSecret"
|
|
||||||
allow-clear
|
|
||||||
:placeholder="form.apiSecretConfigured ? '留空则不修改已保存秘钥' : '请输入 Binance API 秘钥'"
|
|
||||||
type="password"
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
|
|
||||||
<div class="section-head">
|
|
||||||
<div class="section-title">发金币区间</div>
|
|
||||||
<Space>
|
|
||||||
<Button @click="addRateRow">新增区间</Button>
|
|
||||||
<Button :loading="saving" type="primary" @click="submitForm">保存配置</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Table
|
|
||||||
:columns="rateColumns"
|
|
||||||
:data-source="form.rates"
|
|
||||||
:loading="loading"
|
|
||||||
:pagination="false"
|
|
||||||
row-key="rowKey"
|
|
||||||
:scroll="{ x: 760 }"
|
|
||||||
>
|
|
||||||
<template #bodyCell="{ column, index, record }">
|
|
||||||
<template v-if="column.key === 'minAmount'">
|
|
||||||
<Input
|
|
||||||
v-model:value="record.minAmount"
|
|
||||||
placeholder="0"
|
|
||||||
style="width: 140px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'maxAmount'">
|
|
||||||
<Input
|
|
||||||
v-model:value="record.maxAmount"
|
|
||||||
placeholder="0 表示不限制"
|
|
||||||
style="width: 140px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'goldPerUsd'">
|
|
||||||
<Input
|
|
||||||
v-model:value="record.goldPerUsd"
|
|
||||||
placeholder="80000"
|
|
||||||
style="width: 160px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'actions'">
|
|
||||||
<Button danger type="link" @click="removeRateRow(index)">删除</Button>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</Table>
|
|
||||||
</Card>
|
|
||||||
</Page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.toolbar {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(160px, 220px) minmax(240px, 1fr) minmax(240px, 1fr);
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-head {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin: 8px 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.form-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -71,10 +71,6 @@ const canEdit = computed(() => hasPermission('team:manager:list:edit'));
|
|||||||
const canDelete = computed(() => hasPermission('team:manager:list:del'));
|
const canDelete = computed(() => hasPermission('team:manager:list:del'));
|
||||||
const showOperationCol = computed(() => canEdit.value || canDelete.value);
|
const showOperationCol = computed(() => canEdit.value || canDelete.value);
|
||||||
|
|
||||||
function getUserShortId(profile?: Record<string, any> | null) {
|
|
||||||
return profile?.actualAccount || profile?.account || profile?.id || '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadData(reset = false) {
|
async function loadData(reset = false) {
|
||||||
if (reset) {
|
if (reset) {
|
||||||
query.cursor = 1;
|
query.cursor = 1;
|
||||||
@ -174,7 +170,7 @@ loadData(true);
|
|||||||
<div class="user-name-row">
|
<div class="user-name-row">
|
||||||
<span>{{ record.userProfile?.userNickname || '-' }}</span>
|
<span>{{ record.userProfile?.userNickname || '-' }}</span>
|
||||||
<span class="user-short-id">
|
<span class="user-short-id">
|
||||||
短ID:{{ getUserShortId(record.userProfile) }}
|
短ID:{{ record.userProfile?.actualAccount || '-' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-sub">
|
<div class="user-sub">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user