| | |
| | | <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" |
| | |
| | | {{ 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 |
| | |
| | | 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> |
| | | <div |
| | | v-if="priceItem.priceSettings.includes('CURRENCY') && priceItem.currencyAmount > 0" |
| | | class="price-item" |
| | | > |
| | | <div class="price-lable-icon"> |
| | | <span class="price-icon currency">¥</span> |
| | | <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 |
| | |
| | | :model-value="selectedSuiteIds.has(priceItem.id)" |
| | | @change="handleSuiteSelect(priceItem.id, $event)" |
| | | > |
| | | 添加 |
| | | <el-icon class="add-cart-icon"><ShoppingCart /></el-icon> |
| | | </el-checkbox> |
| | | </div> |
| | | </td> |
| | |
| | | </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 }}</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 { 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() |
| | | |
| | | interface PriceItem { |
| | | id: number |
| | |
| | | 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 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(() => { |
| | |
| | | ) |
| | | }) |
| | | |
| | | // 模拟价格数据 |
| | | const mockPriceData: Record<string, PriceItem[]> = { |
| | | '10001': [ |
| | | { |
| | | id: 1, |
| | | productId: '10001', |
| | | productSuite: 'ENTERPRISE_PRIVATE_SAAS_LICENSE', |
| | | salesForm: 'BUYOUT', |
| | | customerObject: 'ENTERPRISE', |
| | | accountQuantity: 50, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 50, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['POINTS', 'CURRENCY', 'AGREEMENT'], |
| | | pointsAmount: 50000, |
| | | currencyAmount: 50000, |
| | | priceUnit: 'SET', |
| | | enableStatus: 'ENABLED' |
| | | }, |
| | | { |
| | | id: 2, |
| | | productId: '10001', |
| | | productSuite: 'ENTERPRISE_PRIVATE_SAAS_LICENSE', |
| | | salesForm: 'LEASE', |
| | | customerObject: 'ENTERPRISE', |
| | | accountQuantity: 0, |
| | | accountQuantityUnlimited: true, |
| | | concurrentNodeQuantity: 50, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['POINTS', 'CURRENCY', 'AGREEMENT'], |
| | | pointsAmount: 800000, |
| | | currencyAmount: 800000, |
| | | priceUnit: 'YEAR', |
| | | enableStatus: 'ENABLED' |
| | | }, |
| | | { |
| | | id: 3, |
| | | productId: '10001', |
| | | productSuite: 'ENTERPRISE_PRIVATE_SAAS_OTA', |
| | | salesForm: 'OTA', |
| | | customerObject: 'ENTERPRISE', |
| | | accountQuantity: 50, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 50, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['CURRENCY'], |
| | | pointsAmount: 0, |
| | | currencyAmount: 7500, |
| | | priceUnit: 'SET_PER_YEAR', |
| | | enableStatus: 'ENABLED' |
| | | }, |
| | | { |
| | | id: 4, |
| | | productId: '10001', |
| | | productSuite: 'ENTERPRISE_PRIVATE_SAAS_USER_INCREMENT', |
| | | salesForm: 'PRIVATE_INCREMENT', |
| | | customerObject: 'ENTERPRISE', |
| | | accountQuantity: 100, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 100, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['CURRENCY'], |
| | | pointsAmount: 0, |
| | | currencyAmount: 10000, |
| | | priceUnit: 'YEAR', |
| | | enableStatus: 'ENABLED' |
| | | }, |
| | | { |
| | | id: 9, |
| | | productId: '10001', |
| | | productSuite: 'PROJECT_PRIVATE_SAAS_OTA', |
| | | salesForm: 'OTA', |
| | | customerObject: 'PROJECT_DEPARTMENT', |
| | | accountQuantity: 50, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 50, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['FREE'], |
| | | pointsAmount: 0, |
| | | currencyAmount: 0, |
| | | priceUnit: 'SET', |
| | | enableStatus: 'ENABLED' |
| | | }, |
| | | { |
| | | id: 10, |
| | | productId: '10001', |
| | | productSuite: 'PROJECT_PRIVATE_SAAS_USER_INCREMENT', |
| | | salesForm: 'PRIVATE_INCREMENT', |
| | | customerObject: 'PROJECT_DEPARTMENT', |
| | | accountQuantity: 50, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 50, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['CURRENCY'], |
| | | pointsAmount: 0, |
| | | currencyAmount: 50000, |
| | | priceUnit: 'YEAR', |
| | | enableStatus: 'ENABLED' |
| | | }, |
| | | { |
| | | id: 5, |
| | | productId: '10001', |
| | | productSuite: 'ENTERPRISE_PUBLIC_SAAS_LICENSE', |
| | | salesForm: 'LEASE', |
| | | customerObject: 'PERSONAL', |
| | | accountQuantity: 50, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 50, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['CURRENCY'], |
| | | pointsAmount: 0, |
| | | currencyAmount: 15000, |
| | | priceUnit: 'YEAR', |
| | | enableStatus: 'ENABLED' |
| | | }, |
| | | { |
| | | id: 6, |
| | | productId: '10001', |
| | | productSuite: 'ENTERPRISE_PUBLIC_SAAS_USER_INCREMENT', |
| | | salesForm: 'PUBLIC_INCREMENT', |
| | | customerObject: 'PERSONAL', |
| | | accountQuantity: 50, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 50, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['CURRENCY'], |
| | | pointsAmount: 0, |
| | | currencyAmount: 5000, |
| | | priceUnit: 'YEAR', |
| | | enableStatus: 'ENABLED' |
| | | }, |
| | | { |
| | | id: 7, |
| | | productId: '10001', |
| | | productSuite: 'ENTERPRISE_PUBLIC_SAAS_CLOUD', |
| | | salesForm: 'CLOUD', |
| | | customerObject: 'PERSONAL', |
| | | accountQuantity: 1, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 1, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['FREE'], |
| | | pointsAmount: 0, |
| | | currencyAmount: 0, |
| | | priceUnit: 'SET', |
| | | enableStatus: 'ENABLED' |
| | | } |
| | | ], |
| | | '10002': [ |
| | | { |
| | | id: 8, |
| | | productId: '10002', |
| | | productSuite: 'ENTERPRISE_PRIVATE_SAAS_LICENSE', |
| | | salesForm: 'BUYOUT', |
| | | customerObject: 'ENTERPRISE', |
| | | accountQuantity: 30, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 30, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['POINTS', 'CURRENCY', 'AGREEMENT'], |
| | | pointsAmount: 30000, |
| | | currencyAmount: 30000, |
| | | priceUnit: 'SET', |
| | | enableStatus: 'ENABLED' |
| | | }, |
| | | { |
| | | id: 11, |
| | | productId: '10002', |
| | | productSuite: 'PROJECT_PRIVATE_SAAS_OTA', |
| | | salesForm: 'OTA', |
| | | customerObject: 'PROJECT_DEPARTMENT', |
| | | accountQuantity: 30, |
| | | accountQuantityUnlimited: false, |
| | | concurrentNodeQuantity: 30, |
| | | concurrentNodeQuantityUnlimited: false, |
| | | priceSettings: ['FREE'], |
| | | pointsAmount: 0, |
| | | currencyAmount: 0, |
| | | priceUnit: 'SET', |
| | | enableStatus: 'ENABLED' |
| | | } |
| | | ] |
| | | // 映射工具 |
| | | function mapSalesFormFromCN(val: string) { |
| | | const map: Record<string, string> = { |
| | | '买断': 'BUYOUT', |
| | | '租赁': 'LEASE', |
| | | '私有增包量': 'PRIVATE_INCREMENT', |
| | | '公有增包量': 'PUBLIC_INCREMENT', |
| | | 'OTA服务': 'OTA', |
| | | '云服务': 'CLOUD', |
| | | '资源包': 'RESOURCE_PACKAGE', |
| | | '个人': 'PERSONAL', |
| | | } |
| | | return (map[val] as any) || val |
| | | } |
| | | |
| | | function mapCustomerFromCN(val: string) { |
| | | const map: Record<string, string> = { '企业': 'ENTERPRISE', '个人': 'PERSONAL', '项目部': 'PROJECT_DEPARTMENT' } |
| | | return (map[val] as any) || val |
| | | } |
| | | |
| | | function mapUnitFromCN(val: string) { |
| | | const map: Record<string, string> = { '套': 'SET', '套/年': 'SET_PER_YEAR', '年': 'YEAR' } |
| | | return (map[val] as any) || val |
| | | } |
| | | |
| | | function mapPriceTypeFromCN(val: string): 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE' | undefined { |
| | | const map: Record<string, any> = { '积分': 'POINTS', '货币': 'CURRENCY', '协议': 'AGREEMENT', '免费': 'FREE' } |
| | | return map[val] |
| | | } |
| | | |
| | | // 模拟产品名称数据 |
| | | const mockProductNames: Record<string, string> = { |
| | | '10001': '数字化产品A', |
| | | '10002': '数字化产品B' |
| | | '1': '数字化产品A', |
| | | '2': '数字化产品B' |
| | | } |
| | | |
| | | // 获取价格信息 |
| | | // 获取价格信息(改为接口获取定价列表) |
| | | const fetchProductData = async (productId: string) => { |
| | | if (!productId) { |
| | | ElMessage.warning('未提供产品ID') |
| | |
| | | |
| | | 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 |
| | | } |
| | | |
| | | // 组装创建订单参数(CreateOrderDTO) |
| | | 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() |
| | | } |
| | | // 调用工作流接口发起审批流程,拿到流程实例ID后回写订单workflow_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 { |
| | | // 这里需要根据实际接口调整,可能需要传递更多参数 |
| | | 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) // 暂时传递默认duration为1 |
| | | } catch (error) { |
| | | console.error('更新数量失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 处理年限变化 |
| | | const handleDurationChange = async (pricingId: number, duration: number) => { |
| | | try { |
| | | // 这里可能需要根据实际接口调整,传递duration参数 |
| | | await updateCartItem(pricingId, null, duration) // 暂时传递默认quantity为1 |
| | | } catch (error) { |
| | | console.error('更新年限失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 获取销售形式文本 |
| | |
| | | </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; |
| | |
| | | justify-content: center; |
| | | align-items: center; |
| | | padding: 8px 0; |
| | | .add-cart-icon { color: #3a7afe; font-size:22px} |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | .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 { |
| | |
| | | } |
| | | |
| | | .product-price-dialog { |
| | | |
| | | :deep(.el-dialog__title) { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | |
| | | :deep(.el-dialog) { |
| | | max-width: 95vw; |
| | | min-width: 800px; |
| | | height: 700px !important; |
| | | } |
| | | |
| | | :deep(.el-dialog__body) { |
| | |
| | | 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; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 确保页面整体层级正确 |
| | | :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> |