经理中心相关
This commit is contained in:
parent
200e1c6fad
commit
fc8d5dc12a
@ -1,6 +1,24 @@
|
|||||||
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,6 +25,12 @@ 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',
|
||||||
|
|||||||
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 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;
|
||||||
@ -170,7 +174,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:{{ record.userProfile?.actualAccount || '-' }}
|
短ID:{{ getUserShortId(record.userProfile) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-sub">
|
<div class="user-sub">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user