src/views/layout/components/sideBar/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/productManage/price/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/productManage/product/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/productManage/productPriceViewer/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/views/layout/components/sideBar/index.vue
@@ -160,7 +160,7 @@ "id": "4fa90aab-2e3c-4bc4-abbf-fbe536723fa2", "icon": "", "name": "查看报价", "url": "/producePrice/priceViewer", "url": "/product/priceViewer", "parentId": "266e3ff1-ca98-4946-8975-7526ef83cd5c", "sort": "3" }] src/views/productManage/price/index.vue
@@ -18,13 +18,13 @@ </template> <el-descriptions v-if="productDetail" :column="2" border> <el-descriptions-item label="产品名称">{{ productDetail.name }}</el-descriptions-item> <el-descriptions-item label="提报单位">{{ productDetail.submitUnit }}</el-descriptions-item> <el-descriptions-item label="提报人">{{ productDetail.submitter }}</el-descriptions-item> <el-descriptions-item label="行业领域">{{ productDetail.industry }}</el-descriptions-item> <el-descriptions-item label="单位工程">{{ productDetail.projectUnit }}</el-descriptions-item> <el-descriptions-item label="产业阶段">{{ productDetail.industryStage }}</el-descriptions-item> <el-descriptions-item label="产品类型">{{ productDetail.productType }}</el-descriptions-item> <el-descriptions-item label="产品名称" label-width="10%">{{ productDetail.name }}</el-descriptions-item> <el-descriptions-item label="提报单位" label-width="10%">{{ productDetail.submitUnit }}</el-descriptions-item> <el-descriptions-item label="提报人" label-width="10%">{{ productDetail.submitter }}</el-descriptions-item> <el-descriptions-item label="行业领域" label-width="10%">{{ productDetail.industry }}</el-descriptions-item> <el-descriptions-item label="单位工程" label-width="10%">{{ productDetail.projectUnit }}</el-descriptions-item> <el-descriptions-item label="产业阶段" label-width="10%">{{ productDetail.industryStage }}</el-descriptions-item> <el-descriptions-item label="产品类型"label-width="10%">{{ productDetail.productType }}</el-descriptions-item> <el-descriptions-item label="产品简介" :span="3"> <div class="intro">{{ productDetail.description }}</div> </el-descriptions-item> @@ -43,16 +43,22 @@ </div> </template> <div class="pricing-cards-container" v-if="priceList.length > 0"> <div class="pricing-cards-wrapper"> <div v-for="pricing in priceList" :key="pricing.id" class="pricing-card" :class="{ 'pricing-card-enabled': pricing.enableStatus === 'ENABLED', 'pricing-card-disabled': pricing.enableStatus === 'DISABLED' }" > <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> @@ -71,19 +77,19 @@ <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.pointsAmount || '-') + '/套' : '-' }}</span> <span class="method-value points">{{ pricing.priceSettings?.includes('POINTS') ? (pricing.pointsPrice || '-') + '/' + getPriceUnitText(pricing.priceUnit) : '-' }}</span> </div> <div class="method-item"> <div class="method-item" v-if="pricing.priceSettings?.includes('CURRENCY')"> <span class="method-label">货币:</span> <span class="method-value currency">{{ pricing.priceSettings?.includes('CURRENCY') ? (pricing.currencyAmount || '-') + '/套' : '-' }}</span> <span class="method-value currency">{{ (pricing.currencyPrice || '-') + '/' + getPriceUnitText(pricing.priceUnit) }}</span> </div> <div class="method-item"> <div class="method-item" v-if="pricing.priceSettings?.includes('AGREEMENT')"> <span class="method-label">协议:</span> <span class="method-value agreement">{{ pricing.priceSettings?.includes('AGREEMENT') ? '每套' : '-' }}</span> <span class="method-value agreement">{{ '/' + getPriceUnitText(pricing.priceUnit) }}</span> </div> <div class="method-item"> <div class="method-item" v-if="pricing.priceSettings?.includes('FREE')"> <span class="method-label">免费:</span> <span class="method-value free">{{ pricing.priceSettings?.includes('FREE') ? '免费' : '-' }}</span> <span class="method-value free">{{ '/' + getPriceUnitText(pricing.priceUnit) }}</span> </div> </div> </div> @@ -109,6 +115,7 @@ </el-icon> {{ pricing.enableStatus === 'ENABLED' ? '停用' : '启用' }} </span> </div> </div> </div> </div> @@ -136,57 +143,48 @@ size="large" class="price-form" > <el-row :gutter="8"> <el-col :span="12"> <el-form-item label="产品套件" prop="productSuite"> <el-select v-model="formData.productSuite" placeholder="请选择" style="width: 100%"> <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-col> <el-col :span="12"> <el-form-item label="客户对象" prop="customerObject"> <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-col> </el-row> </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-row :gutter="8"> <el-col :span="12"> <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-col> <el-col :span="12"> <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-col> </el-row> <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.pointsAmount" :min="0" :precision="2" :controls="false" placeholder="积分金额" style="width: 120px; margin-left: 6px" /> <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.currencyAmount" :min="0" :precision="2" :controls="false" placeholder="货币金额" style="width: 120px; margin-left: 6px" /> <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> @@ -197,24 +195,20 @@ </div> </el-form-item> <el-row :gutter="8"> <el-col :span="12"> <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-col> <el-col :span="12"> <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-col> </el-row> <el-form-item label="备注"> <el-input type="textarea" v-model="formData.remark" :rows="3" placeholder="请输入备注" /> @@ -244,6 +238,8 @@ 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' interface ProductDetail { id: string @@ -269,8 +265,8 @@ concurrentNodeQuantity: number | '' concurrentNodeQuantityUnlimited: boolean priceSettings: Array<'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE'> pointsAmount: number | '' currencyAmount: number | '' pointsPrice: number | '' currencyPrice: number | '' priceUnit: '' | 'SET' | 'SET_PER_YEAR' | 'YEAR' enableStatus: 'ENABLED' | 'DISABLED' authorizationStartTime?: string @@ -302,8 +298,8 @@ // 模拟产品详情数据源 const mockProductMap: Record<string, ProductDetail> = { '10001': { id: '10001', '1': { id: '1', name: '数字化产品A', submitUnit: '中交一公局', submitter: '张三', @@ -314,8 +310,8 @@ description: '本产品定位为以建设期BIM数字资产作为数字底盘,结合项目运营维保需求的实时性、交互性、便捷性的三维可视化运维管理系统。系统提供项目数字化、智能化运维管理功能,能够解决建筑运行维护管理中的实际问题,实现信息快速整合与查询、信息有效共享与传递,提升项目综合管理与维护水平。', shelfStatus: '待上架' }, '10002': { id: '10002', '2': { id: '2', name: '数字化产品B', submitUnit: '中交二航局', submitter: '李四', @@ -343,13 +339,13 @@ productSuite: '', salesForm: 'BUYOUT', customerObject: 'ENTERPRISE', accountQuantity: '', accountQuantity: 1, accountQuantityUnlimited: false, concurrentNodeQuantity: '', concurrentNodeQuantity: 1, concurrentNodeQuantityUnlimited: false, priceSettings: [], pointsAmount: '', currencyAmount: '', pointsPrice: '', currencyPrice: '', priceUnit: '', enableStatus: 'ENABLED', authorizationStartTime: undefined, @@ -359,15 +355,15 @@ // 选项 const productSuiteOptions = [ { label: '企业私有SaaS版许可', value: 'ENTERPRISE_PRIVATE_SAAS_LICENSE' }, { label: '企业私有SaaS版OTA服务', value: 'ENTERPRISE_PRIVATE_SAAS_OTA' }, { label: '企业私有SaaS版用户增量包', value: 'ENTERPRISE_PRIVATE_SAAS_USER_INCREMENT' }, { label: '企业公有SaaS版许可', value: 'ENTERPRISE_PUBLIC_SAAS_LICENSE' }, { label: '企业公有SaaS版OTA服务', value: 'ENTERPRISE_PUBLIC_SAAS_OTA' }, { label: '企业公有SaaS版用户增量包', value: 'ENTERPRISE_PUBLIC_SAAS_USER_INCREMENT' }, { label: '个人公有SaaS化许可', value: 'PERSONAL_PUBLIC_SAAS_LICENSE' }, { label: 'web软件', value: 'WEB_SOFTWARE' }, { label: '桌面软件', value: 'DESKTOP_SOFTWARE' } { 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' }, @@ -418,8 +414,8 @@ // 若选择 FREE,清空其他 if (key === 'FREE') { formData.priceSettings = ['FREE'] formData.pointsAmount = '' formData.currencyAmount = '' formData.pointsPrice = '' formData.currencyPrice = '' return } // 若此前有 FREE,去掉 FREE @@ -456,8 +452,8 @@ 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.pointsAmount === '' || formData.pointsAmount === undefined)) return cb(new Error('请输入积分金额')) if (value.includes('CURRENCY') && (formData.currencyAmount === '' || formData.currencyAmount === undefined)) 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' @@ -499,61 +495,164 @@ return map[unit || ''] || unit || '' } function fetchProductDetail(productId?: string) { async function fetchProductDetail(productId?: string) { if (!productId) { productDetail.value = null ElMessage.warning('未提供产品ID,无法加载产品详情') return } // 模拟异步 loading.value = true setTimeout(() => { try { productDetail.value = mockProductMap[productId] || null // 构造卡片所需的演示定价数据 priceList.value = productDetail.value ? [ { id: 1, productId: productId, productSuite: 'STANDARD', salesForm: 'BUYOUT', customerObject: 'ENTERPRISE', accountQuantity: 50, accountQuantityUnlimited: false, concurrentNodeQuantity: 50, concurrentNodeQuantityUnlimited: false, priceSettings: ['POINTS'], pointsAmount: 50000, currencyAmount: 0, priceUnit: 'SET_PER_YEAR', enableStatus: 'ENABLED', authorizationStartTime: '2025-01-01', authorizationEndTime: '2025-12-31', remark: '演示数据' }, { id: 2, productId: productId, productSuite: 'PRO', salesForm: 'PRIVATE_INCREMENT', customerObject: 'ENTERPRISE', accountQuantity: 100, accountQuantityUnlimited: false, concurrentNodeQuantity: 100, concurrentNodeQuantityUnlimited: false, priceSettings: ['CURRENCY'], pointsAmount: 0, currencyAmount: 7500, priceUnit: 'SET', enableStatus: 'DISABLED', authorizationStartTime: '2025-01-01', authorizationEndTime: '2025-06-30', remark: '演示数据' } ] : [] // const detailRes: any = await productApi.getProductDetail({ id: productId }) // 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 }, 300) } } 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: Number(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, } } // 查看价格 @@ -570,7 +669,6 @@ isEditMode.value = false resetForm() formData.productId = currentProductId.value formData.id = generateId() dialogVisible.value = true } @@ -579,7 +677,7 @@ isEditMode.value = true resetForm() Object.assign(formData, { id: row.id ?? generateId(), id: row.id, productId: currentProductId.value, productSuite: row.productSuite || '', salesForm: row.salesForm || 'BUYOUT', @@ -589,8 +687,8 @@ concurrentNodeQuantity: row.concurrentNodeQuantity ?? '', concurrentNodeQuantityUnlimited: !!row.concurrentNodeQuantityUnlimited, priceSettings: Array.isArray(row.priceSettings) ? row.priceSettings : [], pointsAmount: row.pointsAmount ?? '', currencyAmount: row.currencyAmount ?? '', pointsPrice: row.pointsPrice ?? '', currencyPrice: row.currencyPrice ?? '', priceUnit: row.priceUnit || '', enableStatus: row.enableStatus || 'ENABLED', authorizationStartTime: row.authorizationStartTime, @@ -611,10 +709,10 @@ type: 'warning' } ) .then(() => { const idx = priceList.value.findIndex((p: any) => p.id === row.id) if (idx !== -1) priceList.value.splice(idx, 1) .then(async () => { await productPricingApi.remove(row.id) ElMessage.success('删除成功') if (currentProductId.value) await loadPricingList(currentProductId.value) }) .catch(() => { ElMessage.info('已取消删除') @@ -637,13 +735,13 @@ productSuite: '', salesForm: 'BUYOUT', customerObject: 'ENTERPRISE', accountQuantity: '', accountQuantity: 1, accountQuantityUnlimited: false, concurrentNodeQuantity: '', concurrentNodeQuantity: 1, concurrentNodeQuantityUnlimited: false, priceSettings: [], pointsAmount: '', currencyAmount: '', pointsPrice: '', currencyPrice: '', priceUnit: '', enableStatus: 'ENABLED', authorizationStartTime: undefined, @@ -653,10 +751,6 @@ if (formRef.value) formRef.value.clearValidate() } function generateId() { const maxId = priceList.value.reduce((m: number, it: any) => Math.max(m, Number(it.id || 0)), 0) return maxId + 1 } async function handleSave() { try { @@ -665,17 +759,77 @@ 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: Number(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 { const record: PriceItem = JSON.parse(JSON.stringify(formData)) const idx = priceList.value.findIndex((p: any) => p.id === record.id) if (idx === -1) { priceList.value.push(record) 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 { priceList.value.splice(idx, 1, record) // 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('新建成功') } ElMessage.success(isEditMode.value ? '修改成功' : '新建成功') dialogVisible.value = false await loadPricingList(currentProductId.value) } finally { saving.value = false } @@ -695,11 +849,10 @@ '状态变更', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' } ) .then(() => { const idx = priceList.value.findIndex((p: any) => p.id === row.id) if (idx !== -1) { priceList.value[idx] = { ...priceList.value[idx], enableStatus: newStatus } } .then(async () => { const payload = buildPayloadFromRow(row, { isActive: newStatus === 'ENABLED' }) await productPricingApi.update(payload) await loadPricingList(currentProductId.value as string) ElMessage.success(newStatus === 'ENABLED' ? '启用成功' : '停用成功') }) .catch(() => {}) @@ -741,6 +894,11 @@ .content-area { .price-card { /* 让价格列表可滚动 */ :deep(.el-card__body) { max-height: 600px; overflow-y: auto; } .card-header { display: flex; justify-content: space-between; @@ -801,7 +959,7 @@ .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: 12px; } .price-form :deep(.el-form-item) { margin-bottom: 20px; } /* 上架状态颜色 */ .status-online { color: #67C23A; } .status-pending { color: #E6A23C; } src/views/productManage/product/index.vue
@@ -94,7 +94,7 @@ // 模拟产品数据 const mockProductList: ProductItem[] = [ { id: '10001', id: '1', name: '数字化产品A', productType: '软件/平台', industry: '交通基础设施', @@ -106,7 +106,7 @@ shelfStatus: '待上架' }, { id: '10002', id: '2', name: '数字化产品B', productType: '硬件/传感', industry: '市政工程', @@ -118,7 +118,7 @@ shelfStatus: '已上架' }, { id: '10003', id: '3', name: '数字化产品C', productType: '软件/平台', industry: '建筑工程', src/views/productManage/productPriceViewer/index.vue
@@ -9,7 +9,7 @@ > <div class="price-viewer-container" v-loading="loading"> <!-- 价格对比表格 --> <div class="pricing-table-container" v-if="priceList.length > 0"> <div class="pricing-table-container" v-if="showPricePanel && priceList.length > 0"> <el-tabs v-model="activeTab" type="card" class="pricing-tabs"> <el-tab-pane v-for="(tabData, tabKey) in groupedPriceData" @@ -19,7 +19,7 @@ > <div class="pricing-table-wrapper"> <table class="pricing-table"> <thead> <thead> <tr> <th class="feature-column">功能对比</th> <th @@ -107,25 +107,27 @@ v-if="priceItem.priceSettings.includes('POINTS') && priceItem.pointsAmount > 0" class="price-item" > <span class="price-label">积分:</span> <span class="price-value points"> {{ formatNumber(priceItem.pointsAmount) }} 积分{{ getPriceUnitText(priceItem.priceUnit) }} </span> <div class="price-lable-icon"> <el-icon class="price-icon points"><Coin /></el-icon> <span class="price-label">积分</span> </div> <span class="price-value points">{{ formatNumber(priceItem.pointsAmount) }} / {{ getPriceUnitText(priceItem.priceUnit) }}</span> </div> <div v-if="priceItem.priceSettings.includes('CURRENCY') && priceItem.currencyAmount > 0" class="price-item" > <span class="price-label">¥货币:</span> <span class="price-value currency"> {{ formatNumber(priceItem.currencyAmount) }} 元{{ getPriceUnitText(priceItem.priceUnit) }} </span> <div class="price-lable-icon"> <el-icon class="price-icon currency"><Money /></el-icon> <span class="price-label">货币</span> </div> <span class="price-value currency">{{ formatNumber(priceItem.currencyAmount) }} / {{ getPriceUnitText(priceItem.priceUnit) }}</span> </div> <div v-if="priceItem.priceSettings.includes('FREE')" class="price-item" > <span class="price-label">¥货币:</span> <span class="price-label">费用</span> <span class="price-value free">免费</span> </div> <div @@ -151,7 +153,7 @@ :model-value="selectedSuiteIds.has(priceItem.id)" @change="handleSuiteSelect(priceItem.id, $event)" > 添加 <el-icon class="add-cart-icon"><ShoppingCart /></el-icon> </el-checkbox> </div> </td> @@ -162,6 +164,159 @@ </el-tab-pane> </el-tabs> </div> <!-- 内嵌订购信息面板 --> <div class="order-panel" v-else-if="showOrderPanel"> <!-- 第一部分:产品信息 + 汇总 + 操作(静态数据) --> <el-card class="order-header-card" shadow="never"> <div class="basic-grid"> <div class="grid-item"> <span class="label">产品名称:</span><span class="value strong">{{ productHeader.name }}</span> </div> <div class="grid-item"> <span class="label">行业板块:</span><span class="value">{{ productHeader.industry }}</span> </div> <div class="grid-item"> <span class="label">提供者:</span><span class="value">{{ productHeader.provider }}</span> </div> <div class="grid-item"> <span class="label">用户姓名:</span><span class="value">{{ userInfo.name }}</span> </div> <div class="grid-item"> <span class="label">单位:</span><span class="value">{{ userInfo.unit }}</span> </div> <div class="grid-item"> <span class="label">部门:</span><span class="value">{{ userInfo.department }}</span> </div> </div> </el-card> <!-- 第二部分:选购产品列表 --> <el-card class="selected-suites-card" shadow="never"> <div v-if="orderSuites.length > 0" class="suite-table-wrapper"> <table class="suite-table"> <thead> <tr> <th class="th-left">详情</th> <th class="th-left">规格</th> <th class="th-left">单价</th> <th class="th-center">数量</th> <th class="th-center">年限</th> <th class="th-center">操作</th> </tr> </thead> <tbody> <tr v-for="(suite, idx) in orderSuites" :key="suite.id"> <td class="cell-detail"> <div class="detail-content"> <el-checkbox v-model="suite.selected" /> <span class="suite-name">{{ getProductSuiteText(suite.productSuite) }}</span> </div> </td> <td class="cell-spec cell-spec-gg"> <div class="spec-line-gg"> <div class="spec-line">销售形式:{{ getSalesFormText(suite.salesForm) }}</div> <div class="spec-line">客户对象:{{ getCustomerObjectText(suite.customerObject) }}</div> </div> <div class="spec-line-gg"> <div class="spec-line">账户数量:{{ suite.accountQuantityUnlimited ? '不限' : suite.accountQuantity }}</div> <div class="spec-line">并发节点数:{{ suite.concurrentNodeQuantityUnlimited ? '不限' : suite.concurrentNodeQuantity }}</div> </div> </td> <td class="cell-price"> <div class="price-inline"> <el-select v-model="suite.priceType" size="large" style="width: 90px"> <el-option v-for="t in getAvailablePriceTypes(suite)" :key="t.value" :label="t.label" :value="t.value" /> </el-select> <div class="price-display"> <template v-if="suite.priceType === 'POINTS'"> <span class="price-tag points">{{ formatNumber(suite.pointsAmount) }}</span> <span class="price-unit">{{ getUnitSuffix(suite.priceUnit) }}</span> </template> <template v-else-if="suite.priceType === 'CURRENCY'"> <span class="price-tag currency">{{ formatNumber(suite.currencyAmount) }}</span> <span class="price-unit">{{ getUnitSuffix(suite.priceUnit) }}</span> </template> <template v-else-if="suite.priceType === 'AGREEMENT'"> <span class="price-agreement">/{{ getPriceUnitText(suite.priceUnit) }}</span> </template> <template v-else> <span class="price-free">/{{ getPriceUnitText(suite.priceUnit) }}</span> </template> </div> </div> </td> <td class="th-center"> <el-input-number v-model="suite.quantity" :min="1" :max="999" :controls="false" size="small" /> </td> <td class="th-center"> <el-input-number v-model="suite.duration" :min="1" :max="10" :controls="false" size="small" /> </td> <td class="th-center"> <el-button type="danger" link @click="removeSuite(idx)">删除</el-button> </td> </tr> </tbody> </table> </div> <el-empty v-else description="暂无选中的软件套件" /> <div class="table-toolbar"> <div class="toolbar-left"> <el-checkbox v-model="selectAllChecked">全选</el-checkbox> <span class="summary-info"> 已选 <span class="summary-count">{{ selectedCount }}</span> 件产品 总计 <span class="summary-points">{{ formatNumber(totalByType.points) }}</span> 积分 </span> </div> </div> </el-card> </div> <!-- 订单状态展示面板 --> <div class="order-status-panel" v-else-if="showOrderStatus"> <div class="steps-wrapper"> <el-steps :active="activeStep" align-center finish-status="success" process-status="process"> <el-step title="提交申请" :description="orderStatus.submitTime" /> <el-step title="待授权" /> <el-step title="交易确认" /> <el-step title="评价" /> </el-steps> </div> <div class="result-text"> 您的产品订购申请已成功提交, 请等待授权 </div> <el-card class="order-info-card" shadow="never"> <div class="order-info-title">订单信息</div> <table class="order-info-table"> <tbody> <tr> <td class="label">订单编号:</td> <td class="value">{{ orderStatus.id }}</td> <td class="label">申请时间:</td> <td class="value">{{ orderStatus.applyTime }}</td> </tr> <tr> <td class="label">产品名称:</td> <td class="value">{{ orderStatus.productName }}</td> <td class="label">提供者:</td> <td class="value">{{ orderStatus.provider }}</td> </tr> <tr> <td class="label">订单状态:</td> <td class="value">{{ statusText(orderStatus.status) }}</td> <td></td> <td class="value link"> <el-link type="primary" @click="viewOrder">查看订单信息</el-link> </td> </tr> </tbody> </table> </el-card> </div> <!-- 无价格数据时的提示 --> <div v-else class="no-price-data"> @@ -169,27 +324,31 @@ </div> </div> <template #footer> <template #footer v-if="!showOrderStatus"> <span class="dialog-footer"> <template v-if="showPricePanel"> <el-button @click="handleClose">关闭</el-button> <el-button type="primary" @click="handleOrder">立即订购</el-button> </template> <template v-else> <el-button @click="showOrderPanel = false">返回价格对比</el-button> <el-button type="primary" @click="submitOrder">提交申请</el-button> </template> </span> </template> </el-dialog> <!-- 产品订购对话框 --> <ProductOrderDialog v-model="showOrderDialog" :product-id="props.productId" :suite-ids="Array.from(selectedSuiteIds)" @order-success="handleOrderSuccess" /> <!-- 产品订购对话框移除,改为内嵌展示 --> </template> <script setup lang="ts"> import { ref, watch, computed } from 'vue' import { ElMessage } from 'element-plus' import ProductOrderDialog from '../productOrderDialog/index.vue' import { useRoute } from 'vue-router' import productPricingApi from '@/api/productPricingApi' import { ShoppingCart, Coin, Money } from '@element-plus/icons-vue' const route = useRoute() interface PriceItem { id: number @@ -219,11 +378,18 @@ (e: 'order', selectedItems: PriceItem[]): void } // 兼容 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 props = withDefaults(defineProps<Props>(), { modelValue: false, productId: '', width: '90%' }) const emit = defineEmits<Emits>() @@ -237,7 +403,126 @@ const priceList = ref<PriceItem[]>([]) const productName = ref('') const selectedSuiteIds = ref<Set<number>>(new Set()) const showOrderDialog = ref(false) const showOrderPanel = ref(false) const showPricePanel = ref(true) const showOrderStatus = ref(false) type PriceTypeKey = 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE' interface OrderSuite extends PriceItem { selected: boolean quantity: number duration: number priceType?: PriceTypeKey } const orderSuites = ref<OrderSuite[]>([]) // 订单状态相关 type OrderStatus = 'SUBMITTED' | 'AUTHORIZED' | 'CONFIRMED' | 'EVALUATED' interface OrderStatusDetail { id: string productName: string provider: string status: OrderStatus submitTime: string applyTime: string } const orderStatus = ref<OrderStatusDetail>({ id: '', productName: '', provider: '', status: 'SUBMITTED', submitTime: '', applyTime: '' }) const activeStep = computed(() => { switch (orderStatus.value?.status) { case 'SUBMITTED': return 1 case 'AUTHORIZED': return 2 case 'CONFIRMED': return 3 case 'EVALUATED': return 4 default: return 1 } }) const statusText = (s: OrderStatus) => { const map: Record<OrderStatus, string> = { SUBMITTED: '待授权', AUTHORIZED: '已授权,待确认交易', CONFIRMED: '交易已确认,待评价', EVALUATED: '已完成' } return map[s] } const viewOrder = () => { window.alert('这里可跳转到订单详情页(示例)') } const selectedCount = computed(() => orderSuites.value.filter(s => s.selected).length) const totalByType = computed(() => { const result = { points: 0, currency: 0 } orderSuites.value .filter(s => s.selected) .forEach(s => { const q = s.quantity || 1 const d = s.duration || 1 const multiplier = (s.priceUnit === 'SET_PER_YEAR' || s.priceUnit === 'YEAR') ? d : 1 switch (s.priceType) { case 'POINTS': result.points += (s.pointsAmount || 0) * q * multiplier break case 'CURRENCY': result.currency += (s.currencyAmount || 0) * q * multiplier break } }) return result }) // 第一部分:静态积分信息展示 const pointsInfo = ref({ accountName: '个人积分账户', balance: 50000, frozen: 0, }) // 产品信息/用户信息(静态) const productHeader = ref({ name: '中交方远智能实测实量管理系统', industry: '公路,市政,建筑', provider: '中交建筑集团第一工程有限公司' }) const userInfo = ref({ name: '张静', unit: '信科集团', department: '门户系统临时组' }) // 全选 const selectAllChecked = ref(false) watch(selectAllChecked, (val) => { orderSuites.value.forEach(s => { s.selected = !!val }) }) const getAvailablePriceTypes = (suite: OrderSuite) => { const t: { label: string; value: PriceTypeKey }[] = [] if (suite.priceSettings.includes('POINTS')) t.push({ label: '积分', value: 'POINTS' }) if (suite.priceSettings.includes('CURRENCY')) t.push({ label: '货币', value: 'CURRENCY' }) if (suite.priceSettings.includes('AGREEMENT')) t.push({ label: '协议', value: 'AGREEMENT' }) if (suite.priceSettings.includes('FREE')) t.push({ label: '免费', value: 'FREE' }) return t } const getDefaultPriceType = (suite: OrderSuite): PriceTypeKey => { if (suite.priceSettings.includes('POINTS')) return 'POINTS' if (suite.priceSettings.includes('CURRENCY')) return 'CURRENCY' if (suite.priceSettings.includes('AGREEMENT')) return 'AGREEMENT' return 'FREE' } const getUnitSuffix = (unit?: string) => { const map: Record<string, string> = { SET: '/套', SET_PER_YEAR: '/套/年', YEAR: '/年' } return map[unit || ''] || '' } const removeSuite = (index: number) => { orderSuites.value.splice(index, 1) } // 弹窗标题 const dialogTitle = computed(() => { @@ -277,197 +562,43 @@ ) }) // 模拟价格数据 const mockPriceData: Record<string, PriceItem[]> = { '10001': [ { id: 1, productId: '10001', productSuite: 'ENTERPRISE_PRIVATE_SAAS_LICENSE', salesForm: 'BUYOUT', customerObject: 'ENTERPRISE', accountQuantity: 50, accountQuantityUnlimited: false, concurrentNodeQuantity: 50, concurrentNodeQuantityUnlimited: false, priceSettings: ['POINTS', 'CURRENCY', 'AGREEMENT'], pointsAmount: 50000, currencyAmount: 50000, priceUnit: 'SET', enableStatus: 'ENABLED' }, { id: 2, productId: '10001', productSuite: 'ENTERPRISE_PRIVATE_SAAS_LICENSE', salesForm: 'LEASE', customerObject: 'ENTERPRISE', accountQuantity: 0, accountQuantityUnlimited: true, concurrentNodeQuantity: 50, concurrentNodeQuantityUnlimited: false, priceSettings: ['POINTS', 'CURRENCY', 'AGREEMENT'], pointsAmount: 800000, currencyAmount: 800000, priceUnit: 'YEAR', enableStatus: 'ENABLED' }, { id: 3, productId: '10001', productSuite: 'ENTERPRISE_PRIVATE_SAAS_OTA', salesForm: 'OTA', customerObject: 'ENTERPRISE', accountQuantity: 50, accountQuantityUnlimited: false, concurrentNodeQuantity: 50, concurrentNodeQuantityUnlimited: false, priceSettings: ['CURRENCY'], pointsAmount: 0, currencyAmount: 7500, priceUnit: 'SET_PER_YEAR', enableStatus: 'ENABLED' }, { id: 4, productId: '10001', productSuite: 'ENTERPRISE_PRIVATE_SAAS_USER_INCREMENT', salesForm: 'PRIVATE_INCREMENT', customerObject: 'ENTERPRISE', accountQuantity: 100, accountQuantityUnlimited: false, concurrentNodeQuantity: 100, concurrentNodeQuantityUnlimited: false, priceSettings: ['CURRENCY'], pointsAmount: 0, currencyAmount: 10000, priceUnit: 'YEAR', enableStatus: 'ENABLED' }, { id: 9, productId: '10001', productSuite: 'PROJECT_PRIVATE_SAAS_OTA', salesForm: 'OTA', customerObject: 'PROJECT_DEPARTMENT', accountQuantity: 50, accountQuantityUnlimited: false, concurrentNodeQuantity: 50, concurrentNodeQuantityUnlimited: false, priceSettings: ['FREE'], pointsAmount: 0, currencyAmount: 0, priceUnit: 'SET', enableStatus: 'ENABLED' }, { id: 10, productId: '10001', productSuite: 'PROJECT_PRIVATE_SAAS_USER_INCREMENT', salesForm: 'PRIVATE_INCREMENT', customerObject: 'PROJECT_DEPARTMENT', accountQuantity: 50, accountQuantityUnlimited: false, concurrentNodeQuantity: 50, concurrentNodeQuantityUnlimited: false, priceSettings: ['CURRENCY'], pointsAmount: 0, currencyAmount: 50000, priceUnit: 'YEAR', enableStatus: 'ENABLED' }, { id: 5, productId: '10001', productSuite: 'ENTERPRISE_PUBLIC_SAAS_LICENSE', salesForm: 'LEASE', customerObject: 'PERSONAL', accountQuantity: 50, accountQuantityUnlimited: false, concurrentNodeQuantity: 50, concurrentNodeQuantityUnlimited: false, priceSettings: ['CURRENCY'], pointsAmount: 0, currencyAmount: 15000, priceUnit: 'YEAR', enableStatus: 'ENABLED' }, { id: 6, productId: '10001', productSuite: 'ENTERPRISE_PUBLIC_SAAS_USER_INCREMENT', salesForm: 'PUBLIC_INCREMENT', customerObject: 'PERSONAL', accountQuantity: 50, accountQuantityUnlimited: false, concurrentNodeQuantity: 50, concurrentNodeQuantityUnlimited: false, priceSettings: ['CURRENCY'], pointsAmount: 0, currencyAmount: 5000, priceUnit: 'YEAR', enableStatus: 'ENABLED' }, { id: 7, productId: '10001', productSuite: 'ENTERPRISE_PUBLIC_SAAS_CLOUD', salesForm: 'CLOUD', customerObject: 'PERSONAL', accountQuantity: 1, accountQuantityUnlimited: false, concurrentNodeQuantity: 1, concurrentNodeQuantityUnlimited: false, priceSettings: ['FREE'], pointsAmount: 0, currencyAmount: 0, priceUnit: 'SET', enableStatus: 'ENABLED' } ], '10002': [ { id: 8, productId: '10002', productSuite: 'ENTERPRISE_PRIVATE_SAAS_LICENSE', salesForm: 'BUYOUT', customerObject: 'ENTERPRISE', accountQuantity: 30, accountQuantityUnlimited: false, concurrentNodeQuantity: 30, concurrentNodeQuantityUnlimited: false, priceSettings: ['POINTS', 'CURRENCY', 'AGREEMENT'], pointsAmount: 30000, currencyAmount: 30000, priceUnit: 'SET', enableStatus: 'ENABLED' }, { id: 11, productId: '10002', productSuite: 'PROJECT_PRIVATE_SAAS_OTA', salesForm: 'OTA', customerObject: 'PROJECT_DEPARTMENT', accountQuantity: 30, accountQuantityUnlimited: false, concurrentNodeQuantity: 30, concurrentNodeQuantityUnlimited: false, priceSettings: ['FREE'], pointsAmount: 0, currencyAmount: 0, priceUnit: 'SET', enableStatus: 'ENABLED' } ] // 映射工具 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 mapCustomerFromCN(val: string) { const map: Record<string, string> = { '企业': 'ENTERPRISE', '个人': 'PERSONAL', '项目部': 'PROJECT_DEPARTMENT' } return (map[val] as any) || val } function mapUnitFromCN(val: string) { const map: Record<string, string> = { '套': 'SET', '套/年': 'SET_PER_YEAR', '年': 'YEAR' } return (map[val] as any) || val } function mapPriceTypeFromCN(val: string): 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE' | undefined { const map: Record<string, any> = { '积分': 'POINTS', '货币': 'CURRENCY', '协议': 'AGREEMENT', '免费': 'FREE' } return map[val] } // 模拟产品名称数据 const mockProductNames: Record<string, string> = { '10001': '数字化产品A', '10002': '数字化产品B' '1': '数字化产品A', '2': '数字化产品B' } // 获取价格信息 // 获取价格信息(改为接口获取定价列表) const fetchProductData = async (productId: string) => { if (!productId) { ElMessage.warning('未提供产品ID') @@ -476,20 +607,53 @@ loading.value = true try { // 模拟API调用 await new Promise(resolve => setTimeout(resolve, 500)) // 获取产品名称 productName.value = mockProductNames[productId] || '未知产品' // 获取价格数据 priceList.value = mockPriceData[productId] || [] const res: any = await productPricingApi.listByProductId(productId) if (res?.code === 200) { const list = Array.isArray(res.data) ? res.data : [] const mapped: PriceItem[] = list.map((it: any) => { const priceSettings = String(it.priceType || '') .split(',') .map((t: string) => mapPriceTypeFromCN(t)) .filter(Boolean) const hasPoints = (priceSettings as string[]).includes('POINTS') const hasCurrency = (priceSettings as string[]).includes('CURRENCY') const accountUnlimited = it.accountLimit === '不限' const concurrentUnlimited = it.concurrentNodes === '不限' return { id: it.id, productId: String(it.productId || productId), productSuite: it.suiteName || '', salesForm: mapSalesFormFromCN(it.salesForm) as any, customerObject: mapCustomerFromCN(it.customerType) as any, accountQuantity: accountUnlimited ? 0 : Number(it.accountLimit || 0), accountQuantityUnlimited: accountUnlimited, concurrentNodeQuantity: concurrentUnlimited ? 0 : Number(it.concurrentNodes || 0), concurrentNodeQuantityUnlimited: concurrentUnlimited, priceSettings: priceSettings as any, pointsAmount: hasPoints ? Number(it.pointsPrice || 0) : 0, currencyAmount: hasCurrency ? Number(it.currencyPrice || 0) : 0, priceUnit: mapUnitFromCN(it.priceUnit) as any, enableStatus: it.isActive ? 'ENABLED' : 'DISABLED', } }) priceList.value = mapped if (priceList.value.length === 0) { ElMessage.warning('该产品暂无价格信息') } // 如果后端返回了产品名称可以在此赋值;否则保持现有标题格式 if (res.data && res.data.length > 0 && res.data[0].productName) { productName.value = res.data[0].productName } } else { priceList.value = [] ElMessage.error(res?.msg || '获取定价列表失败') } } catch (error) { console.error('获取价格数据失败:', error) priceList.value = [] ElMessage.error('获取价格数据失败') } finally { loading.value = false @@ -513,6 +677,11 @@ // 关闭弹窗 const handleClose = () => { visible.value = false showOrderPanel.value = false showOrderStatus.value = false showPricePanel.value = true orderSuites.value = [] selectedSuiteIds.value.clear() } // 套件选择处理 @@ -531,13 +700,57 @@ ElMessage.warning('请选择要订购的软件套件') return } // 获取选中的价格项 const selectedItems = priceList.value.filter(item => selectedSuiteIds.value.has(item.id)) emit('order', selectedItems) // 构建内嵌订购列表 orderSuites.value = selectedItems.map(it => ({ ...it, selected: true, quantity: 1, duration: 1, priceType: getDefaultPriceType(it as any) })) as any showOrderPanel.value = true showPricePanel.value = false } // 处理订购结果 const handleOrderResult = (selectedItems: any[]) => {} // 启用/停用按钮 function handleToggleEnableStatus(row: any) { // ... existing code ... } async function submitOrder() { const items = orderSuites.value.filter(s => s.selected) if (items.length === 0) { ElMessage.warning('请至少选择一个套件') return } // 检查是否有货币交易 const hasCurrency = items.some(item => item.priceType === 'CURRENCY') if (hasCurrency) { ElMessage.error('暂不支持货币交易,请选择积分或协议支付方式') return } // 打开订购对话框 showOrderDialog.value = true // 这里可对接实际下单接口;先做前端提示 // emit('order', items as any) // 模拟订单状态数据 orderStatus.value = { id: '4348442557619205545', productName: productHeader.value.name, provider: productHeader.value.provider, status: 'SUBMITTED', submitTime: new Date().toLocaleString(), applyTime: new Date().toLocaleString() } // 显示订单状态面板 showOrderPanel.value = false showPricePanel.value = false showOrderStatus.value = true } // 订购成功回调 @@ -681,7 +894,11 @@ padding: 6px 8px; text-align: center; vertical-align: middle; font-size: 12px; font-size: 16px; } tr th:first-child, td:first-child{ font-weight: 700; color: #303133; } th { @@ -699,7 +916,7 @@ th { background-color: #fafafa; font-weight: 500; font-size: 12px; font-size: 16px; color: #606266; } } @@ -712,7 +929,8 @@ } .feature-value { color: #303133; color: #606266; background: #fafafa; /* 行为灰色 */ } .order-methods { @@ -722,32 +940,36 @@ align-items: center; .method-item { font-size: 13px; font-size: 16px; color: #409eff; } } .price-info { display: flex; flex-direction: column; flex-direction: column; /* 竖排 */ gap: 8px; align-items: center; .price-item { display: flex; align-items: center; gap: 4px; align-items: flex-start; flex-direction: column; gap: 6px; .price-lable-icon{ display: flex; gap: 6px; } .price-label { font-size: 13px; font-size: 16px; color: #606266; } .price-value { font-weight: 500; font-size: 20px; &.points { color: #e6a23c; color: #e7900d; } &.currency { @@ -758,6 +980,8 @@ color: #67c23a; } } .price-icon.points { color: #e6a23c; } .price-icon.currency { color: #f56c6c; } } .add-checkbox { @@ -770,6 +994,7 @@ justify-content: center; align-items: center; padding: 8px 0; .add-cart-icon { color: #409eff; font-size:22px} } } } @@ -792,6 +1017,7 @@ } .product-price-dialog { :deep(.el-dialog__title) { font-size: 14px; font-weight: 600; @@ -800,6 +1026,7 @@ :deep(.el-dialog) { max-width: 95vw; min-width: 800px; height: 700px !important; } :deep(.el-dialog__body) { @@ -807,4 +1034,106 @@ overflow-y: auto; } } .order-header-card { :deep(.el-card__body) { padding: 10px; } .basic-grid { display:grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 30px 16px;} .grid-item { font-size: 16px; color:#606266; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .label { color:#606266; } .value { color:#303133; } .value.strong { font-weight: 600; } margin-bottom: 20px; margin-top: 10px; } .points-info-card { :deep(.el-card__body) { padding: 8px 10px; } .points-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px 12px; .points-item { font-size: 12px; color: #606266; } .label { color: #606266; } .value { font-weight: 600; color: #303133; } .value.emphasis { color: #409eff; } .value.warn { color: #e6a23c; } } } .selected-suites-card { :deep(.el-card__body) { padding: 8px; } } .suite-table-wrapper { overflow-x: auto; width: 100%; } .suite-table { width: 100%; border-collapse: collapse; background: #fff; table-layout: fixed; } .suite-table th, .suite-table td { border: 1px solid #e4e7ed; padding: 6px 8px; vertical-align: middle; font-size: 16px; } .suite-table thead th { background: #f0f2f5; color: #303133; font-weight: 600; font-size: 16px;} .table-toolbar { display:flex; justify-content: space-between; align-items:center; padding: 4px 0 8px; } .toolbar-left { display:flex; align-items:center; gap: 12px; width: 100%;justify-content: space-between;} .toolbar-right { display:flex; align-items:center; gap: 12px; } .summary-info { font-size: 14px; color: #606266; } .summary-count { color: #409eff; font-weight: 600; } .summary-points { color: #e6a23c; font-weight: 600; } .suite-table .th-left { text-align: center; width: 21%;} .suite-table .th-center { text-align: center; } .cell-detail .detail-content { display: inline-flex; align-items: center; gap: 8px; } .suite-name { font-weight: 600; color: #303133; } .cell-spec .spec-line { color: #606266; font-size: 16px; line-height: 20px; } .cell-spec-gg {display: flex; flex-direction: column; gap: 16px; .spec-line-gg{ display: flex; gap: 30px; } } .cell-price .price-inline { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; } .cell-price .price-tag.points { color: #E6A23C; font-weight: 600; } .cell-price .price-tag.currency { color: #F56C6C; font-weight: 600; } .cell-price .price-free { color: #67C23A; font-weight: 600; } .cell-price .price-agreement { color: #409EFF; } .cell-summary .summary-item { font-size: 14px; color: #606266; line-height: 1.5; } .order-status-panel { .steps-wrapper { padding: 6px 8px 0 8px; } .result-text { text-align: center; margin: 18px 0 12px; font-size: 18px; color: #303133; } .order-info-card { :deep(.el-card__body) { padding: 0; } } .order-info-title { background: #f5f7fa; padding: 12px 16px; font-weight: 600; border-bottom: 1px solid #ebeef5; } .order-info-table { width: 100%; border-collapse: collapse; td { padding: 12px 16px; border-bottom: 1px solid #ebeef5; } .label { width: 120px; color: #606266; } .value { color: #303133; } .link { text-align: right; } } } .order-summary { padding: 10px 0; display:flex; align-items:center; justify-content:flex-end; gap: 8px; } .order-summary .summary-label { color:#606266; } .order-summary .summary-value { font-weight:600; } .order-summary .summary-value.highlight{ color:#409eff; } .order-summary .summary-value.total{ color:#e6a23c; } .order-summary .summary-value.currency{ color:#f56c6c; } </style>