diff --git a/src/components/EmptyState.vue b/src/components/EmptyState.vue
new file mode 100644
index 0000000..1b60180
--- /dev/null
+++ b/src/components/EmptyState.vue
@@ -0,0 +1,12 @@
+
+
+

+
{{ text }}
+
+
+
+
diff --git a/src/components/PriceInputPopup.vue b/src/components/PriceInputPopup.vue
index cd8127e..23a761f 100644
--- a/src/components/PriceInputPopup.vue
+++ b/src/components/PriceInputPopup.vue
@@ -15,11 +15,11 @@
@blur="onBlurPrice" class="!text-3xl" />
@@ -42,6 +42,8 @@
diff --git a/src/utils/promotionPricing.js b/src/utils/promotionPricing.js
new file mode 100644
index 0000000..57c4dd4
--- /dev/null
+++ b/src/utils/promotionPricing.js
@@ -0,0 +1,162 @@
+/**
+ * 推广定价计算工具
+ * 统一管理价格计算逻辑,供 Promote.vue 和 PriceInputPopup.vue 共享
+ */
+
+/**
+ * 安全截断小数位数(四舍五入)
+ * 用于前端显示和发送给后端的价格格式化
+ * @param {number} num - 要格式化的数值
+ * @param {number} decimals - 保留小数位数,默认2位
+ * @returns {string} 格式化后的字符串,如 "12.34"
+ */
+export function safeTruncate(num, decimals = 2) {
+ if (Number.isNaN(num) || !Number.isFinite(num)) {
+ return '0.00'
+ }
+
+ const factor = 10 ** decimals
+ const scaled = Math.round(num * factor)
+ return (scaled / factor).toFixed(decimals)
+}
+
+/**
+ * 将元转为分(整数),避免浮点运算精度问题
+ * @param {number} num - 元为单位的数值
+ * @returns {number} 分为单位的整数
+ */
+function toTruncatedCents(num) {
+ if (Number.isNaN(num) || !Number.isFinite(num)) {
+ return 0
+ }
+ return Math.round(num * 100)
+}
+
+/**
+ * 将分转回元,返回固定2位小数字符串
+ * @param {number} cents - 分为单位的整数
+ * @returns {string} 元为单位的字符串,如 "12.34"
+ */
+function centsToFixed(cents) {
+ return (cents / 100).toFixed(2)
+}
+
+/**
+ * 计算提价成本
+ * 当客户查询价超过价格阈值时,超出部分按费率收取
+ * @param {number} price - 客户查询价
+ * @param {object} config - 产品配置
+ * @returns {number} 提价成本(元)
+ */
+function calculateRaiseCost(price, config) {
+ const priceThreshold = Number(config.price_threshold) || 0
+ const priceFeeRate = Number(config.price_fee_rate) || 0
+
+ if (priceThreshold <= 0 || priceFeeRate <= 0) {
+ return 0
+ }
+
+ if (price <= priceThreshold) {
+ return 0
+ }
+
+ return (price - priceThreshold) * priceFeeRate
+}
+
+/**
+ * 计算推广定价(核心函数)
+ * 所有金额先转为分(整数)再计算,避免浮点精度问题
+ *
+ * @param {number|string} priceInput - 客户查询价
+ * @param {object} config - 产品配置,包含以下字段:
+ * - actual_base_price: 实际底价(含等级加成)
+ * - price_threshold: 价格阈值(超过此价格收取提价费用)
+ * - price_fee_rate: 提价费率(0-1之间的小数)
+ * @returns {{ costPrice: string, baseCost: string, raiseCost: string, promotionRevenue: string }}
+ * 各项价格均为 "xx.xx" 格式的字符串
+ */
+export function calculatePromotionPricing(priceInput, config) {
+ if (!config) {
+ return {
+ costPrice: '0.00',
+ baseCost: '0.00',
+ raiseCost: '0.00',
+ promotionRevenue: '0.00',
+ }
+ }
+
+ const price = Number(priceInput)
+ if (!Number.isFinite(price)) {
+ return {
+ costPrice: '0.00',
+ baseCost: '0.00',
+ raiseCost: '0.00',
+ promotionRevenue: '0.00',
+ }
+ }
+
+ const baseCost = Number(config.actual_base_price) || 0
+ const raiseCost = calculateRaiseCost(price, config)
+ const totalCost = baseCost + raiseCost
+
+ // 转为分计算,避免浮点精度问题
+ const priceCents = toTruncatedCents(price)
+ const baseCostCents = toTruncatedCents(baseCost)
+ const raiseCostCents = toTruncatedCents(raiseCost)
+ const totalCostCents = toTruncatedCents(totalCost)
+ const revenueCents = Math.max(0, priceCents - totalCostCents)
+
+ return {
+ costPrice: centsToFixed(totalCostCents),
+ baseCost: centsToFixed(baseCostCents),
+ raiseCost: centsToFixed(raiseCostCents),
+ promotionRevenue: centsToFixed(revenueCents),
+ }
+}
+
+/**
+ * 验证价格输入
+ * @param {number|string} currentPrice - 当前输入的价格
+ * @param {object} productConfig - 产品配置
+ * @param {number} defaultPrice - 默认价格(用于无效输入时的回退)
+ * @returns {{ newPrice: number, message: string }}
+ */
+export function validatePrice(currentPrice, productConfig, defaultPrice) {
+ if (!productConfig) {
+ return { newPrice: Number(defaultPrice || 0), message: '产品配置未就绪,请稍后再试' }
+ }
+
+ const min = Number(productConfig.price_range_min) || 0
+ const max = Number(productConfig.price_range_max) || Infinity
+ let newPrice = Number(currentPrice)
+ let message = ''
+
+ // 处理无效输入
+ if (Number.isNaN(newPrice)) {
+ newPrice = Number(defaultPrice || 0)
+ return { newPrice, message: '输入无效,请输入价格' }
+ }
+
+ // 处理小数位数(兼容科学计数法)
+ try {
+ const priceString = newPrice.toString()
+ const [_, decimalPart = ''] = priceString.split('.')
+ if (decimalPart.length > 2) {
+ newPrice = Number.parseFloat(safeTruncate(newPrice))
+ message = '价格已自动格式化为两位小数'
+ }
+ } catch (e) {
+ console.error('价格格式化异常:', e)
+ }
+
+ // 范围校验
+ if (newPrice < min) {
+ message = `价格不能低于 ${min} 元`
+ newPrice = min
+ } else if (newPrice > max) {
+ message = `价格不能高于 ${max} 元`
+ newPrice = max
+ }
+
+ return { newPrice, message }
+}
diff --git a/src/views/AgentPromoteDetails.vue b/src/views/AgentPromoteDetails.vue
index 661cc5d..17acb27 100644
--- a/src/views/AgentPromoteDetails.vue
+++ b/src/views/AgentPromoteDetails.vue
@@ -1,7 +1,7 @@
-
+
{{ item.create_time || '-' }}
@@ -19,11 +19,15 @@
+
+
+
diff --git a/src/views/Withdraw.vue b/src/views/Withdraw.vue
index 23fad58..454d95a 100644
--- a/src/views/Withdraw.vue
+++ b/src/views/Withdraw.vue
@@ -13,105 +13,123 @@
根据相关规定,提现功能需要完成实名认证后才能使用,提现金额将转入您实名认证的账户中。
+ style="background-color: var(--van-theme-primary);" @click="openRealNameAuth">
立即实名认证