yanghao 2 недель назад
Родитель
Сommit
9bf0eb2154
1 измененных файлов с 504 добавлено и 2 удалено
  1. 504 2
      src/views/researchProject/progress/index.vue

+ 504 - 2
src/views/researchProject/progress/index.vue

@@ -1,5 +1,507 @@
 <template>
-  <div> ssssssssss </div>
+  <ContentWrap>
+    <el-form ref="queryFormRef" :model="queryParams" inline class="project-filter">
+      <el-form-item label="项目名称" prop="projectName">
+        <el-input
+          v-model="queryParams.projectName"
+          clearable
+          placeholder="请输入项目名称"
+          class="!w-220px"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="项目类型" prop="projectType">
+        <el-select
+          v-model="queryParams.projectType"
+          clearable
+          placeholder="请选择项目类型"
+          class="!w-220px"
+        >
+          <el-option v-for="item in projectTypes" :key="item" :label="item" :value="item" />
+        </el-select>
+      </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-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <ContentWrap>
+    <div class="project-table-heading">
+      <div><h2>科研项目</h2><span>维护项目申报信息、预算及阶段性计划</span></div>
+      <el-tag type="info" effect="plain">共 {{ filteredList.length }} 个项目</el-tag>
+    </div>
+    <zm-table :loading="loading" :data="filteredList" row-key="id">
+      <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="executingUnit"
+        min-width="210"
+        show-overflow-tooltip
+      />
+      <zm-table-column label="项目负责人" prop="projectLeader" width="130" />
+      <zm-table-column label="项目类型" prop="projectType" min-width="180" />
+      <zm-table-column label="状态" width="110" align="center"
+        ><template #default="{ row }"
+          ><el-tag :type="row.status === '进行中' ? 'success' : 'info'">{{
+            row.status
+          }}</el-tag></template
+        ></zm-table-column
+      >
+      <zm-table-column label="操作" width="120" fixed="right" align="center"
+        ><template #default="{ row }">
+          <el-button link type="primary" @click="openForm(row)">填报</el-button></template
+        ></zm-table-column
+      >
+    </zm-table>
+    <Pagination
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      :total="filteredList.length"
+    />
+  </ContentWrap>
+
+  <ResearchProjectDialog
+    v-model="dialogVisible"
+    :title="dialogTitle"
+    width="100%"
+    :fullscreen="true"
+    destroy-on-close
+  >
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="112px"
+      class="project-form"
+    >
+      <el-divider content-position="left">项目基本信息</el-divider>
+      <el-row :gutter="24">
+        <el-col :span="12"
+          ><el-form-item label="项目级别" prop="projectLevel"
+            ><el-select
+              disabled
+              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-col :span="12"
+          ><el-form-item label="执行单位" prop="executingUnit"
+            ><el-input
+              disabled
+              v-model="formData.executingUnit"
+              placeholder="请输入执行单位" /></el-form-item
+        ></el-col>
+        <el-col :span="12"
+          ><el-form-item label="项目名称" prop="projectName"
+            ><el-input
+              disabled
+              v-model="formData.projectName"
+              placeholder="请输入项目名称" /></el-form-item
+        ></el-col>
+        <el-col :span="12"
+          ><el-form-item label="项目负责人" prop="projectLeader"
+            ><el-input
+              disabled
+              v-model="formData.projectLeader"
+              placeholder="请输入项目负责人" /></el-form-item
+        ></el-col>
+        <el-col :span="12"
+          ><el-form-item label="项目组成员"
+            ><el-input
+              disabled
+              v-model="formData.projectMembers"
+              placeholder="请输入成员姓名,多个成员用逗号分隔" /></el-form-item
+        ></el-col>
+        <el-col :span="12"
+          ><el-form-item label="项目类型" prop="projectType"
+            ><el-select
+              disabled
+              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-col>
+      </el-row>
+      <el-divider content-position="left">项目内容</el-divider>
+      <el-form-item label="目标和意义" prop="purpose"
+        ><el-input
+          disabled
+          v-model="formData.purpose"
+          type="textarea"
+          :rows="3"
+          maxlength="500"
+          show-word-limit
+          placeholder="请输入项目目标和意义"
+      /></el-form-item>
+      <el-form-item label="主要研究内容" prop="researchContent"
+        ><el-input
+          disabled
+          v-model="formData.researchContent"
+          type="textarea"
+          :rows="4"
+          maxlength="2000"
+          show-word-limit
+          placeholder="请输入主要研究内容"
+      /></el-form-item>
+      <el-form-item label="创新点" prop="innovation"
+        ><el-input
+          disabled
+          v-model="formData.innovation"
+          type="textarea"
+          :rows="3"
+          maxlength="500"
+          show-word-limit
+          placeholder="请输入项目创新点"
+      /></el-form-item>
+      <el-form-item label="进度安排" prop="schedule">
+        <div class="schedule-editor">
+          <div class="schedule-toolbar"><span>按阶段填写工作计划与截止时间</span></div>
+          <el-table :data="formData.schedule" border class="schedule-table">
+            <el-table-column type="index" label="#" width="56" align="center" />
+            <el-table-column label="工作计划" min-width="300"
+              ><template #default="{ row }"
+                ><el-input disabled v-model="row.workPlan" placeholder="请输入工作计划" /></template
+            ></el-table-column>
+            <el-table-column label="截止时间" width="190"
+              ><template #default="{ row }"
+                ><el-date-picker
+                  disabled
+                  v-model="row.deadline"
+                  type="date"
+                  value-format="YYYY-MM-DD"
+                  placeholder="选择日期"
+                  class="w-full" /></template
+            ></el-table-column>
+            <el-table-column label="实际完成情况" min-width="300"
+              ><template #default="{ row }"
+                ><el-input v-model="row.result" placeholder="请输入实际完成情况" /></template
+            ></el-table-column>
+            <!-- <el-table-column label="操作" width="90" align="center"
+              ><template #default="{ $index }"
+                ><el-button link type="danger" @click="removeSchedule($index)"
+                  ><Icon icon="ep:delete" /></el-button></template
+            ></el-table-column> -->
+          </el-table>
+        </div>
+      </el-form-item>
+      <el-divider content-position="left">费用预算(元)</el-divider>
+      <el-row :gutter="18" class="budget-grid"
+        ><el-col v-for="item in budgetItems" :key="item.key" :span="6"
+          ><el-form-item :label="item.label"
+            ><el-input-number
+              v-model="formData.budget[item.key]"
+              :min="0"
+              :precision="2"
+              controls-position="right"
+              class="w-full" /></el-form-item></el-col
+      ></el-row>
+      <div class="budget-total"
+        ><span>预算合计</span><strong>{{ budgetTotal.toFixed(2) }}</strong
+        ><span>元</span></div
+      >
+      <el-divider content-position="left">附件上传</el-divider>
+      <el-form-item label="立项申请表" required
+        ><UploadFile
+          v-model="formData.attachments.application"
+          :return-object="true"
+          :auto-upload="false"
+          :limit="1"
+          :file-type="['doc', 'docx', 'pdf']"
+      /></el-form-item>
+      <el-form-item label="开题报告" required
+        ><UploadFile
+          v-model="formData.attachments.report"
+          :return-object="true"
+          :auto-upload="false"
+          :limit="1"
+          :file-type="['doc', 'docx', 'pdf']"
+      /></el-form-item>
+      <el-form-item label="其他文件"
+        ><UploadFile
+          v-model="formData.attachments.others"
+          :return-object="true"
+          :auto-upload="false"
+          :limit="3"
+          :file-type="['doc', 'docx', 'pdf', 'xls', 'xlsx', 'jpg', 'png']"
+      /></el-form-item>
+    </el-form>
+    <template #footer>
+      <div
+        style="
+          position: fixed;
+          bottom: 0;
+          left: 0;
+          right: 0;
+          background: #fff;
+          padding: 10px;
+          z-index: 100;
+          /* border-top: 1px solid #ccc; */
+          box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
+        "
+      >
+        <el-button @click="dialogVisible = false">取消</el-button
+        ><el-button type="primary" :loading="submitLoading" @click="submitForm">保存项目</el-button>
+      </div>
+    </template>
+  </ResearchProjectDialog>
 </template>
 
