doum
2026-06-17 ea689dd91eaa72425dc01759042c3b4eb2186512
h5/pages/customer/contract/detail.vue
@@ -32,7 +32,11 @@
    <view v-if="contract.billStatusTip" :class="['cu-bill-tip', 'cu-bill-tip--inset', billTipClass(contract.billStatusType)]">
      <text class="cu-bill-tip__icon">{{ billTipIcon(contract.billStatusType) }}</text>
      <view class="cu-bill-tip__icon-box">
        <u-icon :name="billTipIconName(contract.billStatusType)" color="#ffffff" size="18" />
      </view>
      <text class="cu-bill-tip__text">{{ contract.billStatusTip }}</text>
@@ -54,7 +58,17 @@
      <view class="cu-panel">
        <view class="cu-panel__title">房源信息</view>
        <view class="cu-panel__head">
          <view class="cu-panel__icon cu-panel__icon--room">
            <u-icon name="home-fill" color="#ffffff" size="20" />
          </view>
          <text class="cu-panel__title-text">房源信息</text>
        </view>
        <view v-if="contract.roomList && contract.roomList.length" class="cu-room-list">
@@ -80,7 +94,17 @@
      <view class="cu-panel">
        <view class="cu-panel__title">基本信息</view>
        <view class="cu-panel__head">
          <view class="cu-panel__icon cu-panel__icon--info">
            <u-icon name="file-text-fill" color="#ffffff" size="20" />
          </view>
          <text class="cu-panel__title-text">基本信息</text>
        </view>
        <view class="cu-kv__item">
