fetch(add): 新增企业设备授权
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 2m25s

This commit is contained in:
sexygoat
2026-01-30 15:39:19 +08:00
parent 841cf0442b
commit 8a1388608c
25 changed files with 978 additions and 922 deletions

View File

@@ -0,0 +1,310 @@
<template>
<ArtTableFullScreen>
<div class="device-task-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="searchForm"
:items="searchFormItems"
@reset="handleReset"
@search="handleSearch"
></ArtSearchBar>
<ElCard shadow="never" class="art-table-card">
<!-- 表格头部 -->
<ArtTableHeader
:columnList="columnOptions"
v-model:columns="columnChecks"
@refresh="handleRefresh"
/>
<!-- 表格 -->
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="taskList"
:currentPage="pagination.page"
:pageSize="pagination.pageSize"
:total="pagination.total"
:marginTop="10"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { DeviceService } from '@/api/modules'
import { ElMessage, ElTag } from 'element-plus'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { formatDateTime } from '@/utils/business/format'
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
import type { DeviceImportTask, DeviceImportTaskStatus } from '@/types/api/device'
defineOptions({ name: 'DeviceTask' })
const router = useRouter()
const loading = ref(false)
const tableRef = ref()
// 搜索表单初始值
const initialSearchState = {
status: undefined,
batch_no: '',
start_time: '',
end_time: ''
}
// 搜索表单
const searchForm = reactive({ ...initialSearchState })
// 分页
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
// 搜索表单配置
const searchFormItems: SearchFormItem[] = [
{
label: '任务状态',
prop: 'status',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: '待处理', value: 1 },
{ label: '处理中', value: 2 },
{ label: '已完成', value: 3 },
{ label: '失败', value: 4 }
]
},
{
label: '批次号',
prop: 'batch_no',
type: 'input',
config: {
clearable: true,
placeholder: '请输入批次号'
}
},
{
label: '创建时间',
prop: 'dateRange',
type: 'daterange',
config: {
type: 'daterange',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
}
}
]
// 列配置
const columnOptions = [
{ label: '任务编号', prop: 'task_no' },
{ label: '批次号', prop: 'batch_no' },
{ label: '文件名', prop: 'file_name' },
{ label: '任务状态', prop: 'status' },
{ label: '总数', prop: 'total_count' },
{ label: '成功数', prop: 'success_count' },
{ label: '失败数', prop: 'fail_count' },
{ label: '跳过数', prop: 'skip_count' },
{ label: '创建时间', prop: 'created_at' },
{ label: '完成时间', prop: 'completed_at' },
{ label: '操作', prop: 'operation' }
]
const taskList = ref<DeviceImportTask[]>([])
// 获取状态标签类型
const getStatusType = (status: DeviceImportTaskStatus) => {
switch (status) {
case 1:
return 'info'
case 2:
return 'warning'
case 3:
return 'success'
case 4:
return 'danger'
default:
return 'info'
}
}
// 查看详情
const viewDetail = (row: DeviceImportTask) => {
router.push({
path: '/asset-management/task-detail',
query: {
id: row.id,
task_type: 'device'
}
})
}
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'task_no',
label: '任务编号',
width: 150
},
{
prop: 'batch_no',
label: '批次号',
width: 120
},
{
prop: 'file_name',
label: '文件名',
minWidth: 200
},
{
prop: 'status',
label: '任务状态',
width: 100,
formatter: (row: DeviceImportTask) => {
return h(ElTag, { type: getStatusType(row.status) }, () => row.status_text)
}
},
{
prop: 'total_count',
label: '总数',
width: 80
},
{
prop: 'success_count',
label: '成功数',
width: 80
},
{
prop: 'fail_count',
label: '失败数',
width: 80,
formatter: (row: DeviceImportTask) => {
const type = row.fail_count > 0 ? 'danger' : 'success'
return h(ElTag, { type, size: 'small' }, () => row.fail_count)
}
},
{
prop: 'skip_count',
label: '跳过数',
width: 80
},
{
prop: 'created_at',
label: '创建时间',
width: 160,
formatter: (row: DeviceImportTask) => formatDateTime(row.created_at)
},
{
prop: 'completed_at',
label: '完成时间',
width: 160,
formatter: (row: DeviceImportTask) => (row.completed_at ? formatDateTime(row.completed_at) : '-')
},
{
prop: 'operation',
label: '操作',
width: 100,
fixed: 'right',
formatter: (row: DeviceImportTask) => {
return h(ArtButtonTable, {
type: 'view',
onClick: () => viewDetail(row)
})
}
}
])
onMounted(() => {
getTableData()
})
// 获取设备任务列表
const getTableData = async () => {
loading.value = true
try {
const params: any = {
page: pagination.page,
page_size: pagination.pageSize,
status: searchForm.status,
batch_no: searchForm.batch_no || undefined
}
// 处理时间范围
if (searchForm.dateRange && Array.isArray(searchForm.dateRange)) {
params.start_time = searchForm.dateRange[0]
params.end_time = searchForm.dateRange[1]
}
// 清理空值
Object.keys(params).forEach((key) => {
if (params[key] === '' || params[key] === undefined) {
delete params[key]
}
})
const res = await DeviceService.getImportTasks(params)
if (res.code === 0) {
taskList.value = res.data.list || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error(error)
ElMessage.error('获取设备任务列表失败')
} finally {
loading.value = false
}
}
// 重置搜索
const handleReset = () => {
Object.assign(searchForm, { ...initialSearchState })
pagination.page = 1
getTableData()
}
// 搜索
const handleSearch = () => {
pagination.page = 1
getTableData()
}
// 刷新表格
const handleRefresh = () => {
getTableData()
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getTableData()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.page = newCurrentPage
getTableData()
}
</script>
<style lang="scss" scoped>
.device-task-page {
// Device task page styles
}
</style>

View File

@@ -28,6 +28,11 @@
<ElButton type="info" :disabled="selectedCards.length === 0" @click="showSeriesBindingDialog">
批量设置套餐系列
</ElButton>
<ElButton type="primary" @click="cardDistribution">网卡分销</ElButton>
<ElButton type="success" @click="batchRecharge">批量充值</ElButton>
<ElButton type="danger" @click="cardRecycle">网卡回收</ElButton>
<ElButton type="info" @click="batchDownload">批量下载</ElButton>
<ElButton type="warning" @click="changePackage">变更套餐</ElButton>
</template>
</ArtTableHeader>
@@ -404,15 +409,10 @@
const initialSearchState = {
status: undefined,
carrier_id: undefined,
shop_id: undefined,
iccid: '',
msisdn: '',
batch_no: '',
package_id: undefined,
is_distributed: undefined,
is_replaced: undefined,
iccid_start: '',
iccid_end: ''
is_distributed: undefined
}
//
@@ -603,14 +603,19 @@
//
const columnOptions = [
{ label: 'ICCID', prop: 'iccid' },
{ label: 'IMSI', prop: 'imsi' },
{ label: '卡接入号', prop: 'msisdn' },
{ label: '运营商', prop: 'carrier_name' },
{ label: '卡类型', prop: 'card_type' },
{ label: '卡业务类型', prop: 'card_category' },
{ label: '运营商', prop: 'carrier_name' },
{ label: '成本价', prop: 'cost_price' },
{ label: '分销价', prop: 'distribute_price' },
{ label: '状态', prop: 'status' },
{ label: '批次号', prop: 'batch_no' },
{ label: '店铺名称', prop: 'shop_name' },
{ label: '激活时间', prop: 'activated_at' },
{ label: '激活状态', prop: 'activation_status' },
{ label: '网络状态', prop: 'network_status' },
{ label: '实名状态', prop: 'real_name_status' },
{ label: '累计流量(MB)', prop: 'data_usage_mb' },
{ label: '首次佣金', prop: 'first_commission_paid' },
{ label: '累计充值', prop: 'accumulated_recharge' },
{ label: '创建时间', prop: 'created_at' }
]
@@ -653,28 +658,40 @@
{
prop: 'iccid',
label: 'ICCID',
minWidth: 180
},
{
prop: 'imsi',
label: 'IMSI',
width: 150
minWidth: 190
},
{
prop: 'msisdn',
label: '卡接入号',
width: 120
},
{
prop: 'carrier_name',
label: '运营商',
width: 100
width: 130
},
{
prop: 'card_type',
label: '卡类型',
width: 100
},
{
prop: 'card_category',
label: '卡业务类型',
width: 100
},
{
prop: 'carrier_name',
label: '运营商',
width: 150
},
{
prop: 'cost_price',
label: '成本价',
width: 100,
formatter: (row: StandaloneIotCard) => `¥${(row.cost_price / 100).toFixed(2)}`
},
{
prop: 'distribute_price',
label: '分销价',
width: 100,
formatter: (row: StandaloneIotCard) => `¥${(row.distribute_price / 100).toFixed(2)}`
},
{
prop: 'status',
label: '状态',
@@ -684,20 +701,55 @@
}
},
{
prop: 'batch_no',
label: '批次号',
prop: 'activation_status',
label: '激活状态',
width: 100,
formatter: (row: StandaloneIotCard) => {
const type = row.activation_status === 1 ? 'success' : 'info'
const text = row.activation_status === 1 ? '已激活' : '未激活'
return h(ElTag, { type }, () => text)
}
},
{
prop: 'network_status',
label: '网络状态',
width: 100,
formatter: (row: StandaloneIotCard) => {
const type = row.network_status === 1 ? 'success' : 'danger'
const text = row.network_status === 1 ? '开机' : '停机'
return h(ElTag, { type }, () => text)
}
},
{
prop: 'real_name_status',
label: '实名状态',
width: 100,
formatter: (row: StandaloneIotCard) => {
const type = row.real_name_status === 1 ? 'success' : 'warning'
const text = row.real_name_status === 1 ? '已实名' : '未实名'
return h(ElTag, { type }, () => text)
}
},
{
prop: 'data_usage_mb',
label: '累计流量(MB)',
width: 120
},
{
prop: 'shop_name',
label: '店铺名称',
width: 150
prop: 'first_commission_paid',
label: '首次佣金',
width: 100,
formatter: (row: StandaloneIotCard) => {
const type = row.first_commission_paid ? 'success' : 'info'
const text = row.first_commission_paid ? '已支付' : '未支付'
return h(ElTag, { type, size: 'small' }, () => text)
}
},
{
prop: 'activated_at',
label: '激活时间',
width: 160,
formatter: (row: StandaloneIotCard) => (row.activated_at ? formatDateTime(row.activated_at) : '-')
prop: 'accumulated_recharge',
label: '累计充值',
width: 100,
formatter: (row: StandaloneIotCard) => `¥${(row.accumulated_recharge / 100).toFixed(2)}`
},
{
prop: 'created_at',
@@ -729,7 +781,7 @@
const res = await CardService.getStandaloneIotCards(params)
if (res.code === 0) {
cardList.value = res.data.list || []
cardList.value = res.data.items || []
pagination.total = res.data.total || 0
}
} catch (error) {
@@ -1127,6 +1179,31 @@
}
})
}
// -
const cardDistribution = () => {
ElMessage.info('功能正在开发中')
}
// -
const batchRecharge = () => {
ElMessage.info('功能正在开发中')
}
// -
const cardRecycle = () => {
ElMessage.info('功能正在开发中')
}
// -
const batchDownload = () => {
ElMessage.info('功能正在开发中')
}
// -
const changePackage = () => {
ElMessage.info('功能正在开发中')
}
</script>
<style lang="scss" scoped>