-<script lang="ts" setup></script>
+<script setup lang="ts">
+import { computed, reactive, ref } from 'vue'
+import { ElMessage, ElMessageBox, type FormRules } from 'element-plus'
+
+defineOptions({ name: 'ResearchProjectList' })
+type ScheduleItem = { workPlan: string; deadline: 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[]
+  budget: Record<string, number>
+  attachments: { application: any[]; report: any[]; others: any[] }
+  status: string
+}
+const projectLevels = ['专业公司级/石油技术公司级', '集团公司级', '国家级']
+const projectTypes = [
+  '基础研究类',
+  '原理方法类',
+  '产品、工具和设备研发类',
+  '成熟工艺技术配套及推广类',
+  '情报调研类'
+]
+const budgetItems = [
+  { key: 'labor', label: '人工' },
+  { key: 'materials', label: '直接材料投入' },
+  { key: 'supplies', label: '劳保用品' },
+  { key: 'fuel', label: '燃料' },
+  { key: 'waterElectricity', label: '水电费' },
+  { key: 'inspection', label: '检验费' },
+  { key: 'transport', label: '搬运运输费' },
+  { key: 'depreciation', label: '折旧' },
+  { key: 'rental', label: '租赁' },
+  { key: 'design', label: '设计费' },
+  { key: 'recruitment', label: '招待费' },
+  { key: 'travel', label: '差旅费' },
+  { key: 'technical', label: '技术服务' },
+  { key: 'other', label: '其他' }
+]
+const createSchedule = (): ScheduleItem => ({ workPlan: '', deadline: '' })
+const createBudget = () =>
+  Object.fromEntries(budgetItems.map(({ key }) => [key, 0])) as Record<string, number>
+const createForm = () => ({
+  projectLevel: '',
+  executingUnit: '',
+  projectName: '',
+  projectLeader: '',
+  projectMembers: '',
+  projectType: '',
+  purpose: '',
+  researchContent: '',
+  innovation: '',
+  schedule: [createSchedule(), createSchedule(), createSchedule()],
+  budget: createBudget(),
+  attachments: { application: [], report: [], others: [] }
+})
+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' }
+    ],
+    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 formRules = reactive<FormRules>({
+  projectLevel: [{ required: true, message: '请选择项目级别', trigger: 'change' }],
+  executingUnit: [{ required: true, message: '请输入执行单位', trigger: 'blur' }],
+  projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
+  projectLeader: [{ required: true, message: '请输入项目负责人', trigger: 'blur' }],
+  projectType: [{ required: true, message: '请选择项目类型', trigger: 'change' }],
+  purpose: [{ required: true, message: '请输入目标和意义', trigger: 'blur' }],
+  researchContent: [{ required: true, message: '请输入主要研究内容', trigger: 'blur' }],
+  innovation: [{ required: true, message: '请输入创新点', trigger: 'blur' }]
+})
+const budgetTotal = computed(() =>
+  Object.values(formData.value.budget).reduce((sum, value) => sum + Number(value || 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 })),
+  budget: { ...project.budget },
+  attachments: cloneAttachments(project.attachments)
+})
+const openForm = (project?: Project) => {
+  editingId.value = project?.id
+  dialogTitle.value = project ? '编辑科研项目' : '新增科研项目'
+  formData.value = project ? cloneProjectForm(project) : createForm()
+  dialogVisible.value = true
+}
+const addSchedule = () => formData.value.schedule.push(createSchedule())
+const removeSchedule = (index: number) => formData.value.schedule.splice(index, 1)
+const submitForm = async () => {
+  const valid = await formRef.value?.validate().catch(() => false)
+  if (!valid) return
+  submitLoading.value = true
+  const project = {
+    ...formData.value,
+    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('项目已保存')
+}
+const removeProject = async (id: number) => {
+  const confirmed = await ElMessageBox.confirm('确定删除这个科研项目吗?', '提示', {
+    type: 'warning',
+    confirmButtonText: '确定',
+    cancelButtonText: '取消'
+  })
+    .then(() => true)
+    .catch(() => false)
+  if (confirmed) {
+    list.value = list.value.filter((item) => item.id !== id)
+    ElMessage.success('删除成功')
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.project-filter {
+  margin-bottom: -15px;
+}
+.project-table-heading {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+}
+.project-table-heading h2 {
+  margin: 0 0 4px;
+  color: var(--el-text-color-primary);
+  font-size: 18px;
+}
+.project-table-heading span {
+  color: var(--el-text-color-secondary);
+  font-size: 13px;
+}
+.project-form {
+  padding: 0 8px;
+}
+.project-form :deep(.el-divider__text) {
+  color: var(--el-color-primary);
+  font-weight: 600;
+}
+.schedule-editor {
+  width: 100%;
+}
+.schedule-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 10px;
+  color: var(--el-text-color-secondary);
+  font-size: 13px;
+}
+.budget-total {
+  display: flex;
+  align-items: baseline;
+  justify-content: flex-end;
+  gap: 8px;
+  padding: 12px 8px;
+  color: var(--el-text-color-secondary);
+  border-top: 1px solid var(--el-border-color-lighter);
+}
+.budget-total strong {
+  color: var(--el-color-danger);
+  font-size: 20px;
+  font-variant-numeric: tabular-nums;
+}
+@media (max-width: 900px) {
+  .budget-grid :deep(.el-col) {
+    width: 50%;
+    flex: 0 0 50%;
+  }
+}
+</style>