bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 7m10s

This commit is contained in:
sexygoat
2026-04-11 12:19:35 +08:00
parent 90ae585651
commit 33e15314ac
30 changed files with 1077 additions and 532 deletions

View File

@@ -7,9 +7,9 @@
"apiKey": "sk-ZBGcMXCdwtSK7G35s"
}
},
"google": {
"minimax": {
"options": {
"baseURL": "http://45.155.220.179:8317/v1beta",
"baseURL": "http://45.155.220.179:8317/v1",
"apiKey": "sk-ZBGcMXCdwtSK7G35s"
}
}

View File

@@ -9,14 +9,12 @@
### 1. 套餐管理模块 (Package Management) ✅
- [x] **套餐系列页面** (`/package-management/package-series`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增套餐系列"按钮添加 `v-permission="'package_series:add'"`
- ✅ 为状态切换添加 `disabled: !hasAuth('package_series:update_status')`
- ✅ 在操作列中使用 `hasAuth()` 判断编辑权限 (`package_series:edit`)和删除权限 (`package_series:delete`)
- [x] **套餐列表页面** (`/package-management/package-list`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增套餐"按钮添加 `v-permission="'package:add'"`
- ✅ 为上/下架切换添加 `disabled: !hasAuth('package:update_away')`
@@ -24,7 +22,6 @@
- ✅ 在操作列中使用 `hasAuth()` 判断编辑 (`package:edit`) 和删除 (`package:delete`) 权限
- [x] **单套餐分配页面** (`/package-management/package-assign`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增分配"按钮添加 `v-permission="'package_assign:add'"`
- ✅ 为状态切换添加 `disabled: !hasAuth('package_assign:update_status')`
@@ -53,7 +50,6 @@
### 3. 账号管理模块 (Account Management) ✅
- [x] **账号列表页面** (`/account-management/account`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增账号"按钮添加 `v-permission="'account:add'"`
- ✅ 在操作列中使用 `hasAuth()` 判断以下权限:
@@ -61,11 +57,9 @@
- 编辑: `account:edit`
- 删除: `account:delete`
### 4. 资产管理模块 (Asset Management) ✅
- [x] **IoT卡管理页面** (`/asset-management/iot-card-management`)
- ✅ 引入 `useAuth` composable
- ✅ 为表格头部操作按钮添加权限控制:
- 批量分配: `v-permission="'iot_card:batch_allocation'"`
@@ -79,19 +73,16 @@
- ✅ 验证权限控制正确工作
- [x] **IoT卡任务页面** (`/asset-management/iot-card-task`)
- ✅ 引入 `useAuth` composable
- ✅ 为"批量导入IoT卡"按钮添加 `v-permission="''"`
- ✅ 验证权限控制正确工作
- [x] **设备任务页面** (`/asset-management/device-task`)
- ✅ 引入 `useAuth` composable
- ✅ 为"批量导入设备"按钮添加 `v-permission="'device_task:bulk_import'"`
- ✅ 验证权限控制正确工作
- [x] **设备管理页面** (`/asset-management/device-list`)
- ✅ 引入 `useAuth` composable
- ✅ 为表格头部操作按钮添加权限控制:
- 批量分配: `v-permission="'devices:batch_allocation'"`
@@ -110,7 +101,6 @@
### 5. 账户管理模块 (Account Module) ✅
- [x] **企业客户页面** (`/account/enterprise-customer`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增企业客户"按钮添加 `v-permission="'enterprise_customer:add'"`
- ✅ 在操作列中使用 `hasAuth()` 判断以下权限:
@@ -121,7 +111,6 @@
- ✅ 验证权限控制正确工作
- [x] **运营商管理页面** (`/account/carrier-management`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增运营商"按钮添加 `v-permission="'carrier:add'"`
- ✅ 为状态切换添加 `disabled: !hasAuth('carrier:update_status')`
@@ -129,24 +118,21 @@
- ✅ 验证权限控制正确工作
- [x] **订单管理页面** (`/account/orders`)
- ✅ 引入 `useAuth` composable
- ✅ 为"创建订单"按钮添加 `v-permission="'orders:add'"`
- ✅ 验证权限控制正确工作
- [x] **提现配置页面** (`/account/commission/withdrawal-settings`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增配置"按钮添加 `v-permission="'withdrawal_settings:add'"`
- ✅ 验证权限控制正确工作
- [x] **我的佣金页面** (`/account/commission/my-commission`)
- ✅ 引入 `useAuth` composable
- ✅ 为"发起提现"按钮添加 `v-permission="'my_commission:add'"`
- ✅ 验证权限控制正确工作
- [x] **代理商佣金管理页面** (`/account/commission/agent-commission`)
- [x] **代理商资金概况页面** (`/account/commission/agent-fund-overview`)
- ✅ 引入 `useAuth` composable
- ✅ 在操作列中使用 `hasAuth()` 判断查看详情权限 (`agent_commission:detail`)
- ✅ 验证权限控制正确工作
@@ -162,7 +148,6 @@
### 7. 测试与文档
- [ ] **权限控制测试**
- 创建不同权限组合的测试用户
- 验证每个页面的按钮显示/隐藏符合预期
- 验证状态切换的启用/禁用符合预期

View File

@@ -93,10 +93,10 @@ export class CommissionService extends BaseService {
)
}
// ==================== 代理商佣金管理 ====================
// ==================== 代理商资金概况 ====================
/**
* 获取代理商金明细
* 获取代理商金明细
* GET /api/admin/shops/{shop_id}/commission-records
*/
static getShopCommissionRecords(
@@ -137,7 +137,7 @@ export class CommissionService extends BaseService {
}
/**
* 获取代理商金统计
* 获取代理商金统计
* GET /api/admin/shops/{shop_id}/commission-stats
*/
static getShopCommissionStats(

View File

@@ -215,7 +215,7 @@
try {
const params: any = {
page: pagination.page,
pageSize: pagination.pageSize,
page_size: pagination.pageSize,
username: searchForm.username || undefined,
phone: searchForm.phone || undefined,
user_type: searchForm.user_type,

View File

@@ -58,9 +58,15 @@ export const WithdrawalMethodMap = {
bank: { label: '银行卡', icon: 'bank' }
}
// ========== 佣金类型映射 ==========
// ========== 佣金来源映射 ==========
// 佣金类型映射 (one_time:一次性佣金, long_term:长期佣金)
// 佣金来源映射 (cost_diff:成本差价, one_time:一次性佣金)
export const CommissionSourceMap = {
cost_diff: { label: '成本差价', type: 'primary' as const },
one_time: { label: '一次性佣金', type: 'success' as const }
}
// 佣金类型映射 (one_time:一次性佣金, long_term:长期佣金) - 兼容旧字段
export const CommissionTypeMap = {
one_time: { label: '一次性佣金', type: 'primary' as const },
long_term: { label: '长期佣金', type: 'success' as const }

View File

@@ -395,14 +395,14 @@
"commission": {
"title": "佣金管理",
"withdrawal": "提现审批",
"withdrawalSettings": "提现配置",
"myCommission": "我的佣金",
"agentCommission": "代理商佣金"
"agentCommission": "代理商资金概况"
},
"settings": {
"title": "设置管理",
"wechatPayConfiguration": "微信支付配置",
"detailsOfPaymentConfiguration": "支付配置详情"
"detailsOfPaymentConfiguration": "支付配置详情",
"withdrawalSettings": "提现配置"
}
},
"table": {

View File

@@ -543,17 +543,6 @@ export const asyncRoutes: AppRouteRecord[] = [
roles: ['R_SUPER', 'R_ADMIN']
}
},
// 提现配置
{
path: 'withdrawal-settings',
name: 'CommissionWithdrawalSettings',
component: RoutesAlias.CommissionWithdrawalSettings,
meta: {
title: 'menus.commission.withdrawalSettings',
keepAlive: true,
roles: ['R_SUPER', 'R_ADMIN']
}
},
// 我的佣金
{
path: 'my-commission',
@@ -565,11 +554,11 @@ export const asyncRoutes: AppRouteRecord[] = [
roles: ['R_AGENT']
}
},
// 代理商佣金
// 代理商资金概况
{
path: 'agent-commission',
name: 'AgentCommission',
component: RoutesAlias.AgentCommission,
path: 'agent-fund-overview',
name: 'AgentFundOverview',
component: RoutesAlias.AgentFundOverview,
meta: {
title: 'menus.commission.agentCommission',
keepAlive: true,
@@ -599,6 +588,17 @@ export const asyncRoutes: AppRouteRecord[] = [
keepAlive: true
}
},
// 提现配置
{
path: 'withdrawal-settings',
name: 'WithdrawalSettings',
component: RoutesAlias.WithdrawalSettings,
meta: {
title: 'menus.settings.withdrawalSettings',
keepAlive: true,
roles: ['R_SUPER', 'R_ADMIN']
}
},
// 微信支付配置详情
{
path: 'wechat-config/detail/:id',

View File

@@ -72,11 +72,11 @@ export enum RoutesAlias {
// 佣金管理
WithdrawalApproval = '/commission-management/withdrawal-approval', // 提现审批
CommissionWithdrawalSettings = '/commission-management/withdrawal-settings', // 提现配置
MyCommission = '/commission-management/my-commission', // 我的佣金
AgentCommission = '/commission-management/agent-commission', // 代理商佣金管理
AgentFundOverview = '/commission-management/agent-fund-overview', // 代理商资金概况
// 设置管理
WithdrawalSettings = '/settings/withdrawal-settings', // 提现配置
WechatConfig = '/settings/wechat-config', // 微信支付配置
WechatConfigDetail = '/settings/wechat-config/detail', // 微信支付配置详情

View File

@@ -59,6 +59,7 @@ export interface WithdrawalRequestItem {
account_number: string // 收款账号
bank_name?: string // 银行名称
status: WithdrawalStatus // 状态
status_name: string // 状态名称
payment_type: PaymentType // 放款类型
applicant_id: number // 申请人账号ID
applicant_name: string // 申请人用户名
@@ -68,12 +69,14 @@ export interface WithdrawalRequestItem {
remark?: string // 备注
created_at: string // 申请时间
processed_at?: string // 处理时间
paid_at?: string // 到账时间
}
/**
* 提现申请查询参数
*/
export interface WithdrawalRequestQueryParams extends PaginationParams {
page_size?: number // 每页数量
status?: WithdrawalStatus // 状态筛选
withdrawal_no?: string // 提现单号
shop_name?: string // 店铺名称
@@ -141,10 +144,14 @@ export interface MyCommissionRecordItem {
* 佣金记录查询参数
*/
export interface CommissionRecordQueryParams extends PaginationParams {
commission_type?: CommissionType // 佣金类型
status?: CommissionStatus // 状态
page_size?: number // 每页数量
commission_source?: string // 佣金来源cost_diff:成本差价one_time:一次性佣金)
status?: number // 状态1已入账2已失效
start_time?: string // 开始时间
end_time?: string // 结束时间
iccid?: string // ICCID模糊查询
virtual_no?: string // 设备虚拟号(模糊查询)
order_no?: string // 订单号(模糊查询)
}
/**
@@ -171,23 +178,26 @@ export interface SubmitWithdrawalParams {
remark?: string // 备注
}
// ==================== 代理商佣金相关 ====================
// ==================== 代理商资金概况相关 ====================
/**
* 代理商金记录项
* 代理商金记录项
*/
export interface ShopCommissionRecordItem {
id: number // 佣金记录ID
amount: number // 佣金金额(分)
balance_after: number // 入账后佣金余额(分)
commission_type: CommissionType // 佣金类型
status: CommissionStatus // 状态
commission_source: string // 佣金来源cost_diff:成本差价one_time:一次性佣金)
status: number // 状态1已冻结, 2解冻中, 3已发放, 4已失效
status_name: string // 状态名称
order_id?: number // 订单ID
order_no?: string // 订单号
order_created_at?: string // 订单创建时间
iccid?: string // ICCID
virtual_no?: string // 虚拟号(原 device_no
created_at: string // 佣金入账时间
seller_shop_id?: number // 卖家店铺ID
seller_shop_name?: string // 卖家店铺名称
}
/**
@@ -314,7 +324,7 @@ export interface MainWalletTransactionPageResult {
// ==================== 我的佣金统计相关 ====================
/**
* 代理商金统计查询参数shop_id 通过路径参数传递,不再在 query 中)
* 代理商金统计查询参数shop_id 通过路径参数传递,不再在 query 中)
*/
export interface ShopCommissionStatsQueryParams {
start_time?: string // 开始时间
@@ -327,7 +337,7 @@ export interface ShopCommissionStatsQueryParams {
export type MyCommissionStatsQueryParams = ShopCommissionStatsQueryParams
/**
* 代理商金统计响应
* 代理商金统计响应
*/
export interface CommissionStatsResponse {
total_amount: number // 总收入(分)
@@ -346,7 +356,7 @@ export interface CommissionStatsResponse {
export type MyCommissionStatsResponse = CommissionStatsResponse
/**
* 代理商每日金统计查询参数shop_id 通过路径参数传递)
* 代理商每日金统计查询参数shop_id 通过路径参数传递)
*/
export interface DailyCommissionStatsQueryParams {
start_date?: string // 开始日期YYYY-MM-DD
@@ -360,7 +370,7 @@ export interface DailyCommissionStatsQueryParams {
export type MyDailyCommissionStatsQueryParams = DailyCommissionStatsQueryParams
/**
* 每日金统计项
* 每日金统计项
*/
export interface DailyCommissionStatsItem {
date: string // 日期YYYY-MM-DD

View File

@@ -12,10 +12,6 @@ export interface BaseResponse<T = any> {
// 分页请求参数
export interface PaginationParams {
current?: number
size?: number
pageNum?: number
pageSize?: number
page?: number
page_size?: number
}

View File

@@ -34,6 +34,7 @@ export interface OrderItem {
quantity: number
unit_price: number // 单价(分)
amount: number // 小计金额(分)
package_type?: string // 套餐类型快照formal正式套餐或 addon加油包
}
// 订单响应
@@ -43,6 +44,8 @@ export interface Order {
order_type: OrderType
buyer_id: number
buyer_type: BuyerType
buyer_phone?: string // 买家手机号快照(个人客户下单时填充,代理商订单为空)
buyer_nickname?: string // 买家微信昵称快照(个人客户下单时填充,代理商订单为空)
payment_status: PaymentStatus
payment_status_text: string
payment_method: OrderPaymentMethod
@@ -72,6 +75,7 @@ export interface OrderQueryParams {
order_no?: string
identifier?: string // 按资产标识符过滤
purchase_role?: string // 订单渠道
buyer_phone?: string // 按买家手机号精确过滤
start_time?: string
end_time?: string
dateRange?: string[] | any // For date range picker in UI

View File

@@ -130,7 +130,8 @@
/>
</div>
<div class="panel-body">
<ElRadioGroup v-model="roleToAdd" class="role-list">
<ElEmpty v-if="filteredAvailableRoles.length === 0" description="暂无角色" />
<ElRadioGroup v-else v-model="roleToAdd" class="role-list">
<div v-for="role in filteredAvailableRoles" :key="role.ID" class="role-item">
<ElRadio :label="role.ID" :disabled="selectedRoles.includes(role.ID)">
<span class="role-info">
@@ -204,7 +205,7 @@
<script setup lang="ts">
import { h } from 'vue'
import { useRoute } from 'vue-router'
import { FormInstance, ElSwitch, ElCheckbox, ElCheckboxGroup, ElTag } from 'element-plus'
import { FormInstance, ElSwitch, ElCheckbox, ElCheckboxGroup, ElTag, ElEmpty } from 'element-plus'
import { ElMessageBox, ElMessage } from 'element-plus'
import type { FormRules } from 'element-plus'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
@@ -551,7 +552,6 @@
}
getAccountList()
loadAllRoles()
loadShopList()
loadEnterpriseList()
})
@@ -561,7 +561,7 @@
try {
const params: any = {
page: 1,
pageSize: keyword ? 100 : 20 // 搜索时加载更多默认20条
page_size: keyword ? 100 : 20 // 搜索时加载更多默认20条
}
if (keyword) {
params.role_name = keyword
@@ -713,8 +713,8 @@
loading.value = true
try {
const params = {
page: pagination.currentPage,
pageSize: pagination.pageSize,
page: pagination.page,
page_size: pagination.pageSize,
username: formFilters.name || undefined,
phone: formFilters.phone || undefined,
user_type: formFilters.user_type,

View File

@@ -216,6 +216,12 @@
// fetchAssetDetail 内部已经会并行加载所有相关数据,不需要重复调用
await fetchAssetDetail(identifier)
// 如果是设备类型,需要再次调用搜索接口获取完整数据
if (cardInfo.value?.asset_type === 'device') {
const deviceIdentifier = cardInfo.value.virtual_no || cardInfo.value.iccid || identifier
await fetchAssetDetail(deviceIdentifier)
}
// 从接口数据初始化轮询开关,不触发更新接口
pollingInitializing = true
pollingEnabled.value = cardInfo.value?.enable_polling ?? false
@@ -224,7 +230,7 @@
// 重置分页并加载套餐列表
packagePagination.value.page = 1
const result = await loadPackageList(identifier, 1, packagePagination.value.pageSize)
const result = await loadPackageList(identifier, 1, packagePagination.pageSize)
packagePagination.value.total = result.total
}

View File

@@ -671,6 +671,7 @@
virtual_no: string
device_virtual_no: string
is_distributed: undefined | number
is_standalone: undefined | boolean
[key: string]: any
} = {
status: undefined,
@@ -679,7 +680,8 @@
msisdn: '',
virtual_no: '',
device_virtual_no: '',
is_distributed: undefined
is_distributed: undefined,
is_standalone: undefined
}
// 搜索表单
@@ -858,6 +860,19 @@
{ label: '是', value: true },
{ label: '否', value: false }
]
},
{
label: '独立卡',
prop: 'is_standalone',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: '是(未绑定设备)', value: true },
{ label: '否(已绑定设备)', value: false }
]
}
]

View File

@@ -95,22 +95,24 @@
{{ formatMoney(scope.row.balance_after) }}
</template>
</ElTableColumn>
<ElTableColumn label="佣金类型" prop="commission_type" width="120">
<ElTableColumn label="佣金来源" prop="commission_source" width="120">
<template #default="scope">
<ElTag
:type="
CommissionTypeMap[scope.row.commission_type as keyof typeof CommissionTypeMap]
?.type || 'info'
CommissionSourceMap[
scope.row.commission_source as keyof typeof CommissionSourceMap
]?.type || 'info'
"
>
{{
CommissionTypeMap[scope.row.commission_type as keyof typeof CommissionTypeMap]
?.label || scope.row.commission_type
CommissionSourceMap[
scope.row.commission_source as keyof typeof CommissionSourceMap
]?.label || scope.row.commission_source
}}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="状态" prop="status" width="100">
<ElTableColumn label="状态" prop="status_name" width="100">
<template #default="scope">
<ElTag
:type="
@@ -119,13 +121,18 @@
"
>
{{
CommissionStatusMap[scope.row.status as keyof typeof CommissionStatusMap]
?.label || scope.row.status
scope.row.status_name || CommissionStatusMap[scope.row.status]?.label || '-'
}}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="订单号" prop="order_no" min-width="180" show-overflow-tooltip />
<ElTableColumn
label="卖家店铺"
prop="seller_shop_name"
min-width="150"
show-overflow-tooltip
/>
<ElTableColumn label="ICCID" prop="iccid" min-width="150" show-overflow-tooltip />
<ElTableColumn
label="设备号"
@@ -369,7 +376,7 @@
</template>
<script setup lang="ts">
import { h, watch, onBeforeUnmount } from 'vue'
import { h, watch, onBeforeUnmount, ref, reactive, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { CommissionService } from '@/api/modules'
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
@@ -389,7 +396,7 @@
CommissionStatusMap,
WithdrawalStatusMap,
WithdrawalMethodMap,
CommissionTypeMap
CommissionSourceMap
} from '@/config/constants/commission'
defineOptions({ name: 'AgentCommission' })
@@ -605,13 +612,13 @@
getTableData()
})
//
//
const getTableData = async () => {
loading.value = true
try {
const params = {
page: pagination.page,
pageSize: pagination.pageSize,
page_size: pagination.pageSize,
shop_name: searchForm.shop_name || undefined,
shop_code: searchForm.shop_code || undefined
}
@@ -724,7 +731,7 @@
try {
const params = {
page: commissionPagination.page,
pageSize: commissionPagination.pageSize
page_size: commissionPagination.pageSize
}
const res = await CommissionService.getShopCommissionRecords(
currentShop.value.shop_id,
@@ -761,7 +768,7 @@
try {
const params = {
page: withdrawalPagination.page,
pageSize: withdrawalPagination.pageSize
page_size: withdrawalPagination.pageSize
}
const res = await CommissionService.getShopWithdrawalRequests(
currentShop.value.shop_id,
@@ -798,7 +805,7 @@
try {
const params = {
page: mainWalletPagination.page,
pageSize: mainWalletPagination.pageSize
page_size: mainWalletPagination.pageSize
}
const res = await CommissionService.getMainWalletTransactions(
currentShop.value.shop_id,

View File

@@ -199,22 +199,15 @@
import type { FormInstance, FormRules } from 'element-plus'
import type {
MyCommissionSummary,
MyCommissionRecordItem,
ShopCommissionRecordItem,
WithdrawalRequestItem,
CommissionRecordQueryParams,
WithdrawalRequestQueryParams,
SubmitWithdrawalParams,
CommissionType,
CommissionStatus,
WithdrawalStatus
} from '@/types/api/commission'
import { WithdrawalMethod } from '@/types/api/commission'
import {
CommissionStatusMap,
CommissionTypeMap,
WithdrawalStatusMap,
WithdrawalMethodMap
} from '@/config/constants/commission'
import { WithdrawalStatusMap, WithdrawalMethodMap } from '@/config/constants/commission'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
@@ -227,7 +220,7 @@
const userStore = useUserStore()
// 获取当前用户的 shop_id
const currentShopId = computed(() => userStore.userInfo?.shop_id)
const currentShopId = computed(() => userStore.info?.shop_id)
// 如果没有 shop_id显示提示
const hasShopId = computed(() => !!currentShopId.value)
@@ -237,6 +230,7 @@
// 佣金概览
const summary = ref<MyCommissionSummary>({
main_balance: 0,
total_commission: 0,
available_commission: 0,
frozen_commission: 0,
@@ -251,10 +245,13 @@
// 搜索表单初始值
const initialCommissionSearchState = {
commission_type: undefined as CommissionType | undefined,
status: undefined as CommissionStatus | undefined,
commission_source: undefined as string | undefined,
status: undefined as number | undefined,
start_time: '',
end_time: ''
end_time: '',
iccid: '',
virtual_no: '',
order_no: ''
}
// 搜索表单
@@ -263,16 +260,16 @@
// 搜索表单配置
const commissionSearchItems = computed<SearchFormItem[]>(() => [
{
label: '佣金类型',
prop: 'commission_type',
label: '佣金来源',
prop: 'commission_source',
type: 'select',
options: [
{ label: '一次性佣金', value: 'one_time' },
{ label: '长期佣金', value: 'long_term' }
{ label: '成本差价', value: 'cost_diff' },
{ label: '一次性佣金', value: 'one_time' }
],
config: {
clearable: true,
placeholder: '请选择佣金类型'
placeholder: '请选择佣金来源'
}
},
{
@@ -290,6 +287,33 @@
placeholder: '请选择状态'
}
},
{
label: 'ICCID',
prop: 'iccid',
type: 'input',
config: {
clearable: true,
placeholder: '请输入ICCID'
}
},
{
label: '虚拟号',
prop: 'virtual_no',
type: 'input',
config: {
clearable: true,
placeholder: '请输入虚拟号'
}
},
{
label: '订单号',
prop: 'order_no',
type: 'input',
config: {
clearable: true,
placeholder: '请输入订单号'
}
},
{
label: '时间范围',
prop: 'dateRange',
@@ -322,64 +346,95 @@
// 列配置
const commissionColumnOptions = [
{ label: 'ID', prop: 'id' },
{ label: '佣金金额', prop: 'amount' },
{ label: '佣金类型', prop: 'commission_type' },
{ label: '状态', prop: 'status' },
{ label: '订单ID', prop: 'order_id' },
{ label: '佣金来源', prop: 'commission_source' },
{ label: '状态', prop: 'status_name' },
{ label: 'ICCID', prop: 'iccid' },
{ label: '虚拟号', prop: 'virtual_no' },
{ label: '订单号', prop: 'order_no' },
{ label: '卖家店铺', prop: 'seller_shop_name' },
{ label: '入账后余额', prop: 'balance_after' },
{ label: '创建时间', prop: 'created_at' }
]
const commissionList = ref<MyCommissionRecordItem[]>([])
const commissionList = ref<ShopCommissionRecordItem[]>([])
// 佣金来源映射
const CommissionSourceMap = {
cost_diff: { label: '成本差价', type: 'primary' as const },
one_time: { label: '一次性佣金', type: 'success' as const }
}
// 状态映射
const CommissionRecordStatusMap = {
1: { label: '已冻结', type: 'info' as const },
2: { label: '解冻中', type: 'warning' as const },
3: { label: '已发放', type: 'success' as const },
4: { label: '已失效', type: 'danger' as const }
}
// 动态列配置
const { columnChecks: commissionColumnChecks, columns: commissionColumns } = useCheckedColumns(
() => [
{
prop: 'id',
label: 'ID',
width: 80
},
{
prop: 'amount',
label: '佣金金额',
width: 140,
formatter: (row: MyCommissionRecordItem) => formatMoney(row.amount)
width: 120,
formatter: (row: ShopCommissionRecordItem) => formatMoney(row.amount)
},
{
prop: 'commission_type',
label: '佣金类型',
prop: 'commission_source',
label: '佣金来源',
width: 120,
formatter: (row: MyCommissionRecordItem) => {
const config = CommissionTypeMap[row.commission_type as keyof typeof CommissionTypeMap]
formatter: (row: ShopCommissionRecordItem) => {
const config =
CommissionSourceMap[row.commission_source as keyof typeof CommissionSourceMap]
if (!config) return String(row.commission_source ?? '-')
return h(ElTag, { type: config.type }, () => config.label)
}
},
{
prop: 'status',
prop: 'status_name',
label: '状态',
width: 100,
formatter: (row: MyCommissionRecordItem) => {
const config = CommissionStatusMap[row.status as keyof typeof CommissionStatusMap]
return h(ElTag, { type: config.type }, () => config.label)
formatter: (row: ShopCommissionRecordItem) => {
return String(row.status_name ?? '-')
}
},
{
prop: 'order_id',
label: '订单ID',
width: 100,
formatter: (row: MyCommissionRecordItem) => row.order_id || '-'
prop: 'iccid',
label: 'ICCID',
formatter: (row: ShopCommissionRecordItem) => row.iccid || '-'
},
{
prop: 'shop_id',
label: '店铺ID',
width: 100
prop: 'virtual_no',
label: '虚拟号',
width: 130,
formatter: (row: ShopCommissionRecordItem) => row.virtual_no || '-'
},
{
prop: 'order_no',
label: '订单号',
width: 160,
formatter: (row: ShopCommissionRecordItem) => row.order_no || '-'
},
{
prop: 'seller_shop_name',
label: '卖家店铺',
width: 150,
formatter: (row: ShopCommissionRecordItem) => row.seller_shop_name || '-'
},
{
prop: 'balance_after',
label: '入账后余额',
width: 120,
formatter: (row: ShopCommissionRecordItem) => formatMoney(row.balance_after)
},
{
prop: 'created_at',
label: '创建时间',
width: 180,
formatter: (row: MyCommissionRecordItem) => formatDateTime(row.created_at)
formatter: (row: ShopCommissionRecordItem) => formatDateTime(row.created_at)
}
]
)
@@ -393,14 +448,17 @@
commissionLoading.value = true
try {
const params = {
pageSize: commissionPagination.pageSize,
current: commissionPagination.page,
commission_type: commissionSearchForm.commission_type,
const params: CommissionRecordQueryParams = {
page_size: commissionPagination.pageSize,
page: commissionPagination.page,
commission_source: commissionSearchForm.commission_source,
status: commissionSearchForm.status,
start_time: commissionSearchForm.start_time || undefined,
end_time: commissionSearchForm.end_time || undefined
} as CommissionRecordQueryParams
end_time: commissionSearchForm.end_time || undefined,
iccid: commissionSearchForm.iccid || undefined,
virtual_no: commissionSearchForm.virtual_no || undefined,
order_no: commissionSearchForm.order_no || undefined
}
const res = await CommissionService.getShopCommissionRecords(currentShopId.value, params)
if (res.code === 0) {
commissionList.value = res.data.items || []
@@ -512,8 +570,10 @@
{ label: '手续费', prop: 'fee' },
{ label: '实际到账', prop: 'actual_amount' },
{ label: '提现方式', prop: 'withdrawal_method' },
{ label: '状态', prop: 'status' },
{ label: '申请时间', prop: 'created_at' }
{ label: '状态', prop: 'status_name' },
{ label: '拒绝原因', prop: 'reject_reason' },
{ label: '申请时间', prop: 'created_at' },
{ label: '到账时间', prop: 'paid_at' }
]
const withdrawalList = ref<WithdrawalRequestItem[]>([])
@@ -560,19 +620,33 @@
}
},
{
prop: 'status',
prop: 'status_name',
label: '状态',
width: 100,
formatter: (row: WithdrawalRequestItem) => {
const config = WithdrawalStatusMap[row.status as keyof typeof WithdrawalStatusMap]
return h(ElTag, { type: config.type }, () => config.label)
const tagType = config?.type || 'info'
const label = row.status_name || config?.label || String(row.status)
return h(ElTag, { type: tagType }, () => label)
}
},
{
prop: 'reject_reason',
label: '拒绝原因',
minWidth: 120,
formatter: (row: WithdrawalRequestItem) => row.reject_reason || '-'
},
{
prop: 'created_at',
label: '申请时间',
width: 180,
formatter: (row: WithdrawalRequestItem) => formatDateTime(row.created_at)
},
{
prop: 'paid_at',
label: '到账时间',
width: 180,
formatter: (row: WithdrawalRequestItem) => (row.paid_at ? formatDateTime(row.paid_at) : '-')
}
]
)
@@ -586,13 +660,13 @@
withdrawalLoading.value = true
try {
const params = {
pageSize: withdrawalPagination.pageSize,
current: withdrawalPagination.page,
const params: WithdrawalRequestQueryParams = {
page_size: withdrawalPagination.pageSize,
page: withdrawalPagination.page,
status: withdrawalSearchForm.status,
start_time: withdrawalSearchForm.start_time || undefined,
end_time: withdrawalSearchForm.end_time || undefined
} as WithdrawalRequestQueryParams
}
const res = await CommissionService.getShopWithdrawalRequests(currentShopId.value, params)
if (res.code === 0) {
withdrawalList.value = res.data.items || []
@@ -655,7 +729,7 @@
amount: [
{ required: true, message: '请输入提现金额', trigger: 'blur' },
{
validator: (rule, value, callback) => {
validator: (_rule, value, callback) => {
if (value < 100) {
callback(new Error('提现金额不能小于100分1元'))
} else if (value > summary.value.available_commission) {
@@ -678,7 +752,7 @@
],
bank_name: [
{
validator: (rule, value, callback) => {
validator: (_rule, value, callback) => {
if (withdrawalForm.withdrawal_method === WithdrawalMethod.BANK && !value) {
callback(new Error('请选择银行名称'))
} else {
@@ -740,9 +814,9 @@
withdrawalDialogVisible.value = false
// 刷新数据
loadSummary()
await loadSummary()
if (activeTab.value === 'withdrawal') {
getWithdrawalList()
await getWithdrawalList()
}
} catch (error) {
console.error(error)
@@ -765,13 +839,14 @@
// 调用 fund-summary 接口,通过 shop_id 过滤获取自己的数据
const res = await CommissionService.getShopFundSummary({
page: 1,
pageSize: 1
page_size: 1
})
if (res.code === 0 && res.data.items && res.data.items.length > 0) {
// 从结果中找到当前用户的店铺数据
const myShopData = res.data.items.find(item => item.shop_id === currentShopId.value)
const myShopData = res.data.items.find((item) => item.shop_id === currentShopId.value)
if (myShopData) {
summary.value = {
main_balance: myShopData.main_balance || 0,
total_commission: myShopData.total_commission,
available_commission: myShopData.available_commission,
frozen_commission: myShopData.frozen_commission,
@@ -803,9 +878,3 @@
getCommissionList()
})
</script>
<style lang="scss" scoped>
.my-commission-page {
// 样式已移动到分析页的佣金概览组件
}
</style>

View File

@@ -38,11 +38,7 @@
</ArtTable>
<!-- 拒绝提现对话框 -->
<ElDialog
v-model="rejectDialogVisible"
title="拒绝提现申请"
width="500px"
>
<ElDialog v-model="rejectDialogVisible" title="拒绝提现申请" width="500px">
<ElForm ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="100px">
<ElFormItem label="拒绝原因" prop="reject_reason">
<ElInput
@@ -199,12 +195,8 @@
const rejectFormRef = ref<FormInstance>()
const rejectRules = reactive<FormRules>({
reject_reason: [
{ required: true, message: '请输入拒绝原因', trigger: 'blur' }
],
remark: [
{ required: true, message: '请输入备注', trigger: 'blur' }
]
reject_reason: [{ required: true, message: '请输入拒绝原因', trigger: 'blur' }],
remark: [{ required: true, message: '请输入备注', trigger: 'blur' }]
})
const rejectForm = reactive({
@@ -318,7 +310,7 @@
try {
const params = {
page: pagination.page,
pageSize: pagination.pageSize,
page_size: pagination.pageSize,
withdrawal_no: searchForm.withdrawal_no || undefined,
shop_name: searchForm.shop_name || undefined,
status: searchForm.status,

View File

@@ -113,10 +113,12 @@
clearable
size="small"
style="width: 180px"
@input="handleLeftRoleSearch"
/>
</div>
<div class="panel-body">
<ElCheckboxGroup v-model="rolesToAdd" class="role-list">
<ElEmpty v-if="filteredAvailableRoles.length === 0" description="暂无角色" />
<ElCheckboxGroup v-else v-model="rolesToAdd" class="role-list">
<div v-for="role in filteredAvailableRoles" :key="role.ID" class="role-item">
<ElCheckbox :label="role.ID" :disabled="selectedRoles.includes(role.ID)">
<span class="role-info">
@@ -201,7 +203,8 @@
ElFormItem,
ElInput,
ElCheckbox,
ElCheckboxGroup
ElCheckboxGroup,
ElEmpty
} from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
@@ -407,7 +410,7 @@
const entityId = Number(route.params.id)
const params: any = {
page: pagination.page,
pageSize: pagination.pageSize,
page_size: pagination.pageSize,
username: searchForm.username || undefined,
phone: searchForm.phone || undefined,
user_type: isShopType.value ? 3 : 4, // 根据页面类型自动设置: 3:代理账号, 4:企业账号
@@ -553,9 +556,16 @@
}
// 加载所有角色列表
const loadAllRoles = async () => {
const loadAllRoles = async (keyword: string = '') => {
try {
const res = await RoleService.getRoles({ page: 1, pageSize: 100 })
const params: any = {
page: 1,
page_size: 20
}
if (keyword) {
params.role_name = keyword
}
const res = await RoleService.getRoles(params)
if (res.code === 0) {
allRoles.value = res.data.items || []
}
@@ -564,6 +574,17 @@
}
}
// 左侧角色搜索防抖
let leftSearchTimer: ReturnType<typeof setTimeout> | null = null
const handleLeftRoleSearch = (val: string) => {
if (leftSearchTimer) {
clearTimeout(leftSearchTimer)
}
leftSearchTimer = setTimeout(() => {
loadAllRoles(val)
}, 300)
}
// 计算属性:过滤后的可分配角色(根据账号类型过滤)
const filteredAvailableRoles = computed(() => {
let roles = allRoles.value
@@ -580,10 +601,7 @@
roles = roles.filter((role) => role.role_type === 2)
}
// 根据搜索关键词过滤
if (!leftRoleFilter.value) return roles
const keyword = leftRoleFilter.value.toLowerCase()
return roles.filter((role) => role.role_name.toLowerCase().includes(keyword))
return roles
})
// 计算属性:过滤后的已分配角色
@@ -675,7 +693,6 @@
onMounted(() => {
getTableData()
loadAllRoles()
})
</script>

View File

@@ -1,19 +1,9 @@
<template>
<div class="analysis-dashboard">
<!-- 轮询统计 -->
<div class="section-title">轮询统计</div>
<PollingStats />
<!-- 佣金概览 -->
<div class="section-title">佣金概览</div>
<CommissionSummary />
<!-- 提现配置 -->
<div class="section-title">提现配置</div>
<CommissionStats />
<WithdrawalSettings />
<!-- 支付配置 -->
<div class="section-title">支付配置</div>
<ActiveWechatConfig />
<!--<el-row :gutter="20">-->
@@ -52,16 +42,9 @@
</template>
<script setup lang="ts">
import TodaySales from './widget/TodaySales.vue'
import VisitorInsights from './widget/VisitorInsights.vue'
import TotalRevenue from './widget/TotalRevenue.vue'
import CustomerSatisfaction from './widget/CustomerSatisfaction.vue'
import TargetVsReality from './widget/TargetVsReality.vue'
import TopProducts from './widget/TopProducts.vue'
import SalesMappingByCountry from './widget/SalesMappingByCountry.vue'
import VolumeServiceLevel from './widget/VolumeServiceLevel.vue'
import PollingStats from './widget/PollingStats.vue'
import CommissionSummary from './widget/CommissionSummary.vue'
import CommissionStats from './widget/CommissionStats.vue'
import WithdrawalSettings from './widget/WithdrawalSettings.vue'
import ActiveWechatConfig from './widget/ActiveWechatConfig.vue'
@@ -71,15 +54,9 @@
<style lang="scss" scoped>
@use './style';
.section-title {
margin-top: 24px;
margin-bottom: 16px;
font-size: 18px;
font-weight: 600;
color: var(--el-text-color-primary);
&:first-child {
margin-top: 0;
}
.analysis-dashboard {
display: flex;
flex-direction: column;
gap: 16px;
}
</style>

View File

@@ -1,17 +1,18 @@
<template>
<ElCard shadow="never" class="active-wechat-config-widget" v-if="activeConfig">
<ElCard shadow="never" class="active-wechat-config-widget">
<template #header>
<div class="card-header">
<div class="header-left">
<span class="header-title">当前生效支付配置</span>
<ElTag type="success" effect="dark" size="small">生效中</ElTag>
<ElTag v-if="activeConfig" type="success" effect="dark" size="small">生效中</ElTag>
<ElTag v-else type="info" effect="plain" size="small">未配置</ElTag>
</div>
<div class="header-right">
<div v-if="activeConfig" class="header-right">
<span class="creator-info">创建于 {{ formatDateTime(activeConfig.created_at) }}</span>
</div>
</div>
</template>
<div class="config-info">
<div v-if="activeConfig" class="config-info">
<div class="info-card">
<div
class="info-icon"
@@ -61,9 +62,7 @@
</div>
</div>
</div>
</ElCard>
<ElCard shadow="never" v-else class="active-wechat-config-widget empty-state">
<div class="empty-content">
<div v-else class="empty-content">
<i class="el-icon-info" style="font-size: 48px; color: var(--el-text-color-placeholder)"></i>
<p>暂无生效的支付配置</p>
</div>
@@ -203,19 +202,17 @@
}
}
&.empty-state {
.empty-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: var(--el-text-color-secondary);
.empty-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: var(--el-text-color-secondary);
p {
margin-top: 12px;
font-size: 14px;
}
p {
margin-top: 12px;
font-size: 14px;
}
}
}

View File

@@ -0,0 +1,267 @@
<template>
<ElCard shadow="never" class="commission-stats-widget" v-if="stats">
<template #header>
<div class="card-header">
<div class="header-left">
<span class="header-title">佣金资金统计</span>
</div>
<div class="header-right">
<span class="date-range">{{ startTime }} ~ {{ endTime }}</span>
</div>
</div>
</template>
<div class="stats-info">
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
>
<i class="el-icon">💰</i>
</div>
<div class="stats-content">
<div class="stats-label">总收入</div>
<div class="stats-value">{{ formatMoney(stats.total_amount) }}</div>
</div>
</div>
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
>
<i class="el-icon">📊</i>
</div>
<div class="stats-content">
<div class="stats-label">总笔数</div>
<div class="stats-value">{{ stats.total_count }} </div>
</div>
</div>
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
>
<i class="el-icon">📈</i>
</div>
<div class="stats-content">
<div class="stats-label">成本价差收入</div>
<div class="stats-value">{{ formatMoney(stats.cost_diff_amount) }}</div>
</div>
</div>
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
>
<i class="el-icon"></i>
</div>
<div class="stats-content">
<div class="stats-label">一次性佣金收入</div>
<div class="stats-value">{{ formatMoney(stats.one_time_amount) }}</div>
</div>
</div>
</div>
<div class="stats-breakdown">
<div class="breakdown-item">
<span class="breakdown-label">成本价差占比</span>
<span class="breakdown-value">{{ formatPercent(stats.cost_diff_percent) }}</span>
</div>
<div class="breakdown-item">
<span class="breakdown-label">一次性佣金占比</span>
<span class="breakdown-value">{{ formatPercent(stats.one_time_percent) }}</span>
</div>
</div>
</ElCard>
<ElCard shadow="never" v-else class="commission-stats-widget empty-state">
<div class="empty-content">
<i class="el-icon-info" style="font-size: 48px; color: var(--el-text-color-placeholder)"></i>
<p>暂无佣金统计数据</p>
</div>
</ElCard>
</template>
<script setup lang="ts">
import { CommissionService } from '@/api/modules'
import type { CommissionStatsResponse } from '@/types/api/commission'
import { useUserStore } from '@/store/modules/user'
import { formatMoney } from '@/utils/business/format'
defineOptions({ name: 'CommissionStatsWidget' })
const userStore = useUserStore()
const currentShopId = computed(() => userStore.info?.shop_id)
const stats = ref<CommissionStatsResponse | null>(null)
const startTime = ref('')
const endTime = ref('')
const formatPercent = (value: number) => {
return (value / 10).toFixed(1) + '%'
}
const loadCommissionStats = async () => {
if (!currentShopId.value) return
try {
const res = await CommissionService.getShopCommissionStats(currentShopId.value)
if (res.code === 0 && res.data) {
stats.value = res.data
}
} catch (error) {
console.error('获取佣金资金统计失败:', error)
}
}
onMounted(() => {
loadCommissionStats()
})
</script>
<style lang="scss" scoped>
.commission-stats-widget {
:deep(.el-card__header) {
padding: 18px 20px;
background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter);
}
:deep(.el-card__body) {
padding: 20px;
}
.card-header {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
justify-content: space-between;
.header-left {
display: flex;
gap: 10px;
align-items: center;
.header-title {
font-size: 15px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.header-right {
.date-range {
font-size: 13px;
color: var(--el-text-color-secondary);
}
}
}
.stats-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 20px;
.stats-card {
display: flex;
gap: 12px;
align-items: center;
padding: 16px;
background: var(--el-fill-color-blank);
border-radius: 8px;
.stats-icon {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-size: 20px;
border-radius: 8px;
}
.stats-content {
flex: 1;
min-width: 0;
.stats-label {
margin-bottom: 4px;
font-size: 13px;
color: var(--el-text-color-secondary);
}
.stats-value {
overflow: hidden;
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.stats-breakdown {
display: flex;
gap: 24px;
padding: 16px;
background: var(--el-fill-color-light);
border-radius: 8px;
.breakdown-item {
display: flex;
gap: 8px;
align-items: center;
.breakdown-label {
font-size: 13px;
color: var(--el-text-color-secondary);
}
.breakdown-value {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
}
&.empty-state {
.empty-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: var(--el-text-color-secondary);
p {
margin-top: 12px;
font-size: 14px;
}
}
}
}
@media (width <= 768px) {
.commission-stats-widget {
.card-header {
.header-left,
.header-right {
width: 100%;
}
}
.stats-info {
grid-template-columns: 1fr;
gap: 12px;
}
.stats-breakdown {
flex-direction: column;
gap: 12px;
}
}
}
</style>

View File

@@ -1,101 +1,116 @@
<template>
<!-- 错误状态 -->
<ElCard shadow="never" v-if="errorMsg" class="commission-summary-widget error-state">
<ElCard shadow="never" class="commission-summary-widget" v-if="!errorMsg">
<template #header>
<div class="card-header">
<div class="header-left">
<span class="header-title">我的佣金概览</span>
</div>
</div>
</template>
<div class="stats-info">
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%)"
>
<i class="iconfont-sys">&#xe722;</i>
</div>
<div class="stats-content">
<div class="stats-label">预充值余额</div>
<div class="stats-value">{{ formatMoney(summary.main_balance) }}</div>
</div>
</div>
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
>
<i class="iconfont-sys">&#xe71d;</i>
</div>
<div class="stats-content">
<div class="stats-label">总佣金</div>
<div class="stats-value">{{ formatMoney(summary.total_commission) }}</div>
</div>
</div>
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
>
<i class="iconfont-sys">&#xe71e;</i>
</div>
<div class="stats-content">
<div class="stats-label">可提现佣金</div>
<div class="stats-value">{{ formatMoney(summary.available_commission) }}</div>
</div>
</div>
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
>
<i class="iconfont-sys">&#xe720;</i>
</div>
<div class="stats-content">
<div class="stats-label">冻结佣金</div>
<div class="stats-value">{{ formatMoney(summary.frozen_commission) }}</div>
</div>
</div>
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%)"
>
<i class="iconfont-sys">&#xe71f;</i>
</div>
<div class="stats-content">
<div class="stats-label">提现中佣金</div>
<div class="stats-value">{{ formatMoney(summary.withdrawing_commission) }}</div>
</div>
</div>
<div class="stats-card">
<div
class="stats-icon"
style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
>
<i class="iconfont-sys">&#xe721;</i>
</div>
<div class="stats-content">
<div class="stats-label">已提现佣金</div>
<div class="stats-value">{{ formatMoney(summary.withdrawn_commission) }}</div>
</div>
</div>
</div>
<div class="stats-breakdown" v-if="withdrawalSetting">
<div class="breakdown-title">当前提现配置</div>
<div class="breakdown-content">
<div class="breakdown-item">
<span class="breakdown-label">最低提现金额</span>
<span class="breakdown-value">{{
formatMoney(withdrawalSetting.min_withdrawal_amount)
}}</span>
</div>
<div class="breakdown-item">
<span class="breakdown-label">手续费率</span>
<span class="breakdown-value">{{ (withdrawalSetting.fee_rate / 100).toFixed(2) }}%</span>
</div>
<div class="breakdown-item">
<span class="breakdown-label">每日提现次数</span>
<span class="breakdown-value">{{ withdrawalSetting.daily_withdrawal_limit }} </span>
</div>
<div class="breakdown-item">
<span class="breakdown-label">到账天数</span>
<span class="breakdown-value">{{ withdrawalSetting.arrival_days }} </span>
</div>
</div>
</div>
</ElCard>
<ElCard shadow="never" v-else class="commission-summary-widget error-state">
<div class="error-content">
<i class="el-icon-warning" style="font-size: 48px; color: var(--el-color-warning)"></i>
<p>{{ errorMsg }}</p>
</div>
</ElCard>
<!-- 正常显示 -->
<div v-else class="commission-summary-widget">
<div class="stats-container">
<ElCard shadow="hover" class="stat-card-wrapper">
<div class="stat-card">
<div
class="stat-icon"
style="background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%)"
>
<i class="iconfont-sys">&#xe722;</i>
</div>
<div class="stat-content">
<div class="stat-label">预充值余额</div>
<div class="stat-value">{{ formatMoney(summary.main_balance) }}</div>
</div>
</div>
</ElCard>
<ElCard shadow="hover" class="stat-card-wrapper">
<div class="stat-card">
<div
class="stat-icon"
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
>
<i class="iconfont-sys">&#xe71d;</i>
</div>
<div class="stat-content">
<div class="stat-label">总佣金</div>
<div class="stat-value">{{ formatMoney(summary.total_commission) }}</div>
</div>
</div>
</ElCard>
<ElCard shadow="hover" class="stat-card-wrapper">
<div class="stat-card">
<div
class="stat-icon"
style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
>
<i class="iconfont-sys">&#xe71e;</i>
</div>
<div class="stat-content">
<div class="stat-label">可提现佣金</div>
<div class="stat-value">{{ formatMoney(summary.available_commission) }}</div>
</div>
</div>
</ElCard>
<ElCard shadow="hover" class="stat-card-wrapper">
<div class="stat-card">
<div
class="stat-icon"
style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
>
<i class="iconfont-sys">&#xe720;</i>
</div>
<div class="stat-content">
<div class="stat-label">冻结佣金</div>
<div class="stat-value">{{ formatMoney(summary.frozen_commission) }}</div>
</div>
</div>
</ElCard>
<ElCard shadow="hover" class="stat-card-wrapper">
<div class="stat-card">
<div
class="stat-icon"
style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%)"
>
<i class="iconfont-sys">&#xe71f;</i>
</div>
<div class="stat-content">
<div class="stat-label">提现中佣金</div>
<div class="stat-value">{{ formatMoney(summary.withdrawing_commission) }}</div>
</div>
</div>
</ElCard>
<ElCard shadow="hover" class="stat-card-wrapper">
<div class="stat-card">
<div
class="stat-icon"
style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
>
<i class="iconfont-sys">&#xe721;</i>
</div>
<div class="stat-content">
<div class="stat-label">已提现佣金</div>
<div class="stat-value">{{ formatMoney(summary.withdrawn_commission) }}</div>
</div>
</div>
</ElCard>
</div>
</div>
</template>
<script setup lang="ts">
@@ -108,13 +123,10 @@
const userStore = useUserStore()
// 获取当前用户的 shop_id
const currentShopId = computed(() => userStore.userInfo?.shop_id)
const currentShopId = computed(() => userStore.info?.shop_id)
// 错误信息
const errorMsg = ref<string>('')
// 佣金概览
const summary = ref<MyCommissionSummary>({
main_balance: 0,
total_commission: 0,
@@ -124,7 +136,13 @@
withdrawn_commission: 0
})
// 加载佣金概览
const withdrawalSetting = ref<{
min_withdrawal_amount: number
fee_rate: number
daily_withdrawal_limit: number
arrival_days: number
} | null>(null)
const loadSummary = async () => {
if (!currentShopId.value) {
errorMsg.value = '当前账号未关联店铺'
@@ -132,14 +150,12 @@
}
try {
// 调用 fund-summary 接口,通过 shop_id 过滤获取自己的数据
const res = await CommissionService.getShopFundSummary({
page: 1,
pageSize: 1
page_size: 1
})
if (res.code === 0 && res.data.items && res.data.items.length > 0) {
// 从结果中找到当前用户的店铺数据
const myShopData = res.data.items.find(item => item.shop_id === currentShopId.value)
const myShopData = res.data.items.find((item) => item.shop_id === currentShopId.value)
if (myShopData) {
summary.value = {
main_balance: myShopData.main_balance || 0,
@@ -158,7 +174,6 @@
}
} catch (error: any) {
console.error('获取佣金概览失败:', error)
// 尝试从错误响应中提取 msg
if (error?.response?.data?.msg) {
errorMsg.value = error.response.data.msg
} else {
@@ -167,13 +182,144 @@
}
}
const loadWithdrawalSetting = async () => {
try {
const res = await CommissionService.getCurrentWithdrawalSetting()
if (res.code === 0 && res.data) {
withdrawalSetting.value = {
min_withdrawal_amount: res.data.min_withdrawal_amount,
fee_rate: res.data.fee_rate,
daily_withdrawal_limit: res.data.daily_withdrawal_limit,
arrival_days: res.data.arrival_days
}
}
} catch (error) {
console.error('获取提现设置失败:', error)
}
}
onMounted(() => {
loadSummary()
loadWithdrawalSetting()
})
</script>
<style lang="scss" scoped>
.commission-summary-widget {
:deep(.el-card__header) {
padding: 18px 20px;
background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter);
}
:deep(.el-card__body) {
padding: 20px;
}
.card-header {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
justify-content: space-between;
.header-left {
display: flex;
gap: 10px;
align-items: center;
.header-title {
font-size: 15px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
}
.stats-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 20px;
.stats-card {
display: flex;
gap: 12px;
align-items: center;
padding: 16px;
background: var(--el-fill-color-blank);
border-radius: 8px;
.stats-icon {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-size: 20px;
color: white;
border-radius: 8px;
}
.stats-content {
flex: 1;
min-width: 0;
.stats-label {
margin-bottom: 4px;
font-size: 13px;
color: var(--el-text-color-secondary);
}
.stats-value {
overflow: hidden;
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.stats-breakdown {
padding: 16px;
background: var(--el-fill-color-light);
border-radius: 8px;
.breakdown-title {
margin-bottom: 12px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.breakdown-content {
display: flex;
flex-wrap: wrap;
gap: 24px;
.breakdown-item {
display: flex;
gap: 8px;
align-items: center;
.breakdown-label {
font-size: 13px;
color: var(--el-text-color-secondary);
}
.breakdown-value {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
}
}
&.error-state {
.error-content {
display: flex;
@@ -190,108 +336,20 @@
}
}
}
.stats-container {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.stat-card-wrapper {
flex: 1;
min-width: 180px;
:deep(.el-card__body) {
padding: 16px;
}
}
.stat-card {
display: flex;
gap: 12px;
align-items: center;
.stat-icon {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-size: 20px;
color: white;
border-radius: 8px;
}
.stat-content {
flex: 1;
min-width: 0;
.stat-label {
margin-bottom: 4px;
font-size: 12px;
color: var(--el-text-color-secondary);
white-space: nowrap;
}
.stat-value {
overflow: hidden;
font-size: 18px;
font-weight: 600;
color: var(--el-text-color-primary);
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
@media (width <= 1400px) {
.commission-summary-widget {
.stats-container {
gap: 12px;
}
.stat-card-wrapper {
min-width: 160px;
}
.stat-card {
.stat-content {
.stat-value {
font-size: 16px;
}
}
}
}
}
@media (width <= 1200px) {
.commission-summary-widget {
.stat-card-wrapper {
flex: 1 1 calc(33.333% - 12px);
min-width: 200px;
}
}
}
@media (width <= 768px) {
.commission-summary-widget {
.stats-container {
.stats-info {
grid-template-columns: 1fr;
gap: 12px;
}
.stat-card-wrapper {
flex: 1 1 calc(50% - 8px);
min-width: 150px;
}
}
}
@media (width <= 480px) {
.commission-summary-widget {
.stat-card-wrapper {
flex: 1 1 100%;
.stats-breakdown {
.breakdown-content {
flex-direction: column;
gap: 12px;
}
}
}
}

View File

@@ -1,110 +1,136 @@
<template>
<ElCard shadow="never" class="withdrawal-settings-widget" v-if="currentSetting">
<ElCard shadow="never" class="daily-commission-stats-widget" v-if="statsList.length > 0">
<template #header>
<div class="card-header">
<div class="header-left">
<span class="header-title">当前生效配置</span>
<ElTag type="success" effect="dark" size="small">生效中</ElTag>
<span class="header-title">每日佣金统计</span>
</div>
<div class="header-right">
<span class="creator-info"
>{{ currentSetting.creator_name || '-' }} 创建于
{{ formatDateTime(currentSetting.created_at) }}</span
<span class="date-range"
>{{ formatDateRange(startDate) }} ~ {{ formatDateRange(endDate) }}</span
>
</div>
</div>
</template>
<div class="setting-info">
<div class="info-card">
<div class="stats-info">
<div class="stats-card">
<div
class="info-icon"
class="stats-icon"
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
>
<i class="el-icon">💰</i>
</div>
<div class="info-content">
<div class="info-label">最低提现金额</div>
<div class="info-value">{{ formatMoney(currentSetting.min_withdrawal_amount) }}</div>
<div class="stats-content">
<div class="stats-label">总收入</div>
<div class="stats-value">{{ formatMoney(totalAmount) }}</div>
</div>
</div>
<div class="info-card">
<div class="stats-card">
<div
class="info-icon"
style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
class="stats-icon"
style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
>
<i class="el-icon">📊</i>
</div>
<div class="info-content">
<div class="info-label">手续费率</div>
<div class="info-value">{{ formatFeeRate(currentSetting.fee_rate) }}</div>
<div class="stats-content">
<div class="stats-label">总笔数</div>
<div class="stats-value">{{ totalCount }} </div>
</div>
</div>
<div class="info-card">
<div
class="info-icon"
style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
>
<i class="el-icon">🔢</i>
</div>
<div class="info-content">
<div class="info-label">每日提现次数</div>
<div class="info-value">{{ currentSetting.daily_withdrawal_limit }} </div>
</div>
</div>
<div class="info-card">
<div
class="info-icon"
style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
>
<i class="el-icon"></i>
</div>
<div class="info-content">
<div class="info-label">到账天数</div>
<div class="info-value">{{
currentSetting.arrival_days === 0 ? '实时到账' : `${currentSetting.arrival_days}`
}}</div>
</div>
<div class="stats-chart">
<div class="chart-bar" v-for="item in statsList" :key="item.date">
<div class="bar-label">{{ formatDateShort(item.date) }}</div>
<div class="bar-value" :style="{ height: getBarHeight(item.total_amount) + 'px' }">
<ElTooltip :content="formatMoney(item.total_amount)" placement="top">
<div class="bar-fill"></div>
</ElTooltip>
</div>
</div>
</div>
</ElCard>
<ElCard shadow="never" v-else class="withdrawal-settings-widget empty-state">
<ElCard shadow="never" v-else class="daily-commission-stats-widget empty-state">
<div class="empty-content">
<i class="el-icon-info" style="font-size: 48px; color: var(--el-text-color-placeholder)"></i>
<p>暂无提现配置</p>
<p>暂无佣金统计数据</p>
</div>
</ElCard>
</template>
<script setup lang="ts">
import { CommissionService } from '@/api/modules'
import { ElTag } from 'element-plus'
import type { WithdrawalSettingItem } from '@/types/api/commission'
import { formatDateTime, formatMoney, formatFeeRate } from '@/utils/business/format'
import { ElTooltip } from 'element-plus'
import type { DailyCommissionStatsItem } from '@/types/api/commission'
import { useUserStore } from '@/store/modules/user'
import { formatMoney } from '@/utils/business/format'
defineOptions({ name: 'WithdrawalSettingsWidget' })
defineOptions({ name: 'DailyCommissionStatsWidget' })
// 当前生效的配置
const currentSetting = ref<WithdrawalSettingItem | null>(null)
const userStore = useUserStore()
const currentShopId = computed(() => userStore.info?.shop_id)
// 加载当前生效配置
const loadCurrentSetting = async () => {
const statsList = ref<DailyCommissionStatsItem[]>([])
const loading = ref(false)
const startDate = ref('')
const endDate = ref('')
const totalAmount = computed(() =>
statsList.value.reduce((sum, item) => sum + item.total_amount, 0)
)
const totalCount = computed(() =>
statsList.value.reduce((sum, item) => sum + item.total_count, 0)
)
const maxAmount = computed(() => {
if (statsList.value.length === 0) return 0
return Math.max(...statsList.value.map((item) => item.total_amount))
})
const getBarHeight = (amount: number) => {
if (maxAmount.value === 0) return 0
return Math.max(4, (amount / maxAmount.value) * 100)
}
const formatDateShort = (dateStr: string) => {
if (!dateStr) return '-'
return dateStr.slice(5) // MM-DD
}
const formatDateRange = (dateStr: string) => {
if (!dateStr) return '-'
return dateStr // API already returns YYYY-MM-DD format
}
const loadDailyStats = async () => {
if (!currentShopId.value) return
loading.value = true
try {
const res = await CommissionService.getCurrentWithdrawalSetting()
const res = await CommissionService.getShopDailyCommissionStats(currentShopId.value, {
days: 30
})
if (res.code === 0 && res.data) {
currentSetting.value = res.data
statsList.value = res.data
if (res.data.length > 0) {
startDate.value = res.data[res.data.length - 1]?.date || ''
endDate.value = res.data[0]?.date || ''
}
}
} catch (error) {
console.error('获取当前配置失败:', error)
console.error('获取每日佣金统计失败:', error)
} finally {
loading.value = false
}
}
onMounted(() => {
loadCurrentSetting()
loadDailyStats()
})
</script>
<style lang="scss" scoped>
.withdrawal-settings-widget {
.daily-commission-stats-widget {
:deep(.el-card__header) {
padding: 18px 20px;
background: var(--el-fill-color-light);
@@ -135,19 +161,20 @@
}
.header-right {
.creator-info {
.date-range {
font-size: 13px;
color: var(--el-text-color-secondary);
}
}
}
.setting-info {
.stats-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
margin-bottom: 20px;
.info-card {
.stats-card {
display: flex;
gap: 12px;
align-items: center;
@@ -155,7 +182,7 @@
background: var(--el-fill-color-blank);
border-radius: 8px;
.info-icon {
.stats-icon {
display: flex;
flex-shrink: 0;
align-items: center;
@@ -166,17 +193,17 @@
border-radius: 8px;
}
.info-content {
.stats-content {
flex: 1;
min-width: 0;
.info-label {
.stats-label {
margin-bottom: 4px;
font-size: 13px;
color: var(--el-text-color-secondary);
}
.info-value {
.stats-value {
overflow: hidden;
font-size: 16px;
font-weight: 600;
@@ -188,6 +215,46 @@
}
}
.stats-chart {
display: flex;
align-items: flex-end;
gap: 8px;
height: 120px;
padding-top: 10px;
.chart-bar {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
.bar-label {
font-size: 11px;
color: var(--el-text-color-secondary);
}
.bar-value {
width: 100%;
display: flex;
justify-content: center;
.bar-fill {
width: 80%;
max-width: 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 4px 4px 0 0;
cursor: pointer;
transition: opacity 0.2s;
&:hover {
opacity: 0.8;
}
}
}
}
}
&.empty-state {
.empty-content {
display: flex;
@@ -206,7 +273,7 @@
}
@media (width <= 768px) {
.withdrawal-settings-widget {
.daily-commission-stats-widget {
.card-header {
.header-left,
.header-right {
@@ -214,10 +281,15 @@
}
}
.setting-info {
.stats-info {
grid-template-columns: 1fr;
gap: 12px;
}
.stats-chart {
overflow-x: auto;
padding-bottom: 10px;
}
}
}
</style>

View File

@@ -23,6 +23,17 @@
<h3 class="section-title">订单项列表</h3>
<ElTable :data="detailData.items" border style="margin-top: 10px">
<ElTableColumn prop="package_name" label="套餐名称" min-width="150" />
<ElTableColumn prop="package_type" label="套餐类型" width="120">
<template #default="{ row }">
{{
row.package_type === 'formal'
? '正式套餐'
: row.package_type === 'addon'
? '加油包'
: '-'
}}
</template>
</ElTableColumn>
<ElTableColumn prop="quantity" label="数量" width="100" />
<ElTableColumn prop="unit_price" label="单价" width="120">
<template #default="{ row }">
@@ -145,6 +156,16 @@
label: '买家类型',
formatter: (_, data) => (data.buyer_type ? getBuyerTypeText(data.buyer_type) : '-')
},
{
label: '买家手机号',
prop: 'buyer_phone',
formatter: (value) => value || '-'
},
{
label: '买家昵称',
prop: 'buyer_nickname',
formatter: (value) => value || '-'
},
{
label: '买家ID',
prop: 'buyer_id',

View File

@@ -234,6 +234,12 @@
<ElDescriptionsItem :label="'买家类型'">
{{ getBuyerTypeText(currentOrder.buyer_type) }}
</ElDescriptionsItem>
<ElDescriptionsItem :label="'买家手机号'">
{{ currentOrder.buyer_phone || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem :label="'买家昵称'">
{{ currentOrder.buyer_nickname || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem :label="'佣金状态'">
{{ getCommissionStatusText(currentOrder.commission_status) }}
</ElDescriptionsItem>
@@ -256,6 +262,17 @@
<h4>{{ '订单项' }}</h4>
<ElTable :data="currentOrder.items" border style="margin-top: 10px">
<ElTableColumn prop="package_name" :label="'套餐名称'" min-width="150" />
<ElTableColumn prop="package_type" :label="'套餐类型'" width="120">
<template #default="{ row }">
{{
row.package_type === 'formal'
? '正式套餐'
: row.package_type === 'addon'
? '加油包'
: '-'
}}
</template>
</ElTableColumn>
<ElTableColumn prop="quantity" :label="'数量'" width="100" />
<ElTableColumn prop="unit_price" :label="'单价'" width="120">
<template #default="{ row }">
@@ -330,6 +347,7 @@
// 搜索表单初始值
const initialSearchState: OrderQueryParams = {
order_no: '',
buyer_phone: '',
payment_status: undefined,
order_type: undefined,
purchase_role: undefined,
@@ -353,6 +371,16 @@
// 搜索表单配置
const searchFormItems: SearchFormItem[] = [
{
label: '买家手机号',
prop: 'buyer_phone',
type: 'input',
placeholder: '请输入买家手机号',
config: {
maxlength: 20,
clearable: true
}
},
{
label: '支付状态',
prop: 'payment_status',
@@ -419,6 +447,8 @@
// 列配置
const columnOptions = [
{ label: '订单编号', prop: 'order_no' },
{ label: '买家手机号', prop: 'buyer_phone' },
{ label: '买家昵称', prop: 'buyer_nickname' },
{ label: '订单类型', prop: 'order_type' },
{ label: '买家类型', prop: 'buyer_type' },
{ label: '订单渠道', prop: 'purchase_role' },
@@ -707,6 +737,18 @@
)
}
},
{
prop: 'buyer_phone',
label: '买家手机号',
width: 130,
formatter: (row: Order) => row.buyer_phone || '-'
},
{
prop: 'buyer_nickname',
label: '买家昵称',
width: 130,
formatter: (row: Order) => row.buyer_nickname || '-'
},
{
prop: 'order_type',
label: '订单类型',
@@ -854,6 +896,7 @@
page: pagination.page,
page_size: pagination.page_size,
order_no: searchForm.order_no || undefined,
buyer_phone: searchForm.buyer_phone || undefined,
payment_status: searchForm.payment_status,
order_type: searchForm.order_type,
purchase_role: searchForm.purchase_role,

View File

@@ -977,38 +977,47 @@
}
// 显示新增/编辑对话框
const showDialog = (type: string, row?: PackageResponse) => {
const showDialog = async (type: string, row?: PackageResponse) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
form.id = row.id
form.package_code = row.package_code
form.package_name = row.package_name
form.series_id = row.series_id
form.package_type = row.package_type
form.calendar_type = row.calendar_type || undefined
form.duration_days = row.duration_days || undefined
form.duration_months = row.duration_months
form.data_reset_cycle = row.data_reset_cycle || undefined
form.enable_virtual_data = row.enable_virtual_data || false
form.expiry_base = row.expiry_base || 'from_activation'
form.real_data_mb = row.real_data_mb || 0
form.virtual_data_mb = row.virtual_data_mb || 0
// MB 转换为 GB 显示
realDataGb.value = form.real_data_mb ? Number((form.real_data_mb / 1024).toFixed(2)) : 0
// 从虚流量额度反推比例:比例 = (1 - 虚流量/真流量) × 100%
if (form.real_data_mb > 0 && form.virtual_data_mb > 0) {
const ratio = (1 - form.virtual_data_mb / form.real_data_mb) * 100
virtualRatioPercent.value = Number(ratio.toFixed(2))
} else {
virtualRatioPercent.value = null
try {
const res = await PackageManageService.getPackageDetail(row.id)
if (res.code === 0 && res.data) {
const data = res.data
form.id = data.id
form.package_code = data.package_code
form.package_name = data.package_name
form.series_id = data.series_id
form.package_type = data.package_type
form.calendar_type = data.calendar_type || undefined
form.duration_days = data.duration_days
form.duration_months = data.duration_months
form.data_reset_cycle = data.data_reset_cycle || undefined
form.enable_virtual_data = data.enable_virtual_data || false
form.expiry_base = data.expiry_base || 'from_activation'
form.real_data_mb = data.real_data_mb || 0
form.virtual_data_mb = data.virtual_data_mb || 0
// MB 转换为 GB 显示
realDataGb.value = form.real_data_mb ? Number((form.real_data_mb / 1024).toFixed(2)) : 0
// 从虚流量额度反推比例:比例 = (1 - 虚流量/真流量) × 100%
if (form.real_data_mb > 0 && form.virtual_data_mb > 0) {
const ratio = (1 - form.virtual_data_mb / form.real_data_mb) * 100
virtualRatioPercent.value = Number(ratio.toFixed(2))
} else {
virtualRatioPercent.value = null
}
form.cost_price = (data.cost_price ?? 0) / 100 // 分转换为元显示
form.suggested_retail_price = data.suggested_retail_price
? data.suggested_retail_price / 100
: undefined
form.description = data.description || ''
}
} catch (error) {
console.error('获取套餐详情失败:', error)
ElMessage.error('获取套餐详情失败')
}
form.cost_price = (row.cost_price ?? 0) / 100 // 分转换为元显示
form.suggested_retail_price = row.suggested_retail_price
? row.suggested_retail_price / 100
: undefined
form.description = row.description || ''
} else {
form.id = 0
form.package_code = ''
@@ -1126,7 +1135,7 @@
if (form.data_reset_cycle) {
data.data_reset_cycle = form.data_reset_cycle
}
if (form.duration_days !== undefined && form.duration_days !== null) {
if (form.duration_days !== undefined) {
data.duration_days = form.duration_days
}
if (form.enable_virtual_data !== undefined) {

View File

@@ -286,20 +286,14 @@
</div>
</ElFormItem>
<!-- 强充配置 -->
<div class="form-section-title">
<span class="title-text">强充配置</span>
</div>
<!-- 强充配置 - 仅累计充值时显示 -->
<template
v-if="form.one_time_commission_config.trigger_type === 'accumulated_recharge'"
>
<div class="form-section-title">
<span class="title-text">强充配置</span>
</div>
<ElFormItem label="启用强充">
<ElSwitch
v-model="form.one_time_commission_config.enable_force_recharge"
active-text="启用"
inactive-text="不启用"
/>
</ElFormItem>
<template v-if="form.one_time_commission_config.enable_force_recharge">
<ElFormItem label="强充金额">
<ElInputNumber
v-model="form.one_time_commission_config.force_amount"
@@ -967,8 +961,8 @@
commissionConfig.tiers = convertedTiers.length > 0 ? convertedTiers : null
}
// 添加强充配置:元 -> 分
if (form.one_time_commission_config.enable_force_recharge) {
// 添加强充配置:元 -> 分(仅累计充值时需要)
if (form.one_time_commission_config.trigger_type === 'accumulated_recharge') {
commissionConfig.enable_force_recharge = true
commissionConfig.force_amount =
form.one_time_commission_config.force_amount != null

View File

@@ -90,7 +90,7 @@
import { useAuth } from '@/composables/useAuth'
import { formatDateTime, formatMoney, formatFeeRate } from '@/utils/business/format'
defineOptions({ name: 'CommissionWithdrawalSettings' })
defineOptions({ name: 'WithdrawalSettings' })
const { hasAuth } = useAuth()

View File

@@ -728,7 +728,7 @@
loading.value = true
try {
const params = {
page: pagination.currentPage,
page: pagination.page,
page_size: pagination.pageSize,
shop_name: searchForm.shop_name || undefined,
shop_code: searchForm.shop_code || undefined,

View File

@@ -939,7 +939,7 @@
try {
const params = {
page: pagination.page,
pageSize: pagination.pageSize,
page_size: pagination.pageSize,
role_name: searchForm.role_name || undefined,
role_type: searchForm.role_type,
status: searchForm.status