经理中心相关
This commit is contained in:
parent
200e1c6fad
commit
fc8d5dc12a
@ -1,6 +1,24 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
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() {
|
||||
return requestClient.get<Array<Record<string, any>>>(
|
||||
'/sys-pay-open-country/list',
|
||||
|
||||
@ -25,6 +25,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/pay-country.vue'),
|
||||
meta: { title: '开通支付国家' },
|
||||
},
|
||||
{
|
||||
name: 'BinanceRechargeConfig',
|
||||
path: 'binance/config',
|
||||
component: () => import('#/views/operate/binance-recharge-config.vue'),
|
||||
meta: { title: '币安配置' },
|
||||
},
|
||||
{
|
||||
name: 'RegionRelation',
|
||||
path: 'region/relation',
|
||||
|
||||
368
apps/src/views/operate/binance-recharge-config.vue
Normal file
368
apps/src/views/operate/binance-recharge-config.vue
Normal file
@ -0,0 +1,368 @@
|
||||
<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,6 +71,10 @@ const canEdit = computed(() => hasPermission('team:manager:list:edit'));
|
||||
const canDelete = computed(() => hasPermission('team:manager:list:del'));
|
||||
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) {
|
||||
if (reset) {
|
||||
query.cursor = 1;
|
||||
@ -170,7 +174,7 @@ loadData(true);
|
||||
<div class="user-name-row">
|
||||
<span>{{ record.userProfile?.userNickname || '-' }}</span>
|
||||
<span class="user-short-id">
|
||||
短ID:{{ record.userProfile?.actualAccount || '-' }}
|
||||
短ID:{{ getUserShortId(record.userProfile) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="user-sub">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user