This commit is contained in:
2026-04-30 16:06:57 +08:00
parent a4afe70d46
commit 94bcbc8d8f
10 changed files with 249 additions and 443 deletions

View File

@@ -7,12 +7,12 @@ export default defineUniPages({
{ path: 'pages/privacy-consent', style: { navigationStyle: 'custom', navigationBarTitleText: '隐私政策授权' } }, { path: 'pages/privacy-consent', style: { navigationStyle: 'custom', navigationBarTitleText: '隐私政策授权' } },
{ path: 'pages/agent', style: { navigationBarTitleText: '代理中心' } }, { path: 'pages/agent', style: { navigationBarTitleText: '代理中心' } },
{ path: 'pages/agent-manage-agreement', style: { navigationBarTitleText: '代理管理协议', navigationStyle: 'default' } }, { path: 'pages/agent-manage-agreement', style: { navigationBarTitleText: '代理管理协议', navigationStyle: 'default' } },
{ path: 'pages/agent-promote-details', auth: true, style: { navigationBarTitleText: '直推收益明细' } }, { path: 'pages/agent-promote-details', auth: true, style: { navigationBarTitleText: '收益明细' } },
{ path: 'pages/agent-rewards-details', auth: true, style: { navigationBarTitleText: '代理奖励收益明细' } }, { path: 'pages/agent-rewards-details', auth: true, style: { navigationBarTitleText: '奖励明细' } },
{ path: 'pages/agent-service-agreement', style: { navigationBarTitleText: '信息技术服务合同', navigationStyle: 'default' } }, { path: 'pages/agent-service-agreement', style: { navigationBarTitleText: '信息技术服务合同', navigationStyle: 'default' } },
{ path: 'pages/agent-vip', auth: true, style: { navigationBarTitleText: '代理会员' } }, { path: 'pages/agent-vip', auth: true, style: { navigationBarTitleText: '代理会员' } },
{ path: 'pages/agent-vip-apply', auth: true, style: { navigationBarTitleText: 'VIP申请' } }, { path: 'pages/agent-vip-apply', auth: true, style: { navigationBarTitleText: 'VIP代理申请' } },
{ path: 'pages/agent-vip-config', auth: true, style: { navigationBarTitleText: 'VIP配置' } }, { path: 'pages/agent-vip-config', auth: true, style: { navigationBarTitleText: '代理会员报告配置' } },
{ path: 'pages/authorization', style: { navigationBarTitleText: '授权书', navigationStyle: 'default' } }, { path: 'pages/authorization', style: { navigationBarTitleText: '授权书', navigationStyle: 'default' } },
{ path: 'pages/help', style: { navigationBarTitleText: '帮助中心' } }, { path: 'pages/help', style: { navigationBarTitleText: '帮助中心' } },
{ path: 'pages/help-detail', style: { navigationBarTitleText: '帮助详情' } }, { path: 'pages/help-detail', style: { navigationBarTitleText: '帮助详情' } },
@@ -27,11 +27,11 @@ export default defineUniPages({
{ path: 'pages/not-found', style: { navigationBarTitleText: '页面不存在' } }, { path: 'pages/not-found', style: { navigationBarTitleText: '页面不存在' } },
{ path: 'pages/payment-result', auth: true, style: { navigationBarTitleText: '支付结果' } }, { path: 'pages/payment-result', auth: true, style: { navigationBarTitleText: '支付结果' } },
{ path: 'pages/privacy-policy', style: { navigationBarTitleText: '隐私政策', navigationStyle: 'default' } }, { path: 'pages/privacy-policy', style: { navigationBarTitleText: '隐私政策', navigationStyle: 'default' } },
{ path: 'pages/promote', auth: true, style: { navigationBarTitleText: '推广管理' } }, { path: 'pages/promote', auth: true, style: { navigationBarTitleText: '推广' } },
{ path: 'pages/report-example-webview', style: { navigationBarTitleText: '示例报告', navigationStyle: 'default' } }, { path: 'pages/report-example-webview', style: { navigationBarTitleText: '示例报告', navigationStyle: 'default' } },
{ path: 'pages/report-result-webview', auth: true, style: { navigationBarTitleText: '报告结果', navigationStyle: 'default' } }, { path: 'pages/report-result-webview', auth: true, style: { navigationBarTitleText: '报告结果', navigationStyle: 'default' } },
{ path: 'pages/report-share', style: { navigationBarTitleText: '报告分享', navigationStyle: 'default' } }, { path: 'pages/report-share', style: { navigationBarTitleText: '报告分享', navigationStyle: 'default' } },
{ path: 'pages/subordinate-detail', auth: true, style: { navigationBarTitleText: '下级详情' } }, { path: 'pages/subordinate-detail', auth: true, style: { navigationBarTitleText: '下级贡献详情' } },
{ path: 'pages/subordinate-list', auth: true, style: { navigationBarTitleText: '我的下级' } }, { path: 'pages/subordinate-list', auth: true, style: { navigationBarTitleText: '我的下级' } },
{ path: 'pages/user-agreement', style: { navigationBarTitleText: '用户协议', navigationStyle: 'default' } }, { path: 'pages/user-agreement', style: { navigationBarTitleText: '用户协议', navigationStyle: 'default' } },
{ path: 'pages/withdraw', auth: true, style: { navigationBarTitleText: '提现' } }, { path: 'pages/withdraw', auth: true, style: { navigationBarTitleText: '提现' } },

1
src/components.d.ts vendored
View File

@@ -10,6 +10,7 @@ declare module 'vue' {
AccountCancelAgreement: typeof import('./components/AccountCancelAgreement.vue')['default'] AccountCancelAgreement: typeof import('./components/AccountCancelAgreement.vue')['default']
AgentApplicationForm: typeof import('./components/AgentApplicationForm.vue')['default'] AgentApplicationForm: typeof import('./components/AgentApplicationForm.vue')['default']
BindPhoneDialog: typeof import('./components/BindPhoneDialog.vue')['default'] BindPhoneDialog: typeof import('./components/BindPhoneDialog.vue')['default']
EmptyState: typeof import('./components/EmptyState.vue')['default']
ImageSaveGuide: typeof import('./components/ImageSaveGuide.vue')['default'] ImageSaveGuide: typeof import('./components/ImageSaveGuide.vue')['default']
InquireForm: typeof import('./components/InquireForm.vue')['default'] InquireForm: typeof import('./components/InquireForm.vue')['default']
LoginDialog: typeof import('./components/LoginDialog.vue')['default'] LoginDialog: typeof import('./components/LoginDialog.vue')['default']

View File

@@ -0,0 +1,12 @@
<template>
<view class="flex flex-col items-center justify-center py-16">
<image src="/static/images/empty.svg" mode="aspectFit" class="w-48 h-48 mb-4" />
<text class="text-gray-400 text-base">{{ text }}</text>
</view>
</template>
<script setup>
defineProps({
text: { type: String, default: '暂无数据' }
})
</script>

View File

@@ -242,11 +242,9 @@ onReachBottom(() => {
<view v-if="loading" class="py-4 text-center text-sm text-gray-400"> <view v-if="loading" class="py-4 text-center text-sm text-gray-400">
加载中... 加载中...
</view> </view>
<view v-else-if="!data.list.length" class="py-4 text-center text-sm text-gray-400"> <EmptyState v-else-if="!data.list.length" text="暂无收益明细" />
暂无记录
</view>
</view> </view>
<wd-loadmore :state="loadMoreState" @reload="getData" /> <wd-loadmore v-if="data.list.length > 0" :state="loadMoreState" @reload="getData" />
</view> </view>
</template> </template>

View File

@@ -138,11 +138,9 @@ onReachBottom(() => {
<view v-if="loading" class="py-4 text-center text-sm text-gray-400"> <view v-if="loading" class="py-4 text-center text-sm text-gray-400">
加载中... 加载中...
</view> </view>
<view v-else-if="!data.list.length" class="py-4 text-center text-sm text-gray-400"> <EmptyState v-else-if="!data.list.length" text="暂无奖励明细" />
暂无记录
</view>
</view> </view>
<wd-loadmore :state="loadMoreState" @reload="getData" /> <wd-loadmore v-if="data.list.length > 0" :state="loadMoreState" @reload="getData" />
</view> </view>
</template> </template>

View File

@@ -1,5 +1,135 @@
<template>
<view class="p-4 mx-auto min-h-screen">
<!-- 标题部分 -->
<view class="card mb-4 p-4 bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg shadow-lg text-white">
<text class="text-2xl font-extrabold mb-2 block">专业报告定价配置</text>
<text class="opacity-90 block">请选择报告类型并设置定价策略助您实现精准定价</text>
</view>
<view class="mb-4 bg-white rounded-lg overflow-hidden px-4 flex items-center justify-between">
<span class="text-blue-600 font-medium text-sm">📝 选择报告</span>
<wd-picker custom-class="flex-1" v-model="selectedReportId" :columns="[reportOptions]" title="选择报告类型"
placeholder="请选择报告类型" :disabled="!reportOptions.length" @confirm="onConfirmType" />
</view>
<view v-if="selectedReportText" class="space-y-6">
<!-- 配置卡片 -->
<view class="card">
<!-- 当前报告标题 -->
<view class="flex items-center mb-6">
<text class="text-xl font-semibold text-gray-800">
{{ selectedReportText }}配置
</text>
</view>
<!-- 显示当前产品的基础成本信息 -->
<view v-if="productConfigData && productConfigData.cost_price"
class="px-4 py-2 mb-4 bg-gray-50 border border-gray-200 rounded-lg shadow-sm">
<text class="text-lg font-semibold text-gray-700 block">报告基础配置信息</text>
<view class="mt-1 text-sm text-gray-600">
<text class="block">基础成本价<text class="font-medium">{{ productConfigData.cost_price }}</text> </text>
<text class="block">最高设定金额上限<text class="font-medium">{{ productConfigData.price_range_max }}</text>
</text>
<text class="block">最高设定比例上限<text class="font-medium">{{ priceRatioMax }}</text> %</text>
</view>
</view>
<!-- 分隔线 -->
<view class="my-6 flex items-center justify-center">
<view class="bg-gray-200 h-px flex-1" />
<text class="mx-2 text-gray-400 text-sm">成本策略配置</text>
<view class="bg-gray-200 h-px flex-1" />
</view>
<!-- 表单部分 -->
<wd-form>
<!-- 加价金额 -->
<wd-form-item label="🚀 加价金额" prop="price_increase_amount" custom-class="vip-form-item">
<view class="vip-input-row">
<view class="vip-input-wrap">
<wd-input v-model="configData.price_increase_amount" type="number" placeholder="0"
custom-class="vip-wd-input" @blur="validateDecimal('price_increase_amount')" />
</view>
<text class="vip-input-unit"></text>
</view>
</wd-form-item>
<view class="text-xs text-gray-400 mt-1">
<text class="block">提示最大加价金额为{{ priceIncreaseAmountMax }}</text>
<text class="block">说明加价金额是在基础成本价上增加的额外费用决定下级报告的最低定价您将获得所有输入的金额利润</text>
</view>
<!-- 分隔线 -->
<view class="my-6 flex items-center justify-center">
<view class="bg-gray-200 h-px flex-1" />
<text class="mx-2 text-gray-400 text-sm">定价策略配置</text>
<view class="bg-gray-200 h-px flex-1" />
</view>
<!-- 定价区间最低 -->
<wd-form-item label="💰 定价区间最低" prop="price_range_from" custom-class="vip-form-item">
<view class="vip-input-row">
<view class="vip-input-wrap">
<wd-input v-model="configData.price_range_from" type="number" placeholder="0"
custom-class="vip-wd-input" @blur="() => { validateDecimal('price_range_from'); validateRange(); }" />
</view>
<text class="vip-input-unit"></text>
</view>
</wd-form-item>
<view class="text-xs text-gray-400 mt-1">
<text class="block">提示定价区间最低不能低于基础最低 {{ productConfigData?.price_range_min || 0 }} + 加价金额</text>
<text class="block">说明设定的定价区间最低为定价区间的起始值若下级设定的报告金额在区间内则区间内部分将按比例获得收益</text>
</view>
<!-- 定价区间最高 -->
<wd-form-item label="💰 定价区间最高" prop="price_range_to" custom-class="vip-form-item">
<view class="vip-input-row">
<view class="vip-input-wrap">
<wd-input v-model="configData.price_range_to" type="number" placeholder="0" custom-class="vip-wd-input"
@blur="() => { validateDecimal('price_range_to'); validateRange(); }" />
</view>
<text class="vip-input-unit"></text>
</view>
</wd-form-item>
<view class="text-xs text-gray-400 mt-1">
<text class="block">提示定价区间最高不能超过上限{{ productConfigData?.price_range_max || 0 }}和大于定价区间最低{{
priceIncreaseMax
}}</text>
<text class="block">说明设定的定价区间最高为定价区间的结束值若下级设定的报告金额在区间内则区间内部分将按比例获得收益</text>
</view>
<!-- 收取比例 -->
<wd-form-item label="📈 收取比例" prop="price_ratio" custom-class="vip-form-item">
<view class="vip-input-row">
<view class="vip-input-wrap">
<wd-input v-model="configData.price_ratio" type="number" placeholder="0" custom-class="vip-wd-input"
@blur="validateRatio" />
</view>
<text class="vip-input-unit">%</text>
</view>
</wd-form-item>
<view class="text-xs text-gray-400 mt-1">
<text class="block">提示最大收取比例为{{ priceRatioMax }}%</text>
<text class="block">说明收取比例表示对定价区间内即报告金额超过定价区间最低小于定价区间最高的部分的金额按此比例进行利润分成</text>
</view>
</wd-form>
</view>
<!-- 保存按钮 -->
<button type="primary" class="bg-blue-500 text-white py-1 rounded-xl w-full" @click="handleSubmit">
保存当前报告配置
</button>
</view>
<!-- 未选择提示 -->
<view v-else class="text-center py-12">
<text class="text-gray-400 text-4xl block mb-4"></text>
<text class="text-gray-500 block">请先选择需要配置的报告类型</text>
</view>
</view>
</template>
<script setup> <script setup>
import { computed, onMounted, reactive, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
definePage({ layout: 'default', auth: true }) definePage({ layout: 'default', auth: true })
// 报告类型选项:由后端动态返回 // 报告类型选项:由后端动态返回
@@ -26,7 +156,6 @@ const priceRatioMax = ref(null)
const rangeError = ref(false) const rangeError = ref(false)
const ratioError = ref(false) const ratioError = ref(false)
const increaseError = ref(false) const increaseError = ref(false)
const activeField = ref('')
function showToast(message) { function showToast(message) {
if (!message) if (!message)
@@ -70,30 +199,21 @@ function validateDecimal(field) {
// 价格区间验证(在 @blur 中调用) // 价格区间验证(在 @blur 中调用)
function validateRange() { function validateRange() {
if ( if (!productConfigData.value || priceIncreaseMax.value == null)
configData.value.price_range_from === null return
|| configData.value.price_range_to === null if (isNaN(configData.value.price_range_from) || isNaN(configData.value.price_range_to))
) {
rangeError.value = false
return return
}
if (
isNaN(configData.value.price_range_from)
|| isNaN(configData.value.price_range_to)
) {
return
}
const additional = configData.value.price_increase_amount || 0 const additional = configData.value.price_increase_amount || 0
const minAllowed = Number.parseFloat( const minAllowed = Number.parseFloat(
( (
Number(productConfigData.value.cost_price) + Number(additional) Number(productConfigData.value.cost_price) + Number(additional)
).toFixed(2), ).toFixed(2),
) // 使用成本价作为最小值 )
const maxAllowed = productConfigData.value.price_range_max // 使用产品配置中的最大价格作为最大值 const maxAllowed = productConfigData.value.price_range_max
if (configData.value.price_range_from < minAllowed) { if (configData.value.price_range_from < minAllowed) {
configData.value.price_range_from = minAllowed configData.value.price_range_from = minAllowed
showToast(`最低金额不能低于成本价 ${minAllowed}`) showToast(`定价区间最低不能低于成本价 ${minAllowed}`)
rangeError.value = true rangeError.value = true
closeRangeError() closeRangeError()
@@ -107,7 +227,7 @@ function validateRange() {
} }
if (configData.value.price_range_to < configData.value.price_range_from) { if (configData.value.price_range_to < configData.value.price_range_from) {
showToast('最高金额不能低于最低金额') showToast('定价区间最高不能低于定价区间最低')
if ( if (
configData.value.price_range_from + priceIncreaseMax.value configData.value.price_range_from + priceIncreaseMax.value
> maxAllowed > maxAllowed
@@ -116,7 +236,7 @@ function validateRange() {
} }
else { else {
configData.value.price_range_to configData.value.price_range_to
= configData.value.price_range_from + priceIncreaseMax.value = configData.value.price_range_from + priceIncreaseMax.value
} }
rangeError.value = true rangeError.value = true
closeRangeError() closeRangeError()
@@ -130,20 +250,18 @@ function validateRange() {
if (diff > priceIncreaseMax.value) { if (diff > priceIncreaseMax.value) {
showToast(`价格区间最大差值为${priceIncreaseMax.value}`) showToast(`价格区间最大差值为${priceIncreaseMax.value}`)
configData.value.price_range_to configData.value.price_range_to
= configData.value.price_range_from + priceIncreaseMax.value = configData.value.price_range_from + priceIncreaseMax.value
closeRangeError() closeRangeError()
return return
} }
if (configData.value.price_range_to > maxAllowed) { if (configData.value.price_range_to > maxAllowed) {
configData.value.price_range_to = maxAllowed configData.value.price_range_to = maxAllowed
showToast(`最高金额不能超过 ${maxAllowed}`) showToast(`定价区间最高不能超过 ${maxAllowed}`)
closeRangeError() closeRangeError()
} }
if (!rangeError.value) { rangeError.value = false
rangeError.value = false
}
} }
// 收取比例验证(修改为保留两位小数,不再四舍五入取整) // 收取比例验证(修改为保留两位小数,不再四舍五入取整)
@@ -193,17 +311,15 @@ async function getConfig() {
id: respConfigData.product_id, id: respConfigData.product_id,
price_range_from: respConfigData.price_range_from || null, price_range_from: respConfigData.price_range_from || null,
price_range_to: respConfigData.price_range_to || null, price_range_to: respConfigData.price_range_to || null,
price_ratio: respConfigData.price_ratio * 100 || null, // 转换为百分比 price_ratio: respConfigData.price_ratio != null ? respConfigData.price_ratio * 100 : null,
price_increase_amount: price_increase_amount:
respConfigData.price_increase_amount || null, respConfigData.price_increase_amount || null,
} }
// const respProductConfigData = data.value.data.product_config
productConfigData.value = data.value.data.product_config productConfigData.value = data.value.data.product_config
// 设置动态限制值
priceIncreaseMax.value = data.value.data.price_increase_max priceIncreaseMax.value = data.value.data.price_increase_max
priceIncreaseAmountMax.value priceIncreaseAmountMax.value
= data.value.data.price_increase_amount = data.value.data.price_increase_amount
priceRatioMax.value = data.value.data.price_ratio * 100 priceRatioMax.value = data.value.data.price_ratio != null ? data.value.data.price_ratio * 100 : null
} }
} }
catch (error) { catch (error) {
@@ -242,35 +358,31 @@ async function handleSubmit() {
showToast('保存失败,请稍后重试') showToast('保存失败,请稍后重试')
} }
} }
// 最终验证函数 // 最终验证函数
function finalValidation() { function finalValidation() {
// 校验最低金额不能为空且大于0
if ( if (
!configData.value.price_range_from !configData.value.price_range_from
|| configData.value.price_range_from <= 0 || configData.value.price_range_from <= 0
) { ) {
showToast('最低金额不能为空') showToast('定价区间最低不能为空')
return false return false
} }
// 校验最高金额不能为空且大于0
if ( if (
!configData.value.price_range_to !configData.value.price_range_to
|| configData.value.price_range_to <= 0 || configData.value.price_range_to <= 0
) { ) {
showToast('最高金额不能为空') showToast('定价区间最高不能为空')
return false return false
} }
// 校验收取比例不能为空且大于0
if (!configData.value.price_ratio || configData.value.price_ratio <= 0) { if (!configData.value.price_ratio || configData.value.price_ratio <= 0) {
showToast('收取比例不能为空') showToast('收取比例不能为空')
return false return false
} }
// 验证最低金额必须小于最高金额
if (configData.value.price_range_from >= configData.value.price_range_to) { if (configData.value.price_range_from >= configData.value.price_range_to) {
showToast('最低金额必须小于最高金额') showToast('定价区间最低必须小于定价区间最高')
return false return false
} }
// 验证价格区间差值不能超过最大允许差值
const finalDiff = Number.parseFloat( const finalDiff = Number.parseFloat(
( (
configData.value.price_range_to - configData.value.price_range_from configData.value.price_range_to - configData.value.price_range_from
@@ -280,24 +392,22 @@ function finalValidation() {
showToast(`价格区间最大差值为${priceIncreaseMax.value}`) showToast(`价格区间最大差值为${priceIncreaseMax.value}`)
return false return false
} }
// 验证最高金额不能超过产品配置中设定的上限
if ( if (
configData.value.price_range_to configData.value.price_range_to
> productConfigData.value.price_range_max > productConfigData.value.price_range_max
) { ) {
showToast( showToast(
`最高金额不能超过${productConfigData.value.price_range_max}`, `定价区间最高不能超过${productConfigData.value.price_range_max}`,
) )
return false return false
} }
// 验证最低金额不能低于成本价+加价金额(加价金额允许为空)
const additional = configData.value.price_increase_amount || 0 const additional = configData.value.price_increase_amount || 0
if ( if (
configData.value.price_range_from configData.value.price_range_from
< productConfigData.value.cost_price + additional < productConfigData.value.cost_price + additional
) { ) {
showToast( showToast(
`最低金额不能低于成本价${productConfigData.value.cost_price + additional `定价区间最低不能低于成本价${productConfigData.value.cost_price + additional
}`, }`,
) )
return false return false
@@ -326,20 +436,13 @@ function onConfirmType(e) {
getConfig() getConfig()
} }
function closeRangeError() { function closeRangeError() {
setTimeout(() => { setTimeout(() => {
rangeError.value = false rangeError.value = false
}, 2000) }, 2000)
} }
function onFieldFocus(field) {
activeField.value = field
}
function onFieldBlur(field) {
if (activeField.value === field)
activeField.value = ''
}
onMounted(() => { onMounted(() => {
loadReportOptions() loadReportOptions()
}) })
@@ -381,358 +484,72 @@ async function loadReportOptions() {
} }
</script> </script>
<template>
<view class="mx-auto max-w-3xl min-h-screen p-4">
<!-- 标题部分 -->
<view class="card mb-4 rounded-lg from-blue-500 to-blue-600 bg-gradient-to-r p-4 text-white shadow-lg">
<text class="mb-2 text-2xl font-extrabold">
专业报告定价配置
</text>
<text class="opacity-90">
请选择报告类型并设置定价策略助您实现精准定价
</text>
</view>
<view class="mb-4">
<wd-picker
v-model="selectedReportId"
label="报告类型"
label-width="100px"
title="选择报告类型"
:columns="[reportOptions]"
placeholder="请选择报告类型"
:disabled="!reportOptions.length"
@confirm="onConfirmType"
/>
</view>
<view v-if="selectedReportText" class="space-y-6">
<!-- 配置卡片 -->
<view class="card">
<!-- 当前报告标题 -->
<view class="mb-6 flex items-center">
<text class="text-xl text-gray-800 font-semibold">
{{ selectedReportText }}配置
</text>
</view>
<!-- 显示当前产品的基础成本信息 -->
<view
v-if="productConfigData && productConfigData.cost_price"
class="mb-4 border border-gray-200 rounded-lg bg-gray-50 px-4 py-2 shadow-sm"
>
<view class="text-lg text-gray-700 font-semibold">
报告基础配置信息
</view>
<view class="mt-1 text-sm text-gray-600">
<view>
基础成本价<text class="font-medium">
{{
productConfigData.cost_price
}}
</text>
</view>
<!-- <view>区间起始价<text class="font-medium">{{ productConfigData.price_range_min }}</text> </view> -->
<view>
最高设定金额上限<text class="font-medium">
{{
productConfigData.price_range_max
}}
</text>
</view>
<view>
最高设定比例上限<text class="font-medium">
{{
priceRatioMax
}}
</text>
%
</view>
</view>
</view>
<!-- 分隔线 -->
<view class="section-divider my-6">
成本策略配置
</view>
<!-- 加价金额 -->
<view class="custom-field" :class="{ 'field-error': increaseError, 'field-active': activeField === 'price_increase_amount' }">
<text class="field-label">
🚀 加价金额
</text>
<view class="field-input-wrap">
<wd-input
v-model.number="configData.price_increase_amount"
type="number"
placeholder="0"
class="field-wd-input"
no-border
clearable
@focus="onFieldFocus('price_increase_amount')"
@blur="
() => {
onFieldBlur('price_increase_amount')
validateDecimal('price_increase_amount')
}
"
/>
<text class="field-unit">
</text>
</view>
</view>
<view class="mt-1 text-xs text-gray-400">
提示最大加价金额为{{ priceIncreaseAmountMax }}<br>
说明加价金额是在基础成本价上增加的额外费用决定下级报告的最低定价您将获得所有输入的金额利润
</view>
<!-- 分隔线 -->
<view class="section-divider my-6">
定价策略配置
</view>
<!-- 定价区间最低 -->
<view class="custom-field" :class="{ 'field-error': rangeError, 'field-active': activeField === 'price_range_from' }">
<text class="field-label">
💰 最低金额
</text>
<view class="field-input-wrap">
<wd-input
v-model.number="configData.price_range_from"
type="number"
placeholder="0"
class="field-wd-input"
no-border
clearable
@focus="onFieldFocus('price_range_from')"
@blur="
() => {
onFieldBlur('price_range_from')
validateDecimal('price_range_from')
validateRange()
}
"
/>
<text class="field-unit">
</text>
</view>
</view>
<view class="mt-1 text-xs text-gray-400">
提示最低金额不能低于基础最低
{{ productConfigData?.price_range_min || 0 }} +
加价金额<br>
说明设定的最低金额为定价区间的起始值若下级设定的报告金额在区间内则区间内部分将按比例获得收益
</view>
<!-- 定价区间最高 -->
<view class="custom-field" :class="{ 'field-error': rangeError, 'field-active': activeField === 'price_range_to' }">
<text class="field-label">
💰 最高金额
</text>
<view class="field-input-wrap">
<wd-input
v-model.number="configData.price_range_to"
type="number"
placeholder="0"
class="field-wd-input"
no-border
clearable
@focus="onFieldFocus('price_range_to')"
@blur="
() => {
onFieldBlur('price_range_to')
validateDecimal('price_range_to')
validateRange()
}
"
/>
<text class="field-unit">
</text>
</view>
</view>
<view class="mt-1 text-xs text-gray-400">
提示最高金额不能超过上限{{
productConfigData?.price_range_max || 0
}}和大于最低金额{{ priceIncreaseMax }}<br>
说明设定的最高金额为定价区间的结束值若下级设定的报告金额在区间内则区间内部分将按比例获得收益
</view>
<!-- 收取比例 -->
<view class="custom-field" :class="{ 'field-error': ratioError, 'field-active': activeField === 'price_ratio' }">
<text class="field-label">
📈 收取比例
</text>
<view class="field-input-wrap">
<wd-input
v-model.number="configData.price_ratio"
type="number"
placeholder="0"
class="field-wd-input"
no-border
clearable
@focus="onFieldFocus('price_ratio')"
@blur="
() => {
onFieldBlur('price_ratio')
validateRatio()
}
"
/>
<text class="field-unit">
%
</text>
</view>
</view>
<view class="mt-1 text-xs text-gray-400">
提示最大收取比例为{{ priceRatioMax }}%<br>
说明收取比例表示对定价区间内即报告金额超过最低金额小于最高金额的部分的金额按此比例进行利润分成
</view>
</view>
<!-- 保存按钮 -->
<wd-button
type="primary" block
class="h-12 rounded-xl from-blue-500 to-blue-600 bg-gradient-to-r text-white shadow-lg hover:from-blue-600 hover:to-blue-700"
@click="handleSubmit"
>
保存当前报告配置
</wd-button>
</view>
<!-- 未选择提示 -->
<view v-else class="py-12 text-center">
<text class="mb-4 block text-4xl text-gray-400">
</text>
<text class="text-gray-500">
请先选择需要配置的报告类型
</text>
</view>
</view>
</template>
<style scoped> <style scoped>
.selector { .card {
border-radius: 24rpx;
background-color: white;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
padding: 32rpx;
margin-bottom: 32rpx;
}
.space-y-6 {
display: flex;
flex-direction: column;
}
.space-y-6>view {
margin-bottom: 48rpx;
}
.space-y-6>view:last-child {
margin-bottom: 0;
}
:deep(.wd-cell__title) {
line-height: 34px !important;
}
:deep(.wd-cell__left) {
min-width: 120px !important;
max-width: 120px !important;
}
.vip-input-wrap {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; border-radius: 16rpx;
cursor: pointer; padding: 0 24rpx;
} background: #fafafa;
.selector-label {
color: #2563eb;
font-weight: 600;
}
.selector-value {
color: #4b5563;
}
.popup-title {
font-weight: 600;
margin-bottom: 12px;
}
.report-list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.report-item {
padding: 10px 12px;
border: 1px solid #e5e7eb;
border-radius: 8px;
text-align: center;
color: #374151;
}
.report-item.active {
border-color: #2563eb;
color: #2563eb;
background: #eff6ff;
}
.section-divider {
text-align: center;
color: #9ca3af;
font-size: 12px;
position: relative;
}
.section-divider::before,
.section-divider::after {
content: '';
position: absolute;
top: 50%;
width: 30%;
height: 1px;
background: #e5e7eb;
}
.section-divider::before {
left: 0;
}
.section-divider::after {
right: 0;
}
.custom-field {
margin-bottom: 12px;
}
.field-label {
display: block;
margin-bottom: 8px;
color: #4b5563;
font-weight: 500;
}
.field-input-wrap {
display: flex;
align-items: center;
background: #f3f4f6;
border-radius: 8px;
padding: 10px 12px;
transition: all 0.2s ease; transition: all 0.2s ease;
border: 1px solid #e5e7eb;
} }
:deep(.field-wd-input .wd-input__inner) { .vip-input-wrap:focus-within {
flex: 1; border-color: #93c5fd;
background: transparent !important; background: #fafafa;
text-align: left !important; box-shadow: 0 0 0 6rpx rgba(59, 130, 246, 0.32);
} }
.field-unit { .vip-input-row {
margin-left: 8px; display: flex;
align-items: center;
}
.vip-input-unit {
margin-left: 12rpx;
color: #3b82f6; color: #3b82f6;
font-weight: 500; font-weight: 500;
} }
::deep(.field-wd-input.wd-input), :deep(.vip-wd-input.wd-input),
::deep(.field-wd-input .wd-input__body), :deep(.vip-wd-input.wd-input:after),
::deep(.field-wd-input .wd-input__prefix), :deep(.vip-wd-input .wd-input),
::deep(.field-wd-input .wd-input__suffix), :deep(.vip-wd-input .wd-input__body),
::deep(.field-wd-input .wd-input__value), :deep(.vip-wd-input .wd-input__inner),
::deep(.field-wd-input .wd-input__clear) { :deep(.vip-wd-input .wd-input__value),
:deep(.vip-wd-input .wd-input__clear) {
background: transparent !important; background: transparent !important;
} border: none !important;
box-shadow: none !important;
.field-active .field-input-wrap {
border-color: #93c5fd;
background: #eff6ff;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.12);
}
.field-error .field-input-wrap {
border-color: #fca5a5;
background: #fef2f2;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
} }
</style> </style>

View File

@@ -134,11 +134,9 @@ function statusClass(state) {
<view v-if="loading" class="py-4 text-center text-sm text-gray-400"> <view v-if="loading" class="py-4 text-center text-sm text-gray-400">
加载中... 加载中...
</view> </view>
<view v-else-if="!reportList.length" class="py-4 text-center text-sm text-gray-400"> <EmptyState v-else-if="!reportList.length" text="暂无查询记录" />
暂无记录
</view>
</view> </view>
<wd-loadmore :state="loadMoreState" @reload="fetchData" /> <wd-loadmore v-if="reportList.length > 0" :state="loadMoreState" @reload="fetchData" />
</view> </view>
</template> </template>

View File

@@ -45,16 +45,8 @@ function getAgentLevelLabel(value) {
} }
// 获取收益列表 // 获取收益列表
function mergeUniqueById(oldList, newList) { function appendList(oldList, newList) {
const map = new Map() return oldList.concat(newList)
const resolveKey = (item, index) => String(item?.id ?? item?.order_id ?? `${item?.create_time || ''}_${item?.type || ''}_${index}`)
oldList.forEach((item, index) => {
map.set(resolveKey(item, index), item)
})
newList.forEach((item, index) => {
map.set(resolveKey(item, oldList.length + index), item)
})
return Array.from(map.values())
} }
async function fetchRewardDetails(reset = false) { async function fetchRewardDetails(reset = false) {
@@ -177,7 +169,7 @@ async function fetchRewardDetails(reset = false) {
total.value = data.value.data.total || 0 total.value = data.value.data.total || 0
// 处理列表数据 // 处理列表数据
const list = data.value.data.list || [] const list = data.value.data.list || []
rewardDetails.value = reset ? list : mergeUniqueById(rewardDetails.value, list) rewardDetails.value = reset ? list : appendList(rewardDetails.value, list)
const isLastPage = list.length < pageSize || rewardDetails.value.length >= total.value const isLastPage = list.length < pageSize || rewardDetails.value.length >= total.value
hasMore.value = !isLastPage hasMore.value = !isLastPage
loadMoreState.value = isLastPage ? 'finished' : 'loading' loadMoreState.value = isLastPage ? 'finished' : 'loading'
@@ -334,12 +326,8 @@ function formatNumber(num) {
贡献统计 贡献统计
</view> </view>
<view class="grid grid-cols-2 gap-3"> <view class="grid grid-cols-2 gap-3">
<view <view v-for="item in statistics" :key="item.type" class="flex items-center rounded-lg p-2"
v-for="item in statistics" :class="getRewardTypeClass(item.type).split(' ')[0]">
:key="item.type"
class="flex items-center rounded-lg p-2"
:class="getRewardTypeClass(item.type).split(' ')[0]"
>
<image :src="getRewardTypeIcon(item.type)" class="mr-2 h-5 w-5 flex-shrink-0" /> <image :src="getRewardTypeIcon(item.type)" class="mr-2 h-5 w-5 flex-shrink-0" />
<view class="flex-1"> <view class="flex-1">
<view class="text-sm font-medium" :class="getRewardTypeClass(item.type).split(' ')[1]"> <view class="text-sm font-medium" :class="getRewardTypeClass(item.type).split(' ')[1]">
@@ -359,20 +347,18 @@ function formatNumber(num) {
</view> </view>
<view class="mb-4 rounded-xl bg-white p-4 shadow-sm"> <view class="mb-4 rounded-xl bg-white p-4 shadow-sm">
<!-- 贡献记录列表 --> <!-- 贡献记录列表 -->
<view class="text-base text-gray-800 font-medium"> <view class="text-base text-gray-800 font-medium mb-3">
贡献记录 贡献记录
</view> </view>
<view class="detail-scroll p-4"> <view class="detail-scroll">
<view v-if="rewardDetails.length === 0" class="py-8 text-center text-gray-500"> <EmptyState v-if="!loading && rewardDetails.length === 0" text="暂无贡献记录" />
暂无贡献记录 <view v-for="(item, index) in rewardDetails" v-else :key="index" class="reward-item">
</view>
<view v-for="item in rewardDetails" v-else :key="item.id" class="reward-item">
<view class="mb-3 border-b border-gray-200 pb-3"> <view class="mb-3 border-b border-gray-200 pb-3">
<view class="flex items-center justify-between"> <view class="flex items-center justify-between">
<view class="flex items-center space-x-3"> <view class="flex items-center space-x-3">
<image :src="getRewardTypeIcon(item.type)" class="h-5 w-5 flex-shrink-0" /> <image :src="getRewardTypeIcon(item.type)" class="h-4 w-4 flex-shrink-0" />
<view> <view>
<view class="text-gray-800 font-medium"> <view class="text-gray-800 font-sm">
{{ getRewardTypeDescription(item.type) }} {{ getRewardTypeDescription(item.type) }}
</view> </view>
<view class="text-xs text-gray-500"> <view class="text-xs text-gray-500">
@@ -392,7 +378,7 @@ function formatNumber(num) {
加载中... 加载中...
</view> </view>
</view> </view>
<wd-loadmore :state="loadMoreState" @reload="fetchRewardDetails" /> <wd-loadmore v-if="rewardDetails.length > 0" :state="loadMoreState" @reload="fetchRewardDetails" />
</view> </view>
</view> </view>
</view> </view>

View File

@@ -128,7 +128,7 @@ onReachBottom(() => {
<template> <template>
<view class="subordinate-list"> <view class="subordinate-list">
<!-- 顶部统计卡片 --> <!-- 顶部统计卡片 -->
<view class="p-4 pb-0"> <view v-if="subordinates.length > 0" class="p-4 pb-0">
<view class="rounded-xl bg-white p-4 shadow-sm"> <view class="rounded-xl bg-white p-4 shadow-sm">
<view class="flex items-center justify-center"> <view class="flex items-center justify-center">
<view class="text-center"> <view class="text-center">
@@ -212,10 +212,8 @@ onReachBottom(() => {
<view v-if="loading" class="py-4 text-center text-sm text-gray-400"> <view v-if="loading" class="py-4 text-center text-sm text-gray-400">
加载中... 加载中...
</view> </view>
<view v-else-if="!subordinates.length" class="py-4 text-center text-sm text-gray-400"> <EmptyState v-else-if="!subordinates.length" text="暂无我的下级" />
暂无下级代理 <wd-loadmore v-if="subordinates.length > 0" :state="loadMoreState" @reload="fetchSubordinates" />
</view>
<wd-loadmore :state="loadMoreState" @reload="fetchSubordinates" />
</view> </view>
</view> </view>
</template> </template>

View File

@@ -198,11 +198,9 @@ onReachBottom(() => {
<view v-if="loading" class="py-4 text-center text-sm text-gray-400"> <view v-if="loading" class="py-4 text-center text-sm text-gray-400">
加载中... 加载中...
</view> </view>
<view v-else-if="!data.list.length" class="py-10 text-center text-sm text-gray-400"> <EmptyState v-else-if="!data.list.length" text="暂无提现记录" />
暂无提现记录
</view>
</view> </view>
<view class="pagination-wrap"> <view v-if="data.list.length" class="pagination-wrap">
<wd-loadmore :state="loadMoreState" @reload="getData" /> <wd-loadmore :state="loadMoreState" @reload="getData" />
</view> </view>
</view> </view>