From 954446f5a88735b0033b49492ea7b658f2bb9e07 Mon Sep 17 00:00:00 2001 From: seatonwan9 Date: 星期二, 19 八月 2025 01:37:53 +0800 Subject: [PATCH] 定价管理-产品价格 --- src/views/productManage/price/index.vue | 454 ++++++++++++++++++++++++++++++++++++++------------------ 1 files changed, 306 insertions(+), 148 deletions(-) diff --git a/src/views/productManage/price/index.vue b/src/views/productManage/price/index.vue index 0cecbf8..55821cb 100644 --- a/src/views/productManage/price/index.vue +++ b/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: '鏁板瓧鍖栦骇鍝丄', submitUnit: '涓氦涓�鍏眬', submitter: '寮犱笁', @@ -314,8 +310,8 @@ description: '鏈骇鍝佸畾浣嶄负浠ュ缓璁炬湡BIM鏁板瓧璧勪骇浣滀负鏁板瓧搴曠洏锛岀粨鍚堥」鐩繍钀ョ淮淇濋渶姹傜殑瀹炴椂鎬с�佷氦浜掓�с�佷究鎹锋�х殑涓夌淮鍙鍖栬繍缁寸鐞嗙郴缁熴�傜郴缁熸彁渚涢」鐩暟瀛楀寲銆佹櫤鑳藉寲杩愮淮绠$悊鍔熻兘锛岃兘澶熻В鍐冲缓绛戣繍琛岀淮鎶ょ鐞嗕腑鐨勫疄闄呴棶棰橈紝瀹炵幇淇℃伅蹇�熸暣鍚堜笌鏌ヨ銆佷俊鎭湁鏁堝叡浜笌浼犻�掞紝鎻愬崌椤圭洰缁煎悎绠$悊涓庣淮鎶ゆ按骞炽��', shelfStatus: '寰呬笂鏋�' }, - '10002': { - id: '10002', + '2': { + id: '2', name: '鏁板瓧鍖栦骇鍝丅', 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鐗圤TA鏈嶅姟', value: 'ENTERPRISE_PRIVATE_SAAS_OTA' }, - { label: '浼佷笟绉佹湁SaaS鐗堢敤鎴峰閲忓寘', value: 'ENTERPRISE_PRIVATE_SAAS_USER_INCREMENT' }, - { label: '浼佷笟鍏湁SaaS鐗堣鍙�', value: 'ENTERPRISE_PUBLIC_SAAS_LICENSE' }, - { label: '浼佷笟鍏湁SaaS鐗圤TA鏈嶅姟', 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鐗圤TA鏈嶅姟', value: '浼佷笟绉佹湁SaaS鐗圤TA鏈嶅姟' }, + { label: '浼佷笟绉佹湁SaaS鐗堢敤鎴峰閲忓寘', value: '浼佷笟绉佹湁SaaS鐗堢敤鎴峰閲忓寘' }, + { label: '浼佷笟鍏湁SaaS鐗堣鍙�', value: '浼佷笟鍏湁SaaS鐗堣鍙�' }, + { label: '浼佷笟鍏湁SaaS鐗圤TA鏈嶅姟', value: '浼佷笟鍏湁SaaS鐗圤TA鏈嶅姟' }, + { 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('鏈彁渚涗骇鍝両D锛屾棤娉曞姞杞戒骇鍝佽鎯�') 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) => { + // 鎸塸roductSuite鍒嗙粍 + 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('鏈彁渚涗骇鍝両D锛屾棤娉曚繚瀛樺畾浠�') + 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; } -- Gitblit v1.8.0