index.vue 10.5 KB
<template>
  <view class="page">
    <view class="framework-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 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>  
    <!-- 列表卡片组件 -->
    <view :class="['list-box', { 'pad-batch': batchMode }]">
      <card-list
        ref="cardRef"
        :fetch-fn="fetchList"
        :query="query"
        :extra="extraParams"
        :selectable="batchMode"
        row-key="id"
        :enable-refresh="true"
        :enable-load-more="true"
        @error="onCardError"
      >
        <template v-slot="{ item, selected }">
          <view class="card">
            <view class="card-header">
              <text class="title omit2">{{ item.customerName }}</text>
            </view>
             <view class="info-row">
              <text>框架合同编号</text><text>{{ item.code }}</text>
            </view>
             <view class="info-row">
              <text>是否签订</text><text>{{ item.hasFrameworkAgreement }}</text>
            </view>
             <view class="info-row">
              <text>品种</text><text>{{ item.materialTypeName }}</text>
            </view>
             <view class="info-row">
              <text>授信截至时间</text><text>{{ item.validityTime }}</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-data-checkbox
              mode="tag"
              :multiple="false"
              :value-field="'value'"
              :text-field="'text'"
              v-model="model.materialTypeId"
              @change="onMaterialTypeChange"
              :localdata="materialTypeOptions"
            />
          </view>
          <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 {getDicByCodes, getDicName} from '@/utils/dic';
import { queryApi } from '@/api/contract.js'
import { productVarietyQueryApi } from '@/api/devManage.js'
export default {
  components: { CardList, FilterModal },
  data() {
    return {
      searchKeyword: '',
      searchKeywordDebounced: '',
      // 批量选择
      batchMode: false,    
      // 给到 card 的筛选值
      query: {  materialTypeId: '', dateRange: [] },
      extraParams: {},  

      // 筛选弹框
      filterVisible: false,
      filterForm: { materialTypeId: '', dateRange: [] },
      materialTypeOptions: [],
    }

  },
  computed: {

  },
  watch: {

  },
  created() {
    this.getProductVariety();
  },
  // 页面触底兜底:当页面自身滚动到底部时,转调卡片组件加载更多
  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: {
    // uni-search-bar 确认搜索:更新关键字并触发 CardList 刷新
    search(e) {
      const val = e && e.value != null ? e.value : this.searchKeyword
      this.searchKeyword = val
      this.searchKeywordDebounced = val
    },
    // 输入实时搜索:1200ms 防抖,仅在停止输入超过阈值后刷新
    onSearchInput(val) {
      if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
      this.searchDebounceTimer = setTimeout(() => {
        this.searchKeywordDebounced = this.searchKeyword
        this.searchDebounceTimer = null
      }, 1200)
    },
    // 列表接口(真实请求)
    fetchList({ pageIndex, pageSize, query, extra }) {
      const params = { pageIndex, pageSize, ...extra, ...query }
      // 处理日期范围
      if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
        params.validityTimeStart = params.dateRange[0] + ' 00:00:00'
        params.validityTimeEnd = params.dateRange[1] + ' 23:59:59'
        delete params.dateRange
      }
      // 关键字(使用去抖后的值避免频繁触发)
      if (this.searchKeywordDebounced) {
        params.customerName = this.searchKeywordDebounced
      }
      return queryApi(params)
        .then(res => {
          console.log('fetchList', 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('fetchList error', err)
          this.onCardError()
          return { records: [], totalCount: 0, hasNext: false }
        })
    },
    onCardError() {
      uni.showToast({ title: '列表加载失败', icon: 'none' })
    },

    openFilter() {
      this.filterVisible = true
    },
    onFilterReset(payload) {
        // 保持弹框不关闭,仅同步表单
        this.filterForm = payload
    },
    onFilterConfirm(payload) {
        // 合并筛选值
        this.query = { ...this.query, ...payload }
    },
    onMaterialTypeChange(e) {
      const raw = e && e.detail && e.detail.value !== undefined
        ? e.detail.value
        : (e && e.value !== undefined ? e.value : '')
      // 直接同步到外层 filterForm,驱动 FilterModal 的 innerModel 更新
      this.filterForm.materialTypeId = raw
    },

    onAdd() {
    //   uni.navigateTo({ url: '/pages/contract_framework/add' })
    },



    getProductVariety() {
      productVarietyQueryApi({ 
        pageIndex: 1,
        pageSize: 9999,
        available: true}).then(res => {
      const _data = res.data || {};
      const records = _data.datas || [];
      this.materialTypeOptions = records.map(item => ({
        value: item.id,
        text: item.name
      }))
    })
    },
  }
}
</script>

<style lang="scss" scoped>
.page {
  display: flex;
  flex-direction: column;
  height: 100vh;
}
.framework-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;
            }
        }
    }
    
   
}
/* 仅当前页覆盖 uni-search-bar 盒子高度 */
::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: 112rpx;
  &.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: #3D48A3;
            }
            &.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;
        &:last-child {
        margin-bottom: 0;
        }
        text {
            width: 60%;
            &:last-child {
                color: rgba(0,0,0,0.9);
                width: 40%;
            }
        }
    }
}

.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; }
    }
}

/* 深度覆盖 uni-data-checkbox(mode=tag)内部的 tag 展示与间距 */
::v-deep .filter-form .uni-data-checklist .checklist-group {
  .checklist-box {
    &.is--tag {
        width: 212rpx;
        margin-top: 0;
        margin-bottom: 24rpx;
        margin-right: 24rpx;
        height: 80rpx;
        padding: 0;
        border-radius: 12rpx;
        background-color: #f3f3f3;
        border-color: #f3f3f3;
        &:nth-child(3n) {
            margin-right: 0;
        }
        .checklist-content {
            display: flex;
            justify-content: center;
        }
        .checklist-text {
            color: rgba(0,0,0,0.9);
            font-size: 28rpx;
        }
    }
    &.is-checked {
        background-color: $theme-primary-plain-bg !important;
        border-color: $theme-primary-plain-bg !important;
        .checklist-text {
            color: $theme-primary !important;
        }
    }
  }
  
}


</style>