Ver Fonte

feat(pms): 新增瑞鹰设备整改日报管理页面

- 新增瑞鹰设备整改日报 API 封装
  - 支持分页查询、详情查询、新增、编辑、审批接口
  - 定义设备整改日报与审批请求类型

- 新增瑞鹰项目启动设备整改页面
  - 接入分页列表接口
  - 支持按汇报时间范围查询
  - 使用 ZmTable 展示日报数据
  - 增加审批状态列并使用 el-tag 显示状态
  - 支持查看、编辑、审批操作
  - 审核通过后禁止编辑

- 新增设备整改日报表单组件
  - 支持新建、编辑、查看、审批共用
  - 新建/编辑提交逻辑内聚在表单组件中
  - 查看/编辑/审批时通过详情接口回填数据
  - 支持审批通过、审批不通过及审批意见填写
  - 查看模式字段只读,编辑模式审批意见只读,审批模式主字段只读

- 更新忽略规则
  - 忽略 pnpm-workspace.yaml
Zimo há 5 dias atrás
pai
commit
3aa651ed73

+ 1 - 0
.gitignore

@@ -7,3 +7,4 @@ pnpm-debug
 auto-*.d.ts
 .idea
 .history
+pnpm-workspace.yaml

+ 43 - 0
src/api/pms/iotryimprovedailyreport/index.ts

