index.vue 9.88 KB
<template>
    <view class="page">
        <view class="message-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/filter_icon.png" @click="openFilter" />
                </view>
            </view>
        </view>

        <view class="list-box">
            <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" row-key="id"
                :enable-refresh="true" :enable-load-more="true" @loaded="onCardLoaded" @error="onCardError">
        <template v-slot="{ item }">
          <view class="card" @click.stop="onCardClick(item)">
            <view class="card-header">
              <text class="title omit2">{{ item.title || '-' }}</text>
            </view>
                        <view class="info-row">
                            <text>是否已读</text><text>{{ item.readed ? '是' : '否' }}</text>
                        </view>
                        <view class="info-row">
                            <text>创建时间</text><text>{{ item.createTime || '-' }}</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.readed" :localdata="readOptions" />
                    </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 { queryApi as messageQueryApi } from '@/api/message.js'

export default {
    name: 'MessageList',
    components: { CardList, FilterModal },
    data() {
        return {
            searchKeyword: '',
            searchKeywordDebounced: '',
            searchDebounceTimer: null,

            // 给到 card 的筛选值
            query: { readed: '', dateRange: [] },
            extraParams: {},

            // 筛选弹框
            filterVisible: false,
            filterForm: { readed: false, dateRange: [] },
            readOptions: [
                { value: true, text: '是' },
                { value: false, text: '否' }
            ],

            currentItems: []
        }
    },
    computed: {
        extraCombined() {
            return {
                title: this.searchKeywordDebounced || undefined
            }
        }
    },
    watch: {
        extraCombined: {
            deep: true,
            handler(v) {
                this.extraParams = v
            },
            immediate: true
        }
    },
    created() {
        const [start, end] = this.getDefaultDateRange()
        this.filterForm.dateRange = [start, end]
        this.query = { readed: this.filterForm.readed, dateRange: [start, end] }
    },
    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: {
        // 默认日期范围:当前日期前一个月到今日
        getDefaultDateRange() {
            const fmt = d => {
                const y = d.getFullYear()
                const m = String(d.getMonth() + 1).padStart(2, '0')
                const dd = String(d.getDate()).padStart(2, '0')
                return `${y}-${m}-${dd}`
            }
            const end = new Date()
            const start = new Date(end)
            start.setMonth(start.getMonth() - 1)
            return [fmt(start), fmt(end)]
        },
        onCardLoaded({ items }) {
            this.currentItems = items
        },
    onCardError() {
      uni.showToast({ title: '列表加载失败', icon: 'none' })
    },
    onCardClick(item) {
      const id = (item && (item.id || item.code)) || ''
      if (!id) return
      const query = '?id=' + encodeURIComponent(id)
      uni.navigateTo({ url: '/pages/message/detail' + query })
    },
        // 输入实时搜索:1200ms 防抖
        onSearchInput(val) {
            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
        },
        openFilter() {
            this.filterVisible = true
        },
        onFilterReset(payload) {
            this.filterForm = payload
        },
        onFilterConfirm(payload) {
            if ((payload.readed === '' || payload.readed == null) && this.filterForm.readed !== '') {
                payload.readed = this.filterForm.readed
            }
            this.query = { ...payload }
        },
        // 列表接口
        fetchList({ pageIndex, pageSize, query, extra }) {
            const params = {
                pageIndex,
                pageSize,
                ...extra,
                ...query
            }
            if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
                params.createTimeStart = params.dateRange[0] + ' 00:00:00'
                params.createTimeEnd = params.dateRange[1] + ' 23:59:59'
                delete params.dateRange
            }
            if (this.searchKeywordDebounced) {
                params.title = this.searchKeywordDebounced
            }
            return messageQueryApi(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('fetchList error', err)
                    this.onCardError()
                    return { records: [], totalCount: 0, hasNext: false }
                })
        }
    }
}
</script>

<style lang="scss" scoped>
.page {
    display: flex;
    flex-direction: column;
    height: 100vh;
}

.message-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: 140rpx;

    .card {
        position: relative;
    }

    .card-header {
        margin-bottom: 28rpx;

        .title {
            font-size: 36rpx;
            font-weight: 600;
            line-height: 50rpx;
            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;

        &:last-child {
            margin-bottom: 0;
        }

        text {
            width: 60%;
            line-height: 32rpx;

            &: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;
  }

  .uni-easyinput {
    border: 1rpx solid #f3f3f3;
  }

}

/* 深度覆盖 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>