Files
im-uniapp/pages/workbench/reportWork/reportWork.vue
2025-07-11 18:05:05 +08:00

630 lines
14 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="container">
<!-- Tab切换 -->
<view class="tab-container">
<view
class="tab-item"
:class="{ active: activeTab === 'all' }"
@click="switchTab('all')"
>
<text>全部报工</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'my' }"
@click="switchTab('my')"
>
<text>我的报工</text>
</view>
</view>
<!-- 卡片列表 -->
<view class="card-list-container">
<scroll-view
scroll-y
class="card-scroll"
@scrolltolower="loadMore"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
:style="{ height: scrollHeight + 'px' }"
>
<view class="card-list">
<ReportCard
v-for="(item, index) in projectReportList"
:key="item.reportId"
:item="item"
:show-avatar="activeTab === 'all'"
@card-click="handleCardClick"
></ReportCard>
<!-- 空状态 -->
<view v-if="!loading && projectReportList.length === 0" class="empty-state">
<image src="/static/images/empty_lable.png" class="empty-image"></image>
<text class="empty-text">暂无报工数据</text>
</view>
</view>
<!-- 加载更多 -->
<u-loadmore
v-if="total > 0"
:status="loadMoreStatus"
@loadmore="loadMore"
></u-loadmore>
</scroll-view>
</view>
<!-- 圆形新增按钮 -->
<view class="fab-button" @click="handleAdd">
<u-icon name="plus" color="#fff" size="30"></u-icon>
</view>
<!-- 新增弹窗 -->
<uni-popup ref="popup" type="bottom" :mask-click="false">
<view class="form-popup-content">
<view class="popup-header">
<text class="popup-title">{{ title }}</text>
<u-icon name="close" @click="cancel" size="40"></u-icon>
</view>
<scroll-view scroll-y class="form-content">
<view class="form-container">
<view class="form-item">
<view class="form-label">工作地点</view>
<u-input v-model="form.workPlace" placeholder="请输入工作地点"></u-input>
</view>
<view class="form-item">
<view class="form-label">是否出差</view>
<u-radio-group v-model="form.isTrip">
<u-radio :name="1" label="是"></u-radio>
<u-radio :name="0" label="否"></u-radio>
</u-radio-group>
</view>
<view v-if="form.isTrip === 1" class="form-item">
<view class="form-label">国内/国外</view>
<u-radio-group v-model="form.workType">
<u-radio :name="0" label="国内"></u-radio>
<u-radio :name="1" label="国外"></u-radio>
</u-radio-group>
</view>
<view class="form-item">
<view class="form-label">请选择项目</view>
<uni-data-select
v-model="form.projectId"
:localdata="projectList"
placeholder="请选择项目"
@change="handleProjectChange"
></uni-data-select>
</view>
<view class="form-item">
<view class="form-label">报工内容</view>
<u-textarea
v-model="form.content"
placeholder="请输入报工内容"
:height="200"
></u-textarea>
</view>
<view class="form-item">
<view class="form-label">备注</view>
<u-input v-model="form.remark" placeholder="请输入备注"></u-input>
</view>
</view>
</scroll-view>
<view class="popup-footer">
<u-button type="primary" @click="submitForm" :loading="buttonLoading">确定</u-button>
<u-button @click="cancel">取消</u-button>
</view>
</view>
</uni-popup>
<!-- 报工详情弹窗 -->
<ReportDetail
ref="reportDetail"
:detail="reportDetail"
></ReportDetail>
</view>
</template>
<script>
import { listProjectReport, addProjectReport, getProjectReport } from '@/api/oa/projectReport'
import { listProject } from '@/api/oa/project'
import { listDept } from '@/api/oa/dept'
import ReportCard from '@/components/ReportCard/index.vue'
import ReportDetail from '@/components/ReportDetail/index.vue'
import { mapState } from 'vuex'
export default {
components: {
ReportCard,
ReportDetail,
},
computed: {
...mapState('user', ['selfInfo'])
},
data() {
return {
// 当前激活的tab
activeTab: 'all',
// 滚动区域高度
scrollHeight: 0,
// 按钮loading
buttonLoading: false,
// 遮罩层
loading: true,
// 总条数
total: 0,
// 项目报工列表数据
projectReportList: [],
// 弹出层标题
title: "",
// 项目列表
projectList: [],
// 部门列表
deptList: [],
// 表单参数
form: {},
// 加载更多状态
loadMoreStatus: 'loadmore',
// 下拉刷新状态
refreshing: false,
// 报工详情数据
reportDetail: {},
// 分页参数
queryParams: {
pageNum: 1,
pageSize: 10
},
// 表单校验规则
rules: {
workPlace: [
{ required: true, message: "工作地点不能为空", trigger: "blur" }
],
projectId: [
{ required: true, message: "项目不能为空", trigger: "blur" }
],
content: [
{ required: true, message: "报工内容不能为空", trigger: "blur" }
],
isTrip: [
{ required: true, message: '请选择是否出差', trigger: 'change' }
],
workType: [
{
required: true,
message: '请选择出差地点',
trigger: 'change',
validator: (rule, value, callback) => {
if (this.form.isTrip === 1 && !value && value !== 0) {
callback(new Error('请选择出差地点'));
} else {
callback();
}
}
}
]
}
}
},
onLoad() {
this.calculateScrollHeight();
this.getList();
},
onReady() {
// 页面渲染完成后重新计算高度
this.calculateScrollHeight();
},
methods: {
// 计算滚动区域高度
calculateScrollHeight() {
const systemInfo = uni.getSystemInfoSync();
const tabHeight = 100; // tab高度
const containerPadding = 40; // 容器padding
// 悬浮按钮是固定定位,不需要预留空间,让卡片列表到达底部
// 我也不知道为什么要 + 80, 不然滚动高度下面一大片空白,这个不太好调试
this.scrollHeight = systemInfo.windowHeight - tabHeight - containerPadding + 80;
},
// 切换tab
switchTab(tab) {
if (this.activeTab === tab) return;
this.activeTab = tab;
this.queryParams.pageNum = 1;
this.projectReportList = [];
this.getList();
},
// 下拉刷新
onRefresh() {
this.refreshing = true;
this.queryParams.pageNum = 1;
this.getList().finally(() => {
this.refreshing = false;
});
},
// 获取项目列表
getProjectList() {
listProject({ pageNum: 1, pageSize: 9999 }).then(res => {
const rawData = res.rows || [];
// 按照 uni-data-select 的标准格式处理数据
this.projectList = rawData.map(item => ({
value: item.projectId,
text: item.projectName || '未命名项目',
// 保留原始数据用于提交
projectId: item.projectId,
projectName: item.projectName || '未命名项目',
projectNum: item.projectNum,
projectCode: item.projectCode
}));
console.log('处理后的项目列表数据:', this.projectList);
// 检查数据结构
if (this.projectList.length > 0) {
console.log('第一个项目数据:', this.projectList[0]);
}
}).catch(err => {
console.error('获取项目列表失败:', err);
uni.showToast({
title: '获取项目列表失败',
icon: 'none'
});
});
},
// 获取部门列表
getDeptList() {
listDept().then(res => {
this.deptList = res.data || [];
}).catch(err => {
console.error('获取部门列表失败:', err);
});
},
// 查询项目报工列表
getList() {
return new Promise((resolve, reject) => {
this.loading = true;
// 根据当前tab设置查询参数
const params = {
...this.queryParams
};
// 如果是我的报工添加用户ID过滤
if (this.activeTab === 'my') {
// 使用当前登录用户的ID
const oaId = uni.getStorageSync('oaId');
if (oaId) {
params.userId = oaId;
}
}
listProjectReport(params).then(response => {
if (this.queryParams.pageNum === 1) {
this.projectReportList = response.rows || [];
} else {
this.projectReportList = [...this.projectReportList, ...(response.rows || [])];
}
this.total = response.total || 0;
this.loading = false;
// 更新加载更多状态
if (this.queryParams.pageNum > 1) {
this.loadMoreStatus = 'loadmore';
}
this.getProjectList();
// this.getDeptList();
resolve(response);
}).catch(err => {
console.error('获取报工列表失败:', err);
this.loading = false;
this.loadMoreStatus = 'loadmore';
uni.showToast({
title: '获取数据失败',
icon: 'none'
});
reject(err);
});
});
},
// 新增
handleAdd() {
this.reset();
this.title = "添加项目报工";
this.$refs.popup.open();
},
// 卡片点击事件
handleCardClick(item) {
console.log('点击了报工卡片:', item);
// 获取报工详情
this.getReportDetail(item.reportId);
},
// 获取报工详情
getReportDetail(reportId) {
// 先打开弹窗显示加载状态
this.$refs.reportDetail.open();
getProjectReport(reportId).then(response => {
// 根据API返回的数据结构处理
this.reportDetail = response.data || response || {};
}).catch(err => {
console.error('获取报工详情失败:', err);
uni.showToast({
title: '获取详情失败',
icon: 'none'
});
// 关闭弹窗
this.$refs.reportDetail.close();
});
},
// 提交表单
submitForm() {
// 手动验证表单
if (!this.form.workPlace) {
uni.showToast({
title: '工作地点不能为空',
icon: 'none'
});
return;
}
if (this.form.isTrip === undefined) {
uni.showToast({
title: '请选择是否出差',
icon: 'none'
});
return;
}
if (this.form.isTrip === 1 && this.form.workType === undefined) {
uni.showToast({
title: '请选择出差地点',
icon: 'none'
});
return;
}
if (!this.form.projectId) {
uni.showToast({
title: '项目不能为空',
icon: 'none'
});
return;
}
if (!this.form.content) {
uni.showToast({
title: '报工内容不能为空',
icon: 'none'
});
return;
}
// 验证通过,提交表单
this.buttonLoading = true;
// 获取选中的项目信息
const selectedProject = this.projectList.find(p => p.value === this.form.projectId);
const submitData = {
...this.form,
projectName: selectedProject?.projectName || '',
projectNum: selectedProject?.projectNum || '',
projectCode: selectedProject?.projectCode || null
};
addProjectReport(submitData).then(response => {
this.buttonLoading = false;
uni.showToast({
title: '新增成功',
icon: 'success'
});
this.$refs.popup.close();
this.getList(); // 重新获取列表
}).catch(err => {
this.buttonLoading = false;
console.error('新增失败:', err);
uni.showToast({
title: '新增失败',
icon: 'none'
});
});
},
// 取消
cancel() {
this.$refs.popup.close();
this.reset();
},
// 重置表单
reset() {
this.form = {
workPlace: '',
projectId: null,
content: '',
remark: '',
isTrip: undefined,
workType: undefined
};
},
// 加载更多
loadMore() {
if (this.projectReportList.length >= this.total) {
this.loadMoreStatus = 'nomore';
return;
}
this.queryParams.pageNum++;
this.loadMoreStatus = 'loading';
this.getList();
},
// 项目选择变化处理
handleProjectChange(value) {
console.log('项目选择变化:', value);
this.form.projectId = value;
// 获取选中的项目信息
const selectedProject = this.projectList.find(p => p.value === value);
if (selectedProject) {
console.log('选中的项目:', selectedProject);
}
}
}
}
</script>
<style lang="scss" scoped>
.container {
background-color: #f5f5f5;
height: 100vh;
display: flex;
flex-direction: column;
}
.tab-container {
display: flex;
background-color: #fff;
padding: 0 20rpx;
border-bottom: 1rpx solid #e9ecef;
.tab-item {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #666;
position: relative;
&.active {
color: #007bff;
font-weight: 500;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background-color: #007bff;
border-radius: 2rpx;
}
}
}
}
.card-list-container {
flex: 1;
padding: 20rpx;
overflow: hidden;
}
.card-scroll {
width: 100%;
height: 100%;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.empty-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
opacity: 0.6;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
}
.form-popup-content {
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
overflow: hidden;
width: 100%;
max-height: 80vh;
display: flex;
flex-direction: column;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #e9ecef;
.popup-title {
font-size: 32rpx;
font-weight: bold;
}
}
.form-content {
padding: 30rpx;
flex: 1;
overflow-y: auto;
}
.form-container {
.form-item {
margin-bottom: 40rpx;
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
font-weight: 500;
}
}
}
.popup-footer {
display: flex;
justify-content: center;
gap: 20rpx;
padding: 30rpx;
border-top: 1rpx solid #e9ecef;
}
}
.fab-button {
position: fixed;
bottom: 20rpx;
right: 20rpx;
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: #007bff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
padding: 20rpx;
z-index: 999;
}
</style>