1.新增充值档位配置
2.新增充值/提现配置
This commit is contained in:
155
web/src/views/backend/config/depositChannel/index.vue
Normal file
155
web/src/views/backend/config/depositChannel/index.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" type="info" :closable="false" show-icon>
|
||||
{{ t('config.depositChannel.desc') }}
|
||||
</el-alert>
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'edit', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('config.depositChannel.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'config/depositChannel',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit'])
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/config.DepositChannel/'),
|
||||
{
|
||||
pk: 'code',
|
||||
filter: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
},
|
||||
defaultOrder: { prop: 'sort', order: 'asc' },
|
||||
extend: {
|
||||
registry: {} as Record<string, { name: string; name_en: string; sort: number }>,
|
||||
tier_options: [] as { id: string; label: string }[],
|
||||
},
|
||||
column: [
|
||||
{ type: 'selection', align: 'center', operator: false },
|
||||
{
|
||||
label: t('config.depositChannel.sort'),
|
||||
prop: 'sort',
|
||||
align: 'center',
|
||||
width: 88,
|
||||
operator: 'RANGE',
|
||||
sortable: 'custom',
|
||||
},
|
||||
{
|
||||
label: t('config.depositChannel.status'),
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
sortable: false,
|
||||
render: 'switch',
|
||||
replaceValue: { 0: t('config.depositChannel.status_off'), 1: t('config.depositChannel.status_on') },
|
||||
},
|
||||
{
|
||||
label: t('config.depositChannel.code'),
|
||||
prop: 'code',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
},
|
||||
{
|
||||
label: t('config.depositChannel.display_name'),
|
||||
prop: 'display_name',
|
||||
align: 'center',
|
||||
minWidth: 130,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('config.depositChannel.tier_ids'),
|
||||
prop: 'tier_ids',
|
||||
align: 'center',
|
||||
minWidth: 240,
|
||||
operator: false,
|
||||
render: 'tags',
|
||||
formatter: (row: anyObj) => {
|
||||
const ids = row.tier_ids
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return [t('config.depositChannel.tier_all')]
|
||||
}
|
||||
const opts = (baTable.table.extend?.tier_options ?? []) as { id: string; label: string }[]
|
||||
return ids.map((id: string) => {
|
||||
const o = opts.find((x) => x.id === id)
|
||||
return o ? o.label : id
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('Operate'),
|
||||
align: 'center',
|
||||
width: 90,
|
||||
render: 'buttons',
|
||||
buttons: optButtons,
|
||||
operator: false,
|
||||
fixed: 'right',
|
||||
},
|
||||
],
|
||||
dblClickNotEditColumn: [undefined, 'status'],
|
||||
},
|
||||
{
|
||||
defaultItems: {
|
||||
code: '',
|
||||
sort: 10,
|
||||
status: 1,
|
||||
tier_ids: [] as string[],
|
||||
},
|
||||
},
|
||||
{},
|
||||
{
|
||||
getData({ res }) {
|
||||
const d = res.data as
|
||||
| {
|
||||
registry?: Record<string, { name: string; name_en: string; sort: number }>
|
||||
tier_options?: { id: string; label: string }[]
|
||||
}
|
||||
| undefined
|
||||
if (d?.registry) {
|
||||
baTable.table.extend.registry = d.registry
|
||||
}
|
||||
if (d?.tier_options) {
|
||||
baTable.table.extend.tier_options = d.tier_options
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
baTable.initSort()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
109
web/src/views/backend/config/depositChannel/popupForm.vue
Normal file
109
web/src/views/backend/config/depositChannel/popupForm.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="ba-operate-dialog"
|
||||
:close-on-click-modal="false"
|
||||
:model-value="baTable.form.operate === 'Edit'"
|
||||
@close="baTable.toggleForm"
|
||||
>
|
||||
<template #header>
|
||||
<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
|
||||
{{ t('Edit') }}
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar v-loading="baTable.form.loading" class="ba-table-form-scrollbar">
|
||||
<div
|
||||
class="ba-operate-form ba-Edit-form"
|
||||
:style="config.layout.shrink ? '' : 'width: calc(100% - ' + baTable.form.labelWidth! / 2 + 'px)'"
|
||||
>
|
||||
<el-form
|
||||
v-if="!baTable.form.loading"
|
||||
ref="formRef"
|
||||
@submit.prevent=""
|
||||
@keyup.enter="baTable.onSubmit(formRef)"
|
||||
:model="baTable.form.items"
|
||||
:label-position="config.layout.shrink ? 'top' : 'right'"
|
||||
:label-width="baTable.form.labelWidth + 'px'"
|
||||
:rules="formRules"
|
||||
>
|
||||
<el-alert class="ba-table-alert" type="info" :closable="false" show-icon>
|
||||
{{ t('config.depositChannel.form_tip') }}
|
||||
</el-alert>
|
||||
|
||||
<el-form-item :label="t('config.depositChannel.code')" prop="code">
|
||||
<el-input :model-value="String(baTable.form.items!.code ?? '')" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.depositChannel.display_name')">
|
||||
<el-input :model-value="registryDisplayName" readonly />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('config.depositChannel.sort')" prop="sort">
|
||||
<el-input-number v-model="baTable.form.items!.sort" :min="0" :max="9999" :controls="true" class="w100" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.depositChannel.status')" prop="status">
|
||||
<el-switch v-model="baTable.form.items!.status" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('config.depositChannel.tier_ids')" prop="tier_ids">
|
||||
<el-select
|
||||
v-model="baTable.form.items!.tier_ids"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
filterable
|
||||
clearable
|
||||
class="w100"
|
||||
:placeholder="t('config.depositChannel.tier_ids_ph')"
|
||||
>
|
||||
<el-option v-for="opt in tierOptions" :key="opt.id" :label="opt.label" :value="opt.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<div :style="'width: calc(100% - ' + baTable.form.labelWidth! / 1.8 + 'px)'">
|
||||
<el-button @click="baTable.toggleForm()">{{ t('Cancel') }}</el-button>
|
||||
<el-button v-blur :loading="baTable.form.submitLoading" type="primary" @click="baTable.onSubmit(formRef)">
|
||||
{{ t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { computed, inject, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
|
||||
type TierOpt = { id: string; label: string }
|
||||
|
||||
const config = useConfig()
|
||||
const formRef = useTemplateRef<FormInstance>('formRef')
|
||||
const baTable = inject('baTable') as baTable
|
||||
const { t } = useI18n()
|
||||
|
||||
const tierOptions = computed(() => {
|
||||
const raw = baTable.table.extend?.tier_options
|
||||
return Array.isArray(raw) ? (raw as TierOpt[]) : []
|
||||
})
|
||||
|
||||
const registryDisplayName = computed(() => {
|
||||
const code = String(baTable.form.items?.code ?? '')
|
||||
const reg = baTable.table.extend?.registry as Record<string, { name?: string }> | undefined
|
||||
if (!code || !reg || !reg[code]) {
|
||||
return code
|
||||
}
|
||||
const n = reg[code].name
|
||||
return typeof n === 'string' && n !== '' ? n : code
|
||||
})
|
||||
|
||||
const formRules = {}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.w100 {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,278 +1,209 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box deposit-tier-page">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" type="info" :closable="false" show-icon>
|
||||
{{ t('config.depositTier.desc') }}
|
||||
</el-alert>
|
||||
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" :disabled="loading" @click="onAdd">
|
||||
<Icon name="el-icon-Plus" />
|
||||
<span class="ml-6">{{ t('config.depositTier.btn_add') }}</span>
|
||||
</el-button>
|
||||
<el-button type="success" :loading="saving" :disabled="loading" @click="onSave">
|
||||
{{ t('config.depositTier.btn_save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'add', 'edit', 'delete', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('config.depositTier.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
stripe
|
||||
:data="items"
|
||||
row-key="_rowKey"
|
||||
max-height="720"
|
||||
class="deposit-tier-table"
|
||||
header-align="center"
|
||||
>
|
||||
<el-table-column prop="sort" :label="t('config.depositTier.sort')" width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-center">
|
||||
<el-input-number v-model="row.sort" :min="0" :max="9999" :controls="false" style="width: 100%; max-width: 160px" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<Table ref="tableRef"></Table>
|
||||
|
||||
<el-table-column :label="t('config.depositTier.status')" width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-center">
|
||||
<el-switch v-model="row.status" :active-value="1" :inactive-value="0" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('config.depositTier.title_col')" min-width="180" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.title" maxlength="64" :placeholder="t('config.depositTier.title_ph')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('config.depositTier.title_en_col')" min-width="180" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.title_en" maxlength="64" :placeholder="t('config.depositTier.title_en_ph')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('config.depositTier.amount')" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.amount" :placeholder="t('config.depositTier.amount_ph')">
|
||||
<template #suffix>
|
||||
<span class="currency">{{ t('config.depositTier.currency') }}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('config.depositTier.bonus_amount')" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.bonus_amount" :placeholder="t('config.depositTier.bonus_ph')">
|
||||
<template #suffix>
|
||||
<span class="currency">{{ t('config.depositTier.currency') }}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('config.depositTier.desc_col')" min-width="220" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.desc" maxlength="255" :autosize="{ minRows: 1, maxRows: 3 }" type="textarea" :placeholder="t('config.depositTier.desc_ph')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('config.depositTier.desc_en_col')" min-width="220" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.desc_en" maxlength="255" :autosize="{ minRows: 1, maxRows: 3 }" type="textarea" :placeholder="t('config.depositTier.desc_en_ph')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('config.depositTier.tier_id')" width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-text class="tier-id" truncated>{{ row.id || t('config.depositTier.auto_id') }}</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('config.depositTier.operate')" width="90" align="center" header-align="center" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" link @click="onRemove($index)">
|
||||
{{ t('config.depositTier.btn_remove') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import createAxios from '/@/utils/axios'
|
||||
import { auth } from '/@/utils/common'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'config/depositTier',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])
|
||||
|
||||
type Tier = {
|
||||
id: string
|
||||
title: string
|
||||
title_en: string
|
||||
amount: string
|
||||
bonus_amount: string
|
||||
desc: string
|
||||
desc_en: string
|
||||
sort: number
|
||||
status: number
|
||||
_rowKey?: string
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const items = ref<Tier[]>([])
|
||||
|
||||
function genRowKey(): string {
|
||||
return 'r_' + Math.random().toString(36).slice(2, 10)
|
||||
}
|
||||
|
||||
function emptyTier(): Tier {
|
||||
return {
|
||||
id: '',
|
||||
title: '',
|
||||
title_en: '',
|
||||
amount: '',
|
||||
bonus_amount: '0',
|
||||
desc: '',
|
||||
desc_en: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
_rowKey: genRowKey(),
|
||||
function formatAmount4(_row: anyObj, _column: any, cellValue: unknown) {
|
||||
if (cellValue === null || cellValue === undefined || cellValue === '') {
|
||||
return '-'
|
||||
}
|
||||
const s = String(cellValue).trim().replace(',', '.')
|
||||
const n = parseFloat(s)
|
||||
if (!Number.isFinite(n)) {
|
||||
return String(cellValue)
|
||||
}
|
||||
return n.toFixed(4)
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await createAxios({
|
||||
url: '/admin/config.DepositTier/index',
|
||||
method: 'get',
|
||||
})
|
||||
if (res.code === 1 && res.data) {
|
||||
const list = (res.data.items || []) as Tier[]
|
||||
items.value = (Array.isArray(list) ? list : []).map((it) => ({
|
||||
...emptyTier(),
|
||||
...it,
|
||||
_rowKey: genRowKey(),
|
||||
}))
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
function formatPayCell(row: anyObj, _column: any, cellValue: unknown) {
|
||||
const amt = formatAmount4(row, _column, cellValue)
|
||||
const cur = row.currency && String(row.currency).trim() !== '' ? String(row.currency).toUpperCase() : ''
|
||||
if (amt === '-' || cur === '') {
|
||||
return amt
|
||||
}
|
||||
return `${amt} ${cur}`
|
||||
}
|
||||
|
||||
function onAdd() {
|
||||
items.value.push(emptyTier())
|
||||
function formatTotalPlatform(row: anyObj) {
|
||||
const a = parseFloat(String(row.amount ?? '0').replace(',', '.'))
|
||||
const b = parseFloat(String(row.bonus_amount ?? '0').replace(',', '.'))
|
||||
const base = Number.isFinite(a) ? a : 0
|
||||
const bonus = Number.isFinite(b) ? b : 0
|
||||
return (base + bonus).toFixed(4)
|
||||
}
|
||||
|
||||
async function onRemove(idx: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm(t('config.depositTier.confirm_remove'), t('Warning'), {
|
||||
type: 'warning',
|
||||
confirmButtonText: t('Delete'),
|
||||
cancelButtonText: t('Cancel'),
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
items.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
if (!auth('save')) {
|
||||
return
|
||||
}
|
||||
for (let i = 0; i < items.value.length; i++) {
|
||||
const row = items.value[i]
|
||||
if (!row.title || !row.title.trim()) {
|
||||
ElMessage.warning(t('config.depositTier.err_title', { no: i + 1 }))
|
||||
return
|
||||
}
|
||||
const amount = Number(row.amount)
|
||||
if (!row.amount || Number.isNaN(amount) || amount <= 0) {
|
||||
ElMessage.warning(t('config.depositTier.err_amount', { no: i + 1 }))
|
||||
return
|
||||
}
|
||||
const bonusRaw = row.bonus_amount === '' || row.bonus_amount === null || row.bonus_amount === undefined ? '0' : row.bonus_amount
|
||||
const bonus = Number(bonusRaw)
|
||||
if (Number.isNaN(bonus) || bonus < 0) {
|
||||
ElMessage.warning(t('config.depositTier.err_bonus', { no: i + 1 }))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
await createAxios({
|
||||
url: '/admin/config.DepositTier/save',
|
||||
method: 'post',
|
||||
data: {
|
||||
items: items.value.map((r) => ({
|
||||
id: r.id,
|
||||
title: r.title,
|
||||
title_en: r.title_en || '',
|
||||
amount: r.amount,
|
||||
bonus_amount: r.bonus_amount === '' || r.bonus_amount === null || r.bonus_amount === undefined ? '0' : r.bonus_amount,
|
||||
desc: r.desc || '',
|
||||
desc_en: r.desc_en || '',
|
||||
sort: r.sort,
|
||||
status: r.status,
|
||||
})),
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/config.DepositTier/'),
|
||||
{
|
||||
pk: 'id',
|
||||
filter: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
},
|
||||
defaultOrder: { prop: 'sort', order: 'asc' },
|
||||
column: [
|
||||
{ type: 'selection', align: 'center', operator: false },
|
||||
{
|
||||
label: t('config.depositTier.sort'),
|
||||
prop: 'sort',
|
||||
align: 'center',
|
||||
width: 88,
|
||||
operator: 'RANGE',
|
||||
sortable: 'custom',
|
||||
},
|
||||
showSuccessMessage: true,
|
||||
})
|
||||
await load()
|
||||
} finally {
|
||||
saving.value = false
|
||||
{
|
||||
label: t('config.depositTier.status'),
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
sortable: false,
|
||||
render: 'switch',
|
||||
replaceValue: { 0: t('config.depositTier.status_off'), 1: t('config.depositTier.status_on') },
|
||||
},
|
||||
{
|
||||
label: t('config.depositTier.tier_id'),
|
||||
prop: 'id',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('config.depositTier.title_col'),
|
||||
prop: 'title',
|
||||
align: 'center',
|
||||
minWidth: 140,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('config.depositTier.title_en_col'),
|
||||
prop: 'title_en',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('config.depositTier.pay_currency'),
|
||||
prop: 'currency',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
},
|
||||
{
|
||||
label: t('config.depositTier.pay_amount'),
|
||||
prop: 'pay_amount',
|
||||
align: 'center',
|
||||
minWidth: 130,
|
||||
operator: 'RANGE',
|
||||
formatter: formatPayCell,
|
||||
},
|
||||
{
|
||||
label: t('config.depositTier.platform_base'),
|
||||
prop: 'amount',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
operator: 'RANGE',
|
||||
formatter: formatAmount4,
|
||||
},
|
||||
{
|
||||
label: t('config.depositTier.bonus_amount'),
|
||||
prop: 'bonus_amount',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operator: 'RANGE',
|
||||
formatter: formatAmount4,
|
||||
},
|
||||
{
|
||||
label: t('config.depositTier.total_platform'),
|
||||
prop: '__total_platform',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
operator: false,
|
||||
formatter: (row: anyObj) => formatTotalPlatform(row),
|
||||
},
|
||||
{
|
||||
label: t('config.depositTier.desc_col'),
|
||||
prop: 'desc',
|
||||
align: 'center',
|
||||
minWidth: 160,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('Operate'),
|
||||
align: 'center',
|
||||
width: 100,
|
||||
render: 'buttons',
|
||||
buttons: optButtons,
|
||||
operator: false,
|
||||
fixed: 'right',
|
||||
},
|
||||
],
|
||||
dblClickNotEditColumn: [undefined, 'status'],
|
||||
},
|
||||
{
|
||||
defaultItems: {
|
||||
id: '',
|
||||
title: '',
|
||||
title_en: '',
|
||||
currency: 'MYR',
|
||||
pay_amount: '',
|
||||
amount: '',
|
||||
bonus_amount: '0',
|
||||
desc: '',
|
||||
desc_en: '',
|
||||
sort: 10,
|
||||
status: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
void load()
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
baTable.initSort()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.deposit-tier-page {
|
||||
.toolbar {
|
||||
margin: 12px 0;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.currency {
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tier-id {
|
||||
display: inline-block;
|
||||
max-width: 120px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.deposit-tier-table {
|
||||
:deep(.el-table__header th),
|
||||
:deep(.el-table__body td) {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
152
web/src/views/backend/config/depositTier/popupForm.vue
Normal file
152
web/src/views/backend/config/depositTier/popupForm.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="ba-operate-dialog"
|
||||
:close-on-click-modal="false"
|
||||
:model-value="['Add', 'Edit'].includes(baTable.form.operate!)"
|
||||
@close="baTable.toggleForm"
|
||||
>
|
||||
<template #header>
|
||||
<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
|
||||
{{ baTable.form.operate ? t(baTable.form.operate) : '' }}
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar v-loading="baTable.form.loading" class="ba-table-form-scrollbar">
|
||||
<div
|
||||
class="ba-operate-form"
|
||||
:class="'ba-' + baTable.form.operate + '-form'"
|
||||
:style="config.layout.shrink ? '' : 'width: calc(100% - ' + baTable.form.labelWidth! / 2 + 'px)'"
|
||||
>
|
||||
<el-form
|
||||
v-if="!baTable.form.loading"
|
||||
ref="formRef"
|
||||
@submit.prevent=""
|
||||
@keyup.enter="baTable.onSubmit(formRef)"
|
||||
:model="baTable.form.items"
|
||||
:label-position="config.layout.shrink ? 'top' : 'right'"
|
||||
:label-width="baTable.form.labelWidth + 'px'"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-alert class="ba-table-alert" type="info" :closable="false" show-icon>
|
||||
{{ t('config.depositTier.form_tip') }}
|
||||
</el-alert>
|
||||
|
||||
<el-form-item v-if="baTable.form.operate === 'Edit'" :label="t('config.depositTier.tier_id')" prop="id">
|
||||
<el-input :model-value="String(baTable.form.items!.id ?? '')" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="baTable.form.operate === 'Add'" :label="t('config.depositTier.tier_id_optional')" prop="id">
|
||||
<el-input v-model="baTable.form.items!.id" clearable :placeholder="t('config.depositTier.tier_id_optional_ph')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('config.depositTier.sort')" prop="sort">
|
||||
<el-input-number v-model="baTable.form.items!.sort" :min="0" :max="9999" :controls="true" class="w100" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.depositTier.status')" prop="status">
|
||||
<el-switch v-model="baTable.form.items!.status" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('config.depositTier.title_col')" prop="title">
|
||||
<el-input v-model="baTable.form.items!.title" maxlength="64" :placeholder="t('config.depositTier.title_ph')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.depositTier.title_en_col')" prop="title_en">
|
||||
<el-input v-model="baTable.form.items!.title_en" maxlength="64" :placeholder="t('config.depositTier.title_en_ph')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('config.depositTier.pay_currency')" prop="currency">
|
||||
<el-select v-model="baTable.form.items!.currency" filterable allow-create default-first-option class="w100" :placeholder="t('config.depositTier.pay_currency_ph')">
|
||||
<el-option v-for="c in payCurrencies" :key="c" :label="c" :value="c" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.depositTier.pay_amount')" prop="pay_amount">
|
||||
<el-input v-model="baTable.form.items!.pay_amount" :placeholder="t('config.depositTier.pay_amount_ph')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.depositTier.platform_base')" prop="amount">
|
||||
<el-input v-model="baTable.form.items!.amount" :placeholder="t('config.depositTier.platform_base_ph')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.depositTier.bonus_amount')" prop="bonus_amount">
|
||||
<el-input v-model="baTable.form.items!.bonus_amount" :placeholder="t('config.depositTier.bonus_ph')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.depositTier.desc_col')" prop="desc">
|
||||
<el-input v-model="baTable.form.items!.desc" type="textarea" :rows="2" maxlength="255" :placeholder="t('config.depositTier.desc_ph')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.depositTier.desc_en_col')" prop="desc_en">
|
||||
<el-input v-model="baTable.form.items!.desc_en" type="textarea" :rows="2" maxlength="255" :placeholder="t('config.depositTier.desc_en_ph')" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<div :style="'width: calc(100% - ' + baTable.form.labelWidth! / 1.8 + 'px)'">
|
||||
<el-button @click="baTable.toggleForm()">{{ t('Cancel') }}</el-button>
|
||||
<el-button v-blur :loading="baTable.form.submitLoading" @click="baTable.onSubmit(formRef)" type="primary">
|
||||
{{ t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormItemRule } from 'element-plus'
|
||||
import { inject, reactive, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
|
||||
const config = useConfig()
|
||||
const formRef = useTemplateRef<FormInstance>('formRef')
|
||||
const baTable = inject('baTable') as baTable
|
||||
const { t } = useI18n()
|
||||
|
||||
const payCurrencies = ['MYR', 'CNY', 'USD', 'USDT', 'VND', 'THB', 'SGD', 'IDR']
|
||||
|
||||
function positiveAmountRule(msg: string): FormItemRule {
|
||||
return {
|
||||
validator: (_rule, val, callback) => {
|
||||
const s = val === null || val === undefined ? '' : String(val).trim().replace(',', '.')
|
||||
if (s === '' || !/^-?\d+(\.\d+)?$/.test(s)) {
|
||||
callback(new Error(msg))
|
||||
return
|
||||
}
|
||||
const n = parseFloat(s)
|
||||
if (!Number.isFinite(n) || n <= 0) {
|
||||
callback(new Error(msg))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur',
|
||||
}
|
||||
}
|
||||
|
||||
function nonNegAmountRule(msg: string): FormItemRule {
|
||||
return {
|
||||
validator: (_rule, val, callback) => {
|
||||
const s = val === null || val === undefined || val === '' ? '0' : String(val).trim().replace(',', '.')
|
||||
if (!/^-?\d+(\.\d+)?$/.test(s)) {
|
||||
callback(new Error(msg))
|
||||
return
|
||||
}
|
||||
const n = parseFloat(s)
|
||||
if (!Number.isFinite(n) || n < 0) {
|
||||
callback(new Error(msg))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur',
|
||||
}
|
||||
}
|
||||
|
||||
const rules = reactive({
|
||||
title: [{ required: true, message: t('config.depositTier.rule_title'), trigger: 'blur' }],
|
||||
currency: [{ required: true, message: t('config.depositTier.rule_currency'), trigger: 'change' }],
|
||||
pay_amount: [positiveAmountRule(t('config.depositTier.rule_pay_amount'))],
|
||||
amount: [positiveAmountRule(t('config.depositTier.rule_platform_base'))],
|
||||
bonus_amount: [nonNegAmountRule(t('config.depositTier.rule_bonus'))],
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.w100 {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
518
web/src/views/backend/config/financeCashierConfig/index.vue
Normal file
518
web/src/views/backend/config/financeCashierConfig/index.vue
Normal file
@@ -0,0 +1,518 @@
|
||||
<template>
|
||||
<div class="default-main finance-cashier-page">
|
||||
<el-alert type="info" :closable="false" show-icon class="mb-3">
|
||||
{{ t('config.financeCashierConfig.desc') }}
|
||||
</el-alert>
|
||||
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" :loading="saving" :disabled="loading" @click="onSave">
|
||||
{{ t('config.financeCashierConfig.btn_save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-scrollbar max-height="calc(100vh - 220px)">
|
||||
<el-form v-loading="loading" label-width="160px" class="finance-cashier-form">
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>{{ t('config.financeCashierConfig.sec_platform') }}</template>
|
||||
<el-form-item :label="t('config.financeCashierConfig.platform_label_zh')">
|
||||
<el-input v-model="form.platform_coin.label_zh" maxlength="32" class="w400" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.platform_label_en')">
|
||||
<el-input v-model="form.platform_coin.label_en" maxlength="32" class="w400" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>
|
||||
<span>{{ t('config.financeCashierConfig.sec_currencies') }}</span>
|
||||
<el-button type="primary" link class="ml-2" @click="addCurrency">{{ t('config.financeCashierConfig.btn_add_row') }}</el-button>
|
||||
</template>
|
||||
<p class="hint">{{ t('config.financeCashierConfig.currency_rates_hint') }}</p>
|
||||
<el-table :data="form.currencies" border stripe size="small">
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_code')" prop="code" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.code"
|
||||
maxlength="12"
|
||||
:placeholder="t('config.financeCashierConfig.ph_currency_code')"
|
||||
@blur="normalizeCurrencyCode(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_label_zh')" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.label_zh" maxlength="64" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_label_en')" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.label_en" maxlength="64" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_deposit_rate')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.deposit_coins_per_fiat" :placeholder="t('config.financeCashierConfig.ph_ratio')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_withdraw_rate')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.withdraw_coins_per_fiat" :placeholder="t('config.financeCashierConfig.ph_ratio')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_sort')" width="108">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.sort" :min="0" :max="99999" :controls="true" class="w100p" @change="resortCurrenciesInPlace" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('Operate')" width="90" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" link @click="removeRow(form.currencies, $index)">{{ t('Delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>{{ t('config.financeCashierConfig.sec_deposit_channels') }}</template>
|
||||
<p class="hint">{{ t('config.financeCashierConfig.deposit_channels_hint') }}</p>
|
||||
<el-table :data="form.channels" border stripe size="small">
|
||||
<el-table-column :label="t('config.financeCashierConfig.ch_code')" prop="code" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-input :model-value="String(row.code ?? '')" readonly />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.ch_display_name')" min-width="140">
|
||||
<template #default="{ row }">
|
||||
<el-input :model-value="channelDisplayName(String(row.code ?? ''))" readonly />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.ch_sort')" width="108">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.sort"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
:controls="true"
|
||||
class="w100p"
|
||||
@change="resortChannelsInPlace"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.ch_status')" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.status" :active-value="1" :inactive-value="0" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.ch_tier_ids')" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.tier_ids"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
filterable
|
||||
clearable
|
||||
class="w100p"
|
||||
:placeholder="t('config.financeCashierConfig.ch_tier_ids_ph')"
|
||||
>
|
||||
<el-option v-for="opt in tierOptions" :key="opt.id" :label="opt.label" :value="opt.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>
|
||||
<span>{{ t('config.financeCashierConfig.sec_banks') }}</span>
|
||||
<el-button type="primary" link class="ml-2" @click="addBank">{{ t('config.financeCashierConfig.btn_add_row') }}</el-button>
|
||||
</template>
|
||||
<el-table :data="form.withdraw_banks" border stripe size="small">
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_bank_code')" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.code" maxlength="32" :placeholder="t('config.financeCashierConfig.ph_bank_code')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_name_zh')" min-width="140">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.name_zh" maxlength="64" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_name_en')" min-width="140">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.name_en" maxlength="64" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.financeCashierConfig.col_sort')" width="108">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.sort" :min="0" :max="99999" :controls="true" class="w100p" @change="resortBanksInPlace" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('Operate')" width="90" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" link @click="removeRow(form.withdraw_banks, $index)">{{ t('Delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>{{ t('config.financeCashierConfig.sec_limits') }}</template>
|
||||
<el-form-item :label="t('config.financeCashierConfig.min_ewallet')">
|
||||
<el-input v-model="form.withdraw_limits.min_ewallet" class="w240" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.min_bank')">
|
||||
<el-input v-model="form.withdraw_limits.min_bank" class="w240" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>{{ t('config.financeCashierConfig.sec_copy') }}</template>
|
||||
<el-form-item :label="t('config.financeCashierConfig.rate_mode')">
|
||||
<el-select v-model="form.withdraw_copy.rate_mode" class="w240">
|
||||
<el-option :label="t('config.financeCashierConfig.rate_mode_fixed')" value="fixed" />
|
||||
<el-option :label="t('config.financeCashierConfig.rate_mode_live')" value="live" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.rate_hint_zh')">
|
||||
<el-input v-model="form.withdraw_copy.rate_hint_zh" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.rate_hint_en')">
|
||||
<el-input v-model="form.withdraw_copy.rate_hint_en" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.processing_zh')">
|
||||
<el-input v-model="form.withdraw_copy.processing_zh" maxlength="255" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.processing_en')">
|
||||
<el-input v-model="form.withdraw_copy.processing_en" maxlength="255" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.fee_note_zh')">
|
||||
<el-input v-model="form.withdraw_copy.fee_note_zh" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.fee_note_en')">
|
||||
<el-input v-model="form.withdraw_copy.fee_note_en" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>{{ t('config.financeCashierConfig.sec_fields') }}</template>
|
||||
<el-form-item :label="t('config.financeCashierConfig.field_cardholder')">
|
||||
<el-switch v-model="form.withdraw_fields.require_cardholder" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.field_bank_account')">
|
||||
<el-switch v-model="form.withdraw_fields.require_bank_account" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.field_email')">
|
||||
<el-switch v-model="form.withdraw_fields.require_email" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('config.financeCashierConfig.field_mobile')">
|
||||
<el-switch v-model="form.withdraw_fields.require_mobile" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import createAxios from '/@/utils/axios'
|
||||
import { auth } from '/@/utils/common'
|
||||
|
||||
defineOptions({
|
||||
name: 'config/financeCashierConfig',
|
||||
})
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
type CurrencyRow = {
|
||||
code: string
|
||||
label_zh: string
|
||||
label_en: string
|
||||
sort: number
|
||||
deposit_coins_per_fiat: string
|
||||
withdraw_coins_per_fiat: string
|
||||
}
|
||||
type BankRow = { code: string; name_zh: string; name_en: string; sort: number }
|
||||
type ChannelRow = { code: string; sort: number; status: number; tier_ids: string[] }
|
||||
type TierOpt = { id: string; label: string }
|
||||
type RegistryMeta = { name?: string; name_en?: string; sort?: number }
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const registry = ref<Record<string, RegistryMeta>>({})
|
||||
const tierOptions = ref<TierOpt[]>([])
|
||||
|
||||
const form = reactive({
|
||||
platform_coin: { label_zh: '', label_en: '' },
|
||||
currencies: [] as CurrencyRow[],
|
||||
withdraw_banks: [] as BankRow[],
|
||||
withdraw_limits: { min_ewallet: '0', min_bank: '0' },
|
||||
withdraw_copy: {
|
||||
rate_hint_zh: '',
|
||||
rate_hint_en: '',
|
||||
processing_zh: '',
|
||||
processing_en: '',
|
||||
fee_note_zh: '',
|
||||
fee_note_en: '',
|
||||
rate_mode: 'fixed',
|
||||
},
|
||||
withdraw_fields: {
|
||||
require_cardholder: true,
|
||||
require_bank_account: true,
|
||||
require_email: true,
|
||||
require_mobile: true,
|
||||
},
|
||||
channels: [] as ChannelRow[],
|
||||
})
|
||||
|
||||
function rowSortValue(row: { sort?: unknown }): number {
|
||||
const s = row.sort
|
||||
if (typeof s === 'number' && Number.isFinite(s)) {
|
||||
return Math.round(s)
|
||||
}
|
||||
if (typeof s === 'string' && s.trim() !== '') {
|
||||
const n = Number(s)
|
||||
if (!Number.isNaN(n) && Number.isFinite(n)) {
|
||||
return Math.round(n)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function nextSort(rows: { sort?: unknown }[]): number {
|
||||
let m = 0
|
||||
for (const r of rows) {
|
||||
const v = rowSortValue(r)
|
||||
if (v > m) {
|
||||
m = v
|
||||
}
|
||||
}
|
||||
return m > 0 ? m + 10 : 10
|
||||
}
|
||||
|
||||
function addCurrency() {
|
||||
form.currencies.push({
|
||||
code: '',
|
||||
label_zh: '',
|
||||
label_en: '',
|
||||
sort: nextSort(form.currencies),
|
||||
deposit_coins_per_fiat: '100',
|
||||
withdraw_coins_per_fiat: '100',
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeCurrencyCode(row: CurrencyRow) {
|
||||
row.code = String(row.code || '').trim().toUpperCase()
|
||||
}
|
||||
|
||||
function resortCurrenciesInPlace() {
|
||||
form.currencies.sort((a, b) => {
|
||||
const ds = rowSortValue(a) - rowSortValue(b)
|
||||
if (ds !== 0) {
|
||||
return ds
|
||||
}
|
||||
return String(a.code || '').localeCompare(String(b.code || ''))
|
||||
})
|
||||
}
|
||||
|
||||
function resortBanksInPlace() {
|
||||
form.withdraw_banks.sort((a, b) => {
|
||||
const ds = rowSortValue(a) - rowSortValue(b)
|
||||
if (ds !== 0) {
|
||||
return ds
|
||||
}
|
||||
return String(a.code || '').localeCompare(String(b.code || ''))
|
||||
})
|
||||
}
|
||||
|
||||
function resortChannelsInPlace() {
|
||||
form.channels.sort((a, b) => {
|
||||
const ds = rowSortValue(a) - rowSortValue(b)
|
||||
if (ds !== 0) {
|
||||
return ds
|
||||
}
|
||||
return String(a.code || '').localeCompare(String(b.code || ''))
|
||||
})
|
||||
}
|
||||
|
||||
function channelDisplayName(code: string): string {
|
||||
const r = registry.value[code]
|
||||
if (!r) {
|
||||
return code
|
||||
}
|
||||
const loc = String(locale.value ?? '').toLowerCase().replaceAll('_', '-')
|
||||
const preferEn = loc === 'en' || loc.startsWith('en-')
|
||||
if (preferEn) {
|
||||
const ne = r.name_en
|
||||
if (typeof ne === 'string' && ne !== '') {
|
||||
return ne
|
||||
}
|
||||
}
|
||||
const n = r.name
|
||||
if (typeof n === 'string' && n !== '') {
|
||||
return n
|
||||
}
|
||||
const ne = r.name_en
|
||||
if (typeof ne === 'string' && ne !== '') {
|
||||
return ne
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
function normalizeChannelRow(c: Record<string, unknown>): ChannelRow {
|
||||
const tierIds: string[] = []
|
||||
if (Array.isArray(c.tier_ids)) {
|
||||
for (const x of c.tier_ids) {
|
||||
if (typeof x === 'string') {
|
||||
const t = x.trim()
|
||||
if (t !== '') {
|
||||
tierIds.push(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const st = c.status
|
||||
const statusOn = st === 1 || st === true || st === '1'
|
||||
return {
|
||||
code: typeof c.code === 'string' ? c.code : '',
|
||||
sort: rowSortValue({ sort: c.sort }),
|
||||
status: statusOn ? 1 : 0,
|
||||
tier_ids: tierIds,
|
||||
}
|
||||
}
|
||||
|
||||
function addBank() {
|
||||
form.withdraw_banks.push({ code: '', name_zh: '', name_en: '', sort: nextSort(form.withdraw_banks) })
|
||||
}
|
||||
|
||||
function removeRow<T>(arr: T[], index: number) {
|
||||
arr.splice(index, 1)
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await createAxios({
|
||||
url: '/admin/config.FinanceCashierConfig/index',
|
||||
method: 'get',
|
||||
})
|
||||
if (res.code === 1 && res.data && res.data.form) {
|
||||
const f = res.data.form as typeof form
|
||||
const regRaw = res.data.registry
|
||||
registry.value =
|
||||
regRaw !== null && typeof regRaw === 'object' && !Array.isArray(regRaw)
|
||||
? (regRaw as Record<string, RegistryMeta>)
|
||||
: {}
|
||||
const topts = res.data.tier_options
|
||||
tierOptions.value = Array.isArray(topts) ? (topts as TierOpt[]) : []
|
||||
Object.assign(form.platform_coin, f.platform_coin || {})
|
||||
const curList = Array.isArray(f.currencies) ? f.currencies : []
|
||||
const normalized: CurrencyRow[] = curList.map((c: Record<string, unknown>) => ({
|
||||
code: typeof c.code === 'string' ? c.code : '',
|
||||
label_zh: typeof c.label_zh === 'string' ? c.label_zh : '',
|
||||
label_en: typeof c.label_en === 'string' ? c.label_en : '',
|
||||
sort: rowSortValue({ sort: c.sort }),
|
||||
deposit_coins_per_fiat:
|
||||
typeof c.deposit_coins_per_fiat === 'string'
|
||||
? c.deposit_coins_per_fiat
|
||||
: typeof c.deposit_coins_per_fiat === 'number'
|
||||
? String(c.deposit_coins_per_fiat)
|
||||
: '100',
|
||||
withdraw_coins_per_fiat:
|
||||
typeof c.withdraw_coins_per_fiat === 'string'
|
||||
? c.withdraw_coins_per_fiat
|
||||
: typeof c.withdraw_coins_per_fiat === 'number'
|
||||
? String(c.withdraw_coins_per_fiat)
|
||||
: '100',
|
||||
}))
|
||||
form.currencies.splice(0, form.currencies.length, ...normalized)
|
||||
resortCurrenciesInPlace()
|
||||
const bankList = Array.isArray(f.withdraw_banks) ? f.withdraw_banks : []
|
||||
const banksNorm: BankRow[] = bankList.map((b: Record<string, unknown>) => ({
|
||||
code: typeof b.code === 'string' ? b.code : '',
|
||||
name_zh: typeof b.name_zh === 'string' ? b.name_zh : '',
|
||||
name_en: typeof b.name_en === 'string' ? b.name_en : '',
|
||||
sort: rowSortValue({ sort: b.sort }),
|
||||
}))
|
||||
form.withdraw_banks.splice(0, form.withdraw_banks.length, ...banksNorm)
|
||||
resortBanksInPlace()
|
||||
const chList = Array.isArray(f.channels) ? f.channels : []
|
||||
const channelsNorm: ChannelRow[] = chList.map((c) => normalizeChannelRow(c as Record<string, unknown>))
|
||||
form.channels.splice(0, form.channels.length, ...channelsNorm)
|
||||
resortChannelsInPlace()
|
||||
Object.assign(form.withdraw_limits, f.withdraw_limits || {})
|
||||
Object.assign(form.withdraw_copy, f.withdraw_copy || {})
|
||||
Object.assign(form.withdraw_fields, f.withdraw_fields || {})
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
if (!auth('save')) {
|
||||
return
|
||||
}
|
||||
for (const row of form.currencies) {
|
||||
normalizeCurrencyCode(row)
|
||||
}
|
||||
const codes = form.currencies.map((c) => String(c.code || '').trim().toUpperCase()).filter((c) => c !== '')
|
||||
if (new Set(codes).size !== codes.length) {
|
||||
ElMessage.warning(t('config.financeCashierConfig.err_dup_code'))
|
||||
return
|
||||
}
|
||||
resortCurrenciesInPlace()
|
||||
resortBanksInPlace()
|
||||
resortChannelsInPlace()
|
||||
saving.value = true
|
||||
try {
|
||||
await createAxios({
|
||||
url: '/admin/config.FinanceCashierConfig/save',
|
||||
method: 'post',
|
||||
data: JSON.parse(JSON.stringify(form)),
|
||||
showSuccessMessage: true,
|
||||
})
|
||||
await load()
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-cashier-page {
|
||||
.toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.section-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.hint {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.w400 {
|
||||
max-width: 400px;
|
||||
}
|
||||
.w240 {
|
||||
max-width: 240px;
|
||||
}
|
||||
.w100p {
|
||||
width: 100%;
|
||||
}
|
||||
.mb-3 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.ml-2 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
214
web/src/views/backend/order/depositChannelOrder/index.vue
Normal file
214
web/src/views/backend/order/depositChannelOrder/index.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('order.depositChannelOrder.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PopupForm from '../depositOrder/popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'order/depositChannelOrder',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit'])
|
||||
|
||||
function formatAmount(_row: anyObj, _column: any, cellValue: unknown) {
|
||||
if (cellValue === null || cellValue === undefined || cellValue === '') {
|
||||
return '-'
|
||||
}
|
||||
const s = String(cellValue).trim().replace(',', '.')
|
||||
const n = parseFloat(s)
|
||||
if (!Number.isFinite(n)) {
|
||||
return String(cellValue)
|
||||
}
|
||||
return n.toFixed(2)
|
||||
}
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/order.DepositChannelOrder/'),
|
||||
{
|
||||
pk: 'id',
|
||||
column: [
|
||||
{ type: 'selection', align: 'center', operator: false },
|
||||
{ label: t('order.depositOrder.id'), prop: 'id', align: 'center', width: 80, operator: 'RANGE', sortable: 'custom' },
|
||||
{
|
||||
label: t('order.depositOrder.order_no'),
|
||||
prop: 'order_no',
|
||||
align: 'center',
|
||||
minWidth: 170,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.user_username'),
|
||||
prop: 'user.username',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
render: 'tags',
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.channel_name'),
|
||||
prop: 'channel.name',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
render: 'tags',
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.amount'),
|
||||
prop: 'amount',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operator: 'RANGE',
|
||||
formatter: formatAmount,
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.bonus_amount'),
|
||||
prop: 'bonus_amount',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operator: 'RANGE',
|
||||
formatter: formatAmount,
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.status'),
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
render: 'tag',
|
||||
effect: 'dark',
|
||||
custom: {
|
||||
'0': 'info',
|
||||
'1': 'success',
|
||||
'2': 'danger',
|
||||
'3': 'warning',
|
||||
},
|
||||
replaceValue: {
|
||||
'0': t('order.depositOrder.status 0'),
|
||||
'1': t('order.depositOrder.status 1'),
|
||||
'2': t('order.depositOrder.status 2'),
|
||||
'3': t('order.depositOrder.status 3'),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.pay_channel'),
|
||||
prop: 'pay_channel',
|
||||
align: 'center',
|
||||
minWidth: 130,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.deposit_tier_id'),
|
||||
prop: 'deposit_tier_id',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.pay_time'),
|
||||
prop: 'pay_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
operator: 'RANGE',
|
||||
comSearchRender: 'datetime',
|
||||
sortable: 'custom',
|
||||
width: 170,
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.idempotency_key'),
|
||||
prop: 'idempotency_key',
|
||||
align: 'center',
|
||||
minWidth: 170,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
showOverflowTooltip: true,
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.remark'),
|
||||
prop: 'remark',
|
||||
align: 'center',
|
||||
minWidth: 150,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.create_time'),
|
||||
prop: 'create_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
operator: 'RANGE',
|
||||
comSearchRender: 'datetime',
|
||||
sortable: 'custom',
|
||||
width: 170,
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.update_time'),
|
||||
prop: 'update_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
operator: 'RANGE',
|
||||
comSearchRender: 'datetime',
|
||||
sortable: 'custom',
|
||||
width: 170,
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: t('Operate'),
|
||||
align: 'center',
|
||||
width: 90,
|
||||
render: 'buttons',
|
||||
buttons: optButtons,
|
||||
operator: false,
|
||||
fixed: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
defaultItems: { status: 0, amount: '0.0000', bonus_amount: '0.0000' },
|
||||
}
|
||||
)
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
baTable.initSort()
|
||||
baTable.dragSort()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
Reference in New Issue
Block a user