View File

@@ -1,6 +1,6 @@
<template>
<ArtTableFullScreen>
<div class="task-management-page" id="table-full-screen">
<div class="iot-card-task-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="searchForm"
@@ -42,33 +42,26 @@
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { CardService, DeviceService } from '@/api/modules'
import { CardService } from '@/api/modules'
import { ElMessage, ElTag } from 'element-plus'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { formatDateTime } from '@/utils/business/format'
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
import type { IotCardImportTask, IotCardImportTaskStatus } from '@/types/api/card'
import type { DeviceImportTask } from '@/types/api/device'
defineOptions({ name: 'TaskManagement' })
defineOptions({ name: 'IotCardTask' })
const router = useRouter()
const loading = ref(false)
const tableRef = ref()
//
type TaskType = 'card' | 'device'
type ImportTask = IotCardImportTask | DeviceImportTask
//
const initialSearchState = {
task_type: undefined as TaskType | undefined,
status: undefined,
carrier_id: undefined,
batch_no: '',
start_time: '',
end_time: ''
dateRange: undefined as any
}
//
@@ -83,19 +76,6 @@
//
const searchFormItems: SearchFormItem[] = [
{
label: '任务类型',
prop: 'task_type',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: 'ICCID导入', value: 'card' },
{ label: '设备导入', value: 'device' }
]
},
{
label: '任务状态',
prop: 'status',
@@ -150,21 +130,21 @@
//
const columnOptions = [
{ label: '任务编号', prop: 'task_no' },
{ label: '任务类型', prop: 'task_type' },
{ label: '批次号', prop: 'batch_no' },
{ label: '任务状态', prop: 'status' },
{ label: '运营商', prop: 'carrier_name' },
{ label: '文件名', prop: 'file_name' },
{ label: '任务状态', prop: 'status' },
{ label: '总数', prop: 'total_count' },
{ label: '成功数', prop: 'success_count' },
{ label: '失败数', prop: 'fail_count' },
{ label: '跳过数', prop: 'skip_count' },
{ label: '创建时间', prop: 'created_at' },
{ label: '开始时间', prop: 'started_at' },
{ label: '完成时间', prop: 'completed_at' },
{ label: '错误信息', prop: 'error_message' },
{ label: '创建时间', prop: 'created_at' },
{ label: '操作', prop: 'operation' }
]
const taskList = ref<ImportTask[]>([])
const taskList = ref<IotCardImportTask[]>([])
//
const getStatusType = (status: IotCardImportTaskStatus) => {
@@ -182,27 +162,13 @@
}
}
//
const getTaskType = (row: ImportTask): TaskType => {
// device_no carrier_name
if ('device_no' in row || (row.batch_no && row.batch_no.startsWith('DEV-'))) {
return 'device'
}
return 'card'
}
//
const getTaskTypeText = (taskType: TaskType) => {
return taskType === 'device' ? '设备导入' : 'ICCID导入'
}
//
const viewDetail = (row: ImportTask) => {
const viewDetail = (row: IotCardImportTask) => {
router.push({
path: '/asset-management/task-detail',
query: {
id: row.id,
task_type: getTaskType(row)
task_type: 'card'
}
})
}
@@ -212,44 +178,26 @@
{
prop: 'task_no',
label: '任务编号',
width: 150
},
{
prop: 'task_type',
label: '任务类型',
width: 100,
formatter: (row: ImportTask) => {
const taskType = getTaskType(row)
const tagType = taskType === 'device' ? 'warning' : 'primary'
return h(ElTag, { type: tagType, size: 'small' }, () => getTaskTypeText(taskType))
}
},
{
prop: 'batch_no',
label: '批次号',
width: 120
},
{
prop: 'carrier_name',
label: '运营商',
width: 100,
formatter: (row: ImportTask) => {
return (row as IotCardImportTask).carrier_name || '-'
}
},
{
prop: 'file_name',
label: '文件名',
minWidth: 200
width: 180
},
{
prop: 'status',
label: '任务状态',
width: 100,
formatter: (row: ImportTask) => {
formatter: (row: IotCardImportTask) => {
return h(ElTag, { type: getStatusType(row.status) }, () => row.status_text)
}
},
{
prop: 'carrier_name',
label: '运营商',
width: 120
},
{
prop: 'file_name',
label: '文件名',
minWidth: 250
},
{
prop: 'total_count',
label: '总数',
@@ -264,7 +212,7 @@
prop: 'fail_count',
label: '失败数',
width: 80,
formatter: (row: ImportTask) => {
formatter: (row: IotCardImportTask) => {
const type = row.fail_count > 0 ? 'danger' : 'success'
return h(ElTag, { type, size: 'small' }, () => row.fail_count)
}
@@ -275,25 +223,37 @@
width: 80
},
{
prop: 'created_at',
label: '创建时间',
prop: 'started_at',
label: '开始时间',
width: 160,
formatter: (row: ImportTask) => formatDateTime(row.created_at)
formatter: (row: IotCardImportTask) => (row.started_at ? formatDateTime(row.started_at) : '-')
},
{
prop: 'completed_at',
label: '完成时间',
width: 160,
formatter: (row: ImportTask) => (row.completed_at ? formatDateTime(row.completed_at) : '-')
formatter: (row: IotCardImportTask) => (row.completed_at ? formatDateTime(row.completed_at) : '-')
},
{
prop: 'error_message',
label: '错误信息',
minWidth: 200,
formatter: (row: IotCardImportTask) => row.error_message || '-'
},
{
prop: 'created_at',
label: '创建时间',
width: 160,
formatter: (row: IotCardImportTask) => formatDateTime(row.created_at)
},
{
prop: 'operation',
label: '操作',
width: 100,
width: 120,
fixed: 'right',
formatter: (row: ImportTask) => {
formatter: (row: IotCardImportTask) => {
return h(ArtButtonTable, {
type: 'view',
text: '查看详情',
onClick: () => viewDetail(row)
})
}
@@ -304,7 +264,7 @@
getTableData()
})
//
// IoT
const getTableData = async () => {
loading.value = true
try {
@@ -312,6 +272,7 @@
page: pagination.page,
page_size: pagination.pageSize,
status: searchForm.status,
carrier_id: searchForm.carrier_id,
batch_no: searchForm.batch_no || undefined
}
@@ -328,52 +289,14 @@
}
})
//
if (searchForm.task_type === 'device') {
//
const res = await DeviceService.getImportTasks(params)
if (res.code === 0) {
taskList.value = res.data.list || []
pagination.total = res.data.total || 0
}
} else if (searchForm.task_type === 'card') {
// ICCIDcarrier_id
const cardParams = {
...params,
carrier_id: searchForm.carrier_id
}
const res = await CardService.getIotCardImportTasks(cardParams)
if (res.code === 0) {
taskList.value = res.data.list || []
pagination.total = res.data.total || 0
}
} else {
// - API
const [cardRes, deviceRes] = await Promise.all([
CardService.getIotCardImportTasks({
...params,
carrier_id: searchForm.carrier_id
}),
DeviceService.getImportTasks(params)
])
const cardTasks = cardRes.code === 0 ? cardRes.data.list || [] : []
const deviceTasks = deviceRes.code === 0 ? deviceRes.data.list || [] : []
//
const allTasks = [...cardTasks, ...deviceTasks].sort((a, b) => {
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
})
//
const start = (pagination.page - 1) * pagination.pageSize
const end = start + pagination.pageSize
taskList.value = allTasks.slice(start, end)
pagination.total = allTasks.length
const res = await CardService.getIotCardImportTasks(params)
if (res.code === 0) {
taskList.value = res.data.items || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error(error)
ElMessage.error('获取任务列表失败')
ElMessage.error('获取IoT卡任务列表失败')
} finally {
loading.value = false
}
@@ -410,7 +333,7 @@
</script>
<style lang="scss" scoped>
.task-management-page {
// Task management page styles
.iot-card-task-page {
// IoT card task page styles
}
</style>

View File

@@ -1,37 +1,38 @@
<template>
<div class="analysis-dashboard">
<el-row :gutter="20">
<el-col :xl="14" :lg="15" :xs="24">
<TodaySales />
</el-col>
<el-col :xl="10" :lg="9" :xs="24">
<VisitorInsights />
</el-col>
</el-row>
开发中敬请期待...
<!--<el-row :gutter="20">-->
<!-- <el-col :xl="14" :lg="15" :xs="24">-->
<!-- <TodaySales />-->
<!-- </el-col>-->
<!-- <el-col :xl="10" :lg="9" :xs="24">-->
<!-- <VisitorInsights />-->
<!-- </el-col>-->
<!--</el-row>-->
<el-row :gutter="20" class="mt-20">
<el-col :xl="10" :lg="10" :xs="24">
<TotalRevenue />
</el-col>
<el-col :xl="7" :lg="7" :xs="24">
<CustomerSatisfaction />
</el-col>
<el-col :xl="7" :lg="7" :xs="24">
<TargetVsReality />
</el-col>
</el-row>
<!--<el-row :gutter="20" class="mt-20">-->
<!-- <el-col :xl="10" :lg="10" :xs="24">-->
<!-- <TotalRevenue />-->
<!-- </el-col>-->
<!-- <el-col :xl="7" :lg="7" :xs="24">-->
<!-- <CustomerSatisfaction />-->
<!-- </el-col>-->
<!-- <el-col :xl="7" :lg="7" :xs="24">-->
<!-- <TargetVsReality />-->
<!-- </el-col>-->
<!--</el-row>-->
<el-row :gutter="20" class="mt-20">
<el-col :xl="10" :lg="10" :xs="24">
<TopProducts />
</el-col>
<el-col :xl="7" :lg="7" :xs="24">
<SalesMappingByCountry />
</el-col>
<el-col :xl="7" :lg="7" :xs="24">
<VolumeServiceLevel />
</el-col>
</el-row>
<!--<el-row :gutter="20" class="mt-20">-->
<!-- <el-col :xl="10" :lg="10" :xs="24">-->
<!-- <TopProducts />-->
<!-- </el-col>-->
<!-- <el-col :xl="7" :lg="7" :xs="24">-->
<!-- <SalesMappingByCountry />-->
<!-- </el-col>-->
<!-- <el-col :xl="7" :lg="7" :xs="24">-->
<!-- <VolumeServiceLevel />-->
<!-- </el-col>-->
<!--</el-row>-->
</div>
</template>

View File

@@ -1,29 +1,30 @@
<template>
<div class="console">
<CardList></CardList>
开发中敬请期待...
<!--<CardList></CardList>-->
<el-row :gutter="20">
<el-col :sm="24" :md="12" :lg="10">
<ActiveUser />
</el-col>
<el-col :sm="24" :md="12" :lg="14">
<SalesOverview />
</el-col>
</el-row>
<!--<el-row :gutter="20">-->
<!-- <el-col :sm="24" :md="12" :lg="10">-->
<!-- <ActiveUser />-->
<!-- </el-col>-->
<!-- <el-col :sm="24" :md="12" :lg="14">-->
<!-- <SalesOverview />-->
<!-- </el-col>-->
<!--</el-row>-->
<el-row :gutter="20">
<el-col :sm="24" :md="24" :lg="12">
<NewUser />
</el-col>
<el-col :sm="24" :md="12" :lg="6">
<Dynamic />
</el-col>
<el-col :sm="24" :md="12" :lg="6">
<TodoList />
</el-col>
</el-row>
<!--<el-row :gutter="20">-->
<!-- <el-col :sm="24" :md="24" :lg="12">-->
<!-- <NewUser />-->
<!-- </el-col>-->
<!-- <el-col :sm="24" :md="12" :lg="6">-->
<!-- <Dynamic />-->
<!-- </el-col>-->
<!-- <el-col :sm="24" :md="12" :lg="6">-->
<!-- <TodoList />-->
<!-- </el-col>-->
<!--</el-row>-->
<AboutProject />
<!--<AboutProject />-->
</div>
</template>

View File

@@ -1,59 +1,60 @@
<template>
<div class="ecommerce">
<el-row :gutter="20">
<el-col :sm="24" :md="24" :lg="16">
<Banner />
</el-col>
<el-col :sm="12" :md="12" :lg="4">
<TotalOrderVolume />
</el-col>
<el-col :sm="12" :md="12" :lg="4">
<TotalProducts />
</el-col>
</el-row>
开发中敬请期待...
<!--<el-row :gutter="20">-->
<!-- <el-col :sm="24" :md="24" :lg="16">-->
<!-- <Banner />-->
<!-- </el-col>-->
<!-- <el-col :sm="12" :md="12" :lg="4">-->
<!-- <TotalOrderVolume />-->
<!-- </el-col>-->
<!-- <el-col :sm="12" :md="12" :lg="4">-->
<!-- <TotalProducts />-->
<!-- </el-col>-->
<!--</el-row>-->
<el-row :gutter="20">
<el-col :sm="12" :md="12" :lg="8">
<SalesTrend />
</el-col>
<el-col :sm="12" :md="12" :lg="8">
<SalesClassification />
</el-col>
<el-col :sm="24" :md="24" :lg="8">
<el-row :gutter="20">
<el-col :sm="24" :md="12" :lg="12">
<ProductSales />
</el-col>
<el-col :sm="24" :md="12" :lg="12">
<SalesGrowth />
</el-col>
<el-col :span="24" class="no-margin-bottom">
<CartConversionRate />
</el-col>
</el-row>
</el-col>
</el-row>
<!--<el-row :gutter="20">-->
<!-- <el-col :sm="12" :md="12" :lg="8">-->
<!-- <SalesTrend />-->
<!-- </el-col>-->
<!-- <el-col :sm="12" :md="12" :lg="8">-->
<!-- <SalesClassification />-->
<!-- </el-col>-->
<!-- <el-col :sm="24" :md="24" :lg="8">-->
<!-- <el-row :gutter="20">-->
<!-- <el-col :sm="24" :md="12" :lg="12">-->
<!-- <ProductSales />-->
<!-- </el-col>-->
<!-- <el-col :sm="24" :md="12" :lg="12">-->
<!-- <SalesGrowth />-->
<!-- </el-col>-->
<!-- <el-col :span="24" class="no-margin-bottom">-->
<!-- <CartConversionRate />-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </el-col>-->
<!--</el-row>-->
<el-row :gutter="20">
<el-col :sm="24" :md="12" :lg="8">
<HotCommodity />
</el-col>
<el-col :sm="24" :md="12" :lg="8">
<AnnualSales />
</el-col>
<el-col :sm="24" :md="24" :lg="8">
<TransactionList />
</el-col>
</el-row>
<!--<el-row :gutter="20">-->
<!-- <el-col :sm="24" :md="12" :lg="8">-->
<!-- <HotCommodity />-->
<!-- </el-col>-->
<!-- <el-col :sm="24" :md="12" :lg="8">-->
<!-- <AnnualSales />-->
<!-- </el-col>-->
<!-- <el-col :sm="24" :md="24" :lg="8">-->
<!-- <TransactionList />-->
<!-- </el-col>-->
<!--</el-row>-->
<el-row :gutter="20">
<el-col :md="24" :lg="8">
<RecentTransaction />
</el-col>
<el-col :md="24" :lg="16" class="no-margin-bottom">
<HotProductsList />
</el-col>
</el-row>
<!--<el-row :gutter="20">-->
<!-- <el-col :md="24" :lg="8">-->
<!-- <RecentTransaction />-->
<!-- </el-col>-->
<!-- <el-col :md="24" :lg="16" class="no-margin-bottom">-->
<!-- <HotProductsList />-->
<!-- </el-col>-->
<!--</el-row>-->
</div>
</template>

View File

@@ -1,382 +0,0 @@
<template>
<ArtTableFullScreen>
<div class="my-packages-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="searchForm"
:items="searchFormItems"
:show-expand="false"
@reset="handleReset"
@search="handleSearch"
></ArtSearchBar>
<ElCard shadow="never" class="art-table-card">
<!-- 表格头部 -->
<ArtTableHeader
:columnList="columnOptions"
v-model:columns="columnChecks"
@refresh="handleRefresh"
>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="packageList"
:currentPage="pagination.page"
:pageSize="pagination.page_size"
:total="pagination.total"
:marginTop="10"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 详情对话框 -->
<ElDialog v-model="detailDialogVisible" title="套餐详情" width="600px">
<ElDescriptions :column="2" border v-if="currentPackage">
<ElDescriptionsItem label="套餐编码">{{
currentPackage.package_code
}}</ElDescriptionsItem>
<ElDescriptionsItem label="套餐名称">{{
currentPackage.package_name
}}</ElDescriptionsItem>
<ElDescriptionsItem label="所属系列">{{
currentPackage.series_name
}}</ElDescriptionsItem>
<ElDescriptionsItem label="套餐类型">
<ElTag :type="getPackageTypeTag(currentPackage.package_type)">{{
getPackageTypeLabel(currentPackage.package_type)
}}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="流量类型">
<ElTag :type="getDataTypeTag(currentPackage.data_type)">{{
getDataTypeLabel(currentPackage.data_type)
}}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="真流量">{{
currentPackage.real_data_mb
}}MB</ElDescriptionsItem>
<ElDescriptionsItem label="虚流量">{{
currentPackage.virtual_data_mb
}}MB</ElDescriptionsItem>
<ElDescriptionsItem label="有效期">{{
currentPackage.duration_months
}}</ElDescriptionsItem>
<ElDescriptionsItem label="套餐价格">¥{{
(currentPackage.price / 100).toFixed(2)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="上架状态">
<ElTag :type="currentPackage.shelf_status === 1 ? 'success' : 'info'">{{
currentPackage.shelf_status === 1 ? '上架' : '下架'
}}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="状态">
<ElTag :type="currentPackage.status === 1 ? 'success' : 'danger'">{{
currentPackage.status === 1 ? '启用' : '禁用'
}}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="2">{{
currentPackage.description || '无'
}}</ElDescriptionsItem>
</ElDescriptions>
<template #footer>
<div class="dialog-footer">
<ElButton type="primary" @click="detailDialogVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { PackageManageService, PackageSeriesService } from '@/api/modules'
import { ElMessage, ElTag, ElDescriptions, ElDescriptionsItem } from 'element-plus'
import type { PackageResponse } from '@/types/api'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
import { formatDateTime } from '@/utils/business/format'
import {
PACKAGE_TYPE_OPTIONS,
getPackageTypeLabel,
getPackageTypeTag,
getDataTypeLabel,
getDataTypeTag
} from '@/config/constants'
defineOptions({ name: 'MyPackages' })
const loading = ref(false)
const seriesLoading = ref(false)
const detailDialogVisible = ref(false)
const tableRef = ref()
const currentPackage = ref<PackageResponse | null>(null)
const seriesOptions = ref<any[]>([])
// 搜索表单初始值
const initialSearchState = {
series_id: undefined as number | undefined,
package_type: undefined as string | undefined
}
// 搜索表单
const searchForm = reactive({ ...initialSearchState })
// 搜索表单配置
const searchFormItems = computed<SearchFormItem[]>(() => [
{
label: '套餐系列',
prop: 'series_id',
type: 'select',
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: searchSeries,
loading: seriesLoading.value,
placeholder: '请选择或搜索套餐系列'
},
options: () =>
seriesOptions.value.map((s: any) => ({
label: s.series_name,
value: s.id
}))
},
{
label: '套餐类型',
prop: 'package_type',
type: 'select',
config: {
clearable: true,
placeholder: '请选择套餐类型'
},
options: () =>
PACKAGE_TYPE_OPTIONS.map((o) => ({
label: o.label,
value: o.value
}))
}
])
// 分页
const pagination = reactive({
page: 1,
page_size: 20,
total: 0
})
// 列配置
const columnOptions = [
{ label: 'ID', prop: 'id' },
{ label: '套餐编码', prop: 'package_code' },
{ label: '套餐名称', prop: 'package_name' },
{ label: '所属系列', prop: 'series_name' },
{ label: '套餐类型', prop: 'package_type' },
{ label: '流量类型', prop: 'data_type' },
{ label: '真流量', prop: 'real_data_mb' },
{ label: '虚流量', prop: 'virtual_data_mb' },
{ label: '有效期', prop: 'duration_months' },
{ label: '操作', prop: 'operation' }
]
const packageList = ref<PackageResponse[]>([])
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'id',
label: 'ID',
width: 80
},
{
prop: 'package_code',
label: '套餐编码',
minWidth: 150
},
{
prop: 'package_name',
label: '套餐名称',
minWidth: 180
},
{
prop: 'series_name',
label: '所属系列',
width: 120
},
{
prop: 'package_type',
label: '套餐类型',
width: 100,
formatter: (row: PackageResponse) => {
return h(
ElTag,
{ type: getPackageTypeTag(row.package_type), size: 'small' },
() => getPackageTypeLabel(row.package_type)
)
}
},
{
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',
label: '真流量',
width: 100,
formatter: (row: PackageResponse) => `${row.real_data_mb}MB`
},
{
prop: 'virtual_data_mb',
label: '虚流量',
width: 100,
formatter: (row: PackageResponse) => `${row.virtual_data_mb}MB`
},
{
prop: 'duration_months',
label: '有效期',
width: 100,
formatter: (row: PackageResponse) => `${row.duration_months}`
},
{
prop: 'operation',
label: '操作',
width: 100,
fixed: 'right',
formatter: (row: PackageResponse) => {
return h(ArtButtonTable, {
text: '查看详情',
onClick: () => showDetail(row)
})
}
}
])
onMounted(() => {
loadSeriesOptions()
getTableData()
})
// 加载套餐系列选项(默认加载10条)
const loadSeriesOptions = async (seriesName?: string) => {
seriesLoading.value = true
try {
const params: any = {
page: 1,
page_size: 10,
status: 1
}
if (seriesName) {
params.series_name = seriesName
}
const res = await PackageSeriesService.getPackageSeries(params)
if (res.code === 0) {
seriesOptions.value = res.data.items || []
}
} catch (error) {
console.error('加载系列选项失败:', error)
} finally {
seriesLoading.value = false
}
}
// 搜索系列
const searchSeries = (query: string) => {
if (query) {
loadSeriesOptions(query)
} else {
loadSeriesOptions()
}
}
// 获取我的可售套餐列表
const getTableData = async () => {
loading.value = true
try {
const params = {
page: pagination.page,
page_size: pagination.page_size,
series_id: searchForm.series_id || undefined,
package_type: searchForm.package_type || undefined
}
const res = await PackageManageService.getPackages(params)
if (res.code === 0) {
packageList.value = res.data.items || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
// 重置搜索
const handleReset = () => {
Object.assign(searchForm, { ...initialSearchState })
pagination.page = 1
getTableData()
}
// 搜索
const handleSearch = () => {
pagination.page = 1
getTableData()
}
// 刷新表格
const handleRefresh = () => {
getTableData()
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.page_size = newPageSize
getTableData()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.page = newCurrentPage
getTableData()
}
// 显示详情
const showDetail = async (row: PackageResponse) => {
try {
const res = await PackageManageService.getPackageDetail(row.id)
if (res.code === 0) {
currentPackage.value = res.data
detailDialogVisible.value = true
}
} catch (error) {
console.error(error)
ElMessage.error('获取套餐详情失败')
}
}
</script>
<style lang="scss" scoped>
.my-packages-page {
// 可以添加特定样式
}
.dialog-footer {
text-align: right;
}
</style>

View File

@@ -97,6 +97,7 @@
:remote-method="searchPackage"
:loading="packageLoading"
clearable
@change="handlePackageChange"
>
<ElOption
v-for="pkg in packageOptions"
@@ -273,7 +274,21 @@
const rules = reactive<FormRules>({
package_id: [{ required: true, message: '请选择套餐', trigger: 'change' }],
shop_id: [{ required: true, message: '请选择店铺', trigger: 'change' }],
cost_price: [{ required: true, message: '请输入成本价', trigger: 'blur' }]
cost_price: [
{ required: true, message: '请输入成本价', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
if (value === undefined || value === null || value === '') {
callback(new Error('请输入成本价'))
} else if (form.package_base_price && value < form.package_base_price) {
callback(new Error(`成本价不能低于套餐价格 ¥${(form.package_base_price / 100).toFixed(2)}`))
} else {
callback()
}
},
trigger: 'blur'
}
]
})
// 表单数据
@@ -281,7 +296,8 @@
id: 0,
package_id: undefined,
shop_id: undefined,
cost_price: 0
cost_price: 0,
package_base_price: 0 // 存储选中套餐的成本价,用于验证
})
// 成本价表单验证规则
@@ -589,11 +605,13 @@
form.package_id = row.package_id
form.shop_id = row.shop_id
form.cost_price = row.cost_price
form.package_base_price = 0
} else {
form.id = 0
form.package_id = undefined
form.shop_id = undefined
form.cost_price = 0
form.package_base_price = 0
}
// 重置表单验证状态
@@ -602,6 +620,23 @@
})
}
// 处理套餐选择变化
const handlePackageChange = (packageId: number | undefined) => {
if (packageId) {
// 从套餐选项中找到选中的套餐
const selectedPackage = packageOptions.value.find(pkg => pkg.id === packageId)
if (selectedPackage) {
// 将套餐的价格设置为成本价
form.cost_price = selectedPackage.price
form.package_base_price = selectedPackage.price
}
} else {
// 清空时重置成本价
form.cost_price = 0
form.package_base_price = 0
}
}
// 处理弹窗关闭事件
const handleDialogClosed = () => {
// 清除表单验证状态
@@ -611,6 +646,7 @@
form.package_id = undefined
form.shop_id = undefined
form.cost_price = 0
form.package_base_price = 0
}
// 删除分配

