Files
fad_oa/ruoyi-ui/src/views/oa/project/pace/index.vue
2026-04-22 18:44:14 +08:00

396 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>
<div class="app-container" v-loading="loading">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="项目名称" prop="projectId">
<project-select v-model="queryParams.projectId" placeholder="请选择项目" clearable />
<!-- <el-select v-model="queryParams.projectId" filterable placeholder="请选择">
<el-option v-for="item in projects" :key="item.projectId" :label="item.projectName" :value="item.projectId">
</el-option>
</el-select> -->
</el-form-item>
<el-form-item label="项目编号" prop="projectNum">
<el-input v-model="queryParams.projectNum" placeholder="请输入项目编号" clearable />
</el-form-item>
<el-form-item label="贸易类型" prop="tradeType">
<el-select v-model="queryParams.tradeType" placeholder="请选择项目类型" clearable>
<el-option v-for="dict in dict.type.sys_trade_type" :key="dict.value" :label="dict.label"
:value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="项目代号" prop="projectCode">
<el-select v-model="queryParams.projectCode" placeholder="请选择代号类型" style="width: 100%" filterable
@change="handleQuery">
<el-option v-for="dict in dict.type.sys_project_code" :key="dict.value" :label="dict.label"
:value="dict.value">
<span style="float: left">{{ dict.label }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ dict.value }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="项目周期">
<el-date-picker v-model="searchTime" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="['00:00:00', '23:59:59']">
</el-date-picker>
</el-form-item>
<el-form-item label="负责人">
<el-select filterable allow-add v-model="queryParams.steward" @change="handleStatusChange(scope.row)">
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.nickName">
</el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="优质筛选">
<el-switch v-model="queryParams.prePay" active-text="开" active-value="0.1" inactive-value="0"
@change="selectPrePay" inactive-text="">
</el-switch>
</el-form-item> -->
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">绑定进度
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-row :gutter="20">
<el-table v-loading="loading" :data="scheduleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="代号" prop="projectCode" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.projectCode == null" type="danger"></el-tag>
<el-tag v-else>{{ scope.row.projectCode }}</el-tag>
</template>
</el-table-column>
<el-table-column label="项目名称" prop="projectName" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="scope.row.prePay > 0"></span>
<span>{{ scope.row.projectName }}</span>
</template>
</el-table-column>
<el-table-column label="项目编号" prop="projectNum"></el-table-column>
<el-table-column label="负责人" align="center">
<template slot-scope="scope">
<el-select filterable allow-add v-model="scope.row.steward" @change="handleStatusChange(scope.row)">
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.nickName">
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="状态" align="center">
<template slot-scope="scope">
<span v-if="scope.row.isTop" style="color: #ff4d4f;">重点关注</span>
<span v-else style="">一般项目</span>
</template>
</el-table-column>
<el-table-column label="延期进度数" prop="delayCount" />
<el-table-column label="未完成进度" prop="unFinishCount" />
<el-table-column label="完成状态" align="center" prop="sortNum">
<template slot-scope="scope">
<el-select size="mini" v-model="scope.row.status" placeholder="请选择完成状态"
@change="handleStatusChange(scope.row)">
<el-option label="进行中" :value="1"></el-option>
<el-option label="已完成" :value="2"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleDetail(scope.row)">进度详情
</el-button>
<el-button size="mini" type="text" icon="el-icon-time" @click="handlePostpone(scope.row)">延期记录
</el-button>
<el-button size="mini" type="text" icon="el-icon-check"
v-if="scope.row.schedulePercentage === 100 && scope.row.status !== 2"
@click="handleComplete(scope.row)">完成任务
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
</el-row>
<el-drawer title="进度详情" :visible.sync="detailDrawer" direction="btt" size="90%" :before-close="closeDetailShow">
<div style="padding:0 20px">
<project-schedule-step :scheduleId="scheduleDetail.scheduleId" :master="scheduleDetail.functionary"
:projectName="scheduleDetail.projectName" :projectStatus="scheduleDetail.projectStatus"
:isTop="scheduleDetail.isTop" :projectId="scheduleDetail.projectId" />
</div>
</el-drawer>
<FormDialog v-model="addDialog" :projects="projects" @save="handleSave" />
<el-drawer title="延期记录" :visible.sync="postponeDrawer" direction="btt" size="90%">
<postpone :scheduleId="scheduleDetail.scheduleId" />
</el-drawer>
</div>
</template>
<script>
import { listProject } from "@/api/oa/project";
import { addByProjectId, delProjectSchedule, listProjectSchedule, updateProjectSchedule } from "@/api/oa/projectSchedule";
import { listUser } from "@/api/system/user";
import ProjectSelect from "@/components/fad-service/ProjectSelect/index.vue";
import UserSelect from "@/components/UserSelect/index.vue";
import FormDialog from "./components/FormDialog.vue";
import Postpone from "./components/postpone.vue";
import ProjectScheduleStep from "./components/step.vue";
export default {
name: "Schedule",
dicts: ['sys_project_status', 'sys_trade_type', 'sys_project_type', 'sys_project_code'],
components: {
UserSelect,
ProjectScheduleStep,
FormDialog,
ProjectSelect,
Postpone
},
data () {
return {
loading: false,
detailDrawer: false,
fileShow: false,
addDialog: false,
multiple: true,
form: {},
projects: [],
scheduleList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
startTime: '',
endTime: '',
steward: ''
},
total: 0,
searchTime: [],
showSearch: true,
recentProjects: [],
scheduleDetail: {},
userList: [],
postponeDrawer: false
};
},
mounted () {
this.currentUser = this.$store.state.user
this.getList();
this.getProjectList();
this.getAllUser();
const cache = localStorage.getItem('oa_recent_projects');
if (cache) {
this.recentProjects = JSON.parse(cache);
}
},
methods: {
tryOpenDetail (trackId) {
if (!trackId) return;
const found = this.scheduleList.find(item => item.scheduleId === trackId || item.projectId === trackId);
if (found) {
this.getScheduleDetail(found);
} else {
this.$nextTick(() => {
const found = this.scheduleList.find(item => item.scheduleId === trackId || item.projectId === trackId);
if (found) {
this.getScheduleDetail(found);
}
});
}
},
closeDetailShow (done) {
this.getList();
done()
},
getAllUser () {
listUser({ pageNum: 1, pageSize: 999 }).then(res => {
this.userList = res.rows
})
},
handleStatusChange (row) {
updateProjectSchedule(row).then(res => {
this.getList();
this.$modal.msgSuccess("操作成功")
})
},
// 绑定进度
handleAdd () {
this.addDialog = true;
},
selectPrePay () {
this.handleQuery()
},
/** 搜索按钮操作 */
handleQuery () {
this.queryParams.pageNum = 1;
this.getList();
},
getDateStr (date) {
if (!date) {
return ''
}
return this.parseTime(date, '{y}-{m}-{d} {h}:{i}:{s}')
},
getList () {
this.loading = true
/* 日期搜索条件 */
if (this.searchTime && this.searchTime.length) {
this.queryParams.startTime = this.getDateStr(this.searchTime[0])
this.queryParams.endTime = this.getDateStr(this.searchTime[1])
}
this.queryParams.projectId = this.$route.query.projectId?this.$route.query.projectId:null
this.queryParams.trackId = this.$route.query.trackId?this.$route.query.trackId:null
listProjectSchedule(this.queryParams).then(res => {
this.scheduleList = res.rows
this.total = res.total
let cache = JSON.parse(localStorage.getItem('oa_recent_projects') || '[]')
const id2idx = new Map()
cache.forEach((item, idx) => id2idx.set(item.scheduleId, idx))
for (const row of res.rows) {
const hit = id2idx.get(row.scheduleId)
if (hit !== undefined) {
cache[hit] = row
} else {
cache.unshift(row)
}
}
cache = cache.slice(0, 2)
/* 25 回写缓存 + 更新响应式数据 */
localStorage.setItem('oa_recent_projects', JSON.stringify(cache))
this.recentProjects = cache
/* 3. 结束 loading */
this.loading = false
})
},
getProjectList () {
let params = {
pageNum: 1,
pageSize: 999,
}
listProject(params).then(res => {
this.projects = res.rows
})
},
/** 保存处理*/
handleSave (payload) {
addByProjectId(payload).then(response => {
this.getList();
this.$modal.msgSuccess("绑定成功")
})
},
handleDetail (row) {
// 把当前项目放到数组最前面,去重
const list = [row, ...this.recentProjects.filter(p => p.projectId !== row.projectId)];
// 只保留前 2 条
this.recentProjects = list.slice(0, 2);
// 持久化
localStorage.setItem('oa_recent_projects', JSON.stringify(this.recentProjects));
this.getScheduleDetail(row)
},
handlePostpone (row) {
// 打开延期记录弹窗
this.postponeDrawer = true
this.scheduleDetail = row
},
getScheduleDetail (row) {
this.scheduleDetail = row
this.detailDrawer = true
},
/* ========= 左侧主列表删除(支持单删或批量 ids ========= */
handleDelete (row) {
/* 支持row.scheduleId 或 this.ids = '1,2,3' */
const scheduleIds = row.scheduleId || this.ids
this.$modal.confirm(`将会删除进度编号为 "${scheduleIds}" 的数据项, 同时会删除所有的子进度且无法找回 !!! 是否继续? `)
.then(() => {
this.loading = true
return delProjectSchedule(scheduleIds)
})
.then(() => {
/* 刷新左侧列表并提示 */
this.getList()
this.$modal.msgSuccess('删除成功')
})
},
// 多选框选中数据
handleSelectionChange (selection) {
this.ids = selection.map(item => item.scheduleId)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 重置按钮操作 */
resetQuery () {
this.searchTime = [];
this.resetForm("queryForm");
this.handleQuery();
},
},
}
</script>
<style scoped>
.uploader {
margin-top: 12px;
}
.file-item {
margin-top: 12px;
}
.file-row {
display: flex;
font-size: small;
color: #414141;
align-items: center;
}
.file-icon {
width: 40px;
text-align: center;
margin-right: 12px;
}
.file-info {
flex: 1;
}
.file-meta {
color: #909399;
font-size: 12px;
margin-top: 2px;
}
.file-actions el-button+el-button {
margin-left: 4px;
}
</style>