OfficeSystem/pages/verify/list/index.vue

630 lines
14 KiB
Vue
Raw Normal View History

2025-11-13 17:18:37 +08:00
<template>
<view class="verify-page">
2025-11-14 10:01:12 +08:00
<!-- 筛选条件 -->
<view class="filter-wrapper">
<view class="filter-item">
<text class="filter-label">业务类型</text>
<view class="filter-buttons">
<view
class="filter-btn"
:class="{ active: bstType === '' }"
@click="onBstTypeChange('')"
>
全部
</view>
<view
class="filter-btn"
:class="{ active: bstType === 'TASK' }"
@click="onBstTypeChange('TASK')"
>
任务
</view>
<view
class="filter-btn"
:class="{ active: bstType === 'UPDATE_TASK' }"
@click="onBstTypeChange('UPDATE_TASK')"
>
延期审核
</view>
</view>
</view>
<view class="filter-item">
<text class="filter-label">审核状态</text>
<view class="filter-buttons">
<view
class="filter-btn"
:class="{ active: filterStatus === '' }"
@click="onStatusChange('')"
>
全部
</view>
<view
class="filter-btn"
:class="{ active: filterStatus === '1' }"
@click="onStatusChange('1')"
>
已通过
</view>
<view
class="filter-btn"
:class="{ active: filterStatus === '2' }"
@click="onStatusChange('2')"
>
已驳回
</view>
<view
class="filter-btn"
:class="{ active: filterStatus === '3' }"
@click="onStatusChange('3')"
>
待审核
</view>
</view>
</view>
<view class="filter-item">
<text class="filter-label">时间范围</text>
<view class="filter-buttons">
<view
class="filter-btn date-btn"
@click="openDatePicker"
>
<text>{{ dateRangeText }}</text>
<text class="date-icon">📅</text>
</view>
<view
v-if="dateRange.length > 0"
class="filter-btn clear-btn"
@click="clearDateRange"
>
清除
</view>
</view>
</view>
2025-11-13 17:18:37 +08:00
</view>
2025-11-14 10:24:25 +08:00
<scroll-view class="list-scroll" >
2025-11-13 17:18:37 +08:00
<view class="card" v-for="item in displayList" :key="item.id" @click="goDetail(item)">
<view class="card-header">
<view class="left">
2025-11-14 10:01:12 +08:00
<text class="badge">{{ getBstTypeText(item.bstType) }}</text>
2025-11-13 17:18:37 +08:00
<text class="sub">所属项目 · {{ item.projectName || '—' }}</text>
</view>
<view class="right">
<uv-tags :text="statusText(item.status)" :type="statusType(item.status)" size="mini"></uv-tags>
</view>
</view>
<view class="card-body">
2025-11-14 10:24:25 +08:00
<text class="remark" v-if="item.createRemark">备注{{ item.createRemark }}</text>
2025-11-13 17:18:37 +08:00
<view class="row">
2025-11-14 10:01:12 +08:00
<text class="label">创建时间</text>
<text class="value">{{ item.createTime || '—' }}</text>
2025-11-13 17:18:37 +08:00
</view>
2025-11-14 10:24:25 +08:00
<!-- <view class="row">-->
<!-- <text class="label">申请人</text>-->
<!-- <text class="value">{{ item.createName || '—' }}</text>-->
<!-- </view>-->
2025-11-13 17:18:37 +08:00
</view>
<view class="card-footer">
<uv-button
v-if="item.status === '3'"
type="primary"
size="small"
text="去处理"
@click.stop="goHandle(item)"
/>
<uv-button
v-else
type="info"
size="small"
:text="statusText(item.status)"
:plain="true"
:disabled="true"
/>
</view>
</view>
2025-11-14 10:01:12 +08:00
<view class="empty" v-if="isEmpty && !loading">
2025-11-13 17:18:37 +08:00
<text>暂无数据</text>
</view>
<view class="loading" v-if="loading">
<text>加载中...</text>
</view>
2025-11-14 10:01:12 +08:00
<view class="load-more" v-if="list.length > 0">
<text v-if="loading">加载中...</text>
<text v-else-if="noMore">没有更多数据了</text>
<text v-else>上拉加载更多</text>
</view>
2025-11-13 17:18:37 +08:00
</scroll-view>
2025-11-14 10:01:12 +08:00
<!-- 日期范围选择弹窗 -->
<view v-if="showDatePicker" class="modal-mask" @click="closeDatePicker">
<view class="modal-content date-modal" @click.stop>
<view class="modal-title">选择日期范围</view>
<view class="date-picker-content">
<view class="date-section">
<text class="section-label">开始日期</text>
<picker
mode="date"
:value="startDate"
:start="'2020-01-01'"
:end="endDate || '2099-12-31'"
@change="handleStartDateChange"
>
<view class="picker-display">
{{ startDate || '请选择开始日期' }}
</view>
</picker>
</view>
<view class="date-section">
<text class="section-label">结束日期</text>
<picker
mode="date"
:value="endDate"
:start="startDate || '2020-01-01'"
:end="'2099-12-31'"
@change="handleEndDateChange"
>
<view class="picker-display">
{{ endDate || '请选择结束日期' }}
</view>
</picker>
</view>
</view>
<view class="modal-buttons">
<button class="modal-btn" @click="closeDatePicker">取消</button>
<button class="modal-btn primary" @click="confirmDateRange">确定</button>
</view>
</view>
</view>
2025-11-13 17:18:37 +08:00
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
2025-11-13 17:18:37 +08:00
import { getVerifyList } from '@/api';
2025-11-14 10:01:12 +08:00
import { usePagination } from '@/composables';
2025-11-14 10:24:25 +08:00
import { onReachBottom } from '@dcloudio/uni-app'
2025-11-14 10:01:12 +08:00
const bstType = ref('');
const filterStatus = ref('');
const dateRange = ref([]); // [startDate, endDate] 格式: yyyy-MM-dd
const showDatePicker = ref(false);
const startDate = ref('');
const endDate = ref('');
// 使用分页组合式函数
const {
list,
loading,
noMore,
isEmpty,
getList,
loadMore,
updateParams,
queryParams
} = usePagination({
fetchData: async (params) => {
// 构建请求参数
const requestParams = {
...params,
orderByColumn: 'createTime',
isAsc: 'descending'
};
// 添加业务类型筛选
if (bstType.value) {
requestParams.bstType = bstType.value;
}
// 添加审核状态筛选
if (filterStatus.value) {
requestParams.status = filterStatus.value;
}
// 添加时间范围筛选
if (dateRange.value.length === 2) {
requestParams.createTimeList = dateRange.value;
}
const res = await getVerifyList(requestParams);
// 处理数据,添加 expireTime 字段
if (res?.rows) {
res.rows = res.rows.map(r => ({
...r,
expireTime: safeParseExpire(r.data)
}));
}
return res;
},
mode: 'loadMore',
pageSize: 20,
defaultParams: {}
});
2025-11-13 17:18:37 +08:00
2025-11-14 10:01:12 +08:00
// 日期范围显示文本
const dateRangeText = computed(() => {
if (dateRange.value.length === 2) {
return `${dateRange.value[0]}${dateRange.value[1]}`;
}
return '选择日期范围';
});
2025-11-13 17:18:37 +08:00
2025-11-14 10:01:12 +08:00
// 获取业务类型文本
const getBstTypeText = (type) => {
if (type === 'TASK') return '任务审核';
if (type === 'UPDATE_TASK') return '延期审核';
return '审核';
};
2025-11-13 17:18:37 +08:00
function statusText(s) {
if (s === '1') return '已通过';
if (s === '2') return '已驳回';
return '待处理';
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
function statusType(s) {
if (s === '1') return 'success';
if (s === '2') return 'error';
return 'warning';
}
const safeParseExpire = (dataField) => {
if (!dataField) return '';
try {
if (typeof dataField === 'string') {
return JSON.parse(dataField)?.expireTime || '';
}
return dataField.expireTime || '';
} catch (e) {
return '';
}
};
2025-11-14 10:01:12 +08:00
// 显示列表(直接使用分页的 list
const displayList = computed(() => list.value);
// 打开日期选择器
const openDatePicker = () => {
if (dateRange.value.length === 2) {
startDate.value = dateRange.value[0];
endDate.value = dateRange.value[1];
} else {
startDate.value = '';
endDate.value = '';
}
showDatePicker.value = true;
};
// 关闭日期选择器
const closeDatePicker = () => {
showDatePicker.value = false;
};
// 开始日期改变
const handleStartDateChange = (e) => {
startDate.value = e.detail.value;
};
// 结束日期改变
const handleEndDateChange = (e) => {
endDate.value = e.detail.value;
};
// 确认日期范围
const confirmDateRange = () => {
if (startDate.value && endDate.value) {
// 确保开始日期 <= 结束日期
if (startDate.value > endDate.value) {
uni.showToast({
title: '开始日期不能大于结束日期',
icon: 'none'
});
return;
2025-11-13 17:18:37 +08:00
}
2025-11-14 10:01:12 +08:00
dateRange.value = [startDate.value, endDate.value];
closeDatePicker();
refreshList();
} else {
uni.showToast({
title: '请选择完整的日期范围',
icon: 'none'
});
2025-11-13 17:18:37 +08:00
}
};
2025-11-14 10:01:12 +08:00
// 清除日期范围
const clearDateRange = () => {
dateRange.value = [];
startDate.value = '';
endDate.value = '';
refreshList();
};
// 业务类型改变
const onBstTypeChange = (value) => {
bstType.value = value;
refreshList();
2025-11-13 17:18:37 +08:00
};
2025-11-14 10:01:12 +08:00
// 审核状态改变
const onStatusChange = (value) => {
filterStatus.value = value;
refreshList();
};
// 刷新列表
const refreshList = () => {
updateParams({});
2025-11-13 17:18:37 +08:00
};
const goHandle = (item) => {
// 跳转到审核详情页进行处理
uni.navigateTo({
url: `/pages/verify/detail/index?id=${item.id}`
})
2025-11-13 17:18:37 +08:00
};
const goDetail = (item) => {
// 跳转到审核详情页
uni.navigateTo({
url: `/pages/verify/detail/index?id=${item.id}`
})
2025-11-13 17:18:37 +08:00
};
onMounted(() => {
// 读取路由参数
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage?.options || {};
if (options.bstType) {
bstType.value = options.bstType;
}
2025-11-14 10:01:12 +08:00
if (options.status) {
filterStatus.value = options.status;
}
getList(true);
// 监听审核列表刷新事件
uni.$on('verifyListRefresh', refreshList);
});
onUnmounted(() => {
// 取消监听审核列表刷新事件
uni.$off('verifyListRefresh', refreshList);
2025-11-13 17:18:37 +08:00
});
2025-11-14 10:24:25 +08:00
onReachBottom(()=>{
loadMore();
});
2025-11-13 17:18:37 +08:00
</script>
<style lang="scss" scoped>
.verify-page {
width: 100%;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
2025-11-14 10:01:12 +08:00
.filter-wrapper {
2025-11-13 17:18:37 +08:00
background: #fff;
2025-11-14 10:01:12 +08:00
padding: 12px;
border-bottom: 1px solid #f0f0f0;
}
.filter-item {
display: flex;
align-items: center;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.filter-label {
font-size: 13px;
color: #666;
width: 70px;
flex-shrink: 0;
2025-11-13 17:18:37 +08:00
}
2025-11-14 10:01:12 +08:00
.filter-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
flex: 1;
}
.filter-btn {
padding: 6px 12px;
font-size: 12px;
color: #666;
background: #f5f5f5;
border-radius: 16px;
border: 1px solid #e0e0e0;
transition: all 0.3s;
&.active {
color: #fff;
background: #2979ff;
border-color: #2979ff;
}
&.date-btn {
display: flex;
align-items: center;
gap: 4px;
}
&.clear-btn {
background: #ffebee;
color: #f44336;
border-color: #f44336;
}
}
.date-icon {
font-size: 12px;
}
2025-11-13 17:18:37 +08:00
.list-scroll {
flex: 1;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.card {
background: #fff;
margin: 12px 12px 0 12px;
border-radius: 12px;
padding: 12px;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.left {
display: flex;
align-items: center;
gap: 8px;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.badge {
font-size: 14px;
font-weight: 600;
color: #333;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.sub {
font-size: 12px;
color: #999;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.card-body {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.remark {
font-size: 13px;
color: #333;
line-height: 1.6;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.row {
display: flex;
font-size: 12px;
color: #666;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.label {
width: 70px;
flex-shrink: 0;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.value {
color: #333;
}
2025-11-14 10:01:12 +08:00
2025-11-13 17:18:37 +08:00
.card-footer {
margin-top: 10px;
display: flex;
justify-content: flex-start;
}
2025-11-14 10:01:12 +08:00
.empty, .loading, .load-more {
2025-11-13 17:18:37 +08:00
text-align: center;
color: #999;
font-size: 12px;
padding: 16px 0;
}
2025-11-14 10:01:12 +08:00
// 日期选择弹窗样式
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
2025-11-13 17:18:37 +08:00
2025-11-14 10:01:12 +08:00
.modal-content {
background: #fff;
border-radius: 12px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-title {
padding: 16px;
font-size: 16px;
font-weight: 600;
text-align: center;
border-bottom: 1px solid #f0f0f0;
}
.date-picker-content {
padding: 16px;
overflow-y: auto;
flex: 1;
}
.date-section {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
.section-label {
display: block;
font-size: 14px;
color: #333;
margin-bottom: 8px;
font-weight: 500;
}
.picker-display {
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
border: 1px solid #e0e0e0;
font-size: 14px;
color: #333;
text-align: center;
}
.modal-buttons {
display: flex;
border-top: 1px solid #f0f0f0;
}
.modal-btn {
flex: 1;
padding: 16px;
font-size: 14px;
background: #fff;
color: #666;
border: none;
border-right: 1px solid #f0f0f0;
&:last-child {
border-right: none;
}
&.primary {
color: #2979ff;
font-weight: 500;
}
}
</style>