feat(oa/project/dispatch): 新增项目和合同的CRUD功能
重构项目调度页面: 1. 左侧项目列表新增增删改操作按钮 2. 合同模块新增工具栏和增删改功能 3. 实现完整的项目编辑弹窗,包含表单校验和字典数据绑定 4. 实现完整的合同编辑弹窗,支持甲乙双方信息填写 5. 补充对应的API调用和状态管理逻辑 6. 调整页面样式布局优化展示效果
This commit is contained in:
@@ -4,9 +4,16 @@
|
||||
<!-- ══════════════ 左栏:项目列表 ══════════════ -->
|
||||
<div class="panel-left">
|
||||
<div class="left-header">
|
||||
<span class="left-title">项目列表</span>
|
||||
<div class="left-header-row">
|
||||
<span class="left-title">项目管理</span>
|
||||
<span class="left-count">{{ projectList.length }} 个</span>
|
||||
</div>
|
||||
<div class="left-actions">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddProject">新增</el-button>
|
||||
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="!activeProject" @click="handleEditProject">修改</el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="!activeProject" @click="handleDeleteProject">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="left-search">
|
||||
<el-input v-model="searchText" placeholder="编号 / 代号 / 名称" prefix-icon="el-icon-search" size="small" clearable @input="filterProjects" />
|
||||
</div>
|
||||
@@ -103,7 +110,18 @@
|
||||
<!-- ── 合同 ── -->
|
||||
<template v-else-if="activeTab === 'contract'">
|
||||
<div class="section-title">合同记录</div>
|
||||
<el-table :data="moduleData.contract" size="small" border stripe>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddContract">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="!contractSelectedRow" @click="handleEditContract">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="!contractSelectedRow" @click="handleDeleteContract">删除</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-table :data="moduleData.contract" size="small" border stripe highlight-current-row @current-change="val => { contractSelectedRow = val }">
|
||||
<el-table-column label="合同编号" prop="contractNum" width="140" />
|
||||
<el-table-column label="合同名称" prop="contractName" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="合同金额(元)" prop="contractPrice" width="120" align="right">
|
||||
@@ -585,12 +603,223 @@
|
||||
<p>请从左侧选择一个项目</p>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ 项目编辑弹窗 ══════════════ -->
|
||||
<el-dialog :title="projectTitle" :visible.sync="projectEditShow" width="70%" append-to-body @close="closeProjectDialog">
|
||||
<el-form ref="projectFormRef" :model="projectForm" :rules="projectRules" label-width="100px">
|
||||
<el-divider content-position="center">基本信息</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="projectForm.projectName" placeholder="请输入项目名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="项目总金额" prop="funds">
|
||||
<el-input v-model="projectForm.funds" placeholder="元" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="负责人" prop="functionary">
|
||||
<el-input v-model="projectForm.functionary" placeholder="项目负责人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="项目编号" prop="projectNum">
|
||||
<el-input v-model="projectForm.projectNum" placeholder="项目编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="项目代号" prop="projectCode">
|
||||
<el-select v-model="projectForm.projectCodeType" placeholder="代号类型" style="width:45%" filterable>
|
||||
<el-option v-for="dict in dict.type.sys_project_code" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
<el-input v-model="projectForm.projectCodeNumber" placeholder="序号" style="width:55%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目地址" prop="address">
|
||||
<el-input v-model="projectForm.address" placeholder="项目地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="贸易类型">
|
||||
<el-select v-model="projectForm.tradeType" placeholder="贸易类型" style="width:100%">
|
||||
<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-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目类型">
|
||||
<el-select v-model="projectForm.projectType" placeholder="项目类型" style="width:100%">
|
||||
<el-option v-for="dict in dict.type.sys_project_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="优先级">
|
||||
<el-radio-group v-model="projectForm.projectGrade">
|
||||
<el-radio-button v-for="dict in dict.type.sys_sort_grade" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="项目周期">
|
||||
<el-date-picker v-model="projectPeriod" value-format="yyyy-MM-dd HH:mm:ss" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="交货期" prop="delivery">
|
||||
<el-input v-model="projectForm.delivery" placeholder="交货期" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="质保期" prop="guarantee">
|
||||
<el-input v-model="projectForm.guarantee" placeholder="质保期" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签约公司">
|
||||
<el-select v-model="projectForm.signingCompany" placeholder="签约公司" style="width:100%" clearable>
|
||||
<el-option v-for="dict in dict.type.signing_company" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预付款" prop="prePay">
|
||||
<el-input v-model="projectForm.prePay" placeholder="预付款金额" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="项目介绍">
|
||||
<el-input v-model="projectForm.introduction" type="textarea" :rows="3" placeholder="项目介绍" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="projectForm.remark" type="textarea" :rows="2" placeholder="备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="附件">
|
||||
<file-upload v-model="projectForm.accessory" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="closeProjectDialog">取 消</el-button>
|
||||
<el-button type="primary" :loading="projectSubmitLoading" @click="submitProjectForm">保 存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ══════════════ 合同编辑弹窗 ══════════════ -->
|
||||
<el-dialog :title="contractTitle" :visible.sync="contractEditShow" width="65%" append-to-body @close="closeContractDialog">
|
||||
<el-form ref="contractFormRef" :model="contractForm" label-width="100px">
|
||||
<!-- 基本信息 -->
|
||||
<el-divider content-position="left">基本信息</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="合同名称" prop="contractName">
|
||||
<el-input v-model="contractForm.contractName" placeholder="合同名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="合同编号" prop="contractNum">
|
||||
<el-input v-model="contractForm.contractNum" placeholder="合同编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="合同金额(元)" prop="contractPrice">
|
||||
<el-input v-model="contractForm.contractPrice" placeholder="合同金额" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="合同类型">
|
||||
<el-select v-model="contractForm.contractType" placeholder="合同类型" style="width:100%">
|
||||
<el-option label="项目合同" value="2" />
|
||||
<el-option label="采购合同" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="合同状态">
|
||||
<el-input v-model="contractForm.contractStatus" placeholder="合同状态" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签订日期 / 有效期">
|
||||
<el-date-picker v-model="contractPeriod" value-format="yyyy-MM-dd" type="daterange" range-separator="至" start-placeholder="签订日期" end-placeholder="有效期" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 甲方 -->
|
||||
<el-divider content-position="left">甲方信息</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="甲方单位">
|
||||
<el-input v-model="contractForm.firstName" placeholder="甲方单位名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="甲方负责人">
|
||||
<el-input v-model="contractForm.firstPerson" placeholder="负责人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="甲方电话">
|
||||
<el-input v-model="contractForm.firstPhone" placeholder="联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 乙方 -->
|
||||
<el-divider content-position="left">乙方信息</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="乙方单位">
|
||||
<el-input v-model="contractForm.secondName" placeholder="乙方单位名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="乙方负责人">
|
||||
<el-input v-model="contractForm.secondPerson" placeholder="负责人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="乙方电话">
|
||||
<el-input v-model="contractForm.secondPhone" placeholder="联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 其他 -->
|
||||
<el-divider content-position="left">其他信息</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="contractForm.remark" type="textarea" :rows="2" placeholder="备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="附件">
|
||||
<file-upload v-model="contractForm.accessory" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="closeContractDialog">取 消</el-button>
|
||||
<el-button type="primary" :loading="contractSubmitLoading" @click="submitContractForm">保 存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProject, getProject } from '@/api/oa/project'
|
||||
import { listOaContract } from '@/api/oa/oaContract'
|
||||
import { listProject, getProject, addProject, updateProject, delProject } from '@/api/oa/project'
|
||||
import { listOaContract, addOaContract, updateOaContract, delOaContract } from '@/api/oa/oaContract'
|
||||
import { listFinancePro } from '@/api/oa/finance'
|
||||
import request from '@/utils/request'
|
||||
// /oa/cost/list1 支持 projectId 过滤(list 是全项目汇总,不能用)
|
||||
@@ -611,6 +840,7 @@ import { listReportSummary } from '@/api/oa/reportSummary'
|
||||
import { listFileOperationRecord } from '@/api/oa/fileOperationRecord'
|
||||
import { listFeedback } from '@/api/oa/feedback'
|
||||
import { listOperationLog } from '@/api/oa/projectOperationLog'
|
||||
import FileUpload from '@/components/FileUpload/index.vue'
|
||||
|
||||
const MODULES = [
|
||||
{ key: 'info', label: '基本信息', icon: 'el-icon-office-building' },
|
||||
@@ -655,6 +885,8 @@ function makePaging () { return { pageNum: 1, pageSize: 20, total: 0 } }
|
||||
|
||||
export default {
|
||||
name: 'ProjectDispatch',
|
||||
components: { FileUpload },
|
||||
dicts: ['sys_project_status', 'sys_project_type', 'sys_trade_type', 'sys_project_code', 'signing_company', 'sys_sort_grade'],
|
||||
data () {
|
||||
return {
|
||||
MODULES,
|
||||
@@ -667,7 +899,29 @@ export default {
|
||||
moduleData: {},
|
||||
moduleLoading: {},
|
||||
badgeCounts: {},
|
||||
modulePaging: Object.fromEntries(MODULES.map(m => [m.key, makePaging()]))
|
||||
modulePaging: Object.fromEntries(MODULES.map(m => [m.key, makePaging()])),
|
||||
// ── 项目 CRUD ──
|
||||
projectEditShow: false,
|
||||
projectForm: {},
|
||||
projectPeriod: [],
|
||||
projectTitle: '',
|
||||
projectSubmitLoading: false,
|
||||
// ── 合同 CRUD ──
|
||||
contractEditShow: false,
|
||||
contractForm: {},
|
||||
contractTitle: '',
|
||||
contractSubmitLoading: false,
|
||||
contractSelectedRow: null,
|
||||
// 合同表单额外字段
|
||||
contractPeriod: [],
|
||||
// ── 表单校验规则 ──
|
||||
projectRules: {
|
||||
projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
|
||||
projectNum: [{ required: true, message: '项目编号不能为空', trigger: 'blur' }],
|
||||
functionary: [{ required: true, message: '负责人不能为空', trigger: 'blur' }],
|
||||
address: [{ required: true, message: '项目地址不能为空', trigger: 'blur' }],
|
||||
funds: [{ required: true, message: '项目总金额不能为空', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -796,6 +1050,126 @@ export default {
|
||||
listOperationLog(cp).then(r => setCount('oplog', r)).catch(() => {})
|
||||
},
|
||||
|
||||
// ── 项目 CRUD 方法 ──
|
||||
handleAddProject () {
|
||||
this.projectForm = {}
|
||||
this.projectPeriod = []
|
||||
this.projectTitle = '新增项目'
|
||||
this.projectEditShow = true
|
||||
},
|
||||
handleEditProject () {
|
||||
const p = this.activeProject
|
||||
if (!p) { this.$message.warning('请先选择项目'); return }
|
||||
getProject(p.projectId).then(res => {
|
||||
this.projectForm = res.data || {}
|
||||
this.projectPeriod = [this.projectForm.beginTime, this.projectForm.finishTime]
|
||||
this.projectTitle = '编辑项目: ' + (res.data?.projectName || '')
|
||||
this.projectEditShow = true
|
||||
}).catch(() => { this.$message.error('获取项目详情失败') })
|
||||
},
|
||||
handleDeleteProject () {
|
||||
const p = this.activeProject
|
||||
if (!p) { this.$message.warning('请先选择项目'); return }
|
||||
this.$confirm(`确定删除项目"${p.projectName}"?删除后不可恢复。`, '警告', {
|
||||
confirmButtonText: '确定删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
return delProject(p.projectId)
|
||||
}).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.activeProject = null
|
||||
this.loadProjects()
|
||||
}).catch(() => {})
|
||||
},
|
||||
submitProjectForm () {
|
||||
this.projectSubmitLoading = true
|
||||
const payload = {
|
||||
...this.projectForm,
|
||||
beginTime: this.projectPeriod[0] || null,
|
||||
finishTime: this.projectPeriod[1] || null,
|
||||
projectCode: (this.projectForm.projectCodeType && this.projectForm.projectCodeNumber)
|
||||
? this.projectForm.projectCodeType + '-' + this.projectForm.projectCodeNumber
|
||||
: this.projectForm.projectCode || undefined
|
||||
}
|
||||
const isEdit = !!this.projectForm.projectId
|
||||
const api = isEdit ? updateProject(payload) : addProject(payload)
|
||||
api.then(() => {
|
||||
this.$message.success(isEdit ? '修改成功' : '新增成功')
|
||||
this.projectEditShow = false
|
||||
this.loadProjects()
|
||||
// 如果是编辑,刷新右侧详情
|
||||
if (isEdit && this.activeProject) {
|
||||
this.activeProject = { ...this.activeProject, ...payload }
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.error('操作失败')
|
||||
}).finally(() => {
|
||||
this.projectSubmitLoading = false
|
||||
})
|
||||
},
|
||||
closeProjectDialog () {
|
||||
this.projectEditShow = false
|
||||
this.projectForm = {}
|
||||
},
|
||||
// ── 合同 CRUD 方法 ──
|
||||
handleAddContract () {
|
||||
if (!this.activeProject) { this.$message.warning('请先选择项目'); return }
|
||||
this.contractForm = { projectId: this.activeProject.projectId, contractType: '2' }
|
||||
this.contractPeriod = []
|
||||
this.contractTitle = '新增合同'
|
||||
this.contractEditShow = true
|
||||
},
|
||||
handleEditContract () {
|
||||
const row = this.contractSelectedRow
|
||||
if (!row) { this.$message.warning('请先选择一条合同记录'); return }
|
||||
this.contractForm = { ...row }
|
||||
this.contractPeriod = [row.signTime, row.validity]
|
||||
this.contractTitle = '编辑合同: ' + (row.contractName || '')
|
||||
this.contractEditShow = true
|
||||
},
|
||||
handleDeleteContract () {
|
||||
const row = this.contractSelectedRow
|
||||
if (!row) { this.$message.warning('请先选择一条合同记录'); return }
|
||||
this.$confirm(`确定删除合同"${row.contractName}"?`, '警告', {
|
||||
confirmButtonText: '确定删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
return delOaContract(row.contractId)
|
||||
}).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.contractSelectedRow = null
|
||||
this.fetchModule('contract')
|
||||
this.loadAllBadges()
|
||||
}).catch(() => {})
|
||||
},
|
||||
submitContractForm () {
|
||||
this.contractSubmitLoading = true
|
||||
const payload = {
|
||||
...this.contractForm,
|
||||
projectId: this.activeProject?.projectId,
|
||||
signTime: this.contractPeriod[0] || null,
|
||||
validity: this.contractPeriod[1] || null
|
||||
}
|
||||
const isEdit = !!this.contractForm.contractId
|
||||
const api = isEdit ? updateOaContract(payload) : addOaContract(payload)
|
||||
api.then(() => {
|
||||
this.$message.success(isEdit ? '修改成功' : '新增成功')
|
||||
this.contractEditShow = false
|
||||
this.contractSelectedRow = null
|
||||
this.fetchModule('contract')
|
||||
this.loadAllBadges()
|
||||
}).catch(() => {
|
||||
this.$message.error('操作失败')
|
||||
}).finally(() => {
|
||||
this.contractSubmitLoading = false
|
||||
})
|
||||
},
|
||||
closeContractDialog () {
|
||||
this.contractEditShow = false
|
||||
this.contractForm = {}
|
||||
},
|
||||
// ── 辅助 ──
|
||||
// sys_project_status 字典: '0'=进行中(primary), '1'=进度完成(success)
|
||||
statusLabel (s) {
|
||||
@@ -863,15 +1237,18 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
.left-header {
|
||||
padding: 14px 14px 8px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
padding: 10px 12px 8px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.left-header-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
}
|
||||
.left-title { font-size: 14px; font-weight: 700; color: #303133; }
|
||||
.left-count { font-size: 12px; color: #909399; }
|
||||
.left-actions { display: flex; gap: 4px; margin-top: 6px; flex-wrap: wrap; }
|
||||
.left-search { padding: 10px 10px 6px; flex-shrink: 0; }
|
||||
.left-list { flex: 1; overflow-y: auto; }
|
||||
.left-empty { text-align: center; color: #c0c4cc; padding: 40px 0; font-size: 13px; }
|
||||
@@ -1020,6 +1397,7 @@ export default {
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #409EFF;
|
||||
}
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
|
||||
/* ══ 统计卡片(基本信息页底部) ══ */
|
||||
.stat-mini {
|
||||
|
||||
Reference in New Issue
Block a user