View File

@@ -69,6 +69,20 @@
/>
</ElSelect>
</ElFormItem>
<div v-if="dialogType === 'edit'" class="info-row">
<div class="info-item">
<span class="info-label">系列名称:</span>
<span class="info-value">{{ form.series_name || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">店铺名称:</span>
<span class="info-value">{{ form.shop_name || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">分配者店铺:</span>
<span class="info-value">{{ form.allocator_shop_name || '-' }}</span>
</div>
</div>
<ElFormItem label="选择店铺" prop="shop_id" v-if="dialogType === 'add'">
<ElSelect
v-model="form.shop_id"
@@ -121,52 +135,97 @@
</div>
</ElFormItem>
<!-- 梯度返佣配置 -->
<ElDivider content-position="left">梯度返佣设置可选</ElDivider>
<ElFormItem label="启用梯度返佣">
<ElSwitch v-model="form.enable_tier_commission" />
<!-- 一次性佣金配置 -->
<ElDivider content-position="left">一次性佣金设置可选</ElDivider>
<ElFormItem label="启用一次性佣金">
<ElSwitch v-model="form.enable_one_time_commission" />
</ElFormItem>
<template v-if="form.enable_tier_commission">
<ElFormItem label="周期类型" prop="tier_config.period_type">
<ElSelect v-model="form.tier_config.period_type" placeholder="请选择周期类型" style="width: 100%">
<ElOption label="月度" value="monthly" />
<ElOption label="季度" value="quarterly" />
<ElOption label="年度" value="yearly" />
</ElSelect>
<template v-if="form.enable_one_time_commission">
<ElFormItem label="一次性佣金类型" prop="one_time_commission_config.type">
<ElRadioGroup v-model="form.one_time_commission_config.type">
<ElRadio value="fixed">固定</ElRadio>
<ElRadio value="tiered">梯度</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem label="梯度类型" prop="tier_config.tier_type">
<ElSelect v-model="form.tier_config.tier_type" placeholder="请选择梯度类型" style="width: 100%">
<ElOption label="按销量" value="sales_count" />
<ElOption label="按销售额" value="sales_amount" />
</ElSelect>
<ElFormItem label="触发条件" prop="one_time_commission_config.trigger">
<ElRadioGroup v-model="form.one_time_commission_config.trigger">
<ElRadio value="single_recharge">单次充值</ElRadio>
<ElRadio value="accumulated_recharge">累计充值</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem label="梯度档位">
<div class="tier-list">
<div v-for="(tier, index) in form.tier_config.tiers" :key="index" class="tier-item">
<ElInputNumber
v-model="tier.threshold"
:min="1"
:controls="false"
placeholder="阈值"
style="width: 120px"
/>
<ElSelect v-model="tier.mode" placeholder="模式" style="width: 100px">
<ElOption label="固定" value="fixed" />
<ElOption label="百分比" value="percent" />
</ElSelect>
<ElInputNumber
v-model="tier.value"
:min="0"
:controls="false"
placeholder="返佣值"
style="width: 120px"
/>
<ElButton type="danger" @click="removeTier(index)">删除</ElButton>
<ElFormItem label="最低阈值(分)" prop="one_time_commission_config.threshold">
<ElInputNumber
v-model="form.one_time_commission_config.threshold"
:min="1"
:controls="false"
style="width: 100%"
placeholder="请输入最低阈值(分)"
/>
</ElFormItem>
<!-- 固定类型配置 -->
<template v-if="form.one_time_commission_config.type === 'fixed'">
<ElFormItem label="返佣模式" prop="one_time_commission_config.mode">
<ElRadioGroup v-model="form.one_time_commission_config.mode">
<ElRadio value="fixed">固定金额</ElRadio>
<ElRadio value="percent">百分比</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem
:label="form.one_time_commission_config.mode === 'fixed' ? '佣金金额(分)' : '佣金比例(千分比)'"
prop="one_time_commission_config.value"
>
<ElInputNumber
v-model="form.one_time_commission_config.value"
:min="1"
:controls="false"
style="width: 100%"
:placeholder="
form.one_time_commission_config.mode === 'fixed'
? '请输入佣金金额(分)'
: '请输入佣金比例的千分比(如200表示20%)'
"
/>
</ElFormItem>
</template>
<!-- 梯度类型配置 -->
<template v-if="form.one_time_commission_config.type === 'tiered'">
<ElFormItem label="梯度档位">
<div class="tier-list">
<div v-for="(tier, index) in form.one_time_commission_config.tiers" :key="index" class="tier-item">
<ElSelect v-model="tier.tier_type" placeholder="梯度类型" style="width: 120px">
<ElOption label="销量" value="sales_count" />
<ElOption label="销售额" value="sales_amount" />
</ElSelect>
<ElInputNumber
v-model="tier.threshold"
:min="1"
:controls="false"
placeholder="阈值"
style="width: 120px"
/>
<ElSelect v-model="tier.mode" placeholder="返佣模式" style="width: 120px">
<ElOption label="固定金额" value="fixed" />
<ElOption label="百分比" value="percent" />
</ElSelect>
<ElInputNumber
v-model="tier.value"
:min="1"
:controls="false"
placeholder="返佣值"
style="width: 120px"
/>
<ElButton type="danger" @click="removeTier(index)">删除</ElButton>
</div>
<ElButton type="primary" @click="addTier">添加档位</ElButton>
</div>
<ElButton type="primary" @click="addTier">添加档位</ElButton>
</div>
</ElFormItem>
</ElFormItem>
</template>
</template>
</ElForm>
<template #footer>
@@ -295,7 +354,7 @@
{ label: '店铺名称', prop: 'shop_name' },
{ label: '分配者店铺', prop: 'allocator_shop_name' },
{ label: '基础返佣', prop: 'base_commission' },
{ label: '梯度返佣', prop: 'enable_tier_commission' },
{ label: '一次性佣金', prop: 'enable_one_time_commission' },
{ label: '状态', prop: 'status' },
{ label: '创建时间', prop: 'created_at' },
{ label: '操作', prop: 'operation' }
@@ -306,14 +365,20 @@
id: 0,
series_id: undefined,
shop_id: undefined,
series_name: '',
shop_name: '',
allocator_shop_name: '',
base_commission: {
mode: 'fixed',
value: 0
},
enable_tier_commission: false,
tier_config: {
period_type: 'monthly',
tier_type: 'sales_count',
enable_one_time_commission: false,
one_time_commission_config: {
type: 'fixed',
trigger: 'single_recharge',
threshold: 0,
mode: 'fixed',
value: 0,
tiers: []
}
})
@@ -341,14 +406,27 @@
]
}
// 如果启用了梯度返佣,添加梯度返佣的验证规则
if (form.enable_tier_commission) {
baseRules['tier_config.period_type'] = [
{ required: true, message: '请选择周期类型', trigger: 'change' }
// 如果启用了一次性佣金,添加验证规则
if (form.enable_one_time_commission) {
baseRules['one_time_commission_config.type'] = [
{ required: true, message: '请选择一次性佣金类型', trigger: 'change' }
]
baseRules['tier_config.tier_type'] = [
{ required: true, message: '请选择梯度类型', trigger: 'change' }
baseRules['one_time_commission_config.trigger'] = [
{ required: true, message: '请选择触发条件', trigger: 'change' }
]
baseRules['one_time_commission_config.threshold'] = [
{ required: true, message: '请输入最低阈值', trigger: 'blur' }
]
// 固定类型验证
if (form.one_time_commission_config.type === 'fixed') {
baseRules['one_time_commission_config.mode'] = [
{ required: true, message: '请选择返佣模式', trigger: 'change' }
]
baseRules['one_time_commission_config.value'] = [
{ required: true, message: '请输入佣金值', trigger: 'blur' }
]
}
}
return baseRules
@@ -397,14 +475,14 @@
}
},
{
prop: 'enable_tier_commission',
label: '梯度返佣',
width: 100,
prop: 'enable_one_time_commission',
label: '一次性佣金',
width: 120,
formatter: (row: ShopSeriesAllocationResponse) => {
return h(
ElTag,
{ type: row.enable_tier_commission ? 'success' : 'info', size: 'small' },
() => (row.enable_tier_commission ? '已启用' : '未启用')
{ type: row.enable_one_time_commission ? 'success' : 'info', size: 'small' },
() => (row.enable_one_time_commission ? '已启用' : '未启用')
)
}
},
@@ -647,7 +725,8 @@
// 添加档位
const addTier = () => {
form.tier_config.tiers.push({
form.one_time_commission_config.tiers.push({
tier_type: 'sales_count',
threshold: 0,
mode: 'fixed',
value: 0
@@ -656,7 +735,7 @@
// 删除档位
const removeTier = (index: number) => {
form.tier_config.tiers.splice(index, 1)
form.one_time_commission_config.tiers.splice(index, 1)
}
// 显示新增/编辑对话框
@@ -668,21 +747,30 @@
form.id = row.id
form.series_id = row.series_id
form.shop_id = row.shop_id
form.series_name = row.series_name
form.shop_name = row.shop_name
form.allocator_shop_name = row.allocator_shop_name
form.base_commission = {
mode: row.base_commission.mode,
value: row.base_commission.value
}
form.enable_tier_commission = row.enable_tier_commission
if (row.enable_tier_commission && row.tier_config) {
form.tier_config = {
period_type: row.tier_config.period_type,
tier_type: row.tier_config.tier_type,
tiers: row.tier_config.tiers.map((t) => ({ ...t }))
form.enable_one_time_commission = row.enable_one_time_commission
if (row.enable_one_time_commission && row.one_time_commission_config) {
form.one_time_commission_config = {
type: row.one_time_commission_config.type,
trigger: row.one_time_commission_config.trigger,
threshold: row.one_time_commission_config.threshold,
mode: row.one_time_commission_config.mode || 'fixed',
value: row.one_time_commission_config.value || 0,
tiers: row.one_time_commission_config.tiers?.map((t) => ({ ...t })) || []
}
} else {
form.tier_config = {
period_type: 'monthly',
tier_type: 'sales_count',
form.one_time_commission_config = {
type: 'fixed',
trigger: 'single_recharge',
threshold: 0,
mode: 'fixed',
value: 0,
tiers: []
}
}
@@ -690,14 +778,20 @@
form.id = 0
form.series_id = undefined
form.shop_id = undefined
form.series_name = ''
form.shop_name = ''
form.allocator_shop_name = ''
form.base_commission = {
mode: 'fixed',
value: 0
}
form.enable_tier_commission = false
form.tier_config = {
period_type: 'monthly',
tier_type: 'sales_count',
form.enable_one_time_commission = false
form.one_time_commission_config = {
type: 'fixed',
trigger: 'single_recharge',
threshold: 0,
mode: 'fixed',
value: 0,
tiers: []
}
}
@@ -716,14 +810,20 @@
form.id = 0
form.series_id = undefined
form.shop_id = undefined
form.series_name = ''
form.shop_name = ''
form.allocator_shop_name = ''
form.base_commission = {
mode: 'fixed',
value: 0
}
form.enable_tier_commission = false
form.tier_config = {
period_type: 'monthly',
tier_type: 'sales_count',
form.enable_one_time_commission = false
form.one_time_commission_config = {
type: 'fixed',
trigger: 'single_recharge',
threshold: 0,
mode: 'fixed',
value: 0,
tiers: []
}
}
@@ -759,20 +859,22 @@
await formEl.validate(async (valid) => {
if (valid) {
// 验证梯度档位
if (form.enable_tier_commission) {
if (form.tier_config.tiers.length === 0) {
ElMessage.warning('启用梯度返佣时至少需要添加一个档位')
return
}
// 验证档位阈值递增
const thresholds = form.tier_config.tiers.map((t: any) => t.threshold)
for (let i = 1; i < thresholds.length; i++) {
if (thresholds[i] <= thresholds[i - 1]) {
ElMessage.warning('档位阈值必须递增')
// 验证一次性佣金配置
if (form.enable_one_time_commission) {
if (form.one_time_commission_config.type === 'tiered') {
if (form.one_time_commission_config.tiers.length === 0) {
ElMessage.warning('启用梯度类型时至少需要添加一个档位')
return
}
// 验证档位阈值递增
const thresholds = form.one_time_commission_config.tiers.map((t: any) => t.threshold)
for (let i = 1; i < thresholds.length; i++) {
if (thresholds[i] <= thresholds[i - 1]) {
ElMessage.warning('档位阈值必须递增')
return
}
}
}
}
@@ -783,15 +885,26 @@
mode: form.base_commission.mode,
value: form.base_commission.value
},
enable_tier_commission: form.enable_tier_commission
enable_one_time_commission: form.enable_one_time_commission
}
// 如果启用了梯度返佣,加入梯度配置
if (form.enable_tier_commission) {
data.tier_config = {
period_type: form.tier_config.period_type,
tier_type: form.tier_config.tier_type,
tiers: form.tier_config.tiers.map((t: any) => ({
// 如果启用了一次性佣金,加入配置
if (form.enable_one_time_commission) {
data.one_time_commission_config = {
type: form.one_time_commission_config.type,
trigger: form.one_time_commission_config.trigger,
threshold: form.one_time_commission_config.threshold
}
// 固定类型配置
if (form.one_time_commission_config.type === 'fixed') {
data.one_time_commission_config.mode = form.one_time_commission_config.mode
data.one_time_commission_config.value = form.one_time_commission_config.value
}
// 梯度类型配置
else if (form.one_time_commission_config.type === 'tiered') {
data.one_time_commission_config.tiers = form.one_time_commission_config.tiers.map((t: any) => ({
tier_type: t.tier_type,
threshold: t.threshold,
mode: t.mode,
value: t.value
@@ -864,4 +977,30 @@
align-items: center;
margin-bottom: 8px;
}
.info-row {
display: flex;
gap: 20px;
margin-bottom: 18px;
padding: 12px;
border-radius: 4px;
}
.info-item {
flex: 1;
display: flex;
align-items: center;
}
.info-label {
font-size: 14px;
margin-right: 8px;
white-space: nowrap;
}
.info-value {
font-size: 14px;
color: var(--art-primary);
font-weight: 500;
}
</style>