完善租客等级免押与积分管理
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user