<template>
|
<div class="price-manage-container">
|
<div class="page-header">
|
<div class="bar-title">
|
{{ productDetail?.name || '-' }} <span class="status" :class="statusClass">【{{ shelfStatus }}】</span>
|
</div>
|
</div>
|
|
<el-card class="detail-card" shadow="never">
|
<template #header>
|
<div class="card-header">
|
<div class="header-left">
|
<el-icon class="section-icon"><Goods /></el-icon>
|
<span>产品基本信息</span>
|
</div>
|
<div class="id-info">产品ID:{{ currentProductId || '未提供' }}</div>
|
</div>
|
</template>
|
|
<el-descriptions v-if="productDetail" :column="2" border>
|
<el-descriptions-item label="产品名称" label-width="10%">{{ productDetail.name }}</el-descriptions-item>
|
<el-descriptions-item label="提报单位" label-width="10%">{{ productDetail.submissionUnit }}</el-descriptions-item>
|
<el-descriptions-item label="提报人" label-width="10%">{{ productDetail.createBy }}</el-descriptions-item>
|
<el-descriptions-item label="行业领域" label-width="10%">{{ productDetail.industrialChainName }}</el-descriptions-item>
|
<el-descriptions-item label="单位工程" label-width="10%">{{ productDetail.importantAreaName }}</el-descriptions-item>
|
<el-descriptions-item label="产业阶段" label-width="10%">{{ productDetail.businessProcessName }}</el-descriptions-item>
|
<el-descriptions-item label="产品类型"label-width="10%">{{ productDetail.typeName }}</el-descriptions-item>
|
<el-descriptions-item label="产品简介" :span="3">
|
<div class="intro">{{ productDetail.describe }}</div>
|
</el-descriptions-item>
|
</el-descriptions>
|
<el-empty v-else description="未找到该产品的详情" />
|
</el-card>
|
|
<div class="content-area">
|
<el-card class="price-card" shadow="never">
|
<template #header>
|
<div class="card-header">
|
<span>价格列表</span>
|
<div class="header-actions">
|
<el-button type="primary" @click="handleAddPrice">添加价格</el-button>
|
</div>
|
</div>
|
</template>
|
<div class="pricing-cards-container" v-if="priceList.length > 0">
|
<div
|
v-for="group in priceList"
|
:key="group.suiteName"
|
class="pricing-group"
|
>
|
<div class="group-header">{{ group.suiteName }}</div>
|
<div class="pricing-cards-wrapper">
|
<div
|
v-for="pricing in group.items"
|
:key="pricing.id"
|
class="pricing-card"
|
:class="{
|
'pricing-card-enabled': pricing.enableStatus === 'ENABLED',
|
'pricing-card-disabled': pricing.enableStatus === 'DISABLED'
|
}"
|
>
|
<div class="pricing-card-table">
|
<div class="pricing-row">
|
<div class="pricing-cell label-cell">销售形式</div>
|
<div class="pricing-cell value-cell">{{ getSalesFormText(pricing.salesForm) || '-' }}</div>
|
<div class="pricing-cell label-cell">客户对象</div>
|
<div class="pricing-cell value-cell">{{ getCustomerObjectText(pricing.customerObject) || '-' }}</div>
|
</div>
|
<div class="pricing-row">
|
<div class="pricing-cell label-cell">账户数量</div>
|
<div class="pricing-cell value-cell">{{ pricing.accountQuantityUnlimited ? '不限' : (pricing.accountQuantity || '-') }}</div>
|
<div class="pricing-cell label-cell">并发节点数</div>
|
<div class="pricing-cell value-cell">{{ pricing.concurrentNodeQuantityUnlimited ? '不限' : (pricing.concurrentNodeQuantity || '-') }}</div>
|
</div>
|
<div class="pricing-row">
|
<div class="pricing-cell label-cell">购买方式</div>
|
<div class="pricing-cell value-cell purchase-methods-cell">
|
<div class="method-item">
|
<span class="method-label">积分:</span>
|
<span class="method-value points">{{ pricing.priceSettings?.includes('POINTS') ? (pricing.pointsPrice || '-') + '/' + getPriceUnitText(pricing.priceUnit) : '-' }}</span>
|
</div>
|
<div class="method-item" v-if="pricing.priceSettings?.includes('CURRENCY')">
|
<span class="method-label">货币:</span>
|
<span class="method-value currency">{{ (pricing.currencyPrice || '-') + '/' + getPriceUnitText(pricing.priceUnit) }}</span>
|
</div>
|
<div class="method-item" v-if="pricing.priceSettings?.includes('AGREEMENT')">
|
<span class="method-label">协议:</span>
|
<span class="method-value agreement">{{ '/' + getPriceUnitText(pricing.priceUnit) }}</span>
|
</div>
|
<div class="method-item" v-if="pricing.priceSettings?.includes('FREE')">
|
<span class="method-label">免费:</span>
|
<span class="method-value free">{{ '/' + getPriceUnitText(pricing.priceUnit) }}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<div class="pricing-status-tag">
|
<el-tag :type="pricing.enableStatus === 'ENABLED' ? 'success' : 'danger'" size="small" effect="dark">
|
当前状态:{{ getEnableStatusText(pricing.enableStatus) }}
|
</el-tag>
|
</div>
|
|
<div class="pricing-card-actions">
|
<span class="action-link edit-link" @click="handleEdit(pricing)">编辑</span>
|
<span class="action-link delete-link" @click="handleDelete(pricing)">删除</span>
|
<span
|
class="action-link"
|
:class="pricing.enableStatus === 'ENABLED' ? 'disable-link' : 'enable-link'"
|
@click="handleToggleEnableStatus(pricing)"
|
>
|
<el-icon>
|
<Lock v-if="pricing.enableStatus === 'ENABLED'" />
|
<Unlock v-else />
|
</el-icon>
|
{{ pricing.enableStatus === 'ENABLED' ? '停用' : '启用' }}
|
</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div v-else class="empty-pricing">
|
<el-empty description="暂无定价信息" />
|
</div>
|
</el-card>
|
</div>
|
|
<!-- 新增/编辑价格弹窗 -->
|
<el-dialog
|
v-model="dialogVisible"
|
:title="isEditMode ? '编辑价格' : '新增价格'"
|
width="960px"
|
destroy-on-close
|
>
|
<el-form
|
ref="formRef"
|
:model="formData"
|
:rules="formRules"
|
label-width="80px"
|
label-position="left"
|
status-icon
|
size="large"
|
class="price-form"
|
>
|
|
<el-form-item label="产品套件" prop="productSuite">
|
<el-select v-model="formData.productSuite" placeholder="请选择" style="width: 60%">
|
<el-option v-for="opt in productSuiteOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="客户对象" prop="customerObject">
|
<el-radio-group v-model="formData.customerObject">
|
<el-radio v-for="opt in customerObjectOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
<el-form-item label="销售形式" prop="salesForm" class="sales-form-item">
|
<el-radio-group v-model="formData.salesForm" class="sales-form-group">
|
<el-radio v-for="opt in salesFormOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
|
|
<el-form-item label="账户数量" prop="accountQuantity">
|
<div class="quantity-input-group">
|
<el-input-number v-model="formData.accountQuantity" :min="1" :controls="false" style="width: 180px" :disabled="formData.accountQuantityUnlimited" />
|
<el-checkbox v-model="formData.accountQuantityUnlimited" style="margin-left: 6px">不限</el-checkbox>
|
</div>
|
</el-form-item>
|
|
<el-form-item label="并发节点" prop="concurrentNodeQuantity">
|
<div class="quantity-input-group">
|
<el-input-number v-model="formData.concurrentNodeQuantity" :min="1" :controls="false" style="width: 180px" :disabled="formData.concurrentNodeQuantityUnlimited" />
|
<el-checkbox v-model="formData.concurrentNodeQuantityUnlimited" style="margin-left: 6px">不限</el-checkbox>
|
</div>
|
</el-form-item>
|
|
|
<el-form-item label="价格设置" prop="priceSettings">
|
<div class="price-settings">
|
<div class="price-setting-item">
|
<el-checkbox label="POINTS" :disabled="formData.priceSettings.includes('FREE')" v-model="checkboxPoints">积分</el-checkbox>
|
<el-input-number v-if="formData.priceSettings.includes('POINTS')" v-model="formData.pointsPrice" :min="0" :precision="2" :controls="false" placeholder="积分金额" style="width: 120px; margin-left: 6px" />
|
</div>
|
<div class="price-setting-item">
|
<el-checkbox label="CURRENCY" :disabled="formData.priceSettings.includes('FREE')" v-model="checkboxCurrency">货币</el-checkbox>
|
<el-input-number v-if="formData.priceSettings.includes('CURRENCY')" v-model="formData.currencyPrice" :min="0" :precision="2" :controls="false" placeholder="货币金额" style="width: 120px; margin-left: 6px" />
|
</div>
|
<div class="price-setting-item">
|
<el-checkbox label="AGREEMENT" :disabled="formData.priceSettings.includes('FREE')" v-model="checkboxAgreement">协议</el-checkbox>
|
</div>
|
<div class="price-setting-item">
|
<el-checkbox label="FREE" :disabled="formData.priceSettings.length > 0 && !formData.priceSettings.includes('FREE')" v-model="checkboxFree">免费</el-checkbox>
|
</div>
|
</div>
|
</el-form-item>
|
|
|
|
<el-form-item label="价格单位" prop="priceUnit">
|
<el-select v-model="formData.priceUnit" placeholder="请选择" style="width: 100%">
|
<el-option v-for="opt in priceUnitOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
</el-select>
|
</el-form-item>
|
|
<el-form-item label="启用状态" prop="enableStatus">
|
<el-radio-group v-model="formData.enableStatus">
|
<el-radio v-for="opt in enableStatusOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
|
|
<el-form-item label="备注">
|
<el-input type="textarea" v-model="formData.remark" :rows="3" placeholder="请输入备注" />
|
</el-form-item>
|
</el-form>
|
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button type="primary" :loading="saving" @click="handleSave">保 存</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, onMounted, watch, computed, reactive } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { useRoute } from 'vue-router'
|
import { Goods, Lock, Unlock } from '@element-plus/icons-vue'
|
import ProductPriceViewer from '@/views/productManage/productPriceViewer/index.vue'
|
import productPricingApi from '@/api/productPricingApi'
|
import productApi from '@/api/productApi'
|
|
|
interface ProductDetail {
|
id: string
|
name: string
|
submissionUnit: string
|
createBy: string
|
industrialChainName: string
|
importantAreaName: string
|
businessProcessName: string
|
typeName: string
|
describe: string
|
shelfStatus?: '待上架' | '已上架' | '已下架'
|
listingStatusName?: ''
|
}
|
|
interface PriceItem {
|
id: number | null
|
productId: string | undefined
|
productSuite: string
|
salesForm: 'BUYOUT' | 'LEASE' | 'PRIVATE_INCREMENT' | 'PUBLIC_INCREMENT' | 'OTA' | 'CLOUD' | 'RESOURCE_PACKAGE' | 'PERSONAL'
|
customerObject: 'ENTERPRISE' | 'PERSONAL' | 'PROJECT_DEPARTMENT'
|
accountQuantity: number | ''
|
accountQuantityUnlimited: boolean
|
concurrentNodeQuantity: number | ''
|
concurrentNodeQuantityUnlimited: boolean
|
priceSettings: Array<'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE'>
|
pointsPrice: number | ''
|
currencyPrice: number | ''
|
priceUnit: '' | 'SET' | 'SET_PER_YEAR' | 'YEAR'
|
enableStatus: 'ENABLED' | 'DISABLED'
|
authorizationStartTime?: string
|
authorizationEndTime?: string
|
remark?: string
|
}
|
|
const route = useRoute()
|
|
// 兼容 path 参数 productId 与 query 参数 id/productId
|
const currentProductId = computed<string | undefined>(() => {
|
return (route.params.productId as string) || (route.query.productId as string) || (route.query.id as string)
|
})
|
|
const productDetail = ref<ProductDetail | null>(null)
|
const shelfStatus = computed(() => productDetail.value?.listingStatusName || '待上架')
|
const statusClass = computed(() => {
|
switch (shelfStatus.value) {
|
case '已上架':
|
return 'status-online'
|
case '待上架':
|
return 'status-pending'
|
case '已下架':
|
return 'status-offline'
|
default:
|
return 'status-default'
|
}
|
})
|
|
// 模拟产品详情数据源
|
// const mockProductMap: Record<string, ProductDetail> = {
|
// '1': {
|
// id: '1',
|
// name: '数字化产品A',
|
// submitUnit: '中交一公局',
|
// submitter: '张三',
|
// industry: '交通基础设施',
|
// projectUnit: '某高速公路工程',
|
// industryStage: '应用阶段',
|
// productType: '软件/平台',
|
// description: '本产品定位为以建设期BIM数字资产作为数字底盘,结合项目运营维保需求的实时性、交互性、便捷性的三维可视化运维管理系统。系统提供项目数字化、智能化运维管理功能,能够解决建筑运行维护管理中的实际问题,实现信息快速整合与查询、信息有效共享与传递,提升项目综合管理与维护水平。',
|
// shelfStatus: '待上架'
|
// },
|
// '2': {
|
// id: '2',
|
// name: '数字化产品B',
|
// submitUnit: '中交二航局',
|
// submitter: '李四',
|
// industry: '市政工程',
|
// projectUnit: '智慧管廊项目',
|
// industryStage: '研发阶段',
|
// productType: '硬件/传感',
|
// description: '面向城市管廊监测的传感设备与采集网关,支持边缘计算与远程运维。',
|
// shelfStatus: '已上架'
|
// }
|
// }
|
|
const loading = ref(false)
|
const priceList = ref<any[]>([])
|
|
// 弹窗与表单
|
const dialogVisible = ref(false)
|
const showPriceViewer = ref(false)
|
const isEditMode = ref(false)
|
const saving = ref(false)
|
const formRef = ref()
|
const formData = reactive<PriceItem>({
|
id: null,
|
productId: undefined,
|
productSuite: '',
|
salesForm: 'BUYOUT',
|
customerObject: 'ENTERPRISE',
|
accountQuantity: 1,
|
accountQuantityUnlimited: false,
|
concurrentNodeQuantity: 1,
|
concurrentNodeQuantityUnlimited: false,
|
priceSettings: [],
|
pointsPrice: '',
|
currencyPrice: '',
|
priceUnit: '',
|
enableStatus: 'ENABLED',
|
authorizationStartTime: undefined,
|
authorizationEndTime: undefined,
|
remark: ''
|
})
|
|
// 选项
|
const productSuiteOptions = [
|
{ label: '企业私有SaaS版许可', value: '企业私有SaaS版许可' },
|
{ label: '企业私有SaaS版OTA服务', value: '企业私有SaaS版OTA服务' },
|
{ label: '企业私有SaaS版用户增量包', value: '企业私有SaaS版用户增量包' },
|
{ label: '企业公有SaaS版许可', value: '企业公有SaaS版许可' },
|
{ label: '企业公有SaaS版OTA服务', value: '企业公有SaaS版OTA服务' },
|
{ label: '企业公有SaaS版用户增量包', value: '企业公有SaaS版用户增量包' },
|
{ label: '个人公有SaaS化许可', value: '个人公有SaaS化许可' },
|
{ label: 'web软件', value: 'web软件' },
|
{ label: '桌面软件', value: '桌面软件' }
|
]
|
const salesFormOptions = [
|
{ label: '买断', value: 'BUYOUT' },
|
{ label: '租赁', value: 'LEASE' },
|
{ label: '私有增量包', value: 'PRIVATE_INCREMENT' },
|
{ label: '共有增量包', value: 'PUBLIC_INCREMENT' },
|
{ label: 'OTA服务', value: 'OTA' },
|
{ label: '云服务', value: 'CLOUD' },
|
{ label: '资源包', value: 'RESOURCE_PACKAGE' },
|
{ label: '个人', value: 'PERSONAL' }
|
]
|
const customerObjectOptions = [
|
{ label: '企业', value: 'ENTERPRISE' },
|
{ label: '个人', value: 'PERSONAL' },
|
{ label: '项目部', value: 'PROJECT_DEPARTMENT' }
|
]
|
const priceUnitOptions = [
|
{ label: '套', value: 'SET' },
|
{ label: '套/年', value: 'SET_PER_YEAR' },
|
{ label: '年', value: 'YEAR' }
|
]
|
const enableStatusOptions = [
|
{ label: '启用', value: 'ENABLED' },
|
{ label: '停用', value: 'DISABLED' }
|
]
|
|
// 价格设置复选框的双向绑定辅助(方便互斥逻辑)
|
const checkboxPoints = computed({
|
get: () => formData.priceSettings.includes('POINTS'),
|
set: (val: boolean) => updatePriceSettings('POINTS', val)
|
})
|
const checkboxCurrency = computed({
|
get: () => formData.priceSettings.includes('CURRENCY'),
|
set: (val: boolean) => updatePriceSettings('CURRENCY', val)
|
})
|
const checkboxAgreement = computed({
|
get: () => formData.priceSettings.includes('AGREEMENT'),
|
set: (val: boolean) => updatePriceSettings('AGREEMENT', val)
|
})
|
const checkboxFree = computed({
|
get: () => formData.priceSettings.includes('FREE'),
|
set: (val: boolean) => updatePriceSettings('FREE', val)
|
})
|
|
function updatePriceSettings(key: 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE', checked: boolean) {
|
const has = formData.priceSettings.includes(key)
|
if (checked && !has) {
|
// 若选择 FREE,清空其他
|
if (key === 'FREE') {
|
formData.priceSettings = ['FREE']
|
formData.pointsPrice = ''
|
formData.currencyPrice = ''
|
return
|
}
|
// 若此前有 FREE,去掉 FREE
|
formData.priceSettings = formData.priceSettings.filter(k => k !== 'FREE')
|
formData.priceSettings.push(key)
|
} else if (!checked && has) {
|
formData.priceSettings = formData.priceSettings.filter(k => k !== key)
|
}
|
}
|
|
// 校验规则
|
const formRules = {
|
productSuite: [{ required: true, message: '请选择产品套件', trigger: 'change' }],
|
salesForm: [{ required: true, message: '请选择销售形式', trigger: 'change' }],
|
customerObject: [{ required: true, message: '请选择客户对象', trigger: 'change' }],
|
accountQuantity: [{
|
required: true,
|
validator: (_: any, value: any, cb: any) => {
|
if (!formData.accountQuantityUnlimited && (!value || value <= 0)) cb(new Error('请输入账户数量'))
|
else cb()
|
},
|
trigger: 'blur'
|
}],
|
concurrentNodeQuantity: [{
|
required: true,
|
validator: (_: any, value: any, cb: any) => {
|
if (!formData.concurrentNodeQuantityUnlimited && (!value || value <= 0)) cb(new Error('请输入并发节点数量'))
|
else cb()
|
},
|
trigger: 'blur'
|
}],
|
priceSettings: [{
|
required: true,
|
validator: (_: any, value: any[], cb: any) => {
|
if (!value || value.length === 0) return cb(new Error('请至少选择一种价格设置'))
|
if (value.includes('FREE') && value.length > 1) return cb(new Error('免费选项不能与其他选项同时选择'))
|
if (value.includes('POINTS') && (formData.pointsPrice === '' || formData.pointsPrice === undefined)) return cb(new Error('请输入积分金额'))
|
if (value.includes('CURRENCY') && (formData.currencyPrice === '' || formData.currencyPrice === undefined)) return cb(new Error('请输入货币金额'))
|
cb()
|
},
|
trigger: 'change'
|
}],
|
priceUnit: [{ required: true, message: '请选择价格单位', trigger: 'change' }],
|
enableStatus: [{ required: true, message: '请选择启用状态', trigger: 'change' }],
|
// 已移除开始/结束时间字段的设置与校验
|
} as any
|
|
// 下列函数仅用于演示卡片视图标签文本
|
const getSalesFormText = (salesForm?: string) => {
|
const map: Record<string, string> = {
|
BUYOUT: '买断',
|
LEASE: '租赁',
|
PRIVATE_INCREMENT: '私有增量包',
|
PUBLIC_INCREMENT: '共有增量包',
|
OTA: 'OTA服务',
|
CLOUD: '云服务',
|
RESOURCE_PACKAGE: '资源包',
|
PERSONAL: '个人',
|
}
|
return map[salesForm || ''] || salesForm || '-'
|
}
|
const getCustomerObjectText = (customerObject?: string) => {
|
const map: Record<string, string> = { ENTERPRISE: '企业', PERSONAL: '个人', PROJECT_DEPARTMENT: '项目部' }
|
return map[customerObject || ''] || customerObject || '-'
|
}
|
const getEnableStatusText = (status?: string) => {
|
const map: Record<string, string> = { ENABLED: '启用', DISABLED: '停用' }
|
return map[status || ''] || status || '-'
|
}
|
|
const getPriceUnitText = (unit?: string) => {
|
const map: Record<string, string> = {
|
SET: '套',
|
SET_PER_YEAR: '套/年',
|
YEAR: '年'
|
}
|
return map[unit || ''] || unit || ''
|
}
|
|
async function fetchProductDetail(productId?: string) {
|
if (!productId) {
|
productDetail.value = null
|
ElMessage.warning('未提供产品ID,无法加载产品详情')
|
return
|
}
|
loading.value = true
|
try {
|
// productDetail.value = mockProductMap[productId] || null
|
const data = {
|
id: productId
|
}
|
const detailRes: any = await productApi.getProductById(data)
|
if (detailRes?.code === 200) {
|
productDetail.value = detailRes.data || null
|
} else {
|
productDetail.value = null
|
ElMessage.error(detailRes?.msg || '获取产品详情失败')
|
}
|
await loadPricingList(productId)
|
} catch (e) {
|
productDetail.value = null
|
ElMessage.error('获取产品详情失败')
|
} finally {
|
loading.value = false
|
}
|
}
|
|
function mapSalesFormToCN(val: string) {
|
const map: Record<string, string> = {
|
BUYOUT: '买断',
|
LEASE: '租赁',
|
PRIVATE_INCREMENT: '私有增包量',
|
PUBLIC_INCREMENT: '公有增包量',
|
OTA: 'OTA服务',
|
CLOUD: '云服务',
|
RESOURCE_PACKAGE: '资源包',
|
PERSONAL: '个人',
|
}
|
return map[val] || val
|
}
|
|
function mapSalesFormFromCN(val: string) {
|
const map: Record<string, string> = {
|
'买断': 'BUYOUT',
|
'租赁': 'LEASE',
|
'私有增包量': 'PRIVATE_INCREMENT',
|
'公有增包量': 'PUBLIC_INCREMENT',
|
'OTA服务': 'OTA',
|
'云服务': 'CLOUD',
|
'资源包': 'RESOURCE_PACKAGE',
|
'个人': 'PERSONAL',
|
}
|
return (map[val] as any) || val
|
}
|
|
function mapCustomerToCN(val: string) {
|
const map: Record<string, string> = { ENTERPRISE: '企业', PERSONAL: '个人', PROJECT_DEPARTMENT: '项目部' }
|
return map[val] || val
|
}
|
|
function mapCustomerFromCN(val: string) {
|
const map: Record<string, string> = { '企业': 'ENTERPRISE', '个人': 'PERSONAL', '项目部': 'PROJECT_DEPARTMENT' }
|
return (map[val] as any) || val
|
}
|
|
function mapUnitToCN(val: string) {
|
const map: Record<string, string> = { SET: '套', SET_PER_YEAR: '套/年', YEAR: '年' }
|
return map[val] || val
|
}
|
|
function mapUnitFromCN(val: string) {
|
const map: Record<string, string> = { '套': 'SET', '套/年': 'SET_PER_YEAR', '年': 'YEAR' }
|
return (map[val] as any) || val
|
}
|
|
function mapPriceTypeToCN(key: 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE') {
|
const map: Record<string, string> = { POINTS: '积分', CURRENCY: '货币', AGREEMENT: '协议', FREE: '免费' }
|
return map[key]
|
}
|
|
function mapPriceTypeFromCN(val: string): 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE' | undefined {
|
const map: Record<string, any> = { '积分': 'POINTS', '货币': 'CURRENCY', '协议': 'AGREEMENT', '免费': 'FREE' }
|
return map[val]
|
}
|
|
async function loadPricingList(productId: string) {
|
try {
|
const res: any = await productPricingApi.listByProductId(productId)
|
if (res?.code === 200) {
|
const list = Array.isArray(res.data) ? res.data : []
|
const newList = list.map((it: any) => {
|
const priceSettings = it.priceType.split(',').map((t:string) => mapPriceTypeFromCN(t))
|
const pointsPrice = priceSettings.includes('POINTS') ? Number(it.pointsPrice || 0) : 0
|
const currencyPrice = priceSettings.includes('CURRENCY') ? Number(it.currencyPrice || 0) : 0
|
return {
|
id: it.id,
|
productId: String(it.productId || productId),
|
productSuite: it.suiteName || '',
|
salesForm: mapSalesFormFromCN(it.salesForm) as any,
|
customerObject: mapCustomerFromCN(it.customerType) as any,
|
accountQuantityUnlimited: (it.accountLimit === '不限'),
|
accountQuantity: it.accountLimit === '不限' ? '' : Number(it.accountLimit || ''),
|
concurrentNodeQuantityUnlimited: (it.concurrentNodes === '不限'),
|
concurrentNodeQuantity: it.concurrentNodes === '不限' ? '' : Number(it.concurrentNodes || ''),
|
priceSettings: priceSettings,
|
pointsPrice,
|
currencyPrice,
|
priceUnit: mapUnitFromCN(it.priceUnit) as any,
|
enableStatus: it.isActive ? 'ENABLED' : 'DISABLED',
|
authorizationStartTime: undefined,
|
authorizationEndTime: undefined,
|
remark: it.description || '',
|
}
|
})
|
const groupedList = newList.reduce((acc: any[], it: any) => {
|
// 按productSuite分组
|
const productSuite = it.productSuite
|
if (!acc[productSuite]) {
|
acc[productSuite] = []
|
}
|
acc[productSuite].push(it)
|
return acc
|
}, {})
|
const newGroupedList:any = []
|
Object.entries(groupedList).forEach(([k,v]) =>{
|
newGroupedList.push({
|
suiteName: k,
|
items: v
|
})
|
})
|
priceList.value = newGroupedList
|
console.log(priceList.value)
|
console.log(groupedList)
|
} else {
|
priceList.value = []
|
}
|
} catch (e) {
|
priceList.value = []
|
}
|
}
|
|
function buildPayloadFromRow(row: any, overrides: Partial<any> = {}) {const priceTypeCN = row.priceSettings?.[0] ? mapPriceTypeToCN(row.priceSettings[0]) : undefined
|
|
return {
|
id: row.id,
|
productId: row.productId,
|
productName: productDetail.value?.name,
|
suiteName: row.productSuite,
|
salesForm: mapSalesFormToCN(row.salesForm),
|
customerType: mapCustomerToCN(row.customerObject),
|
accountLimit: row.accountQuantityUnlimited ? '不限' : String(row.accountQuantity || ''),
|
concurrentNodes: row.concurrentNodeQuantityUnlimited ? '不限' : String(row.concurrentNodeQuantity || ''),
|
priceType: row.priceSettings.map((t:string)=>mapPriceTypeToCN(t as any)).join(','),
|
priceUnit: mapUnitToCN(row.priceUnit || ''),
|
pointsPrice: row.priceSettings.includes('POINTS') ? row.pointsPrice : '',
|
currencyPrice: row.priceSettings.includes('CURRENCY') ? row.currencyPrice : '',
|
isActive: row.enableStatus === 'ENABLED',
|
description: row.remark || '',
|
...overrides,
|
}
|
}
|
|
// 查看价格
|
const handleViewPricing = () => {
|
if (!currentProductId.value) {
|
ElMessage.warning('请先选择产品')
|
return
|
}
|
showPriceViewer.value = true
|
}
|
|
// 添加价格
|
const handleAddPrice = () => {
|
isEditMode.value = false
|
resetForm()
|
formData.productId = currentProductId.value
|
dialogVisible.value = true
|
}
|
|
// 编辑价格
|
const handleEdit = (row: any) => {
|
isEditMode.value = true
|
resetForm()
|
Object.assign(formData, {
|
id: row.id,
|
productId: currentProductId.value,
|
productSuite: row.productSuite || '',
|
salesForm: row.salesForm || 'BUYOUT',
|
customerObject: row.customerObject || 'ENTERPRISE',
|
accountQuantity: row.accountQuantity ?? '',
|
accountQuantityUnlimited: !!row.accountQuantityUnlimited,
|
concurrentNodeQuantity: row.concurrentNodeQuantity ?? '',
|
concurrentNodeQuantityUnlimited: !!row.concurrentNodeQuantityUnlimited,
|
priceSettings: Array.isArray(row.priceSettings) ? row.priceSettings : [],
|
pointsPrice: row.pointsPrice ?? '',
|
currencyPrice: row.currencyPrice ?? '',
|
priceUnit: row.priceUnit || '',
|
enableStatus: row.enableStatus || 'ENABLED',
|
authorizationStartTime: row.authorizationStartTime,
|
authorizationEndTime: row.authorizationEndTime,
|
remark: row.remark || ''
|
})
|
dialogVisible.value = true
|
}
|
|
// 删除价格
|
const handleDelete = (row: any) => {
|
ElMessageBox.confirm(
|
`确定要删除该价格(ID: ${row.id})吗?`,
|
'确认删除',
|
{
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}
|
)
|
.then(async () => {
|
await productPricingApi.remove(row.id)
|
ElMessage.success('删除成功')
|
if (currentProductId.value) await loadPricingList(currentProductId.value)
|
})
|
.catch(() => {
|
ElMessage.info('已取消删除')
|
})
|
}
|
|
onMounted(() => {
|
fetchProductDetail(currentProductId.value)
|
})
|
|
watch(
|
() => [route.params.productId, route.query.productId, route.query.id],
|
() => fetchProductDetail(currentProductId.value)
|
)
|
|
function resetForm() {
|
Object.assign(formData, {
|
id: null,
|
productId: currentProductId.value,
|
productSuite: '',
|
salesForm: 'BUYOUT',
|
customerObject: 'ENTERPRISE',
|
accountQuantity: 1,
|
accountQuantityUnlimited: false,
|
concurrentNodeQuantity: 1,
|
concurrentNodeQuantityUnlimited: false,
|
priceSettings: [],
|
pointsPrice: '',
|
currencyPrice: '',
|
priceUnit: '',
|
enableStatus: 'ENABLED',
|
authorizationStartTime: undefined,
|
authorizationEndTime: undefined,
|
remark: ''
|
})
|
if (formRef.value) formRef.value.clearValidate()
|
}
|
|
|
async function handleSave() {
|
try {
|
await formRef.value.validate()
|
} catch (e) {
|
return
|
}
|
|
if (!currentProductId.value) {
|
ElMessage.warning('未提供产品ID,无法保存定价')
|
return
|
}
|
|
const selectedTypes = formData.priceSettings
|
if (!selectedTypes || selectedTypes.length === 0) {
|
ElMessage.warning('请至少选择一种价格设置')
|
return
|
}
|
|
const mapSalesFormToCN = (val: string) => ({
|
BUYOUT: '买断',
|
LEASE: '租赁',
|
PRIVATE_INCREMENT: '私有增包量',
|
PUBLIC_INCREMENT: '公有增包量',
|
OTA: 'OTA服务',
|
CLOUD: '云服务',
|
RESOURCE_PACKAGE: '资源包',
|
PERSONAL: '个人',
|
} as any)[val] || val
|
|
const mapCustomerToCN = (val: string) => ({ ENTERPRISE: '企业', PERSONAL: '个人', PROJECT_DEPARTMENT: '项目部' } as any)[val] || val
|
const mapUnitToCN = (val: string) => ({ SET: '套', SET_PER_YEAR: '套/年', YEAR: '年' } as any)[val] || val
|
const mapPriceTypeToCN = (key: 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE') => ({ POINTS: '积分', CURRENCY: '货币', AGREEMENT: '协议', FREE: '免费' } as any)[key]
|
|
const buildPayload = () => {
|
|
return {
|
id: isEditMode.value && formData.id ? formData.id : undefined,
|
productId: currentProductId.value,
|
productName: productDetail.value?.name,
|
suiteName: formData.productSuite,
|
salesForm: mapSalesFormToCN(formData.salesForm),
|
customerType: mapCustomerToCN(formData.customerObject),
|
accountLimit: formData.accountQuantityUnlimited ? '不限' : String(formData.accountQuantity || ''),
|
concurrentNodes: formData.concurrentNodeQuantityUnlimited ? '不限' : String(formData.concurrentNodeQuantity || ''),
|
priceType: formData.priceSettings.map(t=>mapPriceTypeToCN(t)).join(','),
|
priceUnit: mapUnitToCN(formData.priceUnit || ''),
|
pointsPrice: formData.pointsPrice,
|
currencyPrice: formData.currencyPrice,
|
isActive: formData.enableStatus === 'ENABLED',
|
description: formData.remark || '',
|
}
|
}
|
|
saving.value = true
|
try {
|
if (isEditMode.value && formData.id) {
|
const payload = buildPayload()
|
const res: any = await productPricingApi.update(payload)
|
if (res?.code === 200) {
|
ElMessage.success('修改成功')
|
} else {
|
ElMessage.error(res?.msg || '修改失败')
|
return
|
}
|
} else {
|
// const tasks = selectedTypes.map(t => productPricingApi.add(buildPayload(t)))
|
// const results: any[] = await Promise.all(tasks)
|
// const ok = results.every(r => r && r.code === 200)
|
const results: any = await productPricingApi.add(buildPayload())
|
if (results?.code !== 200) {
|
ElMessage.error('部分定价保存失败')
|
return
|
}
|
ElMessage.success('新建成功')
|
// 修改产品状态为已订价
|
// 判断 产品定价列表是否为空,为空则更新状态,不为空则说明更新过了,不需要在此更新
|
if (priceList.value.length === 0) {
|
const updateParams = {
|
id: currentProductId.value,
|
listingStatus: 'PRICED'
|
}
|
const res: any = await productApi.updateProductStatus(updateParams)
|
if (res?.code !== 200) {
|
console.log('产品状态更新失败!!')
|
}
|
}
|
}
|
|
dialogVisible.value = false
|
await loadPricingList(currentProductId.value)
|
} finally {
|
saving.value = false
|
}
|
}
|
|
// 处理订购结果
|
const handleOrderResult = (selectedItems: any[]) => {
|
console.log('用户选择的价格项:', selectedItems)
|
ElMessage.success(`已选择 ${selectedItems.length} 个价格套件进入订购流程`)
|
}
|
|
// 启用/停用按钮
|
function handleToggleEnableStatus(row: any) {
|
const newStatus = row.enableStatus === 'ENABLED' ? 'DISABLED' : 'ENABLED'
|
ElMessageBox.confirm(
|
`确认要${newStatus === 'ENABLED' ? '启用' : '停用'}该价格(ID: ${row.id})吗?`,
|
'状态变更',
|
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
)
|
.then(async () => {
|
const payload = buildPayloadFromRow(row, { isActive: newStatus === 'ENABLED' })
|
await productPricingApi.update(payload)
|
await loadPricingList(currentProductId.value as string)
|
ElMessage.success(newStatus === 'ENABLED' ? '启用成功' : '停用成功')
|
})
|
.catch(() => {})
|
}
|
</script>
|
|
<style scoped lang="scss">
|
.price-manage-container {
|
padding: 20px;
|
|
.page-header {
|
margin-bottom: 16px;
|
background: #e6eef9;
|
border-radius: 2px;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
padding: 10px 0;
|
.bar-title { font-size: 16px; font-weight: 600; color: #5e6b85; }
|
}
|
|
.detail-card { margin-bottom: 16px; }
|
/* 仅控制产品信息卡片的内间距 */
|
.detail-card :deep(.el-card__body) {
|
padding: 2px !important;
|
}
|
|
/* 关键:让描述列表6个单元格等分,从而每条信息(2格)等宽 */
|
.detail-card :deep(.el-descriptions__table) {
|
table-layout: fixed;
|
width: 100%;
|
}
|
.detail-card :deep(.el-descriptions__label),
|
.detail-card :deep(.el-descriptions__content) {
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.content-area {
|
.price-card {
|
/* 让价格列表可滚动 */
|
:deep(.el-card__body) {
|
max-height: 600px;
|
overflow-y: auto;
|
}
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
span { font-size: 14px; font-weight: 700; }
|
}
|
.price-value { color: #e6a23c; font-weight: 500; }
|
}
|
}
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
|
.header-actions {
|
display: flex;
|
gap: 8px;
|
}
|
}
|
.header-left { display: inline-flex; align-items: center; }
|
.section-icon { margin-right: 6px; color: #409eff; }
|
.detail-card .card-header span { font-size: 14px; font-weight: 700; }
|
.id-info { color: #909399; font-size: 12px; }
|
.intro { white-space: pre-wrap; }
|
/* ---------- 定价卡片样式(参考 PricingManagement.vue,做精简) ---------- */
|
.pricing-cards-container { position: relative; display: block; }
|
.pricing-cards-wrapper { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; padding: 10px 0; }
|
.pricing-card { width: 100%; border: 1px solid #e4e7ed; border-radius: 8px; padding: 16px; background: #fff; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: all 0.3s ease; position: relative; }
|
.pricing-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.15); transform: translateY(-2px); }
|
.pricing-card-enabled { border: 2px solid #67c23a; box-shadow: 0 2px 8px rgba(103,194,58,0.2); }
|
.pricing-card-disabled { border: 2px solid #f56c6c; box-shadow: 0 2px 8px rgba(245,108,108,0.2); opacity: 0.9; }
|
.pricing-card-table { width: 100%; border-collapse: collapse; margin-bottom: 16px; border: 1px solid #e4e7ed; }
|
.pricing-row { display: flex; border-bottom: 1px solid #e4e7ed; }
|
.pricing-row:last-child { border-bottom: none; }
|
.pricing-cell { padding: 10px 8px; font-size: 14px; display: flex; align-items: center; border-right: 1px solid #e4e7ed; }
|
.pricing-cell:last-child { border-right: none; }
|
.label-cell { width: 25%; color: #606266; font-weight: 500; background-color: #f5f7fa; justify-content: center; }
|
.value-cell { width: 25%; color: #303133; font-weight: 400; justify-content: center; }
|
.purchase-methods-cell { width: 75%; flex-direction: column; align-items: flex-start; gap: 6px; justify-content: flex-start; }
|
.method-item { display: flex; align-items: center; gap: 8px; width: 100%; }
|
.method-label { font-size: 14px; color: #606266; min-width: 40px; }
|
.method-value { font-size: 14px; font-weight: 500; }
|
.method-value.points,.method-value.currency { color: #f56c6c; }
|
.method-value.free { color: #67c23a; }
|
.pricing-card-actions { display: flex; justify-content: center; gap: 20px; padding-top: 12px; border-top: 1px solid #f0f0f0; }
|
.action-link { cursor: pointer; font-size: 14px; transition: all 0.3s ease; }
|
.action-link.edit-link { color: #409eff; }
|
.action-link.delete-link { color: #f56c6c; }
|
.action-link.disable-link { color: #e6a23c; }
|
.action-link.enable-link { color: #67c23a; }
|
.pricing-status-tag { position: absolute; top: 8px; left: 8px; z-index: 10; }
|
.empty-pricing { text-align: center; padding: 40px 0; }
|
/* 表单局部样式 */
|
.price-settings { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
|
.price-setting-item { display: inline-flex; align-items: center; gap: 8px; }
|
.quantity-input-group { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
|
.sales-form-item :deep(.el-form-item__content) { white-space: nowrap; }
|
.sales-form-group { display: inline-flex; gap: 12px; flex-wrap: nowrap; }
|
.price-form :deep(.el-form-item) { margin-bottom: 20px; }
|
/* 上架状态颜色 */
|
.status-online { color: #67C23A; }
|
.status-pending { color: #E6A23C; }
|
.status-offline { color: #F56C6C; }
|
.status-default { color: #909399; }
|
</style>
|