index.vue 11.4 KB
<template>
  <view class="page">
    <view class="contract-list-fixed">
      <view class="search-row">
        <uni-search-bar
          v-model="searchKeyword"
          radius="6"
          placeholder="请输入需方名称或编号或办事处"
          clearButton="auto"
          cancelButton="none"
          bgColor="#F3F3F3"
          textColor="rgba(0,0,0,0.4)"
          @confirm="search"
          @input="onSearchInput"
        />
        <view class="tool-icons">
          <image v-if="$auth.hasPermi('contract-manage:processed-standard-contract:add')" class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
          <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
        </view>
      </view>

      <view class="tabs">
        <view
          v-for="(t, i) in tabs"
          :key="i"
          :class="['tab', { active: status === t.value }]"
          @click="switchTab(t)"
        >
          {{ t.label }}
        </view>
      </view>
    </view>

    <view :class="['list-box', { 'pad-batch': batchMode }]">
      <card-list
        ref="cardRef"
        :fetch-fn="fetchList"
        :query="query"
        :extra="extraParams"
        :selectable="batchMode"
        row-key="id"
        :selectedKeys.sync="selectedKeys"
        :enable-refresh="true"
        :enable-load-more="true"
        @loaded="onCardLoaded"
        @error="onCardError"
      >
        <template v-slot="{ item }">
          <view class="card" @click="goDetail(item)">
            <view class="card-header">
              <text class="title omit2">{{ item.buyerName }}</text>
              <text v-if="item.status === 'STANDARD'" :class="['status']" :style="{ background: statusMap[item.shippingStatusName] }">{{ item.shippingStatusName }}</text>
            </view>
            <view class="info-row">
              <text>编号</text><text>{{ item.code }}</text>
            </view>
            <view class="info-row">
              <text>办事处</text><text>{{ item.deptName }}</text>
            </view>
            <view class="info-row" v-if="item.status !== 'STANDARD'">
              <text>订单总额</text><text class="amount" :style="{ color: '#b67a76' }">{{ item.totalAmountIncludingTax ? '¥' : '' }}{{ formatAmount(item.totalAmountIncludingTax) || '-' }}</text>
            </view>
            <view class="info-row" v-if="item.status === 'STANDARD'">
              <text>标准合同规范性审核状态</text><span class="info-status" :style="item.standardApprovedName ? getStatusCss(item.standardApprovedName) : ''">{{ item.standardApprovedName || '-' }}</span>
            </view>
            <view class="info-row">
              <text>订货日期</text><text>{{ item.orderDate }}</text>
            </view>
          </view>
        </template>
      </card-list>
    </view>

    <filter-modal
      :visible.sync="filterVisible"
      :value.sync="filterForm"
      title="筛选"
      @reset="onFilterReset"
      @confirm="onFilterConfirm"
    >
      <template v-slot="{ model }">
        <view class="filter-form">
          <view class="form-item">
            <view class="label">订货日期</view>
            <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
          </view>
        </view>
      </template>
    </filter-modal>

  </view>
</template>

<script>
import CardList from '@/components/card/index.vue'
import FilterModal from '@/components/filter/index.vue'
import { queryContractApi, statusStyle,statusMap } from '@/api/contract.js'
import { officeQueryApi } from '@/api/devManage.js'