@@ -142,9 +166,211 @@
      <view v-if="showZlClause" class="cu-panel cu-clause-panel cu-clause-panel--zl">
        <view class="cu-clause-panel__head">
          <view class="cu-clause-panel__badge">
            <u-icon name="red-packet-fill" color="#ffffff" size="22" />
          </view>
          <text class="cu-clause-panel__title">租赁条款</text>
        </view>
        <view class="cu-clause-summary">
          <view class="cu-clause-chip">
            <text class="cu-clause-chip__label">押金金额</text>
            <text class="cu-clause-chip__value">{{ formatDeposit(contract.zlDeposit) }}</text>
          </view>
          <view class="cu-clause-chip">
            <text class="cu-clause-chip__label">付款方式</text>
            <text class="cu-clause-chip__value">{{ formatPayType(contract.zlPayType) }}</text>
          </view>
          <view class="cu-clause-chip cu-clause-chip--wide">
            <text class="cu-clause-chip__label">免租期</text>
            <text class="cu-clause-chip__value">{{ formatFreePeriod(contract.zlFreeStartDate, contract.zlFreeEndDate) }}</text>
          </view>
        </view>
        <view v-if="contract.zlDetailList && contract.zlDetailList.length" class="cu-clause-detail-list">
          <view v-for="(item, idx) in contract.zlDetailList" :key="item.id || idx" class="cu-clause-detail-card">
            <view class="cu-clause-detail-grid">
              <view class="cu-clause-detail-grid__item">
                <text class="cu-clause-detail-grid__label">开始日期</text>
                <text class="cu-clause-detail-grid__value">{{ formatDate(item.startDate) || '-' }}</text>
              </view>
              <view class="cu-clause-detail-grid__item">
                <text class="cu-clause-detail-grid__label">结束日期</text>
                <text class="cu-clause-detail-grid__value">{{ formatDate(item.endDate) || '-' }}</text>
              </view>
              <view class="cu-clause-detail-grid__item cu-clause-detail-grid__item--highlight">
                <text class="cu-clause-detail-grid__label">合同单价</text>
                <text class="cu-clause-detail-grid__value">{{ formatPrice(item.price, item.circleType) }}</text>
              </view>
              <view class="cu-clause-detail-grid__item cu-clause-detail-grid__item--highlight">
                <text class="cu-clause-detail-grid__label">付款提前天数</text>
                <text class="cu-clause-detail-grid__value">{{ item.advanceDays != null ? item.advanceDays + '天' : '-' }}</text>
              </view>
            </view>
          </view>
        </view>
        <view v-else class="cu-clause-empty">暂无条款明细</view>
      </view>
      <view v-if="showWyClause" class="cu-panel cu-clause-panel cu-clause-panel--wy">
        <view class="cu-clause-panel__head">
          <view class="cu-clause-panel__badge">
            <u-icon name="tags-fill" color="#ffffff" size="22" />
          </view>
          <text class="cu-clause-panel__title">物业费条款</text>
        </view>
        <view class="cu-clause-summary">
          <view class="cu-clause-chip">
            <text class="cu-clause-chip__label">押金金额</text>
            <text class="cu-clause-chip__value">{{ formatDeposit(contract.wyDeposit) }}</text>
          </view>
          <view class="cu-clause-chip">
            <text class="cu-clause-chip__label">付款方式</text>
            <text class="cu-clause-chip__value">{{ formatPayType(contract.wyPayType) }}</text>
          </view>
          <view class="cu-clause-chip cu-clause-chip--wide">
            <text class="cu-clause-chip__label">免租期</text>
            <text class="cu-clause-chip__value">{{ formatFreePeriod(contract.wyFreeStartDate, contract.wyFreeEndDate) }}</text>
          </view>
        </view>
        <view v-if="contract.wyDetailList && contract.wyDetailList.length" class="cu-clause-detail-list">
          <view v-for="(item, idx) in contract.wyDetailList" :key="item.id || idx" class="cu-clause-detail-card">
            <view class="cu-clause-detail-grid">
              <view class="cu-clause-detail-grid__item">
                <text class="cu-clause-detail-grid__label">开始日期</text>
                <text class="cu-clause-detail-grid__value">{{ formatDate(item.startDate) || '-' }}</text>
              </view>
              <view class="cu-clause-detail-grid__item">
                <text class="cu-clause-detail-grid__label">结束日期</text>
                <text class="cu-clause-detail-grid__value">{{ formatDate(item.endDate) || '-' }}</text>
              </view>
              <view class="cu-clause-detail-grid__item cu-clause-detail-grid__item--highlight">
                <text class="cu-clause-detail-grid__label">合同单价</text>
                <text class="cu-clause-detail-grid__value">{{ formatPrice(item.price, item.circleType) }}</text>
              </view>
              <view class="cu-clause-detail-grid__item cu-clause-detail-grid__item--highlight">
                <text class="cu-clause-detail-grid__label">付款提前天数</text>
                <text class="cu-clause-detail-grid__value">{{ item.advanceDays != null ? item.advanceDays + '天' : '-' }}</text>
              </view>
            </view>
          </view>
        </view>
        <view v-else class="cu-clause-empty">暂无条款明细</view>
      </view>
      <view class="cu-panel">
        <view class="cu-panel__title">合同附件</view>
        <view class="cu-panel__head">
          <view class="cu-panel__icon cu-panel__icon--file">
            <u-icon name="folder" color="#ffffff" size="20" />
          </view>
          <text class="cu-panel__title-text">合同附件</text>
        </view>
        <view v-if="contract.fileList && contract.fileList.length">
@@ -160,7 +386,11 @@
          >
            <text class="cu-file-item__icon">📎</text>
            <view class="cu-file-item__icon-box">
              <u-icon name="attach" color="#ffffff" size="22" />
            </view>
            <view class="cu-file-item__main">
