From b3fedd4221b807a07058be9d5d5d8ba8998adbcb Mon Sep 17 00:00:00 2001 From: Bang Hu <hu_bang@hotmail.com> Date: 星期四, 11 九月 2025 21:35:31 +0800 Subject: [PATCH] Bug修改代码提交 --- src/views/productManage/productPriceViewer/index.vue | 1902 ++++++++++++++++++++++++++++++++++++++++++++++------------- 1 files changed, 1,484 insertions(+), 418 deletions(-) diff --git a/src/views/productManage/productPriceViewer/index.vue b/src/views/productManage/productPriceViewer/index.vue index d1649a2..4caf97c 100644 --- a/src/views/productManage/productPriceViewer/index.vue +++ b/src/views/productManage/productPriceViewer/index.vue @@ -1,15 +1,8 @@ <template> - <el-dialog - v-model="visible" - :title="dialogTitle" - :before-close="handleClose" - destroy-on-close - class="product-price-dialog" - :width="dialogWidth" - > + <div> <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" @@ -30,18 +23,19 @@ {{ group.productSuite }} </th> </tr> - <tr class="sub-header"> - <th>閿�鍞舰寮�</th> - <th - v-for="priceItem in tabData" - :key="priceItem.id" - :colspan="getColspan(priceItem)" - > - {{ getSalesFormText(priceItem.salesForm) }} - </th> - </tr> </thead> <tbody> + <tr> + <td class="feature-label">閿�鍞舰寮�</td> + <td + v-for="priceItem in tabData" + :key="priceItem.id" + :colspan="getColspan(priceItem)" + class="feature-value" + > + {{ getSalesFormText(priceItem.salesForm) }} + </td> + </tr> <tr> <td class="feature-label">瀹㈡埛瀵硅薄</td> <td @@ -83,15 +77,7 @@ :colspan="getColspan(priceItem)" class="feature-value" > - <div class="order-methods"> - <span - v-for="method in priceItem.priceSettings" - :key="method" - class="method-item" - > - 鉁� {{ getPriceSettingText(method) }} - </span> - </div> + {{ priceItem.priceSettings.map((m:any) => getPriceSettingText(m)).join('銆�') }} </td> </tr> <tr> @@ -102,40 +88,7 @@ :colspan="getColspan(priceItem)" class="feature-value" > - <div class="price-info"> - <div - 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> - <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> - <div - v-if="priceItem.priceSettings.includes('FREE')" - class="price-item" - > - <span class="price-label">楼璐у竵:</span> - <span class="price-value free">鍏嶈垂</span> - </div> - <div - v-if="priceItem.priceSettings.includes('AGREEMENT')" - class="price-item" - > - <span class="price-label">鍗忚:</span> - <span class="price-value agreement">姣弡{ getPriceUnitText(priceItem.priceUnit) }}</span> - </div> - </div> + {{ renderPrice(priceItem) }} </td> </tr> <tr> @@ -151,7 +104,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,34 +115,212 @@ </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.industrialChainName }}</span> + </div> + <div class="grid-item"> + <span class="label">鎻愪緵鑰咃細</span><span class="value">{{ productHeader.submissionUnit }}</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.unitName }}</span> + </div> + <div class="grid-item"> + <span class="label">閮ㄩ棬锛�</span><span class="value">{{ userInfo.departmentName }}</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-detail">璇︽儏</th> + <th class="th-spec">瑙勬牸</th> + <th class="th-price">鍗曚环</th> + <th class="th-quantity">鏁伴噺</th> + <th class="th-years">骞撮檺</th> + <th class="th-action">鎿嶄綔</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">閿�鍞舰寮忥細<span class="spec-value">{{ getSalesFormText(suite.salesForm) }}</span></div> + <div class="spec-line">瀹㈡埛瀵硅薄锛�<span class="spec-value">{{ getCustomerObjectText(suite.customerObject) }}</span></div> + </div> + <div class="spec-line-gg"> + <div class="spec-line">璐︽埛鏁伴噺锛�<span class="spec-value">{{ suite.accountQuantityUnlimited ? '涓嶉檺' : suite.accountQuantity }}</span></div> + <div class="spec-line">骞跺彂鑺傜偣鏁帮細<span class="spec-value">{{ suite.concurrentNodeQuantityUnlimited ? '涓嶉檺' : suite.concurrentNodeQuantity }}</span></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" + class="quantity-input" + @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" + class="duration-input" + :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 }} + <el-link type="primary" @click="goBuyerCenter" style="margin-left: 8px">鐐瑰嚮鏌ョ湅</el-link> + </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"> <el-empty description="鏆傛棤浠锋牸淇℃伅" /> </div> </div> + <div class="footer" v-if="showPricePanel"> + <el-button type="primary" @click="handleOrder">绔嬪嵆璁㈣喘</el-button> + </div> + <div class="footer" v-if="showOrderPanel"> + <el-button @click="returnPricePanel">杩斿洖浠锋牸瀵规瘮</el-button> + <el-button type="primary" @click="submitOrder">鎻愪氦鐢宠</el-button> + </div> + </div> - <template #footer> - <span class="dialog-footer"> - <el-button @click="handleClose">鍏抽棴</el-button> - <el-button type="primary" @click="handleOrder">绔嬪嵆璁㈣喘</el-button> - </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 { ref, watch, computed, onMounted } from 'vue' +import { useRouter } from 'vue-router' 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 { useUserInfo } from '@/stores/modules/userInfo' +import { queryUserDetail } from '@/api/userInfo' +import productApi from '@/api/productApi' +import workFlowApi from '@/api/workFlowApi' + +const route = useRoute() +const router = useRouter() interface PriceItem { id: number @@ -208,46 +339,199 @@ enableStatus: 'ENABLED' | 'DISABLED' } -interface Props { - modelValue: boolean - productId?: string - width?: string -} - -interface Emits { - (e: 'update:modelValue', value: boolean): void - (e: 'order', selectedItems: PriceItem[]): void -} - -const props = withDefaults(defineProps<Props>(), { - modelValue: false, - productId: '', - width: '90%' +// 鍏煎 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 emit = defineEmits<Emits>() - -const visible = computed({ - get: () => props.modelValue, - set: (value) => emit('update:modelValue', value) -}) const loading = ref(false) const activeTab = ref('enterprise') 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 userStore = useUserInfo() +const currentUserId = computed(() => userStore.getUserId || userStore.getUserInfo?.userId) +const currentUnitId = computed(() => userStore.getUnitId || userStore.getUserInfo?.unitId || '') + +onMounted(async () => { + if (!currentUserId.value) { + try { + const res: any = await queryUserDetail() + if (res?.code === 200 && res.data) { + userStore.updateUserDetail(res.data) + // currentUserId.value = res.data.id || res.data.userId + console.log(currentUserId.value) + userInfo.value = res.data + } else { + ElMessage.error(res?.msg || '鏃犳硶鑾峰彇鐢ㄦ埛淇℃伅锛岃鍏堢櫥褰�') + return + } + } catch (e) { + console.error('鑾峰彇鐢ㄦ埛璇︽儏澶辫触:', e) + ElMessage.error('鑾峰彇鐢ㄦ埛淇℃伅澶辫触锛岃绋嶅悗閲嶈瘯') + return + } + }else{ + userInfo.value = userStore.getUserInfos + } + // 鑾峰彇璁㈣喘淇℃伅 + fetchProductData(currentProductId.value ? currentProductId.value : '') +}) +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 goBuyerCenter = () => { + router.push({ name: 'tradeBuyerCenter', query: { t: String(Date.now()) } }) +} + +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: '', + industrialChainName: '', + submissionUnit: '', + createUserId: '' +}) +const userInfo = ref({ + name: '', + departmentName: '', + unitName: '' +}) + +// 鍏ㄩ�� +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(String(suite.id)) + orderSuites.value.splice(index, 1) + selectedSuiteIds.value.delete(suite.id) + } catch (e) { + } +} + +// 璁㈣喘鎴愬姛鍥炶皟 +const handleOrderSuccess = (orderData: any) => { + console.log('璁㈣喘鎴愬姛:', orderData) + ElMessage.success('璁㈣喘鐢宠鎻愪氦鎴愬姛锛�') + // 娓呯┖閫夋嫨鐘舵�� + selectedSuiteIds.value.clear() +} // 寮圭獥鏍囬 const dialogTitle = computed(() => { return `銆�${productName.value}銆戜骇鍝佸畾浠穈 }) -// 寮圭獥瀹藉害 -const dialogWidth = computed(() => { - return props.width -}) // 鎸夊鎴峰璞″垎缁勭殑浠锋牸鏁版嵁 const groupedPriceData = computed(() => { @@ -277,197 +561,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,78 +606,395 @@ 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.listBycondition({ productId: productId,isActive: true }) + 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 } + //鑾峰彇浜у搧璇︽儏淇℃伅 + try { + // productDetail.value = mockProductMap[productId] || null + const data = { + id: productId + } + const detailRes: any = await productApi.getProductById(data) + if (detailRes?.code === 200) { + productHeader.value = detailRes.data || { name: '', industrialChainName: '', submissionUnit: '', createUserId: '' } + } else { + productHeader.value = { name: '', industrialChainName: '', submissionUnit: '', createUserId: '' } + ElMessage.error(detailRes?.msg || '鑾峰彇浜у搧璇︽儏澶辫触') + } + } catch (e) { + productHeader.value = { name: '', industrialChainName: '', submissionUnit: '', createUserId: '' } + ElMessage.error('鑾峰彇浜у搧璇︽儏澶辫触') + } finally { + loading.value = false + } + // 淇敼activeTab 榛樿鍊� + if(priceList.value.length > 0){ + + activeTab.value = groupedPriceData.value.hasOwnProperty('enterprise') ? 'enterprise' + : groupedPriceData.value.hasOwnProperty('project') ? 'project' : 'personal' + } + } // 鐩戝惉浜у搧ID鍙樺寲 -watch(() => props.productId, (newProductId) => { - if (newProductId && visible.value) { +watch(currentProductId, (newProductId) => { + if (newProductId) { fetchProductData(newProductId) } }) - -// 鐩戝惉寮圭獥鏄剧ず鐘舵�� -watch(() => visible.value, (newVisible) => { - if (newVisible && props.productId) { - fetchProductData(props.productId) - } -}) - -// 鍏抽棴寮圭獥 -const handleClose = () => { - visible.value = false -} // 濂椾欢閫夋嫨澶勭悊 const handleSuiteSelect = (suiteId: number, checked: any) => { 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(String(suiteId)) } } // 绔嬪嵆璁㈣喘 const handleOrder = () => { - if (selectedSuiteIds.value.size === 0) { - ElMessage.warning('璇烽�夋嫨瑕佽璐殑杞欢濂椾欢') + // 鏌ヨ璐墿杞︿俊鎭繘琛屽睍绀� + 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 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 } - // 鑾峰彇閫変腑鐨勪环鏍奸」 - const selectedItems = priceList.value.filter(item => selectedSuiteIds.value.has(item.id)) - emit('order', selectedItems) - - // 鎵撳紑璁㈣喘瀵硅瘽妗� - showOrderDialog.value = true + if (!idempotencyToken.value) { + ElMessage.error('涓嬪崟Token鑾峰彇澶辫触锛岃杩斿洖閲嶈瘯') + return + } + const hasPoints = items.some(item => item.priceType === 'POINTS') + const hasAGREEMENT = items.some(item => item.priceType === 'AGREEMENT') + let paymentType = '' + if(hasPoints){ + paymentType = '绉垎' + }else{ + paymentType = '鍗忚' + } + let processdefId: string = '' + const type = hasAGREEMENT ? 'trade_agreement' : 'trade_point'; + // 鑾峰彇宸ヤ綔娴佸弬鏁� + const wkParamsRes: any = await workFlowApi.getWorkFlowParams({ + type: type, + // unitId: '1', + businessKey: type + }) + if(wkParamsRes?.code === 200 && wkParamsRes.data?.processTemplateId){ + processdefId = wkParamsRes.data.processTemplateId + }else { + ElMessage.error('鑾峰彇宸ヤ綔娴佸弬鏁板け璐�!') + return + } + + // 缁勮鍒涘缓璁㈠崟鍙傛暟锛圕reateOrderDTO锛� + const payload = { + userId: currentUserId.value, + unitId: currentUnitId.value, + productName: productHeader.value.name, + providerName: productHeader.value.submissionUnit, + providerId: productHeader.value.createUserId, + paymentType: paymentType, + buyerRemarks: '', + processdefId: processdefId, + items: items.map(it => ({ + pricingId: it.id, + productId: 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: productHeader.value.createUserId, + providerName: productHeader.value.submissionUnit, + 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.submissionUnit, + status: 'SUBMITTED', + submitTime: data.applyTime ? String(data.applyTime) : new Date().toLocaleString(), + applyTime: data.applyTime ? String(data.applyTime) : new Date().toLocaleString() + } + // 璋冪敤宸ヤ綔娴佹帴鍙e彂璧峰鎵规祦绋嬶紝鎷垮埌娴佺▼瀹炰緥ID鍚庡洖鍐欒鍗晈orkflow_id + // 鏀惧悗绔鐞� + // try { + // // 鏍规嵁鏄惁鍖呭惈鍗忚鏄庣粏锛岄厤缃笉鍚屾祦绋嬪畾涔変笌涓氬姟Key锛堝厛鐢ㄩ潤鎬佸�煎崰浣嶏級 + // // const processdefId = hasAGREEMENT ? 'Process_Agreement_Static' : 'Process_Points_Static' + // const businessKey = hasAGREEMENT ? 'agreement_biz_key' : 'points_biz_key' + // const type = hasAGREEMENT ? 'trade_agreement' : 'trade_point'; + // // 鑾峰彇宸ヤ綔娴佸弬鏁� + // const wkParamsRes: any = await workFlowApi.getWorkFlowParams({ + // type: type, + // unitId: '1' + // }) + // if(wkParamsRes?.code === 200 && wkParamsRes.data?.processTemplateId){ + // const wfRes: any = await workFlowApi.startWorkflowAndComplete({ + // processdefId: wkParamsRes.data.processTemplateId, + // userid: String(currentUserId.value || ''), + // businessKey: businessKey + // }) + // if (wfRes?.code === 200 && wfRes.data?.processinstId) { + // await orderApi.updateWorkflowId(data.orderId, wfRes.data.processinstId) + // } + // } + // + // } catch (e) { + // console.warn('鍚姩宸ヤ綔娴佸け璐ユ垨鏇存柊workflow_id澶辫触', e) + // } + + // 娓呯┖璐墿杞︼紙鍚庣 + 鏈湴鐘舵�侊級 + try { + const clearRes: any = await cartApi.clearCart(currentUserId.value, currentUnitId.value,currentProductId.value ? currentProductId.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 handleOrderSuccess = (orderData: any) => { - console.log('璁㈣喘鎴愬姛:', orderData) - ElMessage.success('璁㈣喘鐢宠鎻愪氦鎴愬姛锛�') - // 娓呯┖閫夋嫨鐘舵�� - selectedSuiteIds.value.clear() - // 鍏抽棴浠锋牸鏌ョ湅鍣� - visible.value = false +// 璐墿杞︾浉鍏虫柟娉� +const addToCart = async (priceItem: PriceItem) => { + try { + const cartData = { + pricingId: priceItem.id, + productId: 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: string) => { + try { + const res: any = await cartApi.removeFromCart(currentUserId.value, currentUnitId.value, currentProductId.value? currentProductId.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 | null, duration: number | null) => { + try { + // 杩欓噷闇�瑕佹牴鎹疄闄呮帴鍙h皟鏁达紝鍙兘闇�瑕佷紶閫掓洿澶氬弬鏁� + let res: any = {} + if(quantity){ + res = await cartApi.updateCartItem(currentUserId.value, currentUnitId.value, currentProductId.value? currentProductId.value : '', pricingId, quantity) + } + if(duration){ + res = await cartApi.updateCartItemDuration(currentUserId.value, currentUnitId.value, currentProductId.value? currentProductId.value : '', pricingId, duration) + } + 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, currentProductId.value? currentProductId.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, null) // 鏆傛椂浼犻�掗粯璁uration涓�1 + } catch (error) { + console.error('鏇存柊鏁伴噺澶辫触:', error) + } +} + +// 澶勭悊骞撮檺鍙樺寲 +const handleDurationChange = async (pricingId: number, duration: number) => { + try { + // 杩欓噷鍙兘闇�瑕佹牴鎹疄闄呮帴鍙h皟鏁达紝浼犻�抎uration鍙傛暟 + await updateCartItem(pricingId, null, duration) // 鏆傛椂浼犻�掗粯璁uantity涓�1 + } catch (error) { + console.error('鏇存柊骞撮檺澶辫触:', error) + } } // 鑾峰彇閿�鍞舰寮忔枃鏈� @@ -558,7 +1005,9 @@ PRIVATE_INCREMENT: '绉佹湁澧為噺鍖�', PUBLIC_INCREMENT: '鍏湁澧為噺鍖�', OTA: 'OTA鏈嶅姟', - CLOUD: '浜戞湇鍔�' + CLOUD: '浜戞湇鍔�', + RESOURCE_PACKAGE: '璧勬簮鍖�', + PERSONAL: '涓汉' } return map[salesForm] || salesForm } @@ -634,6 +1083,24 @@ return map[unit] || unit } +// 缁熶竴娓叉煋浠锋牸锛氬幓闄も�滅Н鍒�/璐у竵/璐圭敤/鍗忚鈥濈瓑绫诲瀷鍓嶇紑锛屼粎杈撳嚭绾环鏍兼枃鏈� +const renderPrice = (priceItem: PriceItem) => { + const segments: string[] = [] + if (priceItem.priceSettings.includes('POINTS') && priceItem.pointsAmount > 0) { + segments.push(`${formatNumber(priceItem.pointsAmount)} / ${getPriceUnitText(priceItem.priceUnit)}`) + } + if (priceItem.priceSettings.includes('CURRENCY') && priceItem.currencyAmount > 0) { + segments.push(`${formatNumber(priceItem.currencyAmount)} / ${getPriceUnitText(priceItem.priceUnit)}`) + } + if (priceItem.priceSettings.includes('FREE')) { + segments.push('鍏嶈垂') + } + if (priceItem.priceSettings.includes('AGREEMENT')) { + segments.push(`姣�${getPriceUnitText(priceItem.priceUnit)}`) + } + return segments.join('锛�') +} + // 鑾峰彇鍒嗙粍鍚庣殑琛ㄥご鏁版嵁 const getGroupedHeaders = (tabData: PriceItem[]) => { const groups: Record<string, { count: number; productSuite: string }> = {} @@ -655,110 +1122,301 @@ </script> <style scoped lang="scss"> +.default-main { + padding: 20px; + background-color: #f5f5f5; + min-height: 100vh; + position: relative; + z-index: 1; +} + +// 鍏ㄥ眬琛ㄥご鏂囧瓧澶у皬璋冩暣 +:deep(.el-table__header) { + th { + .cell { + font-size: 14px !important; + font-weight: 700; + } + } +} + +.search-card { + margin-bottom: 20px; +} + +.mt15 { + margin-top: 15px; +} + .price-viewer-container { .pricing-table-container { - .pricing-tabs { - :deep(.el-tabs__header) { - margin-bottom: 5px; + .pricing-tabs { + :deep(.el-tabs__header) { + margin-bottom: 2px; + } + /* 灞呬腑 tabs 瀵艰埅 */ + :deep(.el-tabs__nav-wrap) { + display: flex; + justify-content: center; } - - :deep(.el-tabs__item.is-active) { - font-weight: bold; + :deep(.el-tabs__nav) { + margin: 0 auto; + border: none !important; } + /* 鏅�� tabs 鏍峰紡 */ + :deep(.el-tabs--card > .el-tabs__header .el-tabs__nav) { + border: none; + border-radius: 0; + background: transparent; + box-shadow: none; + display: flex; + gap: 8px; + } + :deep(.el-tabs--card > .el-tabs__header .el-tabs__item) { + border: 1px solid #e4e7ed; + border-radius: 6px; + margin: 0; + padding: 14px 24px; + color: #606266; + background: #fff; + transition: all .2s ease; + font-size: 14px; + font-weight: 500; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + + &:hover { + border-color: #409eff; + box-shadow: 0 4px 8px rgba(64, 158, 255, 0.15); + } + } + :deep(.el-tabs__item:hover) { + color: #409eff; + background: #f5f7fa; + } + :deep(.el-tabs__item.is-active) { + font-weight: 600; + color: #409eff; + background: #ecf5ff; + border-color: #409eff; + box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2); + + &::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + right: 0; + height: 3px; + background: #409eff; + border-radius: 0 0 6px 6px; + } + } } - .pricing-table-wrapper { - overflow-x: auto; + .pricing-table-wrapper { + overflow-x: auto; + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + padding: 0; + border: 1px solid #e4e7ed; + margin-top: 2px; - .pricing-table { - width: 100%; - border-collapse: collapse; - border: 1px solid #e4e7ed; - background: #fff; - - th, td { - border: 1px solid #e4e7ed; - padding: 6px 8px; - text-align: center; - vertical-align: middle; - font-size: 12px; - } - - th { - background-color: #f5f7fa; - font-weight: 600; - color: #303133; - } - - .feature-column { - width: 120px; - background-color: #f5f7fa; - } - - .sub-header { - th { - background-color: #fafafa; - font-weight: 500; - font-size: 12px; - color: #606266; + .pricing-table { + width: 100%; + border-collapse: collapse; + background: #fff; + border: 1px solid #e4e7ed; + border-radius: 8px; + overflow: visible; + + // 缁熶竴鎵�鏈夊崟鍏冩牸鏍峰紡 + th, td { + border-top: 1px solid #e4e7ed !important; + border-bottom: 1px solid #e4e7ed !important; + border-left: none !important; + border-right: none !important; + padding: 8px 10px; + text-align: center; + vertical-align: middle; + font-size: 14px; + line-height: 1.5; + color: #303133; + background: #fff; + transition: all 0.2s ease; } - } - - .feature-label { - background-color: #f5f7fa; - font-weight: 500; - color: #606266; - text-align: center; - } - - .feature-value { - color: #303133; - } + + // 绗竴鍒楁牱寮� + tr th:first-child, td:first-child{ + font-weight: 700; + color: #303133; + background: #f8fafc; + text-align: left; + padding-left: 16px; + } + + // 琛ㄥご鏍峰紡 + th { + background: linear-gradient(180deg, #f7f9fc 0%, #eef2f7 100%); + font-weight: 600; + color: #303133; + border-bottom: 2px solid #e4e7ed !important; + border-left: none !important; + border-right: none !important; + position: relative; + } + + .feature-column { + width: 140px; + min-width: 140px; + } + + // 瀛愯〃澶存牱寮忓凡绉婚櫎锛岄攢鍞舰寮忚鐜板湪鏄櫘閫氳 + + // 鍔熻兘鏍囩鏍峰紡 + .feature-label { + font-weight: 600; + color: #303133; + text-align: left; + background: #f8fafc; + border-right: none !important; + } + + // 鍔熻兘鍊兼牱寮� + .feature-value { + color: #303133; + background: #fff; + font-weight: 500; + } + + // 琛屾偓鍋滄晥鏋� + tbody tr:hover td { + background: #f0f7ff; + border-color: #b3d8ff !important; + } + + // 鍋舵暟琛岃儗鏅� + tbody tr:nth-child(even) td { + background: #fafbfc; + } + + tbody tr:nth-child(even):hover td { + background: #e6f3ff; + } + + // 寮哄埗纭繚鏈�鍚庝竴琛屾湁瀹屾暣鐨勮竟妗� + tbody tr:last-child td { + border-bottom: 1px solid #e4e7ed !important; + } + + // 寮哄埗纭繚鎵�鏈夊崟鍏冩牸閮芥湁瀹屾暣鐨勮竟妗� + tbody td { + border: 1px solid #e4e7ed !important; + } + + // 鐗瑰埆澶勭悊琛ㄦ牸搴曢儴杈规 + tbody tr:last-child { + border-bottom: 1px solid #e4e7ed !important; + } + + // 浣跨敤鏇村叿浣撶殑閫夋嫨鍣ㄧ‘淇濊竟妗嗘樉绀� + .pricing-table tbody tr:last-child td { + border-bottom: 1px solid #e4e7ed !important; + } + + // 纭繚琛ㄦ牸鏈韩鏈夊畬鏁寸殑杈规 + .pricing-table { + border: 1px solid #e4e7ed !important; + } + + // 鐗瑰埆澶勭悊濂椾欢閫夋嫨琛岀殑杈规 + .pricing-table tbody tr:last-child .feature-label, + .pricing-table tbody tr:last-child .feature-value { + border-bottom: 1px solid #e4e7ed !important; + } + + // 纭繚鎵�鏈夎閮芥湁瀹屾暣鐨勮竟妗� + .pricing-table tbody tr td { + border-top: 1px solid #e4e7ed !important; + border-bottom: 1px solid #e4e7ed !important; + border-left: none !important; + border-right: none !important; + } .order-methods { display: flex; flex-direction: column; - gap: 4px; + gap: 6px; align-items: center; .method-item { - font-size: 13px; - color: #409eff; + font-size: 12px; + color: #3a7afe; + background: #f4f7ff; + border: 1px solid #e3ebff; + border-radius: 999px; + padding: 2px 10px; } } - .price-info { - display: flex; - flex-direction: column; - gap: 8px; - align-items: center; - - .price-item { - display: flex; - align-items: center; - gap: 4px; - - .price-label { - font-size: 13px; - color: #606266; - } - - .price-value { - font-weight: 500; - - &.points { - color: #e6a23c; - } - - &.currency { - color: #f56c6c; - } - - &.free { - color: #67c23a; - } - } - } + .price-info { + display: flex; + flex-direction: column; + gap: 8px; + align-items: center; + + .price-item { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border: 1px solid #f0f2f5; + border-radius: 6px; + background: #fff; + box-shadow: inset 0 -1px 0 rgba(0,0,0,0.02); + + .price-lable-icon { + display: flex; + align-items: center; + gap: 4px; + } + + .price-label { + font-size: 12px; + color: #909399; + white-space: nowrap; + } + + .price-value { + font-weight: 600; + font-size: 16px; + white-space: nowrap; + + &.points { + color: #e7900d; + } + + &.currency { + color: #10b981; + } + + &.free { + color: #67c23a; + } + } + + .price-icon.points { + color: #e6a23c; + font-size: 14px; + } + + .price-icon.currency { + color: #16a34a; + font-size: 16px; + font-weight: 600; + font-family: "Microsoft YaHei", "PingFang SC", sans-serif; + } + } .add-checkbox { margin-top: 4px; @@ -770,6 +1428,7 @@ justify-content: center; align-items: center; padding: 8px 0; + .add-cart-icon { color: #3a7afe; font-size:22px} } } } @@ -784,7 +1443,11 @@ .no-price-data { text-align: center; - padding: 40px 0; + padding: 60px 0; + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + margin: 20px 0; } .price-value.agreement { @@ -792,6 +1455,7 @@ } .product-price-dialog { + :deep(.el-dialog__title) { font-size: 14px; font-weight: 600; @@ -800,6 +1464,7 @@ :deep(.el-dialog) { max-width: 95vw; min-width: 800px; + height: 700px !important; } :deep(.el-dialog__body) { @@ -807,4 +1472,405 @@ overflow-y: auto; } } + +.order-header-card { + :deep(.el-card__body) { padding: 16px; } + .basic-grid { display:grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px 16px;} + .grid-item { + font-size: 14px; + color:#606266; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 2; + } + .label { color:#606266; font-weight: 500; } + .value { color:#303133; } + .value.strong { font-weight: 600; } + margin-bottom: 10px; + margin-top: 10px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); +} + +.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; } + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); +} + +.suite-table-wrapper { overflow-x: auto; width: 100%; } +.suite-table { + width: 100%; + border-collapse: collapse; + background: #fff; + table-layout: fixed; + border: 1px solid #e4e7ed; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); +} +.suite-table th, +.suite-table td { + border: 1px solid #e4e7ed; + padding: 8px 10px; + vertical-align: middle; + font-size: 14px; + line-height: 1.5; + color: #303133; + background: #fff; + transition: all 0.2s ease; + text-align: center; +} + +.suite-table thead th { + background: linear-gradient(180deg, #f7f9fc 0%, #eef2f7 100%); + color: #303133; + font-weight: 600; + font-size: 14px; + border-bottom: 2px solid #e4e7ed; + position: relative; +} + +.suite-table tbody tr:hover td { + background: #f0f7ff; + border-color: #b3d8ff; +} + +.suite-table tbody tr:nth-child(even) td { + background: #fafbfc; +} + +.suite-table tbody tr:nth-child(even):hover td { + background: #e6f3ff; +} + +// 纭繚濂椾欢琛ㄦ牸鏈�鍚庝竴琛屾湁瀹屾暣鐨勮竟妗� +.suite-table tbody tr:last-child td { + border-bottom: 1px solid #e4e7ed; +} + +// 纭繚濂椾欢琛ㄦ牸鎵�鏈夊崟鍏冩牸閮芥湁瀹屾暣鐨勮竟妗� +.suite-table tbody td { + border: 1px solid #e4e7ed; +} + +.table-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + background: #f8fafc; + border-top: 1px solid #e4e7ed; + border-radius: 0 0 8px 8px; +} +.toolbar-left { + display: flex; + align-items: center; + gap: 16px; + width: 100%; + justify-content: space-between; +} +.toolbar-right { + display: flex; + align-items: center; + gap: 12px; +} +.summary-info { + font-size: 14px; + color: #606266; + display: flex; + align-items: center; + gap: 8px; +} +.summary-count { + color: #409eff; + font-weight: 600; + background: #ecf5ff; + padding: 4px 8px; + border-radius: 4px; +} +.summary-points { + color: #e6a23c; + font-weight: 600; + background: #fdf6ec; + padding: 4px 8px; + border-radius: 4px; +} + +.suite-table .th-detail { text-align: center; width: 25%;} +.suite-table .th-spec { text-align: center; width: 25%;} +.suite-table .th-price { text-align: center; width: 18%;} +.suite-table .th-quantity { text-align: center; width: 12%;} +.suite-table .th-years { text-align: center; width: 12%;} +.suite-table .th-action { text-align: center; width: 8%;} + +// 璇︽儏銆佽鏍笺�佷环鏍煎垪鐨勫唴瀹瑰乏瀵归綈 +.suite-table .cell-detail, +.suite-table .cell-spec, +.suite-table .cell-price { + text-align: left !important; +} + +.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: 13px; + line-height: 18px; + padding: 2px 0; + + // 瑙勬牸鍒椾腑鏁板�肩殑缁熶竴棰滆壊 + .spec-value { + color: #409eff; + font-weight: 500; + } +} +.cell-spec-gg { + display: flex; + flex-direction: column; + gap: 12px; + + .spec-line-gg{ + display: flex; + gap: 24px; + } +} +.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; + background: #fdf6ec; + padding: 4px 8px; + border-radius: 4px; +} +.cell-price .price-tag.currency { + color: #F56C6C; + font-weight: 600; + background: #fef0f0; + padding: 4px 8px; + border-radius: 4px; +} +.cell-price .price-free { + color: #67C23A; + font-weight: 600; + background: #f0f9ff; + padding: 4px 8px; + border-radius: 4px; +} +.cell-price .price-agreement { + color: #409EFF; + background: #ecf5ff; + padding: 4px 8px; + border-radius: 4px; +} +.cell-summary .summary-item { font-size: 14px; color: #606266; line-height: 1.5; } + +.order-status-panel { + .steps-wrapper { + padding: 20px 16px; + background: #fff; + border-radius: 8px 8px 0 0; + border-bottom: 1px solid #e4e7ed; + } + .result-text { + text-align: center; + margin: 24px 0 20px; + font-size: 18px; + color: #303133; + font-weight: 500; + } + .order-info-card { + :deep(.el-card__body) { padding: 0; } + border-radius: 0 0 8px 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + } + .order-info-title { + background: linear-gradient(180deg, #f7f9fc 0%, #eef2f7 100%); + padding: 16px 20px; + font-weight: 600; + border-bottom: 1px solid #e4e7ed; + color: #303133; + } + .order-info-table { + width: 100%; + border-collapse: collapse; + border: 1px solid #e4e7ed; + border-radius: 8px; + overflow: hidden; + + td { + padding: 10px 12px; + border-bottom: 1px solid #e4e7ed; + border-right: 1px solid #e4e7ed; + vertical-align: middle; + font-size: 14px; + line-height: 1.5; + color: #303133; + background: #fff; + transition: all 0.2s ease; + } + + td:last-child { + border-right: none; + } + + tr:last-child td { + border-bottom: none; + } + + .label { + width: 120px; + color: #606266; + font-weight: 500; + background: #f8fafc; + text-align: center; + } + + .value { + color: #303133; + background: #fff; + } + + .link { + text-align: right; + background: #fff; + } + + tr:hover td { + background: #f0f7ff; + border-color: #b3d8ff; + } + + tr:nth-child(even) td { + background: #fafbfc; + } + + tr:nth-child(even):hover td { + background: #e6f3ff; + } + } +} + +.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; } +.footer { + display: flex; + flex-direction: row-reverse; + margin-right: 50px; + gap: 20px; + margin-top: 10px; + padding: 10px 0; + + .el-button { + border-radius: 6px; + font-weight: 500; + padding: 12px 24px; + + &.el-button--primary { + background: linear-gradient(135deg, #409eff 0%, #3a7afe 100%); + border: none; + box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3); + + &:hover { + background: linear-gradient(135deg, #66b1ff 0%, #5a8cff 100%); + box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4); + } + } + + &:not(.el-button--primary) { + border: 1px solid #dcdfe6; + background: #fff; + + &:hover { + border-color: #409eff; + color: #409eff; + } + } + } +} + +// 纭繚椤甸潰鏁翠綋灞傜骇姝g‘ +:deep(.el-card) { + position: relative; + z-index: 1; +} + +// 淇鍙兘鐨勫叏灞�z-index鍐茬獊 +:deep(.el-table__body-wrapper) { + z-index: 1 !important; +} + +:deep(.el-table__header-wrapper) { + z-index: 1 !important; +} + +// 鏁伴噺鍜屽勾闄愯緭鍏ユ鏍峰紡 +.quantity-input, +.duration-input { + width: 100px !important; + height: 35px !important; + + :deep(.el-input__wrapper) { + padding: 0 8px !important; + } + + :deep(.el-input__inner) { + width: 30px !important; + text-align: center !important; + min-width: 30px !important; + max-width: 30px !important; + } + + :deep(.el-input-number__decrease), + :deep(.el-input-number__increase) { + width: 32px !important; + height: 32px !important; + } +} + +// 浣跨敤鏇村己鐨勯�夋嫨鍣ㄧ‘淇濇牱寮忕敓鏁� +.suite-table .quantity-input, +.suite-table .duration-input { + :deep(.el-input__inner) { + width: 40px !important; + text-align: center !important; + min-width: 40px !important; + max-width: 40px !important; + box-sizing: border-box !important; + } +} + +// 鍏ㄥ眬鏍峰紡瑕嗙洊锛岀‘淇濊緭鍏ユ鍐呴儴瀹藉害鐢熸晥 +:deep(.el-input-number.quantity-input .el-input__inner), +:deep(.el-input-number.duration-input .el-input__inner) { + width: 40px !important; + text-align: center !important; + min-width: 40px !important; + max-width: 40px !important; + box-sizing: border-box !important; +} </style> -- Gitblit v1.8.0