fetch(modify):修改bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m45s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m45s
This commit is contained in:
@@ -193,3 +193,16 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 表单分段标题样式 - 用于对话框中的表单分段
|
||||||
|
.form-section-title {
|
||||||
|
margin: 24px 0 16px 0;
|
||||||
|
padding-left: 12px;
|
||||||
|
border-left: 3px solid var(--el-color-primary);
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
159
src/components/common/DetailPage.vue
Normal file
159
src/components/common/DetailPage.vue
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<div class="detail-container">
|
||||||
|
<ElCard v-for="(section, index) in sections" :key="index" class="detail-section">
|
||||||
|
<template #header>
|
||||||
|
<div class="section-title">{{ section.title }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div :class="['section-fields', section.columns === 1 ? 'single-column' : 'double-column']">
|
||||||
|
<div
|
||||||
|
v-for="(field, fieldIndex) in section.fields"
|
||||||
|
:key="fieldIndex"
|
||||||
|
class="field-item"
|
||||||
|
:class="{ 'full-width': field.fullWidth }"
|
||||||
|
>
|
||||||
|
<div class="field-label">{{ field.label }}:</div>
|
||||||
|
<div class="field-value">
|
||||||
|
<!-- 自定义渲染 -->
|
||||||
|
<template v-if="field.render">
|
||||||
|
<component :is="field.render(data)" />
|
||||||
|
</template>
|
||||||
|
<!-- 默认渲染 -->
|
||||||
|
<template v-else>
|
||||||
|
{{ formatFieldValue(field, data) }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ElCard } from 'element-plus'
|
||||||
|
|
||||||
|
export interface DetailField {
|
||||||
|
label: string // 字段标签
|
||||||
|
prop?: string // 数据属性路径,支持点号分隔的嵌套路径,如 'one_time_commission_config.enable'
|
||||||
|
formatter?: (value: any, data: any) => string // 自定义格式化函数
|
||||||
|
render?: (data: any) => any // 自定义渲染函数,返回 VNode
|
||||||
|
fullWidth?: boolean // 是否占据整行
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DetailSection {
|
||||||
|
title: string // 分组标题
|
||||||
|
fields: DetailField[] // 字段列表
|
||||||
|
columns?: 1 | 2 // 列数,默认2列
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sections: DetailSection[] // 详情页分组配置
|
||||||
|
data: any // 详情数据
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据点号分隔的路径获取嵌套对象的值
|
||||||
|
*/
|
||||||
|
const getNestedValue = (obj: any, path: string): any => {
|
||||||
|
if (!path) return obj
|
||||||
|
return path.split('.').reduce((acc, part) => acc?.[part], obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化字段值
|
||||||
|
*/
|
||||||
|
const formatFieldValue = (field: DetailField, data: any): string => {
|
||||||
|
const value = field.prop ? getNestedValue(data, field.prop) : data
|
||||||
|
|
||||||
|
// 如果有自定义格式化函数
|
||||||
|
if (field.formatter) {
|
||||||
|
return field.formatter(value, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认处理
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.detail-container {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-fields {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px 24px;
|
||||||
|
|
||||||
|
&.single-column {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.double-column {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-height: 32px;
|
||||||
|
|
||||||
|
&.full-width {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 140px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-value {
|
||||||
|
flex: 1;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
line-height: 32px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.section-fields {
|
||||||
|
&.double-column {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-item {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.field-label {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
text?: string
|
text?: string
|
||||||
type?: 'add' | 'edit' | 'delete' | 'more'
|
type?: 'add' | 'edit' | 'delete' | 'more' | 'view'
|
||||||
icon?: string // 自定义图标
|
icon?: string // 自定义图标
|
||||||
iconClass?: BgColorEnum // 自定义按钮背景色、文字颜色
|
iconClass?: BgColorEnum // 自定义按钮背景色、文字颜色
|
||||||
iconColor?: string // 外部传入的图标文字颜色
|
iconColor?: string // 外部传入的图标文字颜色
|
||||||
@@ -31,7 +31,8 @@
|
|||||||
{ type: 'add', icon: '', color: BgColorEnum.PRIMARY },
|
{ type: 'add', icon: '', color: BgColorEnum.PRIMARY },
|
||||||
{ type: 'edit', icon: '', color: BgColorEnum.SECONDARY },
|
{ type: 'edit', icon: '', color: BgColorEnum.SECONDARY },
|
||||||
{ type: 'delete', icon: '', color: BgColorEnum.ERROR },
|
{ type: 'delete', icon: '', color: BgColorEnum.ERROR },
|
||||||
{ type: 'more', icon: '', color: '' }
|
{ type: 'more', icon: '', color: '' },
|
||||||
|
{ type: 'view', icon: '', color: BgColorEnum.SECONDARY }
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
// 计算最终使用的图标:优先使用外部传入的 icon,否则根据 type 获取默认图标
|
// 计算最终使用的图标:优先使用外部传入的 icon,否则根据 type 获取默认图标
|
||||||
|
|||||||
@@ -390,10 +390,14 @@
|
|||||||
"packageCreate": "Create Package",
|
"packageCreate": "Create Package",
|
||||||
"packageBatch": "Batch Management",
|
"packageBatch": "Batch Management",
|
||||||
"packageList": "My Packages",
|
"packageList": "My Packages",
|
||||||
|
"packageDetail": "Package Detail",
|
||||||
"packageChange": "Package Change",
|
"packageChange": "Package Change",
|
||||||
"packageAssign": "Package Assignment",
|
"packageAssign": "Package Assignment",
|
||||||
|
"packageAssignDetail": "Package Assignment Detail",
|
||||||
"seriesAssign": "Series Assignment",
|
"seriesAssign": "Series Assignment",
|
||||||
|
"seriesAssignDetail": "Series Assignment Detail",
|
||||||
"packageSeries": "Package Series",
|
"packageSeries": "Package Series",
|
||||||
|
"packageSeriesDetail": "Package Series Detail",
|
||||||
"packageCommission": "Package Commission Cards"
|
"packageCommission": "Package Commission Cards"
|
||||||
},
|
},
|
||||||
"accountManagement": {
|
"accountManagement": {
|
||||||
@@ -406,6 +410,7 @@
|
|||||||
"agent": "Agent Management",
|
"agent": "Agent Management",
|
||||||
"customerAccount": "Customer Account",
|
"customerAccount": "Customer Account",
|
||||||
"enterpriseCustomer": "Enterprise Customer",
|
"enterpriseCustomer": "Enterprise Customer",
|
||||||
|
"enterpriseCustomerAccounts": "Enterprise Customer Accounts",
|
||||||
"enterpriseCards": "Enterprise Card Management",
|
"enterpriseCards": "Enterprise Card Management",
|
||||||
"customerCommission": "Customer Commission"
|
"customerCommission": "Customer Commission"
|
||||||
},
|
},
|
||||||
@@ -429,7 +434,8 @@
|
|||||||
"packageSeries": "Package Series Management",
|
"packageSeries": "Package Series Management",
|
||||||
"packageList": "Package Management",
|
"packageList": "Package Management",
|
||||||
"packageAssign": "Package Assignment",
|
"packageAssign": "Package Assignment",
|
||||||
"shop": "Shop Management"
|
"shop": "Shop Management",
|
||||||
|
"shopAccounts": "Shop Accounts"
|
||||||
},
|
},
|
||||||
"assetManagement": {
|
"assetManagement": {
|
||||||
"title": "Asset Management",
|
"title": "Asset Management",
|
||||||
|
|||||||
@@ -402,10 +402,14 @@
|
|||||||
"packageCreate": "新建套餐",
|
"packageCreate": "新建套餐",
|
||||||
"packageBatch": "批量管理",
|
"packageBatch": "批量管理",
|
||||||
"packageList": "套餐管理",
|
"packageList": "套餐管理",
|
||||||
|
"packageDetail": "套餐详情",
|
||||||
"packageChange": "套餐变更",
|
"packageChange": "套餐变更",
|
||||||
"packageAssign": "单套餐分配",
|
"packageAssign": "单套餐分配",
|
||||||
|
"packageAssignDetail": "套餐分配详情",
|
||||||
"seriesAssign": "套餐系列分配",
|
"seriesAssign": "套餐系列分配",
|
||||||
|
"seriesAssignDetail": "系列分配详情",
|
||||||
"packageSeries": "套餐系列",
|
"packageSeries": "套餐系列",
|
||||||
|
"packageSeriesDetail": "套餐系列详情",
|
||||||
"packageCommission": "套餐佣金网卡"
|
"packageCommission": "套餐佣金网卡"
|
||||||
},
|
},
|
||||||
"accountManagement": {
|
"accountManagement": {
|
||||||
@@ -418,6 +422,7 @@
|
|||||||
"agent": "代理商管理",
|
"agent": "代理商管理",
|
||||||
"customerAccount": "客户账号",
|
"customerAccount": "客户账号",
|
||||||
"enterpriseCustomer": "企业客户",
|
"enterpriseCustomer": "企业客户",
|
||||||
|
"enterpriseCustomerAccounts": "企业客户账号列表",
|
||||||
"enterpriseCards": "企业卡管理",
|
"enterpriseCards": "企业卡管理",
|
||||||
"customerCommission": "客户账号佣金"
|
"customerCommission": "客户账号佣金"
|
||||||
},
|
},
|
||||||
@@ -432,7 +437,8 @@
|
|||||||
"packageSeries": "套餐系列管理",
|
"packageSeries": "套餐系列管理",
|
||||||
"packageList": "套餐管理",
|
"packageList": "套餐管理",
|
||||||
"packageAssign": "套餐分配",
|
"packageAssign": "套餐分配",
|
||||||
"shop": "店铺管理"
|
"shop": "店铺管理",
|
||||||
|
"shopAccounts": "店铺账号列表"
|
||||||
},
|
},
|
||||||
"assetManagement": {
|
"assetManagement": {
|
||||||
"title": "资产管理",
|
"title": "资产管理",
|
||||||
|
|||||||
@@ -721,6 +721,16 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'package-series/detail/:id',
|
||||||
|
name: 'PackageSeriesDetail',
|
||||||
|
component: RoutesAlias.PackageSeriesDetail,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.packageManagement.packageSeriesDetail',
|
||||||
|
isHide: true,
|
||||||
|
keepAlive: false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'package-list',
|
path: 'package-list',
|
||||||
name: 'PackageList',
|
name: 'PackageList',
|
||||||
@@ -730,6 +740,16 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'package-list/detail/:id',
|
||||||
|
name: 'PackageDetail',
|
||||||
|
component: RoutesAlias.PackageDetail,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.packageManagement.packageDetail',
|
||||||
|
isHide: true,
|
||||||
|
keepAlive: false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'package-assign',
|
path: 'package-assign',
|
||||||
name: 'PackageAssign',
|
name: 'PackageAssign',
|
||||||
@@ -739,6 +759,16 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'package-assign/detail/:id',
|
||||||
|
name: 'PackageAssignDetail',
|
||||||
|
component: RoutesAlias.PackageAssignDetail,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.packageManagement.packageAssignDetail',
|
||||||
|
isHide: true,
|
||||||
|
keepAlive: false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'series-assign',
|
path: 'series-assign',
|
||||||
name: 'SeriesAssign',
|
name: 'SeriesAssign',
|
||||||
@@ -747,6 +777,16 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
title: 'menus.packageManagement.seriesAssign',
|
title: 'menus.packageManagement.seriesAssign',
|
||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'series-assign/detail/:id',
|
||||||
|
name: 'SeriesAssignDetail',
|
||||||
|
component: RoutesAlias.SeriesAssignDetail,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.packageManagement.seriesAssignDetail',
|
||||||
|
isHide: true,
|
||||||
|
keepAlive: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -824,6 +864,25 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
|
{
|
||||||
|
path: 'enterprise-customer',
|
||||||
|
name: 'EnterpriseCustomer',
|
||||||
|
component: RoutesAlias.EnterpriseCustomer,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.accountManagement.enterpriseCustomer',
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'enterprise-customer/customer-accounts/:id',
|
||||||
|
name: 'EnterpriseCustomerAccounts',
|
||||||
|
component: RoutesAlias.EnterpriseCustomerAccounts,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.accountManagement.enterpriseCustomerAccounts',
|
||||||
|
isHide: true,
|
||||||
|
keepAlive: false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'enterprise-cards',
|
path: 'enterprise-cards',
|
||||||
name: 'EnterpriseCards',
|
name: 'EnterpriseCards',
|
||||||
@@ -1013,15 +1072,6 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
{
|
|
||||||
path: 'enterprise-customer',
|
|
||||||
name: 'EnterpriseCustomer',
|
|
||||||
component: RoutesAlias.EnterpriseCustomer,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.accountManagement.enterpriseCustomer',
|
|
||||||
keepAlive: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'carrier-management',
|
path: 'carrier-management',
|
||||||
name: 'CarrierManagement',
|
name: 'CarrierManagement',
|
||||||
|
|||||||
@@ -68,10 +68,14 @@ export enum RoutesAlias {
|
|||||||
PackageCreate = '/package-management/package-create', // 新建套餐
|
PackageCreate = '/package-management/package-create', // 新建套餐
|
||||||
PackageBatch = '/package-management/package-batch', // 批量管理
|
PackageBatch = '/package-management/package-batch', // 批量管理
|
||||||
PackageList = '/package-management/package-list', // 套餐管理
|
PackageList = '/package-management/package-list', // 套餐管理
|
||||||
|
PackageDetail = '/package-management/package-list/detail', // 套餐详情
|
||||||
PackageChange = '/package-management/package-change', // 套餐变更
|
PackageChange = '/package-management/package-change', // 套餐变更
|
||||||
PackageAssign = '/package-management/package-assign', // 单套餐分配
|
PackageAssign = '/package-management/package-assign', // 单套餐分配
|
||||||
|
PackageAssignDetail = '/package-management/package-assign/detail', // 单套餐分配详情
|
||||||
SeriesAssign = '/package-management/series-assign', // 套餐系列分配
|
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', // 套餐系列详情
|
||||||
PackageCommission = '/package-management/package-commission', // 套餐佣金网卡
|
PackageCommission = '/package-management/package-commission', // 套餐佣金网卡
|
||||||
|
|
||||||
// 账号管理
|
// 账号管理
|
||||||
@@ -82,12 +86,14 @@ export enum RoutesAlias {
|
|||||||
AgentManagement = '/account-management/agent', // 代理商管理
|
AgentManagement = '/account-management/agent', // 代理商管理
|
||||||
ShopAccount = '/account-management/shop-account', // 代理账号管理
|
ShopAccount = '/account-management/shop-account', // 代理账号管理
|
||||||
EnterpriseCustomer = '/account-management/enterprise-customer', // 企业客户管理
|
EnterpriseCustomer = '/account-management/enterprise-customer', // 企业客户管理
|
||||||
|
EnterpriseCustomerAccounts = '/common/account-list', // 企业客户账号列表(通用)
|
||||||
EnterpriseCards = '/account-management/enterprise-cards', // 企业卡管理
|
EnterpriseCards = '/account-management/enterprise-cards', // 企业卡管理
|
||||||
CustomerCommission = '/account-management/customer-commission', // 客户账号佣金
|
CustomerCommission = '/account-management/customer-commission', // 客户账号佣金
|
||||||
|
|
||||||
// 产品管理
|
// 产品管理
|
||||||
SimCardManagement = '/product/sim-card', // 网卡产品管理
|
SimCardManagement = '/product/sim-card', // 网卡产品管理
|
||||||
SimCardAssign = '/product/sim-card-assign', // 号卡分配
|
SimCardAssign = '/product/sim-card-assign', // 号卡分配
|
||||||
|
ShopAccounts = '/common/account-list', // 店铺账号列表(通用)
|
||||||
|
|
||||||
// 资产管理
|
// 资产管理
|
||||||
StandaloneCardList = '/asset-management/iot-card-management', // IoT卡管理
|
StandaloneCardList = '/asset-management/iot-card-management', // IoT卡管理
|
||||||
|
|||||||
@@ -21,17 +21,17 @@ export interface OneTimeCommissionTier {
|
|||||||
* 套餐系列一次性佣金配置
|
* 套餐系列一次性佣金配置
|
||||||
*/
|
*/
|
||||||
export interface SeriesOneTimeCommissionConfig {
|
export interface SeriesOneTimeCommissionConfig {
|
||||||
enable: boolean // 是否启用一次性佣金
|
enable?: boolean // 是否启用一次性佣金
|
||||||
commission_type: 'fixed' | 'tiered' // 佣金类型:固定或梯度
|
commission_type?: 'fixed' | 'tiered' // 佣金类型:固定或梯度
|
||||||
commission_amount?: number // 固定佣金金额(分),commission_type=fixed时使用
|
commission_amount?: number // 固定佣金金额(分),commission_type=fixed时使用
|
||||||
threshold: number // 触发阈值(分)
|
threshold?: number // 触发阈值(分)
|
||||||
trigger_type: 'first_recharge' | 'accumulated_recharge' // 触发类型:首次充值或累计充值
|
trigger_type?: 'first_recharge' | 'accumulated_recharge' // 触发类型:首充或累计充值
|
||||||
tiers?: OneTimeCommissionTier[] | null // 梯度配置列表,commission_type=tiered时使用
|
tiers?: OneTimeCommissionTier[] | null // 梯度配置列表,commission_type=tiered时使用
|
||||||
enable_force_recharge: boolean // 是否启用强充
|
enable_force_recharge?: boolean // 是否启用强充
|
||||||
force_amount?: number // 强充金额(分)
|
force_amount?: number // 强充金额(分)
|
||||||
force_calc_type?: 'fixed' | 'dynamic' // 强充计算类型:固定或动态
|
force_calc_type?: 'fixed' | 'dynamic' // 强充计算类型:固定或动态
|
||||||
validity_type: 'permanent' | 'fixed_date' | 'relative' // 时效类型:永久、固定日期或相对时长
|
validity_type?: 'permanent' | 'fixed_date' | 'relative' // 时效类型:永久、固定日期或相对时长
|
||||||
validity_value?: string // 时效值(日期字符串或月数)
|
validity_value?: string // 时效值(日期或月数)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
1
src/types/components.d.ts
vendored
1
src/types/components.d.ts
vendored
@@ -79,6 +79,7 @@ declare module 'vue' {
|
|||||||
CommissionDisplay: typeof import('./../components/business/CommissionDisplay.vue')['default']
|
CommissionDisplay: typeof import('./../components/business/CommissionDisplay.vue')['default']
|
||||||
ContainerSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ContainerSettings.vue')['default']
|
ContainerSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ContainerSettings.vue')['default']
|
||||||
CustomerAccountDialog: typeof import('./../components/business/CustomerAccountDialog.vue')['default']
|
CustomerAccountDialog: typeof import('./../components/business/CustomerAccountDialog.vue')['default']
|
||||||
|
DetailPage: typeof import('./../components/common/DetailPage.vue')['default']
|
||||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<ArtTable
|
<ArtTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
row-key="ID"
|
row-key="id"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
:currentPage="pagination.currentPage"
|
:currentPage="pagination.currentPage"
|
||||||
@@ -43,9 +43,9 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '添加账号' : '编辑账号'"
|
:title="dialogType === 'add' ? '添加账号' : '编辑账号'"
|
||||||
width="500px"
|
width="30%"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="100px">
|
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="80px">
|
||||||
<ElFormItem label="账号名称" prop="username">
|
<ElFormItem label="账号名称" prop="username">
|
||||||
<ElInput v-model="formData.username" placeholder="请输入账号名称" />
|
<ElInput v-model="formData.username" placeholder="请输入账号名称" />
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -84,8 +84,8 @@
|
|||||||
<!-- 分配角色对话框 -->
|
<!-- 分配角色对话框 -->
|
||||||
<ElDialog v-model="roleDialogVisible" title="分配角色" width="500px">
|
<ElDialog v-model="roleDialogVisible" title="分配角色" width="500px">
|
||||||
<ElCheckboxGroup v-model="selectedRoles">
|
<ElCheckboxGroup v-model="selectedRoles">
|
||||||
<div v-for="role in allRoles" :key="role.ID" style="margin-bottom: 12px">
|
<div v-for="role in allRoles" :key="role.id" style="margin-bottom: 12px">
|
||||||
<ElCheckbox :label="role.ID">
|
<ElCheckbox :value="role.id">
|
||||||
{{ role.role_name }}
|
{{ role.role_name }}
|
||||||
<ElTag
|
<ElTag
|
||||||
:type="role.role_type === 1 ? 'primary' : 'success'"
|
:type="role.role_type === 1 ? 'primary' : 'success'"
|
||||||
@@ -113,6 +113,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import { FormInstance, ElSwitch, ElCheckbox, ElCheckboxGroup, ElTag } from 'element-plus'
|
import { FormInstance, ElSwitch, ElCheckbox, ElCheckboxGroup, ElTag } from 'element-plus'
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||||
import type { FormRules } from 'element-plus'
|
import type { FormRules } from 'element-plus'
|
||||||
@@ -130,6 +131,7 @@
|
|||||||
defineOptions({ name: 'Account' }) // 定义组件名称,用于 KeepAlive 缓存控制
|
defineOptions({ name: 'Account' }) // 定义组件名称,用于 KeepAlive 缓存控制
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const dialogType = ref('add')
|
const dialogType = ref('add')
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
@@ -272,7 +274,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'edit' && row) {
|
if (type === 'edit' && row) {
|
||||||
formData.id = row.ID
|
formData.id = row.id
|
||||||
formData.username = row.username
|
formData.username = row.username
|
||||||
formData.phone = row.phone
|
formData.phone = row.phone
|
||||||
formData.user_type = row.user_type
|
formData.user_type = row.user_type
|
||||||
@@ -295,7 +297,7 @@
|
|||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
try {
|
try {
|
||||||
await AccountService.deleteAccount(row.ID)
|
await AccountService.deleteAccount(row.id)
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
getAccountList()
|
getAccountList()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -425,6 +427,12 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 从 URL 查询参数中读取 shop_id
|
||||||
|
const shopIdParam = route.query.shop_id
|
||||||
|
if (shopIdParam) {
|
||||||
|
formFilters.shop_id = Number(shopIdParam)
|
||||||
|
}
|
||||||
|
|
||||||
getAccountList()
|
getAccountList()
|
||||||
loadAllRoles()
|
loadAllRoles()
|
||||||
loadShopList()
|
loadShopList()
|
||||||
@@ -444,7 +452,7 @@
|
|||||||
|
|
||||||
// 显示分配角色对话框
|
// 显示分配角色对话框
|
||||||
const showRoleDialog = async (row: any) => {
|
const showRoleDialog = async (row: any) => {
|
||||||
currentAccountId.value = row.ID
|
currentAccountId.value = row.id
|
||||||
selectedRoles.value = []
|
selectedRoles.value = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -452,11 +460,11 @@
|
|||||||
await loadAllRoles()
|
await loadAllRoles()
|
||||||
|
|
||||||
// 先加载当前账号的角色,再打开对话框
|
// 先加载当前账号的角色,再打开对话框
|
||||||
const res = await AccountService.getAccountRoles(row.ID)
|
const res = await AccountService.getAccountRoles(row.id)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
// 提取角色ID数组
|
// 提取角色ID数组
|
||||||
const roles = res.data || []
|
const roles = res.data || []
|
||||||
selectedRoles.value = roles.map((role: any) => role.ID)
|
selectedRoles.value = roles.map((role: any) => role.id)
|
||||||
// 数据加载完成后再打开对话框
|
// 数据加载完成后再打开对话框
|
||||||
roleDialogVisible.value = true
|
roleDialogVisible.value = true
|
||||||
}
|
}
|
||||||
@@ -582,7 +590,7 @@
|
|||||||
// 先更新UI
|
// 先更新UI
|
||||||
row.status = newStatus
|
row.status = newStatus
|
||||||
try {
|
try {
|
||||||
await AccountService.updateAccountStatus(row.ID, newStatus as 0 | 1)
|
await AccountService.updateAccountStatus(row.id, newStatus as 0 | 1)
|
||||||
ElMessage.success('状态切换成功')
|
ElMessage.success('状态切换成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 切换失败,恢复原状态
|
// 切换失败,恢复原状态
|
||||||
|
|||||||
@@ -168,12 +168,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 客户账号列表弹窗 -->
|
|
||||||
<CustomerAccountDialog
|
|
||||||
v-model="customerAccountDialogVisible"
|
|
||||||
:enterprise-id="currentEnterpriseId"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 修改密码对话框 -->
|
<!-- 修改密码对话框 -->
|
||||||
<ElDialog v-model="passwordDialogVisible" title="修改密码" width="400px">
|
<ElDialog v-model="passwordDialogVisible" title="修改密码" width="400px">
|
||||||
<ElForm ref="passwordFormRef" :model="passwordForm" :rules="passwordRules">
|
<ElForm ref="passwordFormRef" :model="passwordForm" :rules="passwordRules">
|
||||||
@@ -199,6 +193,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 企业客户操作右键菜单 -->
|
||||||
|
<ArtMenuRight
|
||||||
|
ref="enterpriseOperationMenuRef"
|
||||||
|
:menu-items="enterpriseOperationMenuItems"
|
||||||
|
:menu-width="140"
|
||||||
|
@select="handleEnterpriseOperationMenuSelect"
|
||||||
|
/>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
</ArtTableFullScreen>
|
</ArtTableFullScreen>
|
||||||
@@ -215,9 +217,11 @@
|
|||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import CustomerAccountDialog from '@/components/business/CustomerAccountDialog.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 { formatDateTime } from '@/utils/business/format'
|
||||||
import { BgColorEnum } from '@/enums/appEnum'
|
import { BgColorEnum } from '@/enums/appEnum'
|
||||||
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
|
|
||||||
defineOptions({ name: 'EnterpriseCustomer' })
|
defineOptions({ name: 'EnterpriseCustomer' })
|
||||||
|
|
||||||
@@ -227,7 +231,6 @@
|
|||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const passwordDialogVisible = ref(false)
|
const passwordDialogVisible = ref(false)
|
||||||
const customerAccountDialogVisible = ref(false)
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const passwordSubmitLoading = ref(false)
|
const passwordSubmitLoading = ref(false)
|
||||||
@@ -236,6 +239,10 @@
|
|||||||
const currentEnterpriseId = ref<number>(0)
|
const currentEnterpriseId = ref<number>(0)
|
||||||
const shopList = ref<ShopResponse[]>([])
|
const shopList = ref<ShopResponse[]>([])
|
||||||
|
|
||||||
|
// 右键菜单
|
||||||
|
const enterpriseOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
|
const currentOperatingEnterprise = ref<EnterpriseItem | null>(null)
|
||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
enterprise_name: '',
|
enterprise_name: '',
|
||||||
@@ -450,47 +457,30 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 340,
|
width: 200,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: EnterpriseItem) => {
|
formatter: (row: EnterpriseItem) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
|
|
||||||
if (hasAuth('enterprise_customer:edit')) {
|
|
||||||
buttons.push(
|
|
||||||
h(ArtButtonTable, {
|
|
||||||
text: '编辑',
|
|
||||||
iconClass: BgColorEnum.SECONDARY,
|
|
||||||
onClick: () => showDialog('edit', row)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('enterprise_customer:look_customer')) {
|
|
||||||
buttons.push(
|
|
||||||
h(ArtButtonTable, {
|
|
||||||
text: '查看客户',
|
|
||||||
iconClass: BgColorEnum.PRIMARY,
|
|
||||||
onClick: () => viewCustomerAccounts(row)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('enterprise_customer:card_authorization')) {
|
if (hasAuth('enterprise_customer:card_authorization')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
text: '卡授权',
|
text: '卡授权',
|
||||||
iconClass: BgColorEnum.PRIMARY,
|
|
||||||
onClick: () => manageCards(row)
|
onClick: () => manageCards(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAuth('enterprise_customer:update_pwd')) {
|
// 只要有编辑、账号列表、修改密码权限之一,就显示更多操作按钮
|
||||||
|
if (
|
||||||
|
hasAuth('enterprise_customer:edit') ||
|
||||||
|
hasAuth('enterprise_customer:look_customer') ||
|
||||||
|
hasAuth('enterprise_customer:update_pwd')
|
||||||
|
) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
text: '修改密码',
|
text: '更多操作',
|
||||||
iconClass: BgColorEnum.WARNING,
|
onContextmenu: (e: MouseEvent) => showEnterpriseOperationMenu(e, row)
|
||||||
onClick: () => showPasswordDialog(row)
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -749,8 +739,11 @@
|
|||||||
|
|
||||||
// 查看客户账号
|
// 查看客户账号
|
||||||
const viewCustomerAccounts = (row: EnterpriseItem) => {
|
const viewCustomerAccounts = (row: EnterpriseItem) => {
|
||||||
currentEnterpriseId.value = row.id
|
router.push({
|
||||||
customerAccountDialogVisible.value = true
|
name: 'EnterpriseCustomerAccounts',
|
||||||
|
params: { id: row.id },
|
||||||
|
query: { type: 'enterprise' }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 卡管理
|
// 卡管理
|
||||||
@@ -760,6 +753,59 @@
|
|||||||
query: { id: row.id }
|
query: { id: row.id }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 企业客户操作菜单项配置
|
||||||
|
const enterpriseOperationMenuItems = computed((): MenuItemType[] => {
|
||||||
|
const items: MenuItemType[] = []
|
||||||
|
|
||||||
|
if (hasAuth('enterprise_customer:look_customer')) {
|
||||||
|
items.push({
|
||||||
|
key: 'accountList',
|
||||||
|
label: '账号列表'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAuth('enterprise_customer:edit')) {
|
||||||
|
items.push({
|
||||||
|
key: 'edit',
|
||||||
|
label: '编辑'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAuth('enterprise_customer:update_pwd')) {
|
||||||
|
items.push({
|
||||||
|
key: 'updatePassword',
|
||||||
|
label: '修改密码'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
|
||||||
|
// 显示企业客户操作右键菜单
|
||||||
|
const showEnterpriseOperationMenu = (e: MouseEvent, row: EnterpriseItem) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
currentOperatingEnterprise.value = row
|
||||||
|
enterpriseOperationMenuRef.value?.show(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理企业客户操作菜单选择
|
||||||
|
const handleEnterpriseOperationMenuSelect = (item: MenuItemType) => {
|
||||||
|
if (!currentOperatingEnterprise.value) return
|
||||||
|
|
||||||
|
switch (item.key) {
|
||||||
|
case 'accountList':
|
||||||
|
viewCustomerAccounts(currentOperatingEnterprise.value)
|
||||||
|
break
|
||||||
|
case 'edit':
|
||||||
|
showDialog('edit', currentOperatingEnterprise.value)
|
||||||
|
break
|
||||||
|
case 'updatePassword':
|
||||||
|
showPasswordDialog(currentOperatingEnterprise.value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -45,9 +45,9 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '新增平台账号' : '编辑平台账号'"
|
:title="dialogType === 'add' ? '新增平台账号' : '编辑平台账号'"
|
||||||
width="500px"
|
width="30%"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="120px">
|
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="80px">
|
||||||
<ElFormItem label="账号名称" prop="username">
|
<ElFormItem label="账号名称" prop="username">
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="formData.username"
|
v-model="formData.username"
|
||||||
@@ -297,14 +297,14 @@
|
|||||||
|
|
||||||
// 列配置
|
// 列配置
|
||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: 'ID', prop: 'ID' },
|
{ label: 'ID', prop: 'id' },
|
||||||
{ label: '账号名称', prop: 'username' },
|
{ label: '账号名称', prop: 'username' },
|
||||||
{ label: '手机号', prop: 'phone' },
|
{ label: '手机号', prop: 'phone' },
|
||||||
{ label: '账号类型', prop: 'user_type' },
|
{ label: '账号类型', prop: 'user_type' },
|
||||||
{ label: '店铺名称', prop: 'shop_name' },
|
{ label: '店铺名称', prop: 'shop_name' },
|
||||||
{ label: '企业名称', prop: 'enterprise_name' },
|
{ label: '企业名称', prop: 'enterprise_name' },
|
||||||
{ label: '状态', prop: 'status' },
|
{ label: '状态', prop: 'status' },
|
||||||
{ label: '创建时间', prop: 'CreatedAt' },
|
{ label: '创建时间', prop: 'created_at' },
|
||||||
{ label: '操作', prop: 'operation' }
|
{ label: '操作', prop: 'operation' }
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -319,7 +319,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'edit' && row) {
|
if (type === 'edit' && row) {
|
||||||
formData.id = row.ID
|
formData.id = row.id
|
||||||
formData.username = row.username
|
formData.username = row.username
|
||||||
formData.phone = row.phone
|
formData.phone = row.phone
|
||||||
formData.user_type = row.user_type
|
formData.user_type = row.user_type
|
||||||
@@ -348,7 +348,7 @@
|
|||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
try {
|
try {
|
||||||
await AccountService.deleteAccount(row.ID)
|
await AccountService.deleteAccount(row.id)
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
getAccountList()
|
getAccountList()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -362,7 +362,7 @@
|
|||||||
|
|
||||||
// 显示修改密码对话框
|
// 显示修改密码对话框
|
||||||
const showPasswordDialog = (row: PlatformAccount) => {
|
const showPasswordDialog = (row: PlatformAccount) => {
|
||||||
currentAccountId.value = row.ID
|
currentAccountId.value = row.id
|
||||||
passwordForm.new_password = ''
|
passwordForm.new_password = ''
|
||||||
passwordDialogVisible.value = true
|
passwordDialogVisible.value = true
|
||||||
if (passwordFormRef.value) {
|
if (passwordFormRef.value) {
|
||||||
@@ -373,8 +373,8 @@
|
|||||||
// 动态列配置
|
// 动态列配置
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
{
|
{
|
||||||
prop: 'ID',
|
prop: 'id',
|
||||||
label: 'ID',
|
label: 'id',
|
||||||
width: 80
|
width: 80
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -435,10 +435,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'CreatedAt',
|
prop: 'created_at',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: PlatformAccount) => formatDateTime(row.CreatedAt)
|
formatter: (row: PlatformAccount) => formatDateTime(row.created_at)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
@@ -530,7 +530,7 @@
|
|||||||
|
|
||||||
// 显示分配角色对话框
|
// 显示分配角色对话框
|
||||||
const showRoleDialog = async (row: PlatformAccount) => {
|
const showRoleDialog = async (row: PlatformAccount) => {
|
||||||
currentAccountId.value = row.ID
|
currentAccountId.value = row.id
|
||||||
selectedRoles.value = []
|
selectedRoles.value = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -538,11 +538,11 @@
|
|||||||
await loadAllRoles()
|
await loadAllRoles()
|
||||||
|
|
||||||
// 先加载当前账号的角色,再打开对话框
|
// 先加载当前账号的角色,再打开对话框
|
||||||
const res = await AccountService.getAccountRoles(row.ID)
|
const res = await AccountService.getAccountRoles(row.id)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
// 提取角色ID数组
|
// 提取角色ID数组
|
||||||
const roles = res.data || []
|
const roles = res.data || []
|
||||||
selectedRoles.value = roles.map((role: any) => role.ID)
|
selectedRoles.value = roles.map((role: any) => role.id)
|
||||||
// 数据加载完成后再打开对话框
|
// 数据加载完成后再打开对话框
|
||||||
roleDialogVisible.value = true
|
roleDialogVisible.value = true
|
||||||
}
|
}
|
||||||
@@ -738,7 +738,7 @@
|
|||||||
// 先更新UI
|
// 先更新UI
|
||||||
row.status = newStatus
|
row.status = newStatus
|
||||||
try {
|
try {
|
||||||
await AccountService.updateAccountStatus(row.ID, newStatus as 0 | 1)
|
await AccountService.updateAccountStatus(row.id, newStatus as 0 | 1)
|
||||||
ElMessage.success('状态切换成功')
|
ElMessage.success('状态切换成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 切换失败,恢复原状态
|
// 切换失败,恢复原状态
|
||||||
|
|||||||
@@ -250,7 +250,7 @@
|
|||||||
dialogType.value = type
|
dialogType.value = type
|
||||||
|
|
||||||
if (type === 'edit' && row) {
|
if (type === 'edit' && row) {
|
||||||
formData.id = row.ID
|
formData.id = row.id
|
||||||
formData.username = row.username
|
formData.username = row.username
|
||||||
formData.phone = row.phone
|
formData.phone = row.phone
|
||||||
formData.shop_id = row.shop_id || 0
|
formData.shop_id = row.shop_id || 0
|
||||||
@@ -273,7 +273,7 @@
|
|||||||
|
|
||||||
// 显示修改密码对话框
|
// 显示修改密码对话框
|
||||||
const showPasswordDialog = (row: PlatformAccount) => {
|
const showPasswordDialog = (row: PlatformAccount) => {
|
||||||
currentAccountId.value = row.ID
|
currentAccountId.value = row.id
|
||||||
passwordForm.new_password = ''
|
passwordForm.new_password = ''
|
||||||
|
|
||||||
// 重置表单验证状态
|
// 重置表单验证状态
|
||||||
@@ -290,7 +290,7 @@
|
|||||||
// 先更新UI
|
// 先更新UI
|
||||||
row.status = newStatus
|
row.status = newStatus
|
||||||
try {
|
try {
|
||||||
await AccountService.updateAccountStatus(row.ID, newStatus as 0 | 1)
|
await AccountService.updateAccountStatus(row.id, newStatus as 0 | 1)
|
||||||
ElMessage.success('状态切换成功')
|
ElMessage.success('状态切换成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 切换失败,恢复原状态
|
// 切换失败,恢复原状态
|
||||||
@@ -337,7 +337,7 @@
|
|||||||
{
|
{
|
||||||
prop: 'created_at',
|
prop: 'created_at',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
formatter: (row: PlatformAccount) => formatDateTime(row.CreatedAt)
|
formatter: (row: PlatformAccount) => formatDateTime(row.created_at)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
|
|||||||
300
src/views/common/account-list.vue
Normal file
300
src/views/common/account-list.vue
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
<template>
|
||||||
|
<ArtTableFullScreen>
|
||||||
|
<div class="account-list-page" id="table-full-screen">
|
||||||
|
<ElCard shadow="never">
|
||||||
|
<div class="detail-header">
|
||||||
|
<ElButton @click="handleBack">
|
||||||
|
<template #icon><ElIcon><ArrowLeft /></ElIcon></template>
|
||||||
|
返回
|
||||||
|
</ElButton>
|
||||||
|
<h2 class="detail-title">{{ pageTitle }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<ArtSearchBar
|
||||||
|
v-model:filter="searchForm"
|
||||||
|
:items="searchFormItems"
|
||||||
|
:show-expand="false"
|
||||||
|
@reset="handleReset"
|
||||||
|
@search="handleSearch"
|
||||||
|
></ArtSearchBar>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<ArtTable
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="ID"
|
||||||
|
:loading="loading"
|
||||||
|
:data="accountList"
|
||||||
|
:currentPage="pagination.page"
|
||||||
|
:pageSize="pagination.pageSize"
|
||||||
|
:total="pagination.total"
|
||||||
|
:marginTop="10"
|
||||||
|
height="60vh"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<ElTableColumn v-for="col in columns" :key="col.prop || (col as any).type" v-bind="col" />
|
||||||
|
</template>
|
||||||
|
</ArtTable>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
</ArtTableFullScreen>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h, computed } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { ElMessage, ElTag, ElIcon, ElButton, ElCard } from 'element-plus'
|
||||||
|
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||||
|
import { AccountService } from '@/api/modules'
|
||||||
|
import type { SearchFormItem } from '@/types'
|
||||||
|
import type { PlatformAccount } from '@/types/api'
|
||||||
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CommonAccountList' })
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 判断页面类型:通过 query 参数判断
|
||||||
|
const isShopType = computed(() => route.query.type === 'shop')
|
||||||
|
|
||||||
|
// 页面标题
|
||||||
|
const pageTitle = computed(() => (isShopType.value ? '店铺账号列表' : '企业客户账号列表'))
|
||||||
|
|
||||||
|
// 过滤参数键名
|
||||||
|
const filterParamKey = computed(() => (isShopType.value ? 'shop_id' : 'enterprise_id'))
|
||||||
|
|
||||||
|
// 用户类型映射
|
||||||
|
const getUserTypeName = (type: number): string => {
|
||||||
|
const typeMap: Record<number, string> = {
|
||||||
|
1: '超级管理员',
|
||||||
|
2: '平台用户',
|
||||||
|
3: '代理账号',
|
||||||
|
4: '企业账号'
|
||||||
|
}
|
||||||
|
return typeMap[type] || '未知'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态名称映射
|
||||||
|
const getStatusName = (status: number): string => {
|
||||||
|
return status === 1 ? '启用' : '禁用'
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableRef = ref()
|
||||||
|
const accountList = ref<PlatformAccount[]>([])
|
||||||
|
|
||||||
|
// 搜索表单初始值
|
||||||
|
const initialSearchState = {
|
||||||
|
username: '',
|
||||||
|
phone: '',
|
||||||
|
user_type: undefined as number | undefined,
|
||||||
|
status: undefined as number | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({ ...initialSearchState })
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 搜索表单配置
|
||||||
|
const searchFormItems: SearchFormItem[] = [
|
||||||
|
{
|
||||||
|
label: '用户名',
|
||||||
|
prop: 'username',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入用户名'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '手机号',
|
||||||
|
prop: 'phone',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入手机号'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户类型',
|
||||||
|
prop: 'user_type',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '全部'
|
||||||
|
},
|
||||||
|
options: () => [
|
||||||
|
{ label: '代理账号', value: 3 },
|
||||||
|
{ label: '企业账号', value: 4 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '状态',
|
||||||
|
prop: 'status',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '全部'
|
||||||
|
},
|
||||||
|
options: () => [
|
||||||
|
{ label: '启用', value: 1 },
|
||||||
|
{ label: '禁用', value: 0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 获取用户类型标签类型
|
||||||
|
const getUserTypeTag = (type: number) => {
|
||||||
|
return type === 3 ? 'success' : 'primary'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态标签类型
|
||||||
|
const getStatusTag = (status: number) => {
|
||||||
|
return status === 1 ? 'success' : 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 列配置
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
prop: 'username',
|
||||||
|
label: '用户名',
|
||||||
|
minWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'phone',
|
||||||
|
label: '手机号',
|
||||||
|
width: 130
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'user_type',
|
||||||
|
label: '用户类型',
|
||||||
|
width: 110,
|
||||||
|
formatter: (row: PlatformAccount) => {
|
||||||
|
return h(ElTag, { type: getUserTypeTag(row.user_type) }, () => getUserTypeName(row.user_type))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'shop_id',
|
||||||
|
label: '店铺ID',
|
||||||
|
width: 100,
|
||||||
|
formatter: (row: PlatformAccount) => row.shop_id || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'enterprise_id',
|
||||||
|
label: '企业ID',
|
||||||
|
width: 100,
|
||||||
|
formatter: (row: PlatformAccount) => row.enterprise_id || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'status',
|
||||||
|
label: '状态',
|
||||||
|
width: 100,
|
||||||
|
formatter: (row: PlatformAccount) => {
|
||||||
|
return h(ElTag, { type: getStatusTag(row.status) }, () => getStatusName(row.status))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'CreatedAt',
|
||||||
|
label: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
formatter: (row: PlatformAccount) => formatDateTime(row.CreatedAt)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 获取账号列表
|
||||||
|
const getTableData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const entityId = Number(route.params.id)
|
||||||
|
const params: any = {
|
||||||
|
page: pagination.page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
username: searchForm.username || undefined,
|
||||||
|
phone: searchForm.phone || undefined,
|
||||||
|
user_type: searchForm.user_type,
|
||||||
|
status: searchForm.status,
|
||||||
|
[filterParamKey.value]: entityId // 动态设置 shop_id 或 enterprise_id
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理空值
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
if (params[key] === '' || params[key] === undefined) {
|
||||||
|
delete params[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await AccountService.getAccounts(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
accountList.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 handleSizeChange = (newPageSize: number) => {
|
||||||
|
pagination.pageSize = newPageSize
|
||||||
|
getTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (newCurrentPage: number) => {
|
||||||
|
pagination.page = newCurrentPage
|
||||||
|
getTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const handleBack = () => {
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTableData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.account-list-page {
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
149
src/views/package-management/package-assign/detail.vue
Normal file
149
src/views/package-management/package-assign/detail.vue
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<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;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
|
|
||||||
|
.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>
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
<ArtSearchBar
|
<ArtSearchBar
|
||||||
v-model:filter="searchForm"
|
v-model:filter="searchForm"
|
||||||
:items="searchFormItems"
|
:items="searchFormItems"
|
||||||
:show-expand="false"
|
:show-expand="true"
|
||||||
|
label-width="85"
|
||||||
@reset="handleReset"
|
@reset="handleReset"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
></ArtSearchBar>
|
></ArtSearchBar>
|
||||||
@@ -40,70 +41,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
<!-- 修改成本价对话框 -->
|
|
||||||
<ElDialog
|
|
||||||
v-model="costPriceDialogVisible"
|
|
||||||
title="修改成本价"
|
|
||||||
width="500px"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
@closed="handleCostPriceDialogClosed"
|
|
||||||
>
|
|
||||||
<ElForm
|
|
||||||
ref="costPriceFormRef"
|
|
||||||
:model="costPriceForm"
|
|
||||||
:rules="costPriceRules"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="套餐名称">
|
|
||||||
<ElInput v-model="costPriceForm.package_name" disabled />
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="店铺名称">
|
|
||||||
<ElInput v-model="costPriceForm.shop_name" disabled />
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="原成本价(元)">
|
|
||||||
<ElInputNumber
|
|
||||||
v-model="costPriceForm.old_cost_price"
|
|
||||||
disabled
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="新成本价(元)" prop="cost_price">
|
|
||||||
<ElInputNumber
|
|
||||||
v-model="costPriceForm.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="costPriceDialogVisible = false">取消</ElButton>
|
|
||||||
<ElButton
|
|
||||||
type="primary"
|
|
||||||
@click="handleCostPriceSubmit(costPriceFormRef)"
|
|
||||||
:loading="costPriceSubmitLoading"
|
|
||||||
>
|
|
||||||
提交
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ElDialog>
|
|
||||||
|
|
||||||
<!-- 新增/编辑对话框 -->
|
<!-- 新增/编辑对话框 -->
|
||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '新增分配' : '编辑分配'"
|
:title="dialogType === 'add' ? '新增分配' : '编辑分配'"
|
||||||
width="600px"
|
width="30%"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="90px">
|
||||||
<ElFormItem label="选择套餐" prop="package_id" v-if="dialogType === 'add'">
|
<ElFormItem label="选择套餐" prop="package_id" v-if="dialogType === 'add'">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="form.package_id"
|
v-model="form.package_id"
|
||||||
@@ -125,23 +71,18 @@
|
|||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="选择店铺" prop="shop_id" v-if="dialogType === 'add'">
|
<ElFormItem label="选择店铺" prop="shop_id" v-if="dialogType === 'add'">
|
||||||
<ElSelect
|
<ElTreeSelect
|
||||||
v-model="form.shop_id"
|
v-model="form.shop_id"
|
||||||
|
:data="shopTreeData"
|
||||||
|
:props="{ label: 'shop_name', value: 'id', children: 'children' }"
|
||||||
placeholder="请选择店铺"
|
placeholder="请选择店铺"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
filterable
|
filterable
|
||||||
remote
|
|
||||||
:remote-method="searchShop"
|
|
||||||
:loading="shopLoading"
|
|
||||||
clearable
|
clearable
|
||||||
>
|
:loading="shopLoading"
|
||||||
<ElOption
|
check-strictly
|
||||||
v-for="shop in shopOptions"
|
:render-after-expand="false"
|
||||||
:key="shop.id"
|
/>
|
||||||
:label="shop.shop_name"
|
|
||||||
:value="shop.id"
|
|
||||||
/>
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="成本价(元)" prop="cost_price">
|
<ElFormItem label="成本价(元)" prop="cost_price">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
@@ -171,7 +112,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { ShopPackageAllocationService, PackageManageService, ShopService } from '@/api/modules'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ShopPackageAllocationService, PackageManageService, ShopService, ShopSeriesAllocationService } from '@/api/modules'
|
||||||
import { ElMessage, ElMessageBox, ElSwitch } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElSwitch } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import type { ShopPackageAllocationResponse, PackageResponse, ShopResponse } from '@/types/api'
|
import type { ShopPackageAllocationResponse, PackageResponse, ShopResponse } from '@/types/api'
|
||||||
@@ -180,6 +122,7 @@
|
|||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
import {
|
import {
|
||||||
CommonStatus,
|
CommonStatus,
|
||||||
getStatusText,
|
getStatusText,
|
||||||
@@ -190,26 +133,29 @@
|
|||||||
defineOptions({ name: 'PackageAssign' })
|
defineOptions({ name: 'PackageAssign' })
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const costPriceDialogVisible = ref(false)
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const costPriceSubmitLoading = ref(false)
|
|
||||||
const packageLoading = ref(false)
|
const packageLoading = ref(false)
|
||||||
const shopLoading = ref(false)
|
const shopLoading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const costPriceFormRef = ref<FormInstance>()
|
|
||||||
const packageOptions = ref<PackageResponse[]>([])
|
const packageOptions = ref<PackageResponse[]>([])
|
||||||
const shopOptions = ref<ShopResponse[]>([])
|
const shopOptions = ref<ShopResponse[]>([])
|
||||||
|
const shopTreeData = ref<ShopResponse[]>([])
|
||||||
const searchPackageOptions = ref<PackageResponse[]>([])
|
const searchPackageOptions = ref<PackageResponse[]>([])
|
||||||
const searchShopOptions = ref<ShopResponse[]>([])
|
const searchShopOptions = ref<ShopResponse[]>([])
|
||||||
|
const searchAllocatorShopOptions = ref<ShopResponse[]>([])
|
||||||
|
const searchSeriesAllocationOptions = ref<any[]>([])
|
||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
shop_id: undefined as number | undefined,
|
shop_id: undefined as number | undefined,
|
||||||
package_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
|
status: undefined as number | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +165,7 @@
|
|||||||
// 搜索表单配置
|
// 搜索表单配置
|
||||||
const searchFormItems = computed<SearchFormItem[]>(() => [
|
const searchFormItems = computed<SearchFormItem[]>(() => [
|
||||||
{
|
{
|
||||||
label: '店铺',
|
label: '被分配店铺',
|
||||||
prop: 'shop_id',
|
prop: 'shop_id',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
config: {
|
config: {
|
||||||
@@ -254,6 +200,44 @@
|
|||||||
value: p.id
|
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: '状态',
|
label: '状态',
|
||||||
prop: 'status',
|
prop: 'status',
|
||||||
@@ -278,10 +262,11 @@
|
|||||||
|
|
||||||
// 列配置
|
// 列配置
|
||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: 'ID', prop: 'id' },
|
|
||||||
{ label: '套餐编码', prop: 'package_code' },
|
{ label: '套餐编码', prop: 'package_code' },
|
||||||
{ label: '套餐名称', prop: 'package_name' },
|
{ label: '套餐名称', prop: 'package_name' },
|
||||||
{ label: '店铺名称', prop: 'shop_name' },
|
{ label: '系列名称', prop: 'series_name' },
|
||||||
|
{ label: '被分配店铺', prop: 'shop_name' },
|
||||||
|
{ label: '分配者', prop: 'allocator_shop_name' },
|
||||||
{ label: '成本价', prop: 'cost_price' },
|
{ label: '成本价', prop: 'cost_price' },
|
||||||
{ label: '状态', prop: 'status' },
|
{ label: '状态', prop: 'status' },
|
||||||
{ label: '创建时间', prop: 'created_at' },
|
{ label: '创建时间', prop: 'created_at' },
|
||||||
@@ -320,45 +305,47 @@
|
|||||||
package_base_price: 0 // 存储选中套餐的成本价,用于验证
|
package_base_price: 0 // 存储选中套餐的成本价,用于验证
|
||||||
})
|
})
|
||||||
|
|
||||||
// 成本价表单验证规则
|
|
||||||
const costPriceRules = reactive<FormRules>({
|
|
||||||
cost_price: [{ required: true, message: '请输入新成本价', trigger: 'blur' }]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 成本价表单数据
|
|
||||||
const costPriceForm = reactive<any>({
|
|
||||||
id: 0,
|
|
||||||
package_name: '',
|
|
||||||
shop_name: '',
|
|
||||||
old_cost_price: 0,
|
|
||||||
cost_price: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const allocationList = ref<ShopPackageAllocationResponse[]>([])
|
const allocationList = ref<ShopPackageAllocationResponse[]>([])
|
||||||
const dialogType = ref('add')
|
const dialogType = ref('add')
|
||||||
|
|
||||||
// 动态列配置
|
// 动态列配置
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
{
|
|
||||||
prop: 'id',
|
|
||||||
label: 'ID',
|
|
||||||
width: 80
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
prop: 'package_code',
|
prop: 'package_code',
|
||||||
label: '套餐编码',
|
label: '套餐编码',
|
||||||
minWidth: 150
|
minWidth: 200,
|
||||||
|
showOverflowTooltip: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'package_name',
|
prop: 'package_name',
|
||||||
label: '套餐名称',
|
label: '套餐名称',
|
||||||
minWidth: 180
|
minWidth: 180
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'series_name',
|
||||||
|
label: '系列名称',
|
||||||
|
minWidth: 150
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'shop_name',
|
prop: 'shop_name',
|
||||||
label: '店铺名称',
|
label: '被分配店铺',
|
||||||
minWidth: 180
|
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',
|
prop: 'cost_price',
|
||||||
label: '成本价',
|
label: '成本价',
|
||||||
@@ -399,24 +386,22 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 230,
|
width: 200,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: ShopPackageAllocationResponse) => {
|
formatter: (row: ShopPackageAllocationResponse) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
|
|
||||||
if (hasAuth('package_assign:update_cost')) {
|
buttons.push(
|
||||||
buttons.push(
|
h(ArtButtonTable, {
|
||||||
h(ArtButtonTable, {
|
type:"view",
|
||||||
text: '修改成本价',
|
onClick: () => handleViewDetail(row)
|
||||||
onClick: () => showCostPriceDialog(row)
|
})
|
||||||
})
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('package_assign:edit')) {
|
if (hasAuth('package_assign:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'edit',
|
type:"edit",
|
||||||
onClick: () => showDialog('edit', row)
|
onClick: () => showDialog('edit', row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -425,7 +410,7 @@
|
|||||||
if (hasAuth('package_assign:delete')) {
|
if (hasAuth('package_assign:delete')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'delete',
|
type:"delete",
|
||||||
onClick: () => deleteAllocation(row)
|
onClick: () => deleteAllocation(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -436,11 +421,40 @@
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// 构建树形结构数据
|
||||||
|
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(() => {
|
onMounted(() => {
|
||||||
loadPackageOptions()
|
loadPackageOptions()
|
||||||
loadShopOptions()
|
loadShopOptions()
|
||||||
loadSearchPackageOptions()
|
loadSearchPackageOptions()
|
||||||
loadSearchShopOptions()
|
loadSearchShopOptions()
|
||||||
|
loadSearchAllocatorShopOptions()
|
||||||
|
loadSearchSeriesAllocationOptions()
|
||||||
getTableData()
|
getTableData()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -466,20 +480,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载店铺选项(用于新增对话框,默认加载10条)
|
// 加载店铺选项(用于新增对话框,加载所有店铺并构建树形结构)
|
||||||
const loadShopOptions = async (shopName?: string) => {
|
const loadShopOptions = async () => {
|
||||||
shopLoading.value = true
|
shopLoading.value = true
|
||||||
try {
|
try {
|
||||||
const params: any = {
|
// 加载所有店铺,不分页
|
||||||
|
const res = await ShopService.getShops({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 10
|
page_size: 10000 // 使用较大的值获取所有店铺
|
||||||
}
|
})
|
||||||
if (shopName) {
|
|
||||||
params.shop_name = shopName
|
|
||||||
}
|
|
||||||
const res = await ShopService.getShops(params)
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
shopOptions.value = res.data.items || []
|
shopOptions.value = res.data.items || []
|
||||||
|
// 构建树形结构数据
|
||||||
|
shopTreeData.value = buildTreeData(shopOptions.value)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载店铺选项失败:', error)
|
console.error('加载店铺选项失败:', error)
|
||||||
@@ -524,15 +537,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索店铺(用于新增对话框)
|
|
||||||
const searchShop = (query: string) => {
|
|
||||||
if (query) {
|
|
||||||
loadShopOptions(query)
|
|
||||||
} else {
|
|
||||||
loadShopOptions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索套餐(用于搜索栏)
|
// 搜索套餐(用于搜索栏)
|
||||||
const handleSearchPackage = async (query: string) => {
|
const handleSearchPackage = async (query: string) => {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
@@ -573,6 +577,73 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载搜索栏分配者店铺选项(默认加载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 () => {
|
const getTableData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -582,6 +653,8 @@
|
|||||||
page_size: pagination.page_size,
|
page_size: pagination.page_size,
|
||||||
shop_id: searchForm.shop_id || undefined,
|
shop_id: searchForm.shop_id || undefined,
|
||||||
package_id: searchForm.package_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
|
status: searchForm.status || undefined
|
||||||
}
|
}
|
||||||
const res = await ShopPackageAllocationService.getShopPackageAllocations(params)
|
const res = await ShopPackageAllocationService.getShopPackageAllocations(params)
|
||||||
@@ -657,9 +730,9 @@
|
|||||||
// 从套餐选项中找到选中的套餐
|
// 从套餐选项中找到选中的套餐
|
||||||
const selectedPackage = packageOptions.value.find((pkg) => pkg.id === packageId)
|
const selectedPackage = packageOptions.value.find((pkg) => pkg.id === packageId)
|
||||||
if (selectedPackage) {
|
if (selectedPackage) {
|
||||||
// 将套餐的价格(分)转换为元显示
|
// 将套餐的成本价(分)转换为元显示
|
||||||
form.cost_price = selectedPackage.price / 100
|
form.cost_price = selectedPackage.cost_price / 100
|
||||||
form.package_base_price = selectedPackage.price // 保持原始值(分)用于验证
|
form.package_base_price = selectedPackage.cost_price // 保持原始值(分)用于验证
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 清空时重置成本价
|
// 清空时重置成本价
|
||||||
@@ -744,60 +817,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示修改成本价对话框
|
|
||||||
const showCostPriceDialog = (row: ShopPackageAllocationResponse) => {
|
|
||||||
costPriceDialogVisible.value = true
|
|
||||||
costPriceForm.id = row.id
|
|
||||||
costPriceForm.package_name = row.package_name
|
|
||||||
costPriceForm.shop_name = row.shop_name
|
|
||||||
costPriceForm.old_cost_price = row.cost_price / 100 // 分转换为元显示
|
|
||||||
costPriceForm.cost_price = row.cost_price / 100 // 分转换为元显示
|
|
||||||
|
|
||||||
// 重置表单验证状态
|
|
||||||
nextTick(() => {
|
|
||||||
costPriceFormRef.value?.clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理成本价弹窗关闭事件
|
|
||||||
const handleCostPriceDialogClosed = () => {
|
|
||||||
// 清除表单验证状态
|
|
||||||
costPriceFormRef.value?.clearValidate()
|
|
||||||
// 重置表单数据
|
|
||||||
costPriceForm.id = 0
|
|
||||||
costPriceForm.package_name = ''
|
|
||||||
costPriceForm.shop_name = ''
|
|
||||||
costPriceForm.old_cost_price = 0
|
|
||||||
costPriceForm.cost_price = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交成本价修改
|
|
||||||
const handleCostPriceSubmit = async (formEl: FormInstance | undefined) => {
|
|
||||||
if (!formEl) return
|
|
||||||
|
|
||||||
await formEl.validate(async (valid) => {
|
|
||||||
if (valid) {
|
|
||||||
costPriceSubmitLoading.value = true
|
|
||||||
try {
|
|
||||||
// 将元转换为分提交给后端
|
|
||||||
const costPriceInCents = Math.round(costPriceForm.cost_price * 100)
|
|
||||||
|
|
||||||
await ShopPackageAllocationService.updateShopPackageAllocationCostPrice(
|
|
||||||
costPriceForm.id,
|
|
||||||
costPriceInCents
|
|
||||||
)
|
|
||||||
ElMessage.success('修改成本价成功')
|
|
||||||
costPriceDialogVisible.value = false
|
|
||||||
await getTableData()
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
} finally {
|
|
||||||
costPriceSubmitLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态切换
|
// 状态切换
|
||||||
const handleStatusChange = async (
|
const handleStatusChange = async (
|
||||||
row: ShopPackageAllocationResponse,
|
row: ShopPackageAllocationResponse,
|
||||||
@@ -814,6 +833,11 @@
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleViewDetail = (row: ShopPackageAllocationResponse) => {
|
||||||
|
router.push(`${RoutesAlias.PackageAssignDetail}/${row.id}`)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -57,11 +57,11 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="新增套餐"
|
title="新增套餐"
|
||||||
width="800px"
|
width="55%"
|
||||||
align-center
|
align-center
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="140px">
|
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="150px">
|
||||||
<ElRow :gutter="24">
|
<ElRow :gutter="24">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="套餐系列" prop="series">
|
<ElFormItem label="套餐系列" prop="series">
|
||||||
|
|||||||
248
src/views/package-management/package-list/detail.vue
Normal file
248
src/views/package-management/package-list/detail.vue
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<template>
|
||||||
|
<div class="package-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 { PackageManageService } from '@/api/modules'
|
||||||
|
import type { PackageResponse } from '@/types/api'
|
||||||
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
|
defineOptions({ name: 'PackageDetail' })
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const detailData = ref<PackageResponse | null>(null)
|
||||||
|
|
||||||
|
// 详情页配置
|
||||||
|
const detailSections: DetailSection[] = [
|
||||||
|
{
|
||||||
|
title: '基本信息',
|
||||||
|
fields: [
|
||||||
|
{ label: 'ID', prop: 'id' },
|
||||||
|
{ label: '套餐编码', prop: 'package_code' },
|
||||||
|
{ label: '套餐名称', prop: 'package_name' },
|
||||||
|
{ label: '套餐系列', prop: 'series_name' },
|
||||||
|
{
|
||||||
|
label: '套餐类型',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const typeMap = {
|
||||||
|
formal: '正式套餐',
|
||||||
|
addon: '附加套餐'
|
||||||
|
}
|
||||||
|
return typeMap[data.package_type] || data.package_type
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '套餐时长',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return `${data.duration_months} 个月`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '状态',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return data.status === 1 ? '启用' : '禁用'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '上架状态',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return data.shelf_status === 1 ? '上架' : '下架'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
||||||
|
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '流量配置',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '真流量额度',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (data.real_data_mb >= 1024) {
|
||||||
|
return `${(data.real_data_mb / 1024).toFixed(2)} GB`
|
||||||
|
}
|
||||||
|
return `${data.real_data_mb} MB`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '启用虚流量',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return data.enable_virtual_data ? '是' : '否'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '虚流量额度',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (!data.enable_virtual_data) return '-'
|
||||||
|
if (data.virtual_data_mb >= 1024) {
|
||||||
|
return `${(data.virtual_data_mb / 1024).toFixed(2)} GB`
|
||||||
|
}
|
||||||
|
return `${data.virtual_data_mb} MB`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '价格信息',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '成本价',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return `¥${(data.cost_price / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '建议售价',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return `¥${(data.suggested_retail_price / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '利润空间',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (data.profit_margin === null || data.profit_margin === undefined) return '-'
|
||||||
|
return `¥${(data.profit_margin / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '佣金配置',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '当前返佣比例',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return data.current_commission_rate || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '一次性佣金金额',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (data.one_time_commission_amount === null || data.one_time_commission_amount === undefined) return '-'
|
||||||
|
return `¥${(data.one_time_commission_amount / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '当前返佣档位',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (!data.tier_info || !data.tier_info.current_rate) return '-'
|
||||||
|
return data.tier_info.current_rate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '下一档位比例',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (!data.tier_info || !data.tier_info.next_rate) return '-'
|
||||||
|
return data.tier_info.next_rate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '下一档位阈值',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (!data.tier_info || data.tier_info.next_threshold === null || data.tier_info.next_threshold === undefined) return '-'
|
||||||
|
return data.tier_info.next_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 PackageManageService.getPackageDetail(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-detail {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
|
|
||||||
|
.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>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<ArtSearchBar
|
<ArtSearchBar
|
||||||
v-model:filter="searchForm"
|
v-model:filter="searchForm"
|
||||||
:items="searchFormItems"
|
:items="searchFormItems"
|
||||||
:show-expand="false"
|
:show-expand="true"
|
||||||
@reset="handleReset"
|
@reset="handleReset"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
></ArtSearchBar>
|
></ArtSearchBar>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="150px">
|
||||||
<ElFormItem label="套餐编码" prop="package_code">
|
<ElFormItem label="套餐编码" prop="package_code">
|
||||||
<div style="display: flex; gap: 8px;">
|
<div style="display: flex; gap: 8px;">
|
||||||
<ElInput
|
<ElInput
|
||||||
@@ -99,17 +99,7 @@
|
|||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="流量类型" prop="data_type">
|
<ElFormItem label="真流量额度(MB)" prop="real_data_mb">
|
||||||
<ElSelect v-model="form.data_type" placeholder="请选择流量类型" style="width: 100%">
|
|
||||||
<ElOption
|
|
||||||
v-for="option in DATA_TYPE_OPTIONS"
|
|
||||||
:key="option.value"
|
|
||||||
:label="option.label"
|
|
||||||
:value="option.value"
|
|
||||||
/>
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="真流量额度(MB)" prop="real_data_mb" v-if="form.data_type === 'real'">
|
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.real_data_mb"
|
v-model="form.real_data_mb"
|
||||||
:min="0"
|
:min="0"
|
||||||
@@ -118,10 +108,17 @@
|
|||||||
placeholder="请输入真流量额度"
|
placeholder="请输入真流量额度"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
<ElFormItem label="启用虚流量">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="form.enable_virtual_data"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="不启用"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
<ElFormItem
|
<ElFormItem
|
||||||
label="虚流量额度(MB)"
|
label="虚流量额度(MB)"
|
||||||
prop="virtual_data_mb"
|
prop="virtual_data_mb"
|
||||||
v-if="form.data_type === 'virtual'"
|
v-if="form.enable_virtual_data"
|
||||||
>
|
>
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.virtual_data_mb"
|
v-model="form.virtual_data_mb"
|
||||||
@@ -140,15 +137,26 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="价格(元)" prop="price">
|
<ElFormItem label="成本价(元)" prop="cost_price">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.price"
|
v-model="form.cost_price"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入价格"
|
placeholder="请输入成本价"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="建议售价(元)" prop="suggested_retail_price">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.suggested_retail_price"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入建议售价(可选)"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="套餐描述" prop="description">
|
<ElFormItem label="套餐描述" prop="description">
|
||||||
@@ -178,6 +186,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { PackageManageService, PackageSeriesService } from '@/api/modules'
|
import { PackageManageService, PackageSeriesService } from '@/api/modules'
|
||||||
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElButton } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElButton } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
@@ -187,25 +196,22 @@
|
|||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
import {
|
import {
|
||||||
CommonStatus,
|
CommonStatus,
|
||||||
getStatusText,
|
getStatusText,
|
||||||
frontendStatusToApi,
|
frontendStatusToApi,
|
||||||
apiStatusToFrontend,
|
apiStatusToFrontend,
|
||||||
PACKAGE_TYPE_OPTIONS,
|
PACKAGE_TYPE_OPTIONS,
|
||||||
DATA_TYPE_OPTIONS,
|
|
||||||
SHELF_STATUS_OPTIONS,
|
|
||||||
getPackageTypeLabel,
|
getPackageTypeLabel,
|
||||||
getPackageTypeTag,
|
getPackageTypeTag
|
||||||
getDataTypeLabel,
|
|
||||||
getDataTypeTag,
|
|
||||||
getShelfStatusText
|
|
||||||
} from '@/config/constants'
|
} from '@/config/constants'
|
||||||
import { generatePackageCode } from '@/utils/codeGenerator'
|
import { generatePackageCode } from '@/utils/codeGenerator'
|
||||||
|
|
||||||
defineOptions({ name: 'PackageList' })
|
defineOptions({ name: 'PackageList' })
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -308,16 +314,15 @@
|
|||||||
|
|
||||||
// 列配置
|
// 列配置
|
||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: 'ID', prop: 'id' },
|
|
||||||
{ label: '套餐编码', prop: 'package_code' },
|
{ label: '套餐编码', prop: 'package_code' },
|
||||||
{ label: '套餐名称', prop: 'package_name' },
|
{ label: '套餐名称', prop: 'package_name' },
|
||||||
{ label: '所属系列', prop: 'series_name' },
|
{ label: '所属系列', prop: 'series_name' },
|
||||||
{ label: '套餐类型', prop: 'package_type' },
|
{ label: '套餐类型', prop: 'package_type' },
|
||||||
{ label: '流量类型', prop: 'data_type' },
|
|
||||||
{ label: '真流量', prop: 'real_data_mb' },
|
{ label: '真流量', prop: 'real_data_mb' },
|
||||||
{ label: '虚流量', prop: 'virtual_data_mb' },
|
{ label: '虚流量', prop: 'virtual_data_mb' },
|
||||||
{ label: '有效期', prop: 'duration_months' },
|
{ label: '有效期', prop: 'duration_months' },
|
||||||
{ label: '价格', prop: 'price' },
|
{ label: '成本价', prop: 'cost_price' },
|
||||||
|
{ label: '建议售价', prop: 'suggested_retail_price' },
|
||||||
{ label: '上架状态', prop: 'shelf_status' },
|
{ label: '上架状态', prop: 'shelf_status' },
|
||||||
{ label: '状态', prop: 'status' },
|
{ label: '状态', prop: 'status' },
|
||||||
{ label: '创建时间', prop: 'created_at' },
|
{ label: '创建时间', prop: 'created_at' },
|
||||||
@@ -335,20 +340,16 @@
|
|||||||
{ required: true, message: '请输入套餐名称', trigger: 'blur' },
|
{ required: true, message: '请输入套餐名称', trigger: 'blur' },
|
||||||
{ min: 1, max: 255, message: '长度在 1 到 255 个字符', trigger: 'blur' }
|
{ min: 1, max: 255, message: '长度在 1 到 255 个字符', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
series_id: [{ required: true, message: '请选择套餐系列', trigger: 'change' }],
|
|
||||||
package_type: [{ required: true, message: '请选择套餐类型', trigger: 'change' }],
|
package_type: [{ required: true, message: '请选择套餐类型', trigger: 'change' }],
|
||||||
data_type: [{ required: true, message: '请选择流量类型', trigger: 'change' }],
|
|
||||||
duration_months: [
|
duration_months: [
|
||||||
{ required: true, message: '请输入有效期', trigger: 'blur' },
|
{ required: true, message: '请输入有效期', trigger: 'blur' },
|
||||||
{ type: 'number', min: 1, max: 120, message: '有效期范围 1-120 月', trigger: 'blur' }
|
{ type: 'number', min: 1, max: 120, message: '有效期范围 1-120 月', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
price: [{ required: true, message: '请输入价格', trigger: 'blur' }]
|
cost_price: [{ required: true, message: '请输入成本价', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据流量类型动态添加验证规则
|
// 如果启用虚流量,则虚流量额度为必填
|
||||||
if (form.data_type === 'real') {
|
if (form.enable_virtual_data) {
|
||||||
baseRules.real_data_mb = [{ required: true, message: '请输入真流量额度', trigger: 'blur' }]
|
|
||||||
} else if (form.data_type === 'virtual') {
|
|
||||||
baseRules.virtual_data_mb = [{ required: true, message: '请输入虚流量额度', trigger: 'blur' }]
|
baseRules.virtual_data_mb = [{ required: true, message: '请输入虚流量额度', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,11 +363,12 @@
|
|||||||
package_name: '',
|
package_name: '',
|
||||||
series_id: undefined,
|
series_id: undefined,
|
||||||
package_type: '',
|
package_type: '',
|
||||||
data_type: '',
|
enable_virtual_data: false,
|
||||||
real_data_mb: 0,
|
real_data_mb: 0,
|
||||||
virtual_data_mb: 0,
|
virtual_data_mb: 0,
|
||||||
duration_months: 1,
|
duration_months: 1,
|
||||||
price: 0,
|
cost_price: 0,
|
||||||
|
suggested_retail_price: undefined,
|
||||||
description: ''
|
description: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -375,20 +377,16 @@
|
|||||||
|
|
||||||
// 动态列配置
|
// 动态列配置
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
{
|
|
||||||
prop: 'id',
|
|
||||||
label: 'ID',
|
|
||||||
width: 80
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
prop: 'package_code',
|
prop: 'package_code',
|
||||||
label: '套餐编码',
|
label: '套餐编码',
|
||||||
minWidth: 150
|
showOverflowTooltip: true,
|
||||||
|
width: 210
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'package_name',
|
prop: 'package_name',
|
||||||
label: '套餐名称',
|
label: '套餐名称',
|
||||||
minWidth: 180
|
minWidth: 160
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'series_name',
|
prop: 'series_name',
|
||||||
@@ -405,16 +403,6 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
prop: 'data_type',
|
|
||||||
label: '流量类型',
|
|
||||||
width: 100,
|
|
||||||
formatter: (row: PackageResponse) => {
|
|
||||||
return h(ElTag, { type: getDataTypeTag(row.data_type), size: 'small' }, () =>
|
|
||||||
getDataTypeLabel(row.data_type)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
prop: 'real_data_mb',
|
prop: 'real_data_mb',
|
||||||
label: '真流量',
|
label: '真流量',
|
||||||
@@ -434,10 +422,17 @@
|
|||||||
formatter: (row: PackageResponse) => `${row.duration_months}月`
|
formatter: (row: PackageResponse) => `${row.duration_months}月`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'price',
|
prop: 'cost_price',
|
||||||
label: '价格',
|
label: '成本价',
|
||||||
width: 100,
|
width: 100,
|
||||||
formatter: (row: PackageResponse) => `¥${(row.price / 100).toFixed(2)}`
|
formatter: (row: PackageResponse) => `¥${(row.cost_price / 100).toFixed(2)}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'suggested_retail_price',
|
||||||
|
label: '建议售价',
|
||||||
|
width: 100,
|
||||||
|
formatter: (row: PackageResponse) =>
|
||||||
|
row.suggested_retail_price ? `¥${(row.suggested_retail_price / 100).toFixed(2)}` : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'shelf_status',
|
prop: 'shelf_status',
|
||||||
@@ -483,11 +478,18 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 150,
|
width: 200,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: PackageResponse) => {
|
formatter: (row: PackageResponse) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
|
|
||||||
|
buttons.push(
|
||||||
|
h(ArtButtonTable, {
|
||||||
|
type: 'view',
|
||||||
|
onClick: () => handleViewDetail(row)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
if (hasAuth('package:edit')) {
|
if (hasAuth('package:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
@@ -511,14 +513,12 @@
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
// 监听流量类型变化,重置未使用的流量字段
|
// 监听虚流量开关变化,关闭时重置虚流量额度
|
||||||
watch(
|
watch(
|
||||||
() => form.data_type,
|
() => form.enable_virtual_data,
|
||||||
(newType) => {
|
(enabled) => {
|
||||||
if (newType === 'real') {
|
if (!enabled) {
|
||||||
form.virtual_data_mb = 0
|
form.virtual_data_mb = 0
|
||||||
} else if (newType === 'virtual') {
|
|
||||||
form.real_data_mb = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -656,11 +656,12 @@
|
|||||||
form.package_name = row.package_name
|
form.package_name = row.package_name
|
||||||
form.series_id = row.series_id
|
form.series_id = row.series_id
|
||||||
form.package_type = row.package_type
|
form.package_type = row.package_type
|
||||||
form.data_type = row.data_type
|
form.enable_virtual_data = row.enable_virtual_data || false
|
||||||
form.real_data_mb = row.real_data_mb
|
form.real_data_mb = row.real_data_mb || 0
|
||||||
form.virtual_data_mb = row.virtual_data_mb
|
form.virtual_data_mb = row.virtual_data_mb || 0
|
||||||
form.duration_months = row.duration_months
|
form.duration_months = row.duration_months
|
||||||
form.price = row.price / 100 // 分转换为元显示
|
form.cost_price = row.cost_price / 100 // 分转换为元显示
|
||||||
|
form.suggested_retail_price = row.suggested_retail_price ? row.suggested_retail_price / 100 : undefined
|
||||||
form.description = row.description || ''
|
form.description = row.description || ''
|
||||||
} else {
|
} else {
|
||||||
form.id = 0
|
form.id = 0
|
||||||
@@ -668,11 +669,12 @@
|
|||||||
form.package_name = ''
|
form.package_name = ''
|
||||||
form.series_id = undefined
|
form.series_id = undefined
|
||||||
form.package_type = ''
|
form.package_type = ''
|
||||||
form.data_type = ''
|
form.enable_virtual_data = false
|
||||||
form.real_data_mb = 0
|
form.real_data_mb = 0
|
||||||
form.virtual_data_mb = 0
|
form.virtual_data_mb = 0
|
||||||
form.duration_months = 1
|
form.duration_months = 1
|
||||||
form.price = 0
|
form.cost_price = 0
|
||||||
|
form.suggested_retail_price = undefined
|
||||||
form.description = ''
|
form.description = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,6 +687,10 @@
|
|||||||
// 生成套餐编码
|
// 生成套餐编码
|
||||||
const handleGeneratePackageCode = () => {
|
const handleGeneratePackageCode = () => {
|
||||||
form.package_code = generatePackageCode()
|
form.package_code = generatePackageCode()
|
||||||
|
// 生成编码后清除该字段的验证错误
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearValidate('package_code')
|
||||||
|
})
|
||||||
ElMessage.success('编码生成成功')
|
ElMessage.success('编码生成成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,11 +704,12 @@
|
|||||||
form.package_name = ''
|
form.package_name = ''
|
||||||
form.series_id = undefined
|
form.series_id = undefined
|
||||||
form.package_type = ''
|
form.package_type = ''
|
||||||
form.data_type = ''
|
form.enable_virtual_data = false
|
||||||
form.real_data_mb = 0
|
form.real_data_mb = 0
|
||||||
form.virtual_data_mb = 0
|
form.virtual_data_mb = 0
|
||||||
form.duration_months = 1
|
form.duration_months = 1
|
||||||
form.price = 0
|
form.cost_price = 0
|
||||||
|
form.suggested_retail_price = undefined
|
||||||
form.description = ''
|
form.description = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,19 +743,32 @@
|
|||||||
submitLoading.value = true
|
submitLoading.value = true
|
||||||
try {
|
try {
|
||||||
// 将元转换为分提交给后端
|
// 将元转换为分提交给后端
|
||||||
const priceInCents = Math.round(form.price * 100)
|
const costPriceInCents = Math.round(form.cost_price * 100)
|
||||||
|
const suggestedRetailPriceInCents = form.suggested_retail_price
|
||||||
|
? Math.round(form.suggested_retail_price * 100)
|
||||||
|
: undefined
|
||||||
|
|
||||||
const data = {
|
const data: any = {
|
||||||
package_code: form.package_code,
|
package_code: form.package_code,
|
||||||
package_name: form.package_name,
|
package_name: form.package_name,
|
||||||
series_id: form.series_id,
|
|
||||||
package_type: form.package_type,
|
package_type: form.package_type,
|
||||||
data_type: form.data_type,
|
|
||||||
real_data_mb: form.real_data_mb,
|
|
||||||
virtual_data_mb: form.virtual_data_mb,
|
|
||||||
duration_months: form.duration_months,
|
duration_months: form.duration_months,
|
||||||
price: priceInCents,
|
cost_price: costPriceInCents,
|
||||||
description: form.description || undefined
|
enable_virtual_data: form.enable_virtual_data
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选字段
|
||||||
|
if (form.series_id) {
|
||||||
|
data.series_id = form.series_id
|
||||||
|
}
|
||||||
|
if (suggestedRetailPriceInCents !== undefined) {
|
||||||
|
data.suggested_retail_price = suggestedRetailPriceInCents
|
||||||
|
}
|
||||||
|
if (form.real_data_mb) {
|
||||||
|
data.real_data_mb = form.real_data_mb
|
||||||
|
}
|
||||||
|
if (form.enable_virtual_data && form.virtual_data_mb) {
|
||||||
|
data.virtual_data_mb = form.virtual_data_mb
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dialogType.value === 'add') {
|
if (dialogType.value === 'add') {
|
||||||
@@ -797,6 +817,11 @@
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleViewDetail = (row: PackageResponse) => {
|
||||||
|
router.push(`${RoutesAlias.PackageDetail}/${row.id}`)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
263
src/views/package-management/package-series/detail.vue
Normal file
263
src/views/package-management/package-series/detail.vue
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
<template>
|
||||||
|
<div class="package-series-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 { PackageSeriesService } from '@/api/modules'
|
||||||
|
import type { PackageSeriesResponse } from '@/types/api'
|
||||||
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
|
defineOptions({ name: 'PackageSeriesDetail' })
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const detailData = ref<PackageSeriesResponse | null>(null)
|
||||||
|
|
||||||
|
// 详情页配置
|
||||||
|
const detailSections: DetailSection[] = [
|
||||||
|
{
|
||||||
|
title: '基本信息',
|
||||||
|
fields: [
|
||||||
|
{ label: 'ID', prop: 'id' },
|
||||||
|
{ label: '系列编码', prop: 'series_code' },
|
||||||
|
{ label: '系列名称', prop: 'series_name' },
|
||||||
|
{
|
||||||
|
label: '状态',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return data.status === 1 ? '启用' : '禁用'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '描述',
|
||||||
|
prop: 'description',
|
||||||
|
fullWidth: true
|
||||||
|
},
|
||||||
|
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
||||||
|
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '一次性佣金配置',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '启用状态',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return data.one_time_commission_config?.enable ? '已启用' : '未启用'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '佣金类型',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.commission_type) return '-'
|
||||||
|
return config.commission_type === 'fixed' ? '固定佣金' : '梯度佣金'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '固定佣金金额',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (config?.commission_type !== 'fixed' || !config.commission_amount) return '-'
|
||||||
|
return `¥${(config.commission_amount / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '触发阈值',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.threshold) return '-'
|
||||||
|
return `¥${(config.threshold / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '触发类型',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.trigger_type) return '-'
|
||||||
|
return config.trigger_type === 'first_recharge' ? '首次充值' : '累计充值'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '梯度配置',
|
||||||
|
fullWidth: true,
|
||||||
|
render: (data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (config?.commission_type !== 'tiered' || !config.tiers || config.tiers.length === 0) {
|
||||||
|
return h('span', '-')
|
||||||
|
}
|
||||||
|
|
||||||
|
return h('div', { style: 'display: flex; flex-direction: column; gap: 8px;' },
|
||||||
|
config.tiers.map((tier: any, index: number) => {
|
||||||
|
const dimensionText = tier.dimension === 'sales_count' ? '销量' : '销售额'
|
||||||
|
const thresholdText = tier.dimension === 'sales_amount'
|
||||||
|
? `¥${(tier.threshold / 100).toFixed(2)}`
|
||||||
|
: tier.threshold
|
||||||
|
const amountText = `¥${(tier.amount / 100).toFixed(2)}`
|
||||||
|
const scopeText = tier.stat_scope === 'self' ? '仅自己' : '自己+下级'
|
||||||
|
|
||||||
|
return h(ElTag, { type: 'info', size: 'default' },
|
||||||
|
() => `档位${index + 1}: ${dimensionText} ≥ ${thresholdText}, 佣金 ${amountText}, ${scopeText}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '强充配置',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '启用状态',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return data.one_time_commission_config?.enable_force_recharge ? '已启用' : '未启用'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '强充金额',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.force_amount) return '-'
|
||||||
|
return `¥${(config.force_amount / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '强充计算类型',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.force_calc_type) return '-'
|
||||||
|
return config.force_calc_type === 'fixed' ? '固定' : '动态'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '时效配置',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '时效类型',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.validity_type) return '-'
|
||||||
|
const typeMap = {
|
||||||
|
permanent: '永久',
|
||||||
|
fixed_date: '固定日期',
|
||||||
|
relative: '相对时长'
|
||||||
|
}
|
||||||
|
return typeMap[config.validity_type] || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '时效值',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.validity_value) return '-'
|
||||||
|
if (config.validity_type === 'relative') {
|
||||||
|
return `${config.validity_value}个月`
|
||||||
|
} else if (config.validity_type === 'fixed_date') {
|
||||||
|
return config.validity_value
|
||||||
|
}
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
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 PackageSeriesService.getPackageSeriesDetail(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-series-detail {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
|
|
||||||
|
.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>
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
v-model:filter="searchForm"
|
v-model:filter="searchForm"
|
||||||
:items="searchFormItems"
|
:items="searchFormItems"
|
||||||
:show-expand="false"
|
:show-expand="false"
|
||||||
|
label-width="85"
|
||||||
@reset="handleReset"
|
@reset="handleReset"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
></ArtSearchBar>
|
></ArtSearchBar>
|
||||||
@@ -18,7 +19,9 @@
|
|||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
>
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<ElButton type="primary" @click="showDialog('add')" v-permission="'package_series:add'">新增套餐系列</ElButton>
|
<ElButton type="primary" @click="showDialog('add')" v-permission="'package_series:add'"
|
||||||
|
>新增套餐系列</ElButton
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
@@ -44,18 +47,18 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '新增套餐系列' : '编辑套餐系列'"
|
:title="dialogType === 'add' ? '新增套餐系列' : '编辑套餐系列'"
|
||||||
width="500px"
|
width="45%"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||||
<ElFormItem label="系列编码" prop="series_code">
|
<ElFormItem label="系列编码" prop="series_code">
|
||||||
<div style="display: flex; gap: 8px;">
|
<div style="display: flex; gap: 8px">
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="form.series_code"
|
v-model="form.series_code"
|
||||||
placeholder="请输入系列编码或点击生成"
|
placeholder="请输入系列编码或点击生成"
|
||||||
:disabled="dialogType === 'edit'"
|
:disabled="dialogType === 'edit'"
|
||||||
clearable
|
clearable
|
||||||
style="flex: 1;"
|
style="flex: 1"
|
||||||
/>
|
/>
|
||||||
<ElButton v-if="dialogType === 'add'" @click="handleGenerateSeriesCode">
|
<ElButton v-if="dialogType === 'add'" @click="handleGenerateSeriesCode">
|
||||||
生成编码
|
生成编码
|
||||||
@@ -75,6 +78,261 @@
|
|||||||
show-word-limit
|
show-word-limit
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 一次性佣金配置 -->
|
||||||
|
<div class="form-section-title">
|
||||||
|
<span class="title-text">一次性佣金配置</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ElFormItem label="启用一次性佣金">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="form.one_time_commission_config.enable"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="不启用"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<template v-if="form.one_time_commission_config.enable">
|
||||||
|
<!-- 佣金类型 -->
|
||||||
|
<ElFormItem label="佣金类型">
|
||||||
|
<ElRadioGroup v-model="form.one_time_commission_config.commission_type">
|
||||||
|
<ElRadio value="fixed">固定佣金</ElRadio>
|
||||||
|
<ElRadio value="tiered">梯度佣金</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 触发阈值 -->
|
||||||
|
<ElFormItem label="触发阈值">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.one_time_commission_config.threshold"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="触发阈值(元)"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 8px"
|
||||||
|
>单位:元</span
|
||||||
|
>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 触发类型 -->
|
||||||
|
<ElFormItem label="触发类型">
|
||||||
|
<ElRadioGroup v-model="form.one_time_commission_config.trigger_type">
|
||||||
|
<ElRadio value="first_recharge">首次充值</ElRadio>
|
||||||
|
<ElRadio value="accumulated_recharge">累计充值</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 固定佣金金额 -->
|
||||||
|
<ElFormItem
|
||||||
|
v-if="form.one_time_commission_config.commission_type === 'fixed'"
|
||||||
|
label="佣金金额"
|
||||||
|
>
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.one_time_commission_config.commission_amount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="佣金金额(元)"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 8px"
|
||||||
|
>单位:元</span
|
||||||
|
>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 梯度佣金配置 -->
|
||||||
|
<ElFormItem
|
||||||
|
v-if="form.one_time_commission_config.commission_type === 'tiered'"
|
||||||
|
label="梯度配置"
|
||||||
|
>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<div
|
||||||
|
v-for="(tier, index) in form.one_time_commission_config.tiers"
|
||||||
|
:key="index"
|
||||||
|
style="margin-bottom: 12px"
|
||||||
|
>
|
||||||
|
<ElCard shadow="hover">
|
||||||
|
<div style="display: flex; gap: 12px; align-items: flex-start">
|
||||||
|
<div style="flex: 1; display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<!-- 第一行:阈值和维度 -->
|
||||||
|
<div style="display: flex; gap: 12px">
|
||||||
|
<div style="flex: 1">
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
"
|
||||||
|
>
|
||||||
|
达标阈值{{ tier.dimension === 'sales_amount' ? '(元)' : '' }}
|
||||||
|
</div>
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="tier.threshold"
|
||||||
|
:min="0"
|
||||||
|
:precision="tier.dimension === 'sales_amount' ? 2 : 0"
|
||||||
|
:placeholder="
|
||||||
|
tier.dimension === 'sales_amount'
|
||||||
|
? '达标阈值(元)'
|
||||||
|
: '达标阈值(数量)'
|
||||||
|
"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
"
|
||||||
|
>统计维度</div
|
||||||
|
>
|
||||||
|
<ElSelect
|
||||||
|
v-model="tier.dimension"
|
||||||
|
placeholder="统计维度"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<ElOption label="销量" value="sales_count" />
|
||||||
|
<ElOption label="销售额" value="sales_amount" />
|
||||||
|
</ElSelect>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 第二行:佣金金额和统计范围 -->
|
||||||
|
<div style="display: flex; gap: 12px">
|
||||||
|
<div style="flex: 1">
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
"
|
||||||
|
>佣金金额(元)</div
|
||||||
|
>
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="tier.amount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="佣金金额"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
"
|
||||||
|
>统计范围</div
|
||||||
|
>
|
||||||
|
<ElSelect
|
||||||
|
v-model="tier.stat_scope"
|
||||||
|
placeholder="统计范围"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<ElOption label="仅自己" value="self" />
|
||||||
|
<ElOption label="自己+下级" value="self_and_sub" />
|
||||||
|
</ElSelect>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ElButton
|
||||||
|
type="danger"
|
||||||
|
:icon="Delete"
|
||||||
|
circle
|
||||||
|
@click="removeTier(index)"
|
||||||
|
style="flex-shrink: 0; margin-top: 28px; width: 32px; height: 32px; padding: 0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
<ElButton type="primary" :icon="Plus" @click="addTier" style="width: 100%">
|
||||||
|
添加梯度
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 强充配置 -->
|
||||||
|
<div class="form-section-title">
|
||||||
|
<span class="title-text">强充配置</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ElFormItem label="启用强充">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="form.one_time_commission_config.enable_force_recharge"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="不启用"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<template v-if="form.one_time_commission_config.enable_force_recharge">
|
||||||
|
<ElFormItem label="强充金额">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.one_time_commission_config.force_amount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="强充金额(元)"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 8px"
|
||||||
|
>单位:元</span
|
||||||
|
>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<ElFormItem label="强充计算类型">
|
||||||
|
<ElRadioGroup v-model="form.one_time_commission_config.force_calc_type">
|
||||||
|
<ElRadio value="fixed">固定</ElRadio>
|
||||||
|
<ElRadio value="dynamic">动态</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 时效配置 -->
|
||||||
|
<div class="form-section-title">
|
||||||
|
<span class="title-text">时效配置</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ElFormItem label="时效类型">
|
||||||
|
<ElRadioGroup v-model="form.one_time_commission_config.validity_type">
|
||||||
|
<ElRadio value="permanent">永久</ElRadio>
|
||||||
|
<ElRadio value="fixed_date">固定日期</ElRadio>
|
||||||
|
<ElRadio value="relative">相对时长</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<ElFormItem
|
||||||
|
v-if="form.one_time_commission_config.validity_type === 'fixed_date'"
|
||||||
|
label="有效期至"
|
||||||
|
>
|
||||||
|
<ElDatePicker
|
||||||
|
v-model="form.one_time_commission_config.validity_value"
|
||||||
|
type="date"
|
||||||
|
placeholder="选择日期"
|
||||||
|
style="width: 100%"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<ElFormItem
|
||||||
|
v-if="form.one_time_commission_config.validity_type === 'relative'"
|
||||||
|
label="有效月数"
|
||||||
|
>
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.one_time_commission_config.validity_value"
|
||||||
|
:min="1"
|
||||||
|
:precision="0"
|
||||||
|
placeholder="有效月数"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 8px"
|
||||||
|
>单位:月</span
|
||||||
|
>
|
||||||
|
</ElFormItem>
|
||||||
|
</template>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
@@ -94,6 +352,7 @@
|
|||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { PackageSeriesService } from '@/api/modules'
|
import { PackageSeriesService } from '@/api/modules'
|
||||||
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElButton } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElButton } from 'element-plus'
|
||||||
|
import { Plus, Delete } from '@element-plus/icons-vue'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import type { PackageSeriesResponse } from '@/types/api'
|
import type { PackageSeriesResponse } from '@/types/api'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
@@ -108,10 +367,12 @@
|
|||||||
apiStatusToFrontend
|
apiStatusToFrontend
|
||||||
} from '@/config/constants'
|
} from '@/config/constants'
|
||||||
import { generateSeriesCode } from '@/utils/codeGenerator'
|
import { generateSeriesCode } from '@/utils/codeGenerator'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
defineOptions({ name: 'PackageSeries' })
|
defineOptions({ name: 'PackageSeries' })
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -122,7 +383,8 @@
|
|||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
series_name: '',
|
series_name: '',
|
||||||
status: undefined as number | undefined
|
status: undefined as number | undefined,
|
||||||
|
enable_one_time_commission: undefined as boolean | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
@@ -151,6 +413,19 @@
|
|||||||
{ label: '启用', value: 1 },
|
{ label: '启用', value: 1 },
|
||||||
{ label: '禁用', value: 2 }
|
{ label: '禁用', value: 2 }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '一次性佣金',
|
||||||
|
prop: 'enable_one_time_commission',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请选择'
|
||||||
|
},
|
||||||
|
options: () => [
|
||||||
|
{ label: '已启用', value: true },
|
||||||
|
{ label: '未启用', value: false }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -163,13 +438,19 @@
|
|||||||
|
|
||||||
// 列配置
|
// 列配置
|
||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: 'ID', prop: 'id' },
|
|
||||||
{ label: '系列编码', prop: 'series_code' },
|
{ label: '系列编码', prop: 'series_code' },
|
||||||
{ label: '系列名称', prop: 'series_name' },
|
{ label: '系列名称', prop: 'series_name' },
|
||||||
{ label: '描述', prop: 'description' },
|
{ label: '描述', prop: 'description' },
|
||||||
|
{ label: '一次性佣金', prop: 'enable_one_time_commission' },
|
||||||
|
{ label: '佣金类型', prop: 'commission_type' },
|
||||||
|
{ label: '触发阈值', prop: 'commission_threshold' },
|
||||||
|
{ label: '触发类型', prop: 'trigger_type' },
|
||||||
|
{ label: '强充状态', prop: 'enable_force_recharge' },
|
||||||
|
{ label: '强充金额', prop: 'force_amount' },
|
||||||
|
{ label: '强充计算类型', prop: 'force_calc_type' },
|
||||||
|
{ label: '时效类型', prop: 'validity_type' },
|
||||||
{ label: '状态', prop: 'status' },
|
{ label: '状态', prop: 'status' },
|
||||||
{ label: '创建时间', prop: 'created_at' },
|
{ label: '创建时间', prop: 'created_at' },
|
||||||
{ label: '更新时间', prop: 'updated_at' },
|
|
||||||
{ label: '操作', prop: 'operation' }
|
{ label: '操作', prop: 'operation' }
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -191,7 +472,20 @@
|
|||||||
id: 0,
|
id: 0,
|
||||||
series_code: '',
|
series_code: '',
|
||||||
series_name: '',
|
series_name: '',
|
||||||
description: ''
|
description: '',
|
||||||
|
one_time_commission_config: {
|
||||||
|
enable: false,
|
||||||
|
commission_type: 'fixed',
|
||||||
|
commission_amount: undefined,
|
||||||
|
threshold: undefined,
|
||||||
|
trigger_type: 'first_recharge',
|
||||||
|
tiers: [],
|
||||||
|
enable_force_recharge: false,
|
||||||
|
force_amount: undefined,
|
||||||
|
force_calc_type: 'fixed',
|
||||||
|
validity_type: 'permanent',
|
||||||
|
validity_value: undefined
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const seriesList = ref<PackageSeriesResponse[]>([])
|
const seriesList = ref<PackageSeriesResponse[]>([])
|
||||||
@@ -199,25 +493,151 @@
|
|||||||
|
|
||||||
// 动态列配置
|
// 动态列配置
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
{
|
|
||||||
prop: 'id',
|
|
||||||
label: 'ID',
|
|
||||||
width: 80
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
prop: 'series_code',
|
prop: 'series_code',
|
||||||
label: '系列编码',
|
label: '系列编码',
|
||||||
minWidth: 150
|
width: 200,
|
||||||
|
showOverflowTooltip: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'series_name',
|
prop: 'series_name',
|
||||||
label: '系列名称',
|
label: '系列名称',
|
||||||
minWidth: 150
|
minWidth: 150
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
prop: 'description',
|
prop: 'enable_one_time_commission',
|
||||||
label: '描述',
|
label: '一次性佣金',
|
||||||
minWidth: 200
|
width: 110,
|
||||||
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
|
if (!row.one_time_commission_config) {
|
||||||
|
return h(ElTag, { type: 'info', size: 'small' }, () => '未配置')
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
ElTag,
|
||||||
|
{
|
||||||
|
type: row.one_time_commission_config.enable ? 'success' : 'info',
|
||||||
|
size: 'small'
|
||||||
|
},
|
||||||
|
() => (row.one_time_commission_config.enable ? '已启用' : '未启用')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'commission_type',
|
||||||
|
label: '佣金类型',
|
||||||
|
width: 100,
|
||||||
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
|
if (!row.one_time_commission_config?.commission_type) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
const typeMap = {
|
||||||
|
fixed: '固定',
|
||||||
|
tiered: '梯度'
|
||||||
|
}
|
||||||
|
return typeMap[row.one_time_commission_config.commission_type] || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'commission_threshold',
|
||||||
|
label: '触发阈值',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
|
if (!row.one_time_commission_config?.threshold) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
return `¥${(row.one_time_commission_config.threshold / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'trigger_type',
|
||||||
|
label: '触发类型',
|
||||||
|
width: 110,
|
||||||
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
|
if (!row.one_time_commission_config?.trigger_type) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
const typeMap = {
|
||||||
|
first_recharge: '首次充值',
|
||||||
|
accumulated_recharge: '累计充值'
|
||||||
|
}
|
||||||
|
return typeMap[row.one_time_commission_config.trigger_type] || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'enable_force_recharge',
|
||||||
|
label: '强充状态',
|
||||||
|
width: 100,
|
||||||
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
|
if (!row.one_time_commission_config) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
ElTag,
|
||||||
|
{
|
||||||
|
type: row.one_time_commission_config.enable_force_recharge ? 'warning' : 'info',
|
||||||
|
size: 'small'
|
||||||
|
},
|
||||||
|
() => (row.one_time_commission_config.enable_force_recharge ? '已启用' : '未启用')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'force_amount',
|
||||||
|
label: '强充金额',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
|
if (!row.one_time_commission_config?.force_amount) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
'span',
|
||||||
|
{ style: 'color: #f56c6c; font-weight: bold;' },
|
||||||
|
`¥${(row.one_time_commission_config.force_amount / 100).toFixed(2)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'force_calc_type',
|
||||||
|
label: '强充计算类型',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
|
if (!row.one_time_commission_config?.force_calc_type) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
const typeMap = {
|
||||||
|
fixed: '固定',
|
||||||
|
dynamic: '动态'
|
||||||
|
}
|
||||||
|
return typeMap[row.one_time_commission_config.force_calc_type] || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'validity_type',
|
||||||
|
label: '时效类型',
|
||||||
|
width: 180,
|
||||||
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
|
if (!row.one_time_commission_config?.validity_type) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
const typeMap = {
|
||||||
|
permanent: '永久',
|
||||||
|
fixed_date: '固定日期',
|
||||||
|
relative: '相对时长'
|
||||||
|
}
|
||||||
|
const validityType = typeMap[row.one_time_commission_config.validity_type] || '-'
|
||||||
|
|
||||||
|
// 如果有时效值,显示详情
|
||||||
|
if (row.one_time_commission_config.validity_value) {
|
||||||
|
if (row.one_time_commission_config.validity_type === 'relative') {
|
||||||
|
return `${validityType}(${row.one_time_commission_config.validity_value}月)`
|
||||||
|
} else if (row.one_time_commission_config.validity_type === 'fixed_date') {
|
||||||
|
return `${validityType}(${row.one_time_commission_config.validity_value})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validityType
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'status',
|
prop: 'status',
|
||||||
@@ -238,26 +658,34 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'description',
|
||||||
|
label: '描述',
|
||||||
|
minWidth: 200,
|
||||||
|
showOverflowTooltip: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'created_at',
|
prop: 'created_at',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: PackageSeriesResponse) => formatDateTime(row.created_at)
|
formatter: (row: PackageSeriesResponse) => formatDateTime(row.created_at)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
prop: 'updated_at',
|
|
||||||
label: '更新时间',
|
|
||||||
width: 180,
|
|
||||||
formatter: (row: PackageSeriesResponse) => formatDateTime(row.updated_at)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 150,
|
width: 200,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: PackageSeriesResponse) => {
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
|
|
||||||
|
// 详情按钮
|
||||||
|
buttons.push(
|
||||||
|
h(ArtButtonTable, {
|
||||||
|
type: 'view',
|
||||||
|
onClick: () => handleViewDetail(row)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
if (hasAuth('package_series:edit')) {
|
if (hasAuth('package_series:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
@@ -293,7 +721,8 @@
|
|||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
page_size: pagination.page_size,
|
page_size: pagination.page_size,
|
||||||
series_name: searchForm.series_name || undefined,
|
series_name: searchForm.series_name || undefined,
|
||||||
status: searchForm.status || undefined
|
status: searchForm.status || undefined,
|
||||||
|
enable_one_time_commission: searchForm.enable_one_time_commission ?? undefined
|
||||||
}
|
}
|
||||||
const res = await PackageSeriesService.getPackageSeries(params)
|
const res = await PackageSeriesService.getPackageSeries(params)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -347,11 +776,80 @@
|
|||||||
form.series_code = row.series_code
|
form.series_code = row.series_code
|
||||||
form.series_name = row.series_name
|
form.series_name = row.series_name
|
||||||
form.description = row.description || ''
|
form.description = row.description || ''
|
||||||
|
|
||||||
|
// 填充佣金配置
|
||||||
|
if (row.one_time_commission_config) {
|
||||||
|
// 转换梯度配置:分 -> 元
|
||||||
|
const convertedTiers = (row.one_time_commission_config.tiers || []).map((tier: any) => ({
|
||||||
|
...tier,
|
||||||
|
// 只有销售额维度的阈值需要转换
|
||||||
|
threshold:
|
||||||
|
tier.dimension === 'sales_amount' && tier.threshold != null
|
||||||
|
? tier.threshold / 100
|
||||||
|
: tier.threshold,
|
||||||
|
// 佣金金额转换
|
||||||
|
amount: tier.amount != null ? tier.amount / 100 : tier.amount
|
||||||
|
}))
|
||||||
|
|
||||||
|
form.one_time_commission_config = {
|
||||||
|
enable: row.one_time_commission_config.enable || false,
|
||||||
|
commission_type: row.one_time_commission_config.commission_type || 'fixed',
|
||||||
|
// 固定佣金金额:分 -> 元
|
||||||
|
commission_amount:
|
||||||
|
row.one_time_commission_config.commission_amount != null
|
||||||
|
? row.one_time_commission_config.commission_amount / 100
|
||||||
|
: undefined,
|
||||||
|
// 触发阈值:分 -> 元
|
||||||
|
threshold:
|
||||||
|
row.one_time_commission_config.threshold != null
|
||||||
|
? row.one_time_commission_config.threshold / 100
|
||||||
|
: undefined,
|
||||||
|
trigger_type: row.one_time_commission_config.trigger_type || 'first_recharge',
|
||||||
|
tiers: convertedTiers,
|
||||||
|
enable_force_recharge: row.one_time_commission_config.enable_force_recharge || false,
|
||||||
|
// 强充金额:分 -> 元
|
||||||
|
force_amount:
|
||||||
|
row.one_time_commission_config.force_amount != null
|
||||||
|
? row.one_time_commission_config.force_amount / 100
|
||||||
|
: undefined,
|
||||||
|
force_calc_type: row.one_time_commission_config.force_calc_type || 'fixed',
|
||||||
|
validity_type: row.one_time_commission_config.validity_type || 'permanent',
|
||||||
|
validity_value: row.one_time_commission_config.validity_value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 重置为默认值
|
||||||
|
form.one_time_commission_config = {
|
||||||
|
enable: false,
|
||||||
|
commission_type: 'fixed',
|
||||||
|
commission_amount: undefined,
|
||||||
|
threshold: undefined,
|
||||||
|
trigger_type: 'first_recharge',
|
||||||
|
tiers: [],
|
||||||
|
enable_force_recharge: false,
|
||||||
|
force_amount: undefined,
|
||||||
|
force_calc_type: 'fixed',
|
||||||
|
validity_type: 'permanent',
|
||||||
|
validity_value: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
form.id = 0
|
form.id = 0
|
||||||
form.series_code = ''
|
form.series_code = ''
|
||||||
form.series_name = ''
|
form.series_name = ''
|
||||||
form.description = ''
|
form.description = ''
|
||||||
|
form.one_time_commission_config = {
|
||||||
|
enable: false,
|
||||||
|
commission_type: 'fixed',
|
||||||
|
commission_amount: undefined,
|
||||||
|
threshold: undefined,
|
||||||
|
trigger_type: 'first_recharge',
|
||||||
|
tiers: [],
|
||||||
|
enable_force_recharge: false,
|
||||||
|
force_amount: undefined,
|
||||||
|
force_calc_type: 'fixed',
|
||||||
|
validity_type: 'permanent',
|
||||||
|
validity_value: undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置表单验证状态
|
// 重置表单验证状态
|
||||||
@@ -363,6 +861,10 @@
|
|||||||
// 生成系列编码
|
// 生成系列编码
|
||||||
const handleGenerateSeriesCode = () => {
|
const handleGenerateSeriesCode = () => {
|
||||||
form.series_code = generateSeriesCode()
|
form.series_code = generateSeriesCode()
|
||||||
|
// 清除该字段的验证错误提示
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearValidate('series_code')
|
||||||
|
})
|
||||||
ElMessage.success('编码生成成功')
|
ElMessage.success('编码生成成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,12 +897,84 @@
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
submitLoading.value = true
|
submitLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = {
|
// 构建请求数据
|
||||||
series_code: form.series_code,
|
const data: any = {
|
||||||
series_name: form.series_name,
|
series_name: form.series_name,
|
||||||
description: form.description || undefined
|
description: form.description || undefined,
|
||||||
|
enable_one_time_commission: form.one_time_commission_config.enable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增模式下才包含 series_code
|
||||||
|
if (dialogType.value === 'add') {
|
||||||
|
data.series_code = form.series_code
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建佣金配置对象
|
||||||
|
const commissionConfig: any = {
|
||||||
|
enable: form.one_time_commission_config.enable
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有启用时才添加其他配置
|
||||||
|
if (form.one_time_commission_config.enable) {
|
||||||
|
commissionConfig.commission_type = form.one_time_commission_config.commission_type
|
||||||
|
// 触发阈值:元 -> 分
|
||||||
|
commissionConfig.threshold =
|
||||||
|
form.one_time_commission_config.threshold != null
|
||||||
|
? Math.round(form.one_time_commission_config.threshold * 100)
|
||||||
|
: undefined
|
||||||
|
commissionConfig.trigger_type = form.one_time_commission_config.trigger_type
|
||||||
|
|
||||||
|
// 添加固定佣金金额:元 -> 分
|
||||||
|
if (form.one_time_commission_config.commission_type === 'fixed') {
|
||||||
|
commissionConfig.commission_amount =
|
||||||
|
form.one_time_commission_config.commission_amount != null
|
||||||
|
? Math.round(form.one_time_commission_config.commission_amount * 100)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加梯度配置:元 -> 分
|
||||||
|
if (form.one_time_commission_config.commission_type === 'tiered') {
|
||||||
|
const convertedTiers = form.one_time_commission_config.tiers.map((tier: any) => ({
|
||||||
|
...tier,
|
||||||
|
// 只有销售额维度的阈值需要转换
|
||||||
|
threshold:
|
||||||
|
tier.dimension === 'sales_amount' && tier.threshold != null
|
||||||
|
? Math.round(tier.threshold * 100)
|
||||||
|
: tier.threshold,
|
||||||
|
// 佣金金额转换:元 -> 分
|
||||||
|
amount: tier.amount != null ? Math.round(tier.amount * 100) : tier.amount
|
||||||
|
}))
|
||||||
|
|
||||||
|
commissionConfig.tiers = convertedTiers.length > 0 ? convertedTiers : null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加强充配置:元 -> 分
|
||||||
|
if (form.one_time_commission_config.enable_force_recharge) {
|
||||||
|
commissionConfig.enable_force_recharge = true
|
||||||
|
commissionConfig.force_amount =
|
||||||
|
form.one_time_commission_config.force_amount != null
|
||||||
|
? Math.round(form.one_time_commission_config.force_amount * 100)
|
||||||
|
: undefined
|
||||||
|
commissionConfig.force_calc_type = form.one_time_commission_config.force_calc_type
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加时效配置
|
||||||
|
commissionConfig.validity_type = form.one_time_commission_config.validity_type
|
||||||
|
if (form.one_time_commission_config.validity_type !== 'permanent') {
|
||||||
|
// relative 类型的 validity_value 需要转换为字符串
|
||||||
|
if (form.one_time_commission_config.validity_type === 'relative') {
|
||||||
|
commissionConfig.validity_value = String(
|
||||||
|
form.one_time_commission_config.validity_value
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
commissionConfig.validity_value = form.one_time_commission_config.validity_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加佣金配置到请求数据
|
||||||
|
data.one_time_commission_config = commissionConfig
|
||||||
|
|
||||||
if (dialogType.value === 'add') {
|
if (dialogType.value === 'add') {
|
||||||
await PackageSeriesService.createPackageSeries(data)
|
await PackageSeriesService.createPackageSeries(data)
|
||||||
ElMessage.success('新增成功')
|
ElMessage.success('新增成功')
|
||||||
@@ -436,6 +1010,26 @@
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加梯度
|
||||||
|
const addTier = () => {
|
||||||
|
form.one_time_commission_config.tiers.push({
|
||||||
|
threshold: undefined,
|
||||||
|
dimension: 'sales_count',
|
||||||
|
amount: undefined,
|
||||||
|
stat_scope: 'self'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除梯度
|
||||||
|
const removeTier = (index: number) => {
|
||||||
|
form.one_time_commission_config.tiers.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleViewDetail = (row: PackageSeriesResponse) => {
|
||||||
|
router.push(`/package-management/package-series/detail/${row.id}`)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
207
src/views/package-management/series-assign/detail.vue
Normal file
207
src/views/package-management/series-assign/detail.vue
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
<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;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
|
|
||||||
|
.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>
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
<ArtSearchBar
|
<ArtSearchBar
|
||||||
v-model:filter="searchForm"
|
v-model:filter="searchForm"
|
||||||
:items="searchFormItems"
|
:items="searchFormItems"
|
||||||
:show-expand="false"
|
:show-expand="true"
|
||||||
|
label-width="85"
|
||||||
@reset="handleReset"
|
@reset="handleReset"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
></ArtSearchBar>
|
></ArtSearchBar>
|
||||||
@@ -44,11 +45,11 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '新增系列分配' : '编辑系列分配'"
|
:title="dialogType === 'add' ? '新增系列分配' : '编辑系列分配'"
|
||||||
width="650px"
|
width="35%"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="160px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="130px">
|
||||||
<!-- 基本信息 -->
|
<!-- 基本信息 -->
|
||||||
<ElFormItem label="选择套餐系列" prop="series_id" v-if="dialogType === 'add'">
|
<ElFormItem label="选择套餐系列" prop="series_id" v-if="dialogType === 'add'">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
@@ -84,37 +85,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ElFormItem label="选择店铺" prop="shop_id" v-if="dialogType === 'add'">
|
<ElFormItem label="选择店铺" prop="shop_id" v-if="dialogType === 'add'">
|
||||||
<ElSelect
|
<ElTreeSelect
|
||||||
v-model="form.shop_id"
|
v-model="form.shop_id"
|
||||||
|
:data="shopTreeData"
|
||||||
|
:props="{ label: 'shop_name', value: 'id', children: 'children' }"
|
||||||
placeholder="请选择店铺"
|
placeholder="请选择店铺"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
filterable
|
filterable
|
||||||
remote
|
|
||||||
:remote-method="searchShop"
|
|
||||||
:loading="shopLoading"
|
|
||||||
clearable
|
clearable
|
||||||
>
|
:loading="shopLoading"
|
||||||
<ElOption
|
check-strictly
|
||||||
v-for="shop in shopOptions"
|
:render-after-expand="false"
|
||||||
:key="shop.id"
|
/>
|
||||||
:label="shop.shop_name"
|
|
||||||
:value="shop.id"
|
|
||||||
/>
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
<!-- 一次性佣金配置 -->
|
<!-- 一次性佣金配置 -->
|
||||||
<ElDivider content-position="left">一次性佣金配置</ElDivider>
|
<div class="form-section-title">
|
||||||
|
<span class="title-text">一次性佣金配置</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ElFormItem label="佣金金额上限(分)" prop="one_time_commission_amount">
|
<ElFormItem label="佣金金额上限(元)" prop="one_time_commission_amount">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.one_time_commission_amount"
|
v-model="form.one_time_commission_amount"
|
||||||
:min="0"
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入该代理能拿的一次性佣金金额上限(分)"
|
placeholder="请输入该代理能拿的一次性佣金金额上限(元)"
|
||||||
/>
|
/>
|
||||||
<div class="form-tip">该代理在此系列分配下能获得的一次性佣金金额上限(单位:分)</div>
|
<div class="form-tip">该代理在此系列分配下能获得的一次性佣金金额上限(单位:元)</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
<ElFormItem label="启用一次性佣金">
|
<ElFormItem label="启用一次性佣金">
|
||||||
@@ -122,13 +122,15 @@
|
|||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
<template v-if="form.enable_one_time_commission">
|
<template v-if="form.enable_one_time_commission">
|
||||||
<ElFormItem label="触发阈值(分)" prop="one_time_commission_threshold">
|
<ElFormItem label="触发阈值(元)" prop="one_time_commission_threshold">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.one_time_commission_threshold"
|
v-model="form.one_time_commission_threshold"
|
||||||
:min="0"
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入触发阈值(分)"
|
placeholder="请输入触发阈值(元)"
|
||||||
/>
|
/>
|
||||||
<div class="form-tip">达到此充值金额后触发一次性佣金</div>
|
<div class="form-tip">达到此充值金额后触发一次性佣金</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -142,20 +144,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 强制充值配置 -->
|
<!-- 强制充值配置 -->
|
||||||
<ElDivider content-position="left">强制充值配置(可选)</ElDivider>
|
<div class="form-section-title">
|
||||||
|
<span class="title-text">强制充值配置(可选)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ElFormItem label="启用强制充值">
|
<ElFormItem label="启用强制充值">
|
||||||
<ElSwitch v-model="form.enable_force_recharge" />
|
<ElSwitch v-model="form.enable_force_recharge" />
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
<template v-if="form.enable_force_recharge">
|
<template v-if="form.enable_force_recharge">
|
||||||
<ElFormItem label="强充金额(分)" prop="force_recharge_amount">
|
<ElFormItem label="强充金额(元)" prop="force_recharge_amount">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.force_recharge_amount"
|
v-model="form.force_recharge_amount"
|
||||||
:min="0"
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入强制充值金额(分)"
|
placeholder="请输入强制充值金额(元)"
|
||||||
/>
|
/>
|
||||||
<div class="form-tip">用户需要达到的强制充值金额</div>
|
<div class="form-tip">用户需要达到的强制充值金额</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -184,6 +190,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { ShopSeriesAllocationService, PackageSeriesService, ShopService } from '@/api/modules'
|
import { ShopSeriesAllocationService, PackageSeriesService, ShopService } from '@/api/modules'
|
||||||
import { ElMessage, ElMessageBox, ElSwitch, ElTag } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElSwitch, ElTag } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
@@ -207,6 +214,7 @@
|
|||||||
defineOptions({ name: 'SeriesAssign' })
|
defineOptions({ name: 'SeriesAssign' })
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -217,13 +225,16 @@
|
|||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const seriesOptions = ref<PackageSeriesResponse[]>([])
|
const seriesOptions = ref<PackageSeriesResponse[]>([])
|
||||||
const shopOptions = ref<ShopResponse[]>([])
|
const shopOptions = ref<ShopResponse[]>([])
|
||||||
|
const shopTreeData = ref<ShopResponse[]>([])
|
||||||
const searchSeriesOptions = ref<PackageSeriesResponse[]>([])
|
const searchSeriesOptions = ref<PackageSeriesResponse[]>([])
|
||||||
const searchShopOptions = ref<ShopResponse[]>([])
|
const searchShopOptions = ref<ShopResponse[]>([])
|
||||||
|
const searchAllocatorShopOptions = ref<ShopResponse[]>([])
|
||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
shop_id: undefined as number | undefined,
|
shop_id: undefined as number | undefined,
|
||||||
series_id: undefined as number | undefined,
|
series_id: undefined as number | undefined,
|
||||||
|
allocator_shop_id: undefined as number | undefined,
|
||||||
status: undefined as number | undefined
|
status: undefined as number | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +244,7 @@
|
|||||||
// 搜索表单配置
|
// 搜索表单配置
|
||||||
const searchFormItems = computed<SearchFormItem[]>(() => [
|
const searchFormItems = computed<SearchFormItem[]>(() => [
|
||||||
{
|
{
|
||||||
label: '店铺',
|
label: '被分配店铺',
|
||||||
prop: 'shop_id',
|
prop: 'shop_id',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
config: {
|
config: {
|
||||||
@@ -242,7 +253,7 @@
|
|||||||
remote: true,
|
remote: true,
|
||||||
remoteMethod: handleSearchShop,
|
remoteMethod: handleSearchShop,
|
||||||
loading: shopLoading.value,
|
loading: shopLoading.value,
|
||||||
placeholder: '请选择或搜索店铺'
|
placeholder: '请选择或搜索被分配的店铺'
|
||||||
},
|
},
|
||||||
options: () =>
|
options: () =>
|
||||||
searchShopOptions.value.map((s) => ({
|
searchShopOptions.value.map((s) => ({
|
||||||
@@ -250,6 +261,28 @@
|
|||||||
value: s.id
|
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: '套餐系列',
|
label: '套餐系列',
|
||||||
prop: 'series_id',
|
prop: 'series_id',
|
||||||
@@ -268,6 +301,7 @@
|
|||||||
value: s.id
|
value: s.id
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: '状态',
|
label: '状态',
|
||||||
prop: 'status',
|
prop: 'status',
|
||||||
@@ -292,13 +326,17 @@
|
|||||||
|
|
||||||
// 列配置
|
// 列配置
|
||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: 'ID', prop: 'id' },
|
{ label: '系列编码', prop: 'series_code' },
|
||||||
{ label: '系列名称', prop: 'series_name' },
|
{ label: '系列名称', prop: 'series_name' },
|
||||||
{ label: '店铺名称', prop: 'shop_name' },
|
{ label: '店铺名称', prop: 'shop_name' },
|
||||||
{ label: '分配者店铺', prop: 'allocator_shop_name' },
|
{ label: '分配者店铺', prop: 'allocator_shop_name' },
|
||||||
{ label: '一次性佣金金额', prop: 'one_time_commission_amount' },
|
{ label: '一次性佣金金额', prop: 'one_time_commission_amount' },
|
||||||
{ label: '一次性佣金状态', prop: 'enable_one_time_commission' },
|
{ label: '一次性佣金状态', prop: 'enable_one_time_commission' },
|
||||||
|
{ label: '触发类型', prop: 'one_time_commission_trigger' },
|
||||||
|
{ label: '触发阈值', prop: 'one_time_commission_threshold' },
|
||||||
{ label: '强制充值', prop: 'enable_force_recharge' },
|
{ label: '强制充值', prop: 'enable_force_recharge' },
|
||||||
|
{ label: '强充金额', prop: 'force_recharge_amount' },
|
||||||
|
{ label: '强充触发类型', prop: 'force_recharge_trigger_type' },
|
||||||
{ label: '状态', prop: 'status' },
|
{ label: '状态', prop: 'status' },
|
||||||
{ label: '创建时间', prop: 'created_at' },
|
{ label: '创建时间', prop: 'created_at' },
|
||||||
{ label: '操作', prop: 'operation' }
|
{ label: '操作', prop: 'operation' }
|
||||||
@@ -372,9 +410,10 @@
|
|||||||
// 动态列配置
|
// 动态列配置
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
{
|
{
|
||||||
prop: 'id',
|
prop: 'series_code',
|
||||||
label: 'ID',
|
label: '系列编码',
|
||||||
width: 80
|
width: 200,
|
||||||
|
showOverflowTooltip: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'series_name',
|
prop: 'series_name',
|
||||||
@@ -384,20 +423,28 @@
|
|||||||
{
|
{
|
||||||
prop: 'shop_name',
|
prop: 'shop_name',
|
||||||
label: '店铺名称',
|
label: '店铺名称',
|
||||||
minWidth: 180
|
minWidth: 140
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'allocator_shop_name',
|
prop: 'allocator_shop_name',
|
||||||
label: '分配者店铺',
|
label: '分配者店铺',
|
||||||
minWidth: 150,
|
minWidth: 120,
|
||||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||||
|
// 如果是平台分配(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 || '-'
|
return row.allocator_shop_name || '-'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'one_time_commission_amount',
|
prop: 'one_time_commission_amount',
|
||||||
label: '一次性佣金金额',
|
label: '一次性佣金金额',
|
||||||
width: 150,
|
width: 140,
|
||||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||||
return h(
|
return h(
|
||||||
'span',
|
'span',
|
||||||
@@ -418,6 +465,36 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'one_time_commission_trigger',
|
||||||
|
label: '触发类型',
|
||||||
|
width: 110,
|
||||||
|
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||||
|
if (!row.one_time_commission_trigger) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
const typeMap = {
|
||||||
|
first_recharge: '首次充值',
|
||||||
|
accumulated_recharge: '累计充值'
|
||||||
|
}
|
||||||
|
return typeMap[row.one_time_commission_trigger] || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'one_time_commission_threshold',
|
||||||
|
label: '触发阈值',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||||
|
if (!row.one_time_commission_threshold) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
'span',
|
||||||
|
{ style: 'color: #e6a23c' },
|
||||||
|
`¥${(row.one_time_commission_threshold / 100).toFixed(2)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'enable_force_recharge',
|
prop: 'enable_force_recharge',
|
||||||
label: '强制充值',
|
label: '强制充值',
|
||||||
@@ -430,6 +507,36 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'force_recharge_amount',
|
||||||
|
label: '强充金额',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||||
|
if (!row.force_recharge_amount) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
'span',
|
||||||
|
{ style: 'color: #f56c6c; font-weight: bold' },
|
||||||
|
`¥${(row.force_recharge_amount / 100).toFixed(2)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'force_recharge_trigger_type',
|
||||||
|
label: '强充触发类型',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||||
|
if (!row.force_recharge_trigger_type) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
const typeMap = {
|
||||||
|
1: '单次充值',
|
||||||
|
2: '累计充值'
|
||||||
|
}
|
||||||
|
return typeMap[row.force_recharge_trigger_type] || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'status',
|
prop: 'status',
|
||||||
label: '状态',
|
label: '状态',
|
||||||
@@ -458,11 +565,19 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 150,
|
width: 180,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
|
|
||||||
|
// 详情按钮
|
||||||
|
buttons.push(
|
||||||
|
h(ArtButtonTable, {
|
||||||
|
type: 'view',
|
||||||
|
onClick: () => handleViewDetail(row)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
if (hasAuth('series_assign:edit')) {
|
if (hasAuth('series_assign:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
@@ -486,11 +601,39 @@
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// 构建树形结构数据
|
||||||
|
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(() => {
|
onMounted(() => {
|
||||||
loadSeriesOptions()
|
loadSeriesOptions()
|
||||||
loadShopOptions()
|
loadShopOptions()
|
||||||
loadSearchSeriesOptions()
|
loadSearchSeriesOptions()
|
||||||
loadSearchShopOptions()
|
loadSearchShopOptions()
|
||||||
|
loadSearchAllocatorShopOptions()
|
||||||
getTableData()
|
getTableData()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -517,20 +660,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载店铺选项(用于新增对话框,默认加载10条)
|
// 加载店铺选项(用于新增对话框,加载所有店铺并构建树形结构)
|
||||||
const loadShopOptions = async (shopName?: string) => {
|
const loadShopOptions = async () => {
|
||||||
shopLoading.value = true
|
shopLoading.value = true
|
||||||
try {
|
try {
|
||||||
const params: any = {
|
// 加载所有店铺,不分页
|
||||||
|
const res = await ShopService.getShops({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 10
|
page_size: 10000 // 使用较大的值获取所有店铺
|
||||||
}
|
})
|
||||||
if (shopName) {
|
|
||||||
params.shop_name = shopName
|
|
||||||
}
|
|
||||||
const res = await ShopService.getShops(params)
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
shopOptions.value = res.data.items || []
|
shopOptions.value = res.data.items || []
|
||||||
|
// 构建树形结构数据
|
||||||
|
shopTreeData.value = buildTreeData(shopOptions.value)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载店铺选项失败:', error)
|
console.error('加载店铺选项失败:', error)
|
||||||
@@ -567,6 +709,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载搜索栏分配者店铺选项(默认加载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 searchSeries = (query: string) => {
|
const searchSeries = (query: string) => {
|
||||||
if (query) {
|
if (query) {
|
||||||
@@ -576,15 +730,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索店铺(用于新增对话框)
|
|
||||||
const searchShop = (query: string) => {
|
|
||||||
if (query) {
|
|
||||||
loadShopOptions(query)
|
|
||||||
} else {
|
|
||||||
loadShopOptions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索系列(用于搜索栏)
|
// 搜索系列(用于搜索栏)
|
||||||
const handleSearchSeries = async (query: string) => {
|
const handleSearchSeries = async (query: string) => {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
@@ -606,7 +751,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索店铺(用于搜索栏)
|
// 搜索店铺(用于搜索栏-被分配的店铺)
|
||||||
const handleSearchShop = async (query: string) => {
|
const handleSearchShop = async (query: string) => {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
loadSearchShopOptions()
|
loadSearchShopOptions()
|
||||||
@@ -626,6 +771,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索分配者店铺(用于搜索栏)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取分配列表
|
// 获取分配列表
|
||||||
const getTableData = async () => {
|
const getTableData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -635,6 +800,7 @@
|
|||||||
page_size: pagination.page_size,
|
page_size: pagination.page_size,
|
||||||
shop_id: searchForm.shop_id || undefined,
|
shop_id: searchForm.shop_id || undefined,
|
||||||
series_id: searchForm.series_id || undefined,
|
series_id: searchForm.series_id || undefined,
|
||||||
|
allocator_shop_id: searchForm.allocator_shop_id !== undefined ? searchForm.allocator_shop_id : undefined,
|
||||||
status: searchForm.status || undefined
|
status: searchForm.status || undefined
|
||||||
}
|
}
|
||||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocations(params)
|
const res = await ShopSeriesAllocationService.getShopSeriesAllocations(params)
|
||||||
@@ -692,11 +858,16 @@
|
|||||||
form.shop_name = row.shop_name
|
form.shop_name = row.shop_name
|
||||||
form.allocator_shop_name = row.allocator_shop_name
|
form.allocator_shop_name = row.allocator_shop_name
|
||||||
form.enable_one_time_commission = row.enable_one_time_commission
|
form.enable_one_time_commission = row.enable_one_time_commission
|
||||||
form.one_time_commission_amount = row.one_time_commission_amount
|
// 将分转换为元显示
|
||||||
|
form.one_time_commission_amount = row.one_time_commission_amount / 100
|
||||||
form.one_time_commission_threshold = row.one_time_commission_threshold
|
form.one_time_commission_threshold = row.one_time_commission_threshold
|
||||||
|
? row.one_time_commission_threshold / 100
|
||||||
|
: undefined
|
||||||
form.one_time_commission_trigger = row.one_time_commission_trigger || 'first_recharge'
|
form.one_time_commission_trigger = row.one_time_commission_trigger || 'first_recharge'
|
||||||
form.enable_force_recharge = row.enable_force_recharge
|
form.enable_force_recharge = row.enable_force_recharge
|
||||||
form.force_recharge_amount = row.force_recharge_amount
|
form.force_recharge_amount = row.force_recharge_amount
|
||||||
|
? row.force_recharge_amount / 100
|
||||||
|
: undefined
|
||||||
form.force_recharge_trigger_type = row.force_recharge_trigger_type
|
form.force_recharge_trigger_type = row.force_recharge_trigger_type
|
||||||
} else {
|
} else {
|
||||||
form.id = 0
|
form.id = 0
|
||||||
@@ -740,6 +911,11 @@
|
|||||||
form.force_recharge_trigger_type = undefined
|
form.force_recharge_trigger_type = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleViewDetail = (row: ShopSeriesAllocationResponse) => {
|
||||||
|
router.push(`/package-management/series-assign/detail/${row.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
// 删除分配
|
// 删除分配
|
||||||
const deleteAllocation = (row: ShopSeriesAllocationResponse) => {
|
const deleteAllocation = (row: ShopSeriesAllocationResponse) => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
@@ -773,21 +949,24 @@
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
submitLoading.value = true
|
submitLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
// 将元转换为分提交给后端
|
||||||
const data: any = {
|
const data: any = {
|
||||||
one_time_commission_amount: form.one_time_commission_amount,
|
one_time_commission_amount: Math.round(form.one_time_commission_amount * 100),
|
||||||
enable_one_time_commission: form.enable_one_time_commission,
|
enable_one_time_commission: form.enable_one_time_commission,
|
||||||
enable_force_recharge: form.enable_force_recharge
|
enable_force_recharge: form.enable_force_recharge
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果启用了一次性佣金,加入相关字段
|
// 如果启用了一次性佣金,加入相关字段
|
||||||
if (form.enable_one_time_commission) {
|
if (form.enable_one_time_commission) {
|
||||||
data.one_time_commission_threshold = form.one_time_commission_threshold
|
data.one_time_commission_threshold = Math.round(
|
||||||
|
(form.one_time_commission_threshold || 0) * 100
|
||||||
|
)
|
||||||
data.one_time_commission_trigger = form.one_time_commission_trigger
|
data.one_time_commission_trigger = form.one_time_commission_trigger
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果启用了强制充值,加入相关字段
|
// 如果启用了强制充值,加入相关字段
|
||||||
if (form.enable_force_recharge) {
|
if (form.enable_force_recharge) {
|
||||||
data.force_recharge_amount = form.force_recharge_amount
|
data.force_recharge_amount = Math.round((form.force_recharge_amount || 0) * 100)
|
||||||
data.force_recharge_trigger_type = form.force_recharge_trigger_type
|
data.force_recharge_trigger_type = form.force_recharge_trigger_type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,9 @@
|
|||||||
|
|
||||||
<!-- 新增店铺时的初始账号信息 -->
|
<!-- 新增店铺时的初始账号信息 -->
|
||||||
<template v-if="dialogType === 'add'">
|
<template v-if="dialogType === 'add'">
|
||||||
<ElDivider content-position="left">初始账号信息</ElDivider>
|
<div class="form-section-title">
|
||||||
|
<span class="title-text">初始账号信息</span>
|
||||||
|
</div>
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="用户名" prop="init_username">
|
<ElFormItem label="用户名" prop="init_username">
|
||||||
@@ -181,9 +183,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 客户账号列表弹窗 -->
|
|
||||||
<CustomerAccountDialog v-model="customerAccountDialogVisible" :shop-id="currentShopId" />
|
|
||||||
|
|
||||||
<!-- 店铺操作右键菜单 -->
|
<!-- 店铺操作右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="shopOperationMenuRef"
|
ref="shopOperationMenuRef"
|
||||||
@@ -202,7 +201,7 @@
|
|||||||
<!-- 当前默认角色列表 -->
|
<!-- 当前默认角色列表 -->
|
||||||
<div class="default-roles-section">
|
<div class="default-roles-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span>当前默认角色</span>
|
<span style="color:white;">当前默认角色</span>
|
||||||
<ElButton type="primary" @click="showAddRoleDialog">
|
<ElButton type="primary" @click="showAddRoleDialog">
|
||||||
添加角色
|
添加角色
|
||||||
</ElButton>
|
</ElButton>
|
||||||
@@ -299,6 +298,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
FormInstance,
|
FormInstance,
|
||||||
ElMessage,
|
ElMessage,
|
||||||
@@ -314,25 +314,24 @@
|
|||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import CustomerAccountDialog from '@/components/business/CustomerAccountDialog.vue'
|
|
||||||
import { ShopService, RoleService } from '@/api/modules'
|
import { ShopService, RoleService } from '@/api/modules'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import type { ShopResponse, ShopRoleResponse } from '@/types/api'
|
import type { ShopResponse, ShopRoleResponse } from '@/types/api'
|
||||||
import { RoleType } from '@/types/api'
|
import { RoleType } from '@/types/api'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
|
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
|
||||||
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
|
|
||||||
defineOptions({ name: 'Shop' })
|
defineOptions({ name: 'Shop' })
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const dialogType = ref('add')
|
const dialogType = ref('add')
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const customerAccountDialogVisible = ref(false)
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const parentShopLoading = ref(false)
|
const parentShopLoading = ref(false)
|
||||||
const currentShopId = ref<number>(0)
|
|
||||||
const parentShopList = ref<ShopResponse[]>([])
|
const parentShopList = ref<ShopResponse[]>([])
|
||||||
const searchParentShopList = ref<ShopResponse[]>([])
|
const searchParentShopList = ref<ShopResponse[]>([])
|
||||||
|
|
||||||
@@ -614,7 +613,7 @@
|
|||||||
if (hasAuth('shop:look_customer')) {
|
if (hasAuth('shop:look_customer')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
text: '查看客户',
|
text: '账号列表',
|
||||||
onClick: () => viewCustomerAccounts(row)
|
onClick: () => viewCustomerAccounts(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -884,8 +883,12 @@
|
|||||||
|
|
||||||
// 查看客户账号
|
// 查看客户账号
|
||||||
const viewCustomerAccounts = (row: ShopResponse) => {
|
const viewCustomerAccounts = (row: ShopResponse) => {
|
||||||
currentShopId.value = row.id
|
// 跳转到账号列表页面,通过 query 参数区分店铺类型
|
||||||
customerAccountDialogVisible.value = true
|
router.push({
|
||||||
|
name: 'EnterpriseCustomerAccounts',
|
||||||
|
params: { id: row.id },
|
||||||
|
query: { type: 'shop' }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 店铺操作菜单项配置
|
// 店铺操作菜单项配置
|
||||||
|
|||||||
Reference in New Issue
Block a user