Files
im-uniapp/pages/workbench/reportSchedule/reportSchedule.vue
2025-07-16 14:23:42 +08:00

762 lines
19 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="report-schedule">
<!-- header始终显示 -->
<view class="search-bar">
<view class="search-container">
<view class="task-type-button-container">
<view class="task-type-button" @click="openDrawer">
<uni-icons type="settings" color="#2979ff" size="22"></uni-icons>
</view>
<!-- 新增视图切换按钮 -->
<view class="task-type-button" @click="toggleGanttView">
<uni-icons :type="showGanttView ? 'list' : 'calendar'" color="#2979ff" size="22"></uni-icons>
</view>
</view>
<view class="search-input custom-search-input">
<input v-model="searchName" class="input" type="text" placeholder="搜索排产名称" @confirm="onSearch"
@keyup.enter="onSearch" />
<view class="search-icon" @click="onSearch">
<uni-icons type="search" color="#bbb" size="20"></uni-icons>
</view>
<view v-if="searchName" class="clear-icon" @click="onClearSearch">
<uni-icons type="closeempty" color="#bbb" size="18"></uni-icons>
</view>
</view>
<view class="add-button" @click="handleAdd">
<uni-icons type="plusempty" color="#2979ff" size="22"></uni-icons>
</view>
</view>
</view>
<!-- 筛选抽屉 -->
<uni-drawer ref="drawerRef" mode="left" :width="320">
<view class="drawer-content">
<view class="drawer-title">筛选</view>
<view class="drawer-form">
<view class="drawer-form-item">
<text class="drawer-label">项目</text>
<uni-data-select v-model="filterProject" :localdata="projectOptions" placeholder="请选择项目" :clear="true"
filterable />
</view>
<view class="drawer-form-item">
<text class="drawer-label">执行人</text>
<uni-data-select v-model="filterHeader" :localdata="headerOptions" placeholder="请选择执行人" :clear="true"
filterable />
</view>
<view class="drawer-form-item">
<text class="drawer-label">日期</text>
<uni-datetime-picker v-model="filterDateRange" type="daterange" rangeSeparator="至"
start-placeholder="开始日期" end-placeholder="结束日期" :clear-icon="true" style="width:100%" />
</view>
</view>
<view class="drawer-btns">
<button class="drawer-btn-primary" @click="applyFilterAndClose">确定</button>
<button class="drawer-btn" @click="resetFilterAndClose">重置</button>
<button class="drawer-btn" @click="closeDrawer">关闭</button>
</view>
</view>
</uni-drawer>
<!-- 内容区根据showGanttView切换 -->
<gantt v-if="showGanttView" :chart-data="ganttChartData" />
<view v-else>
<!-- 自定义列表右滑菜单uni-ui实现 -->
<scroll-view scroll-y style="height: 100vh;" @scrolltolower="loadMore">
<view v-if="reportScheduleList.length">
<uni-swipe-action>
<block v-for="(item, index) in reportScheduleList" :key="item.scheduleId">
<uni-swipe-action-item :right-options="getSwipeOptions(item)" @click="swipeActionClick($event, item)"
style="margin-bottom: 16rpx;">
<view class="card">
<view class="card-title">
<text class="project">{{ item.scheduleName }}</text>
<text class="status" :class="'status-' + item.status">
{{ getStatusText(item.status) }}
</text>
</view>
<view class="card-content">
<view>项目名称{{ getProjectName(item.projectId) }}</view>
<view>负责人{{ getHeaderName(item.header) }}</view>
<view>开始日期{{ item.startDate }}</view>
<view>结束日期{{ item.endDate }}</view>
<view>备注{{ item.remark }}</view>
</view>
</view>
</uni-swipe-action-item>
</block>
</uni-swipe-action>
</view>
<view v-else class="empty">暂无数据</view>
<view class="load-more-tips">
<u-loading-icon v-if="loadingMore" text="加载中..." size="20" textSize="14" />
<text v-else-if="!hasMore && reportScheduleList.length">没有更多了</text>
</view>
</scroll-view>
<!-- 新增/编辑弹窗 -->
<uni-popup ref="popupRef" type="bottom">
<view class="popup-content">
<view class="uni-form">
<view class="uni-form-item">
<text class="uni-form-label">项目</text>
<uni-data-select v-model="form.projectId" :localdata="projectOptions" placeholder="请选择项目" filterable
:clear="true" />
</view>
<view class="uni-form-item">
<text class="uni-form-label">排产名称</text>
<u-input v-model="form.scheduleName" placeholder="请输入排产名称" />
</view>
<view class="uni-form-item">
<text class="uni-form-label">开始日期</text>
<uni-datetime-picker v-model="form.startDate" type="datetime" placeholder="请选择开始日期" style="width:100%" />
</view>
<view class="uni-form-item">
<text class="uni-form-label">结束日期</text>
<uni-datetime-picker v-model="form.endDate" type="datetime" placeholder="请选择结束日期" style="width:100%" />
</view>
<view class="uni-form-item">
<text class="uni-form-label">负责人</text>
<uni-data-select v-model="form.header" :localdata="headerOptions" placeholder="请选择负责人" filterable
:clear="true" />
</view>
<view class="uni-form-item">
<text class="uni-form-label">备注</text>
<u-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</view>
</view>
<view class="popup-btns">
<u-button type="primary" @click="submitForm">确定</u-button>
<u-button @click="closePopup">取消</u-button>
</view>
</view>
</uni-popup>
<!-- 开始弹窗 -->
<uni-popup ref="startPopupRef" type="bottom">
<view class="popup-content">
<view class="uni-form">
<view class="uni-form-item">
<text class="uni-form-label">汇报标题</text>
<u-input v-model="startForm.reportTitle" placeholder="请输入汇报标题" />
</view>
<view class="uni-form-item">
<text class="uni-form-label">汇报日期</text>
<uni-datetime-picker v-model="startForm.reportDate" type="datetime" placeholder="请选择汇报日期"
style="width:100%" />
</view>
<view class="uni-form-item">
<text class="uni-form-label">汇报人</text>
<uni-data-select v-model="startForm.reporter" :localdata="headerOptions" placeholder="请选择汇报人" filterable
:clear="true" />
</view>
<view class="uni-form-item">
<text class="uni-form-label">涉及项目</text>
<uni-data-select v-model="startForm.projectId" :localdata="projectOptions" placeholder="请选择项目" filterable
:clear="true" />
</view>
<view class="uni-form-item">
<text class="uni-form-label">备注</text>
<u-input v-model="startForm.remark" type="textarea" placeholder="请输入内容" />
</view>
</view>
<view class="popup-btns">
<u-button type="primary" :loading="startLoading" @click="submitStartForm">确定</u-button>
<u-button @click="closeStartPopup">取消</u-button>
</view>
</view>
</uni-popup>
</view>
</view>
</template>
<script>
import uniSwipeAction from '@/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue'
import uniSwipeActionItem from '@/uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue'
import uniDrawer from '@/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue'
import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue'
import uniDataSelect from '@/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue'
import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue'
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
import gantt from './gantt.vue'
import { listProject } from '@/api/oa/project'
import { addReportSchedule, delReportSchedule, getReportSchedule, listReportSchedule, updateReportSchedule } from '@/api/oa/reportSchedule'
import { listUser } from '@/api/oa/user'
import { addReportSummary } from '@/api/oa/reportSummary'
export default {
components: {
gantt,
uniSwipeAction,
uniSwipeActionItem,
uniDrawer,
uniIcons,
uniDataSelect,
uniDatetimePicker,
uniPopup
},
data() {
return {
queryParams: {
pageNum: 1,
pageSize: 10,
projectId: '',
scheduleName: '',
header: '',
dateRange: []
},
projectOptions: [],
headerOptions: [],
reportScheduleList: [],
total: 0,
single: true,
multiple: true,
form: {},
viewType: 'table',
showStartDate: false,
showEndDate: false,
swipeOptions: [
{
text: '编辑',
style: {
backgroundColor: '#2979ff',
color: '#fff'
}
},
{
text: '删除',
style: {
backgroundColor: '#fa3534',
color: '#fff'
}
}
],
// 新增:筛选相关
searchName: '',
filterProject: '',
filterHeader: '',
filterDateRange: [],
pageNum: 1,
pageSize: 10,
loadingMore: false,
hasMore: true,
startForm: {
reportTitle: '',
reportDate: '',
reporter: '',
projectId: '',
remark: '',
type: 1
},
startLoading: false,
startPopupOpen: false,
_startScheduleId: null,
showGanttView: false,
ganttChartData: {
categories: [],
series: []
}
}
},
onLoad() {
this.pageNum = 1
this.hasMore = true
this.getProjectOptions()
this.getHeaderOptions()
this.handleQuery()
},
watch: {
showGanttView(val) {
if (val) {
this.ganttChartData = this.getGanttChartData(this.reportScheduleList)
}
}
},
methods: {
openDrawer() {
this.$refs.drawerRef.open();
},
closeDrawer() {
this.$refs.drawerRef.close();
},
applyFilterAndClose() {
this.applyFilter();
this.closeDrawer();
},
resetFilterAndClose() {
this.resetFilter();
this.closeDrawer();
},
getProjectOptions() {
listProject().then(res => {
this.projectOptions = (res.rows || []).map(item => ({
text: item.projectName,
value: item.projectId
}))
})
},
getHeaderOptions() {
listUser().then(res => {
this.headerOptions = (res.rows || []).map(item => ({
text: item.nickName,
value: item.userId
}))
})
},
// 搜索框检索
onSearch() {
this.searchName = this.searchName
this.handleQuery()
},
onClearSearch() {
this.searchName = ''
this.queryParams.scheduleName = ''
this.handleQuery()
},
// 筛选抽屉应用
applyFilter() {
this.queryParams.projectId = this.filterProject
this.queryParams.header = this.filterHeader
if (this.filterDateRange && this.filterDateRange.length === 2) {
this.queryParams.dateRange = this.filterDateRange
} else {
this.queryParams.dateRange = []
}
this.handleQuery()
},
// 筛选抽屉重置
resetFilter() {
this.filterProject = ''
this.filterHeader = ''
this.filterDateRange = []
},
handleQuery() {
this.pageNum = 1
this.hasMore = true
this.queryParams.pageNum = 1
if (this.queryParams.dateRange && this.queryParams.dateRange.length === 2) {
this.queryParams.startDate = this.queryParams.dateRange[0]
this.queryParams.endDate = this.queryParams.dateRange[1]
} else {
this.queryParams.startDate = ''
this.queryParams.endDate = ''
}
this.getList()
},
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 10,
projectId: '',
scheduleName: '',
header: '',
dateRange: []
}
this.searchName = ''
this.pageNum = 1
this.hasMore = true
this.getList()
},
getList() {
this.loadingMore = true
this.queryParams.pageNum = this.pageNum
this.queryParams.pageSize = this.pageSize
listReportSchedule(this.queryParams).then(res => {
const rows = res.rows || []
if (this.pageNum === 1) {
this.reportScheduleList = rows
} else {
this.reportScheduleList = this.reportScheduleList.concat(rows)
}
// 判断是否还有更多
this.hasMore = rows.length === this.pageSize
this.total = res.total || 0
}).finally(() => {
this.loadingMore = false
})
},
loadMore() {
if (!this.hasMore || this.loadingMore) return
this.pageNum++
this.getList()
},
handleAdd() {
this.form = {}
this.$refs.popupRef.open('bottom')
},
handleUpdate(row) {
getReportSchedule(row.scheduleId).then(res => {
this.form = res.data || {}
this.$refs.popupRef.open('bottom')
})
},
handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除排产“${item.scheduleName}”吗?`,
success: (res) => {
if (res.confirm) {
const ids = item ? [item.scheduleId] : this.selectedIds
delReportSchedule(ids).then(() => {
uni.showToast({ title: '删除成功', icon: 'success' })
this.getList()
})
}
}
})
},
handleExport() {
// 导出逻辑可根据实际API实现
uni.showToast({ title: '导出功能开发中', icon: 'none' })
},
submitForm() {
if (this.form.scheduleId) {
updateReportSchedule(this.form).then(() => {
uni.showToast({ title: '修改成功', icon: 'success' })
this.closePopup()
this.getList()
})
} else {
addReportSchedule(this.form).then(() => {
uni.showToast({ title: '新增成功', icon: 'success' })
this.closePopup()
this.getList()
})
}
},
closePopup() {
this.$refs.popupRef.close()
},
getSwipeOptions(item) {
const options = [
{
text: '编辑',
style: { backgroundColor: '#2979ff', color: '#fff' }
},
{
text: '删除',
style: { backgroundColor: '#fa3534', color: '#fff' }
}
];
if (!item.status || item.status === 0) {
options.push({
text: '开始',
style: { backgroundColor: '#19be6b', color: '#fff' }
});
}
return options;
},
swipeActionClick(e, item) {
const text = e.content.text;
if (text === '编辑') {
this.handleUpdate(item);
} else if (text === '删除') {
this.handleDelete(item);
} else if (text === '开始') {
this.handleStart(item);
}
},
handleStart(row) {
const now = new Date();
const pad = n => n < 10 ? '0' + n : n;
const formatDate = d => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
this.startForm = {
reportTitle: row.scheduleName,
reportDate: formatDate(now),
reporter: row.header,
projectId: row.projectId,
remark: row.remark,
type: 1
}
this._startScheduleId = row.scheduleId
this.openStartPopup()
},
openStartPopup() {
this.$refs.startPopupRef.open('bottom')
this.startPopupOpen = true
},
closeStartPopup() {
this.$refs.startPopupRef.close()
this.startPopupOpen = false
},
async submitStartForm() {
if (!this.startForm.reportTitle || !this.startForm.reportDate || !this.startForm.reporter || !this.startForm.projectId) {
uni.showToast({ title: '请填写完整', icon: 'none' })
return
}
this.startLoading = true
try {
await addReportSummary(this.startForm)
await updateReportSchedule({ scheduleId: this._startScheduleId, status: 1 })
uni.showToast({ title: '开始成功', icon: 'success' })
this.closeStartPopup()
this.getList()
} catch (e) {
uni.showToast({ title: '操作失败', icon: 'none' })
} finally {
this.startLoading = false
}
},
getProjectName(id) {
const p = this.projectOptions.find(i => i.value == id)
return p ? p.text : id
},
getHeaderName(id) {
const h = this.headerOptions.find(i => i.value == id)
return h ? h.text : id
},
getStatusText(status) {
if (status === 1) return '已开始'
if (status === 0 || status === undefined) return '未开始'
return '其他'
},
getGanttChartData(list) {
if (!list || !list.length) {
return { categories: [], series: [] }
}
// 只保留唯一 projectId
const uniqueProjectIds = Array.from(new Set(list.map(item => item.projectId)))
const categories = uniqueProjectIds.map(id => this.getProjectName(id))
const data = list.map(item => ({
...item,
projectName: this.getProjectName(item.projectId),
headerName: this.getHeaderName(item.header),
}))
return {
categories,
series: [
{
name: '进度',
data
}
]
}
},
toggleGanttView() {
this.showGanttView = !this.showGanttView;
if (this.showGanttView) {
this.ganttChartData = this.getGanttChartData(this.reportScheduleList);
}
}
}
}
</script>
<style scoped>
.report-schedule {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
.search-bar {
padding: 20rpx;
position: sticky;
top: 0;
z-index: 100;
background: #fff;
}
.search-container {
display: flex;
align-items: center;
gap: 20rpx;
}
.task-type-button-container {
display: flex;
gap: 12rpx;
}
.task-type-button {
width: 60rpx;
height: 60rpx;
background-color: transparent;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.search-input {
flex: 1;
position: relative;
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 100rpx;
padding: 0 24rpx;
height: 60rpx;
}
.input {
flex: 1;
border: none;
background: transparent;
font-size: 30rpx;
outline: none;
height: 60rpx;
line-height: 60rpx;
}
.search-icon {
margin-left: 8rpx;
display: flex;
align-items: center;
}
.clear-icon {
margin-left: 8rpx;
display: flex;
align-items: center;
}
.drawer-content {
padding: 32rpx 24rpx 24rpx 24rpx;
}
.drawer-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 24rpx;
}
.drawer-form {
margin-bottom: 32rpx;
}
.drawer-form-item {
margin-bottom: 24rpx;
}
.drawer-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.drawer-btns {
display: flex;
gap: 16rpx;
margin-top: 24rpx;
}
.drawer-btn-primary {
flex: 1;
background: #2979ff;
color: #fff;
border: none;
border-radius: 8rpx;
padding: 16rpx 0;
font-size: 28rpx;
}
.drawer-btn {
flex: 1;
background: #f5f5f5;
color: #333;
border: none;
border-radius: 8rpx;
padding: 16rpx 0;
font-size: 28rpx;
}
.card {
background: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
padding: 24rpx;
}
.card-title {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
margin-bottom: 12rpx;
}
.project {
color: #2979ff;
}
.status {
font-size: 24rpx;
padding: 0 12rpx;
border-radius: 8rpx;
}
.status-1 {
background: #e0f7fa;
color: #009688;
}
.status-0,
.status-undefined {
background: #fffbe6;
color: #faad14;
}
.status-other {
background: #fbeff2;
color: #e91e63;
}
.card-content view {
margin-bottom: 8rpx;
color: #666;
font-size: 26rpx;
}
.empty {
text-align: center;
color: #bbb;
margin: 40rpx 0;
}
.popup-content {
padding: 24rpx;
background: #fff;
border-radius: 16rpx 16rpx 0 0;
}
.uni-form {
margin-bottom: 32rpx;
}
.uni-form-item {
margin-bottom: 32rpx;
}
.uni-form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.load-more-tips {
text-align: center;
color: #bbb;
padding: 24rpx 0 32rpx 0;
font-size: 28rpx;
}
.card-ops {
margin-top: 16rpx;
display: flex;
gap: 16rpx;
}
.gantt-toggle-bar {
display: flex;
gap: 16rpx;
padding: 16rpx 0 8rpx 0;
background: #fff;
justify-content: flex-end;
}
</style>