418 lines
13 KiB
Vue
418 lines
13 KiB
Vue
<script setup lang="ts">
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { getFundSummary, getCommissionRecords, getCommissionStats } from '@/api/commission'
|
||
import type { FundSummary, CommissionRecord, CommissionStats } from '@/api/commission'
|
||
import SimplePicker from '@/components/SimplePicker.vue'
|
||
|
||
// 佣金概览
|
||
const commissionSummary = ref<FundSummary | null>(null)
|
||
|
||
// 店铺ID (从登录信息获取)
|
||
const shopId = ref<number>(0)
|
||
|
||
// 从本地存储获取用户信息
|
||
function getUserInfo() {
|
||
const userInfo = uni.getStorageSync('user_info')
|
||
return userInfo || null
|
||
}
|
||
|
||
// 佣金明细列表
|
||
const commissionRecords = ref<CommissionRecord[]>([])
|
||
|
||
// 佣金统计
|
||
const commissionStats = ref<CommissionStats | null>(null)
|
||
|
||
// 加载状态
|
||
const loading = ref(false)
|
||
const loadingRecords = ref(false)
|
||
|
||
// 分页参数
|
||
const page = ref(1)
|
||
const pageSize = 20
|
||
const total = ref(0)
|
||
|
||
// 搜索关键词
|
||
const searchKeyword = ref('')
|
||
|
||
// 佣金来源筛选
|
||
const sourceFilter = ref<string | undefined>(undefined)
|
||
const showSourcePicker = ref(false)
|
||
|
||
// 佣金来源选项
|
||
const sourceOptions = [
|
||
{ label: '全部来源', value: undefined },
|
||
{ label: '成本价差', value: 'cost_diff' },
|
||
{ label: '一次性佣金', value: 'one_time' },
|
||
]
|
||
|
||
// 获取当前选中的来源名称
|
||
const selectedSourceName = computed(() => {
|
||
if (sourceFilter.value === undefined) return '佣金来源'
|
||
const option = sourceOptions.find(o => o.value === sourceFilter.value)
|
||
return option?.label || '佣金来源'
|
||
})
|
||
|
||
// 格式化金额(分转元,带千分号)
|
||
function formatAmount(amount: number) {
|
||
const yuan = (amount / 100).toFixed(2)
|
||
const parts = yuan.split('.')
|
||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||
return `¥${parts.join('.')}`
|
||
}
|
||
|
||
// 获取佣金来源文本
|
||
function getSourceText(source: string) {
|
||
const map: Record<string, string> = {
|
||
cost_diff: '成本价差',
|
||
one_time: '一次性佣金',
|
||
tier_bonus: '梯度奖励',
|
||
}
|
||
return map[source] || source
|
||
}
|
||
|
||
// 获取佣金状态文本
|
||
function getStatusText(status: number) {
|
||
const map: Record<number, string> = {
|
||
1: '已入账',
|
||
2: '已失效',
|
||
}
|
||
return map[status] || '未知'
|
||
}
|
||
|
||
// 获取佣金状态颜色
|
||
function getStatusColor(status: number) {
|
||
const map: Record<number, { bg: string, text: string }> = {
|
||
1: { bg: '#e8f5e9', text: '#2e7d32' }, // 已入账
|
||
2: { bg: '#f5f5f5', text: '#999' }, // 已失效
|
||
}
|
||
return map[status] || { bg: '#f5f5f5', text: '#999' }
|
||
}
|
||
|
||
// 格式化时间
|
||
function formatTime(time: string) {
|
||
if (!time) return '-'
|
||
const date = new Date(time)
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
const hour = String(date.getHours()).padStart(2, '0')
|
||
const minute = String(date.getMinutes()).padStart(2, '0')
|
||
return `${month}-${day} ${hour}:${minute}`
|
||
}
|
||
|
||
// 页面加载
|
||
onMounted(() => {
|
||
// 从登录信息获取 shop_id
|
||
const userInfo = getUserInfo()
|
||
if (userInfo?.shop_id) {
|
||
shopId.value = userInfo.shop_id
|
||
}
|
||
|
||
loadCommissionSummary()
|
||
loadCommissionStats()
|
||
loadCommissionRecords()
|
||
})
|
||
|
||
// 加载佣金概览
|
||
async function loadCommissionSummary() {
|
||
try {
|
||
const summary = await getFundSummary()
|
||
commissionSummary.value = summary
|
||
}
|
||
catch (error) {
|
||
console.error('加载佣金概览失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载佣金统计
|
||
async function loadCommissionStats() {
|
||
if (!shopId.value) return
|
||
|
||
try {
|
||
const stats = await getCommissionStats(shopId.value)
|
||
commissionStats.value = stats
|
||
}
|
||
catch (error) {
|
||
console.error('加载佣金统计失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载佣金明细
|
||
async function loadCommissionRecords() {
|
||
if (!shopId.value) return
|
||
|
||
loadingRecords.value = true
|
||
|
||
try {
|
||
const params: any = {
|
||
page: page.value,
|
||
page_size: pageSize,
|
||
}
|
||
|
||
// 佣金来源筛选
|
||
if (sourceFilter.value) {
|
||
params.commission_source = sourceFilter.value
|
||
}
|
||
|
||
// 搜索关键词(支持 ICCID、虚拟号、订单号)
|
||
if (searchKeyword.value) {
|
||
const keyword = searchKeyword.value.trim()
|
||
// 根据关键词类型判断搜索字段
|
||
if (/^\d{19,20}$/.test(keyword)) {
|
||
// ICCID 通常是19-20位数字
|
||
params.iccid = keyword
|
||
}
|
||
else if (/^\d{15}$/.test(keyword)) {
|
||
// 虚拟号通常是15位数字
|
||
params.virtual_no = keyword
|
||
}
|
||
else {
|
||
// 订单号
|
||
params.order_no = keyword
|
||
}
|
||
}
|
||
|
||
const response = await getCommissionRecords(shopId.value, params)
|
||
|
||
if (page.value === 1) {
|
||
commissionRecords.value = response.items || []
|
||
}
|
||
else {
|
||
commissionRecords.value.push(...(response.items || []))
|
||
}
|
||
|
||
total.value = response.total || 0
|
||
}
|
||
catch (error) {
|
||
console.error('加载佣金明细失败:', error)
|
||
}
|
||
finally {
|
||
loadingRecords.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索
|
||
function handleSearch() {
|
||
page.value = 1
|
||
commissionRecords.value = []
|
||
loadCommissionRecords()
|
||
}
|
||
|
||
// 清空搜索
|
||
function clearSearch() {
|
||
searchKeyword.value = ''
|
||
page.value = 1
|
||
commissionRecords.value = []
|
||
loadCommissionRecords()
|
||
}
|
||
|
||
// 来源筛选变化
|
||
function onSourceConfirm(option: { label: string, value: any }) {
|
||
sourceFilter.value = option.value
|
||
page.value = 1
|
||
commissionRecords.value = []
|
||
loadCommissionRecords()
|
||
}
|
||
|
||
// 加载更多
|
||
function loadMore() {
|
||
if (commissionRecords.value.length >= total.value) {
|
||
return
|
||
}
|
||
page.value++
|
||
loadCommissionRecords()
|
||
}
|
||
|
||
// 刷新
|
||
function refreshList() {
|
||
page.value = 1
|
||
commissionRecords.value = []
|
||
loadCommissionSummary()
|
||
loadCommissionStats()
|
||
loadCommissionRecords()
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<view class="bg-[var(--bg-page)] min-h-screen pb-safe">
|
||
<view v-if="loading" class="pt-20 center">
|
||
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||
</view>
|
||
|
||
<view v-else>
|
||
<view class="bg-brand px-4 py-3 mx-4 mt-4 rounded-12px">
|
||
<text class="text-11px text-white/60">可提现佣金</text>
|
||
<text class="text-24px font-700 text-white block mt-1">
|
||
{{ commissionSummary ? formatAmount(commissionSummary.available_commission) : '--' }}
|
||
</text>
|
||
</view>
|
||
|
||
<view class="bg-[var(--bg-container)] rounded-12px p-4 mx-4 mt-3">
|
||
<view class="grid grid-cols-3 gap-4">
|
||
<view class="text-center">
|
||
<text class="text-11px text-[var(--text-tertiary)] block">累计佣金</text>
|
||
<text class="text-16px font-700 text-[var(--text-primary)] mt-1">
|
||
{{ commissionSummary ? formatAmount(commissionSummary.total_commission) : '--' }}
|
||
</text>
|
||
</view>
|
||
<view class="text-center">
|
||
<text class="text-11px text-[var(--text-tertiary)] block">冻结</text>
|
||
<text class="text-16px font-700 text-[var(--text-tertiary)] mt-1">
|
||
{{ commissionSummary ? formatAmount(commissionSummary.frozen_commission) : '--' }}
|
||
</text>
|
||
</view>
|
||
<view class="text-center">
|
||
<text class="text-11px text-[var(--text-tertiary)] block">已提现</text>
|
||
<text class="text-16px font-700 text-[#52c41a] mt-1">
|
||
{{ commissionSummary ? formatAmount(commissionSummary.withdrawn_commission) : '--' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="px-4 mt-4">
|
||
<text class="text-14px font-600 text-[var(--text-primary)] block mb-2">佣金统计</text>
|
||
<view class="bg-[var(--bg-container)] rounded-12px p-4">
|
||
<view class="flex items-center justify-between">
|
||
<text class="text-13px text-[var(--text-secondary)]">成本价差</text>
|
||
<view class="flex items-center gap-2">
|
||
<text class="text-14px font-600 text-[var(--text-primary)]">
|
||
{{ commissionStats ? formatAmount(commissionStats.cost_diff_amount) : '--' }}
|
||
</text>
|
||
<text class="text-11px text-[var(--text-tertiary)]">
|
||
{{ commissionStats ? (commissionStats.cost_diff_percent / 10).toFixed(1) + '%' : '--' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view class="flex items-center justify-between mt-3">
|
||
<text class="text-13px text-[var(--text-secondary)]">一次性佣金</text>
|
||
<view class="flex items-center gap-2">
|
||
<text class="text-14px font-600 text-[var(--text-primary)]">
|
||
{{ commissionStats ? formatAmount(commissionStats.one_time_amount) : '--' }}
|
||
</text>
|
||
<text class="text-11px text-[var(--text-tertiary)]">
|
||
{{ commissionStats ? (commissionStats.one_time_percent / 10).toFixed(1) + '%' : '--' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view class="flex items-center justify-between mt-3 pt-3 border-t border-[var(--border-secondary)]">
|
||
<text class="text-13px font-600 text-[var(--text-primary)]">总计</text>
|
||
<text class="text-16px font-700 text-brand">
|
||
{{ commissionStats ? formatAmount(commissionStats.total_amount) : '--' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="px-4 mt-4">
|
||
<text class="text-14px font-600 text-[var(--text-primary)] block mb-2">佣金明细</text>
|
||
|
||
<view class="flex items-center gap-2 mb-3">
|
||
<input
|
||
v-model="searchKeyword"
|
||
class="flex-1 h-40px bg-[var(--bg-container)] rounded-8px px-3 text-14px text-[var(--text-primary)]"
|
||
placeholder="搜索 ICCID / 虚拟号 / 订单号"
|
||
placeholder-class="text-[var(--text-quaternary)]"
|
||
@confirm="handleSearch"
|
||
>
|
||
<view
|
||
class="px-4 h-40px bg-brand text-white text-14px rounded-8px flex items-center justify-center"
|
||
@click="handleSearch"
|
||
>
|
||
搜索
|
||
</view>
|
||
</view>
|
||
|
||
<view class="flex items-center gap-2 mb-3">
|
||
<view class="flex-1 bg-[var(--bg-container)] rounded-8px px-3 h-40px flex items-center" @click="showSourcePicker = true">
|
||
<text class="flex-1 text-14px text-[var(--text-primary)]">{{ selectedSourceName }}</text>
|
||
<text class="text-12px text-[var(--text-tertiary)]">▼</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="flex items-center justify-between mb-3">
|
||
<text class="text-12px text-[var(--text-secondary)]">共 {{ total }} 条记录</text>
|
||
</view>
|
||
|
||
<view v-if="loadingRecords && page === 1" class="pt-10 center">
|
||
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||
</view>
|
||
|
||
<view v-else-if="commissionRecords.length > 0" class="space-y-2">
|
||
<view
|
||
v-for="record in commissionRecords"
|
||
:key="record.id"
|
||
class="bg-[var(--bg-container)] rounded-12px p-4"
|
||
>
|
||
<view class="flex items-center justify-between">
|
||
<view class="flex items-center gap-2">
|
||
<text class="text-14px font-600 text-[var(--text-primary)]">
|
||
{{ getSourceText(record.commission_source) }}
|
||
</text>
|
||
<view
|
||
:style="{
|
||
backgroundColor: getStatusColor(record.status).bg,
|
||
color: getStatusColor(record.status).text,
|
||
}"
|
||
class="px-2 py-0.5 rounded text-11px font-600"
|
||
>
|
||
{{ getStatusText(record.status) }}
|
||
</view>
|
||
</view>
|
||
<text class="text-18px font-700 text-brand">
|
||
+{{ formatAmount(record.amount) }}
|
||
</text>
|
||
</view>
|
||
<view class="flex items-center justify-between mt-2">
|
||
<text class="text-12px text-[var(--text-tertiary)]">{{ formatTime(record.created_at) }}</text>
|
||
<text v-if="record.order_id" class="text-11px text-[var(--text-tertiary)]">订单ID: {{ record.order_id }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="commissionRecords.length < total" class="py-3 center">
|
||
<text v-if="loadingRecords" class="text-12px text-[var(--text-secondary)]">加载中...</text>
|
||
<text v-else class="text-12px text-brand" @click="loadMore">加载更多</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-else class="pt-10 center">
|
||
<text class="text-14px text-[var(--text-secondary)]">暂无佣金记录</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="h-16" />
|
||
</view>
|
||
|
||
<SimplePicker
|
||
v-model:show="showSourcePicker"
|
||
:options="sourceOptions"
|
||
title="选择佣金来源"
|
||
@confirm="onSourceConfirm"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.center {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.space-y-2 > view:not(:last-child) {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
from {
|
||
transform: rotate(0deg);
|
||
}
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.animate-spin {
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
</style>
|