fetch(add): 订单管理-企业设备
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 3m30s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 3m30s
This commit is contained in:
382
src/views/package-management/my-packages/index.vue
Normal file
382
src/views/package-management/my-packages/index.vue
Normal file
@@ -0,0 +1,382 @@
|
||||
<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>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,9 @@
|
||||
<div class="package-series-page" id="table-full-screen">
|
||||
<!-- 搜索栏 -->
|
||||
<ArtSearchBar
|
||||
v-model:filter="formFilters"
|
||||
:items="formItems"
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
:show-expand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
@@ -17,8 +18,7 @@
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton type="primary" @click="handleSearch">搜索</ElButton>
|
||||
<ElButton type="success" @click="showAddDialog">新增</ElButton>
|
||||
<ElButton type="primary" @click="showDialog('add')">新增套餐系列</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
@@ -27,12 +27,11 @@
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.currentPage"
|
||||
:pageSize="pagination.pageSize"
|
||||
:data="seriesList"
|
||||
:currentPage="pagination.page"
|
||||
:pageSize="pagination.page_size"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
@selection-change="handleSelectionChange"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
@@ -41,57 +40,41 @@
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 新增套餐系列对话框 -->
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ElDialog
|
||||
v-model="addDialogVisible"
|
||||
title="新增套餐系列"
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增套餐系列' : '编辑套餐系列'"
|
||||
width="500px"
|
||||
align-center
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<ElForm ref="addFormRef" :model="addFormData" :rules="addRules" label-width="120px">
|
||||
<ElFormItem label="系列名称" prop="seriesName">
|
||||
<ElInput v-model="addFormData.seriesName" placeholder="请输入系列名称" clearable />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="包含套餐" prop="packageNames">
|
||||
<ElSelect
|
||||
v-model="addFormData.packageNames"
|
||||
placeholder="请选择要包含的套餐"
|
||||
style="width: 100%"
|
||||
multiple
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<ElFormItem label="系列编码" prop="series_code">
|
||||
<ElInput
|
||||
v-model="form.series_code"
|
||||
placeholder="请输入系列编码"
|
||||
:disabled="dialogType === 'edit'"
|
||||
clearable
|
||||
>
|
||||
<ElOption label="随意联畅玩年卡套餐" value="changwan_yearly" />
|
||||
<ElOption label="随意联畅玩月卡套餐" value="changwan_monthly" />
|
||||
<ElOption label="如意包年3G流量包" value="ruyi_3g" />
|
||||
<ElOption label="如意包月流量包" value="ruyi_monthly" />
|
||||
<ElOption label="Y-NB专享套餐" value="nb_special" />
|
||||
<ElOption label="NB-IoT基础套餐" value="nb_basic" />
|
||||
<ElOption label="100G全国流量月卡套餐" value="big_data_100g" />
|
||||
<ElOption label="200G超值流量包" value="big_data_200g" />
|
||||
<ElOption label="广电飞悦卡无预存50G" value="gdtv_50g" />
|
||||
<ElOption label="广电天翼卡" value="gdtv_tianyi" />
|
||||
</ElSelect>
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="系列名称" prop="series_name">
|
||||
<ElInput v-model="form.series_name" placeholder="请输入系列名称" clearable />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="系列描述" prop="description">
|
||||
<ElInput
|
||||
v-model="addFormData.description"
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
placeholder="请输入系列描述(可选)"
|
||||
:rows="3"
|
||||
maxlength="200"
|
||||
placeholder="请输入系列描述(可选)"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="addDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleAddSubmit" :loading="addLoading">
|
||||
确认新增
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)" :loading="submitLoading">
|
||||
提交
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -103,343 +86,328 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { ElTag, ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { PackageSeriesService } from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag, ElSwitch } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { PackageSeriesResponse } from '@/types/api'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import { SearchChangeParams, SearchFormItem } from '@/types'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import {
|
||||
CommonStatus,
|
||||
getStatusText,
|
||||
frontendStatusToApi,
|
||||
apiStatusToFrontend
|
||||
} from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'PackageSeries' })
|
||||
|
||||
const addDialogVisible = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const addLoading = ref(false)
|
||||
|
||||
// 定义表单搜索初始值
|
||||
const initialSearchState = {
|
||||
seriesName: ''
|
||||
}
|
||||
|
||||
// 响应式表单数据
|
||||
const formFilters = reactive({ ...initialSearchState })
|
||||
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
|
||||
// 表格实例引用
|
||||
const submitLoading = ref(false)
|
||||
const tableRef = ref()
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 选中的行数据
|
||||
const selectedRows = ref<any[]>([])
|
||||
|
||||
// 新增表单实例
|
||||
const addFormRef = ref<FormInstance>()
|
||||
|
||||
// 新增表单数据
|
||||
const addFormData = reactive({
|
||||
seriesName: '',
|
||||
packageNames: [] as string[],
|
||||
description: ''
|
||||
})
|
||||
|
||||
// 模拟数据
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
seriesName: '畅玩系列',
|
||||
operator: '张若暄',
|
||||
operationTime: '2025-11-08 10:30:00',
|
||||
status: '启用'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
seriesName: '如意系列',
|
||||
operator: '孔丽娟',
|
||||
operationTime: '2025-11-07 14:15:00',
|
||||
status: '启用'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
seriesName: 'NB专享',
|
||||
operator: '李佳音',
|
||||
operationTime: '2025-11-06 09:45:00',
|
||||
status: '禁用'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
seriesName: '大流量系列',
|
||||
operator: '赵强',
|
||||
operationTime: '2025-11-05 16:20:00',
|
||||
status: '启用'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
seriesName: '广电系列',
|
||||
operator: '张若暄',
|
||||
operationTime: '2025-11-04 11:30:00',
|
||||
status: '禁用'
|
||||
}
|
||||
]
|
||||
|
||||
// 重置表单
|
||||
const handleReset = () => {
|
||||
Object.assign(formFilters, { ...initialSearchState })
|
||||
pagination.currentPage = 1
|
||||
getPackageSeriesList()
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
series_name: '',
|
||||
status: undefined as number | undefined
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
console.log('搜索参数:', formFilters)
|
||||
pagination.currentPage = 1
|
||||
getPackageSeriesList()
|
||||
}
|
||||
// 搜索表单
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
|
||||
// 表单项变更处理
|
||||
const handleFormChange = (params: SearchChangeParams): void => {
|
||||
console.log('表单项变更:', params)
|
||||
}
|
||||
|
||||
// 表单配置项
|
||||
const formItems: SearchFormItem[] = [
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '系列名称',
|
||||
prop: 'seriesName',
|
||||
prop: 'series_name',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入系列名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择状态'
|
||||
},
|
||||
onChange: handleFormChange
|
||||
options: () => [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 2 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: '勾选', type: 'selection' },
|
||||
{ label: '系列名称', prop: 'seriesName' },
|
||||
{ label: '操作人', prop: 'operator' },
|
||||
{ label: '操作时间', prop: 'operationTime' },
|
||||
{ label: 'ID', prop: 'id' },
|
||||
{ label: '系列编码', prop: 'series_code' },
|
||||
{ label: '系列名称', prop: 'series_name' },
|
||||
{ label: '描述', prop: 'description' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '更新时间', prop: 'updated_at' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusType = (status: string) => {
|
||||
switch (status) {
|
||||
case '启用':
|
||||
return 'success'
|
||||
case '禁用':
|
||||
return 'danger'
|
||||
default:
|
||||
return 'info'
|
||||
}
|
||||
}
|
||||
// 表单验证规则
|
||||
const rules = reactive<FormRules>({
|
||||
series_code: [
|
||||
{ required: true, message: '请输入系列编码', trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
series_name: [
|
||||
{ required: true, message: '请输入系列名称', trigger: 'blur' },
|
||||
{ min: 1, max: 255, message: '长度在 1 到 255 个字符', trigger: 'blur' }
|
||||
],
|
||||
description: [{ max: 500, message: '描述不能超过 500 个字符', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 显示新增对话框
|
||||
const showAddDialog = () => {
|
||||
addDialogVisible.value = true
|
||||
// 重置表单
|
||||
if (addFormRef.value) {
|
||||
addFormRef.value.resetFields()
|
||||
}
|
||||
addFormData.seriesName = ''
|
||||
addFormData.packageNames = []
|
||||
addFormData.description = ''
|
||||
}
|
||||
// 表单数据
|
||||
const form = reactive<any>({
|
||||
id: 0,
|
||||
series_code: '',
|
||||
series_name: '',
|
||||
description: ''
|
||||
})
|
||||
|
||||
// 启用系列
|
||||
const enableSeries = (row: any) => {
|
||||
ElMessageBox.confirm(`确定要启用套餐系列"${row.seriesName}"吗?`, '启用确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success('启用成功')
|
||||
getPackageSeriesList()
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.info('已取消启用')
|
||||
})
|
||||
}
|
||||
|
||||
// 禁用系列
|
||||
const disableSeries = (row: any) => {
|
||||
ElMessageBox.confirm(`确定要禁用套餐系列"${row.seriesName}"吗?`, '禁用确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success('禁用成功')
|
||||
getPackageSeriesList()
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.info('已取消禁用')
|
||||
})
|
||||
}
|
||||
|
||||
// 删除系列
|
||||
const deleteSeries = (row: any) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要删除套餐系列"${row.seriesName}"吗?删除后将无法恢复。`,
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
getPackageSeriesList()
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.info('已取消删除')
|
||||
})
|
||||
}
|
||||
const seriesList = ref<PackageSeriesResponse[]>([])
|
||||
const dialogType = ref('add')
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{ type: 'selection' },
|
||||
{
|
||||
prop: 'seriesName',
|
||||
prop: 'id',
|
||||
label: 'ID',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: 'series_code',
|
||||
label: '系列编码',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'series_name',
|
||||
label: '系列名称',
|
||||
minWidth: 180
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'operator',
|
||||
label: '操作人',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'operationTime',
|
||||
label: '操作时间',
|
||||
width: 160
|
||||
prop: 'description',
|
||||
label: '描述',
|
||||
minWidth: 200
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row) => {
|
||||
return h(ElTag, { type: getStatusType(row.status) }, () => row.status)
|
||||
formatter: (row: PackageSeriesResponse) => {
|
||||
const frontendStatus = apiStatusToFrontend(row.status)
|
||||
return h(ElSwitch, {
|
||||
modelValue: frontendStatus,
|
||||
activeValue: CommonStatus.ENABLED,
|
||||
inactiveValue: CommonStatus.DISABLED,
|
||||
activeText: getStatusText(CommonStatus.ENABLED),
|
||||
inactiveText: getStatusText(CommonStatus.DISABLED),
|
||||
inlinePrompt: true,
|
||||
'onUpdate:modelValue': (val: string | number | boolean) =>
|
||||
handleStatusChange(row, val as number)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: PackageSeriesResponse) => formatDateTime(row.created_at)
|
||||
},
|
||||
{
|
||||
prop: 'updated_at',
|
||||
label: '更新时间',
|
||||
width: 180,
|
||||
formatter: (row: PackageSeriesResponse) => formatDateTime(row.updated_at)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 180,
|
||||
formatter: (row: any) => {
|
||||
const buttons = []
|
||||
|
||||
if (row.status === '启用') {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
text: '禁用',
|
||||
onClick: () => disableSeries(row)
|
||||
})
|
||||
)
|
||||
} else {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
text: '启用',
|
||||
onClick: () => enableSeries(row)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
formatter: (row: PackageSeriesResponse) => {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
text: '删除',
|
||||
type: 'edit',
|
||||
onClick: () => showDialog('edit', row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => deleteSeries(row)
|
||||
})
|
||||
)
|
||||
|
||||
return h('div', { class: 'operation-buttons' }, buttons)
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
getPackageSeriesList()
|
||||
getTableData()
|
||||
})
|
||||
|
||||
// 获取套餐系列列表
|
||||
const getPackageSeriesList = async () => {
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 模拟API调用
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
|
||||
const startIndex = (pagination.currentPage - 1) * pagination.pageSize
|
||||
const endIndex = startIndex + pagination.pageSize
|
||||
const paginatedData = mockData.slice(startIndex, endIndex)
|
||||
|
||||
tableData.value = paginatedData
|
||||
pagination.total = mockData.length
|
||||
loading.value = false
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
series_name: searchForm.series_name || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
const res = await PackageSeriesService.getPackageSeries(params)
|
||||
if (res.code === 0) {
|
||||
seriesList.value = res.data.items || []
|
||||
pagination.total = res.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取套餐系列列表失败:', error)
|
||||
console.error(error)
|
||||
ElMessage.error('获取套餐系列列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
getPackageSeriesList()
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, { ...initialSearchState })
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 处理表格行选择变化
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
selectedRows.value = selection
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 刷新表格
|
||||
const handleRefresh = () => {
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 处理表格分页变化
|
||||
const handleSizeChange = (newPageSize: number) => {
|
||||
pagination.pageSize = newPageSize
|
||||
getPackageSeriesList()
|
||||
pagination.page_size = newPageSize
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (newCurrentPage: number) => {
|
||||
pagination.currentPage = newCurrentPage
|
||||
getPackageSeriesList()
|
||||
pagination.page = newCurrentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 新增表单验证规则
|
||||
const addRules = reactive<FormRules>({
|
||||
seriesName: [
|
||||
{ required: true, message: '请输入系列名称', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '系列名称长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
packageNames: [{ required: true, message: '请选择要包含的套餐', trigger: 'change' }]
|
||||
})
|
||||
// 显示新增/编辑对话框
|
||||
const showDialog = (type: string, row?: PackageSeriesResponse) => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
|
||||
// 提交新增
|
||||
const handleAddSubmit = async () => {
|
||||
if (!addFormRef.value) return
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.series_code = row.series_code
|
||||
form.series_name = row.series_name
|
||||
form.description = row.description || ''
|
||||
} else {
|
||||
form.id = 0
|
||||
form.series_code = ''
|
||||
form.series_name = ''
|
||||
form.description = ''
|
||||
}
|
||||
|
||||
await addFormRef.value.validate((valid) => {
|
||||
// 重置表单验证状态
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
// 删除套餐系列
|
||||
const deleteSeries = (row: PackageSeriesResponse) => {
|
||||
ElMessageBox.confirm(`确定删除套餐系列 ${row.series_name} 吗?`, '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await PackageSeriesService.deletePackageSeries(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
await getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
addLoading.value = true
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
series_code: form.series_code,
|
||||
series_name: form.series_name,
|
||||
description: form.description || undefined
|
||||
}
|
||||
|
||||
// 模拟新增过程
|
||||
setTimeout(() => {
|
||||
ElMessage.success(
|
||||
`新增套餐系列成功!系列名称:${addFormData.seriesName},包含套餐:${addFormData.packageNames.length} 个`
|
||||
)
|
||||
addDialogVisible.value = false
|
||||
addLoading.value = false
|
||||
getPackageSeriesList()
|
||||
}, 2000)
|
||||
if (dialogType.value === 'add') {
|
||||
await PackageSeriesService.createPackageSeries(data)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
await PackageSeriesService.updatePackageSeries(form.id, data)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
formEl.resetFields()
|
||||
await getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 状态切换
|
||||
const handleStatusChange = async (row: PackageSeriesResponse, newFrontendStatus: number) => {
|
||||
const oldStatus = row.status
|
||||
const newApiStatus = frontendStatusToApi(newFrontendStatus)
|
||||
// 先更新UI(将后端状态转换)
|
||||
row.status = newApiStatus
|
||||
try {
|
||||
await PackageSeriesService.updatePackageSeriesStatus(row.id, newApiStatus)
|
||||
ElMessage.success('状态切换成功')
|
||||
} catch (error) {
|
||||
// 切换失败,恢复原状态
|
||||
row.status = oldStatus
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -447,16 +415,7 @@
|
||||
// 可以添加特定样式
|
||||
}
|
||||
|
||||
:deep(.operation-buttons) {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
867
src/views/package-management/series-assign/index.vue
Normal file
867
src/views/package-management/series-assign/index.vue
Normal file
@@ -0,0 +1,867 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="series-assign-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"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton type="primary" @click="showDialog('add')">新增系列分配</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="allocationList"
|
||||
:currentPage="pagination.page"
|
||||
:pageSize="pagination.page_size"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增系列分配' : '编辑系列分配'"
|
||||
width="650px"
|
||||
:close-on-click-modal="false"
|
||||
@closed="handleDialogClosed"
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="160px">
|
||||
<!-- 基本信息 -->
|
||||
<ElFormItem label="选择套餐系列" prop="series_id" v-if="dialogType === 'add'">
|
||||
<ElSelect
|
||||
v-model="form.series_id"
|
||||
placeholder="请选择套餐系列"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="searchSeries"
|
||||
:loading="seriesLoading"
|
||||
clearable
|
||||
>
|
||||
<ElOption
|
||||
v-for="series in seriesOptions"
|
||||
:key="series.id"
|
||||
:label="`${series.series_name} (${series.series_code})`"
|
||||
:value="series.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选择店铺" prop="shop_id" v-if="dialogType === 'add'">
|
||||
<ElSelect
|
||||
v-model="form.shop_id"
|
||||
placeholder="请选择店铺"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="searchShop"
|
||||
:loading="shopLoading"
|
||||
clearable
|
||||
>
|
||||
<ElOption
|
||||
v-for="shop in shopOptions"
|
||||
:key="shop.id"
|
||||
:label="shop.shop_name"
|
||||
:value="shop.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
<!-- 基础返佣配置 -->
|
||||
<ElDivider content-position="left">基础返佣配置</ElDivider>
|
||||
<ElFormItem label="返佣模式" prop="base_commission.mode">
|
||||
<ElRadioGroup v-model="form.base_commission.mode">
|
||||
<ElRadio value="fixed">固定金额</ElRadio>
|
||||
<ElRadio value="percent">百分比</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
:label="form.base_commission.mode === 'fixed' ? '返佣金额(分)' : '返佣百分比(千分比)'"
|
||||
prop="base_commission.value"
|
||||
>
|
||||
<ElInputNumber
|
||||
v-model="form.base_commission.value"
|
||||
:min="0"
|
||||
:controls="false"
|
||||
style="width: 100%"
|
||||
:placeholder="
|
||||
form.base_commission.mode === 'fixed'
|
||||
? '请输入固定返佣金额(分)'
|
||||
: '请输入返佣百分比的千分比(如200表示20%)'
|
||||
"
|
||||
/>
|
||||
<div class="form-tip">
|
||||
{{
|
||||
form.base_commission.mode === 'fixed'
|
||||
? '每笔交易返佣该固定金额(单位:分)'
|
||||
: '返佣百分比的千分比,如200表示20%,即每笔交易返佣 = 交易金额 × 20%'
|
||||
}}
|
||||
</div>
|
||||
</ElFormItem>
|
||||
|
||||
<!-- 梯度返佣配置 -->
|
||||
<ElDivider content-position="left">梯度返佣设置(可选)</ElDivider>
|
||||
<ElFormItem label="启用梯度返佣">
|
||||
<ElSwitch v-model="form.enable_tier_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>
|
||||
</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>
|
||||
<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>
|
||||
</div>
|
||||
<ElButton type="primary" @click="addTier">添加档位</ElButton>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)" :loading="submitLoading">
|
||||
提交
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { ShopSeriesAllocationService, PackageSeriesService, ShopService } from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElSwitch, ElTag } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type {
|
||||
ShopSeriesAllocationResponse,
|
||||
PackageSeriesResponse,
|
||||
ShopResponse
|
||||
} 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 {
|
||||
CommonStatus,
|
||||
getStatusText,
|
||||
frontendStatusToApi,
|
||||
apiStatusToFrontend
|
||||
} from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'SeriesAssign' })
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const seriesLoading = ref(false)
|
||||
const shopLoading = ref(false)
|
||||
const tableRef = ref()
|
||||
const formRef = ref<FormInstance>()
|
||||
const seriesOptions = ref<PackageSeriesResponse[]>([])
|
||||
const shopOptions = ref<ShopResponse[]>([])
|
||||
const searchSeriesOptions = ref<PackageSeriesResponse[]>([])
|
||||
const searchShopOptions = ref<ShopResponse[]>([])
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
shop_id: undefined as number | undefined,
|
||||
series_id: undefined as number | undefined,
|
||||
status: undefined as number | undefined
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems = computed<SearchFormItem[]>(() => [
|
||||
{
|
||||
label: '店铺',
|
||||
prop: 'shop_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: handleSearchShop,
|
||||
loading: shopLoading.value,
|
||||
placeholder: '请选择或搜索店铺'
|
||||
},
|
||||
options: () =>
|
||||
searchShopOptions.value.map((s) => ({
|
||||
label: s.shop_name,
|
||||
value: s.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '套餐系列',
|
||||
prop: 'series_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: handleSearchSeries,
|
||||
loading: seriesLoading.value,
|
||||
placeholder: '请选择或搜索套餐系列'
|
||||
},
|
||||
options: () =>
|
||||
searchSeriesOptions.value.map((s) => ({
|
||||
label: s.series_name,
|
||||
value: s.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择状态'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 2 }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: 'ID', prop: 'id' },
|
||||
{ label: '系列名称', prop: 'series_name' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '分配者店铺', prop: 'allocator_shop_name' },
|
||||
{ label: '基础返佣', prop: 'base_commission' },
|
||||
{ label: '梯度返佣', prop: 'enable_tier_commission' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<any>({
|
||||
id: 0,
|
||||
series_id: undefined,
|
||||
shop_id: undefined,
|
||||
base_commission: {
|
||||
mode: 'fixed',
|
||||
value: 0
|
||||
},
|
||||
enable_tier_commission: false,
|
||||
tier_config: {
|
||||
period_type: 'monthly',
|
||||
tier_type: 'sales_count',
|
||||
tiers: []
|
||||
}
|
||||
})
|
||||
|
||||
// 动态验证规则
|
||||
const rules = computed<FormRules>(() => {
|
||||
const baseRules: FormRules = {
|
||||
series_id: [{ required: true, message: '请选择套餐系列', trigger: 'change' }],
|
||||
shop_id: [{ required: true, message: '请选择店铺', trigger: 'change' }],
|
||||
'base_commission.mode': [{ required: true, message: '请选择返佣模式', trigger: 'change' }],
|
||||
'base_commission.value': [
|
||||
{ required: true, message: '请输入返佣值', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback(new Error('请输入返佣值'))
|
||||
} else if (value < 0) {
|
||||
callback(new Error('返佣值不能小于0'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 如果启用了梯度返佣,添加梯度返佣的验证规则
|
||||
if (form.enable_tier_commission) {
|
||||
baseRules['tier_config.period_type'] = [
|
||||
{ required: true, message: '请选择周期类型', trigger: 'change' }
|
||||
]
|
||||
baseRules['tier_config.tier_type'] = [
|
||||
{ required: true, message: '请选择梯度类型', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
return baseRules
|
||||
})
|
||||
|
||||
const allocationList = ref<ShopSeriesAllocationResponse[]>([])
|
||||
const dialogType = ref('add')
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'id',
|
||||
label: 'ID',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: 'series_name',
|
||||
label: '系列名称',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
prop: 'allocator_shop_name',
|
||||
label: '分配者店铺',
|
||||
minWidth: 150,
|
||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||
return row.allocator_shop_name || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'base_commission',
|
||||
label: '基础返佣',
|
||||
width: 150,
|
||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||
if (!row.base_commission) return '-'
|
||||
const { mode, value } = row.base_commission
|
||||
if (mode === 'fixed') {
|
||||
return `固定 ¥${(value / 100).toFixed(2)}`
|
||||
} else {
|
||||
return `百分比 ${(value / 10).toFixed(1)}%`
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'enable_tier_commission',
|
||||
label: '梯度返佣',
|
||||
width: 100,
|
||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||
return h(
|
||||
ElTag,
|
||||
{ type: row.enable_tier_commission ? 'success' : 'info', size: 'small' },
|
||||
() => (row.enable_tier_commission ? '已启用' : '未启用')
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||
const frontendStatus = apiStatusToFrontend(row.status)
|
||||
return h(ElSwitch, {
|
||||
modelValue: frontendStatus,
|
||||
activeValue: CommonStatus.ENABLED,
|
||||
inactiveValue: CommonStatus.DISABLED,
|
||||
activeText: getStatusText(CommonStatus.ENABLED),
|
||||
inactiveText: getStatusText(CommonStatus.DISABLED),
|
||||
inlinePrompt: true,
|
||||
'onUpdate:modelValue': (val: string | number | boolean) =>
|
||||
handleStatusChange(row, val as number)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: ShopSeriesAllocationResponse) => formatDateTime(row.created_at)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
onClick: () => showDialog('edit', row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => deleteAllocation(row)
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
loadSeriesOptions()
|
||||
loadShopOptions()
|
||||
loadSearchSeriesOptions()
|
||||
loadSearchShopOptions()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 加载店铺选项(用于新增对话框,默认加载10条)
|
||||
const loadShopOptions = async (shopName?: string) => {
|
||||
shopLoading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 10
|
||||
}
|
||||
if (shopName) {
|
||||
params.shop_name = shopName
|
||||
}
|
||||
const res = await ShopService.getShops(params)
|
||||
if (res.code === 0) {
|
||||
shopOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载店铺选项失败:', error)
|
||||
} finally {
|
||||
shopLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载搜索栏系列选项(默认加载10条)
|
||||
const loadSearchSeriesOptions = async () => {
|
||||
try {
|
||||
const res = await PackageSeriesService.getPackageSeries({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
status: 1
|
||||
})
|
||||
if (res.code === 0) {
|
||||
searchSeriesOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载搜索栏系列选项失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载搜索栏店铺选项(默认加载10条)
|
||||
const loadSearchShopOptions = async () => {
|
||||
try {
|
||||
const res = await ShopService.getShops({ page: 1, page_size: 10 })
|
||||
if (res.code === 0) {
|
||||
searchShopOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载搜索栏店铺选项失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索系列(用于新增对话框)
|
||||
const searchSeries = (query: string) => {
|
||||
if (query) {
|
||||
loadSeriesOptions(query)
|
||||
} else {
|
||||
loadSeriesOptions()
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索店铺(用于新增对话框)
|
||||
const searchShop = (query: string) => {
|
||||
if (query) {
|
||||
loadShopOptions(query)
|
||||
} else {
|
||||
loadShopOptions()
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索系列(用于搜索栏)
|
||||
const handleSearchSeries = async (query: string) => {
|
||||
if (!query) {
|
||||
loadSearchSeriesOptions()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await PackageSeriesService.getPackageSeries({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
series_name: query,
|
||||
status: 1
|
||||
})
|
||||
if (res.code === 0) {
|
||||
searchSeriesOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索系列失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索店铺(用于搜索栏)
|
||||
const handleSearchShop = async (query: string) => {
|
||||
if (!query) {
|
||||
loadSearchShopOptions()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await ShopService.getShops({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
shop_name: query
|
||||
})
|
||||
if (res.code === 0) {
|
||||
searchShopOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索店铺失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分配列表
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
shop_id: searchForm.shop_id || undefined,
|
||||
series_id: searchForm.series_id || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocations(params)
|
||||
if (res.code === 0) {
|
||||
allocationList.value = res.data.items || []
|
||||
pagination.total = res.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
ElMessage.error('获取系列分配列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, { ...initialSearchState })
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 刷新表格
|
||||
const handleRefresh = () => {
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 处理表格分页变化
|
||||
const handleSizeChange = (newPageSize: number) => {
|
||||
pagination.page_size = newPageSize
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (newCurrentPage: number) => {
|
||||
pagination.page = newCurrentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 添加档位
|
||||
const addTier = () => {
|
||||
form.tier_config.tiers.push({
|
||||
threshold: 0,
|
||||
mode: 'fixed',
|
||||
value: 0
|
||||
})
|
||||
}
|
||||
|
||||
// 删除档位
|
||||
const removeTier = (index: number) => {
|
||||
form.tier_config.tiers.splice(index, 1)
|
||||
}
|
||||
|
||||
// 显示新增/编辑对话框
|
||||
const showDialog = (type: string, row?: ShopSeriesAllocationResponse) => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.series_id = row.series_id
|
||||
form.shop_id = row.shop_id
|
||||
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 }))
|
||||
}
|
||||
} else {
|
||||
form.tier_config = {
|
||||
period_type: 'monthly',
|
||||
tier_type: 'sales_count',
|
||||
tiers: []
|
||||
}
|
||||
}
|
||||
} else {
|
||||
form.id = 0
|
||||
form.series_id = undefined
|
||||
form.shop_id = undefined
|
||||
form.base_commission = {
|
||||
mode: 'fixed',
|
||||
value: 0
|
||||
}
|
||||
form.enable_tier_commission = false
|
||||
form.tier_config = {
|
||||
period_type: 'monthly',
|
||||
tier_type: 'sales_count',
|
||||
tiers: []
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单验证状态
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
// 处理弹窗关闭事件
|
||||
const handleDialogClosed = () => {
|
||||
// 清除表单验证状态
|
||||
formRef.value?.clearValidate()
|
||||
// 重置表单数据
|
||||
form.id = 0
|
||||
form.series_id = undefined
|
||||
form.shop_id = undefined
|
||||
form.base_commission = {
|
||||
mode: 'fixed',
|
||||
value: 0
|
||||
}
|
||||
form.enable_tier_commission = false
|
||||
form.tier_config = {
|
||||
period_type: 'monthly',
|
||||
tier_type: 'sales_count',
|
||||
tiers: []
|
||||
}
|
||||
}
|
||||
|
||||
// 删除分配
|
||||
const deleteAllocation = (row: ShopSeriesAllocationResponse) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定删除系列 ${row.series_name} 对店铺 ${row.shop_name} 的分配吗?`,
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}
|
||||
)
|
||||
.then(async () => {
|
||||
try {
|
||||
await ShopSeriesAllocationService.deleteShopSeriesAllocation(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
await getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
// 验证梯度档位
|
||||
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('档位阈值必须递增')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const data: any = {
|
||||
base_commission: {
|
||||
mode: form.base_commission.mode,
|
||||
value: form.base_commission.value
|
||||
},
|
||||
enable_tier_commission: form.enable_tier_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) => ({
|
||||
threshold: t.threshold,
|
||||
mode: t.mode,
|
||||
value: t.value
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if (dialogType.value === 'add') {
|
||||
data.series_id = form.series_id
|
||||
data.shop_id = form.shop_id
|
||||
await ShopSeriesAllocationService.createShopSeriesAllocation(data)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
await ShopSeriesAllocationService.updateShopSeriesAllocation(form.id, data)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
formEl.resetFields()
|
||||
await getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 状态切换
|
||||
const handleStatusChange = async (
|
||||
row: ShopSeriesAllocationResponse,
|
||||
newFrontendStatus: number
|
||||
) => {
|
||||
const oldStatus = row.status
|
||||
const newApiStatus = frontendStatusToApi(newFrontendStatus)
|
||||
row.status = newApiStatus
|
||||
try {
|
||||
await ShopSeriesAllocationService.updateShopSeriesAllocationStatus(row.id, newApiStatus)
|
||||
ElMessage.success('状态切换成功')
|
||||
} catch (error) {
|
||||
row.status = oldStatus
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.series-assign-page {
|
||||
// 可以添加特定样式
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.tier-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tier-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user