Files
tydata-webview-v2/src/views/AgentVipConfig.vue
2025-10-30 13:34:28 +08:00

539 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="p-4 max-w-3xl mx-auto min-h-screen">
<!-- 标题部分 -->
<div class="card mb-4 p-4 bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg shadow-lg text-white">
<h1 class="text-2xl font-extrabold mb-2">专业报告定价配置</h1>
<p class="opacity-90">
请选择报告类型并设置定价策略助您实现精准定价
</p>
</div>
<div class="mb-4">
<van-field readonly clickable name="reportType" v-model="selectedReportText" label="报告类型"
placeholder="点击选择报告" @click="showPicker = true" class="card">
<template #label>
<span class="text-blue-600 font-medium">📝 选择报告</span>
</template>
<template #right-icon>
<van-icon name="arrow-down" class="text-gray-400" />
</template>
</van-field>
<van-popup v-model:show="showPicker" position="bottom">
<van-picker :columns="reportOptions" :default-index="0" @confirm="onConfirm"
@cancel="showPicker = false" />
</van-popup>
</div>
<div v-if="selectedReportText" class="space-y-6">
<!-- 配置卡片 -->
<div class="card">
<!-- 当前报告标题 -->
<div class="flex items-center mb-6">
<van-icon name="description" class="text-blue-500 text-xl mr-2" />
<h2 class="text-xl font-semibold text-gray-800">
{{ selectedReportText }}配置
</h2>
</div>
<!-- 显示当前产品的基础成本信息 -->
<div v-if="productConfigData && productConfigData.cost_price"
class="px-4 py-2 mb-4 bg-gray-50 border border-gray-200 rounded-lg shadow-sm">
<div class="text-lg font-semibold text-gray-700">
报告基础配置信息
</div>
<div class="mt-1 text-sm text-gray-600">
<div>
基础成本价<span class="font-medium">{{
productConfigData.cost_price
}}</span>
</div>
<!-- <div>区间起始价<span class="font-medium">{{ productConfigData.price_range_min }}</span> </div> -->
<div>
最高设定金额上限<span class="font-medium">{{
productConfigData.price_range_max
}}</span>
</div>
<div>
最高设定比例上限<span class="font-medium">{{
priceRatioMax
}}</span>
%
</div>
</div>
</div>
<!-- 分隔线 -->
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
<van-icon name="exchange" class="text-gray-400 mx-2" />
<span class="text-gray-400 text-sm">成本策略配置</span>
</van-divider>
<!-- 加价金额 -->
<van-field v-model.number="configData.price_increase_amount" label="加价金额" type="number" placeholder="0"
@blur="validateDecimal('price_increase_amount')" class="custom-field"
:class="{ 'van-field--error': increaseError }">
<template #label>
<span class="text-gray-600 font-medium">🚀 加价金额</span>
</template>
<template #extra>
<span class="text-blue-500 font-medium ml-2"></span>
</template>
</van-field>
<div class="text-xs text-gray-400 mt-1">
提示最大加价金额为{{ priceIncreaseAmountMax }}<br />
说明加价金额是在基础成本价上增加的额外费用决定下级报告的最低定价您将获得所有输入的金额利润
</div>
<!-- 分隔线 -->
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
<van-icon name="exchange" class="text-gray-400 mx-2" />
<span class="text-gray-400 text-sm">定价策略配置</span>
</van-divider>
<!-- 定价区间最低 -->
<van-field v-model.number="configData.price_range_from" label="定价区间最低" type="number" placeholder="0"
@blur="
() => {
validateDecimal('price_range_from');
validateRange();
}
" class="custom-field" :class="{ 'van-field--error': rangeError }">
<template #label>
<span class="text-gray-600 font-medium">💰 最低金额</span>
</template>
<template #extra>
<span class="text-blue-500 font-medium ml-2"></span>
</template>
</van-field>
<div class="text-xs text-gray-400 mt-1">
提示最低金额不能低于基础最低
{{ productConfigData?.price_range_min || 0 }} +
加价金额<br />
说明设定的最低金额为定价区间的起始值若下级设定的报告金额在区间内则区间内部分将按比例获得收益
</div>
<!-- 定价区间最高 -->
<van-field v-model.number="configData.price_range_to" label="定价区间最高" type="number" placeholder="0"
@blur="
() => {
validateDecimal('price_range_to');
validateRange();
}
" class="custom-field" :class="{ 'van-field--error': rangeError }">
<template #label>
<span class="text-gray-600 font-medium">💰 最高金额</span>
</template>
<template #extra>
<span class="text-blue-500 font-medium ml-2"></span>
</template>
</van-field>
<div class="text-xs text-gray-400 mt-1">
提示最高金额不能超过上限{{
productConfigData?.price_range_max || 0
}}和大于最低金额{{ priceIncreaseMax }}<br />
说明设定的最高金额为定价区间的结束值若下级设定的报告金额在区间内则区间内部分将按比例获得收益
</div>
<!-- 收取比例 -->
<van-field v-model.number="configData.price_ratio" label="收取比例" type="digit" placeholder="0" @blur="
() => {
validateRatio();
}
" class="custom-field" :class="{ 'van-field--error': ratioError }">
<template #label>
<span class="text-gray-600 font-medium">📈 收取比例</span>
</template>
<template #extra>
<span class="text-blue-500 font-medium ml-2">%</span>
</template>
</van-field>
<div class="text-xs text-gray-400 mt-1">
提示最大收取比例为{{ priceRatioMax }}%<br />
说明收取比例表示对定价区间内即报告金额超过最低金额小于最高金额的部分的金额按此比例进行利润分成
</div>
</div>
<!-- 保存按钮 -->
<van-button type="primary" block
class="shadow-lg bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white rounded-xl h-12"
@click="handleSubmit">
<van-icon name="success" class="mr-2" />
保存当前报告配置
</van-button>
</div>
<!-- 未选择提示 -->
<div v-else class="text-center py-12">
<van-icon name="warning" class="text-gray-400 text-4xl mb-4" />
<p class="text-gray-500">请先选择需要配置的报告类型</p>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from "vue";
import { showToast } from "vant";
import { settings } from "nprogress";
// 报告类型选项
const reportOptions = [
{ text: "小微企业", value: "companyinfo", id: 2 },
{ text: "贷前风险", value: "preloanbackgroundcheck", id: 5 },
{ text: "个人大数据", value: "personaldata", id: 27 },
{ text: "入职风险", value: "backgroundcheck", id: 1 },
{ text: "家政风险", value: "homeservice", id: 3 },
{ text: "婚恋风险", value: "marriage", id: 4 },
// { text: "租赁风险", value: "rentalrisk", id: 6 },
// { text: "个人风险", value: "riskassessment", id: 7 },
];
// 状态管理
const showPicker = ref(false);
const selectedReport = ref(reportOptions[0]);
const selectedReportText = ref(reportOptions[0].text);
const selectedReportId = ref(reportOptions[0].id);
const configData = ref({});
const productConfigData = ref({});
const priceIncreaseMax = ref(null);
const priceIncreaseAmountMax = ref(null);
const priceRatioMax = ref(null);
const rangeError = ref(false);
const ratioError = ref(false);
const increaseError = ref(false);
// 金额输入格式验证:确保最多两位小数
const validateDecimal = (field) => {
const value = configData.value[field];
if (value === null || value === undefined) return;
const numValue = Number(value);
if (isNaN(numValue)) {
configData.value[field] = null;
return;
}
const fixedValue = parseFloat(numValue.toFixed(2));
configData.value[field] = fixedValue;
if (field === "price_increase_amount") {
if (fixedValue > priceIncreaseAmountMax.value) {
configData.value[field] = priceIncreaseAmountMax.value;
showToast(`加价金额最大为${priceIncreaseAmountMax.value}`);
increaseError.value = true;
setTimeout(() => {
increaseError.value = false;
}, 2000);
} else {
increaseError.value = false;
}
// 当加价金额改变后,重新验证价格区间
validateRange();
}
};
// 价格区间验证(在 @blur 中调用)
const validateRange = () => {
console.log(
"configData.value.price_range_from",
configData.value.price_range_from
);
console.log(
"configData.value.price_range_to",
configData.value.price_range_to
);
if (
configData.value.price_range_from === null ||
configData.value.price_range_to === null
) {
rangeError.value = false;
return;
}
if (
isNaN(configData.value.price_range_from) ||
isNaN(configData.value.price_range_to)
)
return;
const additional = configData.value.price_increase_amount || 0;
const minAllowed = parseFloat(
(
Number(productConfigData.value.cost_price) + Number(additional)
).toFixed(2)
); // 使用成本价作为最小值
const maxAllowed = productConfigData.value.price_range_max; // 使用产品配置中的最大价格作为最大值
if (configData.value.price_range_from < minAllowed) {
configData.value.price_range_from = minAllowed;
showToast(`最低金额不能低于成本价 ${minAllowed}`);
rangeError.value = true;
closeRangeError();
configData.value.price_range_to = parseFloat(
(
Number(configData.value.price_range_from) +
Number(priceIncreaseMax.value)
).toFixed(2)
);
return;
}
if (configData.value.price_range_to < configData.value.price_range_from) {
showToast("最高金额不能低于最低金额");
if (
configData.value.price_range_from + priceIncreaseMax.value >
maxAllowed
) {
configData.value.price_range_to = maxAllowed;
} else {
configData.value.price_range_to =
configData.value.price_range_from + priceIncreaseMax.value;
}
rangeError.value = true;
closeRangeError();
return;
}
const diff = parseFloat(
(
configData.value.price_range_to - configData.value.price_range_from
).toFixed(2)
);
if (diff > priceIncreaseMax.value) {
showToast(`价格区间最大差值为${priceIncreaseMax.value}`);
configData.value.price_range_to =
configData.value.price_range_from + priceIncreaseMax.value;
closeRangeError();
return;
}
if (configData.value.price_range_to > maxAllowed) {
configData.value.price_range_to = maxAllowed;
showToast(`最高金额不能超过 ${maxAllowed}`);
closeRangeError();
}
if (!rangeError.value) {
rangeError.value = false;
}
};
// 收取比例验证(修改为保留两位小数,不再四舍五入取整)
const validateRatio = () => {
let value = configData.value.price_ratio;
if (value === null || value === undefined) return;
const numValue = Number(value);
if (isNaN(numValue)) {
configData.value.price_ratio = null;
ratioError.value = true;
return;
}
if (numValue > priceRatioMax.value) {
configData.value.price_ratio = priceRatioMax.value;
showToast(`收取比例最大为${priceRatioMax.value}%`);
ratioError.value = true;
setTimeout(() => {
ratioError.value = false;
}, 1000);
} else if (numValue < 0) {
configData.value.price_ratio = 0;
ratioError.value = true;
} else {
configData.value.price_ratio = parseFloat(numValue.toFixed(2));
ratioError.value = false;
}
};
// 获取配置
const getConfig = async () => {
try {
const { data, error } = await useApiFetch(
"/agent/membership/user_config?product_id=" + selectedReportId.value
)
.get()
.json();
if (data.value?.code === 200) {
const respConfigData = data.value.data.agent_membership_user_config;
configData.value = {
id: respConfigData.product_id,
price_range_from: respConfigData.price_range_from || null,
price_range_to: respConfigData.price_range_to || null,
price_ratio: respConfigData.price_ratio * 100 || null, // 转换为百分比
price_increase_amount:
respConfigData.price_increase_amount || null,
};
console.log("configData", configData.value);
// const respProductConfigData = data.value.data.product_config
productConfigData.value = data.value.data.product_config;
// 设置动态限制值
priceIncreaseMax.value = data.value.data.price_increase_max;
priceIncreaseAmountMax.value =
data.value.data.price_increase_amount;
priceRatioMax.value = data.value.data.price_ratio * 100;
}
} catch (error) {
showToast("配置加载失败");
}
};
// 提交处理
const handleSubmit = async () => {
try {
if (!finalValidation()) {
return;
}
// 前端数据转换
const submitData = {
product_id: configData.value.id,
price_range_from: configData.value.price_range_from || 0,
price_range_to: configData.value.price_range_to || 0,
price_ratio: (configData.value.price_ratio || 0) / 100, // 转换为小数
price_increase_amount: configData.value.price_increase_amount || 0,
};
console.log("submitData", submitData);
const { data, error } = await useApiFetch(
"/agent/membership/save_user_config"
)
.post(submitData)
.json();
if (data.value?.code === 200) {
setTimeout(() => {
showToast("保存成功");
}, 500);
getConfig();
}
} catch (error) {
showToast("保存失败,请稍后重试");
}
};
// 最终验证函数
const finalValidation = () => {
// 校验最低金额不能为空且大于0
if (
!configData.value.price_range_from ||
configData.value.price_range_from <= 0
) {
showToast("最低金额不能为空");
return false;
}
// 校验最高金额不能为空且大于0
if (
!configData.value.price_range_to ||
configData.value.price_range_to <= 0
) {
showToast("最高金额不能为空");
return false;
}
// 校验收取比例不能为空且大于0
if (!configData.value.price_ratio || configData.value.price_ratio <= 0) {
showToast("收取比例不能为空");
return false;
}
// 验证最低金额必须小于最高金额
if (configData.value.price_range_from >= configData.value.price_range_to) {
showToast("最低金额必须小于最高金额");
return false;
}
// 验证价格区间差值不能超过最大允许差值
const finalDiff = parseFloat(
(
configData.value.price_range_to - configData.value.price_range_from
).toFixed(2)
);
if (finalDiff > priceIncreaseMax.value) {
showToast(`价格区间最大差值为${priceIncreaseMax.value}`);
return false;
}
// 验证最高金额不能超过产品配置中设定的上限
if (
configData.value.price_range_to >
productConfigData.value.price_range_max
) {
showToast(
`最高金额不能超过${productConfigData.value.price_range_max}`
);
return false;
}
// 验证最低金额不能低于成本价+加价金额(加价金额允许为空)
const additional = configData.value.price_increase_amount || 0;
if (
configData.value.price_range_from <
productConfigData.value.cost_price + additional
) {
showToast(
`最低金额不能低于成本价${productConfigData.value.cost_price + additional
}`
);
return false;
}
return true;
};
// 选择器确认
const onConfirm = ({ selectedOptions }) => {
selectedReport.value = selectedOptions[0];
selectedReportText.value = selectedOptions[0].text;
selectedReportId.value = selectedOptions[0].id;
showPicker.value = false;
// 重置错误状态
rangeError.value = false;
ratioError.value = false;
increaseError.value = false;
getConfig();
};
const closeRangeError = () => {
setTimeout(() => {
rangeError.value = false;
}, 2000);
};
onMounted(() => {
getConfig();
});
</script>
<style>
.custom-field {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.custom-field .van-field__body {
@apply bg-gray-50 rounded-lg px-3 py-2;
transition: all 0.3s ease;
}
.custom-field:focus-within .van-field__body {
@apply ring-2 ring-blue-200;
}
.van-picker__toolbar {
@apply bg-gray-50 rounded-t-lg;
}
.van-picker__confirm {
@apply text-blue-500 font-medium;
}
.van-divider {
@apply before:bg-gray-100 after:bg-gray-100;
}
/* 错误状态样式 */
.van-field--error .van-field__control {
color: #ee0a24;
}
.van-field--error .van-field__label {
color: inherit;
}
.van-field--error .van-field__body {
@apply ring-2 ring-red-200 bg-red-50;
}
</style>