将单套餐分配和套餐系列分配改成代理系列授权
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m23s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m23s
This commit is contained in:
@@ -23,8 +23,7 @@ export { DeviceService } from './device'
|
|||||||
export { CarrierService } from './carrier'
|
export { CarrierService } from './carrier'
|
||||||
export { PackageSeriesService } from './packageSeries'
|
export { PackageSeriesService } from './packageSeries'
|
||||||
export { PackageManageService } from './packageManage'
|
export { PackageManageService } from './packageManage'
|
||||||
export { ShopPackageAllocationService } from './shopPackageAllocation'
|
export { ShopSeriesGrantService } from './shopSeriesGrant'
|
||||||
export { ShopSeriesAllocationService } from './shopSeriesAllocation'
|
|
||||||
export { OrderService } from './order'
|
export { OrderService } from './order'
|
||||||
|
|
||||||
// TODO: 按需添加其他业务模块
|
// TODO: 按需添加其他业务模块
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
/**
|
|
||||||
* 单套餐分配 API 服务
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { BaseService } from '../BaseService'
|
|
||||||
import type {
|
|
||||||
ShopPackageAllocationResponse,
|
|
||||||
ShopPackageAllocationQueryParams,
|
|
||||||
CreateShopPackageAllocationRequest,
|
|
||||||
UpdateShopPackageAllocationRequest,
|
|
||||||
UpdateShopPackageAllocationStatusRequest,
|
|
||||||
BaseResponse,
|
|
||||||
PaginationResponse
|
|
||||||
} from '@/types/api'
|
|
||||||
|
|
||||||
export class ShopPackageAllocationService extends BaseService {
|
|
||||||
/**
|
|
||||||
* 获取单套餐分配列表
|
|
||||||
* GET /api/admin/shop-package-allocations
|
|
||||||
* @param params 查询参数
|
|
||||||
*/
|
|
||||||
static getShopPackageAllocations(
|
|
||||||
params?: ShopPackageAllocationQueryParams
|
|
||||||
): Promise<PaginationResponse<ShopPackageAllocationResponse>> {
|
|
||||||
return this.getPage<ShopPackageAllocationResponse>(
|
|
||||||
'/api/admin/shop-package-allocations',
|
|
||||||
params
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建单套餐分配
|
|
||||||
* POST /api/admin/shop-package-allocations
|
|
||||||
* @param data 分配数据
|
|
||||||
*/
|
|
||||||
static createShopPackageAllocation(
|
|
||||||
data: CreateShopPackageAllocationRequest
|
|
||||||
): Promise<BaseResponse<ShopPackageAllocationResponse>> {
|
|
||||||
return this.create<ShopPackageAllocationResponse>('/api/admin/shop-package-allocations', data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单套餐分配详情
|
|
||||||
* GET /api/admin/shop-package-allocations/{id}
|
|
||||||
* @param id 分配ID
|
|
||||||
*/
|
|
||||||
static getShopPackageAllocationDetail(
|
|
||||||
id: number
|
|
||||||
): Promise<BaseResponse<ShopPackageAllocationResponse>> {
|
|
||||||
return this.getOne<ShopPackageAllocationResponse>(`/api/admin/shop-package-allocations/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新单套餐分配
|
|
||||||
* PUT /api/admin/shop-package-allocations/{id}
|
|
||||||
* @param id 分配ID
|
|
||||||
* @param data 分配数据(只允许修改成本价)
|
|
||||||
*/
|
|
||||||
static updateShopPackageAllocation(
|
|
||||||
id: number,
|
|
||||||
data: UpdateShopPackageAllocationRequest
|
|
||||||
): Promise<BaseResponse<ShopPackageAllocationResponse>> {
|
|
||||||
return this.update<ShopPackageAllocationResponse>(
|
|
||||||
`/api/admin/shop-package-allocations/${id}`,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除单套餐分配
|
|
||||||
* DELETE /api/admin/shop-package-allocations/{id}
|
|
||||||
* @param id 分配ID
|
|
||||||
*/
|
|
||||||
static deleteShopPackageAllocation(id: number): Promise<BaseResponse> {
|
|
||||||
return this.remove(`/api/admin/shop-package-allocations/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新单套餐分配成本价
|
|
||||||
* PUT /api/admin/shop-package-allocations/{id}/cost-price
|
|
||||||
* @param id 分配ID
|
|
||||||
* @param costPrice 成本价(分)
|
|
||||||
*/
|
|
||||||
static updateShopPackageAllocationCostPrice(
|
|
||||||
id: number,
|
|
||||||
costPrice: number
|
|
||||||
): Promise<BaseResponse<ShopPackageAllocationResponse>> {
|
|
||||||
return this.put<BaseResponse<ShopPackageAllocationResponse>>(
|
|
||||||
`/api/admin/shop-package-allocations/${id}/cost-price`,
|
|
||||||
{ cost_price: costPrice }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新单套餐分配状态
|
|
||||||
* PUT /api/admin/shop-package-allocations/{id}/status
|
|
||||||
* @param id 分配ID
|
|
||||||
* @param status 状态 (1:启用, 2:禁用)
|
|
||||||
*/
|
|
||||||
static updateShopPackageAllocationStatus(id: number, status: number): Promise<BaseResponse> {
|
|
||||||
const data: UpdateShopPackageAllocationStatusRequest = { status }
|
|
||||||
return this.put<BaseResponse>(`/api/admin/shop-package-allocations/${id}/status`, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/**
|
|
||||||
* 套餐系列分配 API 服务
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { BaseService } from '../BaseService'
|
|
||||||
import type {
|
|
||||||
ShopSeriesAllocationResponse,
|
|
||||||
ShopSeriesAllocationQueryParams,
|
|
||||||
CreateShopSeriesAllocationRequest,
|
|
||||||
UpdateShopSeriesAllocationRequest,
|
|
||||||
UpdateShopSeriesAllocationStatusRequest,
|
|
||||||
BaseResponse,
|
|
||||||
PaginationResponse
|
|
||||||
} from '@/types/api'
|
|
||||||
|
|
||||||
export class ShopSeriesAllocationService extends BaseService {
|
|
||||||
/**
|
|
||||||
* 获取套餐系列分配分页列表
|
|
||||||
* GET /api/admin/shop-series-allocations
|
|
||||||
* @param params 查询参数
|
|
||||||
*/
|
|
||||||
static getShopSeriesAllocations(
|
|
||||||
params?: ShopSeriesAllocationQueryParams
|
|
||||||
): Promise<PaginationResponse<ShopSeriesAllocationResponse>> {
|
|
||||||
return this.getPage<ShopSeriesAllocationResponse>('/api/admin/shop-series-allocations', params)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建套餐系列分配
|
|
||||||
* POST /api/admin/shop-series-allocations
|
|
||||||
* @param data 分配数据
|
|
||||||
*/
|
|
||||||
static createShopSeriesAllocation(
|
|
||||||
data: CreateShopSeriesAllocationRequest
|
|
||||||
): Promise<BaseResponse<ShopSeriesAllocationResponse>> {
|
|
||||||
return this.create<ShopSeriesAllocationResponse>('/api/admin/shop-series-allocations', data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取套餐系列分配详情
|
|
||||||
* GET /api/admin/shop-series-allocations/{id}
|
|
||||||
* @param id 分配ID
|
|
||||||
*/
|
|
||||||
static getShopSeriesAllocationDetail(
|
|
||||||
id: number
|
|
||||||
): Promise<BaseResponse<ShopSeriesAllocationResponse>> {
|
|
||||||
return this.getOne<ShopSeriesAllocationResponse>(`/api/admin/shop-series-allocations/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新套餐系列分配
|
|
||||||
* PUT /api/admin/shop-series-allocations/{id}
|
|
||||||
* @param id 分配ID
|
|
||||||
* @param data 分配数据
|
|
||||||
*/
|
|
||||||
static updateShopSeriesAllocation(
|
|
||||||
id: number,
|
|
||||||
data: UpdateShopSeriesAllocationRequest
|
|
||||||
): Promise<BaseResponse<ShopSeriesAllocationResponse>> {
|
|
||||||
return this.update<ShopSeriesAllocationResponse>(
|
|
||||||
`/api/admin/shop-series-allocations/${id}`,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除套餐系列分配
|
|
||||||
* DELETE /api/admin/shop-series-allocations/{id}
|
|
||||||
* @param id 分配ID
|
|
||||||
*/
|
|
||||||
static deleteShopSeriesAllocation(id: number): Promise<BaseResponse> {
|
|
||||||
return this.remove(`/api/admin/shop-series-allocations/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新套餐系列分配状态
|
|
||||||
* PUT /api/admin/shop-series-allocations/{id}/status
|
|
||||||
* @param id 分配ID
|
|
||||||
* @param status 状态 (1:启用, 2:禁用)
|
|
||||||
*/
|
|
||||||
static updateShopSeriesAllocationStatus(id: number, status: number): Promise<BaseResponse> {
|
|
||||||
const data: UpdateShopSeriesAllocationStatusRequest = { status }
|
|
||||||
return this.put<BaseResponse>(`/api/admin/shop-series-allocations/${id}/status`, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
90
src/api/modules/shopSeriesGrant.ts
Normal file
90
src/api/modules/shopSeriesGrant.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* 代理系列授权 API 服务
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseService } from '../BaseService'
|
||||||
|
import type {
|
||||||
|
ShopSeriesGrantResponse,
|
||||||
|
ShopSeriesGrantQueryParams,
|
||||||
|
CreateShopSeriesGrantRequest,
|
||||||
|
UpdateShopSeriesGrantRequest,
|
||||||
|
ManageGrantPackagesRequest,
|
||||||
|
BaseResponse,
|
||||||
|
PaginationResponse
|
||||||
|
} from '@/types/api'
|
||||||
|
|
||||||
|
export class ShopSeriesGrantService extends BaseService {
|
||||||
|
/**
|
||||||
|
* 获取代理系列授权分页列表
|
||||||
|
* GET /api/admin/shop-series-grants
|
||||||
|
* @param params 查询参数
|
||||||
|
*/
|
||||||
|
static getShopSeriesGrants(
|
||||||
|
params?: ShopSeriesGrantQueryParams
|
||||||
|
): Promise<PaginationResponse<ShopSeriesGrantResponse>> {
|
||||||
|
return this.getPage<ShopSeriesGrantResponse>('/api/admin/shop-series-grants', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建代理系列授权
|
||||||
|
* POST /api/admin/shop-series-grants
|
||||||
|
* @param data 授权数据
|
||||||
|
*/
|
||||||
|
static createShopSeriesGrant(
|
||||||
|
data: CreateShopSeriesGrantRequest
|
||||||
|
): Promise<BaseResponse<ShopSeriesGrantResponse>> {
|
||||||
|
return this.create<ShopSeriesGrantResponse>('/api/admin/shop-series-grants', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取代理系列授权详情
|
||||||
|
* GET /api/admin/shop-series-grants/{id}
|
||||||
|
* @param id 授权ID
|
||||||
|
*/
|
||||||
|
static getShopSeriesGrantDetail(
|
||||||
|
id: number
|
||||||
|
): Promise<BaseResponse<ShopSeriesGrantResponse>> {
|
||||||
|
return this.getOne<ShopSeriesGrantResponse>(`/api/admin/shop-series-grants/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新代理系列授权
|
||||||
|
* PUT /api/admin/shop-series-grants/{id}
|
||||||
|
* @param id 授权ID
|
||||||
|
* @param data 授权数据
|
||||||
|
*/
|
||||||
|
static updateShopSeriesGrant(
|
||||||
|
id: number,
|
||||||
|
data: UpdateShopSeriesGrantRequest
|
||||||
|
): Promise<BaseResponse<ShopSeriesGrantResponse>> {
|
||||||
|
return this.update<ShopSeriesGrantResponse>(
|
||||||
|
`/api/admin/shop-series-grants/${id}`,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除代理系列授权
|
||||||
|
* DELETE /api/admin/shop-series-grants/{id}
|
||||||
|
* @param id 授权ID
|
||||||
|
*/
|
||||||
|
static deleteShopSeriesGrant(id: number): Promise<BaseResponse> {
|
||||||
|
return this.remove(`/api/admin/shop-series-grants/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理代理系列授权的套餐
|
||||||
|
* PUT /api/admin/shop-series-grants/{id}/packages
|
||||||
|
* @param id 授权ID
|
||||||
|
* @param data 套餐管理数据
|
||||||
|
*/
|
||||||
|
static manageGrantPackages(
|
||||||
|
id: number,
|
||||||
|
data: ManageGrantPackagesRequest
|
||||||
|
): Promise<BaseResponse<ShopSeriesGrantResponse>> {
|
||||||
|
return this.put<BaseResponse<ShopSeriesGrantResponse>>(
|
||||||
|
`/api/admin/shop-series-grants/${id}/packages`,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -404,10 +404,8 @@
|
|||||||
"packageList": "套餐管理",
|
"packageList": "套餐管理",
|
||||||
"packageDetail": "套餐详情",
|
"packageDetail": "套餐详情",
|
||||||
"packageChange": "套餐变更",
|
"packageChange": "套餐变更",
|
||||||
"packageAssign": "单套餐分配",
|
"seriesGrants": "代理系列授权",
|
||||||
"packageAssignDetail": "套餐分配详情",
|
"seriesGrantsDetail": "代理系列授权详情",
|
||||||
"seriesAssign": "套餐系列分配",
|
|
||||||
"seriesAssignDetail": "系列分配详情",
|
|
||||||
"packageSeries": "套餐系列",
|
"packageSeries": "套餐系列",
|
||||||
"packageSeriesDetail": "套餐系列详情",
|
"packageSeriesDetail": "套餐系列详情",
|
||||||
"packageCommission": "套餐佣金网卡"
|
"packageCommission": "套餐佣金网卡"
|
||||||
|
|||||||
@@ -760,39 +760,20 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'package-assign',
|
path: 'series-grants',
|
||||||
name: 'PackageAssign',
|
name: 'SeriesGrants',
|
||||||
component: RoutesAlias.PackageAssign,
|
component: RoutesAlias.SeriesGrants,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'menus.packageManagement.packageAssign',
|
title: 'menus.packageManagement.seriesGrants',
|
||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'package-assign/detail/:id',
|
path: 'series-grants/detail/:id',
|
||||||
name: 'PackageAssignDetail',
|
name: 'SeriesGrantsDetail',
|
||||||
component: RoutesAlias.PackageAssignDetail,
|
component: RoutesAlias.SeriesGrantsDetail,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'menus.packageManagement.packageAssignDetail',
|
title: 'menus.packageManagement.seriesGrantsDetail',
|
||||||
isHide: true,
|
|
||||||
keepAlive: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'series-assign',
|
|
||||||
name: 'SeriesAssign',
|
|
||||||
component: RoutesAlias.SeriesAssign,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.packageManagement.seriesAssign',
|
|
||||||
keepAlive: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'series-assign/detail/:id',
|
|
||||||
name: 'SeriesAssignDetail',
|
|
||||||
component: RoutesAlias.SeriesAssignDetail,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.packageManagement.seriesAssignDetail',
|
|
||||||
isHide: true,
|
isHide: true,
|
||||||
keepAlive: false
|
keepAlive: false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,10 +70,8 @@ export enum RoutesAlias {
|
|||||||
PackageList = '/package-management/package-list', // 套餐管理
|
PackageList = '/package-management/package-list', // 套餐管理
|
||||||
PackageDetail = '/package-management/package-list/detail', // 套餐详情
|
PackageDetail = '/package-management/package-list/detail', // 套餐详情
|
||||||
PackageChange = '/package-management/package-change', // 套餐变更
|
PackageChange = '/package-management/package-change', // 套餐变更
|
||||||
PackageAssign = '/package-management/package-assign', // 单套餐分配
|
SeriesGrants = '/package-management/series-grants', // 代理系列授权
|
||||||
PackageAssignDetail = '/package-management/package-assign/detail', // 单套餐分配详情
|
SeriesGrantsDetail = '/package-management/series-grants/detail', // 代理系列授权详情
|
||||||
SeriesAssign = '/package-management/series-assign', // 套餐系列分配
|
|
||||||
SeriesAssignDetail = '/package-management/series-assign/detail', // 套餐系列分配详情
|
|
||||||
PackageSeries = '/package-management/package-series', // 套餐系列
|
PackageSeries = '/package-management/package-series', // 套餐系列
|
||||||
PackageSeriesDetail = '/package-management/package-series/detail', // 套餐系列详情
|
PackageSeriesDetail = '/package-management/package-series/detail', // 套餐系列详情
|
||||||
PackageCommission = '/package-management/package-commission', // 套餐佣金网卡
|
PackageCommission = '/package-management/package-commission', // 套餐佣金网卡
|
||||||
|
|||||||
@@ -200,93 +200,58 @@ export interface SeriesSelectOption {
|
|||||||
series_code: string
|
series_code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 单套餐分配 ====================
|
// ==================== 代理系列授权 (新接口) ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单套餐分配响应
|
* 佣金梯度配置
|
||||||
*/
|
*/
|
||||||
export interface ShopPackageAllocationResponse {
|
export interface CommissionTier {
|
||||||
id: number
|
operator: '>=' // 运算符
|
||||||
|
threshold: number // 阈值
|
||||||
|
amount: number // 佣金金额(分)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 套餐信息(用于系列授权)
|
||||||
|
*/
|
||||||
|
export interface GrantPackageInfo {
|
||||||
package_id: number
|
package_id: number
|
||||||
package_code: string
|
package_name?: string
|
||||||
package_name: string
|
package_code?: string
|
||||||
series_id: number // 套餐系列ID
|
cost_price: number // 成本价(分)
|
||||||
series_name: string // 套餐系列名称
|
shelf_status?: number // 上架状态
|
||||||
|
status?: number // 状态
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理系列授权响应
|
||||||
|
*/
|
||||||
|
export interface ShopSeriesGrantResponse {
|
||||||
|
id: number
|
||||||
shop_id: number
|
shop_id: number
|
||||||
shop_name: string
|
shop_name: string
|
||||||
allocator_shop_id: number // 分配者店铺ID,0表示平台分配
|
|
||||||
allocator_shop_name: string // 分配者店铺名称
|
|
||||||
series_allocation_id?: number | null // 关联的系列分配ID(可空)
|
|
||||||
cost_price: number // 该代理的成本价(分)
|
|
||||||
status: number // 1:启用, 2:禁用
|
|
||||||
created_at: string
|
|
||||||
updated_at: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单套餐分配查询参数
|
|
||||||
*/
|
|
||||||
export interface ShopPackageAllocationQueryParams extends PaginationParams {
|
|
||||||
shop_id?: number // 店铺ID筛选
|
|
||||||
package_id?: number // 套餐ID筛选
|
|
||||||
series_allocation_id?: number // 系列分配ID筛选
|
|
||||||
allocator_shop_id?: number // 分配者店铺ID筛选
|
|
||||||
status?: number // 状态筛选
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建单套餐分配请求
|
|
||||||
*/
|
|
||||||
export interface CreateShopPackageAllocationRequest {
|
|
||||||
package_id: number // 套餐ID,必填
|
|
||||||
shop_id: number // 店铺ID,必填
|
|
||||||
cost_price: number // 覆盖的成本价(分),必填
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新单套餐分配请求
|
|
||||||
*/
|
|
||||||
export interface UpdateShopPackageAllocationRequest {
|
|
||||||
cost_price: number // 只允许修改成本价
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新单套餐分配状态请求
|
|
||||||
*/
|
|
||||||
export interface UpdateShopPackageAllocationStatusRequest {
|
|
||||||
status: number // 1:启用, 2:禁用
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 套餐系列分配 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 套餐系列分配响应
|
|
||||||
*/
|
|
||||||
export interface ShopSeriesAllocationResponse {
|
|
||||||
id: number
|
|
||||||
series_id: number
|
series_id: number
|
||||||
series_name: string
|
series_name: string
|
||||||
series_code: string // 套餐系列编码
|
series_code?: string
|
||||||
shop_id: number
|
commission_type: 'fixed' | 'tiered' // 佣金类型:固定或梯度
|
||||||
shop_name: string
|
one_time_commission_amount: number // 固定佣金金额(分)
|
||||||
allocator_shop_id: number // 分配者店铺ID,0表示平台分配
|
commission_tiers: CommissionTier[] // 梯度配置列表
|
||||||
|
force_recharge_locked: boolean // 是否被平台锁定(true时前端只读)
|
||||||
|
force_recharge_enabled: boolean // 是否启用强充
|
||||||
|
force_recharge_amount: number // 强充金额(分)
|
||||||
|
allocator_shop_id: number // 分配者店铺ID,0表示平台
|
||||||
allocator_shop_name: string // 分配者店铺名称
|
allocator_shop_name: string // 分配者店铺名称
|
||||||
enable_one_time_commission: boolean // 是否启用一次性佣金
|
package_count?: number // 套餐数量
|
||||||
one_time_commission_amount: number // 该代理能拿的一次性佣金金额上限(分)
|
packages?: GrantPackageInfo[] // 套餐列表
|
||||||
one_time_commission_threshold?: number // 一次性佣金触发阈值(分)
|
|
||||||
one_time_commission_trigger?: 'first_recharge' | 'accumulated_recharge' // 一次性佣金触发类型
|
|
||||||
enable_force_recharge: boolean // 是否启用强制充值
|
|
||||||
force_recharge_amount?: number // 强制充值金额(分)
|
|
||||||
force_recharge_trigger_type?: 1 | 2 // 强充触发类型:1(单次充值)、2(累计充值)
|
|
||||||
status: number // 1:启用, 2:禁用
|
status: number // 1:启用, 2:禁用
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 套餐系列分配查询参数
|
* 代理系列授权查询参数
|
||||||
*/
|
*/
|
||||||
export interface ShopSeriesAllocationQueryParams extends PaginationParams {
|
export interface ShopSeriesGrantQueryParams extends PaginationParams {
|
||||||
shop_id?: number // 店铺ID筛选
|
shop_id?: number // 店铺ID筛选
|
||||||
series_id?: number // 系列ID筛选
|
series_id?: number // 系列ID筛选
|
||||||
allocator_shop_id?: number // 分配者店铺ID筛选
|
allocator_shop_id?: number // 分配者店铺ID筛选
|
||||||
@@ -294,37 +259,40 @@ export interface ShopSeriesAllocationQueryParams extends PaginationParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建套餐系列分配请求
|
* 创建代理系列授权请求
|
||||||
*/
|
*/
|
||||||
export interface CreateShopSeriesAllocationRequest {
|
export interface CreateShopSeriesGrantRequest {
|
||||||
series_id: number // 套餐系列ID,必填
|
|
||||||
shop_id: number // 店铺ID,必填
|
shop_id: number // 店铺ID,必填
|
||||||
one_time_commission_amount: number // 一次性佣金金额上限(分),必填
|
series_id: number // 系列ID,必填
|
||||||
enable_one_time_commission?: boolean // 是否启用一次性佣金,可选
|
one_time_commission_amount?: number // 固定佣金金额(分),固定模式时必填
|
||||||
one_time_commission_threshold?: number // 一次性佣金触发阈值(分),可选
|
commission_tiers?: CommissionTier[] // 梯度配置列表,梯度模式时必填
|
||||||
one_time_commission_trigger?: 'first_recharge' | 'accumulated_recharge' // 一次性佣金触发类型,可选
|
enable_force_recharge?: boolean // 是否启用强充
|
||||||
enable_force_recharge?: boolean // 是否启用强制充值,可选
|
force_recharge_amount?: number // 强充金额(分)
|
||||||
force_recharge_amount?: number // 强制充值金额(分),可选
|
packages?: GrantPackageInfo[] // 套餐列表
|
||||||
force_recharge_trigger_type?: 1 | 2 // 强充触发类型,可选
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新套餐系列分配请求
|
* 更新代理系列授权请求
|
||||||
*/
|
*/
|
||||||
export interface UpdateShopSeriesAllocationRequest {
|
export interface UpdateShopSeriesGrantRequest {
|
||||||
enable_one_time_commission?: boolean // 是否启用一次性佣金
|
one_time_commission_amount?: number // 固定佣金金额(分)
|
||||||
one_time_commission_amount?: number // 一次性佣金金额上限(分)
|
commission_tiers?: CommissionTier[] // 梯度配置列表
|
||||||
one_time_commission_threshold?: number // 一次性佣金触发阈值(分)
|
enable_force_recharge?: boolean // 是否启用强充(force_recharge_locked=true时忽略)
|
||||||
one_time_commission_trigger?: 'first_recharge' | 'accumulated_recharge' // 一次性佣金触发类型
|
force_recharge_amount?: number // 强充金额(分)
|
||||||
enable_force_recharge?: boolean // 是否启用强制充值
|
|
||||||
force_recharge_amount?: number // 强制充值金额(分)
|
|
||||||
force_recharge_trigger_type?: 1 | 2 // 强充触发类型
|
|
||||||
status?: number // 状态
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新套餐系列分配状态请求
|
* 管理套餐请求中的套餐项
|
||||||
*/
|
*/
|
||||||
export interface UpdateShopSeriesAllocationStatusRequest {
|
export interface GrantPackageItem {
|
||||||
status: number // 1:启用, 2:禁用
|
package_id?: number // 套餐ID
|
||||||
|
cost_price?: number // 成本价(分)
|
||||||
|
remove?: boolean | null // 是否删除该套餐授权(true=删除)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理套餐请求
|
||||||
|
*/
|
||||||
|
export interface ManageGrantPackagesRequest {
|
||||||
|
packages?: GrantPackageItem[] | null // 套餐操作列表
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="package-assign-detail">
|
|
||||||
<ElCard shadow="never">
|
|
||||||
<!-- 页面头部 -->
|
|
||||||
<div class="detail-header">
|
|
||||||
<ElButton @click="handleBack">
|
|
||||||
<template #icon>
|
|
||||||
<ElIcon><ArrowLeft /></ElIcon>
|
|
||||||
</template>
|
|
||||||
返回
|
|
||||||
</ElButton>
|
|
||||||
<h2 class="detail-title">套餐分配详情</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 详情内容 -->
|
|
||||||
<DetailPage v-if="detailData" :sections="detailSections" :data="detailData" />
|
|
||||||
|
|
||||||
<!-- 加载中 -->
|
|
||||||
<div v-if="loading" class="loading-container">
|
|
||||||
<ElIcon class="is-loading"><Loading /></ElIcon>
|
|
||||||
<span>加载中...</span>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
|
||||||
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
|
||||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
|
||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
|
||||||
import { ShopPackageAllocationService } from '@/api/modules'
|
|
||||||
import type { ShopPackageAllocationResponse } from '@/types/api'
|
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
|
||||||
|
|
||||||
defineOptions({ name: 'PackageAssignDetail' })
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const detailData = ref<ShopPackageAllocationResponse | null>(null)
|
|
||||||
|
|
||||||
// 详情页配置
|
|
||||||
const detailSections: DetailSection[] = [
|
|
||||||
{
|
|
||||||
title: '基本信息',
|
|
||||||
fields: [
|
|
||||||
{ label: 'ID', prop: 'id' },
|
|
||||||
{ label: '套餐编码', prop: 'package_code' },
|
|
||||||
{ label: '套餐名称', prop: 'package_name' },
|
|
||||||
{ label: '系列名称', prop: 'series_name' },
|
|
||||||
{ label: '被分配店铺', prop: 'shop_name' },
|
|
||||||
{
|
|
||||||
label: '分配者店铺',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (data.allocator_shop_id === 0) {
|
|
||||||
return '平台'
|
|
||||||
}
|
|
||||||
return data.allocator_shop_name || '-'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '成本价',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return `¥${(data.cost_price / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '状态',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.status === 1 ? '启用' : '禁用'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
|
||||||
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 返回上一页
|
|
||||||
const handleBack = () => {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取详情数据
|
|
||||||
const fetchDetail = async () => {
|
|
||||||
const id = Number(route.params.id)
|
|
||||||
if (!id) {
|
|
||||||
ElMessage.error('缺少ID参数')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res = await ShopPackageAllocationService.getShopPackageAllocationDetail(id)
|
|
||||||
if (res.code === 0) {
|
|
||||||
detailData.value = res.data
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('获取详情失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchDetail()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.package-assign-detail {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
|
|
||||||
.detail-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,891 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ArtTableFullScreen>
|
|
||||||
<div class="package-assign-page" id="table-full-screen">
|
|
||||||
<!-- 搜索栏 -->
|
|
||||||
<ArtSearchBar
|
|
||||||
v-model:filter="searchForm"
|
|
||||||
:items="searchFormItems"
|
|
||||||
:show-expand="true"
|
|
||||||
label-width="85"
|
|
||||||
@reset="handleReset"
|
|
||||||
@search="handleSearch"
|
|
||||||
></ArtSearchBar>
|
|
||||||
|
|
||||||
<ElCard shadow="never" class="art-table-card">
|
|
||||||
<!-- 表格头部 -->
|
|
||||||
<ArtTableHeader
|
|
||||||
:columnList="columnOptions"
|
|
||||||
v-model:columns="columnChecks"
|
|
||||||
@refresh="handleRefresh"
|
|
||||||
>
|
|
||||||
<template #left>
|
|
||||||
<ElButton type="primary" @click="showDialog('add')" v-permission="'package_assign:add'"
|
|
||||||
>新增分配</ElButton
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</ArtTableHeader>
|
|
||||||
|
|
||||||
<!-- 表格 -->
|
|
||||||
<ArtTable
|
|
||||||
ref="tableRef"
|
|
||||||
row-key="id"
|
|
||||||
:loading="loading"
|
|
||||||
:data="allocationList"
|
|
||||||
:currentPage="pagination.page"
|
|
||||||
:pageSize="pagination.page_size"
|
|
||||||
:total="pagination.total"
|
|
||||||
:marginTop="10"
|
|
||||||
@size-change="handleSizeChange"
|
|
||||||
@current-change="handleCurrentChange"
|
|
||||||
@row-contextmenu="handleRowContextMenu"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
|
||||||
</template>
|
|
||||||
</ArtTable>
|
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
|
||||||
<ArtMenuRight
|
|
||||||
ref="contextMenuRef"
|
|
||||||
:menu-items="contextMenuItems"
|
|
||||||
:menu-width="120"
|
|
||||||
@select="handleContextMenuSelect"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 新增/编辑对话框 -->
|
|
||||||
<ElDialog
|
|
||||||
v-model="dialogVisible"
|
|
||||||
:title="dialogType === 'add' ? '新增分配' : '编辑分配'"
|
|
||||||
width="30%"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
@closed="handleDialogClosed"
|
|
||||||
>
|
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="90px">
|
|
||||||
<ElFormItem label="选择套餐" prop="package_id" v-if="dialogType === 'add'">
|
|
||||||
<ElSelect
|
|
||||||
v-model="form.package_id"
|
|
||||||
placeholder="请选择套餐"
|
|
||||||
style="width: 100%"
|
|
||||||
filterable
|
|
||||||
remote
|
|
||||||
:remote-method="searchPackage"
|
|
||||||
:loading="packageLoading"
|
|
||||||
clearable
|
|
||||||
@change="handlePackageChange"
|
|
||||||
>
|
|
||||||
<ElOption
|
|
||||||
v-for="pkg in packageOptions"
|
|
||||||
:key="pkg.id"
|
|
||||||
:label="`${pkg.package_name} (${pkg.series_name})`"
|
|
||||||
:value="pkg.id"
|
|
||||||
/>
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="选择店铺" prop="shop_id" v-if="dialogType === 'add'">
|
|
||||||
<ElTreeSelect
|
|
||||||
v-model="form.shop_id"
|
|
||||||
:data="shopTreeData"
|
|
||||||
:props="{ label: 'shop_name', value: 'id', children: 'children' }"
|
|
||||||
placeholder="请选择店铺"
|
|
||||||
style="width: 100%"
|
|
||||||
filterable
|
|
||||||
clearable
|
|
||||||
:loading="shopLoading"
|
|
||||||
check-strictly
|
|
||||||
:render-after-expand="false"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="成本价(元)" prop="cost_price">
|
|
||||||
<ElInputNumber
|
|
||||||
v-model="form.cost_price"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
:step="0.01"
|
|
||||||
:controls="false"
|
|
||||||
style="width: 100%"
|
|
||||||
placeholder="请输入成本价"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElForm>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
|
||||||
<ElButton type="primary" @click="handleSubmit(formRef)" :loading="submitLoading">
|
|
||||||
提交
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ElDialog>
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
</ArtTableFullScreen>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { h } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import {
|
|
||||||
ShopPackageAllocationService,
|
|
||||||
PackageManageService,
|
|
||||||
ShopService,
|
|
||||||
ShopSeriesAllocationService
|
|
||||||
} from '@/api/modules'
|
|
||||||
import { ElMessage, ElMessageBox, ElSwitch } from 'element-plus'
|
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
|
||||||
import type { ShopPackageAllocationResponse, PackageResponse, ShopResponse } from '@/types/api'
|
|
||||||
import type { SearchFormItem } from '@/types'
|
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
|
||||||
import { useAuth } from '@/composables/useAuth'
|
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
|
||||||
import {
|
|
||||||
CommonStatus,
|
|
||||||
getStatusText,
|
|
||||||
frontendStatusToApi,
|
|
||||||
apiStatusToFrontend
|
|
||||||
} from '@/config/constants'
|
|
||||||
|
|
||||||
defineOptions({ name: 'PackageAssign' })
|
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
const loading = ref(false)
|
|
||||||
const submitLoading = ref(false)
|
|
||||||
const packageLoading = ref(false)
|
|
||||||
const shopLoading = ref(false)
|
|
||||||
const tableRef = ref()
|
|
||||||
const formRef = ref<FormInstance>()
|
|
||||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
|
||||||
const currentRow = ref<ShopPackageAllocationResponse | null>(null)
|
|
||||||
const packageOptions = ref<PackageResponse[]>([])
|
|
||||||
const shopOptions = ref<ShopResponse[]>([])
|
|
||||||
const shopTreeData = ref<ShopResponse[]>([])
|
|
||||||
const searchPackageOptions = ref<PackageResponse[]>([])
|
|
||||||
const searchShopOptions = ref<ShopResponse[]>([])
|
|
||||||
const searchAllocatorShopOptions = ref<ShopResponse[]>([])
|
|
||||||
const searchSeriesAllocationOptions = ref<any[]>([])
|
|
||||||
|
|
||||||
// 搜索表单初始值
|
|
||||||
const initialSearchState = {
|
|
||||||
shop_id: undefined as number | undefined,
|
|
||||||
package_id: undefined as number | undefined,
|
|
||||||
series_allocation_id: undefined as number | undefined,
|
|
||||||
allocator_shop_id: undefined as number | undefined,
|
|
||||||
status: undefined as number | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索表单
|
|
||||||
const searchForm = reactive({ ...initialSearchState })
|
|
||||||
|
|
||||||
// 搜索表单配置
|
|
||||||
const searchFormItems = computed<SearchFormItem[]>(() => [
|
|
||||||
{
|
|
||||||
label: '被分配店铺',
|
|
||||||
prop: 'shop_id',
|
|
||||||
type: 'select',
|
|
||||||
config: {
|
|
||||||
clearable: true,
|
|
||||||
filterable: true,
|
|
||||||
remote: true,
|
|
||||||
remoteMethod: handleSearchShop,
|
|
||||||
loading: shopLoading.value,
|
|
||||||
placeholder: '请选择或搜索店铺'
|
|
||||||
},
|
|
||||||
options: () =>
|
|
||||||
searchShopOptions.value.map((s) => ({
|
|
||||||
label: s.shop_name,
|
|
||||||
value: s.id
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '套餐',
|
|
||||||
prop: 'package_id',
|
|
||||||
type: 'select',
|
|
||||||
config: {
|
|
||||||
clearable: true,
|
|
||||||
filterable: true,
|
|
||||||
remote: true,
|
|
||||||
remoteMethod: handleSearchPackage,
|
|
||||||
loading: packageLoading.value,
|
|
||||||
placeholder: '请选择或搜索套餐'
|
|
||||||
},
|
|
||||||
options: () =>
|
|
||||||
searchPackageOptions.value.map((p) => ({
|
|
||||||
label: p.package_name,
|
|
||||||
value: p.id
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '系列分配',
|
|
||||||
prop: 'series_allocation_id',
|
|
||||||
type: 'select',
|
|
||||||
config: {
|
|
||||||
clearable: true,
|
|
||||||
filterable: true,
|
|
||||||
remote: true,
|
|
||||||
remoteMethod: handleSearchSeriesAllocation,
|
|
||||||
loading: loading.value,
|
|
||||||
placeholder: '请选择或搜索系列分配'
|
|
||||||
},
|
|
||||||
options: () =>
|
|
||||||
searchSeriesAllocationOptions.value.map((s) => ({
|
|
||||||
label: `${s.series_name} - ${s.shop_name}`,
|
|
||||||
value: s.id
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '分配者店铺',
|
|
||||||
prop: 'allocator_shop_id',
|
|
||||||
type: 'select',
|
|
||||||
config: {
|
|
||||||
clearable: true,
|
|
||||||
filterable: true,
|
|
||||||
remote: true,
|
|
||||||
remoteMethod: handleSearchAllocatorShop,
|
|
||||||
loading: shopLoading.value,
|
|
||||||
placeholder: '请选择或搜索分配者店铺'
|
|
||||||
},
|
|
||||||
options: () => [
|
|
||||||
{ label: '平台', value: 0 },
|
|
||||||
...searchAllocatorShopOptions.value.map((s) => ({
|
|
||||||
label: s.shop_name,
|
|
||||||
value: s.id
|
|
||||||
}))
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '状态',
|
|
||||||
prop: 'status',
|
|
||||||
type: 'select',
|
|
||||||
config: {
|
|
||||||
clearable: true,
|
|
||||||
placeholder: '请选择状态'
|
|
||||||
},
|
|
||||||
options: () => [
|
|
||||||
{ label: '启用', value: 1 },
|
|
||||||
{ label: '禁用', value: 2 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// 分页
|
|
||||||
const pagination = reactive({
|
|
||||||
page: 1,
|
|
||||||
page_size: 20,
|
|
||||||
total: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// 列配置
|
|
||||||
const columnOptions = [
|
|
||||||
{ label: '套餐编码', prop: 'package_code' },
|
|
||||||
{ label: '套餐名称', prop: 'package_name' },
|
|
||||||
{ label: '系列名称', prop: 'series_name' },
|
|
||||||
{ label: '被分配店铺', prop: 'shop_name' },
|
|
||||||
{ label: '分配者', prop: 'allocator_shop_name' },
|
|
||||||
{ label: '成本价', prop: 'cost_price' },
|
|
||||||
{ label: '状态', prop: 'status' },
|
|
||||||
{ label: '创建时间', prop: 'created_at' }
|
|
||||||
]
|
|
||||||
|
|
||||||
// 表单验证规则
|
|
||||||
const rules = reactive<FormRules>({
|
|
||||||
package_id: [{ required: true, message: '请选择套餐', trigger: 'change' }],
|
|
||||||
shop_id: [{ required: true, message: '请选择店铺', trigger: 'change' }],
|
|
||||||
cost_price: [
|
|
||||||
{ required: true, message: '请输入成本价', trigger: 'blur' },
|
|
||||||
{
|
|
||||||
validator: (rule: any, value: any, callback: any) => {
|
|
||||||
if (value === undefined || value === null || value === '') {
|
|
||||||
callback(new Error('请输入成本价'))
|
|
||||||
} else if (form.package_base_price && value < form.package_base_price / 100) {
|
|
||||||
callback(
|
|
||||||
new Error(`成本价不能低于套餐价格 ¥${(form.package_base_price / 100).toFixed(2)}`)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
trigger: 'blur'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 表单数据
|
|
||||||
const form = reactive<any>({
|
|
||||||
id: 0,
|
|
||||||
package_id: undefined,
|
|
||||||
shop_id: undefined,
|
|
||||||
cost_price: 0,
|
|
||||||
package_base_price: 0 // 存储选中套餐的成本价,用于验证
|
|
||||||
})
|
|
||||||
|
|
||||||
const allocationList = ref<ShopPackageAllocationResponse[]>([])
|
|
||||||
const dialogType = ref('add')
|
|
||||||
|
|
||||||
// 动态列配置
|
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
|
||||||
{
|
|
||||||
prop: 'package_code',
|
|
||||||
label: '套餐编码',
|
|
||||||
minWidth: 200,
|
|
||||||
showOverflowTooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'package_name',
|
|
||||||
label: '套餐名称',
|
|
||||||
minWidth: 180
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'series_name',
|
|
||||||
label: '系列名称',
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'shop_name',
|
|
||||||
label: '被分配店铺',
|
|
||||||
minWidth: 180
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'allocator_shop_name',
|
|
||||||
label: '分配者',
|
|
||||||
formatter: (row: ShopPackageAllocationResponse) => {
|
|
||||||
// 如果是平台分配(allocator_shop_id为0),显示"平台"标签
|
|
||||||
if (row.allocator_shop_id === 0) {
|
|
||||||
return h(
|
|
||||||
'span',
|
|
||||||
{ style: 'color: #409eff; font-weight: bold' },
|
|
||||||
row.allocator_shop_name || '平台'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return row.allocator_shop_name
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'cost_price',
|
|
||||||
label: '成本价',
|
|
||||||
width: 100,
|
|
||||||
formatter: (row: ShopPackageAllocationResponse) => {
|
|
||||||
return h(
|
|
||||||
'span',
|
|
||||||
{ style: 'color: #f56c6c; font-weight: bold' },
|
|
||||||
`¥${(row.cost_price / 100).toFixed(2)}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'status',
|
|
||||||
label: '状态',
|
|
||||||
width: 100,
|
|
||||||
formatter: (row: ShopPackageAllocationResponse) => {
|
|
||||||
const frontendStatus = apiStatusToFrontend(row.status)
|
|
||||||
return h(ElSwitch, {
|
|
||||||
modelValue: frontendStatus,
|
|
||||||
activeValue: CommonStatus.ENABLED,
|
|
||||||
inactiveValue: CommonStatus.DISABLED,
|
|
||||||
activeText: getStatusText(CommonStatus.ENABLED),
|
|
||||||
inactiveText: getStatusText(CommonStatus.DISABLED),
|
|
||||||
inlinePrompt: true,
|
|
||||||
disabled: !hasAuth('package_assign:update_status'),
|
|
||||||
'onUpdate:modelValue': (val: string | number | boolean) =>
|
|
||||||
handleStatusChange(row, val as number)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'created_at',
|
|
||||||
label: '创建时间',
|
|
||||||
width: 180,
|
|
||||||
formatter: (row: ShopPackageAllocationResponse) => formatDateTime(row.created_at)
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// 右键菜单项配置
|
|
||||||
const contextMenuItems = computed((): MenuItemType[] => {
|
|
||||||
const items: MenuItemType[] = []
|
|
||||||
|
|
||||||
if (hasAuth('package_assign:detail')) {
|
|
||||||
items.push({
|
|
||||||
key: 'detail',
|
|
||||||
label: '详情'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('package_assign:edit')) {
|
|
||||||
items.push({
|
|
||||||
key: 'edit',
|
|
||||||
label: '编辑'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('package_assign:delete')) {
|
|
||||||
items.push({
|
|
||||||
key: 'delete',
|
|
||||||
label: '删除'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
})
|
|
||||||
|
|
||||||
// 构建树形结构数据
|
|
||||||
const buildTreeData = (items: ShopResponse[]) => {
|
|
||||||
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
|
|
||||||
const tree: ShopResponse[] = []
|
|
||||||
|
|
||||||
// 先将所有项放入 map
|
|
||||||
items.forEach((item) => {
|
|
||||||
map.set(item.id, { ...item, children: [] })
|
|
||||||
})
|
|
||||||
|
|
||||||
// 构建树形结构
|
|
||||||
items.forEach((item) => {
|
|
||||||
const node = map.get(item.id)!
|
|
||||||
if (item.parent_id && map.has(item.parent_id)) {
|
|
||||||
// 有父节点,添加到父节点的 children 中
|
|
||||||
const parent = map.get(item.parent_id)!
|
|
||||||
if (!parent.children) parent.children = []
|
|
||||||
parent.children.push(node)
|
|
||||||
} else {
|
|
||||||
// 没有父节点或父节点不存在,作为根节点
|
|
||||||
tree.push(node)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return tree
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadPackageOptions()
|
|
||||||
loadShopOptions()
|
|
||||||
loadSearchPackageOptions()
|
|
||||||
loadSearchShopOptions()
|
|
||||||
loadSearchAllocatorShopOptions()
|
|
||||||
loadSearchSeriesAllocationOptions()
|
|
||||||
getTableData()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载套餐选项(用于新增对话框,默认加载10条)
|
|
||||||
const loadPackageOptions = async (packageName?: string) => {
|
|
||||||
packageLoading.value = true
|
|
||||||
try {
|
|
||||||
const params: any = {
|
|
||||||
page: 1,
|
|
||||||
page_size: 10
|
|
||||||
}
|
|
||||||
if (packageName) {
|
|
||||||
params.package_name = packageName
|
|
||||||
}
|
|
||||||
const res = await PackageManageService.getPackages(params)
|
|
||||||
if (res.code === 0) {
|
|
||||||
packageOptions.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载套餐选项失败:', error)
|
|
||||||
} finally {
|
|
||||||
packageLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载店铺选项(用于新增对话框,加载所有店铺并构建树形结构)
|
|
||||||
const loadShopOptions = async () => {
|
|
||||||
shopLoading.value = true
|
|
||||||
try {
|
|
||||||
// 加载所有店铺,不分页
|
|
||||||
const res = await ShopService.getShops({
|
|
||||||
page: 1,
|
|
||||||
page_size: 10000 // 使用较大的值获取所有店铺
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
|
||||||
shopOptions.value = res.data.items
|
|
||||||
// 构建树形结构数据
|
|
||||||
shopTreeData.value = buildTreeData(shopOptions.value)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载店铺选项失败:', error)
|
|
||||||
} finally {
|
|
||||||
shopLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载搜索栏套餐选项(默认加载10条)
|
|
||||||
const loadSearchPackageOptions = async () => {
|
|
||||||
try {
|
|
||||||
const res = await PackageManageService.getPackages({
|
|
||||||
page: 1,
|
|
||||||
page_size: 10
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
|
||||||
searchPackageOptions.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载搜索栏套餐选项失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载搜索栏店铺选项(默认加载10条)
|
|
||||||
const loadSearchShopOptions = async () => {
|
|
||||||
try {
|
|
||||||
const res = await ShopService.getShops({ page: 1, page_size: 10 })
|
|
||||||
if (res.code === 0) {
|
|
||||||
searchShopOptions.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载搜索栏店铺选项失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索套餐(用于新增对话框)
|
|
||||||
const searchPackage = (query: string) => {
|
|
||||||
if (query) {
|
|
||||||
loadPackageOptions(query)
|
|
||||||
} else {
|
|
||||||
loadPackageOptions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索套餐(用于搜索栏)
|
|
||||||
const handleSearchPackage = async (query: string) => {
|
|
||||||
if (!query) {
|
|
||||||
loadSearchPackageOptions()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await PackageManageService.getPackages({
|
|
||||||
page: 1,
|
|
||||||
page_size: 10,
|
|
||||||
package_name: query
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
|
||||||
searchPackageOptions.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('搜索套餐失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索店铺(用于搜索栏)
|
|
||||||
const handleSearchShop = async (query: string) => {
|
|
||||||
if (!query) {
|
|
||||||
loadSearchShopOptions()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await ShopService.getShops({
|
|
||||||
page: 1,
|
|
||||||
page_size: 10,
|
|
||||||
shop_name: query
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
|
||||||
searchShopOptions.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('搜索店铺失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载搜索栏分配者店铺选项(默认加载10条)
|
|
||||||
const loadSearchAllocatorShopOptions = async () => {
|
|
||||||
try {
|
|
||||||
const res = await ShopService.getShops({ page: 1, page_size: 10 })
|
|
||||||
if (res.code === 0) {
|
|
||||||
searchAllocatorShopOptions.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载搜索栏分配者店铺选项失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索分配者店铺(用于搜索栏)
|
|
||||||
const handleSearchAllocatorShop = async (query: string) => {
|
|
||||||
if (!query) {
|
|
||||||
loadSearchAllocatorShopOptions()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await ShopService.getShops({
|
|
||||||
page: 1,
|
|
||||||
page_size: 10,
|
|
||||||
shop_name: query
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
|
||||||
searchAllocatorShopOptions.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('搜索分配者店铺失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载搜索栏系列分配选项(默认加载10条)
|
|
||||||
const loadSearchSeriesAllocationOptions = async () => {
|
|
||||||
try {
|
|
||||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocations({
|
|
||||||
page: 1,
|
|
||||||
page_size: 10
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
|
||||||
searchSeriesAllocationOptions.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载搜索栏系列分配选项失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索系列分配(用于搜索栏)
|
|
||||||
const handleSearchSeriesAllocation = async (query: string) => {
|
|
||||||
if (!query) {
|
|
||||||
loadSearchSeriesAllocationOptions()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocations({
|
|
||||||
page: 1,
|
|
||||||
page_size: 10,
|
|
||||||
series_name: query
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
|
||||||
searchSeriesAllocationOptions.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('搜索系列分配失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取分配列表
|
|
||||||
const getTableData = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const params = {
|
|
||||||
page: pagination.page,
|
|
||||||
page_size: pagination.page_size,
|
|
||||||
shop_id: searchForm.shop_id || undefined,
|
|
||||||
package_id: searchForm.package_id || undefined,
|
|
||||||
series_allocation_id: searchForm.series_allocation_id || undefined,
|
|
||||||
allocator_shop_id: searchForm.allocator_shop_id || undefined,
|
|
||||||
status: searchForm.status || undefined
|
|
||||||
}
|
|
||||||
const res = await ShopPackageAllocationService.getShopPackageAllocations(params)
|
|
||||||
if (res.code === 0) {
|
|
||||||
allocationList.value = res.data.items
|
|
||||||
pagination.total = res.data.total || 0
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('获取分配列表失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置搜索
|
|
||||||
const handleReset = () => {
|
|
||||||
Object.assign(searchForm, { ...initialSearchState })
|
|
||||||
pagination.page = 1
|
|
||||||
getTableData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索
|
|
||||||
const handleSearch = () => {
|
|
||||||
pagination.page = 1
|
|
||||||
getTableData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新表格
|
|
||||||
const handleRefresh = () => {
|
|
||||||
getTableData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理表格分页变化
|
|
||||||
const handleSizeChange = (newPageSize: number) => {
|
|
||||||
pagination.page_size = newPageSize
|
|
||||||
getTableData()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCurrentChange = (newCurrentPage: number) => {
|
|
||||||
pagination.page = newCurrentPage
|
|
||||||
getTableData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示新增/编辑对话框
|
|
||||||
const showDialog = (type: string, row?: ShopPackageAllocationResponse) => {
|
|
||||||
dialogVisible.value = true
|
|
||||||
dialogType.value = type
|
|
||||||
|
|
||||||
if (type === 'edit' && row) {
|
|
||||||
form.id = row.id
|
|
||||||
form.package_id = row.package_id
|
|
||||||
form.shop_id = row.shop_id
|
|
||||||
form.cost_price = row.cost_price / 100 // 转换为元显示
|
|
||||||
form.package_base_price = 0
|
|
||||||
} else {
|
|
||||||
form.id = 0
|
|
||||||
form.package_id = undefined
|
|
||||||
form.shop_id = undefined
|
|
||||||
form.cost_price = 0
|
|
||||||
form.package_base_price = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置表单验证状态
|
|
||||||
nextTick(() => {
|
|
||||||
formRef.value?.clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理套餐选择变化
|
|
||||||
const handlePackageChange = (packageId: number | undefined) => {
|
|
||||||
if (packageId) {
|
|
||||||
// 从套餐选项中找到选中的套餐
|
|
||||||
const selectedPackage = packageOptions.value.find((pkg) => pkg.id === packageId)
|
|
||||||
if (selectedPackage) {
|
|
||||||
// 将套餐的成本价(分)转换为元显示
|
|
||||||
form.cost_price = selectedPackage.cost_price / 100
|
|
||||||
form.package_base_price = selectedPackage.cost_price // 保持原始值(分)用于验证
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 清空时重置成本价
|
|
||||||
form.cost_price = 0
|
|
||||||
form.package_base_price = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理弹窗关闭事件
|
|
||||||
const handleDialogClosed = () => {
|
|
||||||
// 清除表单验证状态
|
|
||||||
formRef.value?.clearValidate()
|
|
||||||
// 重置表单数据
|
|
||||||
form.id = 0
|
|
||||||
form.package_id = undefined
|
|
||||||
form.shop_id = undefined
|
|
||||||
form.cost_price = 0
|
|
||||||
form.package_base_price = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除分配
|
|
||||||
const deleteAllocation = (row: ShopPackageAllocationResponse) => {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
`确定删除套餐 ${row.package_name} 对店铺 ${row.shop_name} 的分配吗?`,
|
|
||||||
'删除确认',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(async () => {
|
|
||||||
try {
|
|
||||||
await ShopPackageAllocationService.deleteShopPackageAllocation(row.id)
|
|
||||||
ElMessage.success('删除成功')
|
|
||||||
await getTableData()
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// 用户取消删除
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交表单
|
|
||||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
|
||||||
if (!formEl) return
|
|
||||||
|
|
||||||
await formEl.validate(async (valid) => {
|
|
||||||
if (valid) {
|
|
||||||
submitLoading.value = true
|
|
||||||
try {
|
|
||||||
// 将元转换为分提交给后端
|
|
||||||
const costPriceInCents = Math.round(form.cost_price * 100)
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
package_id: form.package_id,
|
|
||||||
shop_id: form.shop_id,
|
|
||||||
cost_price: costPriceInCents
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dialogType.value === 'add') {
|
|
||||||
await ShopPackageAllocationService.createShopPackageAllocation(data)
|
|
||||||
ElMessage.success('新增成功')
|
|
||||||
} else {
|
|
||||||
await ShopPackageAllocationService.updateShopPackageAllocation(form.id, {
|
|
||||||
cost_price: costPriceInCents
|
|
||||||
})
|
|
||||||
ElMessage.success('修改成功')
|
|
||||||
}
|
|
||||||
|
|
||||||
dialogVisible.value = false
|
|
||||||
formEl.resetFields()
|
|
||||||
await getTableData()
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
} finally {
|
|
||||||
submitLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态切换
|
|
||||||
const handleStatusChange = async (
|
|
||||||
row: ShopPackageAllocationResponse,
|
|
||||||
newFrontendStatus: number
|
|
||||||
) => {
|
|
||||||
const oldStatus = row.status
|
|
||||||
const newApiStatus = frontendStatusToApi(newFrontendStatus)
|
|
||||||
row.status = newApiStatus
|
|
||||||
try {
|
|
||||||
await ShopPackageAllocationService.updateShopPackageAllocationStatus(row.id, newApiStatus)
|
|
||||||
ElMessage.success('状态切换成功')
|
|
||||||
} catch (error) {
|
|
||||||
row.status = oldStatus
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看详情
|
|
||||||
const handleViewDetail = (row: ShopPackageAllocationResponse) => {
|
|
||||||
router.push(`${RoutesAlias.PackageAssignDetail}/${row.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理表格行右键菜单
|
|
||||||
const handleRowContextMenu = (
|
|
||||||
row: ShopPackageAllocationResponse,
|
|
||||||
column: any,
|
|
||||||
event: MouseEvent
|
|
||||||
) => {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
currentRow.value = row
|
|
||||||
contextMenuRef.value?.show(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理右键菜单选择
|
|
||||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
|
||||||
if (!currentRow.value) return
|
|
||||||
|
|
||||||
switch (item.key) {
|
|
||||||
case 'detail':
|
|
||||||
handleViewDetail(currentRow.value)
|
|
||||||
break
|
|
||||||
case 'edit':
|
|
||||||
showDialog('edit', currentRow.value)
|
|
||||||
break
|
|
||||||
case 'delete':
|
|
||||||
deleteAllocation(currentRow.value)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.package-assign-page {
|
|
||||||
// 可以添加特定样式
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="series-assign-detail">
|
|
||||||
<ElCard shadow="never">
|
|
||||||
<!-- 页面头部 -->
|
|
||||||
<div class="detail-header">
|
|
||||||
<ElButton @click="handleBack">
|
|
||||||
<template #icon>
|
|
||||||
<ElIcon><ArrowLeft /></ElIcon>
|
|
||||||
</template>
|
|
||||||
返回
|
|
||||||
</ElButton>
|
|
||||||
<h2 class="detail-title">系列分配详情</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 详情内容 -->
|
|
||||||
<DetailPage v-if="detailData" :sections="detailSections" :data="detailData" />
|
|
||||||
|
|
||||||
<!-- 加载中 -->
|
|
||||||
<div v-if="loading" class="loading-container">
|
|
||||||
<ElIcon class="is-loading"><Loading /></ElIcon>
|
|
||||||
<span>加载中...</span>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, h } from 'vue'
|
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
|
||||||
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
|
||||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
|
||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
|
||||||
import { ShopSeriesAllocationService } from '@/api/modules'
|
|
||||||
import type { ShopSeriesAllocationResponse } from '@/types/api'
|
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
|
||||||
|
|
||||||
defineOptions({ name: 'SeriesAssignDetail' })
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const detailData = ref<ShopSeriesAllocationResponse | null>(null)
|
|
||||||
|
|
||||||
// 详情页配置
|
|
||||||
const detailSections: DetailSection[] = [
|
|
||||||
{
|
|
||||||
title: '基本信息',
|
|
||||||
fields: [
|
|
||||||
{ label: 'ID', prop: 'id' },
|
|
||||||
{ label: '系列编码', prop: 'series_code' },
|
|
||||||
{ label: '系列名称', prop: 'series_name' },
|
|
||||||
{ label: '店铺名称', prop: 'shop_name' },
|
|
||||||
{
|
|
||||||
label: '分配者店铺',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (data.allocator_shop_id === 0) {
|
|
||||||
return '平台'
|
|
||||||
}
|
|
||||||
return data.allocator_shop_name || '-'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '状态',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.status === 1 ? '启用' : '禁用'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
|
||||||
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '一次性佣金配置',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: '启用状态',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.enable_one_time_commission ? '已启用' : '未启用'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '佣金金额上限',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.one_time_commission_amount) return '-'
|
|
||||||
return `¥${(data.one_time_commission_amount / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '触发阈值',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.one_time_commission_threshold) return '-'
|
|
||||||
return `¥${(data.one_time_commission_threshold / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '触发类型',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.one_time_commission_trigger) return '-'
|
|
||||||
const typeMap = {
|
|
||||||
first_recharge: '首次充值',
|
|
||||||
accumulated_recharge: '累计充值'
|
|
||||||
}
|
|
||||||
return typeMap[data.one_time_commission_trigger] || '-'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '强制充值配置',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: '启用状态',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.enable_force_recharge ? '已启用' : '未启用'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '强充金额',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.force_recharge_amount) return '-'
|
|
||||||
return `¥${(data.force_recharge_amount / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '强充触发类型',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.force_recharge_trigger_type) return '-'
|
|
||||||
const typeMap = {
|
|
||||||
1: '单次充值',
|
|
||||||
2: '累计充值'
|
|
||||||
}
|
|
||||||
return typeMap[data.force_recharge_trigger_type] || '-'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 返回上一页
|
|
||||||
const handleBack = () => {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取详情数据
|
|
||||||
const fetchDetail = async () => {
|
|
||||||
const id = Number(route.params.id)
|
|
||||||
if (!id) {
|
|
||||||
ElMessage.error('缺少ID参数')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocationDetail(id)
|
|
||||||
if (res.code === 0) {
|
|
||||||
detailData.value = res.data
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('获取详情失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchDetail()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.series-assign-detail {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
|
|
||||||
.detail-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
File diff suppressed because it is too large
Load Diff
663
src/views/package-management/series-grants/detail.vue
Normal file
663
src/views/package-management/series-grants/detail.vue
Normal file
@@ -0,0 +1,663 @@
|
|||||||
|
<template>
|
||||||
|
<div class="series-grants-detail">
|
||||||
|
<ElCard shadow="never">
|
||||||
|
<!-- 页面头部 -->
|
||||||
|
<div class="detail-header">
|
||||||
|
<ElButton @click="handleBack">
|
||||||
|
<template #icon>
|
||||||
|
<ElIcon><ArrowLeft /></ElIcon>
|
||||||
|
</template>
|
||||||
|
返回
|
||||||
|
</ElButton>
|
||||||
|
<h2 class="detail-title">代理系列授权详情</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情内容 -->
|
||||||
|
<div v-if="detailData" class="detail-content">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<div class="info-section">
|
||||||
|
<div class="section-title">基本信息</div>
|
||||||
|
<ElDescriptions :column="2" border>
|
||||||
|
<ElDescriptionsItem label="ID">{{ detailData.id }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="系列编码">{{
|
||||||
|
detailData.series_code || '-'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="系列名称">{{ detailData.series_name }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="店铺名称">{{ detailData.shop_name }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="分配者店铺">
|
||||||
|
<ElTag v-if="detailData.allocator_shop_id === 0" type="primary">
|
||||||
|
{{ detailData.allocator_shop_name || '平台' }}
|
||||||
|
</ElTag>
|
||||||
|
<span v-else>{{ detailData.allocator_shop_name || '-' }}</span>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="状态">
|
||||||
|
<ElTag :type="detailData.status === 1 ? 'success' : 'danger'">
|
||||||
|
{{ detailData.status === 1 ? '启用' : '禁用' }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="创建时间">{{
|
||||||
|
formatDateTime(detailData.created_at)
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="更新时间">{{
|
||||||
|
formatDateTime(detailData.updated_at)
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 佣金配置 -->
|
||||||
|
<div class="info-section">
|
||||||
|
<div class="section-title">佣金配置</div>
|
||||||
|
<ElDescriptions :column="2" border label-width="120">
|
||||||
|
<ElDescriptionsItem label="佣金类型">
|
||||||
|
<ElTag :type="detailData.commission_type === 'fixed' ? 'success' : 'warning'">
|
||||||
|
{{ detailData.commission_type === 'fixed' ? '固定佣金' : '梯度佣金' }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="固定佣金金额" v-if="detailData.commission_type === 'fixed'">
|
||||||
|
<span v-if="detailData.one_time_commission_amount" class="amount-value">
|
||||||
|
¥{{ (detailData.one_time_commission_amount / 100).toFixed(2) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem
|
||||||
|
label="梯度配置"
|
||||||
|
:span="1"
|
||||||
|
v-if="detailData.commission_type === 'tiered'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="detailData.commission_tiers && detailData.commission_tiers.length > 0"
|
||||||
|
class="tier-table-wrapper"
|
||||||
|
>
|
||||||
|
<ElTable :data="tierTableData" border size="small" style="width: 100%">
|
||||||
|
<ElTableColumn label="档位" width="80" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<ElTag type="primary" size="small">档位{{ $index + 1 }}</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="达标阈值" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="threshold-value"
|
||||||
|
>{{ row.operator }} {{ formatThreshold(row) }}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="统计维度" width="110">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag size="small" type="info">
|
||||||
|
{{ row.dimension === 'sales_count' ? '销量' : '销售额' }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="统计范围" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag size="small" type="warning">
|
||||||
|
{{
|
||||||
|
row.stat_scope === 'self'
|
||||||
|
? '仅自己'
|
||||||
|
: row.stat_scope === 'self_and_sub'
|
||||||
|
? '自己+下级'
|
||||||
|
: '-'
|
||||||
|
}}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="佣金金额" min-width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="amount-value">¥{{ (row.amount / 100).toFixed(2) }}</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
</div>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 强制充值配置 -->
|
||||||
|
<div class="info-section">
|
||||||
|
<div class="section-title">强制充值配置</div>
|
||||||
|
<ElDescriptions :column="2" border>
|
||||||
|
<ElDescriptionsItem label="启用状态">
|
||||||
|
<ElTag :type="detailData.force_recharge_enabled ? 'warning' : 'info'">
|
||||||
|
{{ detailData.force_recharge_enabled ? '已启用' : '未启用' }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="锁定状态">
|
||||||
|
<ElTag :type="detailData.force_recharge_locked ? 'danger' : 'success'">
|
||||||
|
{{ detailData.force_recharge_locked ? '已锁定(平台控制)' : '未锁定' }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="强充金额" :span="2">
|
||||||
|
<span v-if="detailData.force_recharge_amount" class="amount-value">
|
||||||
|
¥{{ (detailData.force_recharge_amount / 100).toFixed(2) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 套餐信息 -->
|
||||||
|
<div class="info-section">
|
||||||
|
<div class="section-title">
|
||||||
|
<span>套餐列表</span>
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="showAddPackageDialog"
|
||||||
|
v-permission="'series_grants:manage_packages'"
|
||||||
|
>
|
||||||
|
添加授权套餐
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 套餐列表 -->
|
||||||
|
<div v-if="detailData.packages && detailData.packages.length > 0" class="package-table">
|
||||||
|
<ElTable :data="detailData.packages" border stripe>
|
||||||
|
<ElTableColumn prop="package_name" label="套餐名称" />
|
||||||
|
<ElTableColumn prop="package_code" label="套餐编码" />
|
||||||
|
<ElTableColumn label="成本价">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="amount-value">¥{{ (row.cost_price / 100).toFixed(2) }}</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="上架状态" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag v-if="row.shelf_status === 1" type="success" size="small">上架</ElTag>
|
||||||
|
<ElTag v-else-if="row.shelf_status === 2" type="info" size="small">下架</ElTag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag v-if="row.status === 1" type="success" size="small">启用</ElTag>
|
||||||
|
<ElTag v-else-if="row.status === 2" type="danger" size="small">禁用</ElTag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="操作" width="150" align="center" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="showEditPackageDialog(row)"
|
||||||
|
v-permission="'series_grants:edit_packages'"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="handleDeletePackage(row)"
|
||||||
|
v-permission="'series_grants:delete_packages'"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
</div>
|
||||||
|
<ElEmpty v-else description="暂无套餐" :image-size="80" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<div v-if="loading" class="loading-container">
|
||||||
|
<ElIcon class="is-loading"><Loading /></ElIcon>
|
||||||
|
<span>加载中...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加/编辑套餐对话框 -->
|
||||||
|
<ElDialog
|
||||||
|
v-model="packageDialogVisible"
|
||||||
|
:title="packageDialogType === 'add' ? '添加套餐' : '编辑套餐'"
|
||||||
|
width="500px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@closed="handlePackageDialogClosed"
|
||||||
|
>
|
||||||
|
<ElForm ref="packageFormRef" :model="packageForm" :rules="packageRules" label-width="100px">
|
||||||
|
<!-- 添加模式:选择套餐 -->
|
||||||
|
<ElFormItem label="选择套餐" prop="package_id" v-if="packageDialogType === 'add'">
|
||||||
|
<ElSelect
|
||||||
|
v-model="packageForm.package_id"
|
||||||
|
placeholder="请选择套餐"
|
||||||
|
style="width: 100%"
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
:remote-method="searchPackages"
|
||||||
|
:loading="packageLoading"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="availablePackages.length === 0 && !packageLoading && detailData?.series_id"
|
||||||
|
>
|
||||||
|
<ElOption disabled value="" label="该系列没有可选套餐" />
|
||||||
|
</template>
|
||||||
|
<ElOption
|
||||||
|
v-for="pkg in availablePackages"
|
||||||
|
:key="pkg.id"
|
||||||
|
:label="`${pkg.package_name} (${pkg.package_code})`"
|
||||||
|
:value="pkg.id"
|
||||||
|
:disabled="isPackageAlreadyAdded(pkg.id)"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 编辑模式:显示套餐信息 -->
|
||||||
|
<ElFormItem label="套餐名称" v-if="packageDialogType === 'edit'">
|
||||||
|
<span>{{ packageForm.package_name }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="套餐编码" v-if="packageDialogType === 'edit'">
|
||||||
|
<span>{{ packageForm.package_code }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 成本价 -->
|
||||||
|
<ElFormItem label="成本价(元)" prop="cost_price_yuan">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="packageForm.cost_price_yuan"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入成本价"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<ElButton @click="packageDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleSavePackage" :loading="submitLoading">
|
||||||
|
保存
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import {
|
||||||
|
ElCard,
|
||||||
|
ElButton,
|
||||||
|
ElIcon,
|
||||||
|
ElMessage,
|
||||||
|
ElMessageBox,
|
||||||
|
ElDescriptions,
|
||||||
|
ElDescriptionsItem,
|
||||||
|
ElTag,
|
||||||
|
ElTable,
|
||||||
|
ElTableColumn,
|
||||||
|
ElEmpty,
|
||||||
|
ElDialog,
|
||||||
|
ElSelect,
|
||||||
|
ElOption,
|
||||||
|
ElInputNumber,
|
||||||
|
ElForm,
|
||||||
|
ElFormItem,
|
||||||
|
FormInstance,
|
||||||
|
FormRules
|
||||||
|
} from 'element-plus'
|
||||||
|
import { ArrowLeft, Loading, Setting, List, Plus } from '@element-plus/icons-vue'
|
||||||
|
import { ShopSeriesGrantService, PackageManageService, PackageSeriesService } from '@/api/modules'
|
||||||
|
import type {
|
||||||
|
ShopSeriesGrantResponse,
|
||||||
|
PackageResponse,
|
||||||
|
GrantPackageItem,
|
||||||
|
GrantPackageInfo,
|
||||||
|
PackageSeriesResponse
|
||||||
|
} from '@/types/api'
|
||||||
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
|
defineOptions({ name: 'SeriesGrantsDetail' })
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
const packageLoading = ref(false)
|
||||||
|
const detailData = ref<ShopSeriesGrantResponse | null>(null)
|
||||||
|
const seriesData = ref<PackageSeriesResponse | null>(null)
|
||||||
|
const packageDialogVisible = ref(false)
|
||||||
|
const packageDialogType = ref<'add' | 'edit'>('add')
|
||||||
|
const availablePackages = ref<PackageResponse[]>([])
|
||||||
|
const packageFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
// 套餐表单
|
||||||
|
const packageForm = ref<{
|
||||||
|
package_id?: number
|
||||||
|
package_name?: string
|
||||||
|
package_code?: string
|
||||||
|
cost_price_yuan: number
|
||||||
|
}>({
|
||||||
|
cost_price_yuan: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const packageRules = computed<FormRules>(() => ({
|
||||||
|
package_id: [
|
||||||
|
{ required: packageDialogType.value === 'add', message: '请选择套餐', trigger: 'change' }
|
||||||
|
],
|
||||||
|
cost_price_yuan: [
|
||||||
|
{ required: true, message: '请输入成本价', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 0, message: '成本价不能小于0', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 梯度配置表格数据(合并授权数据和系列数据)
|
||||||
|
const tierTableData = computed(() => {
|
||||||
|
if (
|
||||||
|
!detailData.value?.commission_tiers ||
|
||||||
|
!seriesData.value?.one_time_commission_config?.tiers
|
||||||
|
) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const grantTiers = detailData.value.commission_tiers
|
||||||
|
const seriesTiers = seriesData.value.one_time_commission_config.tiers
|
||||||
|
|
||||||
|
return grantTiers.map((grantTier, index) => {
|
||||||
|
const seriesTier = seriesTiers[index] || {}
|
||||||
|
return {
|
||||||
|
operator: grantTier.operator || '>=',
|
||||||
|
threshold: grantTier.threshold,
|
||||||
|
dimension: seriesTier.dimension || 'sales_count',
|
||||||
|
stat_scope: seriesTier.stat_scope,
|
||||||
|
amount: grantTier.amount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 格式化达标阈值显示
|
||||||
|
const formatThreshold = (row: any) => {
|
||||||
|
if (row.dimension === 'sales_amount') {
|
||||||
|
// 销售额维度,阈值单位是分,需要转换为元显示
|
||||||
|
return `¥${(row.threshold / 100).toFixed(2)}`
|
||||||
|
} else {
|
||||||
|
// 销量维度,直接显示数字
|
||||||
|
return `${row.threshold}件`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const handleBack = () => {
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取详情数据
|
||||||
|
const fetchDetail = async () => {
|
||||||
|
const id = Number(route.params.id)
|
||||||
|
if (!id) {
|
||||||
|
ElMessage.error('缺少ID参数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await ShopSeriesGrantService.getShopSeriesGrantDetail(id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
detailData.value = res.data
|
||||||
|
|
||||||
|
// 如果是梯度佣金类型,获取系列配置以显示完整的梯度信息
|
||||||
|
if (res.data.commission_type === 'tiered' && res.data.series_id) {
|
||||||
|
await fetchSeriesData(res.data.series_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('获取详情失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取系列配置数据
|
||||||
|
const fetchSeriesData = async (seriesId: number) => {
|
||||||
|
try {
|
||||||
|
const res = await PackageSeriesService.getPackageSeriesDetail(seriesId)
|
||||||
|
if (res.code === 0) {
|
||||||
|
seriesData.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取系列配置失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示添加套餐对话框
|
||||||
|
const showAddPackageDialog = () => {
|
||||||
|
packageDialogType.value = 'add'
|
||||||
|
packageForm.value = {
|
||||||
|
package_id: undefined,
|
||||||
|
cost_price_yuan: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载可用套餐
|
||||||
|
if (detailData.value?.series_id) {
|
||||||
|
loadAvailablePackages()
|
||||||
|
}
|
||||||
|
|
||||||
|
packageDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示编辑套餐对话框
|
||||||
|
const showEditPackageDialog = (row: GrantPackageInfo) => {
|
||||||
|
packageDialogType.value = 'edit'
|
||||||
|
packageForm.value = {
|
||||||
|
package_id: row.package_id,
|
||||||
|
package_name: row.package_name,
|
||||||
|
package_code: row.package_code,
|
||||||
|
cost_price_yuan: row.cost_price / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
packageDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载可用套餐
|
||||||
|
const loadAvailablePackages = async (packageName?: string) => {
|
||||||
|
if (!detailData.value?.series_id) return
|
||||||
|
|
||||||
|
packageLoading.value = true
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
page: 1,
|
||||||
|
page_size: 50,
|
||||||
|
series_id: detailData.value.series_id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageName) {
|
||||||
|
params.package_name = packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await PackageManageService.getPackages(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
availablePackages.value = res.data.items
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载套餐选项失败:', error)
|
||||||
|
} finally {
|
||||||
|
packageLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索套餐
|
||||||
|
const searchPackages = (query: string) => {
|
||||||
|
if (query) {
|
||||||
|
loadAvailablePackages(query)
|
||||||
|
} else {
|
||||||
|
loadAvailablePackages()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查套餐是否已添加
|
||||||
|
const isPackageAlreadyAdded = (packageId: number) => {
|
||||||
|
return detailData.value?.packages?.some((p) => p.package_id === packageId) || false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存套餐
|
||||||
|
const handleSavePackage = async () => {
|
||||||
|
if (!packageFormRef.value || !detailData.value) return
|
||||||
|
|
||||||
|
await packageFormRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
const packages: GrantPackageItem[] = []
|
||||||
|
|
||||||
|
if (packageDialogType.value === 'add') {
|
||||||
|
// 添加模式:添加新套餐
|
||||||
|
packages.push({
|
||||||
|
package_id: packageForm.value.package_id,
|
||||||
|
cost_price: Math.round(packageForm.value.cost_price_yuan * 100)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 编辑模式:更新套餐成本价
|
||||||
|
packages.push({
|
||||||
|
package_id: packageForm.value.package_id,
|
||||||
|
cost_price: Math.round(packageForm.value.cost_price_yuan * 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await ShopSeriesGrantService.manageGrantPackages(detailData.value.id, { packages })
|
||||||
|
ElMessage.success(packageDialogType.value === 'add' ? '添加成功' : '更新成功')
|
||||||
|
packageDialogVisible.value = false
|
||||||
|
|
||||||
|
// 刷新详情数据
|
||||||
|
await fetchDetail()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error(packageDialogType.value === 'add' ? '添加失败' : '更新失败')
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除套餐
|
||||||
|
const handleDeletePackage = (row: GrantPackageInfo) => {
|
||||||
|
ElMessageBox.confirm(`确定删除套餐 ${row.package_name} 的授权吗?`, '删除确认', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
if (!detailData.value) return
|
||||||
|
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
const packages: GrantPackageItem[] = [
|
||||||
|
{
|
||||||
|
package_id: row.package_id,
|
||||||
|
remove: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
await ShopSeriesGrantService.manageGrantPackages(detailData.value.id, { packages })
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
|
||||||
|
// 刷新详情数据
|
||||||
|
await fetchDetail()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 用户取消
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭对话框
|
||||||
|
const handlePackageDialogClosed = () => {
|
||||||
|
packageFormRef.value?.resetFields()
|
||||||
|
packageForm.value = {
|
||||||
|
cost_price_yuan: 0
|
||||||
|
}
|
||||||
|
availablePackages.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchDetail()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.series-grants-detail {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-content {
|
||||||
|
.info-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-table {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tier-table-wrapper {
|
||||||
|
.threshold-value {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1538
src/views/package-management/series-grants/index.vue
Normal file
1538
src/views/package-management/series-grants/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user