myflow.vue 7.61 KB
<template>
	<view class="page">
		<view class="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="onSearchConfirm"
					@input="onSearchInput" />
				<image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
			</view>
		</view>

		<view class="list-box">
			<CardList ref="cardRef" :fetchFn="fetchList" :query="query" :extra="extra" :enable-refresh="true"
				:enable-load-more="true" row-key="id" @loaded="onLoaded" @error="onError">
				<template v-slot="{ item }">
					<view class="card">
						<view class="card-header">
							<text class="title omit2">{{ item.title }}</text>
						</view>
						<view class="info-row">
							<text>流程模块</text><text>{{ getDicName('PROCESS_MODE', item.mode, cardModeOptions) }}</text>
						</view>
						<view class="info-row">
							<text>发起人</text><text>{{ item.startBy }}</text>
						</view>
						<view class="info-row">
							<text>发起时间</text><text>{{ item.startTime }}</text>
						</view>
						<view class="footer">
							<button class="btn" type="primary" plain @click.stop="onDetail(item)">审核详情</button>
						</view>
					</view>
				</template>
			</CardList>
		</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>
						<view class="fake-select" @click="openModeSelect">
							<text v-if="!model.mode" class="placeholder">请选择</text>
							<text v-else class="value">{{ model.modeName }}</text>
						</view>
					</view>
				</view>
			</template>
		</filter-modal>

		<SingleSelectSheet :visible.sync="modeSelectVisible" :options="modeOptions" v-model="filterForm.mode"
			title="流程模块" @confirm="onModeConfirm" />
	</view>
</template>

<script>
	import CardList from '@/components/card/index.vue'
	import FilterModal from '@/components/filter/index.vue'
	import SingleSelectSheet from '@/components/single-select/index.vue'
	import {
		queryApi
	} from '@/api/flow.js'
	import {
		getDicByCodes
	} from '@/utils/dic'
	import {
		getDicName
	} from '@/utils/dic.js'

	export default {
		name: 'MyFlow',
		components: {
			CardList,
			FilterModal,
			SingleSelectSheet
		},
		data() {
			return {
				searchKeyword: '',
				searchKeywordDebounced: '',
				searchDebounceTimer: null,
				filterVisible: false,
				filterForm: {
					mode: '',
					modeName: ''
				},
				modeSelectVisible: false,
				modeOptions: [],
				cardModeOptions: [],
				query: {},
				extra: {},
				currentItems: []
			}
		},
		created() {
			this.loadModeOptions()
		},
		onReachBottom() {
			if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
				this.$refs.cardRef.onLoadMore()
			}
		},
		watch: {},
		methods: {
			onSearchInput(val) {
				if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
				this.searchDebounceTimer = setTimeout(() => {
					this.searchKeywordDebounced = this.searchKeyword
					this.searchDebounceTimer = null
					this.extra = {
						searchKey: this.searchKeywordDebounced || '',
						mode: this.filterForm.mode || ''
					}
				}, 800)
			},
			onSearchConfirm(e) {
				const val = e && e.value != null ? e.value : this.searchKeyword
				this.searchKeyword = val
				this.searchKeywordDebounced = val
				this.extra = {
					searchKey: this.searchKeywordDebounced || '',
					mode: this.filterForm.mode || ''
				}
			},
			openFilter() {
				this.filterVisible = true
			},
			onFilterReset(payload) {
				this.filterForm = payload
			},
			onFilterConfirm(payload) {
				// 兜底:部分端可能未同步 v-model
				if ((payload.mode === '' || payload.mode == null) && this.filterForm.mode !== '') {
					payload.mode = this.filterForm.mode
					payload.modeName = this.filterForm.modeName
				}
				this.filterForm = payload
				// 仅在确认时更新给 CardList 的条件
				this.extra = {
					searchKey: this.searchKeywordDebounced || '',
					mode: payload.mode || ''
				}
				this.filterVisible = false
			},
			openModeSelect() {
				this.modeSelectVisible = true
			},
			onModeConfirm({
				value,
				label
			}) {
				this.filterForm.mode = value || '';
				this.filterForm.modeName = label || '';
			},
			onLoaded({
				items
			}) {
				this.currentItems = items || []
			},
			onError() {
				uni.showToast({
					title: '列表加载失败',
					icon: 'none'
				})
			},
			async loadModeOptions() {
				try {
					const dicCodes = ['PROCESS_MODE']
					const results = await getDicByCodes(dicCodes)
					const option = results.PROCESS_MODE.data || []
					this.cardModeOptions = Array.isArray(option) ? option : [];
					this.modeOptions = option.map(it => ({
						label: it.name || '',
						value: it.code || ''
					}))
				} catch (e) {
					this.modeOptions = [];
					this.cardModeOptions = [];
				}
			},
			fetchList({
				pageIndex,
				pageSize,
				query,
				extra
			}) {
				const params = {
					pageIndex,
					pageSize,
					...query,
					...extra
				}
				return queryApi(params).then(res => {
					const _data = res.data || {}
					const records = _data.datas || _data.list || _data.records || []
					const totalCount = _data.totalCount || _data.count || 0
					const hasNext = _data.hasNext || false
					return {
						records,
						totalCount,
						hasNext
					}
				}).catch(() => {
					this.onError()
					return {
						records: [],
						totalCount: 0,
						hasNext: false
					}
				})
			},
			onDetail(item) {
				uni.showToast({
					title: '审核详情',
					icon: 'none'
				})
			},
			getDicName: getDicName,
		}
	}
</script>

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

	.fixed {
		position: fixed;
		top: 96rpx;
		left: 0;
		right: 0;
		z-index: 2;
		background: #fff;
	}

	.search-row {
		display: flex;
		align-items: center;
		padding: 16rpx 32rpx;

		.tool-icon {
			width: 48rpx;
			height: 48rpx;
			margin-left: 16rpx;
		}

		.uni-searchbar {
			padding: 0;
			flex: 1;
		}
	}

	/* 仅当前页覆盖 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: 112rpx 0 0
	}

	.card {
		.footer {
			margin-top: 28rpx;
			box-shadow: inset 0px 1px 0px 0px #E7E7E7;
			padding-top: 22rpx;
			display: flex;
			justify-content: flex-end;
			.btn {
				height: 64rpx;
				line-height: 60rpx;
				font-size: 28rpx;
				width: 160rpx;
				margin-left: auto;
				margin-right: 0;
			}
		}

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

			.title {
				font-size: 36rpx;
				font-weight: 600;
				line-height: 50rpx;
				color: #323241;
			}
		}

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