productRel.vue 7.35 KB
<template>
  <view class="product-rel">
    <view class="header">
      <image class="opCollapse" src="/static/images/title.png" />
      <text class="title">明细信息</text>
      <view class="ops">
        <view class="op1" @click="toggleViewCollapse">
          <image class="opAdd" :src="collapsedView ? '/static/images/down.png' : '/static/images/up.png'" />
          <text class="op">{{ collapsedView ? '展开' : '收起' }}</text>
        </view>
      </view>
    </view>

    <view v-if="!collapsedView" class="view-list">
      <view v-if="!items.length" class="empty-tip">暂无明细</view>
      <view
        v-for="(item, idx) in items"
        :key="'v-' + (item.id || item.itemId || item._key || idx)"
        class="card"
      >
        <view class="card-index">第 {{ idx + 1 }} 行</view>
        <view class="row"><text class="label">名称</text><text class="value">{{ safeText(item.invoiceProductName) }}</text></view>
        <view class="row"><text class="label">规格型号</text><text class="value">{{ safeText(item.productName) }}</text></view>
        <view class="row"><text class="label">数量</text><text class="value">{{ formatNumber(item.quantity) }}</text></view>
        <view class="row"><text class="label">单价</text><text class="value">{{ formatNumber(item.unitPrice) }}</text></view>
        <view class="row"><text class="label">不含税金额</text><text class="value highlight">{{ formatNumber(item.excludingTaxAmount) }}</text></view>
        <view class="row"><text class="label">税额</text><text class="value highlight">{{ formatNumber(item.taxAmount) }}</text></view>
        <view class="row"><text class="label">合计金额</text><text class="value total">{{ formatNumber(item.totalAmount) }}</text></view>
        <view class="row"><text class="label">交货时间</text><text class="value">{{ formatDateOnly(item.deliveryTime) }}</text></view>
      </view>

      <view v-if="items.length" class="summary-card">
        <view class="summary-title">合计</view>
        <view class="row"><text class="label">数量</text><text class="value">{{ formatNumber(totalQuantity) }}</text></view>
        <view class="row"><text class="label">不含税金额</text><text class="value highlight">{{ formatNumber(totalExcludeTaxAmount) }}</text></view>
        <view class="row"><text class="label">税额</text><text class="value highlight">{{ formatNumber(totalTaxAmount) }}</text></view>
        <view class="row"><text class="label">合计金额</text><text class="value total">{{ formatNumber(totalAmount) }}</text></view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  name: 'DomesticAgreementProductRel',
  props: {
    mode: { type: String, default: 'view' },
    list: { type: Array, default: function () { return [] } }
  },
  data() {
    return {
      items: [],
      collapsedView: false
    }
  },
  computed: {
    totalQuantity() {
      return round2(sum(this.items, 'quantity'))
    },
    totalExcludeTaxAmount() {
      return sum(this.items, 'excludingTaxAmount')
    },
    totalTaxAmount() {
      return sum(this.items, 'taxAmount')
    },
    totalAmount() {
      return sum(this.items, 'totalAmount')
    }
  },
  watch: {
    list: {
      handler(v) {
        this.initItems(v)
      },
      immediate: true,
      deep: true
    }
  },
  methods: {
    initItems(src) {
      const arr = Array.isArray(src) ? src : []
      this.items = arr.map(x => Object.assign({}, x))
    },
    toggleViewCollapse() {
      this.collapsedView = !this.collapsedView
    },
    safeText(v) {
      if (v === null || v === undefined || v === '') return '-'
      return String(v)
    },
    formatNumber(v) {
      if (v === null || v === undefined || v === '') return '-'
      const n = Number(v)
      if (isNaN(n)) return String(v)
      return n.toLocaleString('zh-CN', { maximumFractionDigits: 4 })
    },
    formatDateOnly(v) {
      const s = v == null ? '' : String(v).trim()
      if (!s) return '-'
      if (s.length >= 10) return s.slice(0, 10)
      return s
    }
  }
}

