修复bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 11m14s

This commit is contained in:
sexygoat
2026-04-08 10:19:25 +08:00
parent ff087abd44
commit b510b4539f
55 changed files with 2274 additions and 2612 deletions

View File

@@ -34,6 +34,7 @@
import { PackageManageService } from '@/api/modules'
import type { PackageResponse } from '@/types/api'
import { formatDateTime } from '@/utils/business/format'
import { getStatusLabel, getShelfStatusText } from '@/config/constants'
defineOptions({ name: 'PackageDetail' })
@@ -55,7 +56,7 @@
{
label: '套餐类型',
formatter: (_, data) => {
const typeMap = {
const typeMap: Record<string, string> = {
formal: '正式套餐',
addon: '附加套餐'
}
@@ -72,7 +73,7 @@
label: '套餐周期类型',
formatter: (_, data) => {
if (!data.calendar_type) return '-'
const typeMap = {
const typeMap: Record<string, string> = {
natural_month: '自然月',
by_day: '按天'
}
@@ -90,7 +91,7 @@
label: '流量重置周期',
formatter: (_, data) => {
if (!data.data_reset_cycle) return '-'
const cycleMap = {
const cycleMap: Record<string, string> = {
daily: '每日',
monthly: '每月',
yearly: '每年',
@@ -109,13 +110,13 @@
{
label: '状态',
formatter: (_, data) => {
return data.status === 1 ? '启用' : '禁用'
return getStatusLabel(data.status ?? 0)
}
},
{
label: '上架状态',
formatter: (_, data) => {
return data.shelf_status === 1 ? '上架' : '下架'
return getShelfStatusText(data.shelf_status ?? 0)
}
},
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },

View File

@@ -34,29 +34,15 @@
:pageSize="pagination.page_size"
:total="pagination.total"
:marginTop="10"
:row-class-name="getRowClassName"
:actions="getActions"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@row-contextmenu="handleRowContextMenu"
@cell-mouse-enter="handleCellMouseEnter"
@cell-mouse-leave="handleCellMouseLeave"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 鼠标悬浮提示 -->
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
<!-- 右键菜单 -->
<ArtMenuRight
ref="contextMenuRef"
:menu-items="contextMenuItems"
:menu-width="120"
@select="handleContextMenuSelect"
/>
<!-- 新增/编辑对话框 -->
<ElDialog
v-model="dialogVisible"
@@ -204,14 +190,20 @@
<!-- 真流量额度 -->
<ElRow :gutter="20">
<ElCol :span="12">
<ElFormItem label="真流量额度(MB)" prop="real_data_mb">
<ElFormItem label="真流量额度(GB)" prop="real_data_gb">
<ElInputNumber
v-model="form.real_data_mb"
v-model="realDataGb"
:min="0"
:precision="2"
:step="0.1"
:controls="false"
style="width: 100%"
placeholder="请输入真流量额度"
@change="handleRealDataChange"
/>
<div v-if="realDataGb" style="margin-top: 4px; font-size: 12px; color: var(--el-text-color-secondary)">
{{ realDataGb }}GB = {{ form.real_data_mb }}MB
</div>
</ElFormItem>
</ElCol>
<ElCol :span="12">
@@ -228,14 +220,20 @@
<!-- 虚流量额度和到期计时基准 -->
<ElRow :gutter="20">
<ElCol :span="12" v-if="form.enable_virtual_data">
<ElFormItem label="虚流量额度(MB)" prop="virtual_data_mb">
<ElFormItem label="虚流量额度(GB)" prop="virtual_data_gb">
<ElInputNumber
v-model="form.virtual_data_mb"
v-model="virtualDataGb"
:min="0"
:precision="2"
:step="0.1"
:controls="false"
style="width: 100%"
placeholder="请输入虚流量额度"
@change="handleVirtualDataChange"
/>
<div v-if="virtualDataGb" style="margin-top: 4px; font-size: 12px; color: var(--el-text-color-secondary)">
{{ virtualDataGb }}GB = {{ form.virtual_data_mb }}MB
</div>
</ElFormItem>
</ElCol>
<ElCol :span="12">
@@ -363,16 +361,13 @@
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { PackageManageService, PackageSeriesService } from '@/api/modules'
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElButton, ElInputNumber } from 'element-plus'
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElInputNumber } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import type { PackageResponse, SeriesSelectOption } from '@/types/api'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import { useTableContextMenu } from '@/composables/useTableContextMenu'
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
import { useUserStore } from '@/store/modules/user'
import { formatDateTime } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias'
import {
@@ -382,13 +377,17 @@
apiStatusToFrontend,
PACKAGE_TYPE_OPTIONS,
getPackageTypeLabel,
getPackageTypeTag
getPackageTypeTag,
STATUS_SELECT_OPTIONS,
SHELF_STATUS_SELECT_OPTIONS,
getShelfStatusText
} from '@/config/constants'
import { generatePackageCode } from '@/utils/codeGenerator'
defineOptions({ name: 'PackageList' })
const { hasAuth } = useAuth()
const userStore = useUserStore()
const router = useRouter()
const dialogVisible = ref(false)
@@ -397,8 +396,6 @@
const seriesLoading = ref(false)
const tableRef = ref()
const formRef = ref<FormInstance>()
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const currentRow = ref<PackageResponse | null>(null)
const seriesOptions = ref<SeriesSelectOption[]>([])
const searchSeriesOptions = ref<SeriesSelectOption[]>([])
@@ -419,14 +416,6 @@
})
// 使用表格右键菜单功能
const {
showContextMenuHint,
hintPosition,
getRowClassName,
handleCellMouseEnter,
handleCellMouseLeave
} = useTableContextMenu()
// 搜索表单初始值
const initialSearchState = {
package_name: '',
@@ -490,10 +479,7 @@
clearable: true,
placeholder: '请选择上架状态'
},
options: () => [
{ label: '上架', value: 1 },
{ label: '下架', value: 2 }
]
options: SHELF_STATUS_SELECT_OPTIONS
},
{
label: '状态',
@@ -503,10 +489,7 @@
clearable: true,
placeholder: '请选择状态'
},
options: () => [
{ label: '启用', value: 1 },
{ label: '禁用', value: 2 }
]
options: STATUS_SELECT_OPTIONS
}
])
@@ -559,7 +542,7 @@
baseRules.virtual_data_mb = [
{ required: true, message: '请输入虚流量额度', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
validator: (_rule: any, value: any, callback: any) => {
if (value && form.real_data_mb && value > form.real_data_mb) {
callback(new Error('虚流量额度不能超过真流量额度'))
} else {
@@ -602,6 +585,27 @@
description: ''
})
// GB 输入值(用于界面显示)
const realDataGb = ref<number>(0)
const virtualDataGb = ref<number>(0)
// GB 转 MB 处理
const handleRealDataChange = (value: number | null) => {
if (value === null || value === undefined) {
form.real_data_mb = 0
return
}
form.real_data_mb = Math.round(value * 1024)
}
const handleVirtualDataChange = (value: number | null) => {
if (value === null || value === undefined) {
form.virtual_data_mb = 0
return
}
form.virtual_data_mb = Math.round(value * 1024)
}
const packageList = ref<PackageResponse[]>([])
const dialogType = ref('add')
@@ -619,14 +623,19 @@
minWidth: 160,
showOverflowTooltip: true,
formatter: (row: PackageResponse) => {
const hasPermission = hasAuth('package:detail')
return h(
'span',
{
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
onClick: (e: MouseEvent) => {
e.stopPropagation()
handleNameClick(row)
}
style: hasPermission
? 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;'
: '',
onClick: hasPermission
? (e: MouseEvent) => {
e.stopPropagation()
handleNameClick(row)
}
: undefined
},
row.package_name
)
@@ -652,13 +661,29 @@
prop: 'real_data_mb',
label: '真流量',
width: 120,
formatter: (row: PackageResponse) => `${row.real_data_mb}MB`
formatter: (row: PackageResponse) => {
const mb = row.real_data_mb ?? 0
if (mb >= 1024) {
return `${(mb / 1024).toFixed(2)}GB`
}
return `${mb}MB`
}
},
{
prop: 'virtual_data_mb',
label: '虚流量',
width: 100,
formatter: (row: PackageResponse) => `${row.virtual_data_mb}MB`
width: 120,
formatter: (row: PackageResponse) => {
// 如果没有启用虚流量,显示 "-"
if (!row.enable_virtual_data) {
return '-'
}
const mb = row.virtual_data_mb ?? 0
if (mb >= 1024) {
return `${(mb / 1024).toFixed(2)}GB`
}
return `${mb}MB`
}
},
{
prop: 'virtual_ratio',
@@ -666,8 +691,10 @@
width: 120,
formatter: (row: PackageResponse) => {
// 如果启用虚流量且虚流量大于0计算比例
if (row.enable_virtual_data && row.virtual_data_mb > 0) {
const ratio = row.real_data_mb / row.virtual_data_mb
const virtualData = row.virtual_data_mb ?? 0
const realData = row.real_data_mb ?? 0
if (row.enable_virtual_data && virtualData > 0) {
const ratio = realData / virtualData
return ratio.toFixed(2)
}
// 否则返回 1.0
@@ -684,14 +711,16 @@
prop: 'cost_price',
label: '成本价',
width: 100,
formatter: (row: PackageResponse) => `¥${(row.cost_price / 100).toFixed(2)}`
formatter: (row: PackageResponse) => `¥${((row.cost_price ?? 0) / 100).toFixed(2)}`
},
{
prop: 'suggested_retail_price',
label: '建议售价',
width: 100,
formatter: (row: PackageResponse) =>
row.suggested_retail_price ? `¥${(row.suggested_retail_price / 100).toFixed(2)}` : '-'
row.suggested_retail_price
? `¥${((row.suggested_retail_price ?? 0) / 100).toFixed(2)}`
: '-'
},
{
prop: 'shelf_status',
@@ -700,8 +729,8 @@
formatter: (row: PackageResponse) => {
return h(ElSwitch, {
modelValue: row.shelf_status === 1,
activeText: '上架',
inactiveText: '下架',
activeText: getShelfStatusText(1),
inactiveText: getShelfStatusText(2),
inlinePrompt: true,
disabled: !hasAuth('package:update_away'),
'onUpdate:modelValue': (val: string | number | boolean) =>
@@ -714,7 +743,7 @@
label: '状态',
width: 100,
formatter: (row: PackageResponse) => {
const frontendStatus = apiStatusToFrontend(row.status)
const frontendStatus = apiStatusToFrontend(row.status ?? 0)
return h(ElSwitch, {
modelValue: frontendStatus,
activeValue: CommonStatus.ENABLED,
@@ -736,33 +765,29 @@
}
])
// 右键菜单项配置
const contextMenuItems = computed((): MenuItemType[] => {
const items: MenuItemType[] = []
// 操作列配置
const getActions = (row: PackageResponse) => {
const actions: any[] = []
if (hasAuth('package:edit')) {
items.push({
key: 'edit',
label: '编辑'
})
actions.push({ label: '编辑', handler: () => showDialog('edit', row), type: 'primary' })
}
if (hasAuth('package:update_retail_price')) {
items.push({
key: 'update-retail-price',
label: '修改零售价'
// 只有代理账号(user_type=3)才显示修改零售价
if (hasAuth('package:update_retail_price') && userStore.getUserInfo.user_type === 3) {
actions.push({
label: '修改零售价',
handler: () => showRetailPriceDialog(row),
type: 'primary'
})
}
if (hasAuth('package:delete')) {
items.push({
key: 'delete',
label: '删除'
})
actions.push({ label: '删除', handler: () => deletePackage(row), type: 'danger' })
}
return items
})
return actions
}
// 监听虚流量开关变化,关闭时重置虚流量额度
watch(
@@ -950,7 +975,10 @@
form.expiry_base = row.expiry_base || 'from_activation'
form.real_data_mb = row.real_data_mb || 0
form.virtual_data_mb = row.virtual_data_mb || 0
form.cost_price = row.cost_price / 100 // 分转换为元显示
// MB 转换为 GB 显示
realDataGb.value = form.real_data_mb ? Number((form.real_data_mb / 1024).toFixed(2)) : 0
virtualDataGb.value = form.virtual_data_mb ? Number((form.virtual_data_mb / 1024).toFixed(2)) : 0
form.cost_price = (row.cost_price ?? 0) / 100 // 分转换为元显示
form.suggested_retail_price = row.suggested_retail_price
? row.suggested_retail_price / 100
: undefined
@@ -969,6 +997,9 @@
form.expiry_base = 'from_activation'
form.real_data_mb = 0
form.virtual_data_mb = 0
// 重置 GB 值
realDataGb.value = 0
virtualDataGb.value = 0
form.cost_price = 0
form.suggested_retail_price = undefined
form.description = ''
@@ -1008,6 +1039,9 @@
form.expiry_base = 'from_activation'
form.real_data_mb = 0
form.virtual_data_mb = 0
// 重置 GB 值
realDataGb.value = 0
virtualDataGb.value = 0
form.cost_price = 0
form.suggested_retail_price = undefined
form.description = ''
@@ -1147,20 +1181,12 @@
}
}
// 处理表格行右键菜单
const handleRowContextMenu = (row: PackageResponse, column: any, event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
currentRow.value = row
contextMenuRef.value?.show(event)
}
// 显示零售价修改对话框
const showRetailPriceDialog = (row: PackageResponse) => {
retailPriceForm.id = row.id
retailPriceForm.package_name = row.package_name
// 将分转换为元显示
retailPriceForm.retail_price = row.retail_price ? row.retail_price / 100 : 0
retailPriceForm.retail_price = row.suggested_retail_price ? row.suggested_retail_price / 100 : 0
retailPriceDialogVisible.value = true
}
@@ -1177,7 +1203,7 @@
if (res.code === 0) {
ElMessage.success('零售价修改成功')
retailPriceDialogVisible.value = false
loadPackageList()
await getTableData()
}
} catch (error: any) {
console.error('修改零售价失败:', error)
@@ -1192,34 +1218,9 @@
retailPriceDialogVisible.value = false
retailPriceFormRef.value?.resetFields()
}
// 处理右键菜单选择
const handleContextMenuSelect = (item: MenuItemType) => {
if (!currentRow.value) return
switch (item.key) {
case 'edit':
showDialog('edit', currentRow.value)
break
case 'update-retail-price':
showRetailPriceDialog(currentRow.value)
break
case 'delete':
deletePackage(currentRow.value)
break
}
}
</script>
<style lang="scss" scoped>
:deep(.el-table__row.table-row-with-context-menu) {
cursor: pointer;
}
.package-list-page {
// 可以添加特定样式
}
.dialog-footer {
text-align: right;
}