1.新增充值档位配置

2.新增充值/提现配置
This commit is contained in:
2026-04-21 18:31:43 +08:00
parent aad00e10f8
commit 0f28c0fd2a
29 changed files with 3647 additions and 278 deletions

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>