songshankun
2023-11-27 c06965849f7f85fac75746845004647b629c47fb
feat: 报工列表弹窗/对接上报接口/报工弹窗回显
2个文件已添加
9个文件已修改
339 ■■■■■ 已修改文件
package-lock.json 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/reporting.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/plc.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/DashboardTitle.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/ReportProductionModal.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/ReportingRecordModal.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/TaskControl.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -25,6 +25,7 @@
        "@iconify-json/bx": "^1.1.7",
        "@iconify-json/material-symbols-light": "^1.1.0",
        "@iconify-json/mdi": "^1.1.55",
        "@iconify-json/vaadin": "^1.1.7",
        "@rushstack/eslint-patch": "^1.3.3",
        "@tsconfig/node18": "^18.2.2",
        "@types/node": "^18.18.6",
@@ -835,6 +836,15 @@
      "version": "1.1.55",
      "resolved": "https://registry.npmmirror.com/@iconify-json/mdi/-/mdi-1.1.55.tgz",
      "integrity": "sha512-ycnFub+EQx+3D/aDCg6iC7sjexOUa5GzxUNIZFFl0Pq7aDxbmhIludoyYnguEO3REyWf9FcOOmvVcQkdtwKHTw==",
      "dev": true,
      "dependencies": {
        "@iconify/types": "*"
      }
    },
    "node_modules/@iconify-json/vaadin": {
      "version": "1.1.7",
      "resolved": "https://registry.npmmirror.com/@iconify-json/vaadin/-/vaadin-1.1.7.tgz",
      "integrity": "sha512-gtczBFm5EBEkA3iXny4RR9MynZxIWaApnvwEbF/PV7JeSwDYRb8UJZmstNCSRkVXMFcnD+bdW9LJ0CPyjrq1Tw==",
      "dev": true,
      "dependencies": {
        "@iconify/types": "*"
@@ -5500,6 +5510,15 @@
        "@iconify/types": "*"
      }
    },
    "@iconify-json/vaadin": {
      "version": "1.1.7",
      "resolved": "https://registry.npmmirror.com/@iconify-json/vaadin/-/vaadin-1.1.7.tgz",
      "integrity": "sha512-gtczBFm5EBEkA3iXny4RR9MynZxIWaApnvwEbF/PV7JeSwDYRb8UJZmstNCSRkVXMFcnD+bdW9LJ0CPyjrq1Tw==",
      "dev": true,
      "requires": {
        "@iconify/types": "*"
      }
    },
    "@iconify/types": {
      "version": "2.0.0",
      "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz",
package.json
@@ -30,6 +30,7 @@
    "@iconify-json/bx": "^1.1.7",
    "@iconify-json/material-symbols-light": "^1.1.0",
    "@iconify-json/mdi": "^1.1.55",
    "@iconify-json/vaadin": "^1.1.7",
    "@rushstack/eslint-patch": "^1.3.3",
    "@tsconfig/node18": "^18.2.2",
    "@types/node": "^18.18.6",
src/api/index.ts
@@ -4,6 +4,7 @@
import type { Devices } from './device'
import type { CraftModel } from './craftModel'
import type { Problem } from './problem'
import type { ReportingRecord } from './reporting'
export interface BaseResponse<T = any> {
  code: number
@@ -43,6 +44,7 @@
export interface ProductProgressParams {
  channel: number
  procedureId?: number
}
/**
@@ -187,3 +189,37 @@
    method: 'get'
  })
}
export interface ReportingRecordListParams {
  page?: number
  pageSize?: number
  procedureId: number
}
/**
 * 获取报工记录列表
 */
export function apiGetReportingRecordList(params: ReportingRecordListParams) {
  return request<ListResponse<ReportingRecord[]>>({
    url: '/v1/reportWork/list',
    method: 'get',
    params
  })
}
export interface ReportWorkParams {
  procedureId: number
  reportAmount: number
  workerID: string
}
/**
 * 报工
 */
export function apiReportWork(params: ReportWorkParams) {
  return request<BaseResponse>({
    url: '/v1/reportWork/report',
    method: 'post',
    data: params
  })
}
src/api/reporting.ts
New file
@@ -0,0 +1,19 @@
// 报工记录
export interface ReportingRecord {
  ID: number
  CreatedAt: string
  UpdatedAt: string
  DeletedAt: string
  proceduresId: number
  workOrderId: string
  deviceId: string
  deviceName: string
  procedureId: string
  channel: number
  startTime: number
  endTime: number
  reportAmount: number
  finishAmount: number
  workerID: string
  workerName: string
}
src/components.d.ts
@@ -24,6 +24,8 @@
    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
    ElStep: typeof import('element-plus/es')['ElStep']
    ElSteps: typeof import('element-plus/es')['ElSteps']
    ElTable: typeof import('element-plus/es')['ElTable']
    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
    ElTabPane: typeof import('element-plus/es')['ElTabPane']
    ElTabs: typeof import('element-plus/es')['ElTabs']
    ElText: typeof import('element-plus/es')['ElText']
src/stores/plc.ts
@@ -27,7 +27,8 @@
  } = useRequest(
    () =>
      getProductProgress({
        channel: taskStore.activeChannel ?? 0
        channel: taskStore.activeChannel ?? 0,
        procedureId: taskStore.activeTask?.Procedure.ID ?? undefined
      } as ProductProgressParams),
    {
      manual: true,
src/views/dashboard/components/DashboardTitle.vue
@@ -7,7 +7,7 @@
          <el-text truncated class="device-name">{{ currentDeviceName }}</el-text>
        </template>
      </el-popover>
      <el-icon size="32" color="#0db7f5" style="margin-left: 20px; cursor: pointer" @click="openDevicesModal">
      <el-icon size="32" color="#0db7f5" style="margin-left: 4px; cursor: pointer" @click="openDevicesModal">
        <IconSlider></IconSlider>
      </el-icon>
    </div>
@@ -27,6 +27,17 @@
        </el-icon>
      </div>
      <div class="reporting-record">
        <el-icon
          size="26"
          :color="taskStore.activeTask ? '#0db7f5' : '#c0c0c0'"
          :style="{ 'margin-right': '10px', cursor: taskStore.activeTask ? 'pointer' : 'not-allowed' }"
          @click="openReportingRecord"
        >
          <IconRecords></IconRecords>
        </el-icon>
      </div>
      <div class="params-config" @click="openConfigModal">
        <el-icon size="28"><Setting /></el-icon>
      </div>
@@ -35,9 +46,11 @@
  <DeviceCheckList v-model="showDevicesModal" @should-reload="emits('shouldReload')"></DeviceCheckList>
  <TroubleTrackerModal v-model="showProblemsModal" :problems="problemList"></TroubleTrackerModal>
  <DeliverParamsConfigModal v-model="showConfigModal"></DeliverParamsConfigModal>
  <ReportingRecordModal v-model="showReportingRecordModal"></ReportingRecordModal>
</template>
<script setup lang="ts">
import AlertLightIcon from '@/components/icons/AlertLightIcon.vue'
import IconRecords from '~icons/vaadin/records'
import { computed, onUnmounted, ref } from 'vue'
import { useDevicesStore } from '@/stores/devices'
import TroubleTrackerModal from '@/views/dashboard/components/TroubleTrackerModal.vue'
@@ -50,6 +63,8 @@
import { PROBLEMS_POLLING_DURATION } from '@/common/constants'
import { Setting } from '@element-plus/icons-vue'
import DeliverParamsConfigModal from '@/views/dashboard/components/DeliverParamsConfigModal.vue'
import ReportingRecordModal from '@/views/dashboard/components/ReportingRecordModal.vue'
import { useTasksStore } from '@/stores/tasks'
const emits = defineEmits<{
  shouldReload: []
@@ -129,6 +144,18 @@
onUnmounted(() => {
  cancelProblemsPolling()
})
const taskStore = useTasksStore()
// 是否显示报工记录
const showReportingRecordModal = ref(false)
function openReportingRecord() {
  if (!taskStore.activeTask) {
    ElMessage.error('请先选择任务')
    return
  }
  showReportingRecordModal.value = true
}
</script>
<style scoped lang="scss">
@@ -154,7 +181,7 @@
  cursor: pointer;
}
.device-name {
  max-width: 340px;
  max-width: 220px;
  font-size: 40px;
  color: #fff;
}
src/views/dashboard/components/ReportProductionModal.vue
@@ -24,8 +24,9 @@
import { useVModel } from '@vueuse/core'
import BigButton from './BigButton.vue'
import { ref, watch } from 'vue'
const props = withDefaults(defineProps<{ modelValue: boolean }>(), {
  modelValue: false
const props = withDefaults(defineProps<{ modelValue: boolean; amount?: number }>(), {
  modelValue: false,
  amount: 0
})
const emit = defineEmits<{
  'update:modelValue': [show: boolean]
@@ -72,7 +73,7 @@
}
watch(modelData, () => {
  if (modelData.value) {
    inputNumber.value = ''
    inputNumber.value = (props.amount ?? '').toString()
  }
})
</script>
src/views/dashboard/components/ReportingRecordModal.vue
New file
@@ -0,0 +1,158 @@
<template>
  <div class="base-modal">
    <el-dialog v-model="modelData" :close-on-click-modal="false" :show-close="false" width="80%">
      <template #header>
        <div class="modal-title">
          <div class="modal-title-text">报工记录</div>
          <div class="modal-title-close" @click="closeModal">
            <el-icon :size="22" color="#fff"><CloseBold /></el-icon>
          </div>
        </div>
      </template>
      <div class="table-content">
        <el-table class="table" :data="reportingRecordList" border style="width: 100%" :scrollbar-always-on="true">
          <el-table-column type="index" label="序号" width="56" align="center" :resizable="false"></el-table-column>
          <el-table-column prop="deviceId" label="报工来源" align="center" :resizable="false">
            <template #default="scope">
              {{ scope?.row?.workerName ?? '' }}/{{ scope?.row?.deviceName ?? '' }}
            </template>
          </el-table-column>
          <!--        TODO: 条码字段还没加   -->
          <el-table-column prop="xxx" label="条码" align="center" :resizable="false">条码</el-table-column>
          <el-table-column prop="reportAmount" label="报工数量" align="center" :resizable="false" />
          <el-table-column prop="finishAmount" label="完成数量" align="center" :resizable="false" />
          <el-table-column prop="startTime" label="开始时间" align="center" :resizable="false">
            <template #default="scope">
              {{ formatDate(scope.row.startTime) }}
            </template>
          </el-table-column>
          <el-table-column prop="endTime" label="结束时间" align="center" :resizable="false">
            <template #default="scope">
              {{ formatDate(scope.row.endTime) }}
            </template>
          </el-table-column>
          <!--          TODO: 工时字段还没加-->
          <el-table-column prop="xxx" label="工时" align="center" :resizable="false" />
        </el-table>
      </div>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import { useDateFormat, useVModel } from '@vueuse/core'
import { CloseBold } from '@element-plus/icons-vue'
import { ref, watch } from 'vue'
import { apiGetReportingRecordList } from '@/api'
import type { ReportingRecord } from '@/api/reporting'
import { useTasksStore } from '@/stores/tasks'
export interface BaseModalProps {
  /** 是否展示模态框 */
  modelValue: boolean
}
const props = withDefaults(defineProps<BaseModalProps>(), {
  modelValue: false
})
const emit = defineEmits<{
  'update:modelValue': [show: boolean]
  close: []
}>()
const modelData = useVModel(props, 'modelValue', emit)
function closeModal() {
  emit('update:modelValue', false)
  emit('close')
}
const taskStore = useTasksStore()
// 报工记录列表
const reportingRecordList = ref<ReportingRecord[]>([])
function getReportingList() {
  const procedureId = taskStore.activeTask?.Procedure.ID
  if (!procedureId) {
    return
  }
  apiGetReportingRecordList({
    procedureId: procedureId
  })
    .then((res) => {
      if (res.code === 200) {
        reportingRecordList.value = res?.data ?? []
      } else {
        reportingRecordList.value = []
      }
    })
    .catch((err) => {
      console.error(err)
      reportingRecordList.value = []
    })
}
// 打开弹窗时获取报工记录
watch(modelData, (show) => {
  if (show) {
    getReportingList()
  }
})
/**
 * 格式化时间戳
 * @param timestamp 后端返的10位时间戳
 */
function formatDate(timestamp?: number) {
  if (!timestamp) {
    return '--'
  }
  const time = useDateFormat(timestamp * 1000, 'YYYY-MM-DD HH:mm:ss', { locales: 'zh-cn' })
  return time.value
}
</script>
<style scoped lang="scss">
:deep(.el-dialog) {
  background-color: #1d3081;
}
.modal-title {
  position: relative;
  display: flex;
  align-items: center;
  &-text {
    padding-left: 12px;
    font-size: 26px;
    font-weight: 600;
  }
  &-close {
    cursor: pointer;
    height: 36px;
    width: 36px;
    border-radius: 50%;
    position: absolute;
    display: flex;
    justify-content: center;
    align-items: center;
    top: -16px;
    right: -30px;
  }
}
$bgc: #1d3081;
.table-content {
  :deep(.el-table .el-table__cell) {
    background-color: $bgc;
    color: #dcdfec;
  }
  :deep(.el-table__body tr) {
    background-color: $bgc;
    color: #dcdfec;
  }
  :deep(.el-table__body tr:hover > td) {
    background-color: $bgc;
  }
  :deep(.el-scrollbar__wrap) {
    background-color: $bgc;
  }
  height: calc(70vh - 80px);
}
.table {
  height: 100%;
}
</style>
src/views/dashboard/components/TaskControl.vue
@@ -47,25 +47,29 @@
  <TaskControlModal v-model="showTaskControlModal" :task="task" @produce-start="onProduceStart"></TaskControlModal>
  <ReportProductionModal
    v-model="showReportModal"
    :amount="plcInfo?.finishNumber ?? 0"
    @close="showReportModal = false"
    @submit="showReportModal = false"
    @submit="onReportProduction"
  ></ReportProductionModal>
</template>
<script setup lang="ts">
import type { Task } from '@/api/task'
import type { Task, Worker } from '@/api/task'
import { ref, toRefs } from 'vue'
import BigButton from '@/views/dashboard/components/BigButton.vue'
import { useDateFormat } from '@vueuse/core'
import TaskControlModal from '@/views/dashboard/components/TaskControlModal.vue'
import { CircleCloseFilled } from '@element-plus/icons-vue'
import { finishTask } from '@/api'
import { apiReportWork, finishTask } from '@/api'
import { ElMessage } from 'element-plus'
import ReportProductionModal from '@/views/dashboard/components/ReportProductionModal.vue'
import { usePLCStore } from '@/stores/plc'
import { storeToRefs } from 'pinia'
const props = defineProps<{
  task?: Task
  workers: Worker[]
}>()
const { task } = toRefs(props)
const { task, workers } = toRefs(props)
const emit = defineEmits<{
  shouldReload: [task: Task]
@@ -124,11 +128,48 @@
  const time = useDateFormat(timestamp * 1000, 'YYYY-MM-DD HH:mm:ss', { locales: 'zh-cn' })
  return time.value
}
const plcStore = usePLCStore()
const { plcInfo } = storeToRefs(plcStore)
// 报工
const showReportModal = ref(false)
function openReportModal() {
  showReportModal.value = true
}
/**
 * 上报加工数
 * @param amount 加工数
 */
function onReportProduction(amount: number) {
  if (!task?.value) {
    return
  }
  apiReportWork({
    procedureId: task.value?.Procedure.ID,
    reportAmount: amount,
    workerID: workers.value[0].workerId
  })
    .then((res) => {
      if (res.code === 200) {
        ElMessage({
          message: '报工成功',
          type: 'success',
          duration: 2000
        })
        showReportModal.value = false
      } else {
        ElMessage({
          message: '报工失败',
          type: 'error',
          duration: 3000
        })
      }
    })
    .catch((err) => {
      console.error(err)
    })
}
</script>
<style scoped lang="scss">
$title-text-color: #9599af;
src/views/dashboard/index.vue
@@ -58,7 +58,7 @@
    <template #middleBlock3>
      <SubTitle>任务详情</SubTitle>
      <div class="task-detail">
        <TaskControl :task="activeTask" @should-reload="reloadChannel"></TaskControl>
        <TaskControl :task="activeTask" :workers="currentWorkers" @should-reload="reloadChannel"></TaskControl>
      </div>
      <ColorInfo :type="1"></ColorInfo>
      <ColorInfo :type="2"></ColorInfo>
@@ -96,7 +96,7 @@
import { computed, ref } from 'vue'
import ChannelCollapse from '@/views/dashboard/components/ChannelCollapse.vue'
import type { Worker, Order, Task, Material } from '@/api/task'
import type { Task, Material } from '@/api/task'
import type { CraftModel } from '@/api/craftModel'
import PersonInfo from '@/views/dashboard/components/PersonInfo.vue'
import ProcessInfo from '@/views/dashboard/components/ProcessInfo.vue'
@@ -124,6 +124,7 @@
import { updateCraftParams } from '@/api'
import { Loading } from '@element-plus/icons-vue'
import { isNumber } from 'lodash-es'
defineOptions({
  name: 'DashboardView'
@@ -235,6 +236,15 @@
  currentMaterialInfo.value = material
  showMaterialDetail.value = true
}
const currentWorkers = computed(() => {
  const channel = activeTask.value?.Channel
  if (isNumber(channel)) {
    return channels.value[channel].workers ?? []
  } else {
    return []
  }
})
</script>
<style scoped lang="scss">