feat: 支付商户
This commit is contained in:
@@ -0,0 +1,871 @@
|
||||
<template>
|
||||
<div class="pool-management">
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchItems"
|
||||
:show-expand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<ArtTableHeader
|
||||
:columnList="columnOptions"
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="loadPools"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton v-if="canManage" type="primary" :icon="Plus" @click="showCreateDrawer">
|
||||
新增商户池
|
||||
</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="pools"
|
||||
:currentPage="pagination.page"
|
||||
:pageSize="pagination.page_size"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:actions="getActions"
|
||||
:actionsWidth="220"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
|
||||
<ElDrawer
|
||||
v-model="formDrawerVisible"
|
||||
:title="formMode === 'create' ? '新增商户池' : '编辑商户池'"
|
||||
size="780px"
|
||||
destroy-on-close
|
||||
@closed="resetForm"
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="formRules" label-width="120px">
|
||||
<ElFormItem label="商户池名称" prop="name">
|
||||
<ElInput v-model="form.name" maxlength="100" placeholder="请输入商户池名称" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="支付方式" prop="payment_method">
|
||||
<ElSelect
|
||||
v-model="form.payment_method"
|
||||
:disabled="formMode === 'edit'"
|
||||
style="width: 100%"
|
||||
@change="handlePaymentMethodChange"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in PAYMENT_METHOD_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="成员商户" prop="member_ids">
|
||||
<VueDraggable
|
||||
v-if="orderedMemberIds.length"
|
||||
v-model="orderedMemberIds"
|
||||
:animation="150"
|
||||
handle=".drag-handle"
|
||||
class="pool-member-list"
|
||||
>
|
||||
<div v-for="memberId in orderedMemberIds" :key="memberId" class="pool-member-row">
|
||||
<ElIcon class="drag-handle"><Rank /></ElIcon>
|
||||
<span class="member-name">{{ getMemberName(memberId) }}</span>
|
||||
<ElButton type="danger" link :icon="Delete" @click="removeMember(memberId)" />
|
||||
</div>
|
||||
</VueDraggable>
|
||||
<div v-else class="pool-member-empty">尚未选择成员,请从下方选择</div>
|
||||
<ElDivider />
|
||||
<ElSelect
|
||||
v-model="pendingMemberId"
|
||||
filterable
|
||||
placeholder="选择同支付方式的商户"
|
||||
style="width: 100%"
|
||||
:disabled="!form.payment_method"
|
||||
@change="appendMember"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in availableMerchantOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
:disabled="form.member_ids.includes(item.id)"
|
||||
/>
|
||||
</ElSelect>
|
||||
<div v-if="memberError" class="field-error" role="alert">{{ memberError }}</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="轮询策略" prop="strategy">
|
||||
<ElSelect v-model="form.strategy" style="width: 100%">
|
||||
<ElOption
|
||||
v-for="item in PAYMENT_POOL_STRATEGY_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="form.strategy === 'amount' || form.strategy === 'count'"
|
||||
label="统计周期"
|
||||
prop="statistic_cycle"
|
||||
>
|
||||
<ElSelect v-model="form.statistic_cycle" style="width: 100%">
|
||||
<ElOption
|
||||
v-for="item in PAYMENT_STATISTIC_CYCLE_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="form.strategy === 'amount'"
|
||||
label="金额阈值(元)"
|
||||
prop="threshold_amount_yuan"
|
||||
>
|
||||
<ElInputNumber
|
||||
v-model="form.threshold_amount_yuan"
|
||||
:min="0.01"
|
||||
:precision="2"
|
||||
:step="100"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-else-if="form.strategy === 'count'" label="笔数阈值" prop="threshold_count">
|
||||
<ElInputNumber
|
||||
v-model="form.threshold_count"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<template v-else-if="form.strategy === 'time'">
|
||||
<ElFormItem label="时间单位" prop="time_period_unit">
|
||||
<ElSelect v-model="form.time_period_unit" style="width: 100%">
|
||||
<ElOption
|
||||
v-for="item in PAYMENT_TIME_PERIOD_UNIT_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="时间长度" prop="time_period_value">
|
||||
<ElInputNumber
|
||||
v-model="form.time_period_value"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="时间起点" prop="time_period_started_at">
|
||||
<ElDatePicker
|
||||
v-model="form.time_period_started_at"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择时间起点"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<ElFormItem label="启停状态">
|
||||
<ElSwitch v-model="form.enabled" active-text="启用" inactive-text="停用" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="备注">
|
||||
<ElInput
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="formDrawerVisible = false">取消</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
:disabled="!canManage"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
保存
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDrawer>
|
||||
|
||||
<ElDrawer v-model="detailDrawerVisible" title="商户池详情" size="640px">
|
||||
<ElDescriptions v-if="detail" :column="1" border>
|
||||
<ElDescriptionsItem label="商户池名称">{{ detail.name }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="支付方式">{{
|
||||
getPaymentMethodLabel(detail.payment_method)
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="成员数量">{{ detail.member_ids.length }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="轮询策略">{{
|
||||
getPaymentPoolStrategyLabel(detail.strategy)
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem
|
||||
v-if="detail.strategy === 'amount' || detail.strategy === 'count'"
|
||||
label="统计周期"
|
||||
>
|
||||
{{ getPaymentStatisticCycleLabel(detail.statistic_cycle) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="detail.strategy === 'amount'" label="金额阈值">
|
||||
{{ (Number(detail.threshold_amount) / 100).toFixed(2) }} 元
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="detail.strategy === 'count'" label="笔数阈值">
|
||||
{{ detail.threshold_count }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="detail.strategy === 'time'" label="时间周期">
|
||||
{{ detail.time_period_value }}
|
||||
{{ getPaymentTimePeriodUnitLabel(detail.time_period_unit) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="启停状态">
|
||||
{{ detail.enabled ? '启用' : '停用' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="路由世代">v{{ detail.routing_epoch }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="备注">{{ detail.remark || '-' }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="更新时间">{{
|
||||
formatDateTime(detail.updated_at)
|
||||
}}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElDrawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import { Delete, Plus, Rank } from '@element-plus/icons-vue'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import { ElButton, ElMessage, ElMessageBox, ElTag } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { PaymentMerchantPoolsService } from '@/api/modules'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { usePermission } from '@/composables/usePermission'
|
||||
import type { SearchFormItem } from '@/types/component'
|
||||
import {
|
||||
PAYMENT_METHOD_OPTIONS,
|
||||
PAYMENT_POOL_STRATEGY_OPTIONS,
|
||||
PAYMENT_STATISTIC_CYCLE_OPTIONS,
|
||||
PAYMENT_TIME_PERIOD_UNIT_OPTIONS,
|
||||
getPaymentMethodLabel,
|
||||
getPaymentPoolStrategyLabel,
|
||||
getPaymentStatisticCycleLabel,
|
||||
getPaymentTimePeriodUnitLabel,
|
||||
type PaymentMerchantMethod,
|
||||
type PaymentMerchantPool,
|
||||
type PaymentMerchantPoolPageResult,
|
||||
type PaymentMerchantPoolPayload,
|
||||
type PaymentMerchantPoolMemberOption,
|
||||
type PaymentPoolStrategy
|
||||
} from '@/types/api/paymentMerchantPools'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
|
||||
defineOptions({ name: 'PaymentMerchantPoolManagement' })
|
||||
|
||||
type FilterVo = string | number | undefined | null | unknown[]
|
||||
|
||||
const { isPlatformAccount } = usePermission()
|
||||
const canManage = computed(() => isPlatformAccount.value)
|
||||
|
||||
const searchForm = reactive<Record<string, FilterVo>>({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
payment_method: null
|
||||
})
|
||||
|
||||
const pools = ref<PaymentMerchantPool[]>([])
|
||||
const pagination = reactive({ page: 1, page_size: 10, total: 0 })
|
||||
const loading = ref(false)
|
||||
|
||||
const searchItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '支付方式',
|
||||
prop: 'payment_method',
|
||||
type: 'select',
|
||||
options: PAYMENT_METHOD_OPTIONS.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value
|
||||
})),
|
||||
config: { clearable: true }
|
||||
}
|
||||
]
|
||||
|
||||
const columnOptions = [
|
||||
{ label: '商户池名称', prop: 'name', minWidth: 180 },
|
||||
{
|
||||
label: '支付方式',
|
||||
prop: 'payment_method',
|
||||
width: 110,
|
||||
formatter: (row: PaymentMerchantPool) => getPaymentMethodLabel(row.payment_method)
|
||||
},
|
||||
{
|
||||
label: '成员数量',
|
||||
prop: 'member_ids',
|
||||
width: 110,
|
||||
formatter: (row: PaymentMerchantPool) => `${row.member_ids.length} 个`
|
||||
},
|
||||
{
|
||||
label: '启停状态',
|
||||
prop: 'enabled',
|
||||
width: 110,
|
||||
formatter: (row: PaymentMerchantPool) =>
|
||||
h(
|
||||
ElTag,
|
||||
{ type: row.enabled ? 'success' : 'info' },
|
||||
{ default: () => (row.enabled ? '启用' : '停用') }
|
||||
)
|
||||
},
|
||||
{
|
||||
label: '轮询策略',
|
||||
prop: 'strategy',
|
||||
width: 130,
|
||||
formatter: (row: PaymentMerchantPool) => getPaymentPoolStrategyLabel(row.strategy)
|
||||
},
|
||||
{
|
||||
label: '统计周期',
|
||||
prop: 'statistic_cycle',
|
||||
width: 110,
|
||||
formatter: (row: PaymentMerchantPool) =>
|
||||
row.strategy === 'time' ? '-' : getPaymentStatisticCycleLabel(row.statistic_cycle)
|
||||
},
|
||||
{
|
||||
label: '阈值',
|
||||
prop: 'threshold_summary',
|
||||
width: 150,
|
||||
formatter: (row: PaymentMerchantPool) => describeThreshold(row)
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updated_at',
|
||||
width: 170,
|
||||
formatter: (row: PaymentMerchantPool) => formatDateTime(row.updated_at)
|
||||
}
|
||||
]
|
||||
|
||||
const { columnChecks, columns } = useCheckedColumns(() => columnOptions)
|
||||
|
||||
const describeThreshold = (row: PaymentMerchantPool): string => {
|
||||
if (row.strategy === 'amount') {
|
||||
return `${(Number(row.threshold_amount) / 100).toFixed(2)} 元`
|
||||
}
|
||||
if (row.strategy === 'count') {
|
||||
return `${row.threshold_count} 笔`
|
||||
}
|
||||
return `${row.time_period_value} ${getPaymentTimePeriodUnitLabel(row.time_period_unit)}`
|
||||
}
|
||||
|
||||
const loadPools = async () => {
|
||||
if (!canManage.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
payment_method: searchForm.payment_method as PaymentMerchantMethod | undefined
|
||||
}
|
||||
|
||||
const res = await PaymentMerchantPoolsService.getPaymentMerchantPools(params)
|
||||
if (res.code === 0) {
|
||||
const data = (res.data as PaymentMerchantPoolPageResult) || {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
size: 10
|
||||
}
|
||||
pools.value = data.items || []
|
||||
pagination.total = data.total || 0
|
||||
pagination.page = data.page || pagination.page
|
||||
pagination.page_size = data.size || pagination.page_size
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载商户池失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
loadPools()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.payment_method = null
|
||||
pagination.page = 1
|
||||
loadPools()
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.page_size = size
|
||||
pagination.page = 1
|
||||
loadPools()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page: number) => {
|
||||
pagination.page = page
|
||||
loadPools()
|
||||
}
|
||||
|
||||
// 候选成员
|
||||
const availableMerchants = ref<PaymentMerchantPoolMemberOption[]>([])
|
||||
|
||||
const loadAvailableMerchants = async (paymentMethod: PaymentMerchantMethod) => {
|
||||
try {
|
||||
const res = await PaymentMerchantPoolsService.getPaymentMerchants({
|
||||
page: 1,
|
||||
page_size: 100,
|
||||
payment_method: paymentMethod
|
||||
})
|
||||
if (res.code === 0) {
|
||||
const items = res.data?.items || []
|
||||
availableMerchants.value = items.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
payment_method: item.payment_method,
|
||||
enabled: item.enabled
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载候选商户失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const availableMerchantOptions = computed(() =>
|
||||
availableMerchants.value.filter((m) => m.payment_method === form.payment_method)
|
||||
)
|
||||
|
||||
const getMemberName = (id: number) =>
|
||||
availableMerchants.value.find((m) => m.id === id)?.name || `#${id}`
|
||||
|
||||
// 表单
|
||||
type FormMode = 'create' | 'edit'
|
||||
|
||||
const formDrawerVisible = ref(false)
|
||||
const formMode = ref<FormMode>('create')
|
||||
const submitLoading = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const memberError = ref('')
|
||||
const pendingMemberId = ref<number | undefined>(undefined)
|
||||
|
||||
const initialFormState = () => ({
|
||||
id: 0,
|
||||
name: '',
|
||||
payment_method: 'wechat' as PaymentMerchantMethod,
|
||||
member_ids: [] as number[],
|
||||
enabled: true,
|
||||
strategy: 'amount' as PaymentPoolStrategy,
|
||||
statistic_cycle: 'round' as PaymentMerchantPoolPayload['statistic_cycle'],
|
||||
threshold_amount_yuan: 0,
|
||||
threshold_count: 1,
|
||||
time_period_unit: 'hour' as PaymentMerchantPoolPayload['time_period_unit'],
|
||||
time_period_value: 1,
|
||||
time_period_started_at: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const form = reactive(initialFormState())
|
||||
|
||||
const orderedMemberIds = ref<number[]>([])
|
||||
watch(
|
||||
() => form.member_ids,
|
||||
(val) => {
|
||||
orderedMemberIds.value = [...val]
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
watch(orderedMemberIds, (val) => {
|
||||
form.member_ids = [...val]
|
||||
})
|
||||
|
||||
const formRules = reactive<FormRules>({
|
||||
name: [
|
||||
{ required: true, message: '请输入商户池名称', trigger: 'blur' },
|
||||
{ max: 100, message: '商户池名称不超过 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
payment_method: [{ required: true, message: '请选择支付方式', trigger: 'change' }],
|
||||
member_ids: [
|
||||
{
|
||||
validator: (_rule, value: number[], callback) => {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error('请至少选择一个成员'))
|
||||
return
|
||||
}
|
||||
const unique = new Set(value)
|
||||
if (unique.size !== value.length) {
|
||||
callback(new Error('成员不可重复'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
strategy: [{ required: true, message: '请选择轮询策略', trigger: 'change' }],
|
||||
statistic_cycle: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if ((form.strategy === 'amount' || form.strategy === 'count') && !value) {
|
||||
callback(new Error('请选择统计周期'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
threshold_amount_yuan: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (form.strategy !== 'amount') {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
const num = Number(value)
|
||||
if (!Number.isFinite(num) || num <= 0) {
|
||||
callback(new Error('金额阈值必须大于 0'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
threshold_count: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (form.strategy !== 'count') {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
const num = Number(value)
|
||||
if (!Number.isFinite(num) || !Number.isInteger(num) || num <= 0) {
|
||||
callback(new Error('笔数阈值必须为正整数'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
time_period_unit: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (form.strategy !== 'time') {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
if (!value) {
|
||||
callback(new Error('请选择时间单位'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
time_period_value: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (form.strategy !== 'time') {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
const num = Number(value)
|
||||
if (!Number.isFinite(num) || !Number.isInteger(num) || num <= 0) {
|
||||
callback(new Error('时间长度必须为正整数'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
time_period_started_at: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (form.strategy !== 'time') {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
if (!value) {
|
||||
callback(new Error('请选择时间起点'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const handlePaymentMethodChange = (value: PaymentMerchantMethod) => {
|
||||
form.payment_method = value
|
||||
form.member_ids = []
|
||||
orderedMemberIds.value = []
|
||||
availableMerchants.value = []
|
||||
memberError.value = ''
|
||||
loadAvailableMerchants(value)
|
||||
}
|
||||
|
||||
const appendMember = (id?: number | string) => {
|
||||
const memberId = Number(id)
|
||||
if (!memberId) {
|
||||
pendingMemberId.value = undefined
|
||||
return
|
||||
}
|
||||
if (form.member_ids.includes(memberId)) {
|
||||
memberError.value = '成员不可重复'
|
||||
} else {
|
||||
form.member_ids = [...form.member_ids, memberId]
|
||||
memberError.value = ''
|
||||
}
|
||||
pendingMemberId.value = undefined
|
||||
}
|
||||
|
||||
const removeMember = (id: number) => {
|
||||
form.member_ids = form.member_ids.filter((memberId) => memberId !== id)
|
||||
}
|
||||
|
||||
const buildPayload = (): PaymentMerchantPoolPayload | null => {
|
||||
if (!form.payment_method) {
|
||||
memberError.value = '请选择支付方式'
|
||||
return null
|
||||
}
|
||||
if (form.member_ids.length === 0) {
|
||||
memberError.value = '请至少选择一个成员'
|
||||
return null
|
||||
}
|
||||
if (new Set(form.member_ids).size !== form.member_ids.length) {
|
||||
memberError.value = '成员不可重复'
|
||||
return null
|
||||
}
|
||||
memberError.value = ''
|
||||
|
||||
const payload: PaymentMerchantPoolPayload = {
|
||||
name: form.name.trim(),
|
||||
payment_method: form.payment_method,
|
||||
member_ids: [...form.member_ids],
|
||||
enabled: form.enabled,
|
||||
strategy: form.strategy,
|
||||
remark: form.remark?.trim() || ''
|
||||
}
|
||||
|
||||
if (form.strategy === 'amount') {
|
||||
if (!form.statistic_cycle) return null
|
||||
const yuan = Number(form.threshold_amount_yuan)
|
||||
if (!Number.isFinite(yuan) || yuan <= 0) return null
|
||||
payload.statistic_cycle = form.statistic_cycle
|
||||
payload.threshold_amount = Math.round(yuan * 100)
|
||||
} else if (form.strategy === 'count') {
|
||||
if (!form.statistic_cycle) return null
|
||||
const count = Number(form.threshold_count)
|
||||
if (!Number.isInteger(count) || count <= 0) return null
|
||||
payload.statistic_cycle = form.statistic_cycle
|
||||
payload.threshold_count = count
|
||||
} else {
|
||||
if (!form.time_period_unit) return null
|
||||
const value = Number(form.time_period_value)
|
||||
if (!Number.isInteger(value) || value <= 0) return null
|
||||
payload.time_period_unit = form.time_period_unit
|
||||
payload.time_period_value = value
|
||||
if (form.time_period_started_at) {
|
||||
payload.time_period_started_at = form.time_period_started_at
|
||||
}
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
const showCreateDrawer = async () => {
|
||||
if (!canManage.value) return
|
||||
Object.assign(form, initialFormState())
|
||||
orderedMemberIds.value = []
|
||||
formDrawerVisible.value = true
|
||||
formMode.value = 'create'
|
||||
await loadAvailableMerchants(form.payment_method)
|
||||
}
|
||||
|
||||
const openEditDrawer = async (pool: PaymentMerchantPool) => {
|
||||
if (!canManage.value) return
|
||||
await loadAvailableMerchants(pool.payment_method)
|
||||
Object.assign(form, initialFormState(), {
|
||||
id: pool.id,
|
||||
name: pool.name,
|
||||
payment_method: pool.payment_method,
|
||||
member_ids: [...pool.member_ids],
|
||||
enabled: pool.enabled,
|
||||
strategy: pool.strategy,
|
||||
statistic_cycle: pool.statistic_cycle,
|
||||
threshold_amount_yuan: Number(pool.threshold_amount) / 100,
|
||||
threshold_count: pool.threshold_count,
|
||||
time_period_unit: pool.time_period_unit,
|
||||
time_period_value: pool.time_period_value,
|
||||
time_period_started_at: pool.time_period_started_at || '',
|
||||
remark: pool.remark || ''
|
||||
})
|
||||
orderedMemberIds.value = [...pool.member_ids]
|
||||
formDrawerVisible.value = true
|
||||
formMode.value = 'edit'
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
Object.assign(form, initialFormState())
|
||||
orderedMemberIds.value = []
|
||||
memberError.value = ''
|
||||
pendingMemberId.value = undefined
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!canManage.value) return
|
||||
if (!formRef.value) return
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const payload = buildPayload()
|
||||
if (!payload) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (formMode.value === 'create') {
|
||||
await PaymentMerchantPoolsService.createPaymentMerchantPool(payload)
|
||||
ElMessage.success('商户池创建成功')
|
||||
} else {
|
||||
await PaymentMerchantPoolsService.updatePaymentMerchantPool(form.id, payload)
|
||||
ElMessage.success('商户池已更新')
|
||||
}
|
||||
formDrawerVisible.value = false
|
||||
await loadPools()
|
||||
} catch (error) {
|
||||
console.error('提交商户池失败:', error)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 详情
|
||||
const detailDrawerVisible = ref(false)
|
||||
const detail = ref<PaymentMerchantPool | null>(null)
|
||||
const tableRef = ref()
|
||||
|
||||
const showDetail = async (id: number) => {
|
||||
try {
|
||||
const res = await PaymentMerchantPoolsService.getPaymentMerchantPoolById(id)
|
||||
if (res.code === 0) {
|
||||
detail.value = res.data
|
||||
detailDrawerVisible.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载商户池详情失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleEnabled = async (pool: PaymentMerchantPool) => {
|
||||
if (!canManage.value) return
|
||||
const target = !pool.enabled
|
||||
const action = target ? '启用' : '停用'
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认${action}商户池 “${pool.name}”?`, '操作确认', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = target
|
||||
? await PaymentMerchantPoolsService.enablePaymentMerchantPool(pool.id)
|
||||
: await PaymentMerchantPoolsService.disablePaymentMerchantPool(pool.id)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success(`已${action}`)
|
||||
await loadPools()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${action}商户池失败:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
const getActions = (row: PaymentMerchantPool) => [
|
||||
{
|
||||
label: '详情',
|
||||
type: 'primary' as const,
|
||||
handler: () => showDetail(row.id)
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
type: 'primary' as const,
|
||||
handler: () => openEditDrawer(row),
|
||||
permission: canManage.value ? '' : 'hidden'
|
||||
},
|
||||
{
|
||||
label: row.enabled ? '停用' : '启用',
|
||||
type: 'primary' as const,
|
||||
handler: () => toggleEnabled(row),
|
||||
permission: canManage.value ? '' : 'hidden'
|
||||
}
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (canManage.value) {
|
||||
loadPools()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
pools.value = []
|
||||
detail.value = null
|
||||
availableMerchants.value = []
|
||||
orderedMemberIds.value = []
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pool-management {
|
||||
.pool-member-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.pool-member-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background-color: rgba(var(--art-gray-200-rgb), 0.6);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pool-member-row :deep(.drag-handle) {
|
||||
color: var(--el-text-color-secondary);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.pool-member-empty {
|
||||
padding: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
text-align: center;
|
||||
border: 1px dashed var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.field-error {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user