OfficeSystem/pages/verify/list/index.vue
2025-11-14 10:24:25 +08:00

617 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="verify-page">
<!-- 筛选条件 -->
<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>
</view>
<scroll-view class="list-scroll" >
<view class="card" v-for="item in displayList" :key="item.id" @click="goDetail(item)">
<view class="card-header">
<view class="left">
<text class="badge">{{ getBstTypeText(item.bstType) }}</text>
<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">
<text class="remark" v-if="item.createRemark">备注:{{ item.createRemark }}</text>
<view class="row">
<text class="label">创建时间:</text>
<text class="value">{{ item.createTime || '—' }}</text>
</view>
<!-- <view class="row">-->
<!-- <text class="label">申请人:</text>-->
<!-- <text class="value">{{ item.createName || '—' }}</text>-->
<!-- </view>-->
</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>
<view class="empty" v-if="isEmpty && !loading">
<text>暂无数据</text>
</view>
<view class="loading" v-if="loading">
<text>加载中...</text>
</view>
<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>
</scroll-view>
<!-- 日期范围选择弹窗 -->
<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>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { getVerifyList } from '@/api';
import { usePagination } from '@/composables';
import { onReachBottom } from '@dcloudio/uni-app'
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: {}
});
// 日期范围显示文本
const dateRangeText = computed(() => {
if (dateRange.value.length === 2) {
return `${dateRange.value[0]}${dateRange.value[1]}`;
}
return '选择日期范围';
});
// 获取业务类型文本
const getBstTypeText = (type) => {
if (type === 'TASK') return '任务审核';
if (type === 'UPDATE_TASK') return '延期审核';
return '审核';
};
function statusText(s) {
if (s === '1') return '已通过';
if (s === '2') return '已驳回';
return '待处理';
}
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 '';
}
};
// 显示列表(直接使用分页的 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;
}
dateRange.value = [startDate.value, endDate.value];
closeDatePicker();
refreshList();
} else {
uni.showToast({
title: '请选择完整的日期范围',
icon: 'none'
});
}
};
// 清除日期范围
const clearDateRange = () => {
dateRange.value = [];
startDate.value = '';
endDate.value = '';
refreshList();
};
// 业务类型改变
const onBstTypeChange = (value) => {
bstType.value = value;
refreshList();
};
// 审核状态改变
const onStatusChange = (value) => {
filterStatus.value = value;
refreshList();
};
// 刷新列表
const refreshList = () => {
updateParams({});
};
const goHandle = (item) => {
// 预留处理入口
uni.showToast({ title: '去处理', icon: 'none' });
};
const goDetail = (item) => {
// 可跳转到任务详情或审批详情
};
onMounted(() => {
// 读取路由参数
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage?.options || {};
if (options.bstType) {
bstType.value = options.bstType;
}
if (options.status) {
filterStatus.value = options.status;
}
getList(true);
});
onReachBottom(()=>{
loadMore();
});
</script>
<style lang="scss" scoped>
.verify-page {
width: 100%;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.filter-wrapper {
background: #fff;
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;
}
.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;
}
.list-scroll {
flex: 1;
}
.card {
background: #fff;
margin: 12px 12px 0 12px;
border-radius: 12px;
padding: 12px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.left {
display: flex;
align-items: center;
gap: 8px;
}
.badge {
font-size: 14px;
font-weight: 600;
color: #333;
}
.sub {
font-size: 12px;
color: #999;
}
.card-body {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
}
.remark {
font-size: 13px;
color: #333;
line-height: 1.6;
}
.row {
display: flex;
font-size: 12px;
color: #666;
}
.label {
width: 70px;
flex-shrink: 0;
}
.value {
color: #333;
}
.card-footer {
margin-top: 10px;
display: flex;
justify-content: flex-start;
}
.empty, .loading, .load-more {
text-align: center;
color: #999;
font-size: 12px;
padding: 16px 0;
}
// 日期选择弹窗样式
.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;
}
.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>