834 lines
25 KiB
Vue
834 lines
25 KiB
Vue
<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="canCreatePool" 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="120"
|
||
@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="formMode === 'create' ? !canCreatePool : !canEditPool"
|
||
@click="handleSubmit"
|
||
>
|
||
保存
|
||
</ElButton>
|
||
</template>
|
||
</ElDrawer>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, h, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
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 { useAuth } from '@/composables/useAuth'
|
||
import { PAYMENT_MERCHANT_POOL_PERMISSIONS } from '@/config/constants/paymentMerchantPools'
|
||
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'
|
||
import { RoutesAlias } from '@/router/routesAlias'
|
||
|
||
defineOptions({ name: 'PaymentMerchantPoolManagement' })
|
||
|
||
type FilterVo = string | number | undefined | null | unknown[]
|
||
|
||
const router = useRouter()
|
||
const { hasAuth } = useAuth()
|
||
|
||
const canCreatePool = computed(() => hasAuth(PAYMENT_MERCHANT_POOL_PERMISSIONS.poolCreate))
|
||
const canEditPool = computed(() => hasAuth(PAYMENT_MERCHANT_POOL_PERMISSIONS.poolEdit))
|
||
const canTogglePool = computed(() => hasAuth(PAYMENT_MERCHANT_POOL_PERMISSIONS.poolToggle))
|
||
const canViewPoolDetail = computed(() => hasAuth(PAYMENT_MERCHANT_POOL_PERMISSIONS.poolDetail))
|
||
|
||
const handleNameClick = (row: PaymentMerchantPool) => {
|
||
router.push({ path: `${RoutesAlias.PaymentMerchantPoolDetail}/${row.id}` })
|
||
}
|
||
|
||
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,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: PaymentMerchantPool) =>
|
||
canViewPoolDetail.value
|
||
? h(
|
||
'span',
|
||
{
|
||
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
||
onClick: (event: MouseEvent) => {
|
||
event.stopPropagation()
|
||
handleNameClick(row)
|
||
}
|
||
},
|
||
row.name
|
||
)
|
||
: row.name
|
||
},
|
||
{
|
||
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 () => {
|
||
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 || '未知商户'
|
||
|
||
// 表单
|
||
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())
|
||
|
||
// 成员顺序以 form.member_ids 为单一数据源:VueDraggable 的 v-model 直接写回该数组,
|
||
// 避免双向 watch 互相赋值导致的递归更新。
|
||
const orderedMemberIds = computed<number[]>({
|
||
get: () => form.member_ids,
|
||
set: (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 = []
|
||
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 (!canCreatePool.value) return
|
||
Object.assign(form, initialFormState())
|
||
formDrawerVisible.value = true
|
||
formMode.value = 'create'
|
||
await loadAvailableMerchants(form.payment_method)
|
||
}
|
||
|
||
const openEditDrawer = async (pool: PaymentMerchantPool) => {
|
||
if (!canEditPool.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 || ''
|
||
})
|
||
formDrawerVisible.value = true
|
||
formMode.value = 'edit'
|
||
}
|
||
|
||
const resetForm = () => {
|
||
Object.assign(form, initialFormState())
|
||
memberError.value = ''
|
||
pendingMemberId.value = undefined
|
||
formRef.value?.clearValidate()
|
||
}
|
||
|
||
const handleSubmit = async () => {
|
||
if (formMode.value === 'create' ? !canCreatePool.value : !canEditPool.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 tableRef = ref()
|
||
|
||
const toggleEnabled = async (pool: PaymentMerchantPool) => {
|
||
if (!canTogglePool.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: () => openEditDrawer(row),
|
||
permission: canEditPool.value ? '' : 'hidden'
|
||
},
|
||
{
|
||
label: row.enabled ? '停用' : '启用',
|
||
type: 'primary' as const,
|
||
handler: () => toggleEnabled(row),
|
||
permission: canTogglePool.value ? '' : 'hidden'
|
||
}
|
||
]
|
||
|
||
onMounted(() => {
|
||
loadPools()
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
pools.value = []
|
||
availableMerchants.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>
|