seatonwan9
2025-08-28 bfbb1ea3c6bb2dd7db064fb189290a1bfcf6c9cd
src/views/productManage/productPriceViewer/index.vue
@@ -1,12 +1,5 @@
<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="showPricePanel && priceList.length > 0">
@@ -174,19 +167,19 @@
              <span class="label">产品名称:</span><span class="value strong">{{ productHeader.name }}</span>
            </div>
            <div class="grid-item">
              <span class="label">行业板块:</span><span class="value">{{ productHeader.industry }}</span>
              <span class="label">行业板块:</span><span class="value">{{ productHeader.industrialChainName }}</span>
            </div>
            <div class="grid-item">
              <span class="label">提供者:</span><span class="value">{{ productHeader.provider }}</span>
              <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.unit }}</span>
              <span class="label">单位:</span><span class="value">{{ userInfo.unitName }}</span>
            </div>
            <div class="grid-item">
              <span class="label">部门:</span><span class="value">{{ userInfo.department }}</span>
              <span class="label">部门:</span><span class="value">{{ userInfo.departmentName }}</span>
            </div>
          </div>
        </el-card>
@@ -338,32 +331,29 @@
        <el-empty description="暂无价格信息" />
      </div>
    </div>
    <template #footer v-if="!showOrderStatus">
      <span class="dialog-footer">
        <template v-if="showPricePanel">
        <el-button @click="handleClose">关闭</el-button>
    <div class="footer" v-if="showPricePanel">
        <el-button type="primary" @click="handleOrder">立即订购</el-button>
        </template>
        <template v-else>
    </div>
    <div class="footer" v-else>
          <el-button @click="returnPricePanel">返回价格对比</el-button>
          <el-button type="primary" @click="submitOrder">提交申请</el-button>
        </template>
      </span>
    </template>
  </el-dialog>
    </div>
  </div>
  <!-- 产品订购对话框移除,改为内嵌展示 -->
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { ref, watch, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { useRoute } from 'vue-router'
import productPricingApi from '@/api/productPricingApi'
import cartApi from '@/api/cartApi'
import orderApi from '@/api/orderApi'
import { ShoppingCart, Coin, Money } from '@element-plus/icons-vue'
import { useUserInfo } from '@/stores/modules/userInfo'
import { queryUserDetail } from '@/api/userInfo'
import productApi from '@/api/productApi'
const route = useRoute()
@@ -384,36 +374,11 @@
  enableStatus: 'ENABLED' | 'DISABLED'
}
interface Props {
  modelValue: boolean
  productId?: string
  width?: string
}
interface Emits {
  (e: 'update:modelValue', value: boolean): void
  (e: 'order', selectedItems: PriceItem[]): void
}
// 兼容 path 参数 productId 与 query 参数 id/productId
const currentProductId = computed<string | undefined>(() => {
  return (route.params.productId as string) || (route.query.productId as string) || (route.query.id as string)
})
const props = withDefaults(defineProps<Props>(), {
  modelValue: false,
  productId: '',
  width: '90%'
})
const emit = defineEmits<Emits>()
const visible = computed({
  get: () => props.modelValue,
  set: (value) => emit('update:modelValue', value)
})
const loading = ref(false)
const activeTab = ref('enterprise')
@@ -424,8 +389,32 @@
const showPricePanel = ref(true)
const showOrderStatus = ref(false)
// 模拟用户信息(实际应从用户状态获取)
const currentUserId = ref(1)
const currentUnitId = ref(1)
const userStore = useUserInfo()
let 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 = res.data.userId || res.data.id
        userInfo.value = res.data
      } else {
        ElMessage.error(res?.msg || '无法获取用户信息,请先登录')
        return
      }
    } catch (e) {
      console.error('获取用户详情失败:', e)
      ElMessage.error('获取用户信息失败,请稍后重试')
      return
    }
  }else{
    userInfo.value = userStore.getUserInfos
  }
})
type PriceTypeKey = 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE'
interface OrderSuite extends PriceItem {
  selected: boolean
@@ -513,14 +502,15 @@
// 产品信息/用户信息(静态)
const productHeader = ref({
  name: '中交方远智能实测实量管理系统',
  industry: '公路,市政,建筑',
  provider: '中交建筑集团第一工程有限公司'
  name: '',
  industrialChainName: '',
  submissionUnit: '',
  createUserId: ''
})
const userInfo = ref({
  name: '张静',
  unit: '信科集团',
  department: '门户系统临时组'
  name: '',
  departmentName: '',
  unitName: ''
})
// 全选
@@ -551,7 +541,7 @@
  const suite = orderSuites.value[index]
  if (!suite) return
  try {
    await removeFromCart(suite.id)
    await removeFromCart(String(suite.id))
    orderSuites.value.splice(index, 1)
  } catch (e) {
  }
@@ -563,8 +553,6 @@
  ElMessage.success('订购申请提交成功!')
  // 清空选择状态
  selectedSuiteIds.value.clear()
  // 关闭价格查看器
  visible.value = false
}
// 弹窗标题
@@ -572,10 +560,6 @@
  return `【${productName.value}】产品定价`
})
// 弹窗宽度
const dialogWidth = computed(() => {
  return props.width
})
// 按客户对象分组的价格数据
const groupedPriceData = computed(() => {
@@ -701,31 +685,33 @@
  } 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
  }
}
// 监听产品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
  showOrderPanel.value = false
  showOrderStatus.value = false
  showPricePanel.value = true
  orderSuites.value = []
  selectedSuiteIds.value.clear()
}
},{ immediate: true })
// 套件选择处理
const handleSuiteSelect = (suiteId: number, checked: any) => {
@@ -740,7 +726,7 @@
  } else {
    selectedSuiteIds.value.delete(suiteId)
    // 从购物车移除
    removeFromCart(suiteId)
    removeFromCart(String(suiteId))
  }
}
@@ -789,19 +775,26 @@
    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 = '协议'
  }
  // 组装创建订单参数(CreateOrderDTO)
  const payload = {
    userId: currentUserId.value,
    unitId: currentUnitId.value,
    productName: productHeader.value.name,
    providerName: productHeader.value.provider,
    providerId: 3,
    paymentType: '积分',
    providerName: productHeader.value.submissionUnit,
    providerId: productHeader.value.createUserId,
    paymentType: paymentType,
    buyerRemarks: '',
    items: items.map(it => ({
      pricingId: it.id,
      productId: Number(it.productId),
      productId: it.productId,
      suiteName: getProductSuiteText(it.productSuite),
      salesForm: getSalesFormText(it.salesForm),
      customerType: getCustomerObjectText(it.customerObject),
@@ -813,8 +806,8 @@
      quantity: it.quantity,
      duration: it.duration,
      totalPrice: undefined,
      providerId: 3,
      providerName: productHeader.value.provider,
      providerId: productHeader.value.createUserId,
      providerName: productHeader.value.submissionUnit,
      remarks: ''
    }))
  }
