index.vue 9.95 KB
<template>
	<view class="page">
		<view class="dev-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">
			<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.unitName || '-' }}</text>
							<text :class="['status', `status_${item.status}`]">{{ getAuditStatusText(item.status) }}</text>
						</view>
						<view class="info-row">
							<text>供应商</text><text>{{ item.supplierName || '-' }}</text>
						</view>
						<view class="info-row">
							<text>供应商分类</text><text>{{ supplierCategoryText(item.supplierCategory) }}</text>
						</view>
						<view class="info-row">
							<text>下次评审时间</text><text>{{ formatDateOnly(item.nextReviewTime) }}</text>
						</view>
						<view class="info-row">
							<text>登记日期</text><text>{{ formatDateOnly(item.createTime) }}</text>
						</view>
						<view v-if="reviewHint(item)" class="review-hint">{{ reviewHint(item) }}</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.status" @change="onAuditStatusChange" :localdata="statusOptions" />
					</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.js'
import { foreignCustomerCreditQueryApi } from '@/api/procure-manage/foreignCustomerCredit.js'

export default {
	name: 'ForeignTradeList',
	components: { CardList, FilterModal },
	data() {
		return {
			searchKeyword: '',
			searchKeywordDebounced: '',
			searchDebounceTimer: null,
			query: {
				unitName: '',
				status: '',
				dateRange: []
			},
			extraParams: {},
			currentItems: [],
			filterVisible: false,
			filterForm: {
				status: '',
				dateRange: []
			},
			statusOptions: [
				{ value: '', text: '全部' }
			],
			dicOptions: {
				AUDIT_STATUS: []
			},
		}
	},
	watch: {
		searchKeywordDebounced(v) {
			this.query = { ...this.query, unitName: v || '' }
		}
	},
	created() {
		this.loadDicData()
	},
	onShow() {
		let needRefresh = ''
		try { needRefresh = uni.getStorageSync('FOREIGN_TRADE_LIST_NEED_REFRESH') } catch (e) {}
		if (!needRefresh) return
		try { uni.removeStorageSync('FOREIGN_TRADE_LIST_NEED_REFRESH') } catch (e) {}
		if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.reload) {
			this.$refs.cardRef.reload()
		}
	},
	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: {
		async loadDicData() {
			try {
				const results = await getDicByCodes(['AUDIT_STATUS'])
				this.dicOptions.AUDIT_STATUS = (results.AUDIT_STATUS && results.AUDIT_STATUS.data) || []
				const list = Array.isArray(this.dicOptions.AUDIT_STATUS) ? this.dicOptions.AUDIT_STATUS : []
				this.statusOptions = [{ value: '', text: '全部' }].concat(
					list.map(it => ({ value: it && it.code != null ? String(it.code) : '', text: it && it.name != null ? String(it.name) : '' })).filter(it => it.value)
				)
			} catch (e) {
				this.dicOptions.AUDIT_STATUS = []
				this.statusOptions = [{ value: '', text: '全部' }]
			}
		},
		supplierCategoryText(v) {
			const s = v == null ? '' : String(v).trim()
			if (!s) return '-'
			const map = { A: 'A类供应商', B: 'B类供应商', C: 'C类供应商', D: 'D类供应商' }
			return map[s] || s
		},
		formatDateOnly(v) {
			const s = v == null ? '' : String(v).trim()
			if (!s) return '-'
			if (s.length >= 10) return s.slice(0, 10)
			return s
		},
		parseDateOnly(v) {
			const s = v == null ? '' : String(v).trim()
			if (!s) return null
			const d = new Date(s.length >= 10 ? s.slice(0, 10) : s)
			if (!Number.isFinite(d.getTime())) return null
			return new Date(d.getFullYear(), d.getMonth(), d.getDate())
		},
		reviewHint(item) {
			const nextReviewTime = item && item.nextReviewTime != null ? String(item.nextReviewTime) : ''
			const d = this.parseDateOnly(nextReviewTime)
			if (!d) return ''
			const now = new Date()
			const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
			const diffDays = Math.floor((d.getTime() - today.getTime()) / (24 * 60 * 60 * 1000))
			if (diffDays < 0) return '已到评审时间'
			if (diffDays <= 15) return `距评审还有${diffDays}天`
			return ''
		},
		onAdd() {
			uni.navigateTo({ url: '/pages/foreign_trade/add' })
		},
		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
		},
		openFilter() {
			this.filterForm = {
				status: this.query.status || '',
				dateRange: Array.isArray(this.query.dateRange) ? this.query.dateRange.slice(0) : []
			}
			this.filterVisible = true
		},
		onFilterReset(payload) {
			this.filterForm = payload
		},
		onFilterConfirm(payload) {
			this.query = {
				unitName: this.query.unitName || '',
				status: payload.status || '',
				dateRange: Array.isArray(payload.dateRange) ? payload.dateRange : []
			}
		},
		onAuditStatusChange(e) {
			const raw = e && e.detail && e.detail.value !== undefined ? e.detail.value : (e && e.value !== undefined ? e.value : '')
			this.filterForm.status = raw
		},
		getAuditStatusText(status) {
			const v = status == null ? '' : String(status)
			if (!v) return '-'
			return getDicName('AUDIT_STATUS', v, this.dicOptions.AUDIT_STATUS) || v
		},
		fetchList({ pageIndex, pageSize, query }) {
			const q = query || {}
			const range = Array.isArray(q.dateRange) ? q.dateRange : []
			const params = {
				pageIndex,
				pageSize,
				unitName: q.unitName || '',
				status: q.status || '',
				createDateStart: (range && range.length === 2) ? range[0] : '',
				createDateEnd: (range && range.length === 2) ? range[1] : ''
			}
			return foreignCustomerCreditQueryApi(params).then(res => {
				const _data = res && res.data ? res.data : {}
				let records = _data.datas || _data.list || _data.records || []
				const totalCount = _data.totalCount || _data.count || 0
				const hasNext = _data.hasNext || false
				records = records.map(it => ({ ...it }))
				return { records, totalCount, hasNext }
			}).catch(() => {
				this.onCardError()
				return { records: [], totalCount: 0, hasNext: false }
			})
		},
		onCardClick(item) {
			uni.navigateTo({ url: '/pages/foreign_trade/detail?id=' + item.id })
		},
	}
}
</script>

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

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

::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;
		position: relative;

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

		.status {
			position: absolute;
			top: -32rpx;
			right: -12rpx;
			height: 48rpx;
			line-height: 48rpx;
			font-weight: 600;
			color: #fff;
			font-size: 24rpx;
			padding: 0 14rpx;
			border-radius: 6rpx;

			&.status_AUDIT {
				background: $theme-primary;
			}

			&.status_PASS {
				background: #2BA471;
			}

			&.status_REFUSE {
				background: #D54941;
			}

			&.status_CANCEL {
				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: 50%;

			&:last-child {
				color: rgba(0, 0, 0, 0.9);
				width: 50%;
			}
		}
	}

	.review-hint {
		margin-top: 12rpx;
		font-size: 26rpx;
		color: #D54941;
	}
}

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