export default {
  components: { CardList, FilterModal },
  data() {
    return {
      searchKeyword: '',
      searchKeywordDebounced: '',
      status: 'DRAFT',

      query: { deptId: '', deptName: '', dateRange: [] },
      extraParams: {},

      batchMode: false,
      selectedKeys: [],
      currentItems: [],

      filterVisible: false,
      filterForm: { deptId: '', deptName: '', dateRange: [] },
      deptSelectVisible: false,
      statusStyle,
      statusMap,
    }
  },
  computed: {
    roleCodes() {
      const g = this.$store && this.$store.getters
      return (g && g.roleCodes) || []
    },
    tabs() {
      if (this.roleCodes.includes('constract_admin')) {
        return [
          { label: '草稿合同', value: 'DRAFT' },
          { label: '标准合同', value: 'STANDARD' },
          { label: '已删除合同', value: 'DELETED' }
        ]
      }
      return [
        { label: '草稿合同', value: 'DRAFT' },
        { label: '标准合同', value: 'STANDARD' }
      ]
    },
    extraCombined() {
      return {
        keyword: this.searchKeywordDebounced || undefined,
        status: this.status || undefined,
        type: 'PROCESS_STD_AGMT'
      }
    }
  },
  watch: {
    extraCombined: {
      deep: true,
      handler(v) {
        this.extraParams = v
      },
      immediate: true
    }
  },
  created() {
  },
  onReachBottom() {
    if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
      this.$refs.cardRef.onLoadMore()
    }
  },
  beforeDestroy() {
    if (this.searchDebounceTimer) {
      clearTimeout(this.searchDebounceTimer)
      this.searchDebounceTimer = null
    }
  },
  methods: {
    goDetail(item) {
      const id = item && (item.id || item.contractId)
      if (!id) return uni.showToast({ title: '缺少ID,无法查看详情', icon: 'none' })
      uni.navigateTo({ url: `/pages/contract_process/detail?id=${id}&status=${this.status}` })
    },
    onCardLoaded({ items }) {
      this.currentItems = items
    },
    onCardError() {
      uni.showToast({ title: '列表加载失败', icon: 'none' })
    },
    onSearchInput() {
      if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
      this.searchDebounceTimer = setTimeout(() => {
        this.searchKeywordDebounced = this.searchKeyword
        this.searchDebounceTimer = null
      }, 1200)
    },
    search(e) {
      const val = e && e.value != null ? e.value : this.searchKeyword
      this.searchKeyword = val
      this.searchKeywordDebounced = val
    },
    switchTab(item) {
      this.status = item.value
    },
    openFilter() {
      this.filterVisible = true
    },
    onFilterReset(payload) {
      this.filterForm = payload
    },
    onFilterConfirm(payload) {
      this.query = { ...payload }
    },
    openDeptSelect() {
      this.deptSelectVisible = true
    },
    onDeptConfirm(payload) {
      const val = payload && payload.value != null ? payload.value : ''
      const label = payload && payload.label != null ? payload.label : ''
      this.filterForm.deptId = val
      this.filterForm.deptName = label
    },
    toggleBatch() {
      this.batchMode = !this.batchMode
      if (!this.batchMode) this.selectedKeys = []
    },
    onAdd() {
      uni.navigateTo({ url: '/pages/contract_process/add' })
    },
    fetchList({ pageIndex, pageSize, query, extra }) {
        console.log('fetchList', pageIndex, pageSize, query, extra)
      const params = { pageIndex, pageSize, ...extra, ...query }
      if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
        params.orderDateStart = params.dateRange[0]
        params.orderDateEnd = params.dateRange[1]
        delete params.dateRange
      }
      return queryContractApi(params)
        .then(res => {
          const _data = res.data || {}
          const records = _data.datas || []
          const totalCount = _data.totalCount || 0
          const hasNext = _data.hasNext || false
          return { records, totalCount, hasNext }
        })
        .catch(err => {
          console.error('queryContractApi error', err)
          this.onCardError()
          return { records: [], totalCount: 0, hasNext: false }
        })
    },

    formatAmount(val) {
      if (val == null) return '0.00'
      try {
        const num = Number(val)
        return num.toFixed(2)
      } catch (e) {
        return String(val)
      }
    },
    getStatusCss(name) {
      const list = Array.isArray(this.statusStyle) ? this.statusStyle : []
      const found = list.find(it => it && it.text === name) || {}
      return {
        color: found.color || '#000',
        backgroundColor: found.bgColor || '#000'
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.page {
  display: flex;
  flex-direction: column;
  height: 100vh;
}
.contract-list-fixed {
  position: fixed;
  top: 96rpx;
  left: 0;
  right: 0;
  z-index: 2;
  background: #fff;

  .search-row {
    display: flex;
    align-items: center;
    padding: 16rpx 32rpx;
    .uni-searchbar { padding: 0; flex: 1; }
    .tool-icons { display: flex; .tool-icon { width: 48rpx; height: 48rpx; display: block; margin-left: 32rpx; } }
  }

  .tabs {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 26rpx 34rpx;
    border-bottom: solid 1px #E7E7E7;
    .tab { width: 50%; text-align: center; color: rgba(0,0,0,0.9); line-height: 44rpx; position: relative; font-weight: 600; }
    .tab.active { color: $theme-primary; }
    .tab.active::after {
      content: '';
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      bottom: -13px;
      width: 32rpx;
      height: 6rpx;
      border-radius: 4rpx;
      background: $theme-primary;
    }
  }
}
::v-deep .uni-searchbar__box { height: 80rpx !important; justify-content: start; .uni-searchbar__box-search-input { font-size: 32rpx !important; } }

.list-box {
  flex: 1;
  padding-top: 236rpx;
  &.pad-batch { padding-bottom: 144rpx; }
    .card { position: relative; }
    .card-header {
        margin-bottom: 28rpx;
        position: relative;

        .title {
            font-size: 36rpx;
            font-weight: 600;
            line-height: 50rpx;
            color: rgba(0,0,0,0.9);
            width: 578rpx;
        }

        .status {
            font-size: 30rpx;
            font-weight: 600;
            position: absolute;
            top: -36rpx;
            right: -32rpx;
            height: 48rpx;
            line-height: 48rpx;
            color: #fff;
            font-size: 24rpx;
            padding: 0 14rpx;
            border-radius: 6rpx;
            &.status_1 {
                background: $theme-primary;
            }
            &.status_2 {
                background: #2BA471;
            }
            &.status_3 {
                background: #D54941;
            }
            &.status_4 {
                background: #E7E7E7;
                color: rgba(0,0,0,0.9);
            }
        }
    }
    .info-row {
        display: flex;
        align-items: center;
        color: rgba(0,0,0,0.6);
        font-size: 28rpx;
        margin-bottom: 24rpx;
        height: 32rpx;
        .info-status {
            padding: 4rpx 12rpx;
            border-radius: 6rpx;
            font-size: 24rpx;
            color: #fff;
            line-height: 40rpx;
        }
        &:last-child {
        margin-bottom: 0;
        }
        text {
            width: 50%;
            &:last-child {
                color: rgba(0,0,0,0.9);
                width: 50%;
            }
        }
    }
}

.filter-form { 
    .form-item { margin-bottom: 24rpx; }
    .label { 
        margin-bottom: 20rpx; 
        color: rgba(0,0,0,0.9);
        height: 44rpx;
        line-height: 44rpx;
        font-size: 30rpx;
     }
    .fake-select { 
        height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx; 
        .placeholder { color: #999; }
        .value { color: #333; }
    }
}
</style>