Files
gt-agent-company/src/pages/agent-system/commission-center/index.vue
sexygoat 91d7d4c21f
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 1m27s
fix: 优化界面以及更新接口
2026-04-15 11:54:43 +08:00

418 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>