feat: 角色默认信用与店铺实际额度管理
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m12s

This commit is contained in:
luo
2026-07-23 14:10:48 +08:00
parent d5fd8ac564
commit d7c2c146fe
14 changed files with 669 additions and 21 deletions

View File

@@ -32,7 +32,7 @@
:total="pagination.total"
:marginTop="10"
:actions="getActions"
:actionsWidth="120"
:actionsWidth="180"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
@@ -416,13 +416,74 @@
</div>
</template>
</ElDialog>
<!-- 店铺实际信用额度调整弹框 -->
<ElDialog
v-model="creditDialogVisible"
:title="`调整实际信用额度 - ${currentCreditShop?.shop_name || ''}`"
width="520px"
@closed="resetCreditDialog"
>
<ElDescriptions :column="1" border class="credit-preview">
<ElDescriptionsItem label="店铺名称">
{{ currentCreditShop?.shop_name || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="当前版本">
{{ currentCreditShop?.version ?? '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="修改前">
{{
formatCreditPreview(currentCreditShop?.credit_enabled, currentCreditShop?.credit_limit)
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="修改后">
{{ formatCreditPreview(creditForm.credit_enabled, creditFormCreditLimitFen) }}
</ElDescriptionsItem>
</ElDescriptions>
<ElAlert
title="实际可用金额、欠款金额和欠款状态以后端刷新后的资金概况为准。"
type="info"
:closable="false"
show-icon
class="credit-dialog-alert"
/>
<ElForm ref="creditFormRef" :model="creditForm" :rules="creditRules" label-width="110px">
<ElFormItem label="启用信用">
<ElSwitch v-model="creditForm.credit_enabled" @change="handleCreditEnabledChange" />
</ElFormItem>
<ElFormItem label="实际信用额度" prop="credit_limit_yuan">
<ElInputNumber
v-model="creditForm.credit_limit_yuan"
:disabled="!creditForm.credit_enabled"
:min="0"
:precision="2"
:step="100"
controls-position="right"
style="width: 100%"
placeholder="请输入实际信用额度"
/>
<div class="credit-dialog-tip">单位关闭信用时额度将自动归零</div>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="creditDialogVisible = false">取消</ElButton>
<ElButton type="primary" :loading="creditSubmitting" @click="handleCreditSubmit">
确认调整
</ElButton>
</div>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { h, watch, onBeforeUnmount, ref, reactive, onMounted, computed } from 'vue'
import { h, watch, onBeforeUnmount, ref, reactive, onMounted, computed, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { CommissionService } from '@/api/modules'
import { CommissionService, ShopService } from '@/api/modules'
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { MainWalletTransactionType } from '@/types/api/commission'
@@ -438,7 +499,7 @@
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
import { formatDateTime, formatMoney } from '@/utils/business/format'
import { fenToYuan, formatDateTime, formatMoney, yuanToFen } from '@/utils/business/format'
import {
CommissionStatusMap,
WithdrawalStatusMap,
@@ -508,6 +569,20 @@
remark: ''
})
// 实际信用额度调整弹框状态
const creditDialogVisible = ref(false)
const creditFormRef = ref<FormInstance>()
const creditSubmitting = ref(false)
const currentCreditShop = ref<ShopFundSummaryItem | null>(null)
const creditForm = reactive({
credit_enabled: false,
credit_limit_yuan: 0
})
const creditFormCreditLimitFen = computed(() =>
creditForm.credit_enabled ? yuanToFen(creditForm.credit_limit_yuan) || 0 : 0
)
// 佣金修正表单验证规则
const resolveRules = computed<FormRules>(() => ({
amount:
@@ -525,6 +600,36 @@
remark: [{ max: 500, message: '备注最多500字符', trigger: 'blur' }]
}))
const creditRules = computed<FormRules>(() => ({
credit_limit_yuan: [
{
validator: (
_rule: unknown,
value: number | undefined,
callback: (error?: Error) => void
) => {
if (!creditForm.credit_enabled) {
callback()
return
}
if (value === undefined || value === null || Number.isNaN(value)) {
callback(new Error('请输入实际信用额度'))
return
}
if (value < 0) {
callback(new Error('实际信用额度不能小于0'))
return
}
callback()
},
trigger: 'blur'
}
]
}))
// 预充值钱包流水状态
const mainWalletLoading = ref(false)
const mainWalletTableRef = ref()
@@ -552,6 +657,10 @@
{ label: '手机号', prop: 'phone' },
{ label: '现金余额', prop: 'balance' },
{ label: '冻结金额', prop: 'frozen_balance' },
{ label: '实际信用额度', prop: 'credit_limit' },
{ label: '可用金额', prop: 'available_balance' },
{ label: '欠款金额', prop: 'debt_amount' },
{ label: '版本', prop: 'version' },
{ label: '余额预警', prop: 'low_balance_warning' },
{ label: '总佣金', prop: 'total_commission' },
{ label: '可提现', prop: 'available_commission' },
@@ -656,6 +765,51 @@
minWidth: 120,
formatter: (row: ShopFundSummaryItem) => formatMoney(row.frozen_balance)
},
{
prop: 'credit_limit',
label: '实际信用额度',
minWidth: 150,
formatter: (row: ShopFundSummaryItem) => {
if (!row.credit_enabled) {
return h('span', { style: 'color: var(--el-text-color-secondary)' }, '未启用 / ¥0.00')
}
return h(
'span',
{ style: 'color: var(--el-color-warning); font-weight: 500' },
formatMoney(row.credit_limit)
)
}
},
{
prop: 'available_balance',
label: '可用金额',
minWidth: 130,
formatter: (row: ShopFundSummaryItem) => {
return h(
'span',
{ style: 'color: var(--el-color-success); font-weight: 500' },
formatMoney(row.available_balance)
)
}
},
{
prop: 'debt_amount',
label: '欠款金额',
minWidth: 130,
formatter: (row: ShopFundSummaryItem) => {
const amountText = formatMoney(row.debt_amount)
if (!row.is_in_debt) return amountText
return h('span', { style: 'color: var(--el-color-danger); font-weight: 500' }, amountText)
}
},
{
prop: 'version',
label: '版本',
minWidth: 90,
formatter: (row: ShopFundSummaryItem) => row.version ?? '-'
},
{
prop: 'low_balance_warning',
label: '余额预警',
@@ -803,9 +957,105 @@
})
}
actions.push({
label: '调整额度',
handler: () => showCreditDialog(row),
type: 'primary'
})
return actions
}
const formatCreditPreview = (enabled?: boolean, creditLimit?: number) => {
return enabled ? `启用 / ${formatMoney(creditLimit || 0)}` : '关闭 / ¥0.00'
}
const showCreditDialog = (row: ShopFundSummaryItem) => {
currentCreditShop.value = row
creditForm.credit_enabled = Boolean(row.credit_enabled)
creditForm.credit_limit_yuan = row.credit_enabled ? fenToYuan(row.credit_limit) : 0
creditDialogVisible.value = true
nextTick(() => {
creditFormRef.value?.clearValidate()
})
}
const handleCreditEnabledChange = (enabled: boolean | string | number) => {
if (!enabled) {
creditForm.credit_limit_yuan = 0
creditFormRef.value?.clearValidate('credit_limit_yuan')
}
}
const resetCreditDialog = () => {
creditFormRef.value?.resetFields()
currentCreditShop.value = null
creditForm.credit_enabled = false
creditForm.credit_limit_yuan = 0
}
const isCreditConflictMessage = (message?: string) => {
if (!message) return false
return /版本|冲突|并发|过期|conflict/i.test(message)
}
const isCreditConflictResponse = (response: any) => {
return response?.code === 409 || isCreditConflictMessage(response?.msg)
}
const isCreditConflictError = (error: any) => {
return (
error?.response?.status === 409 ||
error?.response?.data?.code === 409 ||
isCreditConflictMessage(error?.response?.data?.msg || error?.message)
)
}
const handleCreditConflict = async () => {
ElMessage.warning('资金概况已被更新,已刷新最新数据,请重新调整')
creditDialogVisible.value = false
await getTableData()
}
const handleCreditSubmit = async () => {
if (!creditFormRef.value || !currentCreditShop.value) return
await creditFormRef.value.validate(async (valid) => {
if (!valid || !currentCreditShop.value) return
creditSubmitting.value = true
try {
const res = await ShopService.updateShopCreditLimit(currentCreditShop.value.shop_id, {
credit_enabled: creditForm.credit_enabled,
credit_limit: creditFormCreditLimitFen.value,
version: currentCreditShop.value.version
})
if (res.code === 0) {
ElMessage.success('实际信用额度调整成功')
creditDialogVisible.value = false
await getTableData()
return
}
if (isCreditConflictResponse(res)) {
await handleCreditConflict()
return
}
ElMessage.error(res.msg || '实际信用额度调整失败')
} catch (error: any) {
if (isCreditConflictError(error)) {
await handleCreditConflict()
} else {
console.error('实际信用额度调整失败:', error)
}
} finally {
creditSubmitting.value = false
}
})
}
// 监听tab切换
watch(activeTab, (newTab) => {
if (newTab === 'commission') {
@@ -1084,6 +1334,21 @@
}
}
.credit-preview {
margin-bottom: 16px;
}
.credit-dialog-alert {
margin-bottom: 16px;
}
.credit-dialog-tip {
margin-top: 6px;
font-size: 12px;
line-height: 1.4;
color: var(--el-text-color-secondary);
}
@media (width <= 1200px) {
.main-wallet-filter__grid {
grid-template-columns: repeat(2, minmax(220px, 1fr));