Files
hfb_sys/frontend/src/features/admin/components/RenterGrowthConfigDialog.vue
T

222 lines
6.5 KiB
Vue

<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { ref, watch } from 'vue'
import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs'
import {
cloneRenterGrowthConfig,
defaultRenterGrowthConfig,
parseRenterGrowthConfig,
type RenterGrowthConfig,
type RenterGrowthLevelRule,
} from '@/features/admin/api/renterGrowth'
import { readError } from '@/shared/utils/error'
const props = defineProps<{
modelValue: boolean
config: SystemConfig
}>()
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
(event: 'saved'): void
}>()
const submitting = ref(false)
const draft = ref<RenterGrowthConfig>(cloneRenterGrowthConfig(defaultRenterGrowthConfig))
watch(
() => [props.modelValue, props.config.value] as const,
([visible]) => {
if (visible) draft.value = parseRenterGrowthConfig(props.config.value)
},
{ immediate: true }
)
function discountPercent(level: RenterGrowthLevelRule) {
return level.discount_bps / 100
}
function updateDiscountPercent(level: RenterGrowthLevelRule, value: number | undefined) {
level.discount_bps = Math.round(Number(value || 0) * 100)
}
function quotaYuan(level: RenterGrowthLevelRule) {
return level.deposit_free_quota_cent / 100
}
function updateQuotaYuan(level: RenterGrowthLevelRule, value: number | undefined) {
level.deposit_free_quota_cent = Math.round(Number(value || 0) * 100)
}
function resetDefaults() {
draft.value = cloneRenterGrowthConfig(defaultRenterGrowthConfig)
}
function validateDraft() {
if (!Number.isInteger(draft.value.points_per_yuan) || draft.value.points_per_yuan <= 0) {
return '每元积分必须是大于 0 的整数'
}
for (const [index, level] of draft.value.levels.entries()) {
const previous = draft.value.levels[index - 1]
if (!Number.isInteger(level.min_points) || level.min_points < 0) {
return `${level.name}积分门槛不正确`
}
if (index === 0 && level.min_points !== 0) return '普通等级积分门槛必须为 0'
if (previous && level.min_points <= previous.min_points) return '等级积分门槛必须严格递增'
if (level.discount_bps <= 0 || level.discount_bps > 10000) {
return `${level.name}支付比例必须在 0% 到 100% 之间`
}
if (previous && level.discount_bps > previous.discount_bps)
return '高等级支付比例不能高于低等级'
if (!Number.isInteger(level.deposit_free_quota_cent) || level.deposit_free_quota_cent < 0) {
return `${level.name}免押额度不正确`
}
if (previous && level.deposit_free_quota_cent < previous.deposit_free_quota_cent) {
return '高等级免押额度不能低于低等级'
}
}
return ''
}
async function handleSave() {
const validationMessage = validateDraft()
if (validationMessage) {
ElMessage.warning(validationMessage)
return
}
submitting.value = true
try {
await updateSystemConfig(props.config.key, {
value: JSON.stringify(draft.value),
description: '租客成长等级规则(积分门槛、租金折扣、等级免押额度)',
})
ElMessage.success('成长等级配置已更新')
emit('saved')
emit('update:modelValue', false)
} catch (error) {
ElMessage.error(readError(error, '成长等级配置保存失败'))
} finally {
submitting.value = false
}
}
</script>
<template>
<el-dialog
:model-value="modelValue"
title="成长等级配置"
width="860px"
destroy-on-close
@update:model-value="emit('update:modelValue', $event)"
>
<div class="growth-config-form">
<div class="growth-switch-row">
<span>启用成长等级</span>
<el-switch v-model="draft.enabled" />
</div>
<el-form label-position="top" @submit.prevent>
<el-form-item label="每完成 1 元实付租金获得积分">
<el-input-number
v-model="draft.points_per_yuan"
:min="1"
:max="1000"
:step="1"
step-strictly
/>
</el-form-item>
</el-form>
<el-table :data="draft.levels" border>
<el-table-column prop="name" label="等级" width="100" />
<el-table-column label="积分门槛" min-width="170">
<template #default="{ row, $index }">
<el-input-number
v-model="row.min_points"
:disabled="$index === 0"
:min="0"
:max="1000000000"
:step="100"
step-strictly
/>
</template>
</el-table-column>
<el-table-column label="租金支付比例" min-width="190">
<template #default="{ row, $index }">
<el-input-number
:model-value="discountPercent(row)"
:disabled="$index === 0"
:min="0.01"
:max="100"
:precision="2"
:step="0.5"
@update:model-value="updateDiscountPercent(row, $event)"
/>
<span class="unit">%</span>
</template>
</el-table-column>
<el-table-column label="等级免押额度" min-width="190">
<template #default="{ row, $index }">
<el-input-number
:model-value="quotaYuan(row)"
:disabled="$index === 0"
:min="0"
:max="1000000"
:precision="0"
:step="100"
@update:model-value="updateQuotaYuan(row, $event)"
/>
<span class="unit"></span>
</template>
</el-table-column>
</el-table>
</div>
<template #footer>
<el-button :disabled="submitting" @click="resetDefaults">恢复默认</el-button>
<el-button :disabled="submitting" @click="emit('update:modelValue', false)">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSave">保存配置</el-button>
</template>
</el-dialog>
</template>
<style scoped>
.growth-config-form {
display: grid;
gap: 18px;
}
.growth-switch-row {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 40px;
padding-bottom: 14px;
border-bottom: 1px solid #e5e7eb;
color: #111827;
font-weight: 700;
}
.growth-config-form :deep(.el-form-item) {
margin-bottom: 0;
}
.growth-config-form :deep(.el-input-number) {
width: calc(100% - 32px);
}
.unit {
display: inline-block;
width: 28px;
margin-left: 4px;
color: #64748b;
font-size: 13px;
}
@media (max-width: 760px) {
.growth-config-form :deep(.el-table) {
min-width: 760px;
}
}
</style>