function num(v) {
  if (v === null || v === undefined || v === '') return 0
  const n = Number(v)
  return isNaN(n) ? 0 : n
}
function round2(v) {
  const n = Number(v)
  if (isNaN(n)) return ''
  return Math.round(n * 100) / 100
}
function sum(list, field) {
  let s = 0
  if (!Array.isArray(list)) return s
  list.forEach(it => { s += num(it && it[field]) })
  return s
}
</script>

<style lang="scss" scoped>
.product-rel {
  width: 100%;
  margin-top: 24rpx;
}
.header {
  display: flex;
  align-items: center;
  padding: 16rpx 0 16rpx 32rpx;
  .opCollapse { width: 6rpx; height: 32rpx; margin-right: 14rpx; }
  .title { font-size: 30rpx; font-weight: 600; color: rgba(0,0,0,0.9); flex: 1; line-height: 42rpx; }
  .ops { display: flex; align-items: center; padding-right: 32rpx; }
  .op1 {
    display: flex;
    align-items: center;
    padding: 0 12rpx;
    height: 56rpx;
    .opAdd { width: 24rpx; height: 24rpx; margin-right: 8rpx; }
    .op { font-size: 26rpx; color: rgba(0,0,0,0.6); }
  }
}
.view-list {
  padding: 0 32rpx 24rpx;
  .empty-tip {
    text-align: center;
    padding: 80rpx 0;
    color: rgba(0,0,0,0.4);
    font-size: 28rpx;
  }
  .card {
    position: relative;
    background: #fff;
    border-radius: 12rpx;
    padding: 32rpx 28rpx 20rpx;
    margin-bottom: 24rpx;
    box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
    .card-index {
      position: absolute;
      top: 0;
      right: 0;
      background: rgba(24, 144, 255, 0.08);
      color: $theme-primary;
      font-size: 22rpx;
      line-height: 36rpx;
      padding: 0 16rpx;
      border-bottom-left-radius: 12rpx;
      border-top-right-radius: 12rpx;
    }
    .row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 14rpx 0;
      border-bottom: 1rpx dashed #F2F2F2;
      &:last-child { border-bottom: none; }
      .label {
        flex-shrink: 0;
        width: 220rpx;
        font-size: 28rpx;
        color: rgba(0,0,0,0.6);
        line-height: 40rpx;
      }
      .value {
        flex: 1;
        text-align: right;
        font-size: 28rpx;
        color: rgba(0,0,0,0.9);
        line-height: 40rpx;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        &.highlight { color: #E6A23C; }
        &.total { color: #D54941; font-weight: 600; font-size: 30rpx; }
      }
    }
  }
  .summary-card {
    background: linear-gradient(135deg, rgba(24, 144, 255, 0.06), rgba(24, 144, 255, 0.02));
    border: 1rpx solid rgba(24, 144, 255, 0.16);
    border-radius: 12rpx;
    padding: 28rpx;
    .summary-title {
      font-size: 30rpx;
      font-weight: 700;
      color: $theme-primary;
      margin-bottom: 12rpx;
      line-height: 42rpx;
    }
    .row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 12rpx 0;
      border-bottom: 1rpx dashed rgba(24, 144, 255, 0.12);
      &:last-child { border-bottom: none; }
      .label {
        flex-shrink: 0;
        width: 220rpx;
        font-size: 28rpx;
        color: rgba(0,0,0,0.6);
        line-height: 40rpx;
      }
      .value {
        flex: 1;
        text-align: right;
        font-size: 28rpx;
        color: rgba(0,0,0,0.9);
        line-height: 40rpx;
        font-weight: 500;
        &.highlight { color: #E6A23C; }
        &.total { color: #D54941; font-weight: 700; font-size: 32rpx; }
      }
    }
  }
}
</style>