@@ -385,6 +615,7 @@
<script>
import { customerContractDetail } from '@/api'
import { baseUrl } from '@/utils/config.js'
const COST_TYPE_MAP = {
  0: '租赁费',
@@ -404,7 +635,17 @@
  computed: {
    receivableLabel () { return this.billType === 0 ? '应付金额' : '应收金额' },
    receivedLabel () { return this.billType === 0 ? '实付金额' : '实收金额' },
    dueDateLabel () { return this.billType === 0 ? '应付日期' : '应收日期' }
    dueDateLabel () { return this.billType === 0 ? '应付日期' : '应收日期' },
    showZlClause () {
      if (!this.contract) return false
      const t = this.contract.type
      return t === 0 || t === 2
    },
    showWyClause () {
      if (!this.contract) return false
      const t = this.contract.type
      return t === 0 || t === 1
    }
  },
  onLoad (q) { this.id = q.id; this.load() },
@@ -419,7 +660,7 @@
        if (reloadContract) this.contract = res.data.contract
        this.bills = res.data.bills || []
        this.bills = this.sortBillsByPlanPayDate(res.data.bills || [])
      }).finally(() => {
@@ -441,6 +682,22 @@
    },
    sortBillsByPlanPayDate (list) {
      return [...list].sort((a, b) => {
        const pa = this.formatDate(a.planPayDate) || ''
        const pb = this.formatDate(b.planPayDate) || ''
        if (pb !== pa) return pb.localeCompare(pa)
        return (b.id || 0) - (a.id || 0)
      })
    },
    costTypeText (type) { return COST_TYPE_MAP[type] || '账单' },
    formatMoney (val) {
@@ -456,6 +713,74 @@
      if (!val) return ''
      return String(val).replace('T', ' ').substring(0, 10)
    },
    formatDeposit (val) {
      if (val === null || val === undefined || val === '') return '-'
      return `${val}元`
    },
    formatPayType (payType) {
      if (payType === 1) return '每三个月一付'
      if (payType === 2) return '六个月一付'
      if (payType === 3) return '一年一付'
      if (payType === 0) return '一次性付款'
      return '-'
    },
    formatFreePeriod (start, end) {
      const s = this.formatDate(start)
      const e = this.formatDate(end)
      if (!s && !e) return '-'
      return `${s || '-'} ~ ${e || '-'}`
    },
    circleTypeUnit (type) {
      const map = {
        0: '元/m²·天',
        1: '元/m²·月',
        2: '元/m²·年',
        3: '元/天',
        4: '元/月',
        5: '元/年',
        6: '元/场'
      }
      return map[type] || ''
    },
    formatPrice (price, circleType) {
      if (price === null || price === undefined || price === '') return '-'
      const unit = this.circleTypeUnit(circleType)
      return unit ? `${price} ${unit}` : String(price)
    },
@@ -527,19 +852,47 @@
    },
    billTipIcon (type) {
    billTipIconName (type) {
      if (type === 'danger') return '⚠️'
      if (type === 'danger') return 'close-circle-fill'
      if (type === 'warn') return '⏰'
      if (type === 'warn') return 'clock-fill'
      return '✅'
      return 'checkmark-circle-fill'
    },
    resolveFileUrl (raw) {
      if (!raw) return ''
      const url = String(raw).trim()
      if (/^https?:\/\//i.test(url)) return url
      if (typeof window === 'undefined') return url
      if (url.startsWith('//')) return `${window.location.protocol}${url}`
      if (url.startsWith('/')) return `${window.location.origin}${url}`
      try {
        const origin = new URL(baseUrl, window.location.href).origin
        return `${origin}/${url.replace(/^\/+/, '')}`
      } catch (e) {
        return `${window.location.origin}/${url.replace(/^\/+/, '')}`
      }
    },
    openFile (file) {
      const url = file.fileurlFull || file.fileurl
      const url = this.resolveFileUrl(file.fileurlFull || file.fileurl)
      if (!url) {
@@ -549,13 +902,29 @@
      }
      // #ifdef H5
      if (typeof window !== 'undefined' && window.document) {
      window.open(url, '_blank')
        const link = document.createElement('a')
      // #endif
        link.style.display = 'none'
      // #ifndef H5
        link.href = url
        link.target = '_blank'
        link.rel = 'noopener noreferrer'
        if (file.name) link.setAttribute('download', file.name)
        document.body.appendChild(link)
        link.click()
        document.body.removeChild(link)
        return
      }
      uni.showLoading({ title: '下载中' })
@@ -582,8 +951,6 @@
        complete: () => uni.hideLoading()
      })
      // #endif
    }