@@ -827,11 +820,28 @@
      orderStatus.value = {
        id: data.orderId || '—',
        productName: data.productName || productHeader.value.name,
        provider: data.providerName || productHeader.value.provider,
        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 wfRes: any = await orderApi.startWorkflowAndComplete({
          processdefId,
          userid: String(currentUserId.value || ''),
          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)
@@ -859,7 +869,7 @@
  try {
    const cartData = {
      pricingId: priceItem.id,
      productId: Number(priceItem.productId),
      productId: priceItem.productId,
      productName: productName.value,
      suiteName: getProductSuiteText(priceItem.productSuite),
      salesForm: getSalesFormText(priceItem.salesForm),
@@ -885,7 +895,7 @@
  }
}
const removeFromCart = async (pricingId: number) => {
const removeFromCart = async (pricingId: string) => {
  try {
    const res: any = await cartApi.removeFromCart(currentUserId.value, currentUnitId.value, pricingId)
    if (res?.code === 200) {
@@ -1084,29 +1094,65 @@
  .pricing-table-container {
    .pricing-tabs {
      :deep(.el-tabs__header) {
        margin-bottom: 5px;
        margin-bottom: 12px;
      }
      /* 居中 tabs 导航 */
      :deep(.el-tabs__nav-wrap) {
        display: flex;
        justify-content: center;
      }
      :deep(.el-tabs__nav) {
        margin: 0 auto;
        border: none !important;
      }
      /* 卡片型 tabs 美化为圆角胶囊效果 */
      :deep(.el-tabs--card > .el-tabs__header .el-tabs__nav) {
        border: none;
      }
      :deep(.el-tabs--card > .el-tabs__header .el-tabs__item) {
        border: none;
      }
      :deep(.el-tabs__item) {
        border-radius: 999px;
        padding: 8px 18px;
        margin: 0 6px;
        color: #606266;
        background: #f6f8fb;
        transition: all .2s ease;
      }
      :deep(.el-tabs__item:hover) {
        color: #3a7afe;
        background: #eff4ff;
      }
      :deep(.el-tabs__item.is-active) {
        font-weight: bold;
        font-weight: 600;
        color: #fff;
        background: #3a7afe;
        box-shadow: 0 6px 16px rgba(58, 122, 254, 0.25);
      }
    }
    .pricing-table-wrapper {
      overflow-x: auto;
      background: #fff;
      border-radius: 12px;
      box-shadow: 0 6px 24px rgba(0, 0, 0, 0.06);
      padding: 8px;
      
      .pricing-table {
        width: 100%;
        border-collapse: collapse;
        border: 1px solid #e4e7ed;
        border-collapse: separate;
        border-spacing: 0;
        background: #fff;
        border-radius: 10px;
        
        th, td {
          border: 1px solid #e4e7ed;
          padding: 6px 8px;
          border-bottom: 1px solid #eef2f7;
          border-right: 1px solid #f3f5f7;
          padding: 12px 14px;
          text-align: center;
          vertical-align: middle;
          font-size: 16px;
          font-size: 14px;
        }
        tr th:first-child, td:first-child{
          font-weight: 700;
@@ -1114,7 +1160,7 @@
        }
        
        th {
          background-color: #f5f7fa;
          background: linear-gradient(180deg, #f7f9fc 0%, #eef2f7 100%);
          font-weight: 600;
          color: #303133;
        }
@@ -1126,9 +1172,9 @@
        
        .sub-header {
          th {
            background-color: #fafafa;
            background-color: #f8fafc;
            font-weight: 500;
              font-size: 16px;
            font-size: 13px;
            color: #606266;
          }
        }
@@ -1141,26 +1187,34 @@
        }
        
        .feature-value {
          color: #606266;
          background: #fafafa; /* 行为灰色 */
          color: #303133;
          background: #fff;
        }
        tbody tr:hover td {
          background: #fafcff;
        }
        
        .order-methods {
          display: flex;
          flex-direction: column;
          gap: 4px;
          gap: 6px;
          align-items: center;
          
          .method-item {
            font-size: 16px;
            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;
          gap: 10px;
          align-items: center;
          
          .price-item {
@@ -1168,24 +1222,29 @@
            align-items: flex-start;
            flex-direction: column;
            gap: 6px;
            padding: 10px 12px;
            border: 1px solid #f0f2f5;
            border-radius: 10px;
            background: #fff;
            box-shadow: inset 0 -1px 0 rgba(0,0,0,0.02);
            .price-lable-icon{
              display: flex;
              gap: 6px;
            }
            .price-label {
              font-size: 16px;
              color: #606266;
              font-size: 12px;
              color: #909399;
            }
            
            .price-value {
              font-weight: 500;
              font-size: 20px;
              font-weight: 600;
              font-size: 18px;
              &.points {
                color: #e7900d;
              }
              
              &.currency {
                color: #e7900d;
                color: #10b981;
              }
              
              &.free {
@@ -1193,7 +1252,7 @@
              }
            }
            .price-icon.points { color: #e6a23c; }
            .price-icon.currency { color: #e7900d; }
            .price-icon.currency { color: #16a34a; }
          }
          
                     .add-checkbox {
@@ -1206,7 +1265,7 @@
           justify-content: center;
           align-items: center;
           padding: 8px 0;
           .add-cart-icon { color: #409eff; font-size:22px}
           .add-cart-icon { color: #3a7afe; font-size:22px}
         }
      }
    }
@@ -1348,4 +1407,5 @@
.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: 50px; margin-top: 20px;}
</style>