Compare commits
4 Commits
0668eea99b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f2194e08d0 | |||
| df6f91e512 | |||
| b6542ddbc3 | |||
| 3211cc32ce |
@@ -1,6 +0,0 @@
|
|||||||
echo Start running commit-msg hook...
|
|
||||||
|
|
||||||
# Check whether the git commit information is standardized
|
|
||||||
pnpm exec commitlint --edit "$1"
|
|
||||||
|
|
||||||
echo Run commit-msg hook done.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# 每次 git pull 之后, 安装依赖
|
|
||||||
|
|
||||||
pnpm install
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# update `.vscode/vben-admin.code-workspace` file
|
|
||||||
pnpm vsh code-workspace --auto-commit
|
|
||||||
|
|
||||||
# Format and submit code according to lintstagedrc.js configuration
|
|
||||||
pnpm exec lint-staged
|
|
||||||
|
|
||||||
echo Run pre-commit hook done.
|
|
||||||
@@ -4,19 +4,19 @@ export namespace AgentApi {
|
|||||||
export interface AgentListItem {
|
export interface AgentListItem {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
|
agent_code: number;
|
||||||
|
level: number; // 1=普通,2=黄金,3=钻石
|
||||||
level_name: string;
|
level_name: string;
|
||||||
region: string;
|
region: string;
|
||||||
mobile: string;
|
mobile: string;
|
||||||
membership_expiry_time: string;
|
wechat_id?: string;
|
||||||
|
team_leader_id?: number;
|
||||||
balance: number;
|
balance: number;
|
||||||
total_earnings: number;
|
total_earnings: number;
|
||||||
frozen_balance: number;
|
frozen_balance: number;
|
||||||
withdrawn_amount: number;
|
withdrawn_amount: number;
|
||||||
|
is_real_name: boolean;
|
||||||
create_time: string;
|
create_time: string;
|
||||||
is_real_name_verified: boolean;
|
|
||||||
real_name: string;
|
|
||||||
id_card: string;
|
|
||||||
real_name_status: 'approved' | 'pending' | 'rejected';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentList {
|
export interface AgentList {
|
||||||
@@ -29,7 +29,8 @@ export namespace AgentApi {
|
|||||||
pageSize: number;
|
pageSize: number;
|
||||||
mobile?: string;
|
mobile?: string;
|
||||||
region?: string;
|
region?: string;
|
||||||
parent_agent_id?: number;
|
level?: number;
|
||||||
|
team_leader_id?: number;
|
||||||
id?: number;
|
id?: number;
|
||||||
create_time_start?: string;
|
create_time_start?: string;
|
||||||
create_time_end?: string;
|
create_time_end?: string;
|
||||||
@@ -39,8 +40,10 @@ export namespace AgentApi {
|
|||||||
|
|
||||||
export interface AgentLinkListItem {
|
export interface AgentLinkListItem {
|
||||||
agent_id: number;
|
agent_id: number;
|
||||||
|
product_id: number;
|
||||||
product_name: string;
|
product_name: string;
|
||||||
price: number;
|
set_price: number;
|
||||||
|
actual_base_price: number;
|
||||||
link_identifier: string;
|
link_identifier: string;
|
||||||
create_time: string;
|
create_time: string;
|
||||||
}
|
}
|
||||||
@@ -54,6 +57,7 @@ export namespace AgentApi {
|
|||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
agent_id?: number;
|
agent_id?: number;
|
||||||
|
product_id?: number;
|
||||||
product_name?: string;
|
product_name?: string;
|
||||||
link_identifier?: string;
|
link_identifier?: string;
|
||||||
}
|
}
|
||||||
@@ -82,27 +86,28 @@ export namespace AgentApi {
|
|||||||
status?: number;
|
status?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理奖励相关接口
|
// 代理返佣相关接口
|
||||||
export interface AgentRewardListItem {
|
export interface AgentRebateListItem {
|
||||||
id: number;
|
id: number;
|
||||||
agent_id: number;
|
agent_id: number;
|
||||||
relation_agent_id: number;
|
source_agent_id: number;
|
||||||
|
order_id: number;
|
||||||
|
rebate_type: number; // 1=直接上级返佣,2=钻石上级返佣,3=黄金上级返佣
|
||||||
amount: number;
|
amount: number;
|
||||||
type: string;
|
|
||||||
create_time: string;
|
create_time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentRewardList {
|
export interface AgentRebateList {
|
||||||
total: number;
|
total: number;
|
||||||
items: AgentRewardListItem[];
|
items: AgentRebateListItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetAgentRewardListParams {
|
export interface GetAgentRebateListParams {
|
||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
agent_id?: number;
|
agent_id?: number;
|
||||||
relation_agent_id?: number;
|
source_agent_id?: number;
|
||||||
type?: string;
|
rebate_type?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理提现相关接口
|
// 代理提现相关接口
|
||||||
@@ -111,8 +116,11 @@ export namespace AgentApi {
|
|||||||
agent_id: number;
|
agent_id: number;
|
||||||
withdraw_no: string;
|
withdraw_no: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
status: number;
|
tax_amount: number;
|
||||||
|
actual_amount: number;
|
||||||
|
status: number; // 1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败
|
||||||
payee_account: string;
|
payee_account: string;
|
||||||
|
payee_name: string;
|
||||||
remark: string;
|
remark: string;
|
||||||
create_time: string;
|
create_time: string;
|
||||||
}
|
}
|
||||||
@@ -130,66 +138,22 @@ export namespace AgentApi {
|
|||||||
withdraw_no?: string;
|
withdraw_no?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理上级抽佣相关接口
|
export interface AuditWithdrawalParams {
|
||||||
export interface AgentCommissionDeductionListItem {
|
withdrawal_id: number;
|
||||||
id: number;
|
status: number; // 2=通过,3=拒绝
|
||||||
agent_id: number;
|
remark: string;
|
||||||
deducted_agent_id: number;
|
|
||||||
amount: number;
|
|
||||||
product_name: string;
|
|
||||||
type: 'cost' | 'pricing';
|
|
||||||
status: number;
|
|
||||||
create_time: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AgentCommissionDeductionList {
|
|
||||||
total: number;
|
|
||||||
items: AgentCommissionDeductionListItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetAgentCommissionDeductionListParams {
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
agent_id?: number;
|
|
||||||
product_name?: string;
|
|
||||||
type?: 'cost' | 'pricing';
|
|
||||||
status?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 平台抽佣列表项
|
|
||||||
export interface AgentPlatformDeductionListItem {
|
|
||||||
id: number;
|
|
||||||
agent_id: number;
|
|
||||||
amount: number;
|
|
||||||
type: 'cost' | 'pricing';
|
|
||||||
status: number;
|
|
||||||
create_time: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 平台抽佣列表响应
|
|
||||||
export interface AgentPlatformDeductionList {
|
|
||||||
total: number;
|
|
||||||
items: AgentPlatformDeductionListItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取平台抽佣列表参数
|
|
||||||
export interface GetAgentPlatformDeductionListParams {
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
agent_id?: number;
|
|
||||||
type?: 'cost' | 'pricing';
|
|
||||||
status?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理产品配置列表项
|
// 代理产品配置列表项
|
||||||
export interface AgentProductionConfigItem {
|
export interface AgentProductionConfigItem {
|
||||||
id: number;
|
id: number;
|
||||||
|
product_id: number;
|
||||||
product_name: string;
|
product_name: string;
|
||||||
cost_price: number;
|
base_price: number;
|
||||||
price_range_min: number;
|
price_range_min: number;
|
||||||
price_range_max: number;
|
price_range_max: number;
|
||||||
pricing_standard: number;
|
price_threshold: number;
|
||||||
overpricing_ratio: number;
|
price_fee_rate: number;
|
||||||
create_time: string;
|
create_time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,17 +168,17 @@ export namespace AgentApi {
|
|||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
product_name?: string;
|
product_name?: string;
|
||||||
|
product_id?: number;
|
||||||
id?: number;
|
id?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新代理产品配置参数
|
// 更新代理产品配置参数
|
||||||
export interface UpdateAgentProductionConfigParams {
|
export interface UpdateAgentProductionConfigParams {
|
||||||
id: number;
|
id: number;
|
||||||
cost_price: number;
|
base_price: number;
|
||||||
price_range_min: number;
|
|
||||||
price_range_max: number;
|
price_range_max: number;
|
||||||
pricing_standard: number;
|
price_threshold?: number;
|
||||||
overpricing_ratio: number;
|
price_fee_rate?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新代理产品配置响应
|
// 更新代理产品配置响应
|
||||||
@@ -222,76 +186,213 @@ export namespace AgentApi {
|
|||||||
success: boolean;
|
success: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MembershipRechargeOrderListItem {
|
// 代理升级记录相关接口
|
||||||
|
export interface AgentUpgradeListItem {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: number;
|
|
||||||
agent_id: number;
|
agent_id: number;
|
||||||
level_name: string;
|
from_level: number;
|
||||||
amount: number;
|
to_level: number;
|
||||||
payment_method: 'alipay' | 'appleiap' | 'other' | 'wechat';
|
upgrade_type: number; // 1=自主付费,2=钻石升级下级
|
||||||
order_no: string;
|
upgrade_fee: number;
|
||||||
platform_order_id: string;
|
rebate_amount: number;
|
||||||
status: 'cancelled' | 'failed' | 'pending' | 'success';
|
status: number; // 1=待处理,2=已完成,3=已失败
|
||||||
create_time: string;
|
create_time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetMembershipRechargeOrderListParams {
|
export interface AgentUpgradeList {
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
user_id?: number;
|
|
||||||
agent_id?: number;
|
|
||||||
level_name?: string;
|
|
||||||
status?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MembershipRechargeOrderList {
|
|
||||||
total: number;
|
total: number;
|
||||||
items: MembershipRechargeOrderListItem[];
|
items: AgentUpgradeListItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理会员配置相关接口
|
export interface GetAgentUpgradeListParams {
|
||||||
export interface AgentMembershipConfigListItem {
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
agent_id?: number;
|
||||||
|
upgrade_type?: number;
|
||||||
|
status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代理订单相关接口
|
||||||
|
export interface AgentOrderListItem {
|
||||||
id: number;
|
id: number;
|
||||||
level_name: string;
|
agent_id: number;
|
||||||
price: number;
|
order_id: number;
|
||||||
report_commission: number;
|
product_id: number;
|
||||||
lower_activity_reward: null | number;
|
product_name: string;
|
||||||
new_activity_reward: null | number;
|
order_amount: number;
|
||||||
lower_standard_count: null | number;
|
set_price: number;
|
||||||
new_lower_standard_count: null | number;
|
actual_base_price: number;
|
||||||
lower_withdraw_reward_ratio: null | number;
|
price_cost: number;
|
||||||
lower_convert_vip_reward: null | number;
|
agent_profit: number;
|
||||||
lower_convert_svip_reward: null | number;
|
process_status: number; // 0=待处理,1=处理成功,2=处理失败
|
||||||
exemption_amount: number;
|
|
||||||
price_increase_max: null | number;
|
|
||||||
price_ratio: null | number;
|
|
||||||
price_increase_amount: null | number;
|
|
||||||
create_time: string;
|
create_time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetAgentMembershipConfigListParams {
|
export interface AgentOrderList {
|
||||||
page: number;
|
total: number;
|
||||||
pageSize: number;
|
items: AgentOrderListItem[];
|
||||||
level_name?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理会员配置编辑请求参数
|
export interface GetAgentOrderListParams {
|
||||||
export interface UpdateAgentMembershipConfigParams {
|
page: number;
|
||||||
id: number; // 主键
|
pageSize: number;
|
||||||
level_name: string; // 会员级别名称
|
agent_id?: number;
|
||||||
price: number; // 会员年费
|
order_id?: number;
|
||||||
report_commission: number; // 直推报告收益
|
process_status?: number;
|
||||||
lower_activity_reward?: null | number; // 下级活跃奖励金额
|
}
|
||||||
new_activity_reward?: null | number; // 新增活跃奖励金额
|
|
||||||
lower_standard_count?: null | number; // 活跃下级达标个数
|
// 邀请码管理相关接口
|
||||||
new_lower_standard_count?: null | number; // 新增活跃下级达标个数
|
export interface InviteCodeListItem {
|
||||||
lower_withdraw_reward_ratio?: null | number; // 下级提现奖励比例
|
id: number;
|
||||||
lower_convert_vip_reward?: null | number; // 下级转化VIP奖励
|
code: string;
|
||||||
lower_convert_svip_reward?: null | number; // 下级转化SVIP奖励
|
agent_id: number; // 0表示平台发放
|
||||||
exemption_amount?: null | number; // 免责金额
|
agent_mobile: string;
|
||||||
price_increase_max?: null | number; // 提价最高金额
|
target_level: number;
|
||||||
price_ratio?: null | number; // 提价区间收取比例
|
status: number; // 0=未使用,1=已使用,2=已失效
|
||||||
price_increase_amount?: null | number; // 在原本成本上加价的金额
|
used_user_id?: number;
|
||||||
|
used_agent_id?: number;
|
||||||
|
used_time?: string;
|
||||||
|
expire_time?: string;
|
||||||
|
remark?: string;
|
||||||
|
create_time: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InviteCodeList {
|
||||||
|
total: number;
|
||||||
|
items: InviteCodeListItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetInviteCodeListParams {
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
code?: string;
|
||||||
|
agent_id?: number;
|
||||||
|
target_level?: number;
|
||||||
|
status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateDiamondInviteCodeParams {
|
||||||
|
count: number;
|
||||||
|
expire_days?: number; // 可选,0表示不过期
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateDiamondInviteCodeResp {
|
||||||
|
codes: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 系统配置相关接口(价格配置已移除,改为产品配置表管理)
|
||||||
|
export interface AgentConfig {
|
||||||
|
level_bonus: {
|
||||||
|
diamond: number;
|
||||||
|
gold: number;
|
||||||
|
normal: number;
|
||||||
|
};
|
||||||
|
upgrade_fee: {
|
||||||
|
normal_to_gold: number;
|
||||||
|
normal_to_diamond: number;
|
||||||
|
gold_to_diamond: number;
|
||||||
|
};
|
||||||
|
upgrade_rebate: {
|
||||||
|
normal_to_gold_rebate: number;
|
||||||
|
to_diamond_rebate: number;
|
||||||
|
};
|
||||||
|
direct_parent_rebate: {
|
||||||
|
diamond: number; // 直接上级是钻石的返佣金额(6元)
|
||||||
|
gold: number; // 直接上级是黄金的返佣金额(3元)
|
||||||
|
normal: number; // 直接上级是普通的返佣金额(2元)
|
||||||
|
};
|
||||||
|
max_gold_rebate_amount: number; // 黄金代理最大返佣金额(3元)
|
||||||
|
commission_freeze: {
|
||||||
|
ratio: number; // 佣金冻结比例(例如:0.1表示10%)
|
||||||
|
threshold: number; // 佣金冻结阈值(订单单价达到此金额才触发冻结,单位:元)
|
||||||
|
days: number; // 佣金冻结解冻天数(单位:天,例如:30表示30天后解冻)
|
||||||
|
};
|
||||||
|
tax_rate: number;
|
||||||
|
tax_exemption_amount: number;
|
||||||
|
gold_max_uplift_amount: number;
|
||||||
|
diamond_max_uplift_amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateAgentConfigParams {
|
||||||
|
level_bonus?: {
|
||||||
|
diamond?: number;
|
||||||
|
gold?: number;
|
||||||
|
normal?: number;
|
||||||
|
};
|
||||||
|
upgrade_fee?: {
|
||||||
|
normal_to_gold?: number;
|
||||||
|
normal_to_diamond?: number;
|
||||||
|
gold_to_diamond?: number;
|
||||||
|
};
|
||||||
|
upgrade_rebate?: {
|
||||||
|
normal_to_gold_rebate?: number;
|
||||||
|
to_diamond_rebate?: number;
|
||||||
|
};
|
||||||
|
direct_parent_rebate?: {
|
||||||
|
diamond?: number; // 直接上级是钻石的返佣金额(6元)
|
||||||
|
gold?: number; // 直接上级是黄金的返佣金额(3元)
|
||||||
|
normal?: number; // 直接上级是普通的返佣金额(2元)
|
||||||
|
};
|
||||||
|
max_gold_rebate_amount?: number; // 黄金代理最大返佣金额(3元)
|
||||||
|
commission_freeze?: {
|
||||||
|
ratio?: number; // 佣金冻结比例(例如:0.1表示10%)
|
||||||
|
threshold?: number; // 佣金冻结阈值(订单单价达到此金额才触发冻结,单位:元)
|
||||||
|
days?: number; // 佣金冻结解冻天数(单位:天,例如:30表示30天后解冻)
|
||||||
|
};
|
||||||
|
tax_rate?: number;
|
||||||
|
tax_exemption_amount?: number;
|
||||||
|
gold_max_uplift_amount?: number;
|
||||||
|
diamond_max_uplift_amount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实名认证相关接口
|
||||||
|
export interface AgentRealNameListItem {
|
||||||
|
id: number;
|
||||||
|
agent_id: number;
|
||||||
|
name: string;
|
||||||
|
id_card: string; // 加密,需要脱敏显示
|
||||||
|
mobile: string; // 加密
|
||||||
|
status: number; // 1=未验证,2=已通过
|
||||||
|
verify_time?: string;
|
||||||
|
create_time: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentRealNameList {
|
||||||
|
total: number;
|
||||||
|
items: AgentRealNameListItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetAgentRealNameListParams {
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
agent_id?: number;
|
||||||
|
status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代理奖励相关接口
|
||||||
|
export interface AgentRewardListItem {
|
||||||
|
id: number;
|
||||||
|
agent_id: number;
|
||||||
|
type: string; // 奖励类型:register=注册奖励,first_order=首单奖励,level_up=升级奖励
|
||||||
|
amount: number;
|
||||||
|
order_id?: number;
|
||||||
|
status: string; // pending=待发放,paid=已发放,failed=发放失败
|
||||||
|
create_time: string;
|
||||||
|
pay_time?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentRewardList {
|
||||||
|
total: number;
|
||||||
|
items: AgentRewardListItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetAgentRewardListParams {
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
agent_id?: number;
|
||||||
|
type?: string;
|
||||||
|
status?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,8 +409,9 @@ async function getAgentList(params: AgentApi.GetAgentListParams) {
|
|||||||
/**
|
/**
|
||||||
* 获取代理推广链接列表
|
* 获取代理推广链接列表
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async function getAgentLinkList(params: AgentApi.GetAgentLinkListParams) {
|
async function getAgentLinkList(params: AgentApi.GetAgentLinkListParams) {
|
||||||
return requestClient.get<AgentApi.AgentLinkList>('/agent/agent-link/list', {
|
return requestClient.get<AgentApi.AgentLinkList>('/agent/link/list', {
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -321,19 +423,7 @@ async function getAgentCommissionList(
|
|||||||
params: AgentApi.GetAgentCommissionListParams,
|
params: AgentApi.GetAgentCommissionListParams,
|
||||||
) {
|
) {
|
||||||
return requestClient.get<AgentApi.AgentCommissionList>(
|
return requestClient.get<AgentApi.AgentCommissionList>(
|
||||||
'/agent/agent-commission/list',
|
'/agent/commission/list',
|
||||||
{
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取代理奖励列表
|
|
||||||
*/
|
|
||||||
async function getAgentRewardList(params: AgentApi.GetAgentRewardListParams) {
|
|
||||||
return requestClient.get<AgentApi.AgentRewardList>(
|
|
||||||
'/agent/agent-reward/list',
|
|
||||||
{
|
{
|
||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
@@ -347,35 +437,7 @@ async function getAgentWithdrawalList(
|
|||||||
params: AgentApi.GetAgentWithdrawalListParams,
|
params: AgentApi.GetAgentWithdrawalListParams,
|
||||||
) {
|
) {
|
||||||
return requestClient.get<AgentApi.AgentWithdrawalList>(
|
return requestClient.get<AgentApi.AgentWithdrawalList>(
|
||||||
'/agent/agent-withdrawal/list',
|
'/agent/withdrawal/list',
|
||||||
{
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取代理上级抽佣列表
|
|
||||||
*/
|
|
||||||
async function getAgentCommissionDeductionList(
|
|
||||||
params: AgentApi.GetAgentCommissionDeductionListParams,
|
|
||||||
) {
|
|
||||||
return requestClient.get<AgentApi.AgentCommissionDeductionList>(
|
|
||||||
'/agent/agent-commission-deduction/list',
|
|
||||||
{
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取平台抽佣列表
|
|
||||||
*/
|
|
||||||
async function getAgentPlatformDeductionList(
|
|
||||||
params: AgentApi.GetAgentPlatformDeductionListParams,
|
|
||||||
) {
|
|
||||||
return requestClient.get<AgentApi.AgentPlatformDeductionList>(
|
|
||||||
'/agent/agent-platform-deduction/list',
|
|
||||||
{
|
{
|
||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
@@ -389,7 +451,7 @@ async function getAgentProductionConfigList(
|
|||||||
params: AgentApi.GetAgentProductionConfigListParams,
|
params: AgentApi.GetAgentProductionConfigListParams,
|
||||||
) {
|
) {
|
||||||
return requestClient.get<AgentApi.AgentProductionConfigList>(
|
return requestClient.get<AgentApi.AgentProductionConfigList>(
|
||||||
'/agent/agent-production-config/list',
|
'/agent/product_config/list',
|
||||||
{
|
{
|
||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
@@ -403,19 +465,46 @@ async function updateAgentProductionConfig(
|
|||||||
params: AgentApi.UpdateAgentProductionConfigParams,
|
params: AgentApi.UpdateAgentProductionConfigParams,
|
||||||
) {
|
) {
|
||||||
return requestClient.post<AgentApi.UpdateAgentProductionConfigResp>(
|
return requestClient.post<AgentApi.UpdateAgentProductionConfigResp>(
|
||||||
'/agent/agent-production-config/update',
|
'/agent/product_config/update',
|
||||||
params,
|
params,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取会员充值订单列表
|
* 获取代理返佣记录列表
|
||||||
*/
|
*/
|
||||||
async function getMembershipRechargeOrderList(
|
async function getAgentRebateList(params: AgentApi.GetAgentRebateListParams) {
|
||||||
params: AgentApi.GetMembershipRechargeOrderListParams,
|
return requestClient.get<AgentApi.AgentRebateList>('/agent/rebate/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取代理升级记录列表
|
||||||
|
*/
|
||||||
|
async function getAgentUpgradeList(
|
||||||
|
params: AgentApi.GetAgentUpgradeListParams,
|
||||||
) {
|
) {
|
||||||
return requestClient.get<AgentApi.MembershipRechargeOrderList>(
|
return requestClient.get<AgentApi.AgentUpgradeList>('/agent/upgrade/list', {
|
||||||
'/agent/agent-membership-recharge-order/list',
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取代理订单列表
|
||||||
|
*/
|
||||||
|
async function getAgentOrderList(params: AgentApi.GetAgentOrderListParams) {
|
||||||
|
return requestClient.get<AgentApi.AgentOrderList>('/agent/order/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取邀请码列表
|
||||||
|
*/
|
||||||
|
async function getInviteCodeList(params: AgentApi.GetInviteCodeListParams) {
|
||||||
|
return requestClient.get<AgentApi.InviteCodeList>(
|
||||||
|
'/agent/invite_code/list',
|
||||||
{
|
{
|
||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
@@ -423,40 +512,84 @@ async function getMembershipRechargeOrderList(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取代理会员配置列表
|
* 生成钻石邀请码
|
||||||
*/
|
*/
|
||||||
async function getAgentMembershipConfigList(
|
async function generateDiamondInviteCode(
|
||||||
params: AgentApi.GetAgentMembershipConfigListParams,
|
params: AgentApi.GenerateDiamondInviteCodeParams,
|
||||||
) {
|
) {
|
||||||
return requestClient.get<{
|
return requestClient.post<AgentApi.GenerateDiamondInviteCodeResp>(
|
||||||
items: AgentApi.AgentMembershipConfigListItem[];
|
'/agent/invite_code/diamond/generate',
|
||||||
total: number;
|
|
||||||
}>('/agent/agent-membership-config/list', { params });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新代理会员配置
|
|
||||||
*/
|
|
||||||
async function updateAgentMembershipConfig(
|
|
||||||
params: AgentApi.UpdateAgentMembershipConfigParams,
|
|
||||||
) {
|
|
||||||
return requestClient.post<{ success: boolean }>(
|
|
||||||
'/agent/agent-membership-config/update',
|
|
||||||
params,
|
params,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统配置
|
||||||
|
*/
|
||||||
|
async function getAgentConfig() {
|
||||||
|
return requestClient.get<AgentApi.AgentConfig>('/agent/config');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新系统配置
|
||||||
|
*/
|
||||||
|
async function updateAgentConfig(params: AgentApi.UpdateAgentConfigParams) {
|
||||||
|
return requestClient.post<{ success: boolean }>(
|
||||||
|
'/agent/config/update',
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实名认证列表
|
||||||
|
*/
|
||||||
|
async function getAgentRealNameList(
|
||||||
|
params: AgentApi.GetAgentRealNameListParams,
|
||||||
|
) {
|
||||||
|
return requestClient.get<AgentApi.AgentRealNameList>(
|
||||||
|
'/agent/real_name/list',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核提现
|
||||||
|
*/
|
||||||
|
async function auditWithdrawal(params: AgentApi.AuditWithdrawalParams) {
|
||||||
|
return requestClient.post<{ success: boolean }>(
|
||||||
|
'/agent/withdrawal/audit',
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取代理奖励列表
|
||||||
|
*/
|
||||||
|
async function getAgentRewardList(
|
||||||
|
params: AgentApi.GetAgentRewardListParams,
|
||||||
|
) {
|
||||||
|
return requestClient.get<AgentApi.AgentRewardList>('/agent/reward/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getAgentCommissionDeductionList,
|
auditWithdrawal,
|
||||||
|
generateDiamondInviteCode,
|
||||||
getAgentCommissionList,
|
getAgentCommissionList,
|
||||||
|
getAgentConfig,
|
||||||
getAgentLinkList,
|
getAgentLinkList,
|
||||||
getAgentList,
|
getAgentList,
|
||||||
getAgentMembershipConfigList,
|
getAgentOrderList,
|
||||||
getAgentPlatformDeductionList,
|
|
||||||
getAgentProductionConfigList,
|
getAgentProductionConfigList,
|
||||||
|
getAgentRebateList,
|
||||||
|
getAgentRealNameList,
|
||||||
getAgentRewardList,
|
getAgentRewardList,
|
||||||
|
getAgentUpgradeList,
|
||||||
getAgentWithdrawalList,
|
getAgentWithdrawalList,
|
||||||
getMembershipRechargeOrderList,
|
getInviteCodeList,
|
||||||
updateAgentMembershipConfig,
|
updateAgentConfig,
|
||||||
updateAgentProductionConfig,
|
updateAgentProductionConfig,
|
||||||
};
|
};
|
||||||
|
|||||||
160
apps/web-antd/src/api/complaint/complaint.ts
Normal file
160
apps/web-antd/src/api/complaint/complaint.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace ComplaintApi {
|
||||||
|
export interface Complaint {
|
||||||
|
id: string;
|
||||||
|
type: 'alipay' | 'manual';
|
||||||
|
order_id: string;
|
||||||
|
name: string;
|
||||||
|
contact: string;
|
||||||
|
content: string;
|
||||||
|
status: 'pending' | 'processing' | 'resolved' | 'closed';
|
||||||
|
status_description: string;
|
||||||
|
remark: string;
|
||||||
|
handler_id: string;
|
||||||
|
handle_time: string;
|
||||||
|
create_time: string;
|
||||||
|
update_time: string;
|
||||||
|
// 支付宝投诉特有字段
|
||||||
|
task_id?: string;
|
||||||
|
trade_no?: string;
|
||||||
|
complain_amount?: string;
|
||||||
|
gmt_complain?: string;
|
||||||
|
// 主动投诉特有字段
|
||||||
|
subject?: string;
|
||||||
|
priority?: string;
|
||||||
|
source?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComplaintList {
|
||||||
|
total: number;
|
||||||
|
items: Complaint[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComplaintDetail {
|
||||||
|
id: string;
|
||||||
|
type: 'alipay' | 'manual';
|
||||||
|
order_id: string;
|
||||||
|
name: string;
|
||||||
|
contact: string;
|
||||||
|
content: string;
|
||||||
|
status: string;
|
||||||
|
status_description: string;
|
||||||
|
remark: string;
|
||||||
|
handler_id: string;
|
||||||
|
handle_time: string;
|
||||||
|
create_time: string;
|
||||||
|
update_time: string;
|
||||||
|
alipay_complaint?: AlipayComplaintDetail;
|
||||||
|
manual_complaint?: ManualComplaintDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlipayComplaintDetail {
|
||||||
|
id: string;
|
||||||
|
alipay_id: number;
|
||||||
|
task_id: string;
|
||||||
|
opposite_pid: string;
|
||||||
|
opposite_name: string;
|
||||||
|
complain_amount: string;
|
||||||
|
gmt_complain: string;
|
||||||
|
gmt_process: string;
|
||||||
|
complain_content: string;
|
||||||
|
trade_no: string;
|
||||||
|
status: string;
|
||||||
|
status_description: string;
|
||||||
|
process_code: string;
|
||||||
|
process_message: string;
|
||||||
|
process_remark: string;
|
||||||
|
process_img_url_list: string[];
|
||||||
|
gmt_risk_finish_time: string;
|
||||||
|
complain_url: string;
|
||||||
|
certify_info: string[];
|
||||||
|
trade_info_list: AlipayComplaintTradeInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlipayComplaintTradeInfo {
|
||||||
|
id: string;
|
||||||
|
alipay_trade_id: string;
|
||||||
|
alipay_complaint_record_id: string;
|
||||||
|
trade_no: string;
|
||||||
|
out_no: string;
|
||||||
|
gmt_trade: string;
|
||||||
|
gmt_refund: string;
|
||||||
|
status: string;
|
||||||
|
status_description: string;
|
||||||
|
amount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ManualComplaintDetail {
|
||||||
|
id: string;
|
||||||
|
user_id: string;
|
||||||
|
subject: string;
|
||||||
|
priority: string;
|
||||||
|
source: string;
|
||||||
|
attachment_urls: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateStatusRequest {
|
||||||
|
status: 'pending' | 'processing' | 'resolved' | 'closed';
|
||||||
|
status_description?: string;
|
||||||
|
handler_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateRemarkRequest {
|
||||||
|
remark: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取投诉列表
|
||||||
|
*/
|
||||||
|
async function getComplaintList(params: Recordable<any>) {
|
||||||
|
return requestClient.get<ComplaintApi.ComplaintList>('/complaint/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取投诉详情
|
||||||
|
* @param id 投诉 ID
|
||||||
|
*/
|
||||||
|
async function getComplaintDetail(id: string) {
|
||||||
|
return requestClient.get<ComplaintApi.ComplaintDetail>(
|
||||||
|
`/complaint/detail/${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新投诉状态
|
||||||
|
* @param id 投诉 ID
|
||||||
|
* @param data 更新状态请求数据
|
||||||
|
*/
|
||||||
|
async function updateComplaintStatus(
|
||||||
|
id: string,
|
||||||
|
data: ComplaintApi.UpdateStatusRequest,
|
||||||
|
) {
|
||||||
|
return requestClient.put<{ success: boolean }>(
|
||||||
|
`/complaint/update-status/${id}`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新投诉备注
|
||||||
|
* @param id 投诉 ID
|
||||||
|
* @param data 更新备注请求数据
|
||||||
|
*/
|
||||||
|
async function updateComplaintRemark(
|
||||||
|
id: string,
|
||||||
|
data: ComplaintApi.UpdateRemarkRequest,
|
||||||
|
) {
|
||||||
|
return requestClient.put<{ success: boolean }>(
|
||||||
|
`/complaint/update-remark/${id}`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getComplaintList, getComplaintDetail, updateComplaintStatus, updateComplaintRemark };
|
||||||
|
|
||||||
2
apps/web-antd/src/api/complaint/index.ts
Normal file
2
apps/web-antd/src/api/complaint/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './complaint';
|
||||||
|
|
||||||
74
apps/web-antd/src/api/dashboard/dashboard.ts
Normal file
74
apps/web-antd/src/api/dashboard/dashboard.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace DashboardApi {
|
||||||
|
export interface OrderStatistics {
|
||||||
|
today_count: number;
|
||||||
|
month_count: number;
|
||||||
|
total_count: number;
|
||||||
|
yesterday_count: number;
|
||||||
|
change_rate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RevenueStatistics {
|
||||||
|
today_amount: number;
|
||||||
|
month_amount: number;
|
||||||
|
total_amount: number;
|
||||||
|
yesterday_amount: number;
|
||||||
|
change_rate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentStatistics {
|
||||||
|
total_count: number;
|
||||||
|
today_new: number;
|
||||||
|
month_new: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfitDetail {
|
||||||
|
revenue: number; // 营收
|
||||||
|
commission: number; // 佣金
|
||||||
|
rebate: number; // 返利
|
||||||
|
company_tax: number; // 税务成本
|
||||||
|
api_cost: number; // API调用成本
|
||||||
|
tax_income: number; // 提现收税
|
||||||
|
profit: number; // 利润
|
||||||
|
profit_rate: number; // 利润率
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfitStatistics {
|
||||||
|
today_profit: number;
|
||||||
|
month_profit: number;
|
||||||
|
total_profit: number;
|
||||||
|
today_profit_rate: number;
|
||||||
|
month_profit_rate: number;
|
||||||
|
total_profit_rate: number;
|
||||||
|
today_detail: ProfitDetail;
|
||||||
|
month_detail: ProfitDetail;
|
||||||
|
total_detail: ProfitDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrendData {
|
||||||
|
date: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardStatistics {
|
||||||
|
order_stats: OrderStatistics;
|
||||||
|
revenue_stats: RevenueStatistics;
|
||||||
|
agent_stats: AgentStatistics;
|
||||||
|
profit_stats: ProfitStatistics;
|
||||||
|
order_trend: TrendData[];
|
||||||
|
revenue_trend: TrendData[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统计面板数据
|
||||||
|
*/
|
||||||
|
export async function getDashboardStatistics(): Promise<DashboardApi.DashboardStatistics> {
|
||||||
|
return await requestClient.get<DashboardApi.DashboardStatistics>(
|
||||||
|
'/dashboard/statistics',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
2
apps/web-antd/src/api/dashboard/index.ts
Normal file
2
apps/web-antd/src/api/dashboard/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './dashboard';
|
||||||
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
export * from './agent';
|
export * from './agent';
|
||||||
|
export * from './complaint';
|
||||||
export * from './core';
|
export * from './core';
|
||||||
|
export * from './dashboard';
|
||||||
export * from './notification';
|
export * from './notification';
|
||||||
export * from './order';
|
export * from './order';
|
||||||
export * from './platform-user';
|
export * from './platform-user';
|
||||||
export * from './product-manage';
|
export * from './product-manage';
|
||||||
export * from './promotion';
|
|
||||||
export * from './system';
|
export * from './system';
|
||||||
export interface ApiResponse<T = any> {
|
export interface ApiResponse<T = any> {
|
||||||
code: number;
|
code: number;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { requestClient } from '#/api/request';
|
|||||||
|
|
||||||
export namespace OrderApi {
|
export namespace OrderApi {
|
||||||
export interface Order {
|
export interface Order {
|
||||||
id: number;
|
id: string;
|
||||||
order_no: string;
|
order_no: string;
|
||||||
platform_order_id: string;
|
platform_order_id: string;
|
||||||
product_name: string;
|
product_name: string;
|
||||||
@@ -16,7 +16,6 @@ export namespace OrderApi {
|
|||||||
create_time: string;
|
create_time: string;
|
||||||
pay_time: null | string;
|
pay_time: null | string;
|
||||||
refund_time: null | string;
|
refund_time: null | string;
|
||||||
is_promotion: 0 | 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderList {
|
export interface OrderList {
|
||||||
@@ -50,7 +49,7 @@ async function getOrderList(params: Recordable<any>) {
|
|||||||
* @param id 订单 ID
|
* @param id 订单 ID
|
||||||
* @param data 退款请求数据
|
* @param data 退款请求数据
|
||||||
*/
|
*/
|
||||||
async function refundOrder(id: number, data: OrderApi.RefundOrderRequest) {
|
async function refundOrder(id: string, data: OrderApi.RefundOrderRequest) {
|
||||||
return requestClient.post<OrderApi.RefundOrderResponse>(
|
return requestClient.post<OrderApi.RefundOrderResponse>(
|
||||||
`/order/refund/${id}`,
|
`/order/refund/${id}`,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ export namespace OrderQueryApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryDetail {
|
export interface QueryDetail {
|
||||||
id: number;
|
id: string;
|
||||||
order_id: number;
|
order_id: string;
|
||||||
user_id: number;
|
user_id: string;
|
||||||
product_name: string;
|
product_name: string;
|
||||||
query_params: Recordable<any>;
|
query_params: Recordable<any>;
|
||||||
query_data: QueryItem[];
|
query_data: QueryItem[];
|
||||||
@@ -21,13 +21,13 @@ export namespace OrderQueryApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GetQueryDetailRequest {
|
export interface GetQueryDetailRequest {
|
||||||
order_id: number;
|
order_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetQueryDetailResponse {
|
export interface GetQueryDetailResponse {
|
||||||
id: number;
|
id: string;
|
||||||
order_id: number;
|
order_id: string;
|
||||||
user_id: number;
|
user_id: string;
|
||||||
product_name: string;
|
product_name: string;
|
||||||
query_params: Recordable<any>;
|
query_params: Recordable<any>;
|
||||||
query_data: QueryItem[];
|
query_data: QueryItem[];
|
||||||
@@ -119,7 +119,7 @@ export namespace OrderQueryApi {
|
|||||||
* 获取订单查询详情
|
* 获取订单查询详情
|
||||||
* @param orderId 订单ID
|
* @param orderId 订单ID
|
||||||
*/
|
*/
|
||||||
async function getOrderQueryDetail(orderId: number) {
|
async function getOrderQueryDetail(orderId: string) {
|
||||||
return requestClient.get<OrderQueryApi.GetQueryDetailResponse>(
|
return requestClient.get<OrderQueryApi.GetQueryDetailResponse>(
|
||||||
`/query/detail/${orderId}`,
|
`/query/detail/${orderId}`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export namespace FeatureApi {
|
|||||||
id: number;
|
id: number;
|
||||||
api_id: string;
|
api_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
whitelist_price: number;
|
||||||
|
cost_price: number;
|
||||||
create_time: string;
|
create_time: string;
|
||||||
update_time: string;
|
update_time: string;
|
||||||
}
|
}
|
||||||
@@ -19,11 +21,15 @@ export namespace FeatureApi {
|
|||||||
export interface CreateFeatureRequest {
|
export interface CreateFeatureRequest {
|
||||||
api_id: string;
|
api_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
whitelist_price?: number;
|
||||||
|
cost_price?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateFeatureRequest {
|
export interface UpdateFeatureRequest {
|
||||||
api_id?: string;
|
api_id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
whitelist_price?: number;
|
||||||
|
cost_price?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FeatureExampleItem {
|
export interface FeatureExampleItem {
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import { requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
export namespace PromotionAnalyticsApi {
|
|
||||||
export interface OverviewData {
|
|
||||||
today_click_count: number;
|
|
||||||
today_pay_count: number;
|
|
||||||
today_pay_amount: number;
|
|
||||||
total_click_count: number;
|
|
||||||
total_pay_count: number;
|
|
||||||
total_pay_amount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TrendData {
|
|
||||||
id: number;
|
|
||||||
link_id: number;
|
|
||||||
pay_amount: number;
|
|
||||||
click_count: number;
|
|
||||||
pay_count: number;
|
|
||||||
stats_date: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取推广数据概览
|
|
||||||
*/
|
|
||||||
async function statsTotal() {
|
|
||||||
return requestClient.get<PromotionAnalyticsApi.OverviewData>(
|
|
||||||
'/promotion/stats/total',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取推广数据趋势
|
|
||||||
* @param params 日期范围参数
|
|
||||||
*/
|
|
||||||
async function statsHistory(params: { end_date: string; start_date: string }) {
|
|
||||||
return requestClient.get<PromotionAnalyticsApi.TrendData[]>(
|
|
||||||
'/promotion/stats/history',
|
|
||||||
{
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { statsHistory, statsTotal };
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './analytics';
|
|
||||||
export * from './link';
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import type { Recordable } from '@vben/types';
|
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
export namespace PromotionLinkApi {
|
|
||||||
export interface PromotionLinkItem {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
create_time: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PromotionLink {
|
|
||||||
total: number;
|
|
||||||
items: PromotionLinkItem[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取推广链接列表数据
|
|
||||||
*/
|
|
||||||
async function getPromotionLinkList(params: Recordable<any>) {
|
|
||||||
return requestClient.get<PromotionLinkApi.PromotionLink>(
|
|
||||||
'/promotion/link/list',
|
|
||||||
{
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建推广链接
|
|
||||||
* @param data 推广链接数据
|
|
||||||
*/
|
|
||||||
async function createPromotionLink(
|
|
||||||
data: Omit<PromotionLinkApi.PromotionLinkItem, 'id'>,
|
|
||||||
) {
|
|
||||||
return requestClient.post('/promotion/link/create', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新推广链接
|
|
||||||
*
|
|
||||||
* @param id 推广链接 ID
|
|
||||||
* @param data 推广链接数据
|
|
||||||
*/
|
|
||||||
async function updatePromotionLink(
|
|
||||||
id: string,
|
|
||||||
data: Omit<PromotionLinkApi.PromotionLinkItem, 'id'>,
|
|
||||||
) {
|
|
||||||
return requestClient.put(`/promotion/link/update/${id}`, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除推广链接
|
|
||||||
* @param id 推广链接 ID
|
|
||||||
*/
|
|
||||||
async function deletePromotionLink(id: string) {
|
|
||||||
return requestClient.delete(`/promotion/link/delete/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
createPromotionLink,
|
|
||||||
deletePromotionLink,
|
|
||||||
getPromotionLinkList,
|
|
||||||
updatePromotionLink,
|
|
||||||
};
|
|
||||||
@@ -4,9 +4,9 @@ import { requestClient } from '#/api/request';
|
|||||||
|
|
||||||
export namespace SystemApiApi {
|
export namespace SystemApiApi {
|
||||||
export interface SystemApiItem {
|
export interface SystemApiItem {
|
||||||
id: number;
|
id: string;
|
||||||
role_id?: number;
|
role_id?: string;
|
||||||
api_id?: number;
|
api_id?: string;
|
||||||
api_name: string;
|
api_name: string;
|
||||||
api_code: string;
|
api_code: string;
|
||||||
method: string;
|
method: string;
|
||||||
@@ -31,9 +31,9 @@ export namespace SystemApiApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RoleApiItem {
|
export interface RoleApiItem {
|
||||||
id: number;
|
id: string;
|
||||||
role_id: number;
|
role_id: string;
|
||||||
api_id: number;
|
api_id: string;
|
||||||
api_name: string;
|
api_name: string;
|
||||||
api_code: string;
|
api_code: string;
|
||||||
method: string;
|
method: string;
|
||||||
@@ -60,7 +60,7 @@ async function getApiList(params: Recordable<any>) {
|
|||||||
* 获取API详情
|
* 获取API详情
|
||||||
* @param id API ID
|
* @param id API ID
|
||||||
*/
|
*/
|
||||||
async function getApiDetail(id: number) {
|
async function getApiDetail(id: string) {
|
||||||
return requestClient.get<SystemApiApi.SystemApiItem>(`/api/detail/${id}`);
|
return requestClient.get<SystemApiApi.SystemApiItem>(`/api/detail/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ async function createApi(
|
|||||||
* @param data API数据
|
* @param data API数据
|
||||||
*/
|
*/
|
||||||
async function updateApi(
|
async function updateApi(
|
||||||
id: number,
|
id: string,
|
||||||
data: Omit<SystemApiApi.SystemApiItem, 'create_time' | 'id' | 'update_time'>,
|
data: Omit<SystemApiApi.SystemApiItem, 'create_time' | 'id' | 'update_time'>,
|
||||||
) {
|
) {
|
||||||
return requestClient.put(`/api/update/${id}`, data);
|
return requestClient.put(`/api/update/${id}`, data);
|
||||||
@@ -107,7 +107,7 @@ async function batchUpdateApiStatus(data: { ids: number[]; status: 0 | 1 }) {
|
|||||||
* 获取角色API权限列表
|
* 获取角色API权限列表
|
||||||
* @param roleId 角色ID
|
* @param roleId 角色ID
|
||||||
*/
|
*/
|
||||||
async function getRoleApiList(roleId: number) {
|
async function getRoleApiList(roleId: string) {
|
||||||
return requestClient.get<SystemApiApi.SystemRoleApiResponse>(
|
return requestClient.get<SystemApiApi.SystemRoleApiResponse>(
|
||||||
`/role/${roleId}/api/list`,
|
`/role/${roleId}/api/list`,
|
||||||
);
|
);
|
||||||
@@ -118,7 +118,7 @@ async function getRoleApiList(roleId: number) {
|
|||||||
* @param data.api_ids API ID数组
|
* @param data.api_ids API ID数组
|
||||||
* @param data.role_id 角色ID
|
* @param data.role_id 角色ID
|
||||||
*/
|
*/
|
||||||
async function assignRoleApi(data: { api_ids: number[]; role_id: number }) {
|
async function assignRoleApi(data: { api_ids: string[]; role_id: string }) {
|
||||||
return requestClient.post('/role/api/assign', data);
|
return requestClient.post('/role/api/assign', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ async function assignRoleApi(data: { api_ids: number[]; role_id: number }) {
|
|||||||
* @param data.api_ids API ID数组
|
* @param data.api_ids API ID数组
|
||||||
* @param data.role_id 角色ID
|
* @param data.role_id 角色ID
|
||||||
*/
|
*/
|
||||||
async function removeRoleApi(data: { api_ids: number[]; role_id: number }) {
|
async function removeRoleApi(data: { api_ids: string[]; role_id: string }) {
|
||||||
return requestClient.post('/role/api/remove', data);
|
return requestClient.post('/role/api/remove', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ async function removeRoleApi(data: { api_ids: number[]; role_id: number }) {
|
|||||||
* @param data.api_ids API ID数组
|
* @param data.api_ids API ID数组
|
||||||
* @param data.role_id 角色ID
|
* @param data.role_id 角色ID
|
||||||
*/
|
*/
|
||||||
async function updateRoleApi(data: { api_ids: number[]; role_id: number }) {
|
async function updateRoleApi(data: { api_ids: string[]; role_id: string }) {
|
||||||
return requestClient.put('/role/api/update', data);
|
return requestClient.put('/role/api/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import { requestClient } from '#/api/request';
|
|||||||
|
|
||||||
export namespace SystemRoleApi {
|
export namespace SystemRoleApi {
|
||||||
export interface SystemRoleItem {
|
export interface SystemRoleItem {
|
||||||
id: number;
|
id: string;
|
||||||
role_name: string;
|
role_name: string;
|
||||||
role_code: string;
|
role_code: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
status: 0 | 1;
|
status: 0 | 1;
|
||||||
sort: number;
|
sort: number;
|
||||||
create_time: string;
|
create_time: string;
|
||||||
menu_ids: number[];
|
menu_ids: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SystemRole {
|
export interface SystemRole {
|
||||||
@@ -44,7 +44,7 @@ async function createRole(data: Omit<SystemRoleApi.SystemRoleItem, 'id'>) {
|
|||||||
* @param data 角色数据
|
* @param data 角色数据
|
||||||
*/
|
*/
|
||||||
async function updateRole(
|
async function updateRole(
|
||||||
id: number,
|
id: string,
|
||||||
data: Omit<SystemRoleApi.SystemRoleItem, 'id'>,
|
data: Omit<SystemRoleApi.SystemRoleItem, 'id'>,
|
||||||
) {
|
) {
|
||||||
return requestClient.put(`/role/update/${id}`, data);
|
return requestClient.put(`/role/update/${id}`, data);
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ export const overridesPreferences = defineOverridesPreferences({
|
|||||||
accessMode: 'backend',
|
accessMode: 'backend',
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
source: 'https://ctrlph.tianyuandb.com/logo.png',
|
source: 'https://ctrlph.onecha.cn/logo.png',
|
||||||
},
|
},
|
||||||
copyright: {
|
copyright: {
|
||||||
companyName: '海南天远大数据科技有限公司',
|
companyName: '海南海宇大数据有限公司',
|
||||||
companySiteLink: 'https://www.tianyuandb.com',
|
companySiteLink: 'https://www.onecha.cn',
|
||||||
date: '2025',
|
date: '2025',
|
||||||
icp: '琼ICP备2024048057号-1',
|
icp: '琼ICP备2024048057号-2',
|
||||||
icpLink: 'https://beian.miit.gov.cn/',
|
icpLink: 'https://beian.miit.gov.cn/',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
|
|||||||
117
apps/web-antd/src/router/routes/modules/agent.ts
Normal file
117
apps/web-antd/src/router/routes/modules/agent.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:account-group',
|
||||||
|
order: 2000,
|
||||||
|
title: '代理管理',
|
||||||
|
},
|
||||||
|
name: 'Agent',
|
||||||
|
path: '/agent',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/agent/list',
|
||||||
|
name: 'AgentList',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:account-multiple',
|
||||||
|
title: '代理列表',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-list/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/links',
|
||||||
|
name: 'AgentLinks',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:link-variant',
|
||||||
|
title: '推广链接',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-links/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/commission',
|
||||||
|
name: 'AgentCommission',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:cash-multiple',
|
||||||
|
title: '佣金记录',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-commission/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/rebate',
|
||||||
|
name: 'AgentRebate',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:currency-usd',
|
||||||
|
title: '返佣记录',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-rebate/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/upgrade',
|
||||||
|
name: 'AgentUpgrade',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:arrow-up-circle',
|
||||||
|
title: '升级记录',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-upgrade/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/order',
|
||||||
|
name: 'AgentOrder',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:package-variant',
|
||||||
|
title: '订单记录',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-order/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/withdrawal',
|
||||||
|
name: 'AgentWithdrawal',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:bank-transfer-out',
|
||||||
|
title: '提现记录',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-withdrawal/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/invite-code',
|
||||||
|
name: 'AgentInviteCode',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:ticket-confirmation',
|
||||||
|
title: '邀请码管理',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-invite-code/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/config',
|
||||||
|
name: 'AgentConfig',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:cog',
|
||||||
|
title: '系统配置',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-config/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/real-name',
|
||||||
|
name: 'AgentRealName',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:account-check',
|
||||||
|
title: '实名认证',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-real-name/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agent/product-config',
|
||||||
|
name: 'AgentProductConfig',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:package-variant-closed',
|
||||||
|
title: '产品配置',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/agent/agent-product-config/list.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
|
|
||||||
17
apps/web-antd/src/router/routes/modules/complaint.ts
Normal file
17
apps/web-antd/src/router/routes/modules/complaint.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:message-square-warning',
|
||||||
|
order: 2000,
|
||||||
|
title: '投诉管理',
|
||||||
|
},
|
||||||
|
name: 'Complaint',
|
||||||
|
path: '/complaint',
|
||||||
|
component: () => import('#/views/complaint/list/index.vue'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
|
|
||||||
@@ -11,6 +11,15 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/order',
|
path: '/order',
|
||||||
component: () => import('#/views/order/order/index.vue'),
|
component: () => import('#/views/order/order/index.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
hideInMenu: true,
|
||||||
|
title: '订单查询详情',
|
||||||
|
},
|
||||||
|
name: 'OrderQueryDetail',
|
||||||
|
path: '/order/query/detail/:id',
|
||||||
|
component: () => import('#/views/order/query/query-details.vue'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|||||||
113
apps/web-antd/src/utils/agent.ts
Normal file
113
apps/web-antd/src/utils/agent.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* 等级数字转中文名称
|
||||||
|
*/
|
||||||
|
export function getLevelName(level: number): string {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
1: '普通代理',
|
||||||
|
2: '黄金代理',
|
||||||
|
3: '钻石代理',
|
||||||
|
};
|
||||||
|
return map[level] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返佣类型转中文
|
||||||
|
*/
|
||||||
|
export function getRebateTypeName(type: number): string {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
1: '直接上级返佣',
|
||||||
|
2: '钻石上级返佣',
|
||||||
|
3: '黄金上级返佣',
|
||||||
|
};
|
||||||
|
return map[type] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 升级类型转中文
|
||||||
|
*/
|
||||||
|
export function getUpgradeTypeName(type: number): string {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
1: '自主付费',
|
||||||
|
2: '钻石升级下级',
|
||||||
|
};
|
||||||
|
return map[type] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提现状态转中文
|
||||||
|
*/
|
||||||
|
export function getWithdrawalStatusName(status: number): string {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
1: '待审核',
|
||||||
|
2: '审核通过',
|
||||||
|
3: '审核拒绝',
|
||||||
|
4: '提现中',
|
||||||
|
5: '提现成功',
|
||||||
|
6: '提现失败',
|
||||||
|
};
|
||||||
|
return map[status] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单处理状态转中文
|
||||||
|
*/
|
||||||
|
export function getOrderProcessStatusName(status: number): string {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
0: '待处理',
|
||||||
|
1: '处理成功',
|
||||||
|
2: '处理失败',
|
||||||
|
};
|
||||||
|
return map[status] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 升级状态转中文
|
||||||
|
*/
|
||||||
|
export function getUpgradeStatusName(status: number): string {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
1: '待处理',
|
||||||
|
2: '已完成',
|
||||||
|
3: '已失败',
|
||||||
|
};
|
||||||
|
return map[status] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邀请码状态转中文
|
||||||
|
*/
|
||||||
|
export function getInviteCodeStatusName(status: number): string {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
0: '未使用',
|
||||||
|
1: '已使用',
|
||||||
|
2: '已失效',
|
||||||
|
};
|
||||||
|
return map[status] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实名认证状态转中文
|
||||||
|
*/
|
||||||
|
export function getRealNameStatusName(status: number): string {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
1: '未验证',
|
||||||
|
2: '已通过',
|
||||||
|
};
|
||||||
|
return map[status] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份证号脱敏显示
|
||||||
|
*/
|
||||||
|
export function maskIdCard(idCard: string): string {
|
||||||
|
if (!idCard || idCard.length < 14) return idCard;
|
||||||
|
return `${idCard.slice(0, 6)}${'*'.repeat(8)}${idCard.slice(-4)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号脱敏显示
|
||||||
|
*/
|
||||||
|
export function maskMobile(mobile: string): string {
|
||||||
|
if (!mobile || mobile.length !== 11) return mobile;
|
||||||
|
return `${mobile.slice(0, 3)}****${mobile.slice(-4)}`;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
|
|
||||||
export function useCommissionDeductionColumns(): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: 'ID',
|
|
||||||
field: 'id',
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '代理ID',
|
|
||||||
field: 'agent_id',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '被扣代理ID',
|
|
||||||
field: 'deducted_agent_id',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '抽佣金额',
|
|
||||||
field: 'amount',
|
|
||||||
width: 120,
|
|
||||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '产品名称',
|
|
||||||
field: 'product_name',
|
|
||||||
width: 150,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '抽佣类型',
|
|
||||||
field: 'type',
|
|
||||||
width: 120,
|
|
||||||
formatter: ({ cellValue }: { cellValue: 'cost' | 'pricing' }) => {
|
|
||||||
const typeMap = {
|
|
||||||
cost: '成本抽佣',
|
|
||||||
pricing: '定价抽佣',
|
|
||||||
};
|
|
||||||
return typeMap[cellValue] || cellValue;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '状态',
|
|
||||||
field: 'status',
|
|
||||||
width: 100,
|
|
||||||
cellRender: {
|
|
||||||
name: 'CellTag',
|
|
||||||
options: [
|
|
||||||
{ value: 0, color: 'warning', label: '待结算' },
|
|
||||||
{ value: 1, color: 'success', label: '已结算' },
|
|
||||||
{ value: 2, color: 'error', label: '已取消' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建时间',
|
|
||||||
field: 'create_time',
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCommissionDeductionFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
fieldName: 'product_name',
|
|
||||||
label: '产品名称',
|
|
||||||
component: 'Input',
|
|
||||||
componentProps: {
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldName: 'type',
|
|
||||||
label: '抽佣类型',
|
|
||||||
component: 'Select',
|
|
||||||
componentProps: {
|
|
||||||
options: [
|
|
||||||
{ label: '成本抽佣', value: 'cost' },
|
|
||||||
{ label: '定价抽佣', value: 'pricing' },
|
|
||||||
],
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldName: 'status',
|
|
||||||
label: '状态',
|
|
||||||
component: 'Select',
|
|
||||||
componentProps: {
|
|
||||||
options: [
|
|
||||||
{ label: '待结算', value: 0 },
|
|
||||||
{ label: '已结算', value: 1 },
|
|
||||||
{ label: '已取消', value: 2 },
|
|
||||||
],
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -32,9 +32,9 @@ export function useCommissionColumns(): VxeTableGridOptions['columns'] {
|
|||||||
width: 100,
|
width: 100,
|
||||||
formatter: ({ cellValue }: { cellValue: number }) => {
|
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||||
const statusMap: Record<number, string> = {
|
const statusMap: Record<number, string> = {
|
||||||
0: '待结算',
|
1: '已发放',
|
||||||
1: '已结算',
|
2: '已冻结',
|
||||||
2: '已取消',
|
3: '已取消(已退款)',
|
||||||
};
|
};
|
||||||
return statusMap[cellValue] || '未知';
|
return statusMap[cellValue] || '未知';
|
||||||
},
|
},
|
||||||
@@ -64,9 +64,9 @@ export function useCommissionFormSchema(): VbenFormSchema[] {
|
|||||||
componentProps: {
|
componentProps: {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: [
|
options: [
|
||||||
{ label: '待结算', value: 0 },
|
{ label: '已发放', value: 1 },
|
||||||
{ label: '已结算', value: 1 },
|
{ label: '已冻结', value: 2 },
|
||||||
{ label: '已取消', value: 2 },
|
{ label: '已取消(已退款)', value: 3 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
274
apps/web-antd/src/views/agent/agent-config/list.vue
Normal file
274
apps/web-antd/src/views/agent/agent-config/list.vue
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { AgentApi } from '#/api/agent';
|
||||||
|
|
||||||
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button, Card, Col, Form, InputNumber, Row, Space, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getAgentConfig, updateAgentConfig } from '#/api/agent';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const config = ref<AgentApi.AgentConfig | null>(null);
|
||||||
|
|
||||||
|
// 使用 reactive 管理表单数据(价格配置已移除,改为产品配置表管理)
|
||||||
|
const formData = reactive<AgentApi.AgentConfig>({
|
||||||
|
level_bonus: {
|
||||||
|
normal: 0,
|
||||||
|
gold: 0,
|
||||||
|
diamond: 0,
|
||||||
|
},
|
||||||
|
upgrade_fee: {
|
||||||
|
normal_to_gold: 0,
|
||||||
|
normal_to_diamond: 0,
|
||||||
|
gold_to_diamond: 0,
|
||||||
|
},
|
||||||
|
upgrade_rebate: {
|
||||||
|
normal_to_gold_rebate: 0,
|
||||||
|
to_diamond_rebate: 0,
|
||||||
|
},
|
||||||
|
direct_parent_rebate: {
|
||||||
|
diamond: 0,
|
||||||
|
gold: 0,
|
||||||
|
normal: 0,
|
||||||
|
},
|
||||||
|
max_gold_rebate_amount: 0,
|
||||||
|
commission_freeze: {
|
||||||
|
ratio: 0,
|
||||||
|
threshold: 0,
|
||||||
|
days: 0,
|
||||||
|
},
|
||||||
|
tax_rate: 0,
|
||||||
|
tax_exemption_amount: 0,
|
||||||
|
gold_max_uplift_amount: 0,
|
||||||
|
diamond_max_uplift_amount: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载配置
|
||||||
|
async function loadConfig() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getAgentConfig();
|
||||||
|
config.value = res;
|
||||||
|
Object.assign(formData, res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载配置失败:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
async function handleSave() {
|
||||||
|
try {
|
||||||
|
const params: AgentApi.UpdateAgentConfigParams = {
|
||||||
|
level_bonus: {
|
||||||
|
normal: formData.level_bonus.normal,
|
||||||
|
gold: formData.level_bonus.gold,
|
||||||
|
diamond: formData.level_bonus.diamond,
|
||||||
|
},
|
||||||
|
upgrade_fee: {
|
||||||
|
normal_to_gold: formData.upgrade_fee.normal_to_gold,
|
||||||
|
normal_to_diamond: formData.upgrade_fee.normal_to_diamond,
|
||||||
|
},
|
||||||
|
upgrade_rebate: {
|
||||||
|
normal_to_gold_rebate: formData.upgrade_rebate.normal_to_gold_rebate,
|
||||||
|
to_diamond_rebate: formData.upgrade_rebate.to_diamond_rebate,
|
||||||
|
},
|
||||||
|
direct_parent_rebate: {
|
||||||
|
diamond: formData.direct_parent_rebate.diamond,
|
||||||
|
gold: formData.direct_parent_rebate.gold,
|
||||||
|
normal: formData.direct_parent_rebate.normal,
|
||||||
|
},
|
||||||
|
max_gold_rebate_amount: formData.max_gold_rebate_amount,
|
||||||
|
commission_freeze: {
|
||||||
|
ratio: formData.commission_freeze.ratio,
|
||||||
|
threshold: formData.commission_freeze.threshold,
|
||||||
|
days: formData.commission_freeze.days,
|
||||||
|
},
|
||||||
|
tax_rate: formData.tax_rate,
|
||||||
|
tax_exemption_amount: formData.tax_exemption_amount,
|
||||||
|
gold_max_uplift_amount: formData.gold_max_uplift_amount,
|
||||||
|
diamond_max_uplift_amount: formData.diamond_max_uplift_amount,
|
||||||
|
};
|
||||||
|
await updateAgentConfig(params);
|
||||||
|
message.success('配置保存成功');
|
||||||
|
loadConfig();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存配置失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置配置
|
||||||
|
function handleReset() {
|
||||||
|
if (config.value) {
|
||||||
|
Object.assign(formData, config.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadConfig();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Card title="系统配置" :loading="loading">
|
||||||
|
<Form layout="vertical">
|
||||||
|
<Card title="等级奖金" size="small" class="mb-4">
|
||||||
|
<Row :gutter="[16, 16]">
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['level_bonus', 'normal']" label="普通代理奖金">
|
||||||
|
<InputNumber v-model:value="formData.level_bonus.normal" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['level_bonus', 'gold']" label="黄金代理奖金">
|
||||||
|
<InputNumber v-model:value="formData.level_bonus.gold" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['level_bonus', 'diamond']" label="钻石代理奖金">
|
||||||
|
<InputNumber v-model:value="formData.level_bonus.diamond" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="等级最高价上调金额" size="small" class="mb-4">
|
||||||
|
<Row :gutter="[16, 16]">
|
||||||
|
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
|
<Form.Item name="gold_max_uplift_amount" label="黄金代理最高价上调金额">
|
||||||
|
<InputNumber v-model:value="formData.gold_max_uplift_amount" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
|
<Form.Item name="diamond_max_uplift_amount" label="钻石代理最高价上调金额">
|
||||||
|
<InputNumber v-model:value="formData.diamond_max_uplift_amount" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="升级费用" size="small" class="mb-4">
|
||||||
|
<Row :gutter="[16, 16]">
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['upgrade_fee', 'normal_to_gold']" label="普通→黄金">
|
||||||
|
<InputNumber v-model:value="formData.upgrade_fee.normal_to_gold" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['upgrade_fee', 'normal_to_diamond']" label="普通→钻石">
|
||||||
|
<InputNumber v-model:value="formData.upgrade_fee.normal_to_diamond" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="升级返佣" size="small" class="mb-4">
|
||||||
|
<Row :gutter="[16, 16]">
|
||||||
|
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
|
<Form.Item :name="['upgrade_rebate', 'normal_to_gold_rebate']" label="普通→黄金返佣">
|
||||||
|
<InputNumber v-model:value="formData.upgrade_rebate.normal_to_gold_rebate" :min="0" :precision="2"
|
||||||
|
:step="0.01" style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
|
<Form.Item :name="['upgrade_rebate', 'to_diamond_rebate']" label="→钻石返佣">
|
||||||
|
<InputNumber v-model:value="formData.upgrade_rebate.to_diamond_rebate" :min="0" :precision="2"
|
||||||
|
:step="0.01" style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="直接上级返佣配置" size="small" class="mb-4">
|
||||||
|
<Row :gutter="[16, 16]">
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['direct_parent_rebate', 'diamond']" label="直接上级是钻石的返佣金额">
|
||||||
|
<InputNumber v-model:value="formData.direct_parent_rebate.diamond" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['direct_parent_rebate', 'gold']" label="直接上级是黄金的返佣金额">
|
||||||
|
<InputNumber v-model:value="formData.direct_parent_rebate.gold" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['direct_parent_rebate', 'normal']" label="直接上级是普通的返佣金额">
|
||||||
|
<InputNumber v-model:value="formData.direct_parent_rebate.normal" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="返佣限额配置" size="small" class="mb-4">
|
||||||
|
<Row :gutter="[16, 16]">
|
||||||
|
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
|
<Form.Item name="max_gold_rebate_amount" label="黄金代理最大返佣金额">
|
||||||
|
<InputNumber v-model:value="formData.max_gold_rebate_amount" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="佣金冻结配置" size="small" class="mb-4">
|
||||||
|
<Row :gutter="[16, 16]">
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['commission_freeze', 'ratio']" label="佣金冻结比例(例如:0.1表示10%)">
|
||||||
|
<InputNumber v-model:value="formData.commission_freeze.ratio" :min="0" :max="1" :precision="4"
|
||||||
|
:step="0.0001" style="width: 100%" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['commission_freeze', 'threshold']" label="佣金冻结阈值">
|
||||||
|
<InputNumber v-model:value="formData.commission_freeze.threshold" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||||
|
<Form.Item :name="['commission_freeze', 'days']" label="佣金冻结解冻天数">
|
||||||
|
<InputNumber v-model:value="formData.commission_freeze.days" :min="0" :precision="0" :step="1"
|
||||||
|
style="width: 100%" addon-after="天" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="税费配置" size="small" class="mb-4">
|
||||||
|
<Row :gutter="[16, 16]">
|
||||||
|
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
|
<Form.Item name="tax_rate" label="税率(例如:0.06表示6%)">
|
||||||
|
<InputNumber v-model:value="formData.tax_rate" :min="0" :max="1" :precision="4" :step="0.0001"
|
||||||
|
style="width: 100%" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
|
<Form.Item name="tax_exemption_amount" label="免税额度">
|
||||||
|
<InputNumber v-model:value="formData.tax_exemption_amount" :min="0" :precision="2" :step="0.01"
|
||||||
|
style="width: 100%" addon-after="元" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" @click="handleSave">保存</Button>
|
||||||
|
<Button @click="handleReset">重置</Button>
|
||||||
|
</Space>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
109
apps/web-antd/src/views/agent/agent-invite-code/data.ts
Normal file
109
apps/web-antd/src/views/agent/agent-invite-code/data.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { getLevelName } from '#/utils/agent';
|
||||||
|
|
||||||
|
export function useInviteCodeColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'code',
|
||||||
|
title: '邀请码',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'agent_id',
|
||||||
|
title: '发放代理ID',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'agent_mobile',
|
||||||
|
title: '发放代理手机号',
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'target_level',
|
||||||
|
title: '目标等级',
|
||||||
|
width: 100,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||||
|
return getLevelName(cellValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
width: 100,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
options: [
|
||||||
|
{ value: 0, color: 'default', label: '未使用' },
|
||||||
|
{ value: 1, color: 'success', label: '已使用' },
|
||||||
|
{ value: 2, color: 'error', label: '已失效' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'used_user_id',
|
||||||
|
title: '使用用户ID',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'used_agent_id',
|
||||||
|
title: '使用代理ID',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'used_time',
|
||||||
|
title: '使用时间',
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'expire_time',
|
||||||
|
title: '过期时间',
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 160,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInviteCodeFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'code',
|
||||||
|
label: '邀请码',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'agent_id',
|
||||||
|
label: '发放代理ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '未使用', value: 0 },
|
||||||
|
{ label: '已使用', value: 1 },
|
||||||
|
{ label: '已失效', value: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
175
apps/web-antd/src/views/agent/agent-invite-code/list.vue
Normal file
175
apps/web-antd/src/views/agent/agent-invite-code/list.vue
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { AgentApi } from '#/api/agent';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button, Input, InputNumber, Modal, Space, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
generateDiamondInviteCode,
|
||||||
|
getInviteCodeList,
|
||||||
|
} from '#/api/agent';
|
||||||
|
|
||||||
|
import { useInviteCodeColumns, useInviteCodeFormSchema } from './data';
|
||||||
|
|
||||||
|
interface QueryParams {
|
||||||
|
currentPage: number;
|
||||||
|
pageSize: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateModalVisible = ref(false);
|
||||||
|
const generatedCodes = ref<string[]>([]);
|
||||||
|
const generateForm = ref({
|
||||||
|
count: 1,
|
||||||
|
expire_days: 0,
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useInviteCodeFormSchema(),
|
||||||
|
submitOnChange: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useInviteCodeColumns(),
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({
|
||||||
|
page,
|
||||||
|
form,
|
||||||
|
}: {
|
||||||
|
form: Record<string, any>;
|
||||||
|
page: QueryParams;
|
||||||
|
}) => {
|
||||||
|
return await getInviteCodeList({
|
||||||
|
...form,
|
||||||
|
target_level: 3,
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
result: 'items',
|
||||||
|
total: 'total',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成邀请码
|
||||||
|
function handleGenerate() {
|
||||||
|
generateForm.value = {
|
||||||
|
count: 1,
|
||||||
|
expire_days: 0,
|
||||||
|
remark: '',
|
||||||
|
};
|
||||||
|
generatedCodes.value = [];
|
||||||
|
generateModalVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认生成
|
||||||
|
async function confirmGenerate() {
|
||||||
|
try {
|
||||||
|
const res = await generateDiamondInviteCode({
|
||||||
|
count: generateForm.value.count,
|
||||||
|
expire_days:
|
||||||
|
generateForm.value.expire_days > 0
|
||||||
|
? generateForm.value.expire_days
|
||||||
|
: undefined,
|
||||||
|
remark: generateForm.value.remark || undefined,
|
||||||
|
});
|
||||||
|
generatedCodes.value = res.codes;
|
||||||
|
message.success('邀请码生成成功');
|
||||||
|
gridApi.query();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('生成邀请码失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制邀请码
|
||||||
|
function copyCodes() {
|
||||||
|
const text = generatedCodes.value.join('\n');
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
message.success('邀请码已复制到剪贴板');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
function closeGenerateModal() {
|
||||||
|
generateModalVisible.value = false;
|
||||||
|
generatedCodes.value = [];
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="邀请码列表">
|
||||||
|
<template #toolbar>
|
||||||
|
<Button type="primary" @click="handleGenerate">生成钻石邀请码</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Modal v-model:open="generateModalVisible" :title="generatedCodes.length > 0 ? '生成的邀请码' : '生成钻石邀请码'" :width="600"
|
||||||
|
@cancel="closeGenerateModal">
|
||||||
|
<div v-if="generatedCodes.length === 0">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label>生成数量:</label>
|
||||||
|
<InputNumber v-model:value="generateForm.count" :min="1" :max="100" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label>过期天数(0表示不过期):</label>
|
||||||
|
<InputNumber v-model:value="generateForm.expire_days" :min="0" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label>备注:</label>
|
||||||
|
<Input.TextArea v-model:value="generateForm.remark" :rows="3" placeholder="请输入备注" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="mb-4">
|
||||||
|
<strong>已生成 {{ generatedCodes.length }} 个邀请码:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="code-list">
|
||||||
|
<div v-for="(code, index) in generatedCodes" :key="index" class="code-item">
|
||||||
|
{{ code }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<Button v-if="generatedCodes.length > 0" @click="closeGenerateModal">
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
<Button v-else @click="closeGenerateModal">取消</Button>
|
||||||
|
<Button v-if="generatedCodes.length > 0" type="primary" @click="copyCodes">
|
||||||
|
复制所有
|
||||||
|
</Button>
|
||||||
|
<Button v-else type="primary" @click="confirmGenerate">生成</Button>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.code-list {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-item {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-family: monospace;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -9,14 +9,25 @@ export function useLinkColumns(): VxeTableGridOptions['columns'] {
|
|||||||
title: '代理ID',
|
title: '代理ID',
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'product_id',
|
||||||
|
title: '产品ID',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'product_name',
|
field: 'product_name',
|
||||||
title: '产品名称',
|
title: '产品名称',
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'price',
|
field: 'set_price',
|
||||||
title: '价格',
|
title: '设定价格',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actual_base_price',
|
||||||
|
title: '实际底价',
|
||||||
width: 120,
|
width: 120,
|
||||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||||
},
|
},
|
||||||
@@ -37,6 +48,11 @@ export function useLinkColumns(): VxeTableGridOptions['columns'] {
|
|||||||
// 推广链接搜索表单配置
|
// 推广链接搜索表单配置
|
||||||
export function useLinkFormSchema(): VbenFormSchema[] {
|
export function useLinkFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'product_id',
|
||||||
|
label: '产品ID',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
fieldName: 'product_name',
|
fieldName: 'product_name',
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { getLevelName } from '#/utils/agent';
|
||||||
|
|
||||||
// 表单配置
|
// 表单配置
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -11,10 +13,18 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'Select',
|
||||||
fieldName: 'level_name',
|
fieldName: 'level',
|
||||||
label: '等级名称',
|
label: '等级',
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
|
componentProps: {
|
||||||
|
disabled: true,
|
||||||
|
options: [
|
||||||
|
{ label: '普通代理', value: 1 },
|
||||||
|
{ label: '黄金代理', value: 2 },
|
||||||
|
{ label: '钻石代理', value: 3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
@@ -23,13 +33,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'DatePicker',
|
component: 'Input',
|
||||||
fieldName: 'membership_expiry_time',
|
fieldName: 'wechat_id',
|
||||||
label: '会员到期时间',
|
label: '微信号',
|
||||||
rules: 'required',
|
|
||||||
componentProps: {
|
|
||||||
showTime: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -47,6 +53,24 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
fieldName: 'region',
|
fieldName: 'region',
|
||||||
label: '区域',
|
label: '区域',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'level',
|
||||||
|
label: '等级',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '普通代理', value: 1 },
|
||||||
|
{ label: '黄金代理', value: 2 },
|
||||||
|
{ label: '钻石代理', value: 3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'team_leader_id',
|
||||||
|
label: '团队首领ID',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: 'RangePicker',
|
component: 'RangePicker',
|
||||||
fieldName: 'create_time',
|
fieldName: 'create_time',
|
||||||
@@ -71,14 +95,16 @@ export function useColumns(): VxeTableGridOptions['columns'] {
|
|||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'level_name',
|
field: 'agent_code',
|
||||||
title: '等级名称',
|
title: '代理编码',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'level',
|
||||||
|
title: '等级',
|
||||||
width: 120,
|
width: 120,
|
||||||
formatter: ({ cellValue }: { cellValue: string }) => {
|
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||||
if (cellValue === '' || cellValue === 'normal') {
|
return getLevelName(cellValue);
|
||||||
return '普通代理';
|
|
||||||
}
|
|
||||||
return cellValue;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -95,41 +121,25 @@ export function useColumns(): VxeTableGridOptions['columns'] {
|
|||||||
cellRender: {
|
cellRender: {
|
||||||
name: 'CellTag',
|
name: 'CellTag',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'approved', color: 'success', label: '已认证' },
|
{ value: true, color: 'success', label: '已认证' },
|
||||||
{ value: 'pending', color: 'warning', label: '审核中' },
|
{ value: false, color: 'default', label: '未认证' },
|
||||||
{ value: 'rejected', color: 'error', label: '已拒绝' },
|
|
||||||
{ value: '', color: 'default', label: '未认证' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
field: 'real_name_status',
|
field: 'is_real_name',
|
||||||
title: '实名认证状态',
|
title: '实名认证状态',
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'real_name',
|
field: 'wechat_id',
|
||||||
title: '实名姓名',
|
title: '微信号',
|
||||||
width: 120,
|
width: 120,
|
||||||
formatter: ({ cellValue }: { cellValue: string }) => {
|
visible: false,
|
||||||
if (!cellValue) return '-';
|
|
||||||
return cellValue;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'id_card',
|
field: 'team_leader_id',
|
||||||
title: '身份证号',
|
title: '团队首领ID',
|
||||||
width: 180,
|
width: 120,
|
||||||
formatter: ({ cellValue }: { cellValue: string }) => {
|
visible: false,
|
||||||
if (!cellValue) return '-';
|
|
||||||
// 只显示前6位和后4位,中间用*代替
|
|
||||||
return `${cellValue.slice(0, 6)}${'*'.repeat(8)}${cellValue.slice(-4)}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'membership_expiry_time',
|
|
||||||
title: '会员到期时间',
|
|
||||||
width: 160,
|
|
||||||
sortable: true,
|
|
||||||
sortType: 'string' as const,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'balance',
|
field: 'balance',
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|||||||
import { getAgentList } from '#/api/agent';
|
import { getAgentList } from '#/api/agent';
|
||||||
|
|
||||||
import { useColumns, useGridFormSchema } from './data';
|
import { useColumns, useGridFormSchema } from './data';
|
||||||
import CommissionDeductionModal from './modules/commission-deduction-modal.vue';
|
|
||||||
import CommissionModal from './modules/commission-modal.vue';
|
import CommissionModal from './modules/commission-modal.vue';
|
||||||
import Form from './modules/form.vue';
|
import Form from './modules/form.vue';
|
||||||
import LinkModal from './modules/link-modal.vue';
|
import LinkModal from './modules/link-modal.vue';
|
||||||
import PlatformDeductionModal from './modules/platform-deduction-modal.vue';
|
import OrderModal from './modules/order-modal.vue';
|
||||||
import RewardModal from './modules/reward-modal.vue';
|
import RebateModal from './modules/rebate-modal.vue';
|
||||||
|
import UpgradeModal from './modules/upgrade-modal.vue';
|
||||||
import WithdrawalModal from './modules/withdrawal-modal.vue';
|
import WithdrawalModal from './modules/withdrawal-modal.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -46,9 +46,21 @@ const [CommissionModalComponent, commissionModalApi] = useVbenModal({
|
|||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 奖励记录弹窗
|
// 返佣记录弹窗
|
||||||
const [RewardModalComponent, rewardModalApi] = useVbenModal({
|
const [RebateModalComponent, rebateModalApi] = useVbenModal({
|
||||||
connectedComponent: RewardModal,
|
connectedComponent: RebateModal,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 升级记录弹窗
|
||||||
|
const [UpgradeModalComponent, upgradeModalApi] = useVbenModal({
|
||||||
|
connectedComponent: UpgradeModal,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 订单记录弹窗
|
||||||
|
const [OrderModalComponent, orderModalApi] = useVbenModal({
|
||||||
|
connectedComponent: OrderModal,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,20 +70,6 @@ const [WithdrawalModalComponent, withdrawalModalApi] = useVbenModal({
|
|||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 上级抽佣弹窗
|
|
||||||
const [CommissionDeductionModalComponent, commissionDeductionModalApi] =
|
|
||||||
useVbenModal({
|
|
||||||
connectedComponent: CommissionDeductionModal,
|
|
||||||
destroyOnClose: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 平台抽佣弹窗
|
|
||||||
const [PlatformDeductionModalComponent, platformDeductionModalApi] =
|
|
||||||
useVbenModal({
|
|
||||||
connectedComponent: PlatformDeductionModal,
|
|
||||||
destroyOnClose: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置
|
// 表格配置
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
@@ -102,9 +100,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
query: async ({ page, sort }, formValues) => {
|
query: async ({ page, sort }, formValues) => {
|
||||||
const sortParams = sort
|
const sortParams = sort
|
||||||
? {
|
? {
|
||||||
order_by: sort.field,
|
order_by: sort.field,
|
||||||
order_type: sort.order,
|
order_type: sort.order,
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
const res = await getAgentList({
|
const res = await getAgentList({
|
||||||
@@ -112,8 +110,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
pageSize: page.pageSize,
|
pageSize: page.pageSize,
|
||||||
...formValues,
|
...formValues,
|
||||||
...sortParams,
|
...sortParams,
|
||||||
parent_agent_id: route.query.parent_agent_id
|
team_leader_id: route.query.team_leader_id
|
||||||
? Number(route.query.parent_agent_id)
|
? Number(route.query.team_leader_id)
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,21 +146,17 @@ const moreMenuItems = [
|
|||||||
key: 'links',
|
key: 'links',
|
||||||
label: '推广链接',
|
label: '推广链接',
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// key: 'commission',
|
|
||||||
// label: '佣金记录',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'commission-deduction',
|
|
||||||
// label: '上级抽佣',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// key: 'platform-deduction',
|
|
||||||
// label: '平台抽佣',
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
key: 'reward',
|
key: 'rebate',
|
||||||
label: '奖励记录',
|
label: '返佣记录',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'upgrade',
|
||||||
|
label: '升级记录',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'order',
|
||||||
|
label: '订单记录',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'withdrawal',
|
key: 'withdrawal',
|
||||||
@@ -170,15 +164,15 @@ const moreMenuItems = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 上级代理信息
|
// 团队首领信息
|
||||||
const parentAgentId = computed(() => route.query.parent_agent_id);
|
const teamLeaderId = computed(() => route.query.team_leader_id);
|
||||||
|
|
||||||
// 返回上级列表
|
// 返回团队首领列表
|
||||||
function onBackToParent() {
|
function onBackToParent() {
|
||||||
router.replace({
|
router.replace({
|
||||||
query: {
|
query: {
|
||||||
...route.query,
|
...route.query,
|
||||||
parent_agent_id: undefined,
|
team_leader_id: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -194,10 +188,6 @@ function onActionClick(
|
|||||||
onViewCommission(e.row);
|
onViewCommission(e.row);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'commission-deduction': {
|
|
||||||
onViewCommissionDeduction(e.row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
case 'edit': {
|
||||||
onEdit(e.row);
|
onEdit(e.row);
|
||||||
break;
|
break;
|
||||||
@@ -206,19 +196,23 @@ function onActionClick(
|
|||||||
onViewLinks(e.row);
|
onViewLinks(e.row);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'platform-deduction': {
|
case 'rebate': {
|
||||||
onViewPlatformDeduction(e.row);
|
onViewRebate(e.row);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'reward': {
|
case 'upgrade': {
|
||||||
onViewReward(e.row);
|
onViewUpgrade(e.row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'order': {
|
||||||
|
onViewOrder(e.row);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'view-sub-agent': {
|
case 'view-sub-agent': {
|
||||||
router.replace({
|
router.replace({
|
||||||
query: {
|
query: {
|
||||||
...route.query,
|
...route.query,
|
||||||
parent_agent_id: e.row.id,
|
team_leader_id: e.row.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -245,9 +239,19 @@ function onViewCommission(row: AgentApi.AgentListItem) {
|
|||||||
commissionModalApi.setData({ agentId: row.id }).open();
|
commissionModalApi.setData({ agentId: row.id }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看奖励记录
|
// 查看返佣记录
|
||||||
function onViewReward(row: AgentApi.AgentListItem) {
|
function onViewRebate(row: AgentApi.AgentListItem) {
|
||||||
rewardModalApi.setData({ agentId: row.id }).open();
|
rebateModalApi.setData({ agentId: row.id }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看升级记录
|
||||||
|
function onViewUpgrade(row: AgentApi.AgentListItem) {
|
||||||
|
upgradeModalApi.setData({ agentId: row.id }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看订单记录
|
||||||
|
function onViewOrder(row: AgentApi.AgentListItem) {
|
||||||
|
orderModalApi.setData({ agentId: row.id }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看提现记录
|
// 查看提现记录
|
||||||
@@ -255,16 +259,6 @@ function onViewWithdrawal(row: AgentApi.AgentListItem) {
|
|||||||
withdrawalModalApi.setData({ agentId: row.id }).open();
|
withdrawalModalApi.setData({ agentId: row.id }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看上级抽佣记录
|
|
||||||
function onViewCommissionDeduction(row: AgentApi.AgentListItem) {
|
|
||||||
commissionDeductionModalApi.setData({ agentId: row.id }).open();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看平台抽佣记录
|
|
||||||
function onViewPlatformDeduction(row: AgentApi.AgentListItem) {
|
|
||||||
platformDeductionModalApi.setData({ agentId: row.id }).open();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新处理
|
// 刷新处理
|
||||||
function onRefresh() {
|
function onRefresh() {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
@@ -276,16 +270,16 @@ function onRefresh() {
|
|||||||
<FormDrawer @success="onRefresh" />
|
<FormDrawer @success="onRefresh" />
|
||||||
<LinkModalComponent />
|
<LinkModalComponent />
|
||||||
<CommissionModalComponent />
|
<CommissionModalComponent />
|
||||||
<CommissionDeductionModalComponent />
|
<RebateModalComponent />
|
||||||
<PlatformDeductionModalComponent />
|
<UpgradeModalComponent />
|
||||||
<RewardModalComponent />
|
<OrderModalComponent />
|
||||||
<WithdrawalModalComponent />
|
<WithdrawalModalComponent />
|
||||||
|
|
||||||
<!-- 上级代理信息卡片 -->
|
<!-- 团队首领信息卡片 -->
|
||||||
<Card v-if="parentAgentId" class="mb-4">
|
<Card v-if="teamLeaderId" class="mb-4">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<Button @click="onBackToParent">返回上级列表</Button>
|
<Button @click="onBackToParent">返回上级列表</Button>
|
||||||
<div>上级代理ID:{{ parentAgentId }}</div>
|
<div>团队首领ID:{{ teamLeaderId }}</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -295,10 +289,7 @@ function onRefresh() {
|
|||||||
<Button type="link" @click="onActionClick({ code: 'edit', row })">
|
<Button type="link" @click="onActionClick({ code: 'edit', row })">
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button type="link" @click="onActionClick({ code: 'view-sub-agent', row })">
|
||||||
type="link"
|
|
||||||
@click="onActionClick({ code: 'view-sub-agent', row })"
|
|
||||||
>
|
|
||||||
查看下级
|
查看下级
|
||||||
</Button>
|
</Button>
|
||||||
<!-- <Button
|
<!-- <Button
|
||||||
@@ -310,10 +301,7 @@ function onRefresh() {
|
|||||||
<Dropdown>
|
<Dropdown>
|
||||||
<Button type="link">更多操作</Button>
|
<Button type="link">更多操作</Button>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<Menu
|
<Menu :items="moreMenuItems" @click="(e) => onActionClick({ code: String(e.key), row })" />
|
||||||
:items="moreMenuItems"
|
|
||||||
@click="(e) => onActionClick({ code: String(e.key), row })"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { computed } from 'vue';
|
|||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import RewardList from '../../agent-reward/list.vue';
|
import OrderList from '../../agent-order/list.vue';
|
||||||
|
|
||||||
interface ModalData {
|
interface ModalData {
|
||||||
agentId: number;
|
agentId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
title: '奖励记录',
|
title: '订单记录',
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -19,14 +19,15 @@ const modalData = computed(() => modalApi.getData<ModalData>());
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
||||||
<div class="agent-reward-modal">
|
<div class="agent-order-modal">
|
||||||
<RewardList :agent-id="modalData?.agentId" />
|
<OrderList :agent-id="modalData?.agentId" />
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.agent-reward-modal {
|
.agent-order-modal {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -3,14 +3,14 @@ import { computed } from 'vue';
|
|||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import CommissionDeductionList from '../../agent-commission-deduction/list.vue';
|
import RebateList from '../../agent-rebate/list.vue';
|
||||||
|
|
||||||
interface ModalData {
|
interface ModalData {
|
||||||
agentId: number;
|
agentId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
title: '上级抽佣记录',
|
title: '返佣记录',
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -19,14 +19,15 @@ const modalData = computed(() => modalApi.getData<ModalData>());
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
||||||
<div class="agent-commission-deduction-modal">
|
<div class="agent-rebate-modal">
|
||||||
<CommissionDeductionList :agent-id="modalData?.agentId" />
|
<RebateList :agent-id="modalData?.agentId" />
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.agent-commission-deduction-modal {
|
.agent-rebate-modal {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -3,15 +3,14 @@ import { computed } from 'vue';
|
|||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import PlatformDeductionList from '../../agent-platform-deduction/list.vue';
|
import UpgradeList from '../../agent-upgrade/list.vue';
|
||||||
|
|
||||||
interface ModalData {
|
interface ModalData {
|
||||||
agentId: number;
|
agentId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
title: '平台抽佣记录',
|
title: '升级记录',
|
||||||
width: 1000,
|
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -19,13 +18,16 @@ const modalData = computed(() => modalApi.getData<ModalData>());
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal>
|
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
||||||
<PlatformDeductionList v-if="modalData" :agent-id="modalData.agentId" />
|
<div class="agent-upgrade-modal">
|
||||||
|
<UpgradeList :agent-id="modalData?.agentId" />
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="less" scoped>
|
||||||
:deep(.ant-modal-body) {
|
.agent-upgrade-modal {
|
||||||
padding: 24px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
|
|
||||||
// 会员等级选项
|
|
||||||
export const levelNameOptions = [
|
|
||||||
{ label: '普通会员', value: 'normal' },
|
|
||||||
{ label: 'VIP会员', value: 'VIP' },
|
|
||||||
{ label: 'SVIP会员', value: 'SVIP' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 代理会员配置列表列配置
|
|
||||||
export function useColumns(): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
|
||||||
{ field: 'id', title: 'ID', width: 80 },
|
|
||||||
{
|
|
||||||
field: 'level_name',
|
|
||||||
title: '会员等级',
|
|
||||||
formatter: ({ cellValue }) => {
|
|
||||||
const option = levelNameOptions.find(
|
|
||||||
(item) => item.value === cellValue,
|
|
||||||
);
|
|
||||||
return option?.label || cellValue;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'price',
|
|
||||||
title: '会员年费',
|
|
||||||
formatter: ({ cellValue }) =>
|
|
||||||
cellValue !== null && cellValue !== undefined
|
|
||||||
? `¥${cellValue.toFixed(2)}`
|
|
||||||
: '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'report_commission',
|
|
||||||
title: '直推报告收益',
|
|
||||||
formatter: ({ cellValue }) =>
|
|
||||||
cellValue !== null && cellValue !== undefined
|
|
||||||
? `¥${cellValue.toFixed(2)}`
|
|
||||||
: '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'lower_activity_reward',
|
|
||||||
title: '下级活跃奖励',
|
|
||||||
formatter: ({ cellValue }) =>
|
|
||||||
cellValue !== null && cellValue !== undefined
|
|
||||||
? `¥${cellValue.toFixed(2)}`
|
|
||||||
: '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'new_activity_reward',
|
|
||||||
title: '新增活跃奖励',
|
|
||||||
formatter: ({ cellValue }) =>
|
|
||||||
cellValue !== null && cellValue !== undefined
|
|
||||||
? `¥${cellValue.toFixed(2)}`
|
|
||||||
: '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'lower_standard_count',
|
|
||||||
title: '活跃下级达标数',
|
|
||||||
formatter: ({ cellValue }) => cellValue ?? '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'new_lower_standard_count',
|
|
||||||
title: '新增活跃下级达标数',
|
|
||||||
formatter: ({ cellValue }) => cellValue ?? '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'lower_withdraw_reward_ratio',
|
|
||||||
title: '下级提现奖励比例',
|
|
||||||
formatter: ({ cellValue }) =>
|
|
||||||
cellValue !== null && cellValue !== undefined
|
|
||||||
? `${(cellValue * 100).toFixed(2)}%`
|
|
||||||
: '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'create_time',
|
|
||||||
title: '创建时间',
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'center',
|
|
||||||
slots: { default: 'operation' },
|
|
||||||
field: 'operation',
|
|
||||||
fixed: 'right',
|
|
||||||
title: '操作',
|
|
||||||
width: 120,
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 代理会员配置搜索表单配置
|
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'Select',
|
|
||||||
fieldName: 'level_name',
|
|
||||||
label: '会员等级',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: '请选择会员等级',
|
|
||||||
options: levelNameOptions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 代理会员配置编辑表单配置
|
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'Select',
|
|
||||||
fieldName: 'level_name',
|
|
||||||
label: '会员等级',
|
|
||||||
rules: 'required',
|
|
||||||
componentProps: {
|
|
||||||
options: levelNameOptions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'price',
|
|
||||||
label: '会员年费',
|
|
||||||
rules: 'required',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'report_commission',
|
|
||||||
label: '直推报告收益',
|
|
||||||
rules: 'required',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'lower_activity_reward',
|
|
||||||
label: '下级活跃奖励',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'new_activity_reward',
|
|
||||||
label: '新增活跃奖励',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'lower_standard_count',
|
|
||||||
label: '活跃下级达标数',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'new_lower_standard_count',
|
|
||||||
label: '新增活跃下级达标数',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'lower_withdraw_reward_ratio',
|
|
||||||
label: '下级提现奖励比例',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
addonAfter: '%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'lower_convert_vip_reward',
|
|
||||||
label: '下级转化VIP奖励',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'lower_convert_svip_reward',
|
|
||||||
label: '下级转化SVIP奖励',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'exemption_amount',
|
|
||||||
label: '免责金额',
|
|
||||||
rules: 'required',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'price_increase_max',
|
|
||||||
label: '提价最高金额',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'price_ratio',
|
|
||||||
label: '提价区间收取比例',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
addonAfter: '%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'price_increase_amount',
|
|
||||||
label: '加价金额',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type {
|
|
||||||
OnActionClickParams,
|
|
||||||
VxeGridListeners,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { AgentApi } from '#/api/agent';
|
|
||||||
|
|
||||||
import { Page, useVbenDrawer } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { Button, Space } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { getAgentMembershipConfigList } from '#/api/agent';
|
|
||||||
|
|
||||||
import { useColumns, useGridFormSchema } from './data';
|
|
||||||
import Form from './modules/form.vue';
|
|
||||||
|
|
||||||
// 表单抽屉
|
|
||||||
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
|
||||||
connectedComponent: Form,
|
|
||||||
destroyOnClose: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
|
||||||
formOptions: {
|
|
||||||
schema: useGridFormSchema(),
|
|
||||||
submitOnChange: true,
|
|
||||||
},
|
|
||||||
gridEvents: {
|
|
||||||
sortChange: () => {
|
|
||||||
gridApi.query();
|
|
||||||
},
|
|
||||||
} as VxeGridListeners<AgentApi.AgentMembershipConfigListItem>,
|
|
||||||
gridOptions: {
|
|
||||||
columns: useColumns(),
|
|
||||||
height: 'auto',
|
|
||||||
keepSource: true,
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async ({ page }, formValues) => {
|
|
||||||
const res = await getAgentMembershipConfigList({
|
|
||||||
page: page.currentPage,
|
|
||||||
pageSize: page.pageSize,
|
|
||||||
level_name: formValues.level_name,
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
result: 'items',
|
|
||||||
total: 'total',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
custom: true,
|
|
||||||
export: false,
|
|
||||||
refresh: { code: 'query' },
|
|
||||||
search: true,
|
|
||||||
zoom: true,
|
|
||||||
},
|
|
||||||
} as VxeTableGridOptions<AgentApi.AgentMembershipConfigListItem>,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 操作处理函数
|
|
||||||
function onActionClick(
|
|
||||||
e: OnActionClickParams<AgentApi.AgentMembershipConfigListItem>,
|
|
||||||
) {
|
|
||||||
switch (e.code) {
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(e.row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑处理
|
|
||||||
function onEdit(row: AgentApi.AgentMembershipConfigListItem) {
|
|
||||||
formDrawerApi.setData(row).open();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新处理
|
|
||||||
function onRefresh() {
|
|
||||||
gridApi.query();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Page auto-content-height>
|
|
||||||
<FormDrawer @success="onRefresh" />
|
|
||||||
<Grid table-title="代理会员配置列表">
|
|
||||||
<template #operation="{ row }">
|
|
||||||
<Space>
|
|
||||||
<Button type="link" @click="onActionClick({ code: 'edit', row })">
|
|
||||||
配置
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</template>
|
|
||||||
</Grid>
|
|
||||||
</Page>
|
|
||||||
</template>
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { AgentApi } from '#/api/agent';
|
|
||||||
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { updateAgentMembershipConfig } from '#/api/agent';
|
|
||||||
|
|
||||||
import { useFormSchema } from '../data';
|
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
|
||||||
|
|
||||||
const formData = ref<AgentApi.AgentMembershipConfigListItem>();
|
|
||||||
const id = ref<number>();
|
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
|
||||||
schema: useFormSchema(),
|
|
||||||
showDefaultActions: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const drawerTitle = ref('会员配置');
|
|
||||||
|
|
||||||
const [Drawer, drawerApi] = useVbenDrawer({
|
|
||||||
title: drawerTitle.value,
|
|
||||||
destroyOnClose: true,
|
|
||||||
async onConfirm() {
|
|
||||||
const valid = await formApi.validate();
|
|
||||||
if (!valid || !id.value) return;
|
|
||||||
|
|
||||||
const values = await formApi.getValues();
|
|
||||||
const params: AgentApi.UpdateAgentMembershipConfigParams = {
|
|
||||||
id: id.value,
|
|
||||||
level_name: values.level_name,
|
|
||||||
price: values.price,
|
|
||||||
report_commission: values.report_commission,
|
|
||||||
lower_activity_reward: values.lower_activity_reward ?? null,
|
|
||||||
new_activity_reward: values.new_activity_reward ?? null,
|
|
||||||
lower_standard_count: values.lower_standard_count ?? null,
|
|
||||||
new_lower_standard_count: values.new_lower_standard_count ?? null,
|
|
||||||
lower_withdraw_reward_ratio:
|
|
||||||
values.lower_withdraw_reward_ratio !== null &&
|
|
||||||
values.lower_withdraw_reward_ratio !== undefined
|
|
||||||
? values.lower_withdraw_reward_ratio / 100
|
|
||||||
: null,
|
|
||||||
lower_convert_vip_reward: values.lower_convert_vip_reward ?? null,
|
|
||||||
lower_convert_svip_reward: values.lower_convert_svip_reward ?? null,
|
|
||||||
exemption_amount: values.exemption_amount ?? null,
|
|
||||||
price_increase_max: values.price_increase_max ?? null,
|
|
||||||
price_ratio:
|
|
||||||
values.price_ratio !== null && values.price_ratio !== undefined
|
|
||||||
? values.price_ratio / 100
|
|
||||||
: null,
|
|
||||||
price_increase_amount: values.price_increase_amount ?? null,
|
|
||||||
};
|
|
||||||
|
|
||||||
await updateAgentMembershipConfig(params);
|
|
||||||
|
|
||||||
emit('success');
|
|
||||||
drawerApi.close();
|
|
||||||
},
|
|
||||||
onOpenChange(isOpen) {
|
|
||||||
if (isOpen) {
|
|
||||||
const data = drawerApi.getData<AgentApi.AgentMembershipConfigListItem>();
|
|
||||||
formApi.resetForm();
|
|
||||||
if (data) {
|
|
||||||
formData.value = data;
|
|
||||||
id.value = data.id;
|
|
||||||
formApi.setValues({
|
|
||||||
...data,
|
|
||||||
lower_withdraw_reward_ratio: data.lower_withdraw_reward_ratio
|
|
||||||
? data.lower_withdraw_reward_ratio * 100
|
|
||||||
: null,
|
|
||||||
price_ratio: data.price_ratio ? data.price_ratio * 100 : null,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
id.value = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Drawer :title="drawerTitle">
|
|
||||||
<Form />
|
|
||||||
</Drawer>
|
|
||||||
</template>
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
|
|
||||||
// 支付方式选项
|
|
||||||
export const paymentMethodOptions = [
|
|
||||||
{ label: '支付宝', value: 'alipay' },
|
|
||||||
{ label: '微信', value: 'wechat' },
|
|
||||||
{ label: '苹果支付', value: 'appleiap' },
|
|
||||||
{ label: '其他', value: 'other' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 会员等级选项
|
|
||||||
export const levelNameOptions = [
|
|
||||||
{ label: '普通会员', value: '' },
|
|
||||||
{ label: 'VIP会员', value: 'VIP' },
|
|
||||||
{ label: 'SVIP会员', value: 'SVIP' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 状态选项
|
|
||||||
export const statusOptions = [
|
|
||||||
{ label: '待支付', value: 'pending' },
|
|
||||||
{ label: '支付成功', value: 'success' },
|
|
||||||
{ label: '支付失败', value: 'failed' },
|
|
||||||
{ label: '已取消', value: 'cancelled' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 列表列配置
|
|
||||||
export function useMembershipRechargeOrderColumns(): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
|
||||||
{ field: 'id', title: 'ID', width: 80 },
|
|
||||||
{ field: 'user_id', title: '用户ID', width: 100 },
|
|
||||||
{ field: 'agent_id', title: '代理ID', width: 100 },
|
|
||||||
{
|
|
||||||
field: 'level_name',
|
|
||||||
title: '会员等级',
|
|
||||||
width: 100,
|
|
||||||
formatter: ({ cellValue }) => {
|
|
||||||
const option = levelNameOptions.find(
|
|
||||||
(item) => item.value === cellValue,
|
|
||||||
);
|
|
||||||
return option?.label || '普通会员';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'amount',
|
|
||||||
title: '金额',
|
|
||||||
width: 100,
|
|
||||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'payment_method',
|
|
||||||
title: '支付方式',
|
|
||||||
width: 100,
|
|
||||||
formatter: ({ cellValue }) => {
|
|
||||||
const option = paymentMethodOptions.find(
|
|
||||||
(item) => item.value === cellValue,
|
|
||||||
);
|
|
||||||
return option?.label || cellValue;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ field: 'order_no', title: '订单号', width: 180 },
|
|
||||||
{ field: 'platform_order_id', title: '平台订单号', width: 180 },
|
|
||||||
{
|
|
||||||
field: 'status',
|
|
||||||
title: '状态',
|
|
||||||
width: 100,
|
|
||||||
formatter: ({ cellValue }) => {
|
|
||||||
const option = statusOptions.find((item) => item.value === cellValue);
|
|
||||||
return option?.label || cellValue;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'create_time',
|
|
||||||
title: '创建时间',
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索表单配置
|
|
||||||
export function useMembershipRechargeOrderFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'user_id',
|
|
||||||
label: '用户ID',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: '请输入用户ID',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'agent_id',
|
|
||||||
label: '代理ID',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: '请输入代理ID',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Input',
|
|
||||||
fieldName: 'order_no',
|
|
||||||
label: '订单号',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: '请输入订单号',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Input',
|
|
||||||
fieldName: 'platform_order_id',
|
|
||||||
label: '平台订单号',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: '请输入平台订单号',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Select',
|
|
||||||
fieldName: 'status',
|
|
||||||
label: '状态',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: '请选择状态',
|
|
||||||
options: statusOptions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Select',
|
|
||||||
fieldName: 'payment_method',
|
|
||||||
label: '支付方式',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: '请选择支付方式',
|
|
||||||
options: paymentMethodOptions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
|
||||||
import type { AgentApi } from '#/api/agent/agent';
|
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { getMembershipRechargeOrderList } from '#/api/agent';
|
|
||||||
|
|
||||||
import {
|
|
||||||
useMembershipRechargeOrderColumns,
|
|
||||||
useMembershipRechargeOrderFormSchema,
|
|
||||||
} from './data';
|
|
||||||
|
|
||||||
const [Grid, _gridApi] = useVbenVxeGrid({
|
|
||||||
formOptions: {
|
|
||||||
schema: useMembershipRechargeOrderFormSchema(),
|
|
||||||
submitOnChange: true,
|
|
||||||
},
|
|
||||||
gridOptions: {
|
|
||||||
columns: useMembershipRechargeOrderColumns(),
|
|
||||||
height: 'auto',
|
|
||||||
keepSource: true,
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async (
|
|
||||||
{ page }: { page: { currentPage: number; pageSize: number } },
|
|
||||||
formValues: Record<string, any>,
|
|
||||||
) => {
|
|
||||||
const res = await getMembershipRechargeOrderList({
|
|
||||||
page: page.currentPage,
|
|
||||||
pageSize: page.pageSize,
|
|
||||||
...formValues,
|
|
||||||
});
|
|
||||||
return { items: res.items, total: res.total };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
custom: true,
|
|
||||||
export: false,
|
|
||||||
refresh: { code: 'query' },
|
|
||||||
search: true,
|
|
||||||
zoom: true,
|
|
||||||
},
|
|
||||||
} as VxeGridProps<AgentApi.MembershipRechargeOrderListItem>,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Page auto-content-height>
|
|
||||||
<Grid table-title="会员充值订单列表" />
|
|
||||||
</Page>
|
|
||||||
</template>
|
|
||||||
120
apps/web-antd/src/views/agent/agent-order/data.ts
Normal file
120
apps/web-antd/src/views/agent/agent-order/data.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
export function useOrderColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'agent_id',
|
||||||
|
title: '代理ID',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'order_id',
|
||||||
|
title: '订单ID',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'product_id',
|
||||||
|
title: '产品ID',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'product_name',
|
||||||
|
title: '产品名称',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'order_amount',
|
||||||
|
title: '订单金额',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
|
`¥${cellValue.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'set_price',
|
||||||
|
title: '设定价格',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
|
`¥${cellValue.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actual_base_price',
|
||||||
|
title: '实际底价',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
|
`¥${cellValue.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'price_cost',
|
||||||
|
title: '提价成本',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
|
`¥${cellValue.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'agent_profit',
|
||||||
|
title: '代理收益',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
|
`¥${cellValue.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'order_status',
|
||||||
|
title: '订单状态',
|
||||||
|
width: 120,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
options: [
|
||||||
|
{ value: 'pending', color: 'warning', label: '待支付' },
|
||||||
|
{ value: 'paid', color: 'success', label: '已支付' },
|
||||||
|
{ value: 'refunded', color: 'error', label: '已退款' },
|
||||||
|
{ value: 'closed', color: 'default', label: '已关闭' },
|
||||||
|
{ value: 'failed', color: 'error', label: '支付失败' },
|
||||||
|
{ value: 'unknown', color: 'default', label: '未知' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 160,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOrderFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'agent_id',
|
||||||
|
label: '代理ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'order_id',
|
||||||
|
label: '订单ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'order_status',
|
||||||
|
label: '订单状态',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '待支付', value: 'pending' },
|
||||||
|
{ label: '已支付', value: 'paid' },
|
||||||
|
{ label: '已退款', value: 'refunded' },
|
||||||
|
{ label: '已关闭', value: 'closed' },
|
||||||
|
{ label: '支付失败', value: 'failed' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,12 +4,9 @@ import { computed } from 'vue';
|
|||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getAgentPlatformDeductionList } from '#/api/agent';
|
import { getAgentOrderList } from '#/api/agent';
|
||||||
|
|
||||||
import {
|
import { useOrderColumns, useOrderFormSchema } from './data';
|
||||||
usePlatformDeductionColumns,
|
|
||||||
usePlatformDeductionFormSchema,
|
|
||||||
} from './data';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
agentId?: number;
|
agentId?: number;
|
||||||
@@ -29,21 +26,21 @@ const queryParams = computed(() => ({
|
|||||||
|
|
||||||
const [Grid] = useVbenVxeGrid({
|
const [Grid] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: usePlatformDeductionFormSchema(),
|
schema: useOrderFormSchema(),
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: usePlatformDeductionColumns(),
|
columns: useOrderColumns(),
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({
|
query: async ({
|
||||||
form,
|
|
||||||
page,
|
page,
|
||||||
|
form,
|
||||||
}: {
|
}: {
|
||||||
form: Record<string, any>;
|
form: Record<string, any>;
|
||||||
page: QueryParams;
|
page: QueryParams;
|
||||||
}) => {
|
}) => {
|
||||||
return await getAgentPlatformDeductionList({
|
return await getAgentOrderList({
|
||||||
...queryParams.value,
|
...queryParams.value,
|
||||||
...form,
|
...form,
|
||||||
page: page.currentPage,
|
page: page.currentPage,
|
||||||
@@ -62,6 +59,6 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page :auto-content-height="!agentId">
|
<Page :auto-content-height="!agentId">
|
||||||
<Grid :table-title="agentId ? '平台抽佣列表' : '所有平台抽佣记录'" />
|
<Grid :table-title="agentId ? '订单记录列表' : '所有订单记录'" />
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
import type { AgentApi } from '#/api/agent';
|
|
||||||
|
|
||||||
// 平台抽佣列表列配置
|
|
||||||
export function usePlatformDeductionColumns(): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: 'ID',
|
|
||||||
field: 'id',
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '代理ID',
|
|
||||||
field: 'agent_id',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '抽佣金额',
|
|
||||||
field: 'amount',
|
|
||||||
width: 120,
|
|
||||||
formatter: ({ cellValue }) => {
|
|
||||||
return `¥${cellValue.toFixed(2)}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '抽佣类型',
|
|
||||||
field: 'type',
|
|
||||||
width: 120,
|
|
||||||
formatter: ({
|
|
||||||
cellValue,
|
|
||||||
}: {
|
|
||||||
cellValue: AgentApi.AgentPlatformDeductionListItem['type'];
|
|
||||||
}) => {
|
|
||||||
const typeMap = {
|
|
||||||
cost: '成本抽佣',
|
|
||||||
pricing: '定价抽佣',
|
|
||||||
};
|
|
||||||
return typeMap[cellValue] || cellValue;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '状态',
|
|
||||||
field: 'status',
|
|
||||||
width: 100,
|
|
||||||
formatter: ({ cellValue }) => {
|
|
||||||
const statusMap = {
|
|
||||||
0: { text: '待处理', type: 'warning' },
|
|
||||||
1: { text: '已处理', type: 'success' },
|
|
||||||
2: { text: '已取消', type: 'error' },
|
|
||||||
};
|
|
||||||
const status = statusMap[cellValue as keyof typeof statusMap];
|
|
||||||
return status
|
|
||||||
? `<a-tag color="${status.type}">${status.text}</a-tag>`
|
|
||||||
: cellValue;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建时间',
|
|
||||||
field: 'create_time',
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 平台抽佣列表搜索表单配置
|
|
||||||
export function usePlatformDeductionFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'Select',
|
|
||||||
fieldName: 'type',
|
|
||||||
label: '抽佣类型',
|
|
||||||
componentProps: {
|
|
||||||
options: [
|
|
||||||
{ label: '成本抽佣', value: 'cost' },
|
|
||||||
{ label: '定价抽佣', value: 'pricing' },
|
|
||||||
],
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Select',
|
|
||||||
fieldName: 'status',
|
|
||||||
label: '状态',
|
|
||||||
componentProps: {
|
|
||||||
options: [
|
|
||||||
{ label: '待处理', value: 0 },
|
|
||||||
{ label: '已处理', value: 1 },
|
|
||||||
{ label: '已取消', value: 2 },
|
|
||||||
],
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
|
||||||
// 代理产品配置列表列配置
|
// 代理产品配置列表列配置
|
||||||
export function useColumns(): VxeTableGridOptions['columns'] {
|
export function useColumns(): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
@@ -14,14 +18,8 @@ export function useColumns(): VxeTableGridOptions['columns'] {
|
|||||||
title: '产品名称',
|
title: '产品名称',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'cost_price',
|
field: 'base_price',
|
||||||
title: '成本',
|
title: '基础底价',
|
||||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
|
||||||
`¥${cellValue.toFixed(2)}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'price_range_min',
|
|
||||||
title: '最低定价',
|
|
||||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
`¥${cellValue.toFixed(2)}`,
|
`¥${cellValue.toFixed(2)}`,
|
||||||
},
|
},
|
||||||
@@ -32,14 +30,14 @@ export function useColumns(): VxeTableGridOptions['columns'] {
|
|||||||
`¥${cellValue.toFixed(2)}`,
|
`¥${cellValue.toFixed(2)}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'pricing_standard',
|
field: 'price_threshold',
|
||||||
title: '定价标准',
|
title: '价格阈值',
|
||||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
`¥${cellValue.toFixed(2)}`,
|
`¥${cellValue.toFixed(2)}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'overpricing_ratio',
|
field: 'price_fee_rate',
|
||||||
title: '超标抽佣比例',
|
title: '提价费率',
|
||||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
`${(cellValue * 100).toFixed(2)}%`,
|
`${(cellValue * 100).toFixed(2)}%`,
|
||||||
},
|
},
|
||||||
@@ -70,19 +68,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
fieldName: 'cost_price',
|
fieldName: 'base_price',
|
||||||
label: '成本',
|
label: '基础底价',
|
||||||
rules: 'required',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
fieldName: 'price_range_min',
|
|
||||||
label: '最低定价',
|
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
@@ -103,29 +90,104 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
fieldName: 'pricing_standard',
|
fieldName: 'price_threshold',
|
||||||
label: '定价标准',
|
label: '价格阈值',
|
||||||
rules: 'required',
|
defaultValue: undefined,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
precision: 2,
|
precision: 2,
|
||||||
step: 0.01,
|
step: 0.01,
|
||||||
|
placeholder: '可选,不设置则不限制',
|
||||||
},
|
},
|
||||||
|
rules: z
|
||||||
|
.number({
|
||||||
|
invalid_type_error: '请输入有效的数字',
|
||||||
|
})
|
||||||
|
.min(0, '价格阈值不能小于0')
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['base_price'],
|
||||||
|
rules(values) {
|
||||||
|
// 动态校验:价格阈值不能低于基础底价
|
||||||
|
const basePrice = values.base_price;
|
||||||
|
if (basePrice !== undefined && basePrice !== null) {
|
||||||
|
return z
|
||||||
|
.number({
|
||||||
|
invalid_type_error: '请输入有效的数字',
|
||||||
|
})
|
||||||
|
.min(0, '价格阈值不能小于0')
|
||||||
|
.refine(
|
||||||
|
(val) => {
|
||||||
|
if (val === undefined || val === null) return true;
|
||||||
|
return val >= basePrice;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: `价格阈值不能低于基础底价 ${basePrice}`,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.nullable();
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
.number({
|
||||||
|
invalid_type_error: '请输入有效的数字',
|
||||||
|
})
|
||||||
|
.min(0, '价格阈值不能小于0')
|
||||||
|
.optional()
|
||||||
|
.nullable();
|
||||||
|
},
|
||||||
|
trigger(_values, formApi) {
|
||||||
|
// 当基础底价变化时,重新校验价格阈值
|
||||||
|
formApi.validateField('price_threshold');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
suffix: () => h('span', { class: 'text-gray-400 text-xs' }, '可选'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
fieldName: 'overpricing_ratio',
|
fieldName: 'price_fee_rate',
|
||||||
label: '超标抽佣比例',
|
label: '提价费率',
|
||||||
rules: 'required',
|
defaultValue: undefined,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
precision: 2,
|
precision: 4,
|
||||||
step: 0.01,
|
step: 0.01,
|
||||||
addonAfter: '%',
|
addonAfter: '%',
|
||||||
controls: true,
|
controls: true,
|
||||||
|
placeholder: '可选,不设置则不收费',
|
||||||
validateTrigger: ['blur', 'change'],
|
validateTrigger: ['blur', 'change'],
|
||||||
},
|
},
|
||||||
|
rules: z
|
||||||
|
.number({
|
||||||
|
invalid_type_error: '请输入有效的数字',
|
||||||
|
})
|
||||||
|
.min(0, '提价费率不能小于0')
|
||||||
|
.max(100, '提价费率不能大于100%')
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['price_threshold'],
|
||||||
|
required(values) {
|
||||||
|
// 如果价格阈值有值,提价费率必填
|
||||||
|
return values.price_threshold !== undefined && values.price_threshold !== null;
|
||||||
|
},
|
||||||
|
rules(values) {
|
||||||
|
// 如果价格阈值有值,提价费率必填
|
||||||
|
if (values.price_threshold !== undefined && values.price_threshold !== null) {
|
||||||
|
return z.number().min(0, '提价费率不能小于0').max(100, '提价费率不能大于100%');
|
||||||
|
}
|
||||||
|
return z.number().optional().nullable();
|
||||||
|
},
|
||||||
|
trigger(values, formApi) {
|
||||||
|
// 当价格阈值清空时,也清空提价费率
|
||||||
|
if (values.price_threshold === undefined || values.price_threshold === null) {
|
||||||
|
formApi.setFieldValue('price_fee_rate', undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
suffix: () => h('span', { class: 'text-gray-400 text-xs' }, '可选'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { AgentApi } from '#/api/agent';
|
|||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Button, InputNumber } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
|
import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
|
||||||
|
|
||||||
import { updateAgentProductionConfig } from '#/api/agent';
|
import { updateAgentProductionConfig } from '#/api/agent';
|
||||||
@@ -30,11 +32,10 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
const values = await formApi.getValues();
|
const values = await formApi.getValues();
|
||||||
const params: AgentApi.UpdateAgentProductionConfigParams = {
|
const params: AgentApi.UpdateAgentProductionConfigParams = {
|
||||||
id: id.value,
|
id: id.value,
|
||||||
cost_price: values.cost_price,
|
base_price: values.base_price,
|
||||||
price_range_min: values.price_range_min,
|
|
||||||
price_range_max: values.price_range_max,
|
price_range_max: values.price_range_max,
|
||||||
pricing_standard: values.pricing_standard,
|
price_threshold: values.price_threshold ?? undefined,
|
||||||
overpricing_ratio: values.overpricing_ratio / 100,
|
price_fee_rate: values.price_fee_rate ? values.price_fee_rate / 100 : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
await updateAgentProductionConfig(params);
|
await updateAgentProductionConfig(params);
|
||||||
@@ -51,7 +52,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
id.value = data.id;
|
id.value = data.id;
|
||||||
formApi.setValues({
|
formApi.setValues({
|
||||||
...data,
|
...data,
|
||||||
overpricing_ratio: data.overpricing_ratio * 100,
|
price_threshold: data.price_threshold ?? undefined,
|
||||||
|
price_fee_rate: data.price_fee_rate ? data.price_fee_rate * 100 : undefined,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
id.value = undefined;
|
id.value = undefined;
|
||||||
@@ -63,6 +65,31 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Drawer :title="drawerTitle">
|
<Drawer :title="drawerTitle">
|
||||||
<Form />
|
<Form>
|
||||||
|
<template #price_threshold="slotProps">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<InputNumber :value="slotProps.componentField.modelValue" :min="0" :precision="2" :step="0.01"
|
||||||
|
placeholder="可选,不设置则不限制" class="flex-1" @update:value="slotProps.componentField['onUpdate:modelValue']" />
|
||||||
|
<Button type="link" size="small" @click="() => {
|
||||||
|
formApi.setFieldValue('price_threshold', undefined);
|
||||||
|
formApi.setFieldValue('price_fee_rate', undefined);
|
||||||
|
}">
|
||||||
|
不设置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #price_fee_rate="slotProps">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<InputNumber :value="slotProps.componentField.modelValue" :min="0" :max="100" :precision="4" :step="0.01"
|
||||||
|
addon-after="%" placeholder="可选,不设置则不收费" class="flex-1"
|
||||||
|
@update:value="slotProps.componentField['onUpdate:modelValue']" />
|
||||||
|
<Button type="link" size="small" @click="() => {
|
||||||
|
formApi.setFieldValue('price_fee_rate', undefined);
|
||||||
|
}">
|
||||||
|
不设置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Form>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
86
apps/web-antd/src/views/agent/agent-real-name/data.ts
Normal file
86
apps/web-antd/src/views/agent/agent-real-name/data.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { maskIdCard, maskMobile, getRealNameStatusName } from '#/utils/agent';
|
||||||
|
|
||||||
|
export function useRealNameColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'agent_id',
|
||||||
|
title: '代理ID',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '姓名',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'id_card',
|
||||||
|
title: '身份证号',
|
||||||
|
width: 180,
|
||||||
|
formatter: ({ cellValue }: { cellValue: string }) => {
|
||||||
|
return maskIdCard(cellValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'mobile',
|
||||||
|
title: '手机号',
|
||||||
|
width: 140,
|
||||||
|
formatter: ({ cellValue }: { cellValue: string }) => {
|
||||||
|
return maskMobile(cellValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
width: 100,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
options: [
|
||||||
|
{ value: 1, color: 'warning', label: '未验证' },
|
||||||
|
{ value: 2, color: 'success', label: '已通过' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'verify_time',
|
||||||
|
title: '验证时间',
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 160,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRealNameFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'agent_id',
|
||||||
|
label: '代理ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '未验证', value: 1 },
|
||||||
|
{ label: '已通过', value: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,12 +4,9 @@ import { computed } from 'vue';
|
|||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getAgentCommissionDeductionList } from '#/api/agent';
|
import { getAgentRealNameList } from '#/api/agent';
|
||||||
|
|
||||||
import {
|
import { useRealNameColumns, useRealNameFormSchema } from './data';
|
||||||
useCommissionDeductionColumns,
|
|
||||||
useCommissionDeductionFormSchema,
|
|
||||||
} from './data';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
agentId?: number;
|
agentId?: number;
|
||||||
@@ -29,21 +26,21 @@ const queryParams = computed(() => ({
|
|||||||
|
|
||||||
const [Grid] = useVbenVxeGrid({
|
const [Grid] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useCommissionDeductionFormSchema(),
|
schema: useRealNameFormSchema(),
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useCommissionDeductionColumns(),
|
columns: useRealNameColumns(),
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({
|
query: async ({
|
||||||
form,
|
|
||||||
page,
|
page,
|
||||||
|
form,
|
||||||
}: {
|
}: {
|
||||||
form: Record<string, any>;
|
form: Record<string, any>;
|
||||||
page: QueryParams;
|
page: QueryParams;
|
||||||
}) => {
|
}) => {
|
||||||
return await getAgentCommissionDeductionList({
|
return await getAgentRealNameList({
|
||||||
...queryParams.value,
|
...queryParams.value,
|
||||||
...form,
|
...form,
|
||||||
page: page.currentPage,
|
page: page.currentPage,
|
||||||
@@ -62,6 +59,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page :auto-content-height="!agentId">
|
<Page :auto-content-height="!agentId">
|
||||||
<Grid :table-title="agentId ? '上级抽佣列表' : '所有上级抽佣记录'" />
|
<Grid :table-title="agentId ? '实名认证列表' : '所有实名认证'" />
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
105
apps/web-antd/src/views/agent/agent-rebate/data.ts
Normal file
105
apps/web-antd/src/views/agent/agent-rebate/data.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { getRebateTypeName } from '#/utils/agent';
|
||||||
|
|
||||||
|
export function useRebateColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'agent_id',
|
||||||
|
title: '代理ID',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'source_agent_id',
|
||||||
|
title: '来源代理ID',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'order_id',
|
||||||
|
title: '订单ID',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rebate_type',
|
||||||
|
title: '返佣类型',
|
||||||
|
width: 140,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||||
|
return getRebateTypeName(cellValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'amount',
|
||||||
|
title: '返佣金额',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
|
`¥${cellValue.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||||
|
const statusMap: Record<number, string> = {
|
||||||
|
1: '已发放',
|
||||||
|
2: '已冻结',
|
||||||
|
3: '已取消(已退款)',
|
||||||
|
};
|
||||||
|
return statusMap[cellValue] || '未知';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 160,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRebateFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'agent_id',
|
||||||
|
label: '代理ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'source_agent_id',
|
||||||
|
label: '来源代理ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'rebate_type',
|
||||||
|
label: '返佣类型',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '直接上级返佣', value: 1 },
|
||||||
|
{ label: '钻石上级返佣', value: 2 },
|
||||||
|
{ label: '黄金上级返佣', value: 3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '已发放', value: 1 },
|
||||||
|
{ label: '已冻结', value: 2 },
|
||||||
|
{ label: '已取消(已退款)', value: 3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
65
apps/web-antd/src/views/agent/agent-rebate/list.vue
Normal file
65
apps/web-antd/src/views/agent/agent-rebate/list.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getAgentRebateList } from '#/api/agent';
|
||||||
|
|
||||||
|
import { useRebateColumns, useRebateFormSchema } from './data';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
agentId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryParams {
|
||||||
|
currentPage: number;
|
||||||
|
pageSize: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const queryParams = computed(() => ({
|
||||||
|
...(props.agentId ? { agent_id: props.agentId } : {}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const [Grid] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useRebateFormSchema(),
|
||||||
|
submitOnChange: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useRebateColumns(),
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({
|
||||||
|
page,
|
||||||
|
form,
|
||||||
|
}: {
|
||||||
|
form: Record<string, any>;
|
||||||
|
page: QueryParams;
|
||||||
|
}) => {
|
||||||
|
return await getAgentRebateList({
|
||||||
|
...queryParams.value,
|
||||||
|
...form,
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
result: 'items',
|
||||||
|
total: 'total',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page :auto-content-height="!agentId">
|
||||||
|
<Grid :table-title="agentId ? '返佣记录列表' : '所有返佣记录'" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
||||||
116
apps/web-antd/src/views/agent/agent-upgrade/data.ts
Normal file
116
apps/web-antd/src/views/agent/agent-upgrade/data.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getLevelName,
|
||||||
|
getUpgradeStatusName,
|
||||||
|
getUpgradeTypeName,
|
||||||
|
} from '#/utils/agent';
|
||||||
|
|
||||||
|
export function useUpgradeColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'agent_id',
|
||||||
|
title: '代理ID',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'from_level',
|
||||||
|
title: '原等级',
|
||||||
|
width: 100,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||||
|
return getLevelName(cellValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'to_level',
|
||||||
|
title: '目标等级',
|
||||||
|
width: 100,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||||
|
return getLevelName(cellValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'upgrade_type',
|
||||||
|
title: '升级类型',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||||
|
return getUpgradeTypeName(cellValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'upgrade_fee',
|
||||||
|
title: '升级费用',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
|
`¥${cellValue.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rebate_amount',
|
||||||
|
title: '返佣金额',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||||
|
`¥${cellValue.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
width: 100,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
options: [
|
||||||
|
{ value: 1, color: 'warning', label: '待处理' },
|
||||||
|
{ value: 2, color: 'success', label: '已完成' },
|
||||||
|
{ value: 3, color: 'error', label: '已失败' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 160,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpgradeFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'agent_id',
|
||||||
|
label: '代理ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'upgrade_type',
|
||||||
|
label: '升级类型',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '自主付费', value: 1 },
|
||||||
|
{ label: '钻石升级下级', value: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '待处理', value: 1 },
|
||||||
|
{ label: '已完成', value: 2 },
|
||||||
|
{ label: '已失败', value: 3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
65
apps/web-antd/src/views/agent/agent-upgrade/list.vue
Normal file
65
apps/web-antd/src/views/agent/agent-upgrade/list.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getAgentUpgradeList } from '#/api/agent';
|
||||||
|
|
||||||
|
import { useUpgradeColumns, useUpgradeFormSchema } from './data';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
agentId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryParams {
|
||||||
|
currentPage: number;
|
||||||
|
pageSize: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const queryParams = computed(() => ({
|
||||||
|
...(props.agentId ? { agent_id: props.agentId } : {}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const [Grid] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useUpgradeFormSchema(),
|
||||||
|
submitOnChange: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useUpgradeColumns(),
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({
|
||||||
|
page,
|
||||||
|
form,
|
||||||
|
}: {
|
||||||
|
form: Record<string, any>;
|
||||||
|
page: QueryParams;
|
||||||
|
}) => {
|
||||||
|
return await getAgentUpgradeList({
|
||||||
|
...queryParams.value,
|
||||||
|
...form,
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
result: 'items',
|
||||||
|
total: 'total',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page :auto-content-height="!agentId">
|
||||||
|
<Grid :table-title="agentId ? '升级记录列表' : '所有升级记录'" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
||||||
@@ -1,16 +1,36 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
type WithdrawalMethod = 'alipay' | 'bank' | 'wechat';
|
|
||||||
type WithdrawalStatus = 'approved' | 'failed' | 'paid' | 'pending' | 'rejected';
|
|
||||||
|
|
||||||
export function useWithdrawalColumns(): VxeTableGridOptions['columns'] {
|
export function useWithdrawalColumns(): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
field: 'id',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '代理ID',
|
title: '代理ID',
|
||||||
field: 'agent_id',
|
field: 'agent_id',
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '提现单号',
|
||||||
|
field: 'withdraw_no',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '提现方式',
|
||||||
|
field: 'withdrawal_type',
|
||||||
|
width: 100,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
options: [
|
||||||
|
{ value: 1, color: 'blue', label: '支付宝' },
|
||||||
|
{ value: 2, color: 'green', label: '银行卡' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '提现金额',
|
title: '提现金额',
|
||||||
field: 'amount',
|
field: 'amount',
|
||||||
@@ -18,90 +38,98 @@ export function useWithdrawalColumns(): VxeTableGridOptions['columns'] {
|
|||||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '提现方式',
|
title: '税费金额',
|
||||||
field: 'method',
|
field: 'tax_amount',
|
||||||
width: 120,
|
width: 120,
|
||||||
formatter: ({ cellValue }: { cellValue: WithdrawalMethod }) => {
|
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||||
const methodMap: Record<WithdrawalMethod, string> = {
|
},
|
||||||
alipay: '支付宝',
|
{
|
||||||
wechat: '微信',
|
title: '实际到账金额',
|
||||||
bank: '银行卡',
|
field: 'actual_amount',
|
||||||
};
|
width: 120,
|
||||||
return methodMap[cellValue] || cellValue;
|
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '收款账号',
|
title: '收款账号',
|
||||||
field: 'account',
|
field: 'payee_account',
|
||||||
width: 180,
|
width: 180,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '银行卡号',
|
||||||
|
field: 'bank_card_no',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '开户行',
|
||||||
|
field: 'bank_name',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '收款人姓名',
|
||||||
|
field: 'payee_name',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
field: 'status',
|
field: 'status',
|
||||||
width: 100,
|
width: 100,
|
||||||
formatter: ({ cellValue }: { cellValue: WithdrawalStatus }) => {
|
cellRender: {
|
||||||
const statusMap: Record<WithdrawalStatus, string> = {
|
name: 'CellTag',
|
||||||
pending: '待审核',
|
options: [
|
||||||
approved: '已通过',
|
{ value: 1, color: 'warning', label: '待审核' },
|
||||||
rejected: '已拒绝',
|
{ value: 2, color: 'success', label: '审核通过' },
|
||||||
paid: '已打款',
|
{ value: 3, color: 'error', label: '审核拒绝' },
|
||||||
failed: '打款失败',
|
{ value: 4, color: 'processing', label: '提现中' },
|
||||||
};
|
{ value: 5, color: 'success', label: '提现成功' },
|
||||||
return statusMap[cellValue] || cellValue;
|
{ value: 6, color: 'error', label: '提现失败' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '申请时间',
|
|
||||||
field: 'create_time',
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '审核时间',
|
|
||||||
field: 'audit_time',
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '打款时间',
|
|
||||||
field: 'pay_time',
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '备注',
|
title: '备注',
|
||||||
field: 'remark',
|
field: 'remark',
|
||||||
width: 200,
|
width: 200,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
field: 'create_time',
|
||||||
|
width: 160,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'center',
|
||||||
|
slots: { default: 'operation' },
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
title: '操作',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWithdrawalFormSchema(): VbenFormSchema[] {
|
export function useWithdrawalFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
fieldName: 'method',
|
component: 'Input',
|
||||||
label: '提现方式',
|
fieldName: 'withdraw_no',
|
||||||
component: 'Select',
|
label: '提现单号',
|
||||||
componentProps: {
|
|
||||||
options: [
|
|
||||||
{ label: '支付宝', value: 'alipay' },
|
|
||||||
{ label: '微信', value: 'wechat' },
|
|
||||||
{ label: '银行卡', value: 'bank' },
|
|
||||||
],
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
component: 'Select',
|
||||||
fieldName: 'status',
|
fieldName: 'status',
|
||||||
label: '状态',
|
label: '状态',
|
||||||
component: 'Select',
|
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: [
|
|
||||||
{ label: '待审核', value: 'pending' },
|
|
||||||
{ label: '已通过', value: 'approved' },
|
|
||||||
{ label: '已拒绝', value: 'rejected' },
|
|
||||||
{ label: '已打款', value: 'paid' },
|
|
||||||
{ label: '打款失败', value: 'failed' },
|
|
||||||
],
|
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '待审核', value: 1 },
|
||||||
|
{ label: '审核通过', value: 2 },
|
||||||
|
{ label: '审核拒绝', value: 3 },
|
||||||
|
{ label: '提现中', value: 4 },
|
||||||
|
{ label: '提现成功', value: 5 },
|
||||||
|
{ label: '提现失败', value: 6 },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import type { AgentApi } from '#/api/agent';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button, Input, Modal, Space, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getAgentWithdrawalList } from '#/api/agent';
|
import { auditWithdrawal, getAgentWithdrawalList } from '#/api/agent';
|
||||||
|
|
||||||
import { useWithdrawalColumns, useWithdrawalFormSchema } from './data';
|
import { useWithdrawalColumns, useWithdrawalFormSchema } from './data';
|
||||||
|
|
||||||
@@ -24,7 +28,22 @@ const queryParams = computed(() => ({
|
|||||||
...(props.agentId ? { agent_id: props.agentId } : {}),
|
...(props.agentId ? { agent_id: props.agentId } : {}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const [Grid] = useVbenVxeGrid({
|
const auditRemark = ref('');
|
||||||
|
const auditModalVisible = ref(false);
|
||||||
|
const detailModalVisible = ref(false);
|
||||||
|
// 使用 any 以兼容后端新增字段(withdrawal_type、bank_card_no、bank_name 等)
|
||||||
|
const currentWithdrawal = ref<any | null>(null);
|
||||||
|
|
||||||
|
const statusLabelMap: Record<number, string> = {
|
||||||
|
1: '待审核',
|
||||||
|
2: '审核通过',
|
||||||
|
3: '审核拒绝',
|
||||||
|
4: '提现中',
|
||||||
|
5: '提现成功',
|
||||||
|
6: '提现失败',
|
||||||
|
};
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useWithdrawalFormSchema(),
|
schema: useWithdrawalFormSchema(),
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
@@ -55,10 +74,155 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 审核提现
|
||||||
|
function handleAudit(row: AgentApi.AgentWithdrawalListItem) {
|
||||||
|
currentWithdrawal.value = row;
|
||||||
|
auditRemark.value = '';
|
||||||
|
auditModalVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
function handleViewDetail(row: AgentApi.AgentWithdrawalListItem) {
|
||||||
|
currentWithdrawal.value = row;
|
||||||
|
detailModalVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认审核
|
||||||
|
async function confirmAudit(status: number) {
|
||||||
|
if (!currentWithdrawal.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await auditWithdrawal({
|
||||||
|
withdrawal_id: currentWithdrawal.value.id,
|
||||||
|
status,
|
||||||
|
remark: auditRemark.value || '',
|
||||||
|
});
|
||||||
|
message.success('审核成功');
|
||||||
|
auditModalVisible.value = false;
|
||||||
|
gridApi.query();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('审核失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消审核
|
||||||
|
function cancelAudit() {
|
||||||
|
auditModalVisible.value = false;
|
||||||
|
auditRemark.value = '';
|
||||||
|
currentWithdrawal.value = null;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page :auto-content-height="!agentId">
|
<Page :auto-content-height="!agentId">
|
||||||
<Grid :table-title="agentId ? '提现记录列表' : '所有提现记录'" />
|
<Grid :table-title="agentId ? '提现记录列表' : '所有提现记录'">
|
||||||
|
<template #operation="{ row }">
|
||||||
|
<Space>
|
||||||
|
<Button v-if="row.status === 1" type="link" size="small" @click="handleAudit(row)">
|
||||||
|
审核
|
||||||
|
</Button>
|
||||||
|
<Button v-else type="link" size="small" @click="handleViewDetail(row)">
|
||||||
|
详情
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Modal v-model:open="auditModalVisible" title="审核提现" :width="600" @cancel="cancelAudit">
|
||||||
|
<div v-if="currentWithdrawal" class="audit-info">
|
||||||
|
<p><strong>提现单号:</strong>{{ currentWithdrawal.withdraw_no }}</p>
|
||||||
|
<p>
|
||||||
|
<strong>提现方式:</strong>
|
||||||
|
<span v-if="currentWithdrawal.withdrawal_type === 1" style="color: #1890ff;">支付宝</span>
|
||||||
|
<span v-else-if="currentWithdrawal.withdrawal_type === 2" style="color: #52c41a;">银行卡</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>提现金额:</strong>¥{{ currentWithdrawal.amount.toFixed(2) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>税费金额:</strong>¥{{ currentWithdrawal.tax_amount?.toFixed(2) ?? '0.00' }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>实际到账金额:</strong>¥{{ currentWithdrawal.actual_amount?.toFixed(2) ?? '0.00' }}
|
||||||
|
</p>
|
||||||
|
<p v-if="currentWithdrawal.withdrawal_type === 1">
|
||||||
|
<strong>支付宝账号:</strong>{{ currentWithdrawal.payee_account }}
|
||||||
|
</p>
|
||||||
|
<template v-else-if="currentWithdrawal.withdrawal_type === 2">
|
||||||
|
<p><strong>银行卡号:</strong>{{ currentWithdrawal.bank_card_no || currentWithdrawal.payee_account }}</p>
|
||||||
|
<p><strong>开户行:</strong>{{ currentWithdrawal.bank_name }}</p>
|
||||||
|
</template>
|
||||||
|
<p><strong>收款人姓名:</strong>{{ currentWithdrawal.payee_name }}</p>
|
||||||
|
<p v-if="currentWithdrawal.withdrawal_type === 2" style="color: #ff4d4f; font-size: 12px; margin-top: 8px;">
|
||||||
|
⚠️ 请在确认已完成银行卡打款后再点击“通过”,审核通过后将直接记为提现成功
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="audit-remark">
|
||||||
|
<p><strong>审核备注:</strong></p>
|
||||||
|
<Input.TextArea v-model:value="auditRemark" :rows="4" placeholder="请输入审核备注" />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<Button @click="cancelAudit">取消</Button>
|
||||||
|
<Button type="primary" danger @click="confirmAudit(3)">
|
||||||
|
拒绝
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" @click="confirmAudit(2)">通过</Button>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<!-- 提现详情弹窗 -->
|
||||||
|
<Modal v-model:open="detailModalVisible" title="提现详情" :width="600" :footer="null"
|
||||||
|
@cancel="detailModalVisible = false">
|
||||||
|
<div v-if="currentWithdrawal" class="audit-info">
|
||||||
|
<p><strong>提现单号:</strong>{{ currentWithdrawal.withdraw_no }}</p>
|
||||||
|
<p><strong>代理ID:</strong>{{ currentWithdrawal.agent_id }}</p>
|
||||||
|
<p>
|
||||||
|
<strong>提现方式:</strong>
|
||||||
|
<span v-if="currentWithdrawal.withdrawal_type === 1" style="color: #1890ff;">支付宝</span>
|
||||||
|
<span v-else-if="currentWithdrawal.withdrawal_type === 2" style="color: #52c41a;">银行卡</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>提现金额:</strong>¥{{ currentWithdrawal.amount.toFixed(2) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>税费金额:</strong>¥{{ currentWithdrawal.tax_amount?.toFixed(2) ?? '0.00' }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>实际到账金额:</strong>¥{{ currentWithdrawal.actual_amount?.toFixed(2) ?? '0.00' }}
|
||||||
|
</p>
|
||||||
|
<p v-if="currentWithdrawal.withdrawal_type === 1">
|
||||||
|
<strong>支付宝账号:</strong>{{ currentWithdrawal.payee_account }}
|
||||||
|
</p>
|
||||||
|
<template v-else-if="currentWithdrawal.withdrawal_type === 2">
|
||||||
|
<p><strong>银行卡号:</strong>{{ currentWithdrawal.bank_card_no || currentWithdrawal.payee_account }}</p>
|
||||||
|
<p><strong>开户行:</strong>{{ currentWithdrawal.bank_name }}</p>
|
||||||
|
</template>
|
||||||
|
<p><strong>收款人姓名:</strong>{{ currentWithdrawal.payee_name }}</p>
|
||||||
|
<p>
|
||||||
|
<strong>状态:</strong>{{ statusLabelMap[currentWithdrawal.status] ?? currentWithdrawal.status }}
|
||||||
|
</p>
|
||||||
|
<p><strong>备注:</strong>{{ currentWithdrawal.remark }}</p>
|
||||||
|
<p><strong>创建时间:</strong>{{ currentWithdrawal.create_time }}</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.audit-info {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.audit-remark {
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
185
apps/web-antd/src/views/complaint/list/data.ts
Normal file
185
apps/web-antd/src/views/complaint/list/data.ts
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { ComplaintApi } from '#/api/complaint';
|
||||||
|
|
||||||
|
export function useColumns<T = ComplaintApi.Complaint>(
|
||||||
|
onActionClick: OnActionClickFn<T>,
|
||||||
|
): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
options: [
|
||||||
|
{ value: 'alipay', color: 'blue', label: '支付宝投诉' },
|
||||||
|
{ value: 'manual', color: 'green', label: '主动投诉' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'type',
|
||||||
|
title: '投诉类型',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '投诉人姓名',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'contact',
|
||||||
|
title: '联系方式',
|
||||||
|
minWidth: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'content',
|
||||||
|
title: '投诉内容',
|
||||||
|
minWidth: 200,
|
||||||
|
showOverflow: 'tooltip',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
options: [
|
||||||
|
{ value: 'pending', color: 'warning', label: '待处理' },
|
||||||
|
{ value: 'processing', color: 'processing', label: '处理中' },
|
||||||
|
{ value: 'resolved', color: 'success', label: '已解决' },
|
||||||
|
{ value: 'closed', color: 'default', label: '已关闭' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'status',
|
||||||
|
title: '投诉状态',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status_description',
|
||||||
|
title: '状态描述',
|
||||||
|
minWidth: 150,
|
||||||
|
showOverflow: 'tooltip',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'task_id',
|
||||||
|
title: '投诉单号',
|
||||||
|
minWidth: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'trade_no',
|
||||||
|
title: '交易单号',
|
||||||
|
minWidth: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'complain_amount',
|
||||||
|
title: '投诉金额',
|
||||||
|
width: 120,
|
||||||
|
formatter: ({ row }) => {
|
||||||
|
if (row.complain_amount) {
|
||||||
|
return `¥${parseFloat(row.complain_amount).toFixed(2)}`;
|
||||||
|
}
|
||||||
|
return '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'order_id',
|
||||||
|
title: '关联订单ID',
|
||||||
|
minWidth: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'handle_time',
|
||||||
|
title: '处理时间',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'center',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'id',
|
||||||
|
nameTitle: '投诉ID',
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
code: 'detail',
|
||||||
|
text: '查看详情',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'update_status',
|
||||||
|
text: '更新状态',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'update_remark',
|
||||||
|
text: '更新备注',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
title: '操作',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '支付宝投诉', value: 'alipay' },
|
||||||
|
{ label: '主动投诉', value: 'manual' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fieldName: 'type',
|
||||||
|
label: '投诉类型',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '待处理', value: 'pending' },
|
||||||
|
{ label: '处理中', value: 'processing' },
|
||||||
|
{ label: '已解决', value: 'resolved' },
|
||||||
|
{ label: '已关闭', value: 'closed' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '投诉状态',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '投诉人姓名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'contact',
|
||||||
|
label: '联系方式',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'order_id',
|
||||||
|
label: '关联订单ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RangePicker',
|
||||||
|
fieldName: 'create_time',
|
||||||
|
label: '创建时间',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RangePicker',
|
||||||
|
fieldName: 'handle_time',
|
||||||
|
label: '处理时间',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
131
apps/web-antd/src/views/complaint/list/index.vue
Normal file
131
apps/web-antd/src/views/complaint/list/index.vue
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { ComplaintApi } from '#/api/complaint';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
getComplaintDetail,
|
||||||
|
getComplaintList,
|
||||||
|
updateComplaintRemark,
|
||||||
|
updateComplaintStatus,
|
||||||
|
} from '#/api/complaint';
|
||||||
|
|
||||||
|
import { useColumns, useGridFormSchema } from './data';
|
||||||
|
import DetailModal from './modules/detail-modal.vue';
|
||||||
|
import StatusModal from './modules/status-modal.vue';
|
||||||
|
import RemarkModal from './modules/remark-modal.vue';
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
fieldMappingTime: [
|
||||||
|
['create_time', ['create_time_start', 'create_time_end']],
|
||||||
|
['handle_time', ['handle_time_start', 'handle_time_end']],
|
||||||
|
],
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
submitOnChange: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getComplaintList({
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: true,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
search: true,
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<ComplaintApi.Complaint>,
|
||||||
|
});
|
||||||
|
|
||||||
|
const detailData = ref<ComplaintApi.ComplaintDetail | null>(null);
|
||||||
|
|
||||||
|
const [DetailDrawer, detailDrawerApi] = useVbenDrawer({
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [StatusModalInstance, statusModalApi] = useVbenModal({
|
||||||
|
connectedComponent: StatusModal,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [RemarkModalInstance, remarkModalApi] = useVbenModal({
|
||||||
|
connectedComponent: RemarkModal,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
function onActionClick(e: OnActionClickParams<ComplaintApi.Complaint>) {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'detail': {
|
||||||
|
onViewDetail(e.row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'update_status': {
|
||||||
|
onUpdateStatus(e.row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'update_remark': {
|
||||||
|
onUpdateRemark(e.row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onViewDetail(row: ComplaintApi.Complaint) {
|
||||||
|
const detail = await getComplaintDetail(row.id);
|
||||||
|
detailData.value = detail;
|
||||||
|
detailDrawerApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpdateStatus(row: ComplaintApi.Complaint) {
|
||||||
|
statusModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpdateRemark(row: ComplaintApi.Complaint) {
|
||||||
|
remarkModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStatusSuccess() {
|
||||||
|
onRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRemarkSuccess() {
|
||||||
|
onRefresh();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<DetailDrawer>
|
||||||
|
<DetailModal :data="detailData || undefined" />
|
||||||
|
</DetailDrawer>
|
||||||
|
<StatusModalInstance @success="onStatusSuccess" />
|
||||||
|
<RemarkModalInstance @success="onRemarkSuccess" />
|
||||||
|
<Grid table-title="投诉列表" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
289
apps/web-antd/src/views/complaint/list/modules/detail-modal.vue
Normal file
289
apps/web-antd/src/views/complaint/list/modules/detail-modal.vue
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ComplaintApi } from '#/api/complaint';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/**
|
||||||
|
* 外层抽屉通过 connectedComponent 传入的详情数据
|
||||||
|
*/
|
||||||
|
data?: ComplaintApi.ComplaintDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 投诉状态字典映射
|
||||||
|
* 通用状态:pending=待处理,processing=处理中,resolved=已解决,closed=已关闭
|
||||||
|
* 支付宝状态:WAIT_PROCESS=待处理 等(优先使用 status_description,如果没有则映射)
|
||||||
|
*/
|
||||||
|
function getStatusText(status: string): string {
|
||||||
|
if (!status) return '-';
|
||||||
|
|
||||||
|
// 通用状态映射
|
||||||
|
const statusMap: Record<string, string> = {
|
||||||
|
pending: '待处理',
|
||||||
|
processing: '处理中',
|
||||||
|
resolved: '已解决',
|
||||||
|
closed: '已关闭',
|
||||||
|
// 支付宝常见状态(如果后端返回的是支付宝状态值)
|
||||||
|
WAIT_PROCESS: '待处理',
|
||||||
|
PROCESSING: '处理中',
|
||||||
|
RESOLVED: '已解决',
|
||||||
|
CLOSED: '已关闭',
|
||||||
|
};
|
||||||
|
|
||||||
|
return statusMap[status] || status;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 作为 connectedComponent 的内容组件,只负责渲染,不再自己创建 Drawer -->
|
||||||
|
<div v-if="props.data" class="complaint-detail">
|
||||||
|
<!-- 基础信息 -->
|
||||||
|
<section class="block">
|
||||||
|
<h3 class="block-title">基础信息</h3>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉ID</span>
|
||||||
|
<span class="value">{{ props.data.id }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉类型</span>
|
||||||
|
<span class="value">
|
||||||
|
{{ props.data.type === 'alipay' ? '支付宝投诉' : '主动投诉' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉人姓名</span>
|
||||||
|
<span class="value">{{ props.data.name || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">联系方式</span>
|
||||||
|
<span class="value">{{ props.data.contact || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉内容</span>
|
||||||
|
<span class="value">{{ props.data.content || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉状态</span>
|
||||||
|
<span class="value">
|
||||||
|
{{ getStatusText(props.data.status) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">状态描述</span>
|
||||||
|
<span class="value">{{ props.data.status_description || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">处理备注</span>
|
||||||
|
<span class="value">{{ props.data.remark || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">关联订单ID</span>
|
||||||
|
<span class="value">{{ props.data.order_id || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">创建时间</span>
|
||||||
|
<span class="value">{{ props.data.create_time }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">处理时间</span>
|
||||||
|
<span class="value">{{ props.data.handle_time || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">更新时间</span>
|
||||||
|
<span class="value">{{ props.data.update_time }}</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 支付宝投诉详情 -->
|
||||||
|
<section v-if="props.data.type === 'alipay' && props.data.alipay_complaint" class="block">
|
||||||
|
<h3 class="block-title">支付宝投诉详情</h3>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉单号</span>
|
||||||
|
<span class="value">{{ props.data.alipay_complaint.task_id }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">支付宝ID</span>
|
||||||
|
<span class="value">{{ props.data.alipay_complaint.alipay_id }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">被投诉人PID</span>
|
||||||
|
<span class="value">{{ props.data.alipay_complaint.opposite_pid || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">被投诉方名称</span>
|
||||||
|
<span class="value">{{ props.data.alipay_complaint.opposite_name || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉金额</span>
|
||||||
|
<span class="value">
|
||||||
|
¥{{ parseFloat(props.data.alipay_complaint.complain_amount || '0').toFixed(2) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">交易单号</span>
|
||||||
|
<span class="value">{{ props.data.alipay_complaint.trade_no || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉时间</span>
|
||||||
|
<span class="value">{{ props.data.alipay_complaint.gmt_complain || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">处理时间</span>
|
||||||
|
<span class="value">{{ props.data.alipay_complaint.gmt_process || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉网址</span>
|
||||||
|
<span class="value">
|
||||||
|
<a v-if="props.data.alipay_complaint.complain_url" :href="props.data.alipay_complaint.complain_url"
|
||||||
|
target="_blank">
|
||||||
|
{{ props.data.alipay_complaint.complain_url }}
|
||||||
|
</a>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 交易信息列表 -->
|
||||||
|
<div v-if="props.data.alipay_complaint.trade_info_list?.length" class="trade-table">
|
||||||
|
<div class="table-title">交易信息</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>交易单号</th>
|
||||||
|
<th>商家订单号</th>
|
||||||
|
<th>交易时间</th>
|
||||||
|
<th>退款时间</th>
|
||||||
|
<th>金额</th>
|
||||||
|
<th>状态</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="item in props.data.alipay_complaint.trade_info_list" :key="item.id">
|
||||||
|
<td>{{ item.trade_no }}</td>
|
||||||
|
<td>{{ item.out_no }}</td>
|
||||||
|
<td>{{ item.gmt_trade }}</td>
|
||||||
|
<td>{{ item.gmt_refund }}</td>
|
||||||
|
<td>{{ item.amount }}</td>
|
||||||
|
<td>{{ item.status }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 主动投诉详情 -->
|
||||||
|
<section v-if="props.data.type === 'manual' && props.data.manual_complaint" class="block">
|
||||||
|
<h3 class="block-title">主动投诉详情</h3>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉主题</span>
|
||||||
|
<span class="value">{{ props.data.manual_complaint.subject || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">优先级</span>
|
||||||
|
<span class="value">
|
||||||
|
{{
|
||||||
|
props.data.manual_complaint.priority === 'urgent'
|
||||||
|
? '紧急'
|
||||||
|
: props.data.manual_complaint.priority === 'high'
|
||||||
|
? '高'
|
||||||
|
: props.data.manual_complaint.priority === 'medium'
|
||||||
|
? '中'
|
||||||
|
: '低'
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">投诉来源</span>
|
||||||
|
<span class="value">
|
||||||
|
{{
|
||||||
|
props.data.manual_complaint.source === 'web'
|
||||||
|
? '网站'
|
||||||
|
: props.data.manual_complaint.source === 'phone'
|
||||||
|
? '电话'
|
||||||
|
: props.data.manual_complaint.source === 'email'
|
||||||
|
? '邮件'
|
||||||
|
: '其他'
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">用户ID</span>
|
||||||
|
<span class="value">{{ props.data.manual_complaint.user_id || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">附件</span>
|
||||||
|
<span class="value">
|
||||||
|
<template v-if="props.data.manual_complaint.attachment_urls?.length">
|
||||||
|
<a v-for="(url, index) in props.data.manual_complaint.attachment_urls" :key="index" :href="url"
|
||||||
|
target="_blank" class="mr-2">
|
||||||
|
附件{{ index + 1 }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.complaint-detail {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
flex: 0 0 90px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
flex: 1;
|
||||||
|
color: #333;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-table {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-table .table-title {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-table table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-table th,
|
||||||
|
.trade-table td {
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
padding: 4px 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ComplaintApi } from '#/api/complaint';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { updateComplaintRemark } from '#/api/complaint';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data?: ComplaintApi.Complaint;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
success: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal();
|
||||||
|
|
||||||
|
const formData = ref<ComplaintApi.UpdateRemarkRequest>({
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(data) => {
|
||||||
|
if (data) {
|
||||||
|
formData.value = {
|
||||||
|
remark: data.remark || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!props.data) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
await updateComplaintRemark(props.data.id, formData.value);
|
||||||
|
message.success('更新备注成功');
|
||||||
|
emit('success');
|
||||||
|
modalApi.close();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('更新备注失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
setData: (data: ComplaintApi.Complaint) => {
|
||||||
|
formData.value = {
|
||||||
|
remark: data.remark || '',
|
||||||
|
};
|
||||||
|
modalApi.open();
|
||||||
|
return modalApi;
|
||||||
|
},
|
||||||
|
open: () => modalApi.open(),
|
||||||
|
close: () => modalApi.close(),
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal title="更新投诉备注" width="600px">
|
||||||
|
<a-form :model="formData" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||||
|
<a-form-item label="处理备注">
|
||||||
|
<a-textarea v-model:value="formData.remark" :rows="5" placeholder="请输入处理备注" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-button @click="modalApi.close()">取消</a-button>
|
||||||
|
<a-button type="primary" :loading="loading" @click="handleSubmit">
|
||||||
|
确定
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
100
apps/web-antd/src/views/complaint/list/modules/status-modal.vue
Normal file
100
apps/web-antd/src/views/complaint/list/modules/status-modal.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ComplaintApi } from '#/api/complaint';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { updateComplaintStatus } from '#/api/complaint';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data?: ComplaintApi.Complaint;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
success: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal();
|
||||||
|
|
||||||
|
const formData = ref<ComplaintApi.UpdateStatusRequest>({
|
||||||
|
status: 'pending',
|
||||||
|
status_description: '',
|
||||||
|
handler_id: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(data) => {
|
||||||
|
if (data) {
|
||||||
|
formData.value = {
|
||||||
|
status: (data.status as any) || 'pending',
|
||||||
|
status_description: data.status_description || '',
|
||||||
|
handler_id: data.handler_id || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!props.data) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
await updateComplaintStatus(props.data.id, formData.value);
|
||||||
|
message.success('更新状态成功');
|
||||||
|
emit('success');
|
||||||
|
modalApi.close();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('更新状态失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
setData: (data: ComplaintApi.Complaint) => {
|
||||||
|
formData.value = {
|
||||||
|
status: (data.status as any) || 'pending',
|
||||||
|
status_description: data.status_description || '',
|
||||||
|
handler_id: data.handler_id || '',
|
||||||
|
};
|
||||||
|
modalApi.open();
|
||||||
|
return modalApi;
|
||||||
|
},
|
||||||
|
open: () => modalApi.open(),
|
||||||
|
close: () => modalApi.close(),
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal title="更新投诉状态" width="600px">
|
||||||
|
<a-form :model="formData" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||||
|
<a-form-item label="投诉状态" required>
|
||||||
|
<a-select v-model:value="formData.status">
|
||||||
|
<a-select-option value="pending">待处理</a-select-option>
|
||||||
|
<a-select-option value="processing">处理中</a-select-option>
|
||||||
|
<a-select-option value="resolved">已解决</a-select-option>
|
||||||
|
<a-select-option value="closed">已关闭</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="状态描述">
|
||||||
|
<a-textarea v-model:value="formData.status_description" :rows="3" placeholder="请输入状态描述" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="处理人ID">
|
||||||
|
<a-input v-model:value="formData.handler_id" placeholder="请输入处理人ID" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-button @click="modalApi.close()">取消</a-button>
|
||||||
|
<a-button type="primary" :loading="loading" @click="handleSubmit">
|
||||||
|
确定
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DashboardApi } from '#/api/dashboard';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
profitStats?: DashboardApi.ProfitStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
// 格式化金额
|
||||||
|
const formatAmount = (amount: number) => {
|
||||||
|
if (amount >= 10000) {
|
||||||
|
return `${(amount / 10000).toFixed(2)}万`;
|
||||||
|
}
|
||||||
|
return amount.toFixed(2);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="profitStats" class="revenue-panel">
|
||||||
|
<div class="revenue-panel-header">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-800">收入统计</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="revenue-panel-content">
|
||||||
|
<!-- 三个时间段的格子 -->
|
||||||
|
<div class="revenue-grid">
|
||||||
|
<!-- 今日 -->
|
||||||
|
<div class="revenue-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">今日</span>
|
||||||
|
<div class="card-profit">
|
||||||
|
<span class="profit-label">利润</span>
|
||||||
|
<span class="profit-value" :class="profitStats.today_profit >= 0 ? 'text-green-600' : 'text-red-600'">
|
||||||
|
¥{{ formatAmount(profitStats.today_profit) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<!-- 收入 -->
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="section-title">收入</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">营收</span>
|
||||||
|
<span class="detail-value text-green-600">
|
||||||
|
+¥{{ formatAmount(profitStats.today_detail?.revenue || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">提现收税</span>
|
||||||
|
<span class="detail-value text-green-600">
|
||||||
|
+¥{{ formatAmount(profitStats.today_detail?.tax_income || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item total">
|
||||||
|
<span class="detail-label font-semibold">收入合计</span>
|
||||||
|
<span class="detail-value font-semibold text-green-600">
|
||||||
|
+¥{{ formatAmount((profitStats.today_detail?.revenue || 0) + (profitStats.today_detail?.tax_income ||
|
||||||
|
0)) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 成本 -->
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="section-title">成本</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">代理佣金</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.today_detail?.commission || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">代理返利</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.today_detail?.rebate || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">税务成本</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.today_detail?.company_tax || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">API调用成本</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.today_detail?.api_cost || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item total">
|
||||||
|
<span class="detail-label font-semibold">成本合计</span>
|
||||||
|
<span class="detail-value font-semibold text-red-600">
|
||||||
|
-¥{{ formatAmount((profitStats.today_detail?.commission || 0) + (profitStats.today_detail?.rebate ||
|
||||||
|
0) + (profitStats.today_detail?.company_tax || 0) + (profitStats.today_detail?.api_cost || 0)) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 当月 -->
|
||||||
|
<div class="revenue-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">当月</span>
|
||||||
|
<div class="card-profit">
|
||||||
|
<span class="profit-label">利润</span>
|
||||||
|
<span class="profit-value" :class="profitStats.month_profit >= 0 ? 'text-green-600' : 'text-red-600'">
|
||||||
|
¥{{ formatAmount(profitStats.month_profit) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<!-- 收入 -->
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="section-title">收入</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">营收</span>
|
||||||
|
<span class="detail-value text-green-600">
|
||||||
|
+¥{{ formatAmount(profitStats.month_detail?.revenue || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">提现收税</span>
|
||||||
|
<span class="detail-value text-green-600">
|
||||||
|
+¥{{ formatAmount(profitStats.month_detail?.tax_income || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item total">
|
||||||
|
<span class="detail-label font-semibold">收入合计</span>
|
||||||
|
<span class="detail-value font-semibold text-green-600">
|
||||||
|
+¥{{ formatAmount((profitStats.month_detail?.revenue || 0) + (profitStats.month_detail?.tax_income ||
|
||||||
|
0)) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 成本 -->
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="section-title">成本</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">代理佣金</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.month_detail?.commission || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">代理返利</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.month_detail?.rebate || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">税务成本</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.month_detail?.company_tax || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">API调用成本</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.month_detail?.api_cost || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item total">
|
||||||
|
<span class="detail-label font-semibold">成本合计</span>
|
||||||
|
<span class="detail-value font-semibold text-red-600">
|
||||||
|
-¥{{ formatAmount((profitStats.month_detail?.commission || 0) + (profitStats.month_detail?.rebate ||
|
||||||
|
0) + (profitStats.month_detail?.company_tax || 0) + (profitStats.month_detail?.api_cost || 0)) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 总计 -->
|
||||||
|
<div class="revenue-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">总计</span>
|
||||||
|
<div class="card-profit">
|
||||||
|
<span class="profit-label">利润</span>
|
||||||
|
<span class="profit-value" :class="profitStats.total_profit >= 0 ? 'text-green-600' : 'text-red-600'">
|
||||||
|
¥{{ formatAmount(profitStats.total_profit) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<!-- 收入 -->
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="section-title">收入</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">营收</span>
|
||||||
|
<span class="detail-value text-green-600">
|
||||||
|
+¥{{ formatAmount(profitStats.total_detail?.revenue || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">提现收税</span>
|
||||||
|
<span class="detail-value text-green-600">
|
||||||
|
+¥{{ formatAmount(profitStats.total_detail?.tax_income || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item total">
|
||||||
|
<span class="detail-label font-semibold">收入合计</span>
|
||||||
|
<span class="detail-value font-semibold text-green-600">
|
||||||
|
+¥{{ formatAmount((profitStats.total_detail?.revenue || 0) + (profitStats.total_detail?.tax_income ||
|
||||||
|
0)) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 成本 -->
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="section-title">成本</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">代理佣金</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.total_detail?.commission || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">代理返利</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.total_detail?.rebate || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">税务成本</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.total_detail?.company_tax || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">API调用成本</span>
|
||||||
|
<span class="detail-value text-red-600">
|
||||||
|
-¥{{ formatAmount(profitStats.total_detail?.api_cost || 0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item total">
|
||||||
|
<span class="detail-label font-semibold">成本合计</span>
|
||||||
|
<span class="detail-value font-semibold text-red-600">
|
||||||
|
-¥{{ formatAmount((profitStats.total_detail?.commission || 0) + (profitStats.total_detail?.rebate ||
|
||||||
|
0) + (profitStats.total_detail?.company_tax || 0) + (profitStats.total_detail?.api_cost || 0)) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.revenue-panel {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.revenue-panel-header {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.revenue-panel-content {
|
||||||
|
.revenue-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.revenue-card {
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fafafa;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-profit {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.profit-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
padding: 12px 16px;
|
||||||
|
|
||||||
|
.detail-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
&.total {
|
||||||
|
border-bottom: 2px solid #e0e0e0;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
import type { DashboardApi } from '#/api/dashboard';
|
||||||
|
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data?: DashboardApi.TrendData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const chartRef = ref<EchartsUIType>();
|
||||||
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!props.data || props.data.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dates = props.data.map((item) => item.date);
|
||||||
|
const values = props.data.map((item) => item.value);
|
||||||
|
|
||||||
|
renderEcharts({
|
||||||
|
grid: {
|
||||||
|
bottom: 0,
|
||||||
|
containLabel: true,
|
||||||
|
left: '1%',
|
||||||
|
right: '1%',
|
||||||
|
top: '2%',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
areaStyle: {},
|
||||||
|
data: values,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#019680',
|
||||||
|
},
|
||||||
|
smooth: true,
|
||||||
|
type: 'line',
|
||||||
|
name: '营收金额',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
axisPointer: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#019680',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
const param = params[0];
|
||||||
|
return `${param.name}<br/>${param.seriesName}: ¥${param.value.toFixed(2)}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
boundaryGap: false,
|
||||||
|
data: dates,
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
type: 'category',
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
splitArea: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (value: number) => {
|
||||||
|
if (value >= 10000) {
|
||||||
|
return `${(value / 10000).toFixed(1)}万`;
|
||||||
|
}
|
||||||
|
return value.toString();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
() => {
|
||||||
|
updateChart();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateChart();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<EchartsUI ref="chartRef" />
|
||||||
|
</template>
|
||||||
|
|
||||||
@@ -1,14 +1,28 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
import type { DashboardApi } from '#/api/dashboard';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data?: DashboardApi.TrendData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
||||||
onMounted(() => {
|
const updateChart = () => {
|
||||||
|
if (!props.data || props.data.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dates = props.data.map((item) => item.date);
|
||||||
|
const values = props.data.map((item) => item.value);
|
||||||
|
|
||||||
renderEcharts({
|
renderEcharts({
|
||||||
grid: {
|
grid: {
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
@@ -20,17 +34,13 @@ onMounted(() => {
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
areaStyle: {},
|
areaStyle: {},
|
||||||
data: [
|
data: values,
|
||||||
120, 300, 500, 800, 1200, 1800, 2500, 3000, 2800, 2600, 2400, 2200,
|
|
||||||
2000, 1800, 1600, 1400, 1200, 1000, 800, 600, 400, 200, 100, 50, 30,
|
|
||||||
20, 10, 5, 2, 1,
|
|
||||||
],
|
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: '#5ab1ef',
|
color: '#5ab1ef',
|
||||||
},
|
},
|
||||||
smooth: true,
|
smooth: true,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
name: '访问量',
|
name: '订单数',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -47,9 +57,7 @@ onMounted(() => {
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: Array.from({ length: 30 }).map(
|
data: dates,
|
||||||
(_item, index) => `Day ${index + 1}`,
|
|
||||||
),
|
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'solid',
|
type: 'solid',
|
||||||
@@ -64,7 +72,6 @@ onMounted(() => {
|
|||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
max: 3000,
|
|
||||||
splitArea: {
|
splitArea: {
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
@@ -73,6 +80,18 @@ onMounted(() => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
() => {
|
||||||
|
updateChart();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateChart();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,102 +1,207 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { AnalysisOverviewItem } from '@vben/common-ui';
|
|
||||||
import type { TabOption } from '@vben/types';
|
import type { TabOption } from '@vben/types';
|
||||||
|
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnalysisChartCard,
|
AnalysisChartCard,
|
||||||
AnalysisChartsTabs,
|
AnalysisChartsTabs,
|
||||||
AnalysisOverview,
|
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
import {
|
import { message } from 'ant-design-vue';
|
||||||
SvgBellIcon,
|
|
||||||
SvgCakeIcon,
|
import { getDashboardStatistics } from '#/api/dashboard';
|
||||||
SvgCardIcon,
|
import type { DashboardApi } from '#/api/dashboard';
|
||||||
SvgDownloadIcon,
|
|
||||||
} from '@vben/icons';
|
|
||||||
|
|
||||||
import AnalyticsTrends from './analytics-trends.vue';
|
import AnalyticsTrends from './analytics-trends.vue';
|
||||||
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
import AnalyticsRevenueTrend from './analytics-revenue-trend.vue';
|
||||||
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
import AnalyticsProfitPanel from './analytics-profit-panel.vue';
|
||||||
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
|
||||||
import AnalyticsVisits from './analytics-visits.vue';
|
|
||||||
|
|
||||||
const overviewItems: AnalysisOverviewItem[] = [
|
const loading = ref(false);
|
||||||
{
|
const statistics = ref<DashboardApi.DashboardStatistics | null>(null);
|
||||||
icon: SvgCardIcon,
|
|
||||||
title: '平台用户数',
|
// 格式化金额
|
||||||
totalTitle: '总用户数',
|
const formatAmount = (amount: number) => {
|
||||||
totalValue: 120_000,
|
if (amount >= 10000) {
|
||||||
value: 2000,
|
return `${(amount / 10000).toFixed(2)}万`;
|
||||||
},
|
}
|
||||||
{
|
return amount.toFixed(2);
|
||||||
icon: SvgCakeIcon,
|
};
|
||||||
title: '推广访问量',
|
|
||||||
totalTitle: '总推广访问量',
|
// 格式化数字
|
||||||
totalValue: 500_000,
|
const formatNumber = (num: number) => {
|
||||||
value: 20_000,
|
if (num >= 10000) {
|
||||||
},
|
return `${(num / 10000).toFixed(2)}万`;
|
||||||
{
|
}
|
||||||
icon: SvgDownloadIcon,
|
return num.toString();
|
||||||
title: '产品数量',
|
};
|
||||||
totalTitle: '总产品数量',
|
|
||||||
totalValue: 120,
|
// 加载统计数据
|
||||||
value: 8,
|
const loadStatistics = async () => {
|
||||||
},
|
loading.value = true;
|
||||||
{
|
try {
|
||||||
icon: SvgBellIcon,
|
const data = await getDashboardStatistics();
|
||||||
title: '代理数量',
|
statistics.value = data;
|
||||||
totalTitle: '总代理数量',
|
} catch (error) {
|
||||||
totalValue: 5000,
|
message.error('加载统计数据失败');
|
||||||
value: 500,
|
} finally {
|
||||||
},
|
loading.value = false;
|
||||||
];
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const chartTabs: TabOption[] = [
|
const chartTabs: TabOption[] = [
|
||||||
{
|
{
|
||||||
label: '推广访问趋势',
|
label: '订单趋势',
|
||||||
value: 'trends',
|
value: 'order',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '订单趋势',
|
label: '营收趋势',
|
||||||
value: 'visits',
|
value: 'revenue',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadStatistics();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-5">
|
<div class="p-5 dashboard-wrapper">
|
||||||
<div class="mb-4 ml-4 text-lg text-gray-500">
|
<a-spin :spinning="loading">
|
||||||
该数据为演示模拟生成,不为真实数据
|
<div v-if="statistics" class="dashboard-container">
|
||||||
</div>
|
<!-- 左侧:统计卡片和图表 -->
|
||||||
<AnalysisOverview :items="overviewItems" />
|
<div class="dashboard-main">
|
||||||
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
|
<!-- 合并后的统计卡片 -->
|
||||||
<template #trends>
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<AnalyticsTrends />
|
<!-- 订单统计卡片 -->
|
||||||
</template>
|
<AnalysisChartCard title="订单统计">
|
||||||
<template #visits>
|
<div class="py-2">
|
||||||
<AnalyticsVisits />
|
<!-- 今日数据 -->
|
||||||
</template>
|
<div class="mb-3">
|
||||||
</AnalysisChartsTabs>
|
<div class="text-xs text-gray-500 mb-1">今日订单</div>
|
||||||
|
<div class="flex items-baseline justify-between">
|
||||||
|
<div class="text-3xl font-bold text-blue-600">
|
||||||
|
{{ statistics.order_stats.today_count }}<span
|
||||||
|
class="text-lg font-normal ml-1 text-gray-500">单</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs" v-if="statistics.order_stats.change_rate">
|
||||||
|
<span :class="statistics.order_stats.change_rate > 0 ? 'text-green-500' : 'text-red-500'">
|
||||||
|
{{ statistics.order_stats.change_rate > 0 ? '↑' : '↓' }} {{
|
||||||
|
Math.abs(statistics.order_stats.change_rate).toFixed(1) }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 当月和总计 - 两列布局 -->
|
||||||
|
<div class="grid grid-cols-2 gap-3 pt-3 border-t">
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-gray-500 mb-1">当月</div>
|
||||||
|
<div class="text-lg font-semibold text-gray-700">
|
||||||
|
{{ formatNumber(statistics.order_stats.month_count) }}<span
|
||||||
|
class="text-sm font-normal text-gray-500 ml-1">单</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-gray-500 mb-1">总计</div>
|
||||||
|
<div class="text-lg font-semibold text-gray-700">
|
||||||
|
{{ formatNumber(statistics.order_stats.total_count) }}<span
|
||||||
|
class="text-sm font-normal text-gray-500 ml-1">单</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnalysisChartCard>
|
||||||
|
|
||||||
<div class="mt-5 w-full md:flex">
|
<!-- 代理统计卡片 -->
|
||||||
<AnalysisChartCard
|
<AnalysisChartCard title="代理统计">
|
||||||
class="mt-5 md:mr-4 md:mt-0 md:w-1/3"
|
<div class="py-2">
|
||||||
title="推广数据分析"
|
<!-- 总数数据 -->
|
||||||
>
|
<div class="mb-3">
|
||||||
<AnalyticsVisitsData />
|
<div class="text-xs text-gray-500 mb-1">代理总数</div>
|
||||||
</AnalysisChartCard>
|
<div class="text-3xl font-bold text-purple-600">
|
||||||
<AnalysisChartCard
|
{{ statistics.agent_stats.total_count }}<span
|
||||||
class="mt-5 md:mr-4 md:mt-0 md:w-1/3"
|
class="text-lg font-normal ml-1 text-gray-500">人</span>
|
||||||
title="订单来源分析"
|
</div>
|
||||||
>
|
</div>
|
||||||
<AnalyticsVisitsSource />
|
<!-- 新增数据 - 两列布局 -->
|
||||||
</AnalysisChartCard>
|
<div class="grid grid-cols-2 gap-3 pt-3 border-t">
|
||||||
<AnalysisChartCard
|
<div>
|
||||||
class="mt-5 md:mt-0 md:w-1/3"
|
<div class="text-xs text-gray-500 mb-1">今日新增</div>
|
||||||
title="佣金/奖励/提现统计"
|
<div class="text-lg font-semibold text-gray-700">
|
||||||
>
|
{{ statistics.agent_stats.today_new }}<span
|
||||||
<AnalyticsVisitsSales />
|
class="text-sm font-normal text-gray-500 ml-1">人</span>
|
||||||
</AnalysisChartCard>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-gray-500 mb-1">当月新增</div>
|
||||||
|
<div class="text-lg font-semibold text-gray-700">
|
||||||
|
{{ statistics.agent_stats.month_new }}<span
|
||||||
|
class="text-sm font-normal text-gray-500 ml-1">人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnalysisChartCard>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 趋势图表 -->
|
||||||
|
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
|
||||||
|
<template #order>
|
||||||
|
<AnalyticsTrends :data="statistics.order_trend" />
|
||||||
|
</template>
|
||||||
|
<template #revenue>
|
||||||
|
<AnalyticsRevenueTrend :data="statistics.revenue_trend" />
|
||||||
|
</template>
|
||||||
|
</AnalysisChartsTabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧:收入统计面板 -->
|
||||||
|
<div class="dashboard-sidebar">
|
||||||
|
<AnalyticsProfitPanel :profit-stats="statistics.profit_stats" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.dashboard-wrapper {
|
||||||
|
/* 让整个页面区域占满视口高度,方便右侧面板自适应 */
|
||||||
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.dashboard-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-sidebar {
|
||||||
|
/* 右侧面板根据视口高度拉满,高度=视口高度-上下padding(假设24px) */
|
||||||
|
width: 800px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 16px;
|
||||||
|
max-height: calc(100vh - 32px);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1600px) {
|
||||||
|
.dashboard-container {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.dashboard-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
position: static;
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -107,18 +107,6 @@ export function useColumns<T = OrderApi.Order>(
|
|||||||
title: '退款时间',
|
title: '退款时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
cellRender: {
|
|
||||||
name: 'CellTag',
|
|
||||||
options: [
|
|
||||||
{ value: 0, color: 'default', label: '否' },
|
|
||||||
{ value: 1, color: 'success', label: '是' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
field: 'is_promotion',
|
|
||||||
title: '推广订单',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
align: 'center',
|
align: 'center',
|
||||||
cellRender: {
|
cellRender: {
|
||||||
@@ -212,18 +200,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
fieldName: 'status',
|
fieldName: 'status',
|
||||||
label: '支付状态',
|
label: '支付状态',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
component: 'Select',
|
|
||||||
componentProps: {
|
|
||||||
allowClear: true,
|
|
||||||
options: [
|
|
||||||
{ label: '否', value: 0 },
|
|
||||||
{ label: '是', value: 1 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
fieldName: 'is_promotion',
|
|
||||||
label: '推广订单',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
component: 'RangePicker',
|
component: 'RangePicker',
|
||||||
fieldName: 'create_time',
|
fieldName: 'create_time',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { getOrderQueryDetail } from '#/api/order/query';
|
|||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const orderId = Number(route.params.id);
|
const orderId = route.params.id as string;
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const queryDetail = ref<OrderQueryApi.QueryDetail>();
|
const queryDetail = ref<OrderQueryApi.QueryDetail>();
|
||||||
|
|
||||||
@@ -111,7 +111,9 @@ onMounted(() => {
|
|||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="mb-4 flex items-center">
|
<div class="mb-4 flex items-center">
|
||||||
<Button @click="handleBack">
|
<Button @click="handleBack">
|
||||||
<template #icon><MdiArrowLeft /></template>
|
<template #icon>
|
||||||
|
<MdiArrowLeft />
|
||||||
|
</template>
|
||||||
返回订单管理
|
返回订单管理
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,10 +124,7 @@ onMounted(() => {
|
|||||||
<span class="text-lg font-medium">订单查询详情</span>
|
<span class="text-lg font-medium">订单查询详情</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-gray-500">查询状态:</span>
|
<span class="text-gray-500">查询状态:</span>
|
||||||
<Tag
|
<Tag v-if="queryDetail" :color="getQueryStateConfig(queryDetail.query_state).color">
|
||||||
v-if="queryDetail"
|
|
||||||
:color="getQueryStateConfig(queryDetail.query_state).color"
|
|
||||||
>
|
|
||||||
{{ getQueryStateConfig(queryDetail.query_state).label }}
|
{{ getQueryStateConfig(queryDetail.query_state).label }}
|
||||||
</Tag>
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
@@ -160,11 +159,8 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
<template v-if="queryDetail.query_params">
|
<template v-if="queryDetail.query_params">
|
||||||
<Descriptions :column="2" bordered>
|
<Descriptions :column="2" bordered>
|
||||||
<Descriptions.Item
|
<Descriptions.Item v-for="(value, key) in queryDetail.query_params" :key="key"
|
||||||
v-for="(value, key) in queryDetail.query_params"
|
:label="getFieldDisplayName(key)">
|
||||||
:key="key"
|
|
||||||
:label="getFieldDisplayName(key)"
|
|
||||||
>
|
|
||||||
{{ value }}
|
{{ value }}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
@@ -177,30 +173,21 @@ onMounted(() => {
|
|||||||
<span class="text-lg font-medium">查询数据</span>
|
<span class="text-lg font-medium">查询数据</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="queryDetail.query_data?.length">
|
<template v-if="queryDetail.query_data?.length">
|
||||||
<Collapse
|
<Collapse :default-active-key="queryDetail.query_data.map((_, index) => index)
|
||||||
:default-active-key="
|
">
|
||||||
queryDetail.query_data.map((_, index) => index)
|
<Collapse.Panel v-for="(item, index) in queryDetail.query_data" :key="index">
|
||||||
"
|
|
||||||
>
|
|
||||||
<Collapse.Panel
|
|
||||||
v-for="(item, index) in queryDetail.query_data"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-lg font-medium">{{
|
<span class="text-lg font-medium">{{
|
||||||
item.feature.featureName
|
item.feature.featureName
|
||||||
}}</span>
|
}}</span>
|
||||||
<Tag color="blue">API: {{ item.data.apiID }}</Tag>
|
<Tag color="blue">API: {{ item.data.apiID }}</Tag>
|
||||||
</div>
|
</div>
|
||||||
<Tag
|
<Tag :color="String(item.data.success) === 'true'
|
||||||
:color="
|
? 'success'
|
||||||
String(item.data.success) === 'true'
|
: 'error'
|
||||||
? 'success'
|
">
|
||||||
: 'error'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{
|
{{
|
||||||
String(item.data.success) === 'true'
|
String(item.data.success) === 'true'
|
||||||
? '查询成功'
|
? '查询成功'
|
||||||
@@ -213,13 +200,7 @@ onMounted(() => {
|
|||||||
<div class="grid gap-4">
|
<div class="grid gap-4">
|
||||||
<div v-if="item.data.data">
|
<div v-if="item.data.data">
|
||||||
<div class="mb-2 font-medium">查询结果:</div>
|
<div class="mb-2 font-medium">查询结果:</div>
|
||||||
<JsonViewer
|
<JsonViewer :value="item.data.data" copyable :expand-depth="2" boxed @copied="handleCopied" />
|
||||||
:value="item.data.data"
|
|
||||||
copyable
|
|
||||||
:expand-depth="2"
|
|
||||||
boxed
|
|
||||||
@copied="handleCopied"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-500">
|
<div class="text-gray-500">
|
||||||
查询时间: {{ item.data.timestamp }}
|
查询时间: {{ item.data.timestamp }}
|
||||||
|
|||||||
@@ -17,6 +17,39 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
label: '描述',
|
label: '描述',
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: 'Switch',
|
||||||
|
fieldName: 'no_offline',
|
||||||
|
label: '不支持下架',
|
||||||
|
defaultValue: false,
|
||||||
|
help: '勾选后该模块不开放下架功能,提交时白名单价格传 -1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'whitelist_price',
|
||||||
|
label: '白名单屏蔽价格(元)',
|
||||||
|
componentProps: {
|
||||||
|
min: 0,
|
||||||
|
precision: 2,
|
||||||
|
placeholder: '0=免费下架,>0=付费下架;勾选「不支持下架」时此项忽略',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['no_offline'],
|
||||||
|
if(values) {
|
||||||
|
return !values?.no_offline;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
min: 0,
|
||||||
|
precision: 2,
|
||||||
|
},
|
||||||
|
fieldName: 'cost_price',
|
||||||
|
label: '成本价(元)',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +84,28 @@ export function useColumns<T = FeatureApi.FeatureItem>(
|
|||||||
title: '描述',
|
title: '描述',
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'whitelist_price',
|
||||||
|
title: '白名单屏蔽价格(元)',
|
||||||
|
minWidth: 150,
|
||||||
|
cellRender: {
|
||||||
|
name: 'VxeCellRender',
|
||||||
|
props: {
|
||||||
|
render: ({ row }: { row: FeatureApi.FeatureItem }) => {
|
||||||
|
const price = (row as FeatureApi.FeatureItem).whitelist_price ?? 0;
|
||||||
|
if (price < 0) return '不支持下架';
|
||||||
|
if (price === 0) return '免费下架';
|
||||||
|
return `¥${price.toFixed(2)}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'cost_price',
|
||||||
|
formatter: ({ cellValue }) => `¥${(cellValue || 0).toFixed(2)}`,
|
||||||
|
title: '成本价(元)',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'create_time',
|
field: 'create_time',
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
async onConfirm() {
|
async onConfirm() {
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
const values = await formApi.getValues();
|
const values = await formApi.getValues() as Record<string, unknown>;
|
||||||
|
// 「不支持下架」时给后端传 whitelist_price: -1
|
||||||
|
if (values.no_offline) {
|
||||||
|
values.whitelist_price = -1;
|
||||||
|
}
|
||||||
|
delete values.no_offline;
|
||||||
drawerApi.lock();
|
drawerApi.lock();
|
||||||
try {
|
try {
|
||||||
await (id.value
|
await (id.value
|
||||||
@@ -43,7 +48,13 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
if (data) {
|
if (data) {
|
||||||
formData.value = data;
|
formData.value = data;
|
||||||
id.value = data.id;
|
id.value = data.id;
|
||||||
formApi.setValues(data);
|
// 回显:whitelist_price < 0 表示不支持下架,勾选「不支持下架」并隐藏价格输入
|
||||||
|
const noOffline = (data.whitelist_price ?? 0) < 0;
|
||||||
|
formApi.setValues({
|
||||||
|
...data,
|
||||||
|
no_offline: noOffline,
|
||||||
|
whitelist_price: noOffline ? 0 : (data.whitelist_price ?? 0),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
id.value = undefined;
|
id.value = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,16 +28,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
fieldName: 'notes',
|
fieldName: 'notes',
|
||||||
label: '备注',
|
label: '备注',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
component: 'InputNumber',
|
|
||||||
componentProps: {
|
|
||||||
min: 0,
|
|
||||||
precision: 2,
|
|
||||||
},
|
|
||||||
fieldName: 'cost_price',
|
|
||||||
label: '成本价',
|
|
||||||
rules: 'required',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
@@ -86,12 +76,6 @@ export function useColumns<T = ProductApi.ProductItem>(
|
|||||||
field: 'description',
|
field: 'description',
|
||||||
title: '描述',
|
title: '描述',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'cost_price',
|
|
||||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
|
||||||
title: '成本价',
|
|
||||||
width: 120,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'sell_price',
|
field: 'sell_price',
|
||||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { AnalysisOverviewItem } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import type { PromotionAnalyticsApi } from '#/api/promotion/analytics';
|
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import { AnalysisChartCard, AnalysisOverview } from '@vben/common-ui';
|
|
||||||
import { SvgCakeIcon, SvgCardIcon, SvgDownloadIcon } from '@vben/icons';
|
|
||||||
|
|
||||||
import { DatePicker } from 'ant-design-vue';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
import { statsHistory, statsTotal } from '#/api/promotion/analytics';
|
|
||||||
|
|
||||||
import PromotionTrends from './promotion-trends.vue';
|
|
||||||
|
|
||||||
const overviewItems = ref<AnalysisOverviewItem[]>([
|
|
||||||
{
|
|
||||||
icon: SvgCardIcon,
|
|
||||||
title: '今日点击数',
|
|
||||||
totalTitle: '累计',
|
|
||||||
totalValue: 0,
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: SvgCakeIcon,
|
|
||||||
title: '今日付费次数',
|
|
||||||
totalTitle: '累计',
|
|
||||||
totalValue: 0,
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: SvgDownloadIcon,
|
|
||||||
title: '今日付费金额',
|
|
||||||
totalTitle: '累计',
|
|
||||||
totalValue: 0,
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const trendData = ref<PromotionAnalyticsApi.TrendData[]>([]);
|
|
||||||
const dateRange = ref<[dayjs.Dayjs, dayjs.Dayjs]>([
|
|
||||||
dayjs().subtract(7, 'day'),
|
|
||||||
dayjs(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const fetchOverview = async () => {
|
|
||||||
try {
|
|
||||||
const data = await statsTotal();
|
|
||||||
overviewItems.value = [
|
|
||||||
{
|
|
||||||
icon: SvgCardIcon,
|
|
||||||
title: '今日点击数',
|
|
||||||
totalTitle: '累计',
|
|
||||||
totalValue: data.total_click_count,
|
|
||||||
value: data.today_click_count,
|
|
||||||
decimals: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: SvgCakeIcon,
|
|
||||||
title: '今日付费次数',
|
|
||||||
totalTitle: '累计',
|
|
||||||
totalValue: data.total_pay_count,
|
|
||||||
value: data.today_pay_count,
|
|
||||||
decimals: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: SvgDownloadIcon,
|
|
||||||
title: '今日付费金额',
|
|
||||||
totalTitle: '累计',
|
|
||||||
totalValue: data.total_pay_amount,
|
|
||||||
value: data.today_pay_amount,
|
|
||||||
decimals: 2,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取概览数据失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchTrendData = async () => {
|
|
||||||
try {
|
|
||||||
const data = await statsHistory({
|
|
||||||
start_date: dateRange.value[0].format('YYYY-MM-DD'),
|
|
||||||
end_date: dateRange.value[1].format('YYYY-MM-DD'),
|
|
||||||
});
|
|
||||||
trendData.value = data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取趋势数据失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDateChange = () => {
|
|
||||||
fetchTrendData();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchOverview();
|
|
||||||
fetchTrendData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="p-5">
|
|
||||||
<AnalysisOverview :items="overviewItems" />
|
|
||||||
<div class="mt-5">
|
|
||||||
<AnalysisChartCard title="数据趋势">
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div class="flex justify-end">
|
|
||||||
<DatePicker.RangePicker
|
|
||||||
v-model:value="dateRange"
|
|
||||||
@change="handleDateChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<PromotionTrends type="count" :data="trendData" />
|
|
||||||
<PromotionTrends type="amount" :data="trendData" />
|
|
||||||
</div>
|
|
||||||
</AnalysisChartCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
import type { PromotionAnalyticsApi } from '#/api/promotion/analytics';
|
|
||||||
|
|
||||||
import { onMounted, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
data: PromotionAnalyticsApi.TrendData[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
const renderChart = () => {
|
|
||||||
// 计算转化率数据
|
|
||||||
const totalClicks = props.data.reduce(
|
|
||||||
(sum, item) => sum + item.click_count,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
const totalPays = props.data.reduce((sum, item) => sum + item.pay_count, 0);
|
|
||||||
const totalAmount = props.data.reduce(
|
|
||||||
(sum, item) => sum + item.pay_amount,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
const conversionRate = totalClicks > 0 ? (totalPays / totalClicks) * 100 : 0;
|
|
||||||
const averageAmount = totalPays > 0 ? totalAmount / totalPays : 0;
|
|
||||||
|
|
||||||
renderEcharts({
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
formatter: '{a} <br/>{b} : {c}%',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '转化率',
|
|
||||||
type: 'funnel',
|
|
||||||
left: '10%',
|
|
||||||
top: 60,
|
|
||||||
bottom: 60,
|
|
||||||
width: '80%',
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
minSize: '0%',
|
|
||||||
maxSize: '100%',
|
|
||||||
sort: 'descending',
|
|
||||||
gap: 2,
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
position: 'inside',
|
|
||||||
},
|
|
||||||
labelLine: {
|
|
||||||
length: 10,
|
|
||||||
lineStyle: {
|
|
||||||
width: 1,
|
|
||||||
type: 'solid',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
itemStyle: {
|
|
||||||
borderColor: '#fff',
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
label: {
|
|
||||||
fontSize: 20,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{ value: 100, name: '点击量' },
|
|
||||||
{ value: conversionRate, name: '付费转化率' },
|
|
||||||
{ value: averageAmount, name: '平均付费金额' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(() => props.data, renderChart, { deep: true });
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderChart();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" class="h-[400px]" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
import type { PromotionAnalyticsApi } from '#/api/promotion/analytics';
|
|
||||||
|
|
||||||
import { onMounted, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
data: PromotionAnalyticsApi.TrendData[];
|
|
||||||
type: 'amount' | 'count';
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
|
||||||
|
|
||||||
const renderChart = () => {
|
|
||||||
const dates = props.data.map((item) => item.stats_date);
|
|
||||||
const clickCounts = props.data.map((item) => item.click_count);
|
|
||||||
const payCounts = props.data.map((item) => item.pay_count);
|
|
||||||
const payAmounts = props.data.map((item) => item.pay_amount);
|
|
||||||
|
|
||||||
renderEcharts({
|
|
||||||
grid: {
|
|
||||||
top: 60,
|
|
||||||
left: 50,
|
|
||||||
right: 50,
|
|
||||||
bottom: 50,
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
data: props.type === 'count' ? ['点击数', '付费次数'] : ['付费金额'],
|
|
||||||
top: 20,
|
|
||||||
},
|
|
||||||
series:
|
|
||||||
props.type === 'count'
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: '点击数',
|
|
||||||
type: 'line',
|
|
||||||
data: clickCounts,
|
|
||||||
smooth: true,
|
|
||||||
showSymbol: false,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#5ab1ef',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '付费次数',
|
|
||||||
type: 'line',
|
|
||||||
data: payCounts,
|
|
||||||
smooth: true,
|
|
||||||
showSymbol: false,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#019680',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
name: '付费金额',
|
|
||||||
type: 'line',
|
|
||||||
data: payAmounts,
|
|
||||||
smooth: true,
|
|
||||||
showSymbol: false,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#b6a2de',
|
|
||||||
},
|
|
||||||
areaStyle: {
|
|
||||||
color: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
x2: 0,
|
|
||||||
y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{
|
|
||||||
offset: 0,
|
|
||||||
color: 'rgba(182, 162, 222, 0.3)',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
offset: 1,
|
|
||||||
color: 'rgba(182, 162, 222, 0.1)',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'cross',
|
|
||||||
label: {
|
|
||||||
backgroundColor: '#6a7985',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: dates,
|
|
||||||
axisLabel: {
|
|
||||||
formatter: (value: string) => value.slice(5), // 只显示月-日
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
type: 'solid',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
axisLabel: {
|
|
||||||
formatter: (value: number) => {
|
|
||||||
if (props.type === 'amount') {
|
|
||||||
return `¥${value}`;
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
type: 'solid',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(() => props.data, renderChart, { deep: true });
|
|
||||||
watch(() => props.type, renderChart);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
renderChart();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EchartsUI ref="chartRef" class="h-[400px]" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
import type { SystemRoleApi } from '#/api';
|
|
||||||
|
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'Input',
|
|
||||||
fieldName: 'name',
|
|
||||||
label: '名称',
|
|
||||||
rules: 'required',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'Input',
|
|
||||||
fieldName: 'name',
|
|
||||||
label: '名称',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Input',
|
|
||||||
fieldName: 'url',
|
|
||||||
label: '链接',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useColumns<T = SystemRoleApi.SystemRole>(
|
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
field: 'name',
|
|
||||||
title: '名称',
|
|
||||||
minWidth: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'url',
|
|
||||||
title: '链接',
|
|
||||||
minWidth: 250,
|
|
||||||
slots: { default: 'url' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'click_count',
|
|
||||||
title: '累计点击数',
|
|
||||||
minWidth: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'pay_count',
|
|
||||||
title: '付费次数',
|
|
||||||
minWidth: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'pay_amount',
|
|
||||||
title: '付费金额',
|
|
||||||
minWidth: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'create_time',
|
|
||||||
title: '创建时间',
|
|
||||||
minWidth: 180,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'last_click_time',
|
|
||||||
title: '最后点击时间',
|
|
||||||
minWidth: 180,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'last_pay_time',
|
|
||||||
title: '最后付费时间',
|
|
||||||
minWidth: 180,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'center',
|
|
||||||
cellRender: {
|
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
nameTitle: '名称',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
},
|
|
||||||
field: 'operation',
|
|
||||||
fixed: 'right',
|
|
||||||
title: '操作',
|
|
||||||
width: 130,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type {
|
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { PromotionLinkApi } from '#/api';
|
|
||||||
|
|
||||||
import { Page, useVbenDrawer } from '@vben/common-ui';
|
|
||||||
import { Copy, Plus } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { deletePromotionLink, getPromotionLinkList } from '#/api';
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
import { useColumns, useGridFormSchema } from './data';
|
|
||||||
import Form from './modules/form.vue';
|
|
||||||
|
|
||||||
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
|
||||||
connectedComponent: Form,
|
|
||||||
destroyOnClose: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
|
||||||
formOptions: {
|
|
||||||
fieldMappingTime: [['create_time', ['startTime', 'endTime']]],
|
|
||||||
schema: useGridFormSchema(),
|
|
||||||
submitOnChange: true,
|
|
||||||
},
|
|
||||||
gridOptions: {
|
|
||||||
columns: useColumns(onActionClick),
|
|
||||||
height: 'auto',
|
|
||||||
keepSource: true,
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async ({ page }, formValues) => {
|
|
||||||
return await getPromotionLinkList({
|
|
||||||
page: page.currentPage,
|
|
||||||
pageSize: page.pageSize,
|
|
||||||
...formValues,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
},
|
|
||||||
|
|
||||||
toolbarConfig: {
|
|
||||||
custom: true,
|
|
||||||
export: false,
|
|
||||||
refresh: { code: 'query' },
|
|
||||||
search: true,
|
|
||||||
zoom: true,
|
|
||||||
},
|
|
||||||
} as VxeTableGridOptions<PromotionLinkApi.PromotionLink>,
|
|
||||||
});
|
|
||||||
|
|
||||||
function onActionClick(
|
|
||||||
e: OnActionClickParams<PromotionLinkApi.PromotionLinkItem>,
|
|
||||||
) {
|
|
||||||
switch (e.code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(e.row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(e.row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onEdit(row: PromotionLinkApi.PromotionLinkItem) {
|
|
||||||
formDrawerApi.setData(row).open();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDelete(row: PromotionLinkApi.PromotionLinkItem) {
|
|
||||||
const hideLoading = message.loading({
|
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
|
||||||
duration: 0,
|
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
|
||||||
deletePromotionLink(row.id.toString())
|
|
||||||
.then(() => {
|
|
||||||
message.success({
|
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
|
||||||
onRefresh();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
hideLoading();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRefresh() {
|
|
||||||
gridApi.query();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCreate() {
|
|
||||||
formDrawerApi.setData({}).open();
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyToClipboard(text: string) {
|
|
||||||
navigator.clipboard.writeText(text).then(
|
|
||||||
() => {
|
|
||||||
message.success('复制成功');
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
message.error('复制失败');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<Page auto-content-height>
|
|
||||||
<FormDrawer @success="onRefresh" />
|
|
||||||
<Grid table-title="链接列表">
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<Button type="primary" @click="onCreate">
|
|
||||||
<Plus class="size-5" />
|
|
||||||
创建链接
|
|
||||||
</Button>
|
|
||||||
</template>
|
|
||||||
<template #url="{ row }">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="truncate">{{ row.url }}</span>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
class="!p-0"
|
|
||||||
@click="copyToClipboard(row.url)"
|
|
||||||
>
|
|
||||||
<Copy class="size-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Grid>
|
|
||||||
</Page>
|
|
||||||
</template>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { PromotionLinkApi } from '#/api';
|
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
|
|
||||||
import { useVbenDrawer } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
|
||||||
import { createPromotionLink, updatePromotionLink } from '#/api';
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
import { useFormSchema } from '../data';
|
|
||||||
|
|
||||||
const emits = defineEmits(['success']);
|
|
||||||
|
|
||||||
const formData = ref<PromotionLinkApi.PromotionLinkItem>();
|
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
|
||||||
schema: useFormSchema(),
|
|
||||||
showDefaultActions: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const id = ref();
|
|
||||||
const [Drawer, drawerApi] = useVbenDrawer({
|
|
||||||
async onConfirm() {
|
|
||||||
const { valid } = await formApi.validate();
|
|
||||||
if (!valid) return;
|
|
||||||
const values =
|
|
||||||
await formApi.getValues<Omit<PromotionLinkApi.PromotionLinkItem, 'id'>>();
|
|
||||||
drawerApi.lock();
|
|
||||||
(id.value
|
|
||||||
? updatePromotionLink(id.value, values)
|
|
||||||
: createPromotionLink(values)
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
emits('success');
|
|
||||||
drawerApi.close();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
drawerApi.unlock();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onOpenChange(isOpen) {
|
|
||||||
if (isOpen) {
|
|
||||||
const data = drawerApi.getData<PromotionLinkApi.PromotionLinkItem>();
|
|
||||||
formApi.resetForm();
|
|
||||||
if (data) {
|
|
||||||
formData.value = data;
|
|
||||||
id.value = data.id;
|
|
||||||
formApi.setValues(data);
|
|
||||||
} else {
|
|
||||||
id.value = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const getDrawerTitle = computed(() => {
|
|
||||||
return formData.value?.id
|
|
||||||
? $t('common.edit', $t('system.role.name'))
|
|
||||||
: $t('common.create', $t('system.role.name'));
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<Drawer :title="getDrawerTitle">
|
|
||||||
<Form />
|
|
||||||
</Drawer>
|
|
||||||
</template>
|
|
||||||
<style lang="css" scoped>
|
|
||||||
:deep(.ant-tree-title) {
|
|
||||||
.tree-actions {
|
|
||||||
display: none;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-tree-title:hover) {
|
|
||||||
.tree-actions {
|
|
||||||
display: flex;
|
|
||||||
flex: auto;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -15,9 +15,9 @@ const emits = defineEmits(['success']);
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const allApiList = ref<any[]>([]);
|
const allApiList = ref<any[]>([]);
|
||||||
const roleApiList = ref<any[]>([]);
|
const roleApiList = ref<any[]>([]);
|
||||||
const selectedApiIds = ref<number[]>([]);
|
const selectedApiIds = ref<string[]>([]);
|
||||||
const formData = ref<SystemRoleApi.SystemRoleItem>();
|
const formData = ref<SystemRoleApi.SystemRoleItem>();
|
||||||
const roleId = ref<number>();
|
const roleId = ref<string>();
|
||||||
|
|
||||||
const [Drawer, drawerApi] = useVbenDrawer({
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
async onConfirm() {
|
async onConfirm() {
|
||||||
@@ -141,11 +141,7 @@ const getDrawerTitle = computed(() => {
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<!-- 全选操作 -->
|
<!-- 全选操作 -->
|
||||||
<div class="flex items-center gap-4 rounded-lg bg-gray-50 p-3">
|
<div class="flex items-center gap-4 rounded-lg bg-gray-50 p-3">
|
||||||
<Checkbox
|
<Checkbox :checked="isAllSelected" :indeterminate="isIndeterminate" @change="toggleSelectAll">
|
||||||
:checked="isAllSelected"
|
|
||||||
:indeterminate="isIndeterminate"
|
|
||||||
@change="toggleSelectAll"
|
|
||||||
>
|
|
||||||
全选
|
全选
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<span class="text-sm text-gray-500">
|
<span class="text-sm text-gray-500">
|
||||||
@@ -155,38 +151,29 @@ const getDrawerTitle = computed(() => {
|
|||||||
|
|
||||||
<!-- API列表 -->
|
<!-- API列表 -->
|
||||||
<div class="max-h-96 overflow-y-auto rounded-lg border">
|
<div class="max-h-96 overflow-y-auto rounded-lg border">
|
||||||
<div
|
<div v-for="api in allApiList" :key="api.api_id"
|
||||||
v-for="api in allApiList"
|
class="flex items-center gap-3 border-b p-3 last:border-b-0 hover:bg-gray-50">
|
||||||
:key="api.api_id"
|
<Checkbox :checked="selectedApiIdsSet.has(api.api_id)" @change="
|
||||||
class="flex items-center gap-3 border-b p-3 last:border-b-0 hover:bg-gray-50"
|
(e) => {
|
||||||
>
|
if (e.target.checked) {
|
||||||
<Checkbox
|
selectedApiIds.push(api.api_id);
|
||||||
:checked="selectedApiIdsSet.has(api.api_id)"
|
} else {
|
||||||
@change="
|
const index = selectedApiIds.indexOf(api.api_id);
|
||||||
(e) => {
|
if (index > -1) {
|
||||||
if (e.target.checked) {
|
selectedApiIds.splice(index, 1);
|
||||||
selectedApiIds.push(api.api_id);
|
|
||||||
} else {
|
|
||||||
const index = selectedApiIds.indexOf(api.api_id);
|
|
||||||
if (index > -1) {
|
|
||||||
selectedApiIds.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"
|
}
|
||||||
/>
|
" />
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium">{{ api.api_name }}</span>
|
<span class="font-medium">{{ api.api_name }}</span>
|
||||||
<span
|
<span class="rounded px-2 py-1 text-xs" :class="{
|
||||||
class="rounded px-2 py-1 text-xs"
|
'bg-blue-100 text-blue-800': api.method === 'GET',
|
||||||
:class="{
|
'bg-green-100 text-green-800': api.method === 'POST',
|
||||||
'bg-blue-100 text-blue-800': api.method === 'GET',
|
'bg-orange-100 text-orange-800': api.method === 'PUT',
|
||||||
'bg-green-100 text-green-800': api.method === 'POST',
|
'bg-red-100 text-red-800': api.method === 'DELETE',
|
||||||
'bg-orange-100 text-orange-800': api.method === 'PUT',
|
}">
|
||||||
'bg-red-100 text-red-800': api.method === 'DELETE',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ api.method }}
|
{{ api.method }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ export default defineConfig(async () => {
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
// mock代理目标地址
|
// mock代理目标地址
|
||||||
// target: 'http://localhost:8888/api',
|
target: 'http://localhost:8888/api',
|
||||||
// target: 'https://www.tianyuandb.com/api',
|
// target: 'https://www.onecha.cn/api',
|
||||||
// target: 'https://www.zhinengcha.cn/api',
|
|
||||||
target: 'https://www.quannengcha./api',
|
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
57
playground/src/api/dashboard/dashboard.ts
Normal file
57
playground/src/api/dashboard/dashboard.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace DashboardApi {
|
||||||
|
export interface OrderStatistics {
|
||||||
|
today_count: number;
|
||||||
|
month_count: number;
|
||||||
|
total_count: number;
|
||||||
|
yesterday_count: number;
|
||||||
|
change_rate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RevenueStatistics {
|
||||||
|
today_amount: number;
|
||||||
|
month_amount: number;
|
||||||
|
total_amount: number;
|
||||||
|
yesterday_amount: number;
|
||||||
|
change_rate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentStatistics {
|
||||||
|
total_count: number;
|
||||||
|
today_new: number;
|
||||||
|
month_new: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfitStatistics {
|
||||||
|
today_profit: number;
|
||||||
|
month_profit: number;
|
||||||
|
total_profit: number;
|
||||||
|
today_profit_rate: number;
|
||||||
|
month_profit_rate: number;
|
||||||
|
total_profit_rate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrendData {
|
||||||
|
date: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardStatistics {
|
||||||
|
order_stats: OrderStatistics;
|
||||||
|
revenue_stats: RevenueStatistics;
|
||||||
|
agent_stats: AgentStatistics;
|
||||||
|
profit_stats: ProfitStatistics;
|
||||||
|
order_trend: TrendData[];
|
||||||
|
revenue_trend: TrendData[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDashboardStatistics(): Promise<DashboardApi.DashboardStatistics> {
|
||||||
|
return await requestClient.get<DashboardApi.DashboardStatistics>(
|
||||||
|
'/api/v1/admin/dashboard/statistics',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
2
playground/src/api/dashboard/index.ts
Normal file
2
playground/src/api/dashboard/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './dashboard';
|
||||||
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
import type { DashboardApi } from '#/api/dashboard';
|
||||||
|
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data?: DashboardApi.TrendData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const chartRef = ref<EchartsUIType>();
|
||||||
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!props.data || props.data.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dates = props.data.map((item) => item.date);
|
||||||
|
const values = props.data.map((item) => item.value);
|
||||||
|
|
||||||
|
renderEcharts({
|
||||||
|
grid: {
|
||||||
|
bottom: 0,
|
||||||
|
containLabel: true,
|
||||||
|
left: '1%',
|
||||||
|
right: '1%',
|
||||||
|
top: '2%',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
areaStyle: {},
|
||||||
|
data: values,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#019680',
|
||||||
|
},
|
||||||
|
smooth: true,
|
||||||
|
type: 'line',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
axisPointer: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#019680',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
const param = params[0];
|
||||||
|
return `${param.name}<br/>${param.seriesName}: ¥${param.value.toFixed(2)}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
boundaryGap: false,
|
||||||
|
data: dates,
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
type: 'category',
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
splitArea: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
splitNumber: 4,
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (value: number) => {
|
||||||
|
if (value >= 10000) {
|
||||||
|
return `${(value / 10000).toFixed(1)}万`;
|
||||||
|
}
|
||||||
|
return value.toString();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
() => {
|
||||||
|
updateChart();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateChart();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<EchartsUI ref="chartRef" />
|
||||||
|
</template>
|
||||||
|
|
||||||
@@ -1,72 +1,62 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
import type { DashboardApi } from '#/api/dashboard';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data?: DashboardApi.TrendData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
||||||
onMounted(() => {
|
const updateChart = () => {
|
||||||
|
if (!props.data || props.data.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dates = props.data.map((item) => item.date);
|
||||||
|
const values = props.data.map((item) => item.value);
|
||||||
|
|
||||||
renderEcharts({
|
renderEcharts({
|
||||||
grid: {
|
grid: {
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
containLabel: true,
|
containLabel: true,
|
||||||
left: '1%',
|
left: '1%',
|
||||||
right: '1%',
|
right: '1%',
|
||||||
top: '2 %',
|
top: '2%',
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
areaStyle: {},
|
areaStyle: {},
|
||||||
data: [
|
data: values,
|
||||||
111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,
|
|
||||||
36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,
|
|
||||||
111,
|
|
||||||
],
|
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: '#5ab1ef',
|
color: '#5ab1ef',
|
||||||
},
|
},
|
||||||
smooth: true,
|
smooth: true,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
areaStyle: {},
|
|
||||||
data: [
|
|
||||||
33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,
|
|
||||||
11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,
|
|
||||||
],
|
|
||||||
itemStyle: {
|
|
||||||
color: '#019680',
|
|
||||||
},
|
|
||||||
smooth: true,
|
|
||||||
type: 'line',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
tooltip: {
|
tooltip: {
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: '#019680',
|
color: '#5ab1ef',
|
||||||
width: 1,
|
width: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
},
|
},
|
||||||
// xAxis: {
|
|
||||||
// axisTick: {
|
|
||||||
// show: false,
|
|
||||||
// },
|
|
||||||
// boundaryGap: false,
|
|
||||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
|
||||||
// type: 'category',
|
|
||||||
// },
|
|
||||||
xAxis: {
|
xAxis: {
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
data: dates,
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'solid',
|
type: 'solid',
|
||||||
@@ -81,7 +71,6 @@ onMounted(() => {
|
|||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
max: 80_000,
|
|
||||||
splitArea: {
|
splitArea: {
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
@@ -90,6 +79,18 @@ onMounted(() => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
() => {
|
||||||
|
updateChart();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateChart();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import type { AnalysisOverviewItem } from '@vben/common-ui';
|
import type { AnalysisOverviewItem } from '@vben/common-ui';
|
||||||
import type { TabOption } from '@vben/types';
|
import type { TabOption } from '@vben/types';
|
||||||
|
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnalysisChartCard,
|
AnalysisChartCard,
|
||||||
AnalysisChartsTabs,
|
AnalysisChartsTabs,
|
||||||
@@ -13,78 +15,211 @@ import {
|
|||||||
SvgCardIcon,
|
SvgCardIcon,
|
||||||
SvgDownloadIcon,
|
SvgDownloadIcon,
|
||||||
} from '@vben/icons';
|
} from '@vben/icons';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getDashboardStatistics } from '#/api/dashboard';
|
||||||
|
import type { DashboardApi } from '#/api/dashboard';
|
||||||
|
|
||||||
import AnalyticsTrends from './analytics-trends.vue';
|
import AnalyticsTrends from './analytics-trends.vue';
|
||||||
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
import AnalyticsRevenueTrend from './analytics-revenue-trend.vue';
|
||||||
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
|
||||||
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
|
||||||
import AnalyticsVisits from './analytics-visits.vue';
|
|
||||||
|
|
||||||
const overviewItems: AnalysisOverviewItem[] = [
|
const loading = ref(false);
|
||||||
{
|
const statistics = ref<DashboardApi.DashboardStatistics | null>(null);
|
||||||
icon: SvgCardIcon,
|
|
||||||
title: '用户量',
|
// 格式化金额
|
||||||
totalTitle: '总用户量',
|
const formatAmount = (amount: number) => {
|
||||||
totalValue: 120_000,
|
if (amount >= 10000) {
|
||||||
value: 2000,
|
return `${(amount / 10000).toFixed(2)}万`;
|
||||||
},
|
}
|
||||||
{
|
return amount.toFixed(2);
|
||||||
icon: SvgCakeIcon,
|
};
|
||||||
title: '访问量',
|
|
||||||
totalTitle: '总访问量',
|
// 格式化数字
|
||||||
totalValue: 500_000,
|
const formatNumber = (num: number) => {
|
||||||
value: 20_000,
|
if (num >= 10000) {
|
||||||
},
|
return `${(num / 10000).toFixed(2)}万`;
|
||||||
{
|
}
|
||||||
icon: SvgDownloadIcon,
|
return num.toString();
|
||||||
title: '下载量',
|
};
|
||||||
totalTitle: '总下载量',
|
|
||||||
totalValue: 120_000,
|
// 加载统计数据
|
||||||
value: 8000,
|
const loadStatistics = async () => {
|
||||||
},
|
loading.value = true;
|
||||||
{
|
try {
|
||||||
icon: SvgBellIcon,
|
const data = await getDashboardStatistics();
|
||||||
title: '使用量',
|
statistics.value = data;
|
||||||
totalTitle: '总使用量',
|
} catch (error) {
|
||||||
totalValue: 50_000,
|
message.error('加载统计数据失败');
|
||||||
value: 5000,
|
} finally {
|
||||||
},
|
loading.value = false;
|
||||||
];
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算概览卡片数据
|
||||||
|
const overviewItems = computed<AnalysisOverviewItem[]>(() => {
|
||||||
|
if (!statistics.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { order_stats, revenue_stats, agent_stats, profit_stats } =
|
||||||
|
statistics.value;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
icon: SvgCardIcon,
|
||||||
|
title: '今日订单',
|
||||||
|
totalTitle: '总订单',
|
||||||
|
totalValue: order_stats.total_count,
|
||||||
|
value: order_stats.today_count,
|
||||||
|
suffix: order_stats.change_rate
|
||||||
|
? ` ${order_stats.change_rate > 0 ? '↑' : '↓'} ${Math.abs(
|
||||||
|
order_stats.change_rate,
|
||||||
|
).toFixed(1)}%`
|
||||||
|
: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SvgCakeIcon,
|
||||||
|
title: '今日营收',
|
||||||
|
totalTitle: '总营收',
|
||||||
|
totalValue: revenue_stats.total_amount,
|
||||||
|
value: revenue_stats.today_amount,
|
||||||
|
suffix: revenue_stats.change_rate
|
||||||
|
? ` ${revenue_stats.change_rate > 0 ? '↑' : '↓'} ${Math.abs(
|
||||||
|
revenue_stats.change_rate,
|
||||||
|
).toFixed(1)}%`
|
||||||
|
: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SvgBellIcon,
|
||||||
|
title: '代理总数',
|
||||||
|
totalTitle: '代理总数',
|
||||||
|
totalValue: agent_stats.total_count,
|
||||||
|
value: agent_stats.today_new,
|
||||||
|
suffix: ` 今日新增: ${agent_stats.today_new}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SvgDownloadIcon,
|
||||||
|
title: '今日利润',
|
||||||
|
totalTitle: '总利润',
|
||||||
|
totalValue: profit_stats.total_profit,
|
||||||
|
value: profit_stats.today_profit,
|
||||||
|
suffix: ` 利润率: ${profit_stats.today_profit_rate.toFixed(1)}%`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
const chartTabs: TabOption[] = [
|
const chartTabs: TabOption[] = [
|
||||||
{
|
{
|
||||||
label: '流量趋势',
|
label: '订单趋势',
|
||||||
value: 'trends',
|
value: 'order',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '月访问量',
|
label: '营收趋势',
|
||||||
value: 'visits',
|
value: 'revenue',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadStatistics();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<AnalysisOverview :items="overviewItems" />
|
<a-spin :spinning="loading">
|
||||||
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
|
<div v-if="statistics">
|
||||||
<template #trends>
|
<!-- 统计卡片 -->
|
||||||
<AnalyticsTrends />
|
<AnalysisOverview :items="overviewItems" />
|
||||||
</template>
|
|
||||||
<template #visits>
|
|
||||||
<AnalyticsVisits />
|
|
||||||
</template>
|
|
||||||
</AnalysisChartsTabs>
|
|
||||||
|
|
||||||
<div class="mt-5 w-full md:flex">
|
<!-- 趋势图表 -->
|
||||||
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
|
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
|
||||||
<AnalyticsVisitsData />
|
<template #order>
|
||||||
</AnalysisChartCard>
|
<AnalyticsTrends :data="statistics.order_trend" />
|
||||||
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
|
</template>
|
||||||
<AnalyticsVisitsSource />
|
<template #revenue>
|
||||||
</AnalysisChartCard>
|
<AnalyticsRevenueTrend :data="statistics.revenue_trend" />
|
||||||
<AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">
|
</template>
|
||||||
<AnalyticsVisitsSales />
|
</AnalysisChartsTabs>
|
||||||
</AnalysisChartCard>
|
|
||||||
</div>
|
<!-- 详细统计信息 -->
|
||||||
|
<div class="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||||
|
<!-- 订单统计卡片 -->
|
||||||
|
<AnalysisChartCard title="订单统计">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">今日订单:</span>
|
||||||
|
<span class="font-semibold">{{ statistics.order_stats.today_count }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">当月订单:</span>
|
||||||
|
<span class="font-semibold">{{ formatNumber(statistics.order_stats.month_count) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">总订单:</span>
|
||||||
|
<span class="font-semibold">{{ formatNumber(statistics.order_stats.total_count) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnalysisChartCard>
|
||||||
|
|
||||||
|
<!-- 营收统计卡片 -->
|
||||||
|
<AnalysisChartCard title="营收统计">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">今日营收:</span>
|
||||||
|
<span class="font-semibold">¥{{ formatAmount(statistics.revenue_stats.today_amount) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">当月营收:</span>
|
||||||
|
<span class="font-semibold">¥{{ formatAmount(statistics.revenue_stats.month_amount) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">总营收:</span>
|
||||||
|
<span class="font-semibold">¥{{ formatAmount(statistics.revenue_stats.total_amount) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnalysisChartCard>
|
||||||
|
|
||||||
|
<!-- 代理统计卡片 -->
|
||||||
|
<AnalysisChartCard title="代理统计">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">代理总数:</span>
|
||||||
|
<span class="font-semibold">{{ statistics.agent_stats.total_count }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">今日新增:</span>
|
||||||
|
<span class="font-semibold">{{ statistics.agent_stats.today_new }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">当月新增:</span>
|
||||||
|
<span class="font-semibold">{{ statistics.agent_stats.month_new }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnalysisChartCard>
|
||||||
|
|
||||||
|
<!-- 利润统计卡片 -->
|
||||||
|
<AnalysisChartCard title="利润统计">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">今日利润:</span>
|
||||||
|
<span class="font-semibold">¥{{ formatAmount(statistics.profit_stats.today_profit) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">当月利润:</span>
|
||||||
|
<span class="font-semibold">¥{{ formatAmount(statistics.profit_stats.month_profit) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">总利润:</span>
|
||||||
|
<span class="font-semibold">¥{{ formatAmount(statistics.profit_stats.total_profit) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600">利润率:</span>
|
||||||
|
<span class="font-semibold">{{ statistics.profit_stats.total_profit_rate.toFixed(1) }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnalysisChartCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
534
代理管理菜单路由配置清单.md
Normal file
534
代理管理菜单路由配置清单.md
Normal file
@@ -0,0 +1,534 @@
|
|||||||
|
# 代理管理菜单路由配置清单
|
||||||
|
|
||||||
|
本文档列出了重构后的代理管理菜单路由配置,用于配置到数据库的 `admin_menu` 表中。
|
||||||
|
|
||||||
|
## 菜单结构
|
||||||
|
|
||||||
|
```
|
||||||
|
代理管理 (父菜单)
|
||||||
|
├── 代理列表
|
||||||
|
├── 推广链接
|
||||||
|
├── 佣金记录
|
||||||
|
├── 返佣记录 (新增)
|
||||||
|
├── 升级记录 (新增)
|
||||||
|
├── 订单记录 (新增)
|
||||||
|
├── 提现记录
|
||||||
|
├── 邀请码管理 (新增)
|
||||||
|
├── 系统配置 (新增)
|
||||||
|
├── 实名认证 (新增)
|
||||||
|
└── 产品配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据库配置说明
|
||||||
|
|
||||||
|
**表名**: `admin_menu`
|
||||||
|
|
||||||
|
**字段说明**:
|
||||||
|
- `pid`: 父菜单ID(0表示顶级菜单)
|
||||||
|
- `name`: 路由名称(Route Name)
|
||||||
|
- `path`: 路由路径
|
||||||
|
- `component`: 组件路径(前端视图组件路径)
|
||||||
|
- `redirect`: 重定向路径(可选)
|
||||||
|
- `meta`: 元数据JSON字符串,包含 `title`(标题)、`icon`(图标)、`order`(排序)
|
||||||
|
- `status`: 状态(1=启用,0=禁用)
|
||||||
|
- `type`: 菜单类型(catalog=目录,menu=菜单)
|
||||||
|
- `sort`: 排序号
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 父菜单:代理管理
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": 0,
|
||||||
|
"name": "Agent",
|
||||||
|
"path": "/agent",
|
||||||
|
"component": "",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"代理管理\",\"icon\":\"mdi:account-group\",\"order\":2000}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "catalog",
|
||||||
|
"sort": 2000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**:
|
||||||
|
- 父菜单ID设为 `0` 或插入后获取的实际ID
|
||||||
|
- `type` 使用字典值 `catalog`(目录类型)
|
||||||
|
- `component` 留空(因为这是父菜单,不直接渲染组件)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 子菜单配置
|
||||||
|
|
||||||
|
### 2.1 代理列表
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentList",
|
||||||
|
"path": "/agent/list",
|
||||||
|
"component": "/views/agent/agent-list/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"代理列表\",\"icon\":\"mdi:account-multiple\",\"order\":2001}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2001
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 推广链接
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentLinks",
|
||||||
|
"path": "/agent/links",
|
||||||
|
"component": "/views/agent/agent-links/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"推广链接\",\"icon\":\"mdi:link-variant\",\"order\":2002}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2002
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 佣金记录
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentCommission",
|
||||||
|
"path": "/agent/commission",
|
||||||
|
"component": "/views/agent/agent-commission/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"佣金记录\",\"icon\":\"mdi:cash-multiple\",\"order\":2003}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2003
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 返佣记录 (新增)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentRebate",
|
||||||
|
"path": "/agent/rebate",
|
||||||
|
"component": "/views/agent/agent-rebate/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"返佣记录\",\"icon\":\"mdi:currency-usd\",\"order\":2004}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2004
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 升级记录 (新增)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentUpgrade",
|
||||||
|
"path": "/agent/upgrade",
|
||||||
|
"component": "/views/agent/agent-upgrade/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"升级记录\",\"icon\":\"mdi:arrow-up-circle\",\"order\":2005}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2005
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.6 订单记录 (新增)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentOrder",
|
||||||
|
"path": "/agent/order",
|
||||||
|
"component": "/views/agent/agent-order/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"订单记录\",\"icon\":\"mdi:package-variant\",\"order\":2006}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2006
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.7 提现记录
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentWithdrawal",
|
||||||
|
"path": "/agent/withdrawal",
|
||||||
|
"component": "/views/agent/agent-withdrawal/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"提现记录\",\"icon\":\"mdi:bank-transfer-out\",\"order\":2007}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2007
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.8 邀请码管理 (新增)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentInviteCode",
|
||||||
|
"path": "/agent/invite-code",
|
||||||
|
"component": "/views/agent/agent-invite-code/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"邀请码管理\",\"icon\":\"mdi:ticket-confirmation\",\"order\":2008}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2008
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.9 系统配置 (新增)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentConfig",
|
||||||
|
"path": "/agent/config",
|
||||||
|
"component": "/views/agent/agent-config/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"系统配置\",\"icon\":\"mdi:cog\",\"order\":2009}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2009
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.10 实名认证 (新增)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentRealName",
|
||||||
|
"path": "/agent/real-name",
|
||||||
|
"component": "/views/agent/agent-real-name/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"实名认证\",\"icon\":\"mdi:account-check\",\"order\":2010}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2010
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.11 产品配置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pid": "[父菜单ID - Agent的ID]",
|
||||||
|
"name": "AgentProductConfig",
|
||||||
|
"path": "/agent/product-config",
|
||||||
|
"component": "/views/agent/agent-product-config/list",
|
||||||
|
"redirect": "",
|
||||||
|
"meta": "{\"title\":\"产品配置\",\"icon\":\"mdi:package-variant-closed\",\"order\":2011}",
|
||||||
|
"status": 1,
|
||||||
|
"type": "menu",
|
||||||
|
"sort": 2011
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 需要删除的旧菜单 (如果存在)
|
||||||
|
|
||||||
|
以下菜单应该从数据库中删除(已移除的功能):
|
||||||
|
|
||||||
|
1. **上级抽佣记录** - `AgentCommissionDeduction`
|
||||||
|
- 路径: `/agent/commission-deduction` 或类似路径
|
||||||
|
|
||||||
|
2. **平台抽佣记录** - `AgentPlatformDeduction`
|
||||||
|
- 路径: `/agent/platform-deduction` 或类似路径
|
||||||
|
|
||||||
|
3. **会员充值订单** - `MembershipRechargeOrder`
|
||||||
|
- 路径: `/agent/membership-recharge-order` 或类似路径
|
||||||
|
|
||||||
|
4. **会员配置** - `AgentMembershipConfig`
|
||||||
|
- 路径: `/agent/membership-config` 或类似路径
|
||||||
|
|
||||||
|
5. **奖励记录** - `AgentReward` (如果存在独立菜单)
|
||||||
|
- 路径: `/agent/reward` 或类似路径
|
||||||
|
- **注意**: 已被"返佣记录"替代
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. SQL 插入示例 (MySQL)
|
||||||
|
|
||||||
|
假设父菜单ID为 `100`(请根据实际情况替换):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 插入代理列表
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentList',
|
||||||
|
'/agent/list',
|
||||||
|
'/views/agent/agent-list/list',
|
||||||
|
'',
|
||||||
|
'{"title":"代理列表","icon":"mdi:account-multiple","order":2001}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2001,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入推广链接
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentLinks',
|
||||||
|
'/agent/links',
|
||||||
|
'/views/agent/agent-links/list',
|
||||||
|
'',
|
||||||
|
'{"title":"推广链接","icon":"mdi:link-variant","order":2002}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2002,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入佣金记录
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentCommission',
|
||||||
|
'/agent/commission',
|
||||||
|
'/views/agent/agent-commission/list',
|
||||||
|
'',
|
||||||
|
'{"title":"佣金记录","icon":"mdi:cash-multiple","order":2003}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2003,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入返佣记录 (新增)
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentRebate',
|
||||||
|
'/agent/rebate',
|
||||||
|
'/views/agent/agent-rebate/list',
|
||||||
|
'',
|
||||||
|
'{"title":"返佣记录","icon":"mdi:currency-usd","order":2004}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2004,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入升级记录 (新增)
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentUpgrade',
|
||||||
|
'/agent/upgrade',
|
||||||
|
'/views/agent/agent-upgrade/list',
|
||||||
|
'',
|
||||||
|
'{"title":"升级记录","icon":"mdi:arrow-up-circle","order":2005}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2005,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入订单记录 (新增)
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentOrder',
|
||||||
|
'/agent/order',
|
||||||
|
'/views/agent/agent-order/list',
|
||||||
|
'',
|
||||||
|
'{"title":"订单记录","icon":"mdi:package-variant","order":2006}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2006,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入提现记录
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentWithdrawal',
|
||||||
|
'/agent/withdrawal',
|
||||||
|
'/views/agent/agent-withdrawal/list',
|
||||||
|
'',
|
||||||
|
'{"title":"提现记录","icon":"mdi:bank-transfer-out","order":2007}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2007,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入邀请码管理 (新增)
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentInviteCode',
|
||||||
|
'/agent/invite-code',
|
||||||
|
'/views/agent/agent-invite-code/list',
|
||||||
|
'',
|
||||||
|
'{"title":"邀请码管理","icon":"mdi:ticket-confirmation","order":2008}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2008,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入系统配置 (新增)
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentConfig',
|
||||||
|
'/agent/config',
|
||||||
|
'/views/agent/agent-config/list',
|
||||||
|
'',
|
||||||
|
'{"title":"系统配置","icon":"mdi:cog","order":2009}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2009,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入实名认证 (新增)
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentRealName',
|
||||||
|
'/agent/real-name',
|
||||||
|
'/views/agent/agent-real-name/list',
|
||||||
|
'',
|
||||||
|
'{"title":"实名认证","icon":"mdi:account-check","order":2010}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2010,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入产品配置
|
||||||
|
INSERT INTO `admin_menu` (`pid`, `name`, `path`, `component`, `redirect`, `meta`, `status`, `type`, `sort`, `create_time`, `update_time`, `del_state`, `version`)
|
||||||
|
VALUES (
|
||||||
|
100,
|
||||||
|
'AgentProductConfig',
|
||||||
|
'/agent/product-config',
|
||||||
|
'/views/agent/agent-product-config/list',
|
||||||
|
'',
|
||||||
|
'{"title":"产品配置","icon":"mdi:package-variant-closed","order":2011}',
|
||||||
|
1,
|
||||||
|
'menu',
|
||||||
|
2011,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 配置步骤
|
||||||
|
|
||||||
|
1. **查找或创建父菜单**:
|
||||||
|
- 查询数据库中是否已存在 `name = 'Agent'` 且 `path = '/agent'` 的菜单
|
||||||
|
- 如果不存在,先插入父菜单(`pid = 0`,`type = 'catalog'`)
|
||||||
|
- 记录父菜单的ID,后续子菜单会使用
|
||||||
|
|
||||||
|
2. **删除旧菜单** (如果存在):
|
||||||
|
- 删除"上级抽佣记录"、"平台抽佣记录"、"会员充值订单"、"会员配置"等旧菜单
|
||||||
|
- 如果存在"奖励记录"独立菜单,也删除(已改为"返佣记录")
|
||||||
|
|
||||||
|
3. **更新现有菜单**:
|
||||||
|
- 检查并更新现有菜单的路径、组件路径等信息(如有变化)
|
||||||
|
|
||||||
|
4. **插入新菜单**:
|
||||||
|
- 按照上面的SQL示例插入新的菜单项
|
||||||
|
- 确保 `pid` 使用正确的父菜单ID
|
||||||
|
- 确保 `sort` 和 `meta.order` 保持一致的排序
|
||||||
|
|
||||||
|
5. **验证**:
|
||||||
|
- 登录后台管理系统,检查菜单是否正常显示
|
||||||
|
- 点击每个菜单项,确认路由跳转正常
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 注意事项
|
||||||
|
|
||||||
|
1. **菜单类型 (type)**:
|
||||||
|
- 父菜单使用 `catalog`(目录类型)
|
||||||
|
- 子菜单使用 `menu`(菜单类型)
|
||||||
|
- 需要确保这些类型值在系统的字典表 `admin_menu_type` 中存在
|
||||||
|
|
||||||
|
2. **组件路径 (component)**:
|
||||||
|
- 路径格式为:`/views/agent/xxx/list`
|
||||||
|
- 前端会自动添加 `.vue` 后缀
|
||||||
|
|
||||||
|
3. **Meta字段格式**:
|
||||||
|
- 必须是有效的JSON字符串
|
||||||
|
- 包含 `title`(标题)、`icon`(图标)、`order`(排序)
|
||||||
|
|
||||||
|
4. **排序号 (sort)**:
|
||||||
|
- 建议与 `meta.order` 保持一致
|
||||||
|
- 数值越小,排序越靠前
|
||||||
|
|
||||||
|
5. **状态 (status)**:
|
||||||
|
- `1` = 启用
|
||||||
|
- `0` = 禁用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 快速检查清单
|
||||||
|
|
||||||
|
- [ ] 父菜单"代理管理"已存在或已创建
|
||||||
|
- [ ] 已删除旧的菜单项(上级抽佣、平台抽佣、会员充值订单、会员配置)
|
||||||
|
- [ ] 所有新菜单项已插入数据库
|
||||||
|
- [ ] 所有菜单项的 `pid` 正确指向父菜单ID
|
||||||
|
- [ ] 所有菜单项的 `component` 路径正确
|
||||||
|
- [ ] 所有菜单项的 `meta` JSON格式正确
|
||||||
|
- [ ] 菜单排序号(sort)设置合理
|
||||||
|
- [ ] 菜单状态(status)为启用(1)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**配置完成后,刷新后台管理系统页面,新的菜单结构就会生效。**
|
||||||
|
|
||||||
Reference in New Issue
Block a user