审批管理,添加条件筛选

This commit is contained in:
WindowBird 2025-11-14 10:01:12 +08:00
parent 1a8c41c096
commit 5466a0534a
3 changed files with 464 additions and 65 deletions

View File

@ -1,5 +1,36 @@
// 审批相关 API
export const getVerifyList = (params = {}) => {
// 处理 createTimeList 参数,如果是数组则转换为重复参数格式
const processedParams = { ...params };
if (Array.isArray(processedParams.createTimeList)) {
const tempList = processedParams.createTimeList;
// 删除原来的 createTimeList因为我们要手动处理
delete processedParams.createTimeList;
// 构建查询字符串
let queryString = '';
tempList.forEach(date => {
queryString += `&createTimeList=${encodeURIComponent(date)}`;
});
return uni.$uv.http.get(`/bst/verify/list?${queryString}`, {
params: {
pageNum: 1,
pageSize: 20,
orderByColumn: 'createTime',
isAsc: 'descending',
bstType: 'UPDATE_TASK',
...processedParams
},
custom: {
auth: true
}
});
}
// 如果不是数组,正常处理
return uni.$uv.http.get('/bst/verify/list', {
params: {
pageNum: 1,
@ -7,12 +38,10 @@ export const getVerifyList = (params = {}) => {
orderByColumn: 'createTime',
isAsc: 'descending',
bstType: 'UPDATE_TASK',
...params
...processedParams
},
custom: {
auth: true
}
});
};
};

View File

@ -1,14 +1,92 @@
<template>
<view class="verify-page">
<view class="tabs-wrapper">
<uv-tabs :list="tabs" :current="currentTab" @click="onTabClick"></uv-tabs>
<!-- 筛选条件 -->
<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" scroll-y @scrolltolower="loadMore">
<view class="card" v-for="item in displayList" :key="item.id" @click="goDetail(item)">
<view class="card-header">
<view class="left">
<text class="badge">申请延期</text>
<text class="badge">{{ getBstTypeText(item.bstType) }}</text>
<text class="sub">所属项目 · {{ item.projectName || '—' }}</text>
</view>
<view class="right">
@ -19,8 +97,8 @@
<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.expireTime || '—' }}</text>
<text class="label">创建时间</text>
<text class="value">{{ item.createTime || '—' }}</text>
</view>
<view class="row">
<text class="label">申请人</text>
@ -47,51 +125,150 @@
</view>
</view>
<view class="empty" v-if="!loading && displayList.length === 0">
<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';
const tabs = [
{ name: '待我处理', value: 'pending' },
{ name: '我已处理', value: 'done' }
];
const currentTab = ref(0);
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 pageNum = ref(1);
const pageSize = ref(20);
const total = ref(0);
const rawList = ref([]);
const loading = ref(false);
const bstType = ref('UPDATE_TASK');
// 使
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 displayList = computed(() => {
if (currentTab.value === 0) {
return rawList.value.filter(i => String(i.status) === '3');
}
return rawList.value.filter(i => String(i.status) !== '3');
});
const safeParseExpire = (dataField) => {
if (!dataField) return '';
try {
@ -104,41 +281,81 @@ const safeParseExpire = (dataField) => {
}
};
const fetchList = async () => {
if (loading.value) return;
loading.value = true;
try {
const res = await getVerifyList({
pageNum: pageNum.value,
pageSize: pageSize.value,
orderByColumn: 'createTime',
isAsc: 'descending',
bstType: bstType.value
});
const rows = res?.rows || [];
total.value = res?.total || 0;
const mapped = rows.map(r => ({
...r,
expireTime: safeParseExpire(r.data)
}));
if (pageNum.value === 1) {
rawList.value = mapped;
} else {
rawList.value = rawList.value.concat(mapped);
// 使 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;
}
} finally {
loading.value = false;
dateRange.value = [startDate.value, endDate.value];
closeDatePicker();
refreshList();
} else {
uni.showToast({
title: '请选择完整的日期范围',
icon: 'none'
});
}
};
const loadMore = () => {
if (rawList.value.length >= total.value) return;
pageNum.value += 1;
fetchList();
//
const clearDateRange = () => {
dateRange.value = [];
startDate.value = '';
endDate.value = '';
refreshList();
};
const onTabClick = (tab) => {
currentTab.value = tab.index;
//
const onBstTypeChange = (value) => {
bstType.value = value;
refreshList();
};
//
const onStatusChange = (value) => {
filterStatus.value = value;
refreshList();
};
//
const refreshList = () => {
updateParams({});
};
const goHandle = (item) => {
@ -158,85 +375,238 @@ onMounted(() => {
if (options.bstType) {
bstType.value = options.bstType;
}
fetchList();
if (options.status) {
filterStatus.value = options.status;
}
getList(true);
});
</script>
<style lang="scss" scoped>
.verify-page {
width: 100%;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.tabs-wrapper {
.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 {
.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>

View File

@ -11,7 +11,7 @@ export const Request = () => {
uni.$uv.http.setConfig((config) => {
/* config 为默认全局配置*/
config.baseURL = 'http://192.168.1.5:4001'; /* 根域名 */
config.baseURL = 'https://pm.ccttiot.com/prod-api'; /* 根域名 */
// config.baseURL = 'https://pm.ccttiot.com/prod-api'; /* 根域名 */
return config
})