<template>
<div class="side-project">
<div
v-for="item in projectType"
:key="item.key" @click="changeProjectType(item.key)"
:class="{ 'project-type': true, 'active': assignRole === item.key }"
>
{{ item.name }}
</div>
<div class="project-list-wrap" v-permission="['task:manage:list:current:aggregation']">
<div class="project-filter">
<el-popover width="410" trigger="click" :visible="filterVisible">
<template #reference>
<div class="filter-title" @click="filterVisible = !filterVisible">
项目
<i class="iconfont srt-icon-filter"/>
</div>
</template>
<div class="filter-content">
<div class="filter-item">
<div>项目时间</div>
<el-date-picker
v-model="filterParams.projectTime"
type="daterange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 364px"
:teleported="false"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</div>
<div class="filter-item">
<div>项目状态</div>
<div class="stage-list">
<div
v-for="item in projectStageList"
:key="item.value"
@click="changeProjectStage(item.value)"
:class="{ 'stage-item': true, 'active': filterParams.projectStage.includes(item.value) }"
>
{{ item.label }}
</div>
</div>
</div>
<p @click="saveFilterStatus" class="save-filter">保存筛选设置</p>
</div>
</el-popover>
<span class="tip">我的任务进度</span>
</div>
<!-- 添加滚动容器,并绑定滚动事件 -->
<div class="project-list-container" ref="projectListContainer" @scroll="handleScroll">
<div class="project-list" v-if="projectList.length">
<div
v-for="item in projectList"
:key="item.projectId"
@click="changeActiveProject(item.projectId)"
:class="{ 'project-item': true, 'active': projectIdList.includes(item.projectId) }"
>
<div class="project-item-top">
<span>{{ item.projectName }}</span>
<span>{{ item.finishedTaskCount }}/{{ item.totalTaskCount }}</span>
</div>
<div class="project-item-content text-overflow">
{{ item.projectStatus || '' }}
</div>
</div>
<!-- 加载提示 -->
<div v-if="loading" class="loading-tip">加载中...</div>
<div v-if="!hasMore && projectList.length > 0" class="no-more-tip">没有更多数据了</div>
</div>
<!-- 空状态 -->
<div v-else-if="!loading" class="empty-tip">暂无数据~</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted, nextTick } from 'vue';
import { useUserStore } from '@/store/user';
const userStore = useUserStore();
const emit = defineEmits(['refresh-list']);
const permissionKeys = computed(() => {
return userStore.userInfo?.permissionKeys || []
})
// 负责/参与
const assignRole = ref(1);
// 项目列表选中项
const projectIdList = ref([]);
const projectType = computed(() => {
return [
permissionKeys.value.includes('task:manage:list:current:responsibility') ? {
key: 1,
name: '我负责的'
} : '',
permissionKeys.value.includes('task:manage:list:current:join') ? {
key: 2,
name: '我参与的'
} : ''
].filter(i => !!i)
});
const projectStageList = ref([
{ label: '全部', value: '' },
{ label: '立项中', value: '3' },
{ label: '待执行', value: '0' },
{ label: '执行中', value: '1' },
{ label: '已结束', value: '2' },
{ label: '已归档', value: '4' },
])
const filterParams = ref({
projectTime: [],
projectStage: ['0', '1', '2'],
})
const projectList = ref([]);
const filterVisible = ref(false);
const projectListContainer = ref(null);
const loading = ref(false); // 加载状态
const hasMore = ref(true); // 是否有更多数据
const currentPage = ref(1); // 当前页码
const pageSize = ref(1); // 每页条数,从10改为1
// 项目类型变更
const changeProjectType = (type) => {
assignRole.value = type;
}
// 项目状态筛选
const changeProjectStage = (stage) => {
filterParams.value.projectStage =
filterParams.value.projectStage.includes(stage)
? filterParams.value.projectStage.filter(item => item !== stage)
: [...filterParams.value.projectStage, stage];
}
// 项目选中
const changeActiveProject = (id) => {
projectIdList.value = projectIdList.value.includes(id)
? projectIdList.value.filter(item => item !== id)
: [...projectIdList.value, id];
}
// 保存筛选条件
const saveFilterStatus = () => {
filterVisible.value = false;
projectIdList.value = [];
resetScrollLoad(); // 重置滑动加载状态
}
// 重置滑动加载状态
const resetScrollLoad = () => {
projectList.value = [];
currentPage.value = 1;
hasMore.value = true;
nextTick(() => {
if (projectListContainer.value) {
projectListContainer.value.scrollTop = 0;
}
getMyTaskProjects(); // 重新加载第一页数据
});
}
// 滚动事件处理 - 优化触发条件
const handleScroll = () => {
if (!projectListContainer.value || loading.value || !hasMore.value) {
return;
}
const container = projectListContainer.value;
const { scrollTop, scrollHeight, clientHeight } = container;
// 优化触发条件:当滚动到底部20px内时,加载更多
if (scrollHeight - scrollTop - clientHeight < 20) {
loadMore();
}
};
// 加载更多数据
const loadMore = () => {
if (!hasMore.value || loading.value) return;
currentPage.value++;
getMyTaskProjects(true); // true表示加载更多
};
function getMyTaskProjects(isLoadMore = false) {
if (loading.value) return;
loading.value = true;
G_transmit('getMyTaskProjects', {
queries: {
// assignRole: assignRole.value,
pageIndex: currentPage.value,
pageSize: pageSize.value,
startTime: filterParams.value?.projectTime?.[0] || '',
endTime: filterParams.value?.projectTime?.[1] || '',
projectStatusList: filterParams.value?.projectStage?.join(',') || '',
}
}).then((res) => {
const newData = res.data.rows || [];
if (isLoadMore) {
projectList.value = [...projectList.value, ...newData];
} else {
projectList.value = newData;
}
hasMore.value = projectList.value.length < res.data.total;
}).catch((err) => {
console.log(err);
// 加载失败时回滚页码
if (isLoadMore) {
currentPage.value--;
}
}).finally(() => {
loading.value = false;
});
}
// 监听项目类型变化
watch(() => projectType.value, () => {
assignRole.value = projectType.value[0]?.key || 1;
}, { immediate: true, deep: true })
// 监听筛选条件变化
watch(() => [assignRole.value, projectIdList.value], () => {
emit('refresh-list', assignRole.value, projectIdList.value);
},{immediate: true, deep: true})
onMounted(() => {
getMyTaskProjects();
})
</script>
<style lang="scss" scoped>
.side-project {
width: 280px;
padding: 16px 0;
background-color: #ffffff;
border-radius: 6px;
}
.project-type {
height: 28px;
line-height: 28px;
padding: 10px;
color: rgba(0, 0, 0, 0.88);
font-weight: 600;
cursor: pointer;
&:hover, &.active {
background-color: rgba(22, 119, 255, 0.06);
color: #1677ff;
}
}
.project-list-wrap {
padding: 10px;
}
.project-filter {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid rgba(0, 0, 0, 0.06);
padding-top: 16px;
.filter-title {
font-size: 14px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
& i {
cursor: pointer;
}
}
.tip {
font-size: 12px;
font-weight: 400;
color: rgba(0, 0, 0, 0.45);
}
}
.filter-content {
.filter-item {
margin-bottom: 16px;
&>div:first-child {
margin-bottom: 8px;
}
}
.stage-list {
display: flex;
align-items: center;
border: 1px solid rgba(0, 0, 0, 0.06);
padding: 4px;
.stage-item {
height: 24px;
line-height: 24px;
padding: 0 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: rgba(0, 0, 0, 0.88);
margin-right: 8px;
cursor: pointer;
&:hover, &.active {
background-color: rgba(22, 119, 255, 0.06);
color: #1677ff;
}
}
}
.save-filter {
font-size: 12px;
color: #007CFE;
cursor: pointer;
text-align: right;
}
}
.project-list-container {
max-height: 49px;
overflow-y: auto;
margin-top: 10px;
}
.project-list {
.project-item {
padding: 10px;
margin-top: 10px;
background-color: #f5f7fa;
border-radius: 6px;
color: rgba(0, 0, 0, 0.88);
cursor: pointer;
height: 50px;
box-sizing: border-box;
.project-item-top {
display: flex;
justify-content: space-between;
align-items: center;
}
.project-item-content {
margin-top: 5px;
}
&:hover, &.active {
background-color: rgba(22, 119, 255, 0.06);
color: #1677ff;
}
.text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
// 加载提示样式
.loading-tip, .no-more-tip, .empty-tip {
margin-top: 4px;
text-align: center;
padding: 20px;
color: #999;
font-size: 12px;
}
// 第一条项目没有上边距
.project-list .project-item:first-child {
margin-top: 0;
}
</style>帮我整改一下,实现滚动分页加载projectList的数据,我先用的一条来测试