@@ -0,0 +1,43 @@
+import request from '@/config/axios'
+
+export interface IotRyImproveDailyReportVO {
+  id?: number
+  title: string
+  createTime: number
+  workLocation: string
+  workPurpose: string
+  relocationDays: number
+  productionStatus: string
+  personnel: string
+  nextPlan: string
+  auditStatus?: 0 | 10 | 20 | 30 | 40
+  opinion?: string
+}
+
+export interface IotRyImproveDailyReportApprovalVO {
+  id: number
+  auditStatus: 20 | 30
+  opinion?: string
+}
+
+export const IotRyImproveDailyReportApi = {
+  getIotRyImproveDailyReportPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-ry-improve-daily-report/page`, params })
+  },
+
+  getIotRyImproveDailyReport: async (id: number) => {
+    return await request.get({ url: `/pms/iot-ry-improve-daily-report/get?id=` + id })
+  },
+
+  createIotRyImproveDailyReport: async (data: IotRyImproveDailyReportVO) => {
+    return await request.post({ url: `/pms/iot-ry-improve-daily-report/create`, data })
+  },
+
+  updateIotRyImproveDailyReport: async (data: IotRyImproveDailyReportVO) => {
+    return await request.put({ url: `/pms/iot-ry-improve-daily-report/update`, data })
+  },
+
+  approvalIotRyImproveDailyReport: async (data: IotRyImproveDailyReportApprovalVO) => {
+    return await request.put({ url: `/pms/iot-ry-improve-daily-report/approval`, data })
+  }
+}

+ 369 - 0
src/views/pms/iotrydailyreport/components/equipment-form.vue

@@ -0,0 +1,369 @@
+<script setup lang="ts">
+import { IotRyImproveDailyReportApi } from '@/api/pms/iotryimprovedailyreport'
+import { FormInstance, FormRules } from 'element-plus'
+import { Close } from '@element-plus/icons-vue'
+
+defineOptions({ name: 'IotRyEquipmentReportForm' })
+
+type FormMode = 'create' | 'edit' | 'detail' | 'approval'
+
+interface EquipmentReportForm {
+  id?: number
+  title: string
+  createTime: number | undefined
+  workLocation: string
+  workPurpose: string
+  relocationDays: number | undefined
+  productionStatus: string
+  nextPlan: string
+  personnel: string
+  auditStatus?: 0 | 10 | 20 | 30 | 40
+  opinion?: string
+}
+
+interface Props {
+  visible?: boolean
+  loadList: () => void | Promise<void>
+  reloadAfterCreate?: () => void | Promise<void>
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  visible: false
+})
+
+const emits = defineEmits<{
+  'update:visible': [value: boolean]
+}>()
+
+const defaultForm: EquipmentReportForm = {
+  title: '',
+  createTime: undefined,
+  workLocation: '',
+  workPurpose: '',
+  relocationDays: undefined,
+  productionStatus: '',
+  nextPlan: '',
+  personnel: '',
+  auditStatus: undefined,
+  opinion: ''
+}
+
+const formRef = ref<FormInstance>()
+const formMode = ref<FormMode>('create')
+const form = ref<EquipmentReportForm>({ ...defaultForm })
+const formLoading = ref(false)
+const detailLoading = ref(false)
+const message = useMessage()
+
+const auditStatusMap = {
+  0: { label: '未提交', type: 'info' },
+  10: { label: '审批中', type: 'warning' },
+  20: { label: '审核通过', type: 'success' },
+  30: { label: '审核不通过', type: 'danger' },
+  40: { label: '已取消', type: 'info' }
+} as const
+
+const rules: FormRules = {
+  title: [{ required: true, message: '请输入标题', trigger: ['blur', 'change'] }],
+  createTime: [{ required: true, message: '请选择汇报时间', trigger: ['blur', 'change'] }],
+  workLocation: [{ required: true, message: '请输入工作地点', trigger: ['blur', 'change'] }],
+  workPurpose: [{ required: true, message: '请输入施工目的', trigger: ['blur', 'change'] }],
+  relocationDays: [{ required: true, message: '请输入安全作业天数', trigger: ['blur', 'change'] }],
+  productionStatus: [{ required: true, message: '请输入施工动态', trigger: ['blur', 'change'] }],
+  nextPlan: [{ required: true, message: '请输入下步计划', trigger: ['blur', 'change'] }],
+  personnel: [{ required: true, message: '请输入人员情况', trigger: ['blur', 'change'] }]
+}
+
+const drawerTitle = computed(() => {
+  const titleMap: Record<FormMode, string> = {
+    create: '新建设备汇报',
+    edit: '编辑设备汇报',
+    detail: '查看设备汇报',
+    approval: '审批设备汇报'
+  }
+
+  return titleMap[formMode.value]
+})
+
+const formDisabled = computed(() => formMode.value === 'detail')
+const mainFieldDisabled = computed(
+  () => formMode.value === 'detail' || formMode.value === 'approval'
+)
+const showAuditInfo = computed(() => formMode.value !== 'create')
+
+function getAuditStatus(status?: number) {
+  return auditStatusMap[status as keyof typeof auditStatusMap] || auditStatusMap[0]
+}
+
+function toFormData(row?: any): EquipmentReportForm {
+  if (!row) return { ...defaultForm }
+
+  return {
+    id: row.id,
+    title: row.title,
+    createTime: row.createTime,
+    workLocation: row.workLocation,
+    workPurpose: row.workPurpose,
+    relocationDays: row.relocationDays,
+    productionStatus: row.productionStatus,
+    nextPlan: row.nextPlan,
+    personnel: row.personnel,
+    auditStatus: row.auditStatus,
+    opinion: row.opinion || ''
+  }
+}
+
+async function handleOpenForm(mode: FormMode = 'create', row?: any) {
+  formMode.value = mode
+  form.value = mode === 'create' ? { ...defaultForm } : toFormData(row)
+  emits('update:visible', true)
+
+  if (mode !== 'create' && row?.id) {
+    detailLoading.value = true
+    try {
+      const detail = await IotRyImproveDailyReportApi.getIotRyImproveDailyReport(row.id)
+      form.value = toFormData(detail)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  nextTick(() => {
+    formRef.value?.clearValidate()
+  })
+}
+
+function handleCloseForm() {
+  emits('update:visible', false)
+}
+
+async function submitForm() {
+  if (!formRef.value || formDisabled.value || formMode.value === 'approval') return
+
+  try {
+    formLoading.value = true
+    await formRef.value.validate()
+
+    const data = { ...form.value, createTime: Number(form.value.createTime) }
+
+    if (data.id) {
+      await IotRyImproveDailyReportApi.updateIotRyImproveDailyReport({
+        id: data.id,
+        title: data.title,
+        createTime: data.createTime,
+        workLocation: data.workLocation,
+        workPurpose: data.workPurpose,
+        relocationDays: Number(data.relocationDays),
+        productionStatus: data.productionStatus,
+        personnel: data.personnel,
+        nextPlan: data.nextPlan
+      })
+      message.success('编辑成功')
+      await props.loadList()
+    } else {
+      await IotRyImproveDailyReportApi.createIotRyImproveDailyReport({
+        title: data.title,
+        createTime: data.createTime,
+        workLocation: data.workLocation,
+        workPurpose: data.workPurpose,
+        relocationDays: Number(data.relocationDays),
+        productionStatus: data.productionStatus,
+        personnel: data.personnel,
+        nextPlan: data.nextPlan
+      })
+      message.success('新增成功')
+      await (props.reloadAfterCreate ? props.reloadAfterCreate() : props.loadList())
+    }
+
+    handleCloseForm()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+async function submitApproval(auditStatus: 20 | 30) {
+  if (!form.value.id) return
+
+  try {
+    formLoading.value = true
+    await IotRyImproveDailyReportApi.approvalIotRyImproveDailyReport({
+      id: form.value.id,
+      auditStatus,
+      opinion: form.value.opinion
+    })
+    message.success(auditStatus === 20 ? '审批通过' : '审批不通过')
+    await props.loadList()
+    handleCloseForm()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+defineExpose({ handleOpenForm })
+</script>
+
+<template>
+  <el-drawer
+    :model-value="visible"
+    @update:model-value="emits('update:visible', $event)"
+    header-class="mb-0!"
+    :with-header="false"
+    size="70%"
+    :close-on-press-escape="false"
+    :close-on-click-modal="false">
+    <div class="size-full flex flex-col gap-4">
+      <div class="flex justify-between items-center">
+        <div class="flex items-center gap-4">
+          <div class="text-xl font-bold text-gray-900 leading-tight">{{ drawerTitle }}</div>
+          <template v-if="showAuditInfo">
+            <el-tag :type="getAuditStatus(form.auditStatus).type">
+              {{ getAuditStatus(form.auditStatus).label }}
+            </el-tag>
+          </template>
+        </div>
+        <el-button link @click="handleCloseForm">
+          <el-icon size="24"><Close /></el-icon>
+        </el-button>
+      </div>
+
+      <el-form
+        ref="formRef"
+        v-loading="detailLoading"
+        size="default"
+        label-position="top"
+        :model="form"
+        require-asterisk-position="right"
+        class="flex flex-col"
+        :rules="rules"
+        :disabled="formDisabled">
+        <div class="grid grid-cols-2 gap-x-8">
+          <el-form-item class="col-span-2" label="标题" prop="title">
+            <el-input
+              v-model="form.title"
+              clearable
+              placeholder="请输入标题"
+              :disabled="mainFieldDisabled" />
+          </el-form-item>
+
+          <el-form-item label="汇报时间" prop="createTime">
+            <el-date-picker
+              v-model="form.createTime"
+              type="date"
+              clearable
+              class="w-full!"
+              placeholder="请选择汇报时间"
+              value-format="x"
+              :disabled="mainFieldDisabled" />
+          </el-form-item>
+
+          <el-form-item label="工作地点" prop="workLocation">
+            <el-input
+              v-model="form.workLocation"
+              clearable
+              placeholder="请输入工作地点"
+              :disabled="mainFieldDisabled" />
+          </el-form-item>
+
+          <el-form-item label="施工目的" prop="workPurpose">
+            <el-input
+              v-model="form.workPurpose"
+              clearable
+              placeholder="请输入施工目的"
+              :disabled="mainFieldDisabled" />
+          </el-form-item>
+
+          <el-form-item label="安全作业天数" prop="relocationDays">
+            <el-input-number
+              v-model="form.relocationDays"
+              :min="0"
+              :controls="false"
+              align="left"
+              class="w-full!"
+              placeholder="请输入安全作业天数"
+              :disabled="mainFieldDisabled">
+              <template #suffix>天</template>
+            </el-input-number>
+          </el-form-item>
+
+          <el-form-item label="人员情况" prop="personnel">
+            <el-input
+              v-model="form.personnel"
+              clearable
+              placeholder="请输入人员情况"
+              :disabled="mainFieldDisabled" />
+          </el-form-item>
+
+          <el-form-item class="col-span-2" label="施工动态" prop="productionStatus">
+            <el-input
+              v-model="form.productionStatus"
+              type="textarea"
+              :autosize="{ minRows: 8 }"
+              resize="none"
+              show-word-limit
+              :maxlength="2000"
+              placeholder="请输入施工动态,每行一条"
+              :disabled="mainFieldDisabled" />
+          </el-form-item>
+
+          <el-form-item class="col-span-2" label="下步计划" prop="nextPlan">
+            <el-input
+              v-model="form.nextPlan"
+              type="textarea"
+              :autosize="{ minRows: 2 }"
+              resize="none"
+              show-word-limit
+              :maxlength="1000"
+              placeholder="请输入下步计划"
+              :disabled="mainFieldDisabled" />
+          </el-form-item>
+
+          <el-form-item v-if="showAuditInfo" class="col-span-2" label="审批意见" prop="opinion">
+            <el-input
+              v-model="form.opinion"
+              type="textarea"
+              :autosize="{ minRows: 3 }"
+              resize="none"
+              show-word-limit
+              :maxlength="1000"
+              :placeholder="formMode === 'approval' ? '请输入审批意见' : '暂无审批意见'"
+              :disabled="formMode !== 'approval'" />
+          </el-form-item>
+        </div>
+      </el-form>
+    </div>
+
+    <template #footer>
+      <el-button
+        v-if="formMode === 'create' || formMode === 'edit'"
+        size="default"
+        type="primary"
+        @click="submitForm"
+        :loading="formLoading">
+        确 定
+      </el-button>
+      <el-button
+        v-if="formMode === 'approval'"
+        size="default"
+        type="success"
+        @click="submitApproval(20)"
+        :loading="formLoading">
+        审批通过
+      </el-button>
+      <el-button
+        v-if="formMode === 'approval'"
+        size="default"
+        type="danger"
+        @click="submitApproval(30)"
+        :loading="formLoading">
+        审批不通过
+      </el-button>
+      <el-button size="default" @click="handleCloseForm">取 消</el-button>
+    </template>
+  </el-drawer>
+</template>
+
+<style scoped>
+:deep(.el-form-item__label) {
+  font-weight: 500;
+}
+</style>

+ 221 - 0
src/views/pms/iotrydailyreport/equipment.vue

@@ -0,0 +1,221 @@
+<script lang="ts" setup>
+import { IotRyImproveDailyReportApi } from '@/api/pms/iotryimprovedailyreport'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+import dayjs from 'dayjs'
+import EquipmentForm from './components/equipment-form.vue'
+
+defineOptions({ name: 'IotRyEquipmentDailyReport' })
+
+interface ReportRow {
+  id: number
+  title: string
+  createTime: number
+  workLocation: string
+  workPurpose: string
+  relocationDays: number
+  productionStatus: string
+  nextPlan: string
+  personnel: string
+  auditStatus: 0 | 10 | 20 | 30 | 40
+}
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  createTime?: string[]
+}
+
+const deptId = ref(158)
+const loading = ref(false)
+const { ZmTable, ZmTableColumn } = useTableComponents<ReportRow>()
+const formRef = ref()
+const formVisible = ref(false)
+const total = ref(0)
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  createTime: undefined
+})
+
+const reportRows = ref<ReportRow[]>([])
+
+async function loadList() {
+  loading.value = true
+  try {
+    const data = await IotRyImproveDailyReportApi.getIotRyImproveDailyReportPage(query.value)
+    reportRows.value = data.list || []
+    total.value = data.total || 0
+  } finally {
+    loading.value = false
+  }
+}
+
+function handleQuery() {
+  query.value.pageNo = 1
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    createTime: undefined
+  }
+  loadList()
+}
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const auditStatusMap = {
+  0: { label: '未提交', type: 'info' },
+  10: { label: '审批中', type: 'warning' },
+  20: { label: '审核通过', type: 'success' },
+  30: { label: '审核不通过', type: 'danger' },
+  40: { label: '已取消', type: 'info' }
+} as const
+
+function getAuditStatus(status?: number) {
+  return auditStatusMap[status as keyof typeof auditStatusMap] || auditStatusMap[0]
+}
+
+function handleOpenForm(type: 'create' | 'edit' | 'detail' | 'approval', row?: ReportRow) {
+  formRef.value?.handleOpenForm(type, row)
+}
+
+function reloadAfterCreate() {
+  query.value.pageNo = 1
+  return loadList()
+}
+
+onMounted(() => {
+  loadList()
+})
+</script>
+
+<template>
+  <div
+    class="grid grid-cols-[auto_1fr] grid-rows-[48px_1fr] gap-x-4 gap-y-3 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]">
+    <DeptTreeSelect
+      :top-id="158"
+      :deptId="deptId"
+      v-model="deptId"
+      :show-title="false"
+      class="row-span-2" />
+
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-6 gap-8 flex items-center justify-between">
+      <div class="flex items-center gap-8">
+        <el-form-item label="汇报时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-260px" />
+        </el-form-item>
+      </div>
+      <el-form-item>
+        <el-button type="primary" @click="handleOpenForm('create')">
+          <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-form-item>
+    </el-form>
+
+    <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4 min-h-0">
+      <div class="flex-1 relative">
+        <el-auto-resizer class="absolute">
+          <template #default="{ width, height }">
+            <zm-table
+              :data="reportRows"
+              :loading="loading"
+              :width="width"
+              :max-height="height"
+              :height="height"
+              empty-text="暂无设备搬迁整改项目汇报"
+              show-border>
+              <zm-table-column type="index" label="序号" width="70" fixed="left" />
+              <zm-table-column label="标题" prop="title" fixed="left" />
+              <zm-table-column label="汇报时间" prop="createTime">
+                <template #default="{ row }">
+                  {{ dayjs(row.createTime).format('YYYY.MM.DD') }}
+                </template>
+              </zm-table-column>
+              <zm-table-column label="工作地点" prop="workLocation" />
+              <zm-table-column label="施工目的" prop="workPurpose" />
+              <zm-table-column
+                label="安全作业天数"
+                prop="relocationDays"
+                :real-value="(row) => row.relocationDays + '天'"
+                cover-formatter />
+              <zm-table-column label="施工动态" prop="productionStatus" />
+              <zm-table-column label="下步计划" prop="nextPlan" />
+              <zm-table-column label="人员情况" prop="personnel" />
+              <zm-table-column label="审批状态" prop="auditStatus" width="120">
+                <template #default="{ row }">
+                  <el-tag :type="getAuditStatus(row.auditStatus).type">
+                    {{ getAuditStatus(row.auditStatus).label }}
+                  </el-tag>
+                </template>
+              </zm-table-column>
+              <zm-table-column label="操作" width="160" fixed="right" action>
+                <template #default="{ row }">
+                  <el-button link type="primary" @click="handleOpenForm('detail', row)">
+                    查看
+                  </el-button>
+                  <el-button
+                    v-if="row.auditStatus !== 20"
+                    link
+                    type="primary"
+                    @click="handleOpenForm('edit', row)">
+                    编辑
+                  </el-button>
+                  <el-button link type="primary" @click="handleOpenForm('approval', row)">
+                    审批
+                  </el-button>
+                </template>
+              </zm-table-column>
+            </zm-table>
+          </template>
+        </el-auto-resizer>
+      </div>
+      <div class="mt-3 flex justify-end">
+        <el-pagination
+          v-model:current-page="query.pageNo"
+          v-model:page-size="query.pageSize"
+          :total="total"
+          :page-sizes="[10, 20, 30, 50, 100]"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange" />
+      </div>
+    </div>
+  </div>
+
+  <EquipmentForm
+    ref="formRef"
+    v-model:visible="formVisible"
+    :load-list="loadList"
+    :reload-after-create="reloadAfterCreate" />
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+</style>