myflow.vue 7.7 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="extraParams" :enable-refresh="true"
				:enable-load-more="true" row-key="instanceId" @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 {
	myFlowQueryApi
} 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: {},
			extraParams: {},
			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.extraParams = {
					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.extraParams = {
				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.extraParams = {
				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 myFlowQueryApi(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) {
			const CACHE_KEY = 'FLOW_AUDIT_CACHE'
			const _ext = JSON.parse(item.ext || '{}');
			const payload = {
				taskId: item.taskId || '',
				instanceId: item.instanceId || '',
				businessId: item.businessId || '',
				bizFlag: _ext.bizFlag || ''
			}
			uni.setStorageSync(CACHE_KEY, payload)
			uni.navigateTo({ url: '/pages/flow/audit_detail' })
		},
		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;
			padding: 0;
		}
	}

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

	.fake-select {
		height: 80rpx;
		line-height: 80rpx;
		padding: 0 20rpx;
		background: #f3f3f3;
		border-radius: 12rpx;

		.placeholder {
			color: #999;
		}

		.value {
			color: #333;
		}
	}
}
</style>