From ade0cb41b9850438e78e06bcdf2d6590c41d6bc1 Mon Sep 17 00:00:00 2001 From: seatonwan9 Date: 星期二, 19 八月 2025 18:15:30 +0800 Subject: [PATCH] 订购管理 --- src/views/productManage/productPriceViewer/index.vue | 1035 +++++++++++++++++++++++++++++++++++++++++++------------- 1 files changed, 788 insertions(+), 247 deletions(-) diff --git a/src/views/productManage/productPriceViewer/index.vue b/src/views/productManage/productPriceViewer/index.vue index d1649a2..a559117 100644 --- a/src/views/productManage/productPriceViewer/index.vue +++ b/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,174 @@ </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="true" + size="large" + @change="(value) => handleQuantityChange(suite.id, value?value:1)" + /> + </td> + <td class="th-center"> + <el-input-number + v-model="suite.duration" + :min="1" + :max="100" + :controls="true" + size="large" + :disabled="suite.priceType === 'FREE'" + @change="(value) => handleDurationChange(suite.id, value?value:1)" + /> + </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 +339,33 @@ </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="returnPricePanel">杩斿洖浠锋牸瀵规瘮</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 cartApi from '@/api/cartApi' +import orderApi from '@/api/orderApi' +import { ShoppingCart, Coin, Money } from '@element-plus/icons-vue' + +const route = useRoute() interface PriceItem { id: number @@ -219,11 +395,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 +420,152 @@ 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) +// 妯℃嫙鐢ㄦ埛淇℃伅锛堝疄闄呭簲浠庣敤鎴风姸鎬佽幏鍙栵級 +const currentUserId = ref(1) +const currentUnitId = ref(1) +type PriceTypeKey = 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE' +interface OrderSuite extends PriceItem { + selected: boolean + quantity: number + duration: number + priceType?: PriceTypeKey +} +const orderSuites = ref<OrderSuite[]>([]) + +// 璐墿杞︾浉鍏崇姸鎬� +const cartItems = ref<any[]>([]) +const cartLoading = ref(false) +const idempotencyToken = ref<string>('') + +// 璁㈠崟鐘舵�佺浉鍏� +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 = async (index: number) => { + const suite = orderSuites.value[index] + if (!suite) return + try { + await removeFromCart(suite.id) + orderSuites.value.splice(index, 1) + } catch (e) { + } +} + +// 璁㈣喘鎴愬姛鍥炶皟 +const handleOrderSuccess = (orderData: any) => { + console.log('璁㈣喘鎴愬姛:', orderData) + ElMessage.success('璁㈣喘鐢宠鎻愪氦鎴愬姛锛�') + // 娓呯┖閫夋嫨鐘舵�� + selectedSuiteIds.value.clear() + // 鍏抽棴浠锋牸鏌ョ湅鍣� + visible.value = false +} // 寮圭獥鏍囬 const dialogTitle = computed(() => { @@ -277,197 +605,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': '鏁板瓧鍖栦骇鍝丄', - '10002': '鏁板瓧鍖栦骇鍝丅' + '1': '鏁板瓧鍖栦骇鍝丄', + '2': '鏁板瓧鍖栦骇鍝丅' } -// 鑾峰彇浠锋牸淇℃伅 +// 鑾峰彇浠锋牸淇℃伅锛堟敼涓烘帴鍙h幏鍙栧畾浠峰垪琛級 const fetchProductData = async (productId: string) => { if (!productId) { ElMessage.warning('鏈彁渚涗骇鍝両D') @@ -476,20 +650,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 +720,11 @@ // 鍏抽棴寮圭獥 const handleClose = () => { visible.value = false + showOrderPanel.value = false + showOrderStatus.value = false + showPricePanel.value = true + orderSuites.value = [] + selectedSuiteIds.value.clear() } // 濂椾欢閫夋嫨澶勭悊 @@ -520,34 +732,247 @@ const isChecked = Boolean(checked) if (isChecked) { selectedSuiteIds.value.add(suiteId) + // 娣诲姞鍒拌喘鐗╄溅 + const priceItem = priceList.value.find(item => item.id === suiteId) + if (priceItem) { + addToCart(priceItem) + } } else { selectedSuiteIds.value.delete(suiteId) + // 浠庤喘鐗╄溅绉婚櫎 + removeFromCart(suiteId) } } // 绔嬪嵆璁㈣喘 const handleOrder = () => { - if (selectedSuiteIds.value.size === 0) { - ElMessage.warning('璇烽�夋嫨瑕佽璐殑杞欢濂椾欢') - return - } - - // 鑾峰彇閫変腑鐨勪环鏍奸」 - const selectedItems = priceList.value.filter(item => selectedSuiteIds.value.has(item.id)) - emit('order', selectedItems) - - // 鎵撳紑璁㈣喘瀵硅瘽妗� - showOrderDialog.value = true + // 鏌ヨ璐墿杞︿俊鎭繘琛屽睍绀� + fetchCartItems() + // 鑾峰彇涓�娆℃�ч槻閲嶅鎻愪氦token + orderApi.getIdempotencyToken(currentUserId.value).then((res: any) => { + if (res?.code === 200) { + idempotencyToken.value = res.data as string + } + }).catch(() => {}) + showOrderPanel.value = true + showPricePanel.value = false } -// 璁㈣喘鎴愬姛鍥炶皟 -const handleOrderSuccess = (orderData: any) => { - console.log('璁㈣喘鎴愬姛:', orderData) - ElMessage.success('璁㈣喘鐢宠鎻愪氦鎴愬姛锛�') - // 娓呯┖閫夋嫨鐘舵�� - selectedSuiteIds.value.clear() - // 鍏抽棴浠锋牸鏌ョ湅鍣� - visible.value = false +// 澶勭悊璁㈣喘缁撴灉 +const handleOrderResult = (selectedItems: any[]) => {} + +// 鍚敤/鍋滅敤鎸夐挳 +function handleToggleEnableStatus(row: any) { + // ... existing code ... +} + +function returnPricePanel() { + showOrderPanel.value = false + showOrderStatus.value = false + showPricePanel.value = true +} + +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 + } + + if (!idempotencyToken.value) { + ElMessage.error('涓嬪崟Token鑾峰彇澶辫触锛岃杩斿洖閲嶈瘯') + return + } + + // 缁勮鍒涘缓璁㈠崟鍙傛暟锛圕reateOrderDTO锛� + const payload = { + userId: currentUserId.value, + unitId: currentUnitId.value, + productName: productHeader.value.name, + providerName: productHeader.value.provider, + providerId: 3, + paymentType: '绉垎', + buyerRemarks: '', + items: items.map(it => ({ + pricingId: it.id, + productId: Number(it.productId), + suiteName: getProductSuiteText(it.productSuite), + salesForm: getSalesFormText(it.salesForm), + customerType: getCustomerObjectText(it.customerObject), + accountLimit: it.accountQuantityUnlimited ? '涓嶉檺' : String(it.accountQuantity), + concurrentNodes: it.concurrentNodeQuantityUnlimited ? '涓嶉檺' : String(it.concurrentNodeQuantity), + priceType: (it.priceType === 'POINTS' ? '绉垎' : it.priceType === 'CURRENCY' ? '璐у竵' : it.priceType === 'AGREEMENT' ? '鍗忚' : '鍏嶈垂'), + priceUnit: getPriceUnitText(it.priceUnit), + unitPrice: (it.priceType === 'POINTS' ? it.pointsAmount : it.currencyAmount) || 0, + quantity: it.quantity, + duration: it.duration, + totalPrice: undefined, + providerId: 3, + providerName: productHeader.value.provider, + remarks: '' + })) + } + + try { + const res: any = await orderApi.createOrder(payload, idempotencyToken.value) + if (res?.code === 200) { + ElMessage.success('璁㈠崟鍒涘缓鎴愬姛') + const data = res.data || {} + orderStatus.value = { + id: data.orderId || '鈥�', + productName: data.productName || productHeader.value.name, + provider: data.providerName || productHeader.value.provider, + status: 'SUBMITTED', + submitTime: data.applyTime ? String(data.applyTime) : new Date().toLocaleString(), + applyTime: data.applyTime ? String(data.applyTime) : new Date().toLocaleString() + } + // 娓呯┖璐墿杞︼紙鍚庣 + 鏈湴鐘舵�侊級 + try { + const clearRes: any = await cartApi.clearCart(currentUserId.value, currentUnitId.value) + if (clearRes?.code === 200) { + cartItems.value = [] + orderSuites.value = [] + selectedSuiteIds.value.clear() + selectAllChecked.value = false + } + } catch (e) { /* ignore */ } + // 鏄剧ず璁㈠崟鐘舵�侀潰鏉� + showOrderPanel.value = false + showPricePanel.value = false + showOrderStatus.value = true + } else { + ElMessage.error(res?.msg || '鍒涘缓璁㈠崟澶辫触') + } + } catch (e) { + ElMessage.error('鍒涘缓璁㈠崟澶辫触') + } +} + +// 璐墿杞︾浉鍏虫柟娉� +const addToCart = async (priceItem: PriceItem) => { + try { + const cartData = { + pricingId: priceItem.id, + productId: Number(priceItem.productId), + productName: productName.value, + suiteName: getProductSuiteText(priceItem.productSuite), + salesForm: getSalesFormText(priceItem.salesForm), + customerType: getCustomerObjectText(priceItem.customerObject), + accountLimit: priceItem.accountQuantityUnlimited ? '涓嶉檺' : String(priceItem.accountQuantity), + concurrentNodes: priceItem.concurrentNodeQuantityUnlimited ? '涓嶉檺' : String(priceItem.concurrentNodeQuantity), + priceType: priceItem.priceSettings.map(item => getPriceSettingText(item)).join(','), + priceUnit: getPriceUnitText(priceItem.priceUnit), + unitPrice: priceItem.pointsAmount || priceItem.currencyAmount || 0, + quantity: 1, + duration: 1 + } + + const res: any = await cartApi.addToCart(cartData, currentUserId.value, currentUnitId.value) + if (res?.code === 200) { + ElMessage.success('宸叉坊鍔犲埌璐墿杞�') + } else { + ElMessage.error(res?.msg || '娣诲姞鍒拌喘鐗╄溅澶辫触') + } + } catch (error) { + console.error('娣诲姞鍒拌喘鐗╄溅澶辫触:', error) + ElMessage.error('娣诲姞鍒拌喘鐗╄溅澶辫触') + } +} + +const removeFromCart = async (pricingId: number) => { + try { + const res: any = await cartApi.removeFromCart(currentUserId.value, currentUnitId.value, pricingId) + if (res?.code === 200) { + ElMessage.success('宸蹭粠璐墿杞︾Щ闄�') + } else { + ElMessage.error(res?.msg || '浠庤喘鐗╄溅绉婚櫎澶辫触') + } + } catch (error) { + console.error('浠庤喘鐗╄溅绉婚櫎澶辫触:', error) + ElMessage.error('浠庤喘鐗╄溅绉婚櫎澶辫触') + } +} + +const updateCartItem = async (pricingId: number, quantity: number, duration: number) => { + try { + // 杩欓噷闇�瑕佹牴鎹疄闄呮帴鍙h皟鏁达紝鍙兘闇�瑕佷紶閫掓洿澶氬弬鏁� + const res: any = await cartApi.updateCartItem(currentUserId.value, currentUnitId.value, pricingId, quantity) + if (res?.code === 200) { + ElMessage.success('璐墿杞﹀凡鏇存柊') + } else { + ElMessage.error(res?.msg || '鏇存柊璐墿杞﹀け璐�') + } + } catch (error) { + console.error('鏇存柊璐墿杞﹀け璐�:', error) + ElMessage.error('鏇存柊璐墿杞﹀け璐�') + } +} + +const fetchCartItems = async () => { + cartLoading.value = true + try { + const res: any = await cartApi.getCartItems(currentUserId.value, currentUnitId.value) + if (res?.code === 200) { + cartItems.value = res.data || [] + // 灏嗚喘鐗╄溅鏁版嵁杞崲涓鸿鍗曞浠舵牸寮� + orderSuites.value = cartItems.value.map((item: any) => ({ + id: item.pricingId, + productId: String(item.productId), + productSuite: item.suiteName, + salesForm: mapSalesFormFromCN(item.salesForm), + customerObject: mapCustomerFromCN(item.customerType), + accountQuantity: item.accountLimit === '涓嶉檺' ? 0 : Number(item.accountLimit), + accountQuantityUnlimited: item.accountLimit === '涓嶉檺', + concurrentNodeQuantity: item.concurrentNodes === '涓嶉檺' ? 0 : Number(item.concurrentNodes), + concurrentNodeQuantityUnlimited: item.concurrentNodes === '涓嶉檺', + priceSettings: item.priceType.split(',').map((t: string) => mapPriceTypeFromCN(t)).filter(Boolean), + pointsAmount: item.priceType.includes('绉垎') ? item.unitPrice : 0, + currencyAmount: item.priceType.includes('璐у竵') ? item.unitPrice : 0, + priceUnit: mapUnitFromCN(item.priceUnit), + enableStatus: 'ENABLED', + selected: true, + quantity: item.quantity || 1, + duration: item.duration || 1, + priceType: getDefaultPriceType({ + priceSettings: item.priceType.split(',').map((t: string) => mapPriceTypeFromCN(t)).filter(Boolean) + } as any) + })) as any + } else { + orderSuites.value = [] + ElMessage.error(res?.msg || '鑾峰彇璐墿杞︿俊鎭け璐�') + } + } catch (error) { + console.error('鑾峰彇璐墿杞︿俊鎭け璐�:', error) + orderSuites.value = [] + ElMessage.error('鑾峰彇璐墿杞︿俊鎭け璐�') + } finally { + cartLoading.value = false + } +} + +// 澶勭悊鏁伴噺鍙樺寲 +const handleQuantityChange = async (pricingId: number, quantity: number) => { + try { + await updateCartItem(pricingId, quantity, 1) // 鏆傛椂浼犻�掗粯璁uration涓�1 + } catch (error) { + console.error('鏇存柊鏁伴噺澶辫触:', error) + } +} + +// 澶勭悊骞撮檺鍙樺寲 +const handleDurationChange = async (pricingId: number, duration: number) => { + try { + // 杩欓噷鍙兘闇�瑕佹牴鎹疄闄呮帴鍙h皟鏁达紝浼犻�抎uration鍙傛暟 + await updateCartItem(pricingId, 1, duration) // 鏆傛椂浼犻�掗粯璁uantity涓�1 + } catch (error) { + console.error('鏇存柊骞撮檺澶辫触:', error) + } } // 鑾峰彇閿�鍞舰寮忔枃鏈� @@ -681,7 +1106,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 +1128,7 @@ th { background-color: #fafafa; font-weight: 500; - font-size: 12px; + font-size: 16px; color: #606266; } } @@ -712,7 +1141,8 @@ } .feature-value { - color: #303133; + color: #606266; + background: #fafafa; /* 琛屼负鐏拌壊 */ } .order-methods { @@ -722,42 +1152,48 @@ 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 { - color: #f56c6c; + color: #e7900d; } &.free { color: #67c23a; } } + .price-icon.points { color: #e6a23c; } + .price-icon.currency { color: #e7900d; } } .add-checkbox { @@ -770,6 +1206,7 @@ justify-content: center; align-items: center; padding: 8px 0; + .add-cart-icon { color: #409eff; font-size:22px} } } } @@ -792,6 +1229,7 @@ } .product-price-dialog { + :deep(.el-dialog__title) { font-size: 14px; font-weight: 600; @@ -800,6 +1238,7 @@ :deep(.el-dialog) { max-width: 95vw; min-width: 800px; + height: 700px !important; } :deep(.el-dialog__body) { @@ -807,4 +1246,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> -- Gitblit v1.8.0