|
@@ -21,33 +21,36 @@
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
<el-form-item>
|
|
|
- <el-button type="primary" @click="handleQuery"
|
|
|
|
|
- ><Icon icon="ep:search" class="mr-5px" />搜索</el-button
|
|
|
|
|
- >
|
|
|
|
|
- <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
|
|
|
|
|
- <el-button type="primary" plain @click="openForm()"
|
|
|
|
|
- ><Icon icon="ep:plus" class="mr-5px" />新增项目</el-button
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <el-button type="primary" @click="handleQuery">
|
|
|
|
|
+ <Icon icon="ep:search" class="mr-5px" />搜索
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" />重置 </el-button>
|
|
|
|
|
+ <el-button type="primary" plain @click="openForm()" v-hasPermi="['rd:project:create']">
|
|
|
|
|
+ <Icon icon="ep:plus" class="mr-5px" />新增项目
|
|
|
|
|
+ </el-button>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
</ContentWrap>
|
|
</ContentWrap>
|
|
|
|
|
|
|
|
<ContentWrap style="border: none">
|
|
<ContentWrap style="border: none">
|
|
|
- <div class="project-table-heading">
|
|
|
|
|
- <div><h2>科研项目</h2><span>维护项目申报信息、预算及阶段性计划</span></div>
|
|
|
|
|
- <el-tag type="info" effect="plain">共 {{ filteredList.length }} 个项目</el-tag>
|
|
|
|
|
- </div>
|
|
|
|
|
- <zm-table v-loading="loading" :data="filteredList" row-key="id">
|
|
|
|
|
|
|
+ <zm-table v-loading="loading" :data="list" row-key="id">
|
|
|
<zm-table-column label="项目名称" prop="projectName" min-width="250" show-overflow-tooltip />
|
|
<zm-table-column label="项目名称" prop="projectName" min-width="250" show-overflow-tooltip />
|
|
|
- <zm-table-column label="项目级别" prop="projectLevel" min-width="180" />
|
|
|
|
|
|
|
+ <zm-table-column label="立项时间" prop="projectDate" min-width="250" show-overflow-tooltip />
|
|
|
|
|
+ <zm-table-column
|
|
|
|
|
+ label="计划结题时间"
|
|
|
|
|
+ prop="projectEndDate"
|
|
|
|
|
+ min-width="250"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
<zm-table-column
|
|
<zm-table-column
|
|
|
label="执行单位"
|
|
label="执行单位"
|
|
|
prop="executingUnit"
|
|
prop="executingUnit"
|
|
|
min-width="210"
|
|
min-width="210"
|
|
|
show-overflow-tooltip
|
|
show-overflow-tooltip
|
|
|
/>
|
|
/>
|
|
|
- <zm-table-column label="项目负责人" prop="projectLeader" width="130" />
|
|
|
|
|
- <zm-table-column label="项目类型" prop="projectType" min-width="180" />
|
|
|
|
|
|
|
+ <zm-table-column label="项目级别" prop="projectLevel" min-width="180" />
|
|
|
|
|
+
|
|
|
|
|
+ <zm-table-column label="项目进度" prop="progress" width="130" />
|
|
|
<zm-table-column label="状态" width="110" align="center"
|
|
<zm-table-column label="状态" width="110" align="center"
|
|
|
><template #default="{ row }"
|
|
><template #default="{ row }"
|
|
|
><el-tag :type="row.status === '进行中' ? 'success' : 'info'">{{
|
|
><el-tag :type="row.status === '进行中' ? 'success' : 'info'">{{
|
|
@@ -58,7 +61,7 @@
|
|
|
<zm-table-column label="操作" width="240" fixed="right" align="center"
|
|
<zm-table-column label="操作" width="240" fixed="right" align="center"
|
|
|
><template #default="{ row }">
|
|
><template #default="{ row }">
|
|
|
<el-button link type="primary" @click="openForm(row)">提交</el-button>
|
|
<el-button link type="primary" @click="openForm(row)">提交</el-button>
|
|
|
- <el-button link type="primary" @click="openForm(row)">修改申请</el-button>
|
|
|
|
|
|
|
+
|
|
|
<el-button link type="primary" @click="openForm(row)">编辑</el-button
|
|
<el-button link type="primary" @click="openForm(row)">编辑</el-button
|
|
|
><el-button link type="danger" @click="removeProject(row.id)">删除</el-button></template
|
|
><el-button link type="danger" @click="removeProject(row.id)">删除</el-button></template
|
|
|
></zm-table-column
|
|
></zm-table-column
|
|
@@ -67,7 +70,8 @@
|
|
|
<Pagination
|
|
<Pagination
|
|
|
v-model:page="queryParams.pageNo"
|
|
v-model:page="queryParams.pageNo"
|
|
|
v-model:limit="queryParams.pageSize"
|
|
v-model:limit="queryParams.pageSize"
|
|
|
- :total="filteredList.length"
|
|
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ @pagination="getList"
|
|
|
/>
|
|
/>
|
|
|
</ContentWrap>
|
|
</ContentWrap>
|
|
|
|
|
|
|
@@ -89,19 +93,29 @@
|
|
|
<el-divider content-position="left">项目基本信息</el-divider>
|
|
<el-divider content-position="left">项目基本信息</el-divider>
|
|
|
<el-row :gutter="24">
|
|
<el-row :gutter="24">
|
|
|
<el-col :span="12"
|
|
<el-col :span="12"
|
|
|
- ><el-form-item label="项目级别" prop="projectLevel"
|
|
|
|
|
- ><el-select v-model="formData.projectLevel" class="w-full" placeholder="请选择项目级别"
|
|
|
|
|
- ><el-option
|
|
|
|
|
- v-for="item in projectLevels"
|
|
|
|
|
- :key="item"
|
|
|
|
|
- :label="item"
|
|
|
|
|
- :value="item" /></el-select></el-form-item
|
|
|
|
|
- ></el-col>
|
|
|
|
|
|
|
+ ><el-form-item label="项目级别" prop="projectLevel">
|
|
|
|
|
+ <el-select v-model="formData.projectLevel" placeholder="请选择项目级别" clearable>
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="dict in getStrDictOptions(DICT_TYPE.PROJECT_LEVEL)"
|
|
|
|
|
+ :key="dict.value"
|
|
|
|
|
+ :label="dict.label"
|
|
|
|
|
+ :value="dict.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
<el-col :span="12"
|
|
<el-col :span="12"
|
|
|
- ><el-form-item label="执行单位" prop="executingUnit"
|
|
|
|
|
- ><el-input
|
|
|
|
|
- v-model="formData.executingUnit"
|
|
|
|
|
- placeholder="请输入执行单位" /></el-form-item
|
|
|
|
|
|
|
+ ><el-form-item label="执行单位" prop="executingUnit">
|
|
|
|
|
+ <el-tree-select
|
|
|
|
|
+ clearable
|
|
|
|
|
+ v-model="formData.deptId"
|
|
|
|
|
+ :data="deptList2"
|
|
|
|
|
+ :props="defaultProps"
|
|
|
|
|
+ :check-strictly="false"
|
|
|
|
|
+ node-key="id"
|
|
|
|
|
+ filterable
|
|
|
|
|
+ placeholder="请选择所在部门"
|
|
|
|
|
+ /> </el-form-item
|
|
|
></el-col>
|
|
></el-col>
|
|
|
<el-col :span="12"
|
|
<el-col :span="12"
|
|
|
><el-form-item label="项目名称" prop="projectName"
|
|
><el-form-item label="项目名称" prop="projectName"
|
|
@@ -121,12 +135,13 @@
|
|
|
></el-col>
|
|
></el-col>
|
|
|
<el-col :span="12"
|
|
<el-col :span="12"
|
|
|
><el-form-item label="项目类型" prop="projectType"
|
|
><el-form-item label="项目类型" prop="projectType"
|
|
|
- ><el-select v-model="formData.projectType" class="w-full" placeholder="请选择项目类型"
|
|
|
|
|
- ><el-option
|
|
|
|
|
- v-for="item in projectTypes"
|
|
|
|
|
- :key="item"
|
|
|
|
|
- :label="item"
|
|
|
|
|
- :value="item" /></el-select></el-form-item
|
|
|
|
|
|
|
+ ><el-select v-model="formData.projectType" placeholder="请选择项目类型" clearable>
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="dict in getStrDictOptions(DICT_TYPE.PROJECT_TYPE)"
|
|
|
|
|
+ :key="dict.value"
|
|
|
|
|
+ :label="dict.label"
|
|
|
|
|
+ :value="dict.value"
|
|
|
|
|
+ /> </el-select></el-form-item
|
|
|
></el-col>
|
|
></el-col>
|
|
|
</el-row>
|
|
</el-row>
|
|
|
<el-divider content-position="left">项目内容</el-divider>
|
|
<el-divider content-position="left">项目内容</el-divider>
|
|
@@ -161,9 +176,9 @@
|
|
|
<div class="schedule-editor">
|
|
<div class="schedule-editor">
|
|
|
<div class="schedule-toolbar"
|
|
<div class="schedule-toolbar"
|
|
|
><span>按阶段填写工作计划与截止时间</span
|
|
><span>按阶段填写工作计划与截止时间</span
|
|
|
- ><el-button type="primary" plain size="small" @click="addSchedule"
|
|
|
|
|
- ><Icon icon="ep:plus" class="mr-5px" />增加工作</el-button
|
|
|
|
|
- ></div
|
|
|
|
|
|
|
+ ><el-button type="primary" plain size="small" @click="addSchedule">
|
|
|
|
|
+ <Icon icon="ep:plus" class="mr-5px" />增加工作
|
|
|
|
|
+ </el-button></div
|
|
|
>
|
|
>
|
|
|
<el-table :data="formData.schedule" border class="schedule-table">
|
|
<el-table :data="formData.schedule" border class="schedule-table">
|
|
|
<el-table-column type="index" label="#" width="56" align="center" />
|
|
<el-table-column type="index" label="#" width="56" align="center" />
|
|
@@ -182,8 +197,8 @@
|
|
|
></el-table-column>
|
|
></el-table-column>
|
|
|
<el-table-column label="操作" width="90" align="center"
|
|
<el-table-column label="操作" width="90" align="center"
|
|
|
><template #default="{ $index }"
|
|
><template #default="{ $index }"
|
|
|
- ><el-button link type="danger" @click="removeSchedule($index)"
|
|
|
|
|
- ><Icon icon="ep:delete" /></el-button></template
|
|
|
|
|
|
|
+ ><el-button link type="danger" @click="removeSchedule($index)">
|
|
|
|
|
+ <Icon icon="ep:delete" /> </el-button></template
|
|
|
></el-table-column>
|
|
></el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
|
</div>
|
|
</div>
|
|
@@ -252,30 +267,33 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<el-divider content-position="left">附件上传</el-divider>
|
|
<el-divider content-position="left">附件上传</el-divider>
|
|
|
- <el-form-item label="立项申请表" required
|
|
|
|
|
- ><UploadFile
|
|
|
|
|
- v-model="formData.attachments.application"
|
|
|
|
|
|
|
+ <el-form-item label="立项申请表" required>
|
|
|
|
|
+ <UploadFile
|
|
|
|
|
+ v-model="formData.applicationFile"
|
|
|
:return-object="true"
|
|
:return-object="true"
|
|
|
:auto-upload="false"
|
|
:auto-upload="false"
|
|
|
:limit="1"
|
|
:limit="1"
|
|
|
:file-type="['doc', 'docx', 'pdf']"
|
|
:file-type="['doc', 'docx', 'pdf']"
|
|
|
- /></el-form-item>
|
|
|
|
|
- <el-form-item label="开题报告" required
|
|
|
|
|
- ><UploadFile
|
|
|
|
|
- v-model="formData.attachments.report"
|
|
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="开题报告" required>
|
|
|
|
|
+ <UploadFile
|
|
|
|
|
+ v-model="formData.openingReportFile"
|
|
|
:return-object="true"
|
|
:return-object="true"
|
|
|
:auto-upload="false"
|
|
:auto-upload="false"
|
|
|
:limit="1"
|
|
:limit="1"
|
|
|
:file-type="['doc', 'docx', 'pdf']"
|
|
:file-type="['doc', 'docx', 'pdf']"
|
|
|
- /></el-form-item>
|
|
|
|
|
- <el-form-item label="其他文件"
|
|
|
|
|
- ><UploadFile
|
|
|
|
|
- v-model="formData.attachments.others"
|
|
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="其他文件">
|
|
|
|
|
+ <UploadFile
|
|
|
|
|
+ v-model="formData.otherFile"
|
|
|
:return-object="true"
|
|
:return-object="true"
|
|
|
:auto-upload="false"
|
|
:auto-upload="false"
|
|
|
:limit="3"
|
|
:limit="3"
|
|
|
:file-type="['doc', 'docx', 'pdf', 'xls', 'xlsx', 'jpg', 'png']"
|
|
:file-type="['doc', 'docx', 'pdf', 'xls', 'xlsx', 'jpg', 'png']"
|
|
|
- /></el-form-item>
|
|
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<div
|
|
<div
|
|
@@ -299,30 +317,19 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { computed, reactive, ref } from 'vue'
|
|
|
|
|
|
|
+import { computed, reactive, ref, onMounted } from 'vue'
|
|
|
import { ElMessage, ElMessageBox, type FormRules } from 'element-plus'
|
|
import { ElMessage, ElMessageBox, type FormRules } from 'element-plus'
|
|
|
|
|
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
|
|
|
|
+import {
|
|
|
|
|
+ getProjectPage,
|
|
|
|
|
+ getProject,
|
|
|
|
|
+ createProject,
|
|
|
|
|
+ updateProject,
|
|
|
|
|
+ deleteProject
|
|
|
|
|
+} from '@/api/project'
|
|
|
|
|
|
|
|
defineOptions({ name: 'ResearchProjectList' })
|
|
defineOptions({ name: 'ResearchProjectList' })
|
|
|
-type ScheduleItem = { workPlan: string; deadline: string }
|
|
|
|
|
-type BudgetDetail = { subject: string; amount: number; remark: string }
|
|
|
|
|
-type Project = {
|
|
|
|
|
- id: number
|
|
|
|
|
- projectLevel: string
|
|
|
|
|
- executingUnit: string
|
|
|
|
|
- projectName: string
|
|
|
|
|
- projectLeader: string
|
|
|
|
|
- projectMembers: string
|
|
|
|
|
- projectType: string
|
|
|
|
|
- purpose: string
|
|
|
|
|
- researchContent: string
|
|
|
|
|
- innovation: string
|
|
|
|
|
- schedule: ScheduleItem[]
|
|
|
|
|
- budgetDetails: BudgetDetail[]
|
|
|
|
|
- budget: Record<string, number>
|
|
|
|
|
- attachments: { application: any[]; report: any[]; others: any[] }
|
|
|
|
|
- status: string
|
|
|
|
|
-}
|
|
|
|
|
-const projectLevels = ['专业公司级/石油技术公司级', '集团公司级', '国家级']
|
|
|
|
|
|
|
+
|
|
|
const projectTypes = [
|
|
const projectTypes = [
|
|
|
'基础研究类',
|
|
'基础研究类',
|
|
|
'原理方法类',
|
|
'原理方法类',
|
|
@@ -346,163 +353,165 @@ const budgetItems = [
|
|
|
{ key: 'technical', label: '技术服务' },
|
|
{ key: 'technical', label: '技术服务' },
|
|
|
{ key: 'other', label: '其他' }
|
|
{ key: 'other', label: '其他' }
|
|
|
]
|
|
]
|
|
|
|
|
+
|
|
|
|
|
+// ========== 类型 ==========
|
|
|
|
|
+type ScheduleItem = { workPlan: string; deadline: string }
|
|
|
|
|
+type BudgetDetail = { financialSubject: string; budgetAmount: number; budgetDescription: string }
|
|
|
|
|
+
|
|
|
|
|
+// ========== 列表与查询 ==========
|
|
|
|
|
+const list = ref<any[]>([])
|
|
|
|
|
+const total = ref(0)
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const queryFormRef = ref()
|
|
|
|
|
+const queryParams = reactive({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ projectName: '',
|
|
|
|
|
+ projectType: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const getList = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await getProjectPage(queryParams)
|
|
|
|
|
+ list.value = data.list
|
|
|
|
|
+ total.value = data.total
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+onMounted(getList)
|
|
|
|
|
+
|
|
|
|
|
+const handleQuery = () => {
|
|
|
|
|
+ queryParams.pageNo = 1
|
|
|
|
|
+ getList()
|
|
|
|
|
+}
|
|
|
|
|
+const resetQuery = () => {
|
|
|
|
|
+ queryFormRef.value?.resetFields()
|
|
|
|
|
+ handleQuery()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ========== 表单 ==========
|
|
|
|
|
+const dialogVisible = ref(false)
|
|
|
|
|
+const dialogTitle = ref('新增科研项目')
|
|
|
|
|
+const formLoading = ref(false)
|
|
|
|
|
+const submitLoading = ref(false)
|
|
|
|
|
+const formRef = ref()
|
|
|
|
|
+const editingId = ref<number>()
|
|
|
|
|
+
|
|
|
const createSchedule = (): ScheduleItem => ({ workPlan: '', deadline: '' })
|
|
const createSchedule = (): ScheduleItem => ({ workPlan: '', deadline: '' })
|
|
|
-const createBudgetDetail = (data: Partial<BudgetDetail> = {}): BudgetDetail => ({
|
|
|
|
|
- subject: '',
|
|
|
|
|
- amount: 0,
|
|
|
|
|
- remark: '',
|
|
|
|
|
- ...data
|
|
|
|
|
|
|
+const createBudgetDetail = (): BudgetDetail => ({
|
|
|
|
|
+ financialSubject: '',
|
|
|
|
|
+ budgetAmount: 0,
|
|
|
|
|
+ budgetDescription: ''
|
|
|
})
|
|
})
|
|
|
-const createBudget = () =>
|
|
|
|
|
- Object.fromEntries(budgetItems.map(({ key }) => [key, 0])) as Record<string, number>
|
|
|
|
|
|
|
+
|
|
|
const createForm = () => ({
|
|
const createForm = () => ({
|
|
|
|
|
+ deptId: '',
|
|
|
projectLevel: '',
|
|
projectLevel: '',
|
|
|
executingUnit: '',
|
|
executingUnit: '',
|
|
|
projectName: '',
|
|
projectName: '',
|
|
|
projectLeader: '',
|
|
projectLeader: '',
|
|
|
projectMembers: '',
|
|
projectMembers: '',
|
|
|
projectType: '',
|
|
projectType: '',
|
|
|
|
|
+ goalAndSignificance: '',
|
|
|
|
|
+ mainResearchContent: '',
|
|
|
|
|
+ innovationPoint: '',
|
|
|
|
|
+ applicationFile: '',
|
|
|
|
|
+ openingReportFile: '',
|
|
|
|
|
+ otherFile: '',
|
|
|
|
|
+ status: 0,
|
|
|
|
|
+ remark: '',
|
|
|
|
|
+ scheduleList: [createSchedule()],
|
|
|
|
|
+ budgetList: [createBudgetDetail()],
|
|
|
purpose: '',
|
|
purpose: '',
|
|
|
researchContent: '',
|
|
researchContent: '',
|
|
|
innovation: '',
|
|
innovation: '',
|
|
|
- schedule: [createSchedule(), createSchedule(), createSchedule()],
|
|
|
|
|
- budgetDetails: [createBudgetDetail()],
|
|
|
|
|
- budget: createBudget(),
|
|
|
|
|
- attachments: { application: [], report: [], others: [] }
|
|
|
|
|
|
|
+ schedule: ''
|
|
|
})
|
|
})
|
|
|
-const list = ref<Project[]>([
|
|
|
|
|
- {
|
|
|
|
|
- id: 1,
|
|
|
|
|
- projectLevel: '专业公司级/石油技术公司级',
|
|
|
|
|
- executingUnit: '瑞恒兴仪 / 四川瑞都',
|
|
|
|
|
- projectName: '高温高压井下工具可靠性研究',
|
|
|
|
|
- projectLeader: '张明远',
|
|
|
|
|
- projectMembers: '李强,王璐',
|
|
|
|
|
- projectType: '产品、工具和设备研发类',
|
|
|
|
|
- purpose: '提升复杂工况下工具运行可靠性。',
|
|
|
|
|
- researchContent: '开展材料、结构及工艺适应性研究。',
|
|
|
|
|
- innovation: '建立高温高压工况下的评价方法。',
|
|
|
|
|
- schedule: [
|
|
|
|
|
- { workPlan: '完成方案设计', deadline: '2026-06-30' },
|
|
|
|
|
- { workPlan: '完成样机试制', deadline: '2026-10-31' }
|
|
|
|
|
- ],
|
|
|
|
|
- budgetDetails: [
|
|
|
|
|
- createBudgetDetail({ subject: '直接材料投入', amount: 180000, remark: '样机材料及加工费用' }),
|
|
|
|
|
- createBudgetDetail({ subject: '检验费', amount: 65000, remark: '高温高压环境测试' }),
|
|
|
|
|
- createBudgetDetail({ subject: '差旅费', amount: 30000, remark: '现场调研与试验支持' })
|
|
|
|
|
- ],
|
|
|
|
|
- budget: createBudget(),
|
|
|
|
|
- attachments: { application: [], report: [], others: [] },
|
|
|
|
|
- status: '进行中'
|
|
|
|
|
- }
|
|
|
|
|
-])
|
|
|
|
|
-const loading = ref(false)
|
|
|
|
|
-const queryFormRef = ref()
|
|
|
|
|
-const queryParams = reactive({ pageNo: 1, pageSize: 10, projectName: '', projectType: '' })
|
|
|
|
|
-const filteredList = computed(() =>
|
|
|
|
|
- list.value.filter(
|
|
|
|
|
- (item) =>
|
|
|
|
|
- (!queryParams.projectName || item.projectName.includes(queryParams.projectName)) &&
|
|
|
|
|
- (!queryParams.projectType || item.projectType === queryParams.projectType)
|
|
|
|
|
- )
|
|
|
|
|
-)
|
|
|
|
|
-const dialogVisible = ref(false)
|
|
|
|
|
-const dialogTitle = ref('新增科研项目')
|
|
|
|
|
-const formLoading = ref(false)
|
|
|
|
|
-const submitLoading = ref(false)
|
|
|
|
|
-const formRef = ref()
|
|
|
|
|
-const editingId = ref<number>()
|
|
|
|
|
|
|
+
|
|
|
const formData = ref(createForm())
|
|
const formData = ref(createForm())
|
|
|
|
|
+
|
|
|
const formRules = reactive<FormRules>({
|
|
const formRules = reactive<FormRules>({
|
|
|
projectLevel: [{ required: true, message: '请选择项目级别', trigger: 'change' }],
|
|
projectLevel: [{ required: true, message: '请选择项目级别', trigger: 'change' }],
|
|
|
executingUnit: [{ required: true, message: '请输入执行单位', trigger: 'blur' }],
|
|
executingUnit: [{ required: true, message: '请输入执行单位', trigger: 'blur' }],
|
|
|
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
|
|
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
|
|
|
projectLeader: [{ required: true, message: '请输入项目负责人', trigger: 'blur' }],
|
|
projectLeader: [{ required: true, message: '请输入项目负责人', trigger: 'blur' }],
|
|
|
projectType: [{ required: true, message: '请选择项目类型', trigger: 'change' }],
|
|
projectType: [{ required: true, message: '请选择项目类型', trigger: 'change' }],
|
|
|
- purpose: [{ required: true, message: '请输入目标和意义', trigger: 'blur' }],
|
|
|
|
|
- researchContent: [{ required: true, message: '请输入主要研究内容', trigger: 'blur' }],
|
|
|
|
|
- innovation: [{ required: true, message: '请输入创新点', trigger: 'blur' }]
|
|
|
|
|
|
|
+ goalAndSignificance: [{ required: true, message: '请输入目标和意义', trigger: 'blur' }],
|
|
|
|
|
+ mainResearchContent: [{ required: true, message: '请输入主要研究内容', trigger: 'blur' }],
|
|
|
|
|
+ innovationPoint: [{ required: true, message: '请输入创新点', trigger: 'blur' }]
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
const budgetTotal = computed(() =>
|
|
const budgetTotal = computed(() =>
|
|
|
- formData.value.budgetDetails.reduce((sum, item) => sum + Number(item.amount || 0), 0)
|
|
|
|
|
|
|
+ formData.value.budgetList.reduce((sum, item) => sum + Number(item.budgetAmount || 0), 0)
|
|
|
)
|
|
)
|
|
|
-const handleQuery = () => {
|
|
|
|
|
- queryParams.pageNo = 1
|
|
|
|
|
-}
|
|
|
|
|
-const resetQuery = () => {
|
|
|
|
|
- queryFormRef.value?.resetFields()
|
|
|
|
|
- handleQuery()
|
|
|
|
|
-}
|
|
|
|
|
-const cloneAttachments = (attachments: Project['attachments']) => ({
|
|
|
|
|
- application:
|
|
|
|
|
- attachments?.application?.map((file) =>
|
|
|
|
|
- file && typeof file === 'object' ? { ...file } : file
|
|
|
|
|
- ) || [],
|
|
|
|
|
- report:
|
|
|
|
|
- attachments?.report?.map((file) => (file && typeof file === 'object' ? { ...file } : file)) ||
|
|
|
|
|
- [],
|
|
|
|
|
- others:
|
|
|
|
|
- attachments?.others?.map((file) => (file && typeof file === 'object' ? { ...file } : file)) ||
|
|
|
|
|
- []
|
|
|
|
|
-})
|
|
|
|
|
-const cloneProjectForm = (project: Project) => ({
|
|
|
|
|
- projectLevel: project.projectLevel,
|
|
|
|
|
- executingUnit: project.executingUnit,
|
|
|
|
|
- projectName: project.projectName,
|
|
|
|
|
- projectLeader: project.projectLeader,
|
|
|
|
|
- projectMembers: project.projectMembers,
|
|
|
|
|
- projectType: project.projectType,
|
|
|
|
|
- purpose: project.purpose,
|
|
|
|
|
- researchContent: project.researchContent,
|
|
|
|
|
- innovation: project.innovation,
|
|
|
|
|
- schedule: project.schedule.map((item) => ({ ...item })),
|
|
|
|
|
- budgetDetails:
|
|
|
|
|
- project.budgetDetails?.length > 0
|
|
|
|
|
- ? project.budgetDetails.map((item) => ({ ...item }))
|
|
|
|
|
- : budgetItems
|
|
|
|
|
- .filter(({ key }) => Number(project.budget?.[key] || 0) > 0)
|
|
|
|
|
- .map(({ label, key }) =>
|
|
|
|
|
- createBudgetDetail({ subject: label, amount: project.budget[key] })
|
|
|
|
|
- ),
|
|
|
|
|
- budget: { ...project.budget },
|
|
|
|
|
- attachments: cloneAttachments(project.attachments)
|
|
|
|
|
-})
|
|
|
|
|
-const openForm = (project?: Project) => {
|
|
|
|
|
|
|
+
|
|
|
|
|
+// ========== 打开表单 ==========
|
|
|
|
|
+const openForm = async (project?: any) => {
|
|
|
|
|
+ dialogVisible.value = true
|
|
|
editingId.value = project?.id
|
|
editingId.value = project?.id
|
|
|
dialogTitle.value = project ? '编辑科研项目' : '新增科研项目'
|
|
dialogTitle.value = project ? '编辑科研项目' : '新增科研项目'
|
|
|
- formData.value = project ? cloneProjectForm(project) : createForm()
|
|
|
|
|
- dialogVisible.value = true
|
|
|
|
|
|
|
+ formLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (project?.id) {
|
|
|
|
|
+ const data = await getProject(project.id)
|
|
|
|
|
+ formData.value = {
|
|
|
|
|
+ ...createForm(),
|
|
|
|
|
+ ...data,
|
|
|
|
|
+ scheduleList: data.scheduleList?.length ? data.scheduleList : [createSchedule()],
|
|
|
|
|
+ budgetList: data.budgetList?.length ? data.budgetList : [createBudgetDetail()]
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ formData.value = createForm()
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ formLoading.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
-const addSchedule = () => formData.value.schedule.push(createSchedule())
|
|
|
|
|
-const removeSchedule = (index: number) => formData.value.schedule.splice(index, 1)
|
|
|
|
|
-const addBudgetDetail = () => formData.value.budgetDetails.push(createBudgetDetail())
|
|
|
|
|
-const removeBudgetDetail = (index: number) => formData.value.budgetDetails.splice(index, 1)
|
|
|
|
|
|
|
+
|
|
|
|
|
+// ========== 子表增删 ==========
|
|
|
|
|
+const addSchedule = () => formData.value.scheduleList.push(createSchedule())
|
|
|
|
|
+const removeSchedule = (index: number) => formData.value.scheduleList.splice(index, 1)
|
|
|
|
|
+const addBudgetDetail = () => formData.value.budgetList.push(createBudgetDetail())
|
|
|
|
|
+const removeBudgetDetail = (index: number) => formData.value.budgetList.splice(index, 1)
|
|
|
|
|
+
|
|
|
|
|
+// ========== 提交 ==========
|
|
|
const submitForm = async () => {
|
|
const submitForm = async () => {
|
|
|
const valid = await formRef.value?.validate().catch(() => false)
|
|
const valid = await formRef.value?.validate().catch(() => false)
|
|
|
if (!valid) return
|
|
if (!valid) return
|
|
|
- if (formData.value.budgetDetails.some((item) => !item.subject.trim())) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (formData.value.budgetList.some((item) => !item.financialSubject?.trim())) {
|
|
|
ElMessage.warning('请完善预算科目后再保存')
|
|
ElMessage.warning('请完善预算科目后再保存')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ if (formData.value.scheduleList.some((item) => !item.workPlan?.trim() || !item.deadline)) {
|
|
|
|
|
+ ElMessage.warning('请完善进度安排后再保存')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
submitLoading.value = true
|
|
submitLoading.value = true
|
|
|
- const project = {
|
|
|
|
|
- ...formData.value,
|
|
|
|
|
- budget: Object.fromEntries(
|
|
|
|
|
- formData.value.budgetDetails.map((item, index) => [
|
|
|
|
|
- item.subject || `subject_${index}`,
|
|
|
|
|
- item.amount
|
|
|
|
|
- ])
|
|
|
|
|
- ),
|
|
|
|
|
- id: editingId.value || Date.now(),
|
|
|
|
|
- status: '进行中'
|
|
|
|
|
- } as Project
|
|
|
|
|
- const index = list.value.findIndex((item) => item.id === project.id)
|
|
|
|
|
- if (index === -1) list.value.unshift(project)
|
|
|
|
|
- else list.value[index] = project
|
|
|
|
|
- submitLoading.value = false
|
|
|
|
|
- dialogVisible.value = false
|
|
|
|
|
- ElMessage.success('项目已保存')
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 设置 sort(按数组下标)
|
|
|
|
|
+ formData.value.scheduleList.forEach((item, index) => (item.sort = index))
|
|
|
|
|
+ formData.value.budgetList.forEach((item, index) => (item.sort = index))
|
|
|
|
|
+
|
|
|
|
|
+ const payload = { ...formData.value }
|
|
|
|
|
+ if (editingId.value) {
|
|
|
|
|
+ payload.id = editingId.value
|
|
|
|
|
+ await updateProject(payload)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await createProject(payload)
|
|
|
|
|
+ }
|
|
|
|
|
+ ElMessage.success('保存成功')
|
|
|
|
|
+ dialogVisible.value = false
|
|
|
|
|
+ await getList()
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitLoading.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// ========== 删除 ==========
|
|
|
const removeProject = async (id: number) => {
|
|
const removeProject = async (id: number) => {
|
|
|
const confirmed = await ElMessageBox.confirm('确定删除这个科研项目吗?', '提示', {
|
|
const confirmed = await ElMessageBox.confirm('确定删除这个科研项目吗?', '提示', {
|
|
|
type: 'warning',
|
|
type: 'warning',
|
|
@@ -512,8 +521,9 @@ const removeProject = async (id: number) => {
|
|
|
.then(() => true)
|
|
.then(() => true)
|
|
|
.catch(() => false)
|
|
.catch(() => false)
|
|
|
if (confirmed) {
|
|
if (confirmed) {
|
|
|
- list.value = list.value.filter((item) => item.id !== id)
|
|
|
|
|
|
|
+ await deleteProject(id)
|
|
|
ElMessage.success('删除成功')
|
|
ElMessage.success('删除成功')
|
|
|
|
|
+ await getList()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
@@ -522,31 +532,38 @@ const removeProject = async (id: number) => {
|
|
|
.project-filter {
|
|
.project-filter {
|
|
|
margin-bottom: -15px;
|
|
margin-bottom: -15px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.project-table-heading {
|
|
.project-table-heading {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
margin-bottom: 16px;
|
|
margin-bottom: 16px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.project-table-heading h2 {
|
|
.project-table-heading h2 {
|
|
|
margin: 0 0 4px;
|
|
margin: 0 0 4px;
|
|
|
color: var(--el-text-color-primary);
|
|
color: var(--el-text-color-primary);
|
|
|
font-size: 18px;
|
|
font-size: 18px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.project-table-heading span {
|
|
.project-table-heading span {
|
|
|
color: var(--el-text-color-secondary);
|
|
color: var(--el-text-color-secondary);
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.project-form {
|
|
.project-form {
|
|
|
padding: 0 8px;
|
|
padding: 0 8px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.project-form :deep(.el-divider__text) {
|
|
.project-form :deep(.el-divider__text) {
|
|
|
color: var(--el-color-primary);
|
|
color: var(--el-color-primary);
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.schedule-editor {
|
|
.schedule-editor {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.schedule-toolbar {
|
|
.schedule-toolbar {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
@@ -555,9 +572,11 @@ const removeProject = async (id: number) => {
|
|
|
color: var(--el-text-color-secondary);
|
|
color: var(--el-text-color-secondary);
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.budget-editor {
|
|
.budget-editor {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.budget-toolbar {
|
|
.budget-toolbar {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
@@ -566,9 +585,11 @@ const removeProject = async (id: number) => {
|
|
|
color: var(--el-text-color-secondary);
|
|
color: var(--el-text-color-secondary);
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.budget-table {
|
|
.budget-table {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.budget-total {
|
|
.budget-total {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: flex-end;
|
|
justify-content: flex-end;
|
|
@@ -577,18 +598,22 @@ const removeProject = async (id: number) => {
|
|
|
padding: 14px 8px 0;
|
|
padding: 14px 8px 0;
|
|
|
color: var(--el-text-color-secondary);
|
|
color: var(--el-text-color-secondary);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.budget-total strong {
|
|
.budget-total strong {
|
|
|
color: var(--el-color-primary);
|
|
color: var(--el-color-primary);
|
|
|
font-size: 22px;
|
|
font-size: 22px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
@media (max-width: 900px) {
|
|
@media (max-width: 900px) {
|
|
|
.budget-grid :deep(.el-col) {
|
|
.budget-grid :deep(.el-col) {
|
|
|
width: 50%;
|
|
width: 50%;
|
|
|
flex: 0 0 50%;
|
|
flex: 0 0 50%;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.budget-table {
|
|
.budget-table {
|
|
|
min-width: 760px;
|
|
min-width: 760px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.budget-editor {
|
|
.budget-editor {
|
|
|
overflow-x: auto;
|
|
overflow-x: auto;
|
|
|
}
|
|
}
|