Files
砂糖 08029c6406 feat(oa): 新增项目进度和进度步骤跟踪的API接口
添加项目进度管理和进度步骤跟踪的相关API接口,包括列表查询、详情获取、新增、修改、删除等功能
2025-11-07 17:18:33 +08:00

333 lines
8.6 KiB
Vue
Raw Permalink 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="container">
<!-- 第一级tabNode横向滚动Tab -->
<view class="tab-container">
<scroll-view
class="tab-scroll"
scroll-x
show-scrollbar="false"
:scroll-with-animation="true"
>
<view class="tab-wrapper">
<view
v-for="(tab, index) in tabOption"
:key="index"
class="tab-item"
:class="{ 'tab-active': defaultTabNode === tab.value }"
@click="handleTabClick(tab.value)"
>
<text>{{ tab.label }}</text>
<!-- 选中状态显示清除图标 -->
<text
v-if="defaultTabNode === tab.value"
class="clear-icon"
@click.stop="clearTab"
>×</text>
</view>
</view>
</scroll-view>
</view>
<!-- 第二级firstLevelNode横向滚动Tab联动 -->
<view class="tab-container second-tab" v-if="filteredFirstLevelOptions.length > 0">
<scroll-view
class="tab-scroll"
scroll-x
show-scrollbar="false"
:scroll-with-animation="true"
:disabled="!defaultTabNode"
>
<view class="tab-wrapper">
<view
v-for="(item, index) in filteredFirstLevelOptions"
:key="index"
class="tab-item"
:class="{
'tab-active': defaultFirstLevelNode === item.value,
'tab-disabled': !defaultTabNode
}"
@click="handleFirstLevelClick(item.value)"
>
<text>{{ item.label }}</text>
<!-- 选中状态显示清除图标 -->
<text
v-if="defaultFirstLevelNode === item.value"
class="clear-icon"
@click.stop="clearFirstLevel"
>×</text>
</view>
</view>
</scroll-view>
</view>
<!-- 筛选后的列表 -->
<view class="list-container">
<view class="summary">{{ scheduleSummary }}</view>
<view v-if="loading" class="loading">加载中...</view>
<view v-else-if="filterList.length === 0" class="empty">暂无数据</view>
<view v-else class="list-item" v-for="(item, index) in filterList" :key="item.id || index">
<view class="item-name">
{{ item.stepName }}
<oa-remind-time :expire-date="item.planEnd" :threshold-days="5" :finished="item.status == 2"/>
</view>
<view class="item-info">
状态{{ item.status === 2 ? '已完成' : item.status === 1 ? '待验收' : '未开始' }}
</view>
<view class="item-info">
负责人{{ item.nodeHeader || '未指定' }}
</view>
</view>
</view>
</view>
</template>
<script>
import { listPage } from "@/api/oa/projectScheduleStep";
export default {
data() {
return {
projectScheduleStepList: [],
total: 0,
loading: false,
defaultTabNode: "", // 默认不选中
defaultFirstLevelNode: "", // 默认不选中
filterParams: {
onlyMy: false,
onlyUnfinished: false
},
queryParams: {
pageNum: 1,
pageSize: 999,
scheduleId: undefined,
},
};
},
computed: {
// 第一级Tab选项
tabOption() {
const seen = new Set();
const tabNodes = [];
for (const item of this.projectScheduleStepList) {
const currentTabNode = item.tabNode;
if (currentTabNode && !seen.has(currentTabNode)) {
seen.add(currentTabNode);
tabNodes.push(currentTabNode);
}
}
return tabNodes.map(item => ({ label: item, value: item }));
},
// 第二级原始选项带关联tabNode
firstLevelOption() {
const uniqueMap = {};
this.projectScheduleStepList.forEach(item => {
if (item.firstLevelNode && item.tabNode) {
const uniqueKey = `${item.firstLevelNode}-${item.tabNode}`;
uniqueMap[uniqueKey] = item;
}
});
return Object.values(uniqueMap).map(item => ({
label: item.firstLevelNode,
value: item.firstLevelNode,
tabNode: item.tabNode
}));
},
// 联动过滤后的第二级选项
filteredFirstLevelOptions() {
if (!this.defaultTabNode) return [];
return this.firstLevelOption.filter(option =>
option.tabNode === this.defaultTabNode
);
},
// 统计信息
scheduleSummary() {
const totalCount = this.projectScheduleStepList.length;
const completedCount = this.projectScheduleStepList.filter(item => item.status === 2).length;
const pendingCount = this.projectScheduleStepList.filter(item => item.status === 1).length;
return `已完成(${completedCount}+ 待验收(${pendingCount} / 总节点数(${totalCount}`;
},
// 筛选后的列表
filterList() {
// const { onlyMy, onlyUnfinished } = this.filterParams;
let filtered = this.projectScheduleStepList.filter(item => {
// 只有选中了才过滤,未选中则不过滤该条件
if (this.defaultTabNode && item.tabNode !== this.defaultTabNode) return false;
if (this.defaultFirstLevelNode && item.firstLevelNode !== this.defaultFirstLevelNode) return false;
return true;
});
// if (onlyMy) {
// filtered = filtered.filter(item => item.nodeHeader === this.$store.getters.nickName);
// }
// if (onlyUnfinished) {
// filtered = filtered.filter(item => item.status !== 2);
// }
console.log('筛选后的列表', filtered)
return filtered;
}
},
methods: {
getList() {
this.loading = true;
listPage(this.queryParams).then(response => {
this.projectScheduleStepList = response.rows || [];
this.total = response.total || 0;
// 移除自动选中逻辑,保持默认不选中
}).catch(() => {
this.projectScheduleStepList = [];
}).finally(() => {
this.loading = false;
});
},
// 第一级Tab点击
handleTabClick(value) {
// 如果点击的是已选中的Tab不做操作清除通过图标实现
if (this.defaultTabNode === value) return;
this.defaultTabNode = value;
this.defaultFirstLevelNode = ""; // 切换Tab时重置第二级
},
// 清除第一级选中状态
clearTab() {
this.defaultTabNode = "";
this.defaultFirstLevelNode = ""; // 同时清空第二级
},
// 第二级Tab点击
handleFirstLevelClick(value) {
if (!this.defaultTabNode) return;
// 如果点击的是已选中的Tab不做操作清除通过图标实现
if (this.defaultFirstLevelNode === value) return;
this.defaultFirstLevelNode = value;
},
// 清除第二级选中状态
clearFirstLevel() {
this.defaultFirstLevelNode = "";
}
},
onLoad(options) {
this.queryParams.scheduleId = options.id;
this.getList();
}
};
</script>
<style scoped>
.container {
padding: 16rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
/* 横向滚动Tab样式 */
.tab-container {
background-color: #fff;
padding: 8rpx 0;
margin-bottom: 16rpx;
border-radius: 8rpx;
}
.second-tab {
margin-bottom: 24rpx;
}
.tab-scroll {
width: 100%;
white-space: nowrap;
padding: 8rpx 16rpx;
}
.tab-wrapper {
display: inline-flex;
gap: 24rpx;
}
.tab-item {
padding: 12rpx 24rpx;
font-size: 28rpx;
border-radius: 30rpx;
background-color: #f0f0f0;
color: #333;
white-space: nowrap;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 8rpx; /* 文字和清除图标间距 */
}
.tab-item.tab-active {
background-color: #007aff;
color: #fff;
font-weight: 500;
}
.tab-item.tab-disabled {
color: #ccc;
background-color: #f9f9f9;
}
/* 清除图标样式 */
.clear-icon {
font-size: 24rpx;
width: 24rpx;
height: 24rpx;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: rgba(255,255,255,0.3);
cursor: pointer;
}
/* 列表样式 */
.list-container {
background-color: #fff;
border-radius: 8rpx;
padding: 16rpx;
}
.summary {
font-size: 28rpx;
color: #666;
padding: 16rpx 0;
border-bottom: 1px solid #eee;
margin-bottom: 16rpx;
}
.loading, .empty {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}
.list-item {
padding: 20rpx 0;
border-bottom: 1px solid #eee;
}
.list-item:last-child {
border-bottom: none;
}
.item-name {
font-size: 32rpx;
font-weight: 500;
margin-bottom: 12rpx;
color: #333;
}
.item-info {
font-size: 26rpx;
color: #666;
margin-bottom: 8rpx;
}
</style>