Files
SmartParks_uniapp/pages/sys/workbench/oa/oa.vue
liyuanchao c7ff9a5234
Some checks failed
Uniapp 自动化打包 CI/CD / 打包 Uniapp 项目 (push) Has been cancelled
1.
2025-09-05 16:54:53 +08:00

519 lines
12 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="oa-container">
<!-- 搜索框 -->
<view class="oa-search">
<view class="search-box">
<image src="/static/ic_search_gray.png" class="search-icon" />
<text class="search-placeholder">申批名称内容发起人编号</text>
</view>
</view>
<!-- tab栏 -->
<view class="oa-tabs">
<view v-for="(tab, idx) in tabs" :key="idx" :class="['oa-tab', {active: idx === activeTab}]"
@click="changeTab(idx)">
{{ tab }}
<view v-if="idx === activeTab" class="tab-underline"></view>
</view>
</view>
<!-- 列表区 -->
<view class="oa-list-container">
<scroll-view
v-for="(tab, idx) in tabs"
:key="idx"
v-show="idx === activeTab"
class="oa-list-scroll"
scroll-y
:scroll-top="scrollTop[idx]"
:refresher-enabled="true"
:refresher-triggered="refreshing[idx]"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
@scroll="onScroll($event, idx)">
<view v-for="(item, index) in tabData[idx]" :key="index" class="oa-card" @click="goToDetail(item)">
<view class="card-row">
<view class="card-type">{{ item.type }}</view>
<view class="card-status-tag" :class="item.statusClass">{{ item.status }}</view>
</view>
<view class="card-info">
<view class="card-leave-type">请假类型<text class="card-leave-type">{{ item.leaveType }}</text></view>
<view class="card-leave-type">
<text>开始时间{{ item.startTime }}</text>
</view>
<view class="card-leave-type">
<text>结束时间{{ item.endTime }}</text>
</view>
</view>
<view class="card-footer">
<view class="card-user">
<view class="user-avatar" :style="{backgroundColor: item.avatarColor}">{{ item.avatarText }}</view>
<text class="user-name">{{ item.userName }}</text>
</view>
<text class="card-datetime">{{ item.dateTime }}</text>
</view>
</view>
<!-- 加载更多提示 -->
<view class="loading-more">
<view v-if="loading && !refreshing[idx]" class="loading-text">加载中...</view>
<view v-else-if="!hasMore[idx] && tabData[idx].length > 0" class="loading-text">没有更多数据了</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
tabs: ['待办', '已办', '已发起'],
activeTab: 2, // 默认选中已发起
tabData: [
[], // 待办
[], // 已办
[] // 已发起
],
tabLoaded: [false, false, false], // 每个tab是否已加载
loading: false,
pageNum: [1, 1, 1], // 每个tab的页码
pageSize: 10, // 每页条数
hasMore: [true, true, true], // 每个tab是否还有更多数据
refreshing: [false, false, false], // 每个tab是否正在刷新
scrollTop: [0, 0, 0] // 每个tab的滚动位置
}
},
computed: {
list() {
return this.tabData[this.activeTab];
}
},
created() {
this.loadAllTabsData(); // 预加载所有标签页数据
},
methods: {
goBack() {
uni.navigateBack();
},
// 跳转到详情页
goToDetail(item) {
uni.navigateTo({
url: './oaDetail'
});
},
async changeTab(idx) {
this.activeTab = idx;
// 移除切换标签时的加载逻辑,避免重复调用接口
/*
if (!this.tabLoaded[idx]) {
await this.loadTabData(idx);
}
*/
},
// 监听滚动事件记录每个tab的滚动位置
onScroll(e, idx) {
this.scrollTop[idx] = e.detail.scrollTop;
},
// 下拉刷新
async onRefresh() {
// 设置当前tab为刷新状态
this.$set(this.refreshing, this.activeTab, true);
// 重置页码
this.$set(this.pageNum, this.activeTab, 1);
// 重新加载数据
await this.loadTabData(this.activeTab);
// 取消刷新状态
this.$set(this.refreshing, this.activeTab, false);
},
// 上拉加载更多
async onLoadMore() {
// 如果没有更多数据或正在加载,则不处理
if (!this.hasMore[this.activeTab] || this.loading) {
return;
}
// 页码加1
this.$set(this.pageNum, this.activeTab, this.pageNum[this.activeTab] + 1);
// 加载更多数据
await this.loadTabData(this.activeTab);
},
async loadTabData(idx) {
this.loading = true;
// 模拟接口请求不同tab返回不同mock数据
let data = [];
if (idx === 0) { // 待办
data = [
{
type: '请假',
leaveType: '病假',
status: '待办',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
}
];
} else if (idx === 1) { // 已办
data = [
{
type: '请假',
leaveType: '病假',
status: '已通过',
statusClass: 'green',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
}
];
} else { // 已发起
data = [
{
type: '请假',
leaveType: '病假',
status: '已通过',
statusClass: 'green',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
}
];
}
// 模拟分页效果
const start = (this.pageNum[idx] - 1) * this.pageSize;
const end = start + this.pageSize;
const pageData = data.slice(start, end);
// 模拟网络延迟
await new Promise(res => setTimeout(res, 300));
if (this.pageNum[idx] === 1) {
// 刷新操作,替换数据
this.$set(this.tabData, idx, pageData);
} else {
// 加载更多,追加数据
this.tabData[idx] = [...this.tabData[idx], ...pageData];
}
// 判断是否还有更多数据
this.$set(this.hasMore, idx, end < data.length);
this.$set(this.tabLoaded, idx, true);
this.loading = false;
},
// 添加预加载所有标签页数据的方法
async loadAllTabsData() {
// 并行加载所有标签页数据,提高加载速度
const loadPromises = [0, 1, 2].map((index) => {
return this.loadTabData(index);
});
await Promise.all(loadPromises);
// 标记所有标签页已加载
this.tabLoaded = [true, true, true];
}
}
}
</script>
<style scoped>
.oa-container {
height: 100vh;
background: #f7f7f7;
display: flex;
flex-direction: column;
overflow: hidden;
}
.oa-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.oa-title {
font-size: 36rpx;
color: #000;
}
.oa-search {
background: #fff;
padding: 20rpx 30rpx;
flex-shrink: 0;
}
.search-box {
height: 70rpx;
background: #F7F7F7;
border-radius: 35rpx;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 30rpx;
}
.search-icon {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
}
.search-placeholder {
color: #9A9A9A;
font-size: 28rpx;
}
.oa-tabs {
display: flex;
align-items: center;
justify-content: space-around;
background: #fff;
height: 80rpx;
border-bottom: 1px solid #f0f0f0;
flex-shrink: 0;
}
.oa-tab {
flex: 1;
text-align: center;
font-size: 32rpx;
color: #737373;
position: relative;
padding: 0 0 10rpx 0;
cursor: pointer;
}
.oa-tab.active {
color: #007CFF;
font-weight: bold;
}
.tab-underline {
width: 60rpx;
height: 6rpx;
background: #2186FF;
border-radius: 3rpx;
margin: 0 auto;
margin-top: 8rpx;
}
.oa-list-container {
flex: 1;
position: relative;
margin-top: 20rpx;
}
.oa-list-scroll {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 0 35rpx;
overflow-y: auto;
box-sizing: border-box;
}
.oa-list {
margin: 25rpx 0 0 0;
padding: 0 35rpx;
flex: 1;
overflow-y: auto;
}
.oa-card {
background: #fff;
border-radius: 10rpx;
margin-bottom: 26rpx;
padding: 20rpx 20rpx 20rpx 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.card-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 46rpx;
}
.card-type {
width: 126rpx;
height: 46rpx;
font-weight: 600;
font-size: 32rpx;
padding-left: 10rpx;
padding-top: 7rpx;
border-radius: 15rpx;
color: #fff;
background: linear-gradient(90deg, #007CFF 0%, #FFFFFF 100%);
}
.card-status-tag {
font-size: 28rpx;
}
.card-status-tag.orange {
color: #F27A0F;
}
.card-status-tag.green {
color: #0AC88F;
}
.card-status-tag.gray {
color: #8F8F8F;
background-color: rgba(143, 143, 143, 0.1);
border: 1px solid #8F8F8F;
}
.card-info {
font-size: 26rpx;
color: #333;
margin-bottom: 42rpx;
}
.card-leave-type {
color: #626262;
font-size: 28;
margin-bottom: 24rpx;
}
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-user {
display: flex;
align-items: center;
}
.user-avatar {
width: 54rpx;
height: 54rpx;
border-radius: 27rpx;
background-color: #688CFF;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
margin-right: 18rpx;
}
.user-name {
font-size: 24rpx;
color: #626262;
}
.card-datetime {
font-size: 24rpx;
color: #626262;
}
.loading-more {
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx 0;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
</style>