f
@@ -48,7 +48,8 @@ export default defineManifestConfig({
|
||||
"privacyDescription": {
|
||||
"NSLocalNetworkUsageDescription": "需要本地网络进行服务使用",
|
||||
"NSPhotoLibraryAddUsageDescription": "需要保存二维码海报"
|
||||
}
|
||||
},
|
||||
"idfa": false
|
||||
},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs": {
|
||||
@@ -89,6 +90,18 @@ export default defineManifestConfig({
|
||||
"spotlight@3x": "static/icons/120x120.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
splashscreen: {
|
||||
androidStyle: "default",
|
||||
android: {
|
||||
hdpi: "static/launchImg/android@480x762.png",
|
||||
xhdpi: "static/launchImg/android@720x1242.png",
|
||||
xxhdpi: "static/launchImg/android@1080x1882.png"
|
||||
},
|
||||
iosStyle: "storyboard",
|
||||
ios: {
|
||||
storyboard: "static/launchImg/storyboard.zip"
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ export default defineUniPages({
|
||||
{ path: 'pages/agent', style: { navigationBarTitleText: '代理中心' } },
|
||||
{ path: 'pages/agent-manage-agreement', style: { navigationBarTitleText: '代理管理协议', navigationStyle: 'default' } },
|
||||
{ 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-vip', auth: true, style: { navigationBarTitleText: '代理会员' } },
|
||||
{ path: 'pages/agent-vip-apply', auth: true, style: { navigationBarTitleText: 'VIP申请' } },
|
||||
|
||||
10
src/auto-imports.d.ts
vendored
@@ -12,6 +12,7 @@ declare global {
|
||||
const aesEncrypt: typeof import('./utils/crypto.js')['aesEncrypt']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const calculatePromotionPricing: typeof import('./utils/promotionPricing.js')['calculatePromotionPricing']
|
||||
const chatCrypto: typeof import('./utils/chatCrypto.js')['default']
|
||||
const chatEncrypt: typeof import('./utils/chatEncrypt.js')['default']
|
||||
const clearAuthStorage: typeof import('./utils/storage')['clearAuthStorage']
|
||||
@@ -143,6 +144,7 @@ declare global {
|
||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||
const resolveUserAvatarUrl: typeof import('./utils/avatarUrl')['resolveUserAvatarUrl']
|
||||
const resolveWebToUni: typeof import('./composables/uni-router')['resolveWebToUni']
|
||||
const safeTruncate: typeof import('./utils/promotionPricing.js')['safeTruncate']
|
||||
const setAgentInfo: typeof import('./utils/storage')['setAgentInfo']
|
||||
const setAuthSession: typeof import('./utils/storage')['setAuthSession']
|
||||
const setPosterMergePending: typeof import('./utils/posterRenderMergeBridge')['setPosterMergePending']
|
||||
@@ -401,13 +403,11 @@ declare module 'vue' {
|
||||
interface GlobalComponents {}
|
||||
interface ComponentCustomProperties {
|
||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||
readonly ZoomAdapter: UnwrapRef<typeof import('./utils/zoomAdapter.js')['ZoomAdapter']>
|
||||
readonly aesDecrypt: UnwrapRef<typeof import('./utils/crypto.js')['aesDecrypt']>
|
||||
readonly aesEncrypt: UnwrapRef<typeof import('./utils/crypto.js')['aesEncrypt']>
|
||||
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
||||
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
||||
readonly chatCrypto: UnwrapRef<typeof import('./utils/chatCrypto.js')['default']>
|
||||
readonly chatEncrypt: UnwrapRef<typeof import('./utils/chatEncrypt.js')['default']>
|
||||
readonly calculatePromotionPricing: UnwrapRef<typeof import('./utils/promotionPricing.js')['calculatePromotionPricing']>
|
||||
readonly clearAuthStorage: UnwrapRef<typeof import('./utils/storage')['clearAuthStorage']>
|
||||
readonly clearToken: UnwrapRef<typeof import('./utils/storage')['clearToken']>
|
||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||
@@ -537,6 +537,7 @@ declare module 'vue' {
|
||||
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
||||
readonly resolveUserAvatarUrl: UnwrapRef<typeof import('./utils/avatarUrl')['resolveUserAvatarUrl']>
|
||||
readonly resolveWebToUni: UnwrapRef<typeof import('./composables/uni-router')['resolveWebToUni']>
|
||||
readonly safeTruncate: UnwrapRef<typeof import('./utils/promotionPricing.js')['safeTruncate']>
|
||||
readonly setAgentInfo: UnwrapRef<typeof import('./utils/storage')['setAgentInfo']>
|
||||
readonly setAuthSession: UnwrapRef<typeof import('./utils/storage')['setAuthSession']>
|
||||
readonly setPosterMergePending: UnwrapRef<typeof import('./utils/posterRenderMergeBridge')['setPosterMergePending']>
|
||||
@@ -586,7 +587,6 @@ declare module 'vue' {
|
||||
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
|
||||
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
|
||||
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||
readonly useAuthGuard: UnwrapRef<typeof import('./composables/useAuthGuard')['useAuthGuard']>
|
||||
readonly useAuthStore: UnwrapRef<typeof import('./stores/auth')['useAuthStore']>
|
||||
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
|
||||
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
|
||||
@@ -684,7 +684,6 @@ declare module 'vue' {
|
||||
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
||||
readonly usePreferredReducedTransparency: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedTransparency']>
|
||||
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
||||
readonly useQuery: UnwrapRef<typeof import('./composables/useQuery')['useQuery']>
|
||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
||||
readonly useReportWebview: UnwrapRef<typeof import('./composables/useReportWebview')['useReportWebview']>
|
||||
@@ -761,6 +760,5 @@ declare module 'vue' {
|
||||
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
|
||||
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
|
||||
readonly writePngBase64ToLocal: UnwrapRef<typeof import('./utils/appLocalFile')['writePngBase64ToLocal']>
|
||||
readonly zoomAdapter: UnwrapRef<typeof import('./utils/zoomAdapter.js')['default']>
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onUnmounted, ref } from 'vue'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { setAuthSession } from '@/utils/storage'
|
||||
|
||||
const emit = defineEmits(['login-success'])
|
||||
const dialogStore = useDialogStore()
|
||||
const userStore = useUserStore()
|
||||
const agentStore = useAgentStore()
|
||||
|
||||
const phoneNumber = ref('')
|
||||
const verificationCode = ref('')
|
||||
const password = ref('')
|
||||
const isPasswordLogin = ref(false)
|
||||
const isAgreed = ref(false)
|
||||
const isCountingDown = ref(false)
|
||||
const countdown = ref(60)
|
||||
@@ -34,12 +34,7 @@ const isPhoneNumberValid = computed(() => {
|
||||
const canLogin = computed(() => {
|
||||
if (!isPhoneNumberValid.value)
|
||||
return false
|
||||
if (isPasswordLogin.value) {
|
||||
return password.value.length >= 6
|
||||
}
|
||||
else {
|
||||
return verificationCode.value.length === 6
|
||||
}
|
||||
return verificationCode.value.length === 6
|
||||
})
|
||||
|
||||
async function sendVerificationCode() {
|
||||
@@ -83,17 +78,9 @@ async function handleLogin() {
|
||||
showToast({ message: '请输入有效的手机号' })
|
||||
return
|
||||
}
|
||||
if (isPasswordLogin.value) {
|
||||
if (password.value.length < 6) {
|
||||
showToast({ message: '密码长度不能小于6位' })
|
||||
return
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (verificationCode.value.length !== 6) {
|
||||
showToast({ message: '请输入有效的验证码' })
|
||||
return
|
||||
}
|
||||
if (verificationCode.value.length !== 6) {
|
||||
showToast({ message: '请输入有效的验证码' })
|
||||
return
|
||||
}
|
||||
if (!isAgreed.value) {
|
||||
showToast({ message: '请先同意用户协议' })
|
||||
@@ -114,6 +101,7 @@ async function performLogin() {
|
||||
setAuthSession(data.value.data)
|
||||
|
||||
await userStore.fetchUserInfo()
|
||||
await agentStore.fetchAgentStatus()
|
||||
|
||||
showToast({ message: '登录成功' })
|
||||
closeDialog()
|
||||
@@ -123,6 +111,9 @@ async function performLogin() {
|
||||
showToast(data.value.msg)
|
||||
}
|
||||
}
|
||||
else {
|
||||
showToast({ message: '登录失败' })
|
||||
}
|
||||
}
|
||||
finally {
|
||||
uni.hideLoading()
|
||||
@@ -133,8 +124,6 @@ function closeDialog() {
|
||||
dialogStore.closeLogin()
|
||||
phoneNumber.value = ''
|
||||
verificationCode.value = ''
|
||||
password.value = ''
|
||||
isPasswordLogin.value = false
|
||||
isAgreed.value = false
|
||||
isCountingDown.value = false
|
||||
countdown.value = 60
|
||||
@@ -203,7 +192,7 @@ onUnmounted(() => {
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-if="!isPasswordLogin" class="form-item">
|
||||
<view class="form-item">
|
||||
<text class="form-label">
|
||||
验证码
|
||||
</text>
|
||||
@@ -232,27 +221,6 @@ onUnmounted(() => {
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="form-item">
|
||||
<text class="form-label">
|
||||
密码
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="password"
|
||||
class="phone-wd-input"
|
||||
type="text"
|
||||
show-password
|
||||
placeholder="请输入密码"
|
||||
no-border
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="flex items-center justify-end py-1">
|
||||
<text class="switch-login-type" @click="isPasswordLogin = !isPasswordLogin">
|
||||
{{ isPasswordLogin ? '验证码登录' : '密码登录' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-wrapper">
|
||||
<wd-checkbox v-model="isAgreed" shape="square" size="18px" />
|
||||
<text class="agreement-text">
|
||||
@@ -393,8 +361,4 @@ onUnmounted(() => {
|
||||
letter-spacing: 0.25rem;
|
||||
}
|
||||
|
||||
.switch-login-type {
|
||||
font-size: 0.875rem;
|
||||
color: #2563eb;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup>
|
||||
import { calculatePromotionPricing, safeTruncate } from '@/utils/promotionPricing'
|
||||
|
||||
const props = defineProps({
|
||||
defaultPrice: {
|
||||
type: Number,
|
||||
@@ -34,33 +36,12 @@ watch(show, (visible) => {
|
||||
price.value = Number(defaultPrice.value || 0)
|
||||
})
|
||||
|
||||
const costPrice = computed(() => {
|
||||
if (!productConfig.value)
|
||||
return 0.00
|
||||
// 平台定价成本
|
||||
let platformPricing = 0
|
||||
platformPricing += productConfig.value.cost_price
|
||||
if (price.value > productConfig.value.p_pricing_standard) {
|
||||
platformPricing += (price.value - productConfig.value.p_pricing_standard) * productConfig.value.p_overpricing_ratio
|
||||
}
|
||||
|
||||
if (productConfig.value.a_pricing_standard > platformPricing && productConfig.value.a_pricing_end > platformPricing && productConfig.value.a_overpricing_ratio > 0) {
|
||||
if (price.value > productConfig.value.a_pricing_standard) {
|
||||
if (price.value > productConfig.value.a_pricing_end) {
|
||||
platformPricing += (productConfig.value.a_pricing_end - productConfig.value.a_pricing_standard) * productConfig.value.a_overpricing_ratio
|
||||
}
|
||||
else {
|
||||
platformPricing += (price.value - productConfig.value.a_pricing_standard) * productConfig.value.a_overpricing_ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return safeTruncate(platformPricing)
|
||||
const pricingResult = computed(() => {
|
||||
return calculatePromotionPricing(price.value, productConfig.value)
|
||||
})
|
||||
|
||||
const promotionRevenue = computed(() => {
|
||||
return safeTruncate(price.value - costPrice.value)
|
||||
})
|
||||
const costPrice = computed(() => pricingResult.value.costPrice)
|
||||
const promotionRevenue = computed(() => pricingResult.value.promotionRevenue)
|
||||
|
||||
/** APP 端 placeholder 需用原生 style,否则字号易偏小 */
|
||||
const PRICE_PLACEHOLDER_STYLE = 'color:#9ca3af;font-size:40rpx;font-weight:500;'
|
||||
@@ -107,16 +88,6 @@ function validatePrice(currentPrice) {
|
||||
}
|
||||
return { newPrice, message }
|
||||
}
|
||||
function safeTruncate(num, decimals = 2) {
|
||||
if (Number.isNaN(num) || !Number.isFinite(num))
|
||||
return '0.00'
|
||||
|
||||
const factor = 10 ** decimals
|
||||
const scaled = Math.trunc(num * factor)
|
||||
const truncated = scaled / factor
|
||||
|
||||
return truncated.toFixed(decimals)
|
||||
}
|
||||
const isManualConfirm = ref(false)
|
||||
function onConfirm() {
|
||||
if (!hasProductConfig.value) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
definePage({ layout: 'default', auth: true })
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
// 颜色配置(根据产品名称映射)
|
||||
const typeColors = {
|
||||
小微企业: { bg: 'bg-blue-100', text: 'text-blue-800', dot: 'bg-blue-500' },
|
||||
@@ -40,6 +41,8 @@ const data = ref({
|
||||
list: [],
|
||||
})
|
||||
const loading = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const loadMoreState = ref('loading')
|
||||
|
||||
// 获取颜色样式
|
||||
function getReportTypeStyle(name) {
|
||||
@@ -119,16 +122,48 @@ function getStatusStyle(item) {
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
async function getData() {
|
||||
function mergeUniqueById(oldList, newList) {
|
||||
const map = new Map()
|
||||
const resolveKey = (item, index) => String(item?.id ?? item?.order_id ?? `${item?.create_time || ''}_${item?.status || ''}_${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 getData(reset = false) {
|
||||
try {
|
||||
if (loading.value)
|
||||
return
|
||||
if (!reset && !hasMore.value)
|
||||
return
|
||||
loading.value = true
|
||||
if (reset) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadMoreState.value = 'loading'
|
||||
data.value = { total: 0, list: [] }
|
||||
}
|
||||
const { data: res, error } = await useApiFetch(
|
||||
`/agent/commission?page=${page.value}&page_size=${pageSize.value}`,
|
||||
{ silent: true },
|
||||
).get().json()
|
||||
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
data.value = res.value.data
|
||||
const incoming = res.value.data.list || []
|
||||
data.value.total = res.value.data.total || 0
|
||||
data.value.list = reset ? incoming : mergeUniqueById(data.value.list, incoming)
|
||||
const isLastPage = incoming.length < pageSize.value || data.value.list.length >= data.value.total
|
||||
hasMore.value = !isLastPage
|
||||
loadMoreState.value = isLastPage ? 'finished' : 'loading'
|
||||
if (!isLastPage)
|
||||
page.value += 1
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -136,13 +171,12 @@ async function getData() {
|
||||
}
|
||||
}
|
||||
|
||||
function onPageChange({ value }) {
|
||||
page.value = value
|
||||
getData()
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
onMounted(() => {
|
||||
getData(true)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
@@ -212,14 +246,7 @@ onMounted(() => {
|
||||
暂无记录
|
||||
</view>
|
||||
</view>
|
||||
<wd-pagination
|
||||
v-model="page"
|
||||
:total="data.total"
|
||||
:page-size="pageSize"
|
||||
show-icon
|
||||
show-message
|
||||
@change="onPageChange"
|
||||
/>
|
||||
<wd-loadmore :state="loadMoreState" @reload="getData" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -235,6 +262,6 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.detail-scroll {
|
||||
height: calc(100vh - 110px);
|
||||
min-height: calc(100vh - 110px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
definePage({ layout: 'default', auth: true })
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
// 类型映射配置
|
||||
const typeConfig = {
|
||||
descendant_promotion: {
|
||||
@@ -31,6 +32,8 @@ const data = ref({
|
||||
list: [],
|
||||
})
|
||||
const loading = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const loadMoreState = ref('loading')
|
||||
|
||||
// 类型转中文
|
||||
function typeToChinese(type) {
|
||||
@@ -49,16 +52,50 @@ function getDotColor(type) {
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
async function getData() {
|
||||
function mergeUniqueById(oldList, newList) {
|
||||
const map = new Map()
|
||||
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 getData(reset = false) {
|
||||
try {
|
||||
if (loading.value)
|
||||
return
|
||||
if (!reset && !hasMore.value)
|
||||
return
|
||||
loading.value = true
|
||||
if (reset) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadMoreState.value = 'loading'
|
||||
data.value = { total: 0, list: [] }
|
||||
}
|
||||
const { data: res, error } = await useApiFetch(
|
||||
`/agent/rewards?page=${page.value}&page_size=${pageSize.value}`,
|
||||
{ silent: true },
|
||||
).get().json()
|
||||
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
data.value = res.value.data
|
||||
const incoming = res.value.data.list || []
|
||||
data.value.total = res.value.data.total || 0
|
||||
data.value.list = reset
|
||||
? incoming
|
||||
: mergeUniqueById(data.value.list, incoming)
|
||||
const isLastPage = incoming.length < pageSize.value || data.value.list.length >= data.value.total
|
||||
hasMore.value = !isLastPage
|
||||
loadMoreState.value = isLastPage ? 'finished' : 'loading'
|
||||
if (!isLastPage)
|
||||
page.value += 1
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -66,13 +103,12 @@ async function getData() {
|
||||
}
|
||||
}
|
||||
|
||||
function onPageChange({ value }) {
|
||||
page.value = value
|
||||
getData()
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
onMounted(() => {
|
||||
getData(true)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
@@ -106,14 +142,7 @@ onMounted(() => {
|
||||
暂无记录
|
||||
</view>
|
||||
</view>
|
||||
<wd-pagination
|
||||
v-model="page"
|
||||
:total="data.total"
|
||||
:page-size="pageSize"
|
||||
show-icon
|
||||
show-message
|
||||
@change="onPageChange"
|
||||
/>
|
||||
<wd-loadmore :state="loadMoreState" @reload="getData" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -129,6 +158,6 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.reward-scroll {
|
||||
height: calc(100vh - 110px);
|
||||
min-height: calc(100vh - 110px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -132,18 +132,18 @@ const revenueData = computed(() => {
|
||||
// 计算月总收益
|
||||
const vipMonthlyTotal
|
||||
= baseRevenue
|
||||
+ vipCommissionRevenue
|
||||
+ vipFloatingRevenue
|
||||
+ vipConversionRevenue
|
||||
+ vipExtraRevenue
|
||||
+ vipCommissionRevenue
|
||||
+ vipFloatingRevenue
|
||||
+ vipConversionRevenue
|
||||
+ vipExtraRevenue
|
||||
|
||||
const svipMonthlyTotal
|
||||
= baseRevenue
|
||||
+ svipCommissionRevenue
|
||||
+ svipFloatingRevenue
|
||||
+ withdrawReward
|
||||
+ svipConversionRevenue
|
||||
+ svipExtraRevenue
|
||||
+ svipCommissionRevenue
|
||||
+ svipFloatingRevenue
|
||||
+ withdrawReward
|
||||
+ svipConversionRevenue
|
||||
+ svipExtraRevenue
|
||||
|
||||
// 计算VIP和SVIP之间的差额
|
||||
const monthlyDifference = svipMonthlyTotal - vipMonthlyTotal
|
||||
@@ -239,12 +239,6 @@ function selectType(type) {
|
||||
|
||||
// 申请VIP或SVIP
|
||||
async function applyVip() {
|
||||
// 如果是VIP想升级到SVIP,提示联系客服
|
||||
if (isVip.value && selectedType.value === 'svip') {
|
||||
contactService()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是SVIP要降级到VIP,提示不能降级
|
||||
if (isSvip.value && selectedType.value === 'vip') {
|
||||
showToast('SVIP会员不能降级到VIP会员')
|
||||
@@ -289,24 +283,20 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<view class="agent-VIP-apply min-h-screen w-full from-amber-50 via-amber-100 to-amber-50 bg-gradient-to-b pb-24">
|
||||
<!-- 装饰元素 -->
|
||||
<view
|
||||
class="absolute right-0 top-0 h-32 w-32 rounded-bl-full from-amber-300 to-amber-500 bg-gradient-to-br opacity-20"
|
||||
/>
|
||||
class="absolute right-0 top-0 h-32 w-32 rounded-bl-full from-amber-300 to-amber-500 bg-gradient-to-br opacity-20" />
|
||||
<view
|
||||
class="absolute left-0 top-40 h-16 w-16 rounded-tr-full from-amber-400 to-amber-600 bg-gradient-to-tr opacity-20"
|
||||
/>
|
||||
class="absolute left-0 top-40 h-16 w-16 rounded-tr-full from-amber-400 to-amber-600 bg-gradient-to-tr opacity-20" />
|
||||
<view
|
||||
class="absolute bottom-60 right-0 h-24 w-24 rounded-tl-full from-amber-300 to-amber-500 bg-gradient-to-bl opacity-20"
|
||||
/>
|
||||
class="absolute bottom-60 right-0 h-24 w-24 rounded-tl-full from-amber-300 to-amber-500 bg-gradient-to-bl opacity-20" />
|
||||
|
||||
<!-- 顶部标题区域 -->
|
||||
<view class="header relative px-4 pb-6 pt-8 text-center">
|
||||
<view
|
||||
class="absolute left-1/2 h-1 w-24 animate-pulse rounded-full from-amber-300 via-amber-500 to-amber-300 bg-gradient-to-r -top-2 -translate-x-1/2"
|
||||
/>
|
||||
<text class="mb-1 text-3xl text-amber-800 font-bold">
|
||||
class="absolute left-1/2 h-1 w-24 animate-pulse rounded-full from-amber-300 via-amber-500 to-amber-300 bg-gradient-to-r -top-2 -translate-x-1/2" />
|
||||
<view class="mb-1 text-3xl text-amber-800 font-bold">
|
||||
{{ isVipOrSvip ? '代理会员续费' : 'VIP代理申请' }}
|
||||
</text>
|
||||
<text class="mx-auto mt-2 max-w-xs text-sm text-amber-700">
|
||||
</view>
|
||||
<view class="mx-auto mt-2 max-w-xs text-sm text-amber-700">
|
||||
<template v-if="isVipOrSvip">
|
||||
您的会员有效期至 {{ formatExpiryTime(ExpiryTime) }},续费后有效期至
|
||||
{{ renewalExpiryTime }}
|
||||
@@ -314,20 +304,18 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<template v-else>
|
||||
平台为疯狂推广者定制的赚买计划,助您收益<text class="text-red-500 font-bold">翻倍增升</text>
|
||||
</template>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 装饰性金币图标 -->
|
||||
<view class="absolute left-4 top-6 transform -rotate-12">
|
||||
<view
|
||||
class="h-8 w-8 flex items-center justify-center rounded-full from-yellow-300 to-yellow-500 bg-gradient-to-br shadow-lg"
|
||||
>
|
||||
class="h-8 w-8 flex items-center justify-center rounded-full from-yellow-300 to-yellow-500 bg-gradient-to-br shadow-lg">
|
||||
<text class="text-xs text-white font-bold">¥</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="absolute right-6 top-10 rotate-12 transform">
|
||||
<view
|
||||
class="h-6 w-6 flex items-center justify-center rounded-full from-yellow-400 to-yellow-600 bg-gradient-to-br shadow-lg"
|
||||
>
|
||||
class="h-6 w-6 flex items-center justify-center rounded-full from-yellow-400 to-yellow-600 bg-gradient-to-br shadow-lg">
|
||||
<text class="text-xs text-white font-bold">¥</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -336,16 +324,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<!-- 选择代理类型 -->
|
||||
<view class="card-container mb-8 px-4">
|
||||
<view class="transform overflow-hidden border border-amber-100 rounded-xl bg-white shadow-lg transition-all">
|
||||
<text
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold"
|
||||
>
|
||||
<text class="relative z-10">选择代理类型</text>
|
||||
<view
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold">
|
||||
<view class="relative z-10">选择代理类型</view>
|
||||
<view class="absolute inset-0 bg-amber-500 opacity-30">
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30"
|
||||
/>
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</view>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex gap-4 p-6">
|
||||
<view
|
||||
@@ -354,8 +340,7 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
selectedType === 'vip'
|
||||
? 'border-amber-500 bg-amber-50 shadow-md'
|
||||
: 'border-gray-200 hover:border-amber-300',
|
||||
]" @click="selectType('vip')"
|
||||
>
|
||||
]" @click="selectType('vip')">
|
||||
<view class="text-xl text-amber-700 font-bold">
|
||||
VIP代理
|
||||
</view>
|
||||
@@ -365,10 +350,8 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<view class="mt-2 text-sm text-gray-600">
|
||||
标准VIP权益
|
||||
</view>
|
||||
<view
|
||||
v-if="selectedType === 'vip'"
|
||||
class="absolute h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br shadow-md -right-2 -top-2"
|
||||
>
|
||||
<view v-if="selectedType === 'vip'"
|
||||
class="absolute h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br shadow-md -right-2 -top-2">
|
||||
<wd-icon name="check" custom-style="color:#fff;font-size:14px;" />
|
||||
</view>
|
||||
</view>
|
||||
@@ -379,8 +362,7 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
selectedType === 'svip'
|
||||
? 'border-amber-500 bg-amber-50 shadow-md'
|
||||
: 'border-gray-200 hover:border-amber-300',
|
||||
]" @click="selectType('svip')"
|
||||
>
|
||||
]" @click="selectType('svip')">
|
||||
<view class="text-xl text-amber-700 font-bold">
|
||||
SVIP代理
|
||||
</view>
|
||||
@@ -390,10 +372,8 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<view class="mt-2 text-sm text-gray-600">
|
||||
超级VIP权益
|
||||
</view>
|
||||
<view
|
||||
v-if="selectedType === 'svip'"
|
||||
class="absolute h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br shadow-md -right-2 -top-2"
|
||||
>
|
||||
<view v-if="selectedType === 'svip'"
|
||||
class="absolute h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br shadow-md -right-2 -top-2">
|
||||
<wd-icon name="check" custom-style="color:#fff;font-size:14px;" />
|
||||
</view>
|
||||
</view>
|
||||
@@ -404,26 +384,22 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<!-- 六大超值权益 -->
|
||||
<view class="card-container mb-8 px-4">
|
||||
<view class="overflow-hidden border border-amber-100 rounded-xl bg-white shadow-lg">
|
||||
<text
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold"
|
||||
>
|
||||
<text class="relative z-10">六大超值权益</text>
|
||||
<view
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold">
|
||||
<view class="relative z-10">六大超值权益</view>
|
||||
<view class="absolute inset-0 bg-amber-500 opacity-30">
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30"
|
||||
/>
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</view>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="grid grid-cols-2 gap-4 p-4">
|
||||
<!-- 权益1 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md"
|
||||
>
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white"
|
||||
>1</text>
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">1</text>
|
||||
下级贡献收益
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
@@ -433,12 +409,10 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
|
||||
<!-- 权益2 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md"
|
||||
>
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white"
|
||||
>2</text>
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">2</text>
|
||||
下级提现收益
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
@@ -448,12 +422,10 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
|
||||
<!-- 权益3 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md"
|
||||
>
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white"
|
||||
>3</text>
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">3</text>
|
||||
转换高额奖励
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
@@ -463,12 +435,10 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
|
||||
<!-- 权益4 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md"
|
||||
>
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white"
|
||||
>4</text>
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">4</text>
|
||||
下级提现奖励
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
@@ -478,12 +448,10 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
|
||||
<!-- 权益6 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md"
|
||||
>
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white"
|
||||
>6</text>
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">6</text>
|
||||
平台专项扶持
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
@@ -497,16 +465,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<!-- 权益对比表 -->
|
||||
<view v-if="selectedType" class="card-container mb-8 px-4">
|
||||
<view class="overflow-hidden border border-amber-100 rounded-xl bg-white shadow-lg">
|
||||
<text
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold"
|
||||
>
|
||||
<text class="relative z-10">{{ selectedType === 'vip' ? 'VIP' : 'SVIP' }}代理权益对比</text>
|
||||
<view
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold">
|
||||
<view class="relative z-10">{{ selectedType === 'vip' ? 'VIP' : 'SVIP' }}代理权益对比</view>
|
||||
<view class="absolute inset-0 bg-amber-500 opacity-30">
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30"
|
||||
/>
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</view>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="overflow-x-auto p-4">
|
||||
<table class="w-full border-collapse">
|
||||
@@ -518,16 +484,12 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<th class="border border-amber-200 p-2 text-center text-amber-800">
|
||||
普通代理
|
||||
</th>
|
||||
<th
|
||||
class="border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'vip' }"
|
||||
>
|
||||
<th class="border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'vip' }">
|
||||
VIP代理
|
||||
</th>
|
||||
<th
|
||||
class="border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'svip' }"
|
||||
>
|
||||
<th class="border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'svip' }">
|
||||
SVIP代理
|
||||
</th>
|
||||
</tr>
|
||||
@@ -540,18 +502,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
普通代理<br>免费
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center font-bold" :class="{
|
||||
'text-amber-700 bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center font-bold" :class="{
|
||||
'text-amber-700 bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.price }}{{ vipConfig.priceUnit }}
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center font-bold" :class="{
|
||||
'text-amber-700 bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center font-bold" :class="{
|
||||
'text-amber-700 bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipPrice }}{{ vipConfig.priceUnit }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -562,18 +520,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
1元/单
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.vipCommission }}元/单
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipCommission }}元/单
|
||||
</td>
|
||||
</tr>
|
||||
@@ -584,18 +538,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
❌
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
✓
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
✓
|
||||
</td>
|
||||
</tr>
|
||||
@@ -606,18 +556,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
❌
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
最高{{ vipConfig.vipFloatingRate }}%
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
最高{{ vipConfig.svipFloatingRate }}%
|
||||
</td>
|
||||
</tr>
|
||||
@@ -628,18 +574,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
❌
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
❌
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.withdrawRatio }}%
|
||||
</td>
|
||||
</tr>
|
||||
@@ -650,18 +592,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
❌
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.vipConversionBonus }}元*10个
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipConversionBonus }}元*10个
|
||||
</td>
|
||||
</tr>
|
||||
@@ -672,18 +610,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
800元/次
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.vipWithdrawalLimit }}元/次
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipWithdrawalLimit }}元/次
|
||||
</td>
|
||||
</tr>
|
||||
@@ -694,18 +628,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
1次/日
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
1次/日
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
2次/日
|
||||
</td>
|
||||
</tr>
|
||||
@@ -718,16 +648,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<!-- 收益预估 -->
|
||||
<view v-if="selectedType" class="card-container mb-8 px-4">
|
||||
<view class="overflow-hidden border border-amber-100 rounded-xl bg-white shadow-lg">
|
||||
<text
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold"
|
||||
>
|
||||
<text class="relative z-10">收益预估对比</text>
|
||||
<view
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold">
|
||||
<view class="relative z-10">收益预估对比</view>
|
||||
<view class="absolute inset-0 bg-amber-500 opacity-30">
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30"
|
||||
/>
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</view>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="p-4">
|
||||
<!-- 顶部收益概览 -->
|
||||
@@ -772,16 +700,12 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<th class="border border-amber-200 p-2 text-left text-amber-800">
|
||||
收益来源
|
||||
</th>
|
||||
<th
|
||||
class="w-1/3 border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'vip' }"
|
||||
>
|
||||
<th class="w-1/3 border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'vip' }">
|
||||
VIP代理
|
||||
</th>
|
||||
<th
|
||||
class="w-1/3 border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'svip' }"
|
||||
>
|
||||
<th class="w-1/3 border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'svip' }">
|
||||
SVIP代理
|
||||
</th>
|
||||
</tr>
|
||||
@@ -791,18 +715,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
推广收益(月)
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
300单×50元=15,000元
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
300单×50元=15,000元
|
||||
</td>
|
||||
</tr>
|
||||
@@ -810,18 +730,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级贡献收益(月)
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
300单×{{ vipConfig.vipCommission }}元=360元
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
300单×{{ vipConfig.svipCommission }}元=450元
|
||||
</td>
|
||||
</tr>
|
||||
@@ -829,18 +745,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级价格浮动收益(月)
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
100单×100元×{{ vipConfig.vipFloatingRate }}%=500元
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
200单×100元×{{ vipConfig.svipFloatingRate }}%=2,000元
|
||||
</td>
|
||||
</tr>
|
||||
@@ -848,18 +760,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级提现奖励(月)
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
-
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ revenueData.withdrawReward }}元
|
||||
</td>
|
||||
</tr>
|
||||
@@ -867,18 +775,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级转化奖励(月)
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.vipConversionBonus }}元×2个=598元
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipConversionBonus }}元×2个=798元
|
||||
</td>
|
||||
</tr>
|
||||
@@ -886,18 +790,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
额外业务收益(月)
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
约3,000元
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
约6,000元
|
||||
</td>
|
||||
</tr>
|
||||
@@ -905,18 +805,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-3">
|
||||
月计收益
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-3 text-center text-amber-700" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-3 text-center text-amber-700" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'vip',
|
||||
}">
|
||||
{{ revenueData.vipMonthly }}元
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-3 text-center text-red-500" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-3 text-center text-red-500" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'svip',
|
||||
}">
|
||||
{{ revenueData.svipMonthly }}元
|
||||
</td>
|
||||
</tr>
|
||||
@@ -924,18 +820,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
<td class="border border-amber-200 p-3">
|
||||
年计收益
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-3 text-center text-amber-700" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'vip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-3 text-center text-amber-700" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'vip',
|
||||
}">
|
||||
{{ revenueData.vipYearly }}元
|
||||
</td>
|
||||
<td
|
||||
class="border border-amber-200 p-3 text-center text-red-500" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'svip',
|
||||
}"
|
||||
>
|
||||
<td class="border border-amber-200 p-3 text-center text-red-500" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'svip',
|
||||
}">
|
||||
{{ revenueData.svipYearly }}元
|
||||
</td>
|
||||
</tr>
|
||||
@@ -995,8 +887,7 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="h-6 w-6 flex flex-shrink-0 items-center justify-center rounded-full bg-red-500 text-white"
|
||||
>
|
||||
class="h-6 w-6 flex flex-shrink-0 items-center justify-center rounded-full bg-red-500 text-white">
|
||||
<view class="transform -translate-y-px">
|
||||
→
|
||||
</view>
|
||||
@@ -1010,8 +901,7 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="h-6 w-6 flex flex-shrink-0 items-center justify-center rounded-full bg-red-500 text-white"
|
||||
>
|
||||
class="h-6 w-6 flex flex-shrink-0 items-center justify-center rounded-full bg-red-500 text-white">
|
||||
<text class="transform -translate-y-px">→</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
@@ -1036,21 +926,14 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
|
||||
<!-- 申请按钮(固定在底部) -->
|
||||
<view
|
||||
class="fixed bottom-0 left-0 right-0 z-30 from-amber-100 to-transparent bg-gradient-to-t px-4 py-3 backdrop-blur-sm"
|
||||
>
|
||||
class="fixed bottom-0 left-0 right-0 z-30 from-amber-100 to-transparent bg-gradient-to-t px-4 py-3 backdrop-blur-sm">
|
||||
<view class="flex flex-col gap-2">
|
||||
<wd-button :class="buttonClass" block :disabled="!canPerformAction" @click="applyVip">
|
||||
<text class="relative z-10">{{ buttonText }}</text>
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30"
|
||||
/>
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</wd-button>
|
||||
<wd-button
|
||||
custom-class="contact-wd-btn"
|
||||
block
|
||||
plain
|
||||
@click="contactService"
|
||||
>
|
||||
<wd-button custom-class="contact-wd-btn" block plain @click="contactService">
|
||||
<view class="flex items-center justify-center text-amber-700 font-medium">
|
||||
<wd-icon name="service" custom-class="mr-1" />
|
||||
<text>联系客服咨询</text>
|
||||
|
||||
@@ -2,23 +2,21 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
definePage({ layout: 'default', auth: true })
|
||||
|
||||
// 报告类型选项
|
||||
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 reportOptions = ref([])
|
||||
const reportOptionsByValue = computed(() => {
|
||||
const map = new Map()
|
||||
reportOptions.value.forEach((item) => {
|
||||
map.set(item.value, item)
|
||||
})
|
||||
return map
|
||||
})
|
||||
|
||||
// 状态管理
|
||||
const showPicker = ref(false)
|
||||
const selectedReport = ref(reportOptions[0])
|
||||
const selectedReportText = ref(reportOptions[0].text)
|
||||
const selectedReportId = ref(reportOptions[0].id)
|
||||
const selectedReport = ref(null)
|
||||
const selectedReportText = ref('')
|
||||
const selectedReportId = ref(null)
|
||||
|
||||
const configData = ref({})
|
||||
const productConfigData = ref({})
|
||||
@@ -28,6 +26,7 @@ const priceRatioMax = ref(null)
|
||||
const rangeError = ref(false)
|
||||
const ratioError = ref(false)
|
||||
const increaseError = ref(false)
|
||||
const activeField = ref('')
|
||||
|
||||
function showToast(message) {
|
||||
if (!message)
|
||||
@@ -307,11 +306,19 @@ function finalValidation() {
|
||||
}
|
||||
|
||||
// 选择器确认
|
||||
function onSelectReport(option) {
|
||||
function onConfirmType(e) {
|
||||
const raw = e?.value
|
||||
const nextValue = Array.isArray(raw) ? raw[0] : raw
|
||||
const fromItem = e?.selectedItems
|
||||
const resolved = nextValue ?? (Array.isArray(fromItem) ? fromItem[0]?.value : fromItem?.value)
|
||||
if (resolved == null || resolved === '')
|
||||
return
|
||||
const option = reportOptionsByValue.value.get(Number(resolved))
|
||||
if (!option)
|
||||
return
|
||||
selectedReport.value = option
|
||||
selectedReportText.value = option.text
|
||||
selectedReportText.value = option.label
|
||||
selectedReportId.value = option.id
|
||||
showPicker.value = false
|
||||
// 重置错误状态
|
||||
rangeError.value = false
|
||||
ratioError.value = false
|
||||
@@ -324,9 +331,53 @@ function closeRangeError() {
|
||||
rangeError.value = false
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
function onFieldFocus(field) {
|
||||
activeField.value = field
|
||||
}
|
||||
|
||||
function onFieldBlur(field) {
|
||||
if (activeField.value === field)
|
||||
activeField.value = ''
|
||||
}
|
||||
onMounted(() => {
|
||||
getConfig()
|
||||
loadReportOptions()
|
||||
})
|
||||
|
||||
async function loadReportOptions() {
|
||||
try {
|
||||
const { data, error } = await useApiFetch('/agent/product_config', { silent: true }).get().json()
|
||||
if (error.value || data.value?.code !== 200) {
|
||||
showToast(data.value?.msg || '加载报告类型失败')
|
||||
return
|
||||
}
|
||||
const list = data.value?.data?.AgentProductConfig || data.value?.data?.agent_product_config || []
|
||||
reportOptions.value = list
|
||||
.map(item => ({
|
||||
label: item.product_name || `报告${item.product_id}`,
|
||||
value: item.product_id,
|
||||
id: item.product_id,
|
||||
}))
|
||||
.filter(item => item.id != null)
|
||||
|
||||
if (!reportOptions.value.length) {
|
||||
showToast('暂无可配置报告类型')
|
||||
selectedReport.value = null
|
||||
selectedReportText.value = ''
|
||||
selectedReportId.value = null
|
||||
return
|
||||
}
|
||||
|
||||
const first = reportOptions.value[0]
|
||||
selectedReport.value = first
|
||||
selectedReportText.value = first.label
|
||||
selectedReportId.value = first.id
|
||||
getConfig()
|
||||
}
|
||||
catch {
|
||||
showToast('加载报告类型失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -342,30 +393,16 @@ onMounted(() => {
|
||||
</view>
|
||||
|
||||
<view class="mb-4">
|
||||
<view class="card selector" @click="showPicker = true">
|
||||
<view class="selector-label">
|
||||
📝 选择报告
|
||||
</view>
|
||||
<view class="selector-value">
|
||||
{{ selectedReportText }}
|
||||
</view>
|
||||
</view>
|
||||
<wd-popup v-model="showPicker" position="bottom" custom-style="padding: 16px;">
|
||||
<view class="popup-title">
|
||||
选择报告类型
|
||||
</view>
|
||||
<view class="report-list">
|
||||
<view
|
||||
v-for="item in reportOptions"
|
||||
:key="item.value"
|
||||
class="report-item"
|
||||
:class="{ active: selectedReportId === item.id }"
|
||||
@click="onSelectReport(item)"
|
||||
>
|
||||
{{ item.text }}
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
<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">
|
||||
@@ -421,7 +458,7 @@ onMounted(() => {
|
||||
</view>
|
||||
|
||||
<!-- 加价金额 -->
|
||||
<view class="custom-field" :class="{ 'field-error': increaseError }">
|
||||
<view class="custom-field" :class="{ 'field-error': increaseError, 'field-active': activeField === 'price_increase_amount' }">
|
||||
<text class="field-label">
|
||||
🚀 加价金额
|
||||
</text>
|
||||
@@ -433,7 +470,13 @@ onMounted(() => {
|
||||
class="field-wd-input"
|
||||
no-border
|
||||
clearable
|
||||
@blur="validateDecimal('price_increase_amount')"
|
||||
@focus="onFieldFocus('price_increase_amount')"
|
||||
@blur="
|
||||
() => {
|
||||
onFieldBlur('price_increase_amount')
|
||||
validateDecimal('price_increase_amount')
|
||||
}
|
||||
"
|
||||
/>
|
||||
<text class="field-unit">
|
||||
元
|
||||
@@ -451,7 +494,7 @@ onMounted(() => {
|
||||
</view>
|
||||
|
||||
<!-- 定价区间最低 -->
|
||||
<view class="custom-field" :class="{ 'field-error': rangeError }">
|
||||
<view class="custom-field" :class="{ 'field-error': rangeError, 'field-active': activeField === 'price_range_from' }">
|
||||
<text class="field-label">
|
||||
💰 最低金额
|
||||
</text>
|
||||
@@ -463,8 +506,10 @@ onMounted(() => {
|
||||
class="field-wd-input"
|
||||
no-border
|
||||
clearable
|
||||
@focus="onFieldFocus('price_range_from')"
|
||||
@blur="
|
||||
() => {
|
||||
onFieldBlur('price_range_from')
|
||||
validateDecimal('price_range_from')
|
||||
validateRange()
|
||||
}
|
||||
@@ -483,7 +528,7 @@ onMounted(() => {
|
||||
</view>
|
||||
|
||||
<!-- 定价区间最高 -->
|
||||
<view class="custom-field" :class="{ 'field-error': rangeError }">
|
||||
<view class="custom-field" :class="{ 'field-error': rangeError, 'field-active': activeField === 'price_range_to' }">
|
||||
<text class="field-label">
|
||||
💰 最高金额
|
||||
</text>
|
||||
@@ -495,8 +540,10 @@ onMounted(() => {
|
||||
class="field-wd-input"
|
||||
no-border
|
||||
clearable
|
||||
@focus="onFieldFocus('price_range_to')"
|
||||
@blur="
|
||||
() => {
|
||||
onFieldBlur('price_range_to')
|
||||
validateDecimal('price_range_to')
|
||||
validateRange()
|
||||
}
|
||||
@@ -515,7 +562,7 @@ onMounted(() => {
|
||||
</view>
|
||||
|
||||
<!-- 收取比例 -->
|
||||
<view class="custom-field" :class="{ 'field-error': ratioError }">
|
||||
<view class="custom-field" :class="{ 'field-error': ratioError, 'field-active': activeField === 'price_ratio' }">
|
||||
<text class="field-label">
|
||||
📈 收取比例
|
||||
</text>
|
||||
@@ -527,7 +574,13 @@ onMounted(() => {
|
||||
class="field-wd-input"
|
||||
no-border
|
||||
clearable
|
||||
@blur="validateRatio()"
|
||||
@focus="onFieldFocus('price_ratio')"
|
||||
@blur="
|
||||
() => {
|
||||
onFieldBlur('price_ratio')
|
||||
validateRatio()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<text class="field-unit">
|
||||
%
|
||||
@@ -642,11 +695,11 @@ onMounted(() => {
|
||||
.field-input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f9fafb;
|
||||
background: #f3f4f6;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid transparent;
|
||||
padding: 10px 12px;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
:deep(.field-wd-input .wd-input__inner) {
|
||||
@@ -661,12 +714,24 @@ onMounted(() => {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.custom-field:focus-within .field-input-wrap {
|
||||
border-color: #bfdbfe;
|
||||
::deep(.field-wd-input.wd-input),
|
||||
::deep(.field-wd-input .wd-input__body),
|
||||
::deep(.field-wd-input .wd-input__prefix),
|
||||
::deep(.field-wd-input .wd-input__suffix),
|
||||
::deep(.field-wd-input .wd-input__value),
|
||||
::deep(.field-wd-input .wd-input__clear) {
|
||||
background: transparent !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>
|
||||
|
||||
@@ -93,9 +93,11 @@ function toSubordinateList() {
|
||||
<view class="safe-area-top min-h-screen p-4">
|
||||
<view class="mb-4 rounded-xl from-blue-50/70 to-blue-100/50 bg-gradient-to-r p-6 shadow-lg">
|
||||
<view class="mb-3 flex items-center justify-between">
|
||||
<text class="text-lg text-gray-800 font-bold">
|
||||
余额
|
||||
</text>
|
||||
<view class="flex items-center">
|
||||
<text class="text-lg text-gray-800 font-bold">
|
||||
余额
|
||||
</text>
|
||||
</view>
|
||||
<text class="text-3xl text-blue-600 font-bold">
|
||||
¥ {{ (data?.balance || 0).toFixed(2) }}
|
||||
</text>
|
||||
@@ -121,9 +123,11 @@ function toSubordinateList() {
|
||||
|
||||
<view class="mb-4 rounded-xl from-blue-50/40 to-cyan-50/50 bg-gradient-to-r p-6 shadow-lg">
|
||||
<view class="mb-4 flex items-center justify-between">
|
||||
<text class="text-lg text-gray-800 font-bold">
|
||||
直推报告收益
|
||||
</text>
|
||||
<view class="flex items-center">
|
||||
<text class="text-lg text-gray-800 font-bold">
|
||||
直推报告收益
|
||||
</text>
|
||||
</view>
|
||||
<view class="text-right">
|
||||
<text class="text-2xl text-blue-600 font-bold">
|
||||
¥ {{ (data?.direct_push?.total_commission || 0).toFixed(2) }}
|
||||
@@ -176,7 +180,7 @@ function toSubordinateList() {
|
||||
<view class="rounded-xl from-green-50/40 to-cyan-50/30 bg-gradient-to-r p-6 shadow-lg">
|
||||
<view class="mb-4">
|
||||
<text class="text-lg text-gray-800 font-bold">
|
||||
团队奖励
|
||||
活跃下级奖励
|
||||
</text>
|
||||
</view>
|
||||
|
||||
@@ -192,7 +196,7 @@ function toSubordinateList() {
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="grid grid-cols-1 mb-6 gap-2">
|
||||
<view class="grid grid-cols-2 mb-6 gap-2">
|
||||
<view class="rounded-lg bg-green-50/60 p-3 backdrop-blur-sm">
|
||||
<view class="text-sm text-gray-500">
|
||||
{{ teamTimeText }}下级推广奖励
|
||||
@@ -221,7 +225,7 @@ function toSubordinateList() {
|
||||
|
||||
<view class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<wd-button plain block @click="goToRewardsDetail">
|
||||
团队奖励明细
|
||||
查看奖励明细
|
||||
</wd-button>
|
||||
<wd-button type="success" block @click="toSubordinateList">
|
||||
查看我的下级
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
definePage({ layout: 'default', auth: true })
|
||||
|
||||
const page = ref(1)
|
||||
@@ -7,30 +8,65 @@ const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const reportList = ref([])
|
||||
const loading = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const loadMoreState = ref('loading')
|
||||
// 初始加载数据
|
||||
async function fetchData() {
|
||||
function mergeUniqueById(oldList, newList) {
|
||||
const map = new Map()
|
||||
const resolveKey = (item, index) => String(item?.id ?? item?.order_id ?? `${item?.create_time || ''}_${item?.query_state || ''}_${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 fetchData(reset = false) {
|
||||
if (loading.value)
|
||||
return
|
||||
if (!reset && !hasMore.value)
|
||||
return
|
||||
loading.value = true
|
||||
if (reset) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadMoreState.value = 'loading'
|
||||
reportList.value = []
|
||||
}
|
||||
const { data, error } = await useApiFetch(`query/list?page=${page.value}&page_size=${pageSize.value}`, { silent: true })
|
||||
.get()
|
||||
.json()
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
total.value = data.value.data.total
|
||||
reportList.value = data.value.data.list || []
|
||||
const incoming = data.value.data.list || []
|
||||
reportList.value = reset ? incoming : mergeUniqueById(reportList.value, incoming)
|
||||
const isLastPage = incoming.length < pageSize.value || reportList.value.length >= total.value
|
||||
hasMore.value = !isLastPage
|
||||
loadMoreState.value = isLastPage ? 'finished' : 'loading'
|
||||
if (!isLastPage)
|
||||
page.value += 1
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 初始加载
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
fetchData(true)
|
||||
})
|
||||
|
||||
function onPageChange({ value }) {
|
||||
page.value = value
|
||||
onReachBottom(() => {
|
||||
fetchData()
|
||||
}
|
||||
})
|
||||
|
||||
function toDetail(item) {
|
||||
if (item.query_state !== 'success')
|
||||
@@ -102,20 +138,13 @@ function statusClass(state) {
|
||||
暂无记录
|
||||
</view>
|
||||
</view>
|
||||
<wd-pagination
|
||||
v-model="page"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
show-icon
|
||||
show-message
|
||||
@change="onPageChange"
|
||||
/>
|
||||
<wd-loadmore :state="loadMoreState" @reload="fetchData" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.history-scroll {
|
||||
height: calc(100vh - 120px);
|
||||
min-height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
|
||||
@@ -60,10 +60,8 @@ function toHistory() {
|
||||
<template>
|
||||
<view class="box-border min-h-screen">
|
||||
<view class="relative p-4">
|
||||
<swiper
|
||||
class="banner-swiper overflow-hidden rounded-xl" circular autoplay :interval="3000" indicator-dots
|
||||
indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#ffffff"
|
||||
>
|
||||
<swiper class="banner-swiper overflow-hidden rounded-xl" circular autoplay :interval="3000" indicator-dots
|
||||
indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#ffffff">
|
||||
<swiper-item v-for="(item, index) in banners" :key="index">
|
||||
<image :src="item" class="h-full w-full" mode="aspectFill" />
|
||||
</swiper-item>
|
||||
@@ -74,8 +72,7 @@ function toHistory() {
|
||||
<view class="grid grid-cols-3 gap-3">
|
||||
<view class="flex flex-col items-center justify-center text-center" @click="toPromote">
|
||||
<view
|
||||
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg"
|
||||
>
|
||||
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg">
|
||||
<image :src="indexPromoteIcon" class="h-12 w-12" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="mt-1 text-center font-bold">
|
||||
@@ -85,8 +82,7 @@ function toHistory() {
|
||||
|
||||
<view class="flex flex-col items-center justify-center text-center" @click="toInvitation">
|
||||
<view
|
||||
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg"
|
||||
>
|
||||
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg">
|
||||
<image :src="indexInvitationIcon" class="h-12 w-12" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="mt-1 text-center font-bold">
|
||||
@@ -96,8 +92,7 @@ function toHistory() {
|
||||
|
||||
<view class="flex flex-col items-center justify-center text-center" @click="toHistory">
|
||||
<view
|
||||
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg"
|
||||
>
|
||||
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg">
|
||||
<image :src="indexMyReportIcon" class="h-12 w-12" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="mt-1 text-center font-bold">
|
||||
@@ -109,13 +104,11 @@ function toHistory() {
|
||||
|
||||
<view class="relative p-4 pt-0">
|
||||
<view class="grid grid-cols-2 my-4 gap-4" style="grid-template-rows: repeat(2, 1fr);">
|
||||
<view
|
||||
v-for="(service, index) in services" :key="index"
|
||||
<view v-for="(service, index) in services" :key="index"
|
||||
class="relative min-h-18 flex flex-col rounded-xl px-4 py-2 shadow-lg"
|
||||
:class="index === 0 ? 'row-span-2' : ''"
|
||||
:style="`background: url(${service.bg}) no-repeat; background-size: 100% 100%; background-position: center;`"
|
||||
@click="toInquire(service.name)"
|
||||
>
|
||||
@click="toInquire(service.name)">
|
||||
<view class="min-h-18 flex items-end">
|
||||
<!-- <text class="text-base text-gray-700 font-semibold">
|
||||
{{ service.title }}
|
||||
@@ -126,12 +119,10 @@ function toHistory() {
|
||||
|
||||
<scroll-view scroll-x class="risk-scroll my-4 px-1 pb-4 pt-2 -mx-1">
|
||||
<view class="inline-flex gap-2">
|
||||
<view
|
||||
v-for="(service, index) in riskServices" :key="index"
|
||||
<view v-for="(service, index) in riskServices" :key="index"
|
||||
class="relative h-24 w-[107px] flex-shrink-0 rounded-xl shadow-lg"
|
||||
:style="`background: url(${service.bg}) no-repeat; background-size: 100% 100%; background-position: center;`"
|
||||
@click="toInquire(service.name)"
|
||||
>
|
||||
@click="toInquire(service.name)">
|
||||
<view class="h-full flex items-end px-2 py-2">
|
||||
<!-- <text class="text-sm text-gray-700 font-semibold">
|
||||
{{ service.title }}
|
||||
@@ -152,10 +143,8 @@ function toHistory() {
|
||||
<image :src="honestyBanner" class="block w-full" mode="widthFix" />
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="mt-4 box-border h-14 w-full flex items-center rounded-lg bg-white px-4 text-gray-700 shadow-lg"
|
||||
@click="toHistory"
|
||||
>
|
||||
<view class="mt-4 box-border h-14 w-full flex items-center rounded-lg bg-white px-4 text-gray-700 shadow-lg"
|
||||
@click="toHistory">
|
||||
<view class="mr-4 h-full flex items-center justify-center">
|
||||
<image class="h-10 w-10" :src="bgIcon" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
@@ -97,7 +97,13 @@ async function performLogin() {
|
||||
await userStore.fetchUserInfo()
|
||||
await agentStore.fetchAgentStatus()
|
||||
|
||||
uni.reLaunch({ url: redirectUrl.value || '/pages/index' })
|
||||
const target = redirectUrl.value || '/pages/index'
|
||||
if (redirectUrl.value) {
|
||||
uni.redirectTo({ url: target })
|
||||
}
|
||||
else {
|
||||
uni.reLaunch({ url: target })
|
||||
}
|
||||
}
|
||||
else {
|
||||
toast(data.value.msg || '登录失败')
|
||||
|
||||
@@ -16,8 +16,20 @@ const { isAgent, level, ExpiryTime } = storeToRefs(agentStore)
|
||||
const { userAvatar, isLoggedIn, mobile } = storeToRefs(userStore)
|
||||
const isWeChat = ref(false)
|
||||
|
||||
const normalizedLevel = computed(() => {
|
||||
const raw = String(level.value || '').trim()
|
||||
if (!raw || ['NORMAL', 'NORNAL'].includes(raw.toUpperCase()) || raw === 'normal' || raw === 'nornal')
|
||||
return 'NORMAL'
|
||||
if (raw.toUpperCase().includes('SVIP'))
|
||||
return 'SVIP'
|
||||
if (raw.toUpperCase().includes('VIP'))
|
||||
return 'VIP'
|
||||
return 'NORMAL'
|
||||
})
|
||||
|
||||
const levelNames = {
|
||||
'normal': '普通代理',
|
||||
'NORMAL': '普通代理',
|
||||
'': '普通代理',
|
||||
'VIP': 'VIP代理',
|
||||
'SVIP': 'SVIP代理',
|
||||
@@ -25,6 +37,7 @@ const levelNames = {
|
||||
|
||||
const levelText = {
|
||||
'normal': '基础代理特权',
|
||||
'NORMAL': '基础代理特权',
|
||||
'': '基础代理特权',
|
||||
'VIP': '高级代理特权',
|
||||
'SVIP': '尊享代理特权',
|
||||
@@ -33,24 +46,27 @@ const levelText = {
|
||||
const levelGradient = computed(() => ({
|
||||
border: {
|
||||
'normal': '',
|
||||
'NORMAL': '',
|
||||
'': '',
|
||||
'VIP': '',
|
||||
'SVIP': '',
|
||||
}[level.value],
|
||||
}[normalizedLevel.value],
|
||||
|
||||
badge: {
|
||||
'normal': 'bg-gradient-to-r from-gray-500 to-gray-600',
|
||||
'NORMAL': 'bg-gradient-to-r from-gray-500 to-gray-600',
|
||||
'': 'bg-gradient-to-r from-gray-500 to-gray-600',
|
||||
'VIP': 'bg-gradient-to-r from-yellow-500 to-amber-600',
|
||||
'SVIP': 'bg-gradient-to-r from-purple-500 to-pink-500',
|
||||
}[level.value],
|
||||
}[normalizedLevel.value],
|
||||
|
||||
text: {
|
||||
'normal': 'text-gray-600',
|
||||
'NORMAL': 'text-gray-600',
|
||||
'': 'text-gray-600',
|
||||
'VIP': 'text-amber-600',
|
||||
'SVIP': 'text-purple-600',
|
||||
}[level.value],
|
||||
}[normalizedLevel.value],
|
||||
}))
|
||||
|
||||
function maskName(name) {
|
||||
@@ -122,21 +138,18 @@ function formatExpiryTime(expiryTimeStr) {
|
||||
return expiryTimeStr.split(' ')[0]
|
||||
}
|
||||
|
||||
/** 与 bdrp-mini `/static/image/shot_*.png` 一致,资源放在 `src/static/image/` */
|
||||
/** 代理默认头像资源位于 `src/static/images/shot_*.png` */
|
||||
function getDefaultAvatar() {
|
||||
if (!isAgent.value)
|
||||
return headShot
|
||||
|
||||
const normalizedLevel = String(level.value || '').toUpperCase()
|
||||
switch (normalizedLevel) {
|
||||
switch (normalizedLevel.value) {
|
||||
case 'NORMAL':
|
||||
case 'normal':
|
||||
case '':
|
||||
return '/static/image/shot_nonal.png'
|
||||
return '/static/images/shot_nonal.png'
|
||||
case 'VIP':
|
||||
return '/static/image/shot_vip.png'
|
||||
return '/static/images/shot_vip.png'
|
||||
case 'SVIP':
|
||||
return '/static/image/shot_svip.png'
|
||||
return '/static/images/shot_svip.png'
|
||||
default:
|
||||
return headShot
|
||||
}
|
||||
@@ -203,7 +216,7 @@ onBeforeMount(() => {
|
||||
<view v-if="isAgent" class="absolute -bottom-2 -right-2">
|
||||
<view class="flex items-center justify-center rounded-full px-3 py-1 text-xs text-white font-bold shadow-sm"
|
||||
:class="levelGradient.badge">
|
||||
{{ levelNames[level] }}
|
||||
{{ levelNames[normalizedLevel] }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -226,15 +239,15 @@ onBeforeMount(() => {
|
||||
</view>
|
||||
</template>
|
||||
<view v-if="isAgent" class="text-sm font-medium" :class="levelGradient.text">
|
||||
🎖️ {{ levelText[level] }}
|
||||
🎖️ {{ levelText[normalizedLevel] }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<VipBanner v-if="isAgent && (level === 'normal' || level === '')" />
|
||||
<VipBanner v-if="isAgent && normalizedLevel === 'NORMAL'" />
|
||||
<!-- 功能菜单 -->
|
||||
<view>
|
||||
<view class="overflow-hidden rounded-xl bg-white shadow-sm">
|
||||
<template v-if="isAgent && ['VIP', 'SVIP'].includes(level)">
|
||||
<template v-if="isAgent && ['VIP', 'SVIP'].includes(normalizedLevel)">
|
||||
<view
|
||||
class="w-full flex items-center justify-between border-b border-gray-100 px-6 py-4 transition-colors hover:bg-purple-50"
|
||||
@click="toVipConfig">
|
||||
|
||||
@@ -26,6 +26,17 @@ const pollingCount = ref(0)
|
||||
const maxPollingCount = 30 // 最大轮询次数
|
||||
const baseInterval = 2000 // 基础轮询间隔(2秒)
|
||||
|
||||
function navigateToReportResult() {
|
||||
const url = `/pages/report-result-webview?orderNo=${encodeURIComponent(orderNo.value)}`
|
||||
uni.redirectTo({
|
||||
url,
|
||||
fail: () => {
|
||||
// redirectTo 失败时兜底,避免无响应
|
||||
uni.navigateTo({ url })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const statusText = computed(() => {
|
||||
if (isApiError.value) {
|
||||
@@ -152,9 +163,7 @@ async function checkPaymentStatus() {
|
||||
&& (newStatus === 'paid' || newStatus === 'refunded')
|
||||
) {
|
||||
stopPolling()
|
||||
uni.redirectTo({
|
||||
url: `/pages/report-result-webview?orderNo=${encodeURIComponent(orderNo.value)}`,
|
||||
})
|
||||
navigateToReportResult()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -230,16 +239,14 @@ onBeforeUnmount(() => {
|
||||
// 处理导航逻辑
|
||||
function handleNavigation() {
|
||||
if (paymentType.value === 'agent_vip') {
|
||||
// 跳转到代理会员页面
|
||||
uni.switchTab({ url: '/pages/agent' })
|
||||
// 该项目未使用原生 tabBar,不能用 switchTab
|
||||
uni.reLaunch({ url: '/pages/agent' })
|
||||
agentStore.fetchAgentStatus()
|
||||
userStore.fetchUserInfo()
|
||||
}
|
||||
else {
|
||||
// 跳转到查询结果页面
|
||||
uni.redirectTo({
|
||||
url: `/pages/report-result-webview?orderNo=${encodeURIComponent(orderNo.value)}`,
|
||||
})
|
||||
navigateToReportResult()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import PriceInputPopup from '@/components/PriceInputPopup.vue'
|
||||
import VipBanner from '@/components/VipBanner.vue'
|
||||
import { calculatePromotionPricing, safeTruncate } from '@/utils/promotionPricing'
|
||||
|
||||
definePage({ layout: 'default' })
|
||||
|
||||
@@ -30,39 +31,12 @@ const availableReportTypes = computed(() => {
|
||||
.filter(item => !!item.value)
|
||||
})
|
||||
|
||||
const costPrice = computed(() => {
|
||||
if (!pickerProductConfig.value)
|
||||
return '0.00'
|
||||
let platformPricing = 0
|
||||
platformPricing += pickerProductConfig.value.cost_price
|
||||
if (formData.value.clientPrice > pickerProductConfig.value.p_pricing_standard) {
|
||||
platformPricing += (formData.value.clientPrice - pickerProductConfig.value.p_pricing_standard) * pickerProductConfig.value.p_overpricing_ratio
|
||||
}
|
||||
|
||||
if (pickerProductConfig.value.a_pricing_standard > platformPricing && pickerProductConfig.value.a_pricing_end > platformPricing && pickerProductConfig.value.a_overpricing_ratio > 0) {
|
||||
if (formData.value.clientPrice > pickerProductConfig.value.a_pricing_standard) {
|
||||
if (formData.value.clientPrice > pickerProductConfig.value.a_pricing_end) {
|
||||
platformPricing += (pickerProductConfig.value.a_pricing_end - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
|
||||
}
|
||||
else {
|
||||
platformPricing += (formData.value.clientPrice - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
return safeTruncate(platformPricing)
|
||||
const pricingResult = computed(() => {
|
||||
return calculatePromotionPricing(formData.value.clientPrice, pickerProductConfig.value)
|
||||
})
|
||||
|
||||
const promotionRevenue = computed(() => {
|
||||
return safeTruncate(formData.value.clientPrice - costPrice.value)
|
||||
})
|
||||
|
||||
function safeTruncate(num, decimals = 2) {
|
||||
if (Number.isNaN(num) || !Number.isFinite(num))
|
||||
return '0.00'
|
||||
const factor = 10 ** decimals
|
||||
const scaled = Math.trunc(num * factor)
|
||||
return (scaled / factor).toFixed(decimals)
|
||||
}
|
||||
const costPrice = computed(() => pricingResult.value.costPrice)
|
||||
const promotionRevenue = computed(() => pricingResult.value.promotionRevenue)
|
||||
|
||||
function selectProductType(reportTypeValue) {
|
||||
const reportType = availableReportTypes.value.find(item => item.id === reportTypeValue || item.value === reportTypeValue)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
import { useRoute } from '@/composables/uni-router'
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
|
||||
@@ -14,15 +15,65 @@ const rewardDetails = ref([])
|
||||
const userInfo = ref({})
|
||||
const summary = ref({})
|
||||
const statistics = ref([])
|
||||
const subordinateId = ref(0)
|
||||
const hasMore = ref(true)
|
||||
const loadMoreState = ref('loading')
|
||||
|
||||
function normalizeAgentLevel(value) {
|
||||
const raw = String(value || '').trim()
|
||||
if (!raw)
|
||||
return 'NORMAL'
|
||||
const cleaned = raw.replace(/代理$/, '')
|
||||
if (cleaned === '普通')
|
||||
return 'NORMAL'
|
||||
const upper = cleaned.toUpperCase()
|
||||
if (upper.includes('SVIP'))
|
||||
return 'SVIP'
|
||||
if (upper.includes('VIP'))
|
||||
return 'VIP'
|
||||
if (upper === 'NORMAL' || upper === 'NORNAL')
|
||||
return 'NORMAL'
|
||||
return 'NORMAL'
|
||||
}
|
||||
|
||||
function getAgentLevelLabel(value) {
|
||||
return {
|
||||
NORMAL: '普通代理',
|
||||
VIP: 'VIP代理',
|
||||
SVIP: 'SVIP代理',
|
||||
}[normalizeAgentLevel(value)] || '普通代理'
|
||||
}
|
||||
|
||||
// 获取收益列表
|
||||
async function fetchRewardDetails() {
|
||||
function mergeUniqueById(oldList, newList) {
|
||||
const map = new Map()
|
||||
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) {
|
||||
if (loading.value)
|
||||
return
|
||||
if (!subordinateId.value)
|
||||
return
|
||||
if (!reset && !hasMore.value)
|
||||
return
|
||||
|
||||
loading.value = true
|
||||
if (reset) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadMoreState.value = 'loading'
|
||||
rewardDetails.value = []
|
||||
}
|
||||
const { data, error } = await useApiFetch(
|
||||
`/agent/subordinate/contribution/detail?subordinate_id=${route.params.id}&page=${page.value}&page_size=${pageSize}`,
|
||||
`/agent/subordinate/contribution/detail?subordinate_id=${subordinateId.value}&page=${page.value}&page_size=${pageSize}`,
|
||||
{ silent: true },
|
||||
)
|
||||
.get()
|
||||
@@ -34,7 +85,7 @@ async function fetchRewardDetails() {
|
||||
// 更新用户信息
|
||||
userInfo.value = {
|
||||
createTime: data.value.data.create_time,
|
||||
level: data.value.data.level_name || '普通',
|
||||
level: data.value.data.level_name || data.value.data.level || '',
|
||||
mobile: data.value.data.mobile,
|
||||
}
|
||||
// 更新汇总数据
|
||||
@@ -122,28 +173,57 @@ async function fetchRewardDetails() {
|
||||
conversionSvipStat.count = stats.descendant_upgrade_svip_count || 0
|
||||
}
|
||||
}
|
||||
rewardDetails.value = []
|
||||
}
|
||||
total.value = data.value.data.total || 0
|
||||
// 处理列表数据
|
||||
if (data.value.data.list) {
|
||||
const list = data.value.data.list
|
||||
rewardDetails.value = list
|
||||
}
|
||||
else {
|
||||
rewardDetails.value = []
|
||||
}
|
||||
const list = data.value.data.list || []
|
||||
rewardDetails.value = reset ? list : mergeUniqueById(rewardDetails.value, list)
|
||||
const isLastPage = list.length < pageSize || rewardDetails.value.length >= total.value
|
||||
hasMore.value = !isLastPage
|
||||
loadMoreState.value = isLastPage ? 'finished' : 'loading'
|
||||
if (!isLastPage)
|
||||
page.value += 1
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function onPageChange({ value }) {
|
||||
page.value = value
|
||||
fetchRewardDetails()
|
||||
function resolveSubordinateId(query) {
|
||||
const rawId = query?.id
|
||||
?? query?.subordinate_id
|
||||
?? route.params?.id
|
||||
?? route.query?.id
|
||||
?? route.params?.subordinate_id
|
||||
?? route.query?.subordinate_id
|
||||
const parsedId = Number.parseInt(String(rawId ?? ''), 10)
|
||||
return Number.isFinite(parsedId) && parsedId > 0 ? parsedId : 0
|
||||
}
|
||||
|
||||
onLoad((query) => {
|
||||
const parsedId = resolveSubordinateId(query)
|
||||
subordinateId.value = parsedId
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const parsedId = subordinateId.value || resolveSubordinateId()
|
||||
if (!Number.isFinite(parsedId) || parsedId <= 0) {
|
||||
uni.showToast({ title: '下级参数错误', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 800)
|
||||
return
|
||||
}
|
||||
subordinateId.value = parsedId
|
||||
fetchRewardDetails(true)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
fetchRewardDetails()
|
||||
})
|
||||
|
||||
@@ -213,7 +293,7 @@ function formatNumber(num) {
|
||||
{{ userInfo.mobile }}
|
||||
</view>
|
||||
<text class="rounded-full bg-blue-100 px-3 py-1 text-sm text-blue-600 font-medium">
|
||||
{{ userInfo.level }}代理
|
||||
{{ getAgentLevelLabel(userInfo.level) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -320,16 +400,7 @@ function formatNumber(num) {
|
||||
加载中...
|
||||
</view>
|
||||
</view>
|
||||
<view class="px-4 pb-4">
|
||||
<wd-pagination
|
||||
v-model="page"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
show-icon
|
||||
show-message
|
||||
@change="onPageChange"
|
||||
/>
|
||||
</view>
|
||||
<wd-loadmore :state="loadMoreState" @reload="fetchRewardDetails" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
|
||||
definePage({ layout: 'default', auth: true })
|
||||
@@ -8,34 +9,65 @@ const subordinates = ref([])
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 8
|
||||
const hasMore = ref(true)
|
||||
const loadMoreState = ref('loading')
|
||||
// 计算统计数据
|
||||
const statistics = ref({
|
||||
totalSubordinates: 0,
|
||||
})
|
||||
|
||||
// 获取下级列表
|
||||
async function fetchSubordinates() {
|
||||
function mergeUniqueById(oldList, newList) {
|
||||
const map = new Map()
|
||||
const resolveKey = (item, index) => String(item?.id ?? item?.subordinate_id ?? item?.user_id ?? `${item?.mobile || ''}_${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 fetchSubordinates(reset = false) {
|
||||
if (loading.value)
|
||||
return
|
||||
if (!reset && !hasMore.value)
|
||||
return
|
||||
|
||||
loading.value = true
|
||||
if (reset) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadMoreState.value = 'loading'
|
||||
subordinates.value = []
|
||||
}
|
||||
const { data, error } = await useApiFetch(`/agent/subordinate/list?page=${page.value}&page_size=${pageSize}`, { silent: true })
|
||||
.get()
|
||||
.json()
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
statistics.value.totalSubordinates = data.value.data.total
|
||||
subordinates.value = data.value.data.list || []
|
||||
const currentList = data.value.data.list || []
|
||||
subordinates.value = reset
|
||||
? currentList
|
||||
: mergeUniqueById(subordinates.value, currentList)
|
||||
const isLastPage = currentList.length < pageSize || subordinates.value.length >= data.value.data.total
|
||||
hasMore.value = !isLastPage
|
||||
loadMoreState.value = isLastPage ? 'finished' : 'loading'
|
||||
if (!isLastPage)
|
||||
page.value += 1
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function onPageChange({ value }) {
|
||||
page.value = value
|
||||
fetchSubordinates()
|
||||
}
|
||||
|
||||
// 格式化金额
|
||||
function formatNumber(num) {
|
||||
if (!num)
|
||||
@@ -45,15 +77,26 @@ function formatNumber(num) {
|
||||
|
||||
/** 与后端 /agent/subordinate/list 一致:level_name,兼容历史 level 字段 */
|
||||
function getLevelText(item) {
|
||||
return item?.level_name || item?.level || '普通'
|
||||
const raw = String(item?.level_name || item?.level || '').trim()
|
||||
if (!raw)
|
||||
return '普通代理'
|
||||
const cleaned = raw.replace(/代理$/, '')
|
||||
if (cleaned === '普通')
|
||||
return '普通代理'
|
||||
const upper = cleaned.toUpperCase()
|
||||
if (upper.includes('SVIP'))
|
||||
return 'SVIP代理'
|
||||
if (upper.includes('VIP'))
|
||||
return 'VIP代理'
|
||||
return '普通代理'
|
||||
}
|
||||
|
||||
// 获取等级标签样式
|
||||
function getLevelClass(level) {
|
||||
switch (level) {
|
||||
case 'SVIP':
|
||||
case 'SVIP代理':
|
||||
return 'bg-purple-100 text-purple-600'
|
||||
case 'VIP':
|
||||
case 'VIP代理':
|
||||
return 'bg-blue-100 text-blue-600'
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-600'
|
||||
@@ -62,12 +105,22 @@ function getLevelClass(level) {
|
||||
|
||||
// 查看详情
|
||||
function viewDetail(item) {
|
||||
const rawId = item?.id ?? item?.subordinate_id ?? item?.user_id
|
||||
const parsedId = Number.parseInt(String(rawId ?? ''), 10)
|
||||
if (!Number.isFinite(parsedId) || parsedId <= 0) {
|
||||
uni.showToast({ title: '下级信息异常,无法查看详情', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/subordinate-detail?id=${encodeURIComponent(String(item.id))}`,
|
||||
url: `/pages/subordinate-detail?id=${encodeURIComponent(String(parsedId))}`,
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSubordinates(true)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
fetchSubordinates()
|
||||
})
|
||||
</script>
|
||||
@@ -105,7 +158,7 @@ onMounted(() => {
|
||||
{{ item.mobile }}
|
||||
</view>
|
||||
<text class="rounded-full px-3 py-1 text-sm font-medium" :class="[getLevelClass(getLevelText(item))]">
|
||||
{{ getLevelText(item) }}代理
|
||||
{{ getLevelText(item) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -162,16 +215,7 @@ onMounted(() => {
|
||||
<view v-else-if="!subordinates.length" class="py-4 text-center text-sm text-gray-400">
|
||||
暂无下级代理
|
||||
</view>
|
||||
</view>
|
||||
<view class="px-4 pb-4">
|
||||
<wd-pagination
|
||||
v-model="page"
|
||||
:total="statistics.totalSubordinates"
|
||||
:page-size="pageSize"
|
||||
show-icon
|
||||
show-message
|
||||
@change="onPageChange"
|
||||
/>
|
||||
<wd-loadmore :state="loadMoreState" @reload="fetchSubordinates" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -183,7 +227,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.subordinate-scroll {
|
||||
height: calc(100vh - 180px);
|
||||
min-height: calc(100vh - 180px);
|
||||
}
|
||||
|
||||
.subordinate-item {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
definePage({ layout: 'default', auth: true })
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
// 状态映射配置
|
||||
const statusConfig = {
|
||||
1: {
|
||||
@@ -38,10 +39,8 @@ const data = ref({
|
||||
list: [],
|
||||
})
|
||||
const loading = ref(false)
|
||||
const pageCount = computed(() => {
|
||||
const total = Number(data.value.total || 0)
|
||||
return total > 0 ? Math.ceil(total / pageSize.value) : 1
|
||||
})
|
||||
const hasMore = ref(true)
|
||||
const loadMoreState = ref('loading')
|
||||
|
||||
// 账户脱敏处理
|
||||
function maskName(name) {
|
||||
@@ -86,32 +85,63 @@ function getAmountColor(status) {
|
||||
}
|
||||
|
||||
// 获取当前页数据
|
||||
async function getData() {
|
||||
function mergeUniqueById(oldList, newList) {
|
||||
const map = new Map()
|
||||
const resolveKey = (item, index) => String(item?.id ?? item?.withdraw_no ?? `${item?.create_time || ''}_${item?.amount || ''}_${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 getData(reset = false) {
|
||||
try {
|
||||
if (loading.value)
|
||||
return
|
||||
if (!reset && !hasMore.value)
|
||||
return
|
||||
loading.value = true
|
||||
if (reset) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadMoreState.value = 'loading'
|
||||
data.value = { total: 0, list: [] }
|
||||
}
|
||||
const { data: res, error } = await useApiFetch(
|
||||
`/agent/withdrawal?page=${page.value}&page_size=${pageSize.value}`,
|
||||
{ silent: true },
|
||||
)
|
||||
.get()
|
||||
.json()
|
||||
if (res.value?.code === 200 && !error.value)
|
||||
data.value = res.value.data
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
const incoming = res.value.data.list || []
|
||||
data.value.total = res.value.data.total || 0
|
||||
data.value.list = reset ? incoming : mergeUniqueById(data.value.list, incoming)
|
||||
const isLastPage = incoming.length < pageSize.value || data.value.list.length >= data.value.total
|
||||
hasMore.value = !isLastPage
|
||||
loadMoreState.value = isLastPage ? 'finished' : 'loading'
|
||||
if (!isLastPage)
|
||||
page.value += 1
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onPageChange({ value }) {
|
||||
page.value = value
|
||||
getData()
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
onMounted(async () => {
|
||||
page.value = 1
|
||||
await getData()
|
||||
await getData(true)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -173,17 +203,7 @@ onMounted(async () => {
|
||||
</view>
|
||||
</view>
|
||||
<view class="pagination-wrap">
|
||||
<wd-pagination
|
||||
v-model="page"
|
||||
:total="data.total"
|
||||
:page-size="pageSize"
|
||||
show-icon
|
||||
show-message
|
||||
@change="onPageChange"
|
||||
/>
|
||||
<text class="mt-1 block text-center text-xs text-gray-400">
|
||||
共 {{ pageCount }} 页
|
||||
</text>
|
||||
<wd-loadmore :state="loadMoreState" @reload="getData" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -204,9 +224,6 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.pagination-wrap {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
background: #fff;
|
||||
padding: 8px 12px 12px;
|
||||
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
44
src/static/launchImg/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24765" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_9" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24743"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="440" height="956"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="launchscreen.png" translatesAutoresizingMaskIntoConstraints="NO" id="eKa-Do-o9M">
|
||||
<rect key="frame" x="0.0" y="124" width="440" height="764"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="IW3-oA-Ytg"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="IW3-oA-Ytg" firstAttribute="bottom" secondItem="eKa-Do-o9M" secondAttribute="bottom" id="C62-2E-pZC"/>
|
||||
<constraint firstItem="IW3-oA-Ytg" firstAttribute="trailing" secondItem="eKa-Do-o9M" secondAttribute="trailing" id="DqN-c8-YSD"/>
|
||||
<constraint firstItem="eKa-Do-o9M" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="UM0-sL-jGn"/>
|
||||
<constraint firstItem="eKa-Do-o9M" firstAttribute="top" secondItem="IW3-oA-Ytg" secondAttribute="top" id="iwy-bq-Ia7"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="52.173913043478265" y="374.33035714285711"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="launchscreen.png" width="596.15997314453125" height="1059.8399658203125"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
BIN
src/static/launchImg/android@1080x1882.png
Normal file
|
After Width: | Height: | Size: 808 KiB |
BIN
src/static/launchImg/android@480x762.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
src/static/launchImg/android@720x1242.png
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
src/static/launchImg/launchscreen@2x.png
Normal file
|
After Width: | Height: | Size: 716 KiB |
BIN
src/static/launchImg/launchscreen@3x.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
src/static/launchImg/storyboard.zip
Normal file
46
src/utils/promotionPricing.js
Normal file
@@ -0,0 +1,46 @@
|
||||
export function safeTruncate(num, decimals = 2) {
|
||||
if (Number.isNaN(num) || !Number.isFinite(num))
|
||||
return '0.00'
|
||||
|
||||
const factor = 10 ** decimals
|
||||
const scaled = Math.trunc(num * factor)
|
||||
return (scaled / factor).toFixed(decimals)
|
||||
}
|
||||
|
||||
function calculatePlatformOverpricingCost(price, config) {
|
||||
if (price <= config.p_pricing_standard)
|
||||
return 0
|
||||
return (price - config.p_pricing_standard) * config.p_overpricing_ratio
|
||||
}
|
||||
|
||||
function calculateSuperiorOverpricingCost(price, config) {
|
||||
if (config.a_overpricing_ratio <= 0)
|
||||
return 0
|
||||
if (price <= config.a_pricing_standard)
|
||||
return 0
|
||||
if (config.a_pricing_end <= config.a_pricing_standard)
|
||||
return 0
|
||||
|
||||
const superiorRangeAmount = Math.min(price, config.a_pricing_end) - config.a_pricing_standard
|
||||
return Math.max(0, superiorRangeAmount) * config.a_overpricing_ratio
|
||||
}
|
||||
|
||||
export function calculatePromotionPricing(priceInput, config) {
|
||||
if (!config)
|
||||
return { costPrice: '0.00', promotionRevenue: '0.00' }
|
||||
|
||||
const price = Number(priceInput)
|
||||
if (!Number.isFinite(price))
|
||||
return { costPrice: '0.00', promotionRevenue: '0.00' }
|
||||
|
||||
const baseCost = Number(config.cost_price) || 0
|
||||
const platformOverpricingCost = calculatePlatformOverpricingCost(price, config)
|
||||
const superiorOverpricingCost = calculateSuperiorOverpricingCost(price, config)
|
||||
const totalCost = baseCost + platformOverpricingCost + superiorOverpricingCost
|
||||
const revenue = price - totalCost
|
||||
|
||||
return {
|
||||
costPrice: safeTruncate(totalCost),
|
||||
promotionRevenue: safeTruncate(revenue),
|
||||
}
|
||||
}
|
||||