|
|
|
|
@ -1,55 +1,16 @@
|
|
|
|
|
<template>
|
|
|
|
|
<el-row :gutter="16" class="production-report-layout">
|
|
|
|
|
<el-col :span="6" :xs="24">
|
|
|
|
|
<ContentWrap class="production-report-tree-panel">
|
|
|
|
|
<div class="production-report-tree-toolbar">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="keyword"
|
|
|
|
|
clearable
|
|
|
|
|
:placeholder="t('ProductionReport.Index.treeSearchPlaceholder')"
|
|
|
|
|
@input="handleKeywordChange"
|
|
|
|
|
/>
|
|
|
|
|
<el-button :loading="treeLoading" @click="handleTreeRefresh">
|
|
|
|
|
<Icon icon="ep:refresh" class="mr-5px" />
|
|
|
|
|
{{ t('ProductionReport.Index.refreshTree') }}
|
|
|
|
|
<el-col :span="24">
|
|
|
|
|
<el-row :gutter="16" class="production-report-content-row">
|
|
|
|
|
<el-col :span="13" :xs="24">
|
|
|
|
|
<ContentWrap class="production-report-calendar-panel">
|
|
|
|
|
<div class="production-report-report-toolbar">
|
|
|
|
|
<el-button type="primary" @click="handleOpenGenerateDialog">
|
|
|
|
|
<Icon icon="ep:document-add" class="mr-5px" />
|
|
|
|
|
{{ t('ProductionReport.Index.generateDailyReport') }}
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<el-tree
|
|
|
|
|
ref="treeRef"
|
|
|
|
|
v-loading="treeLoading"
|
|
|
|
|
class="production-report-tree"
|
|
|
|
|
:data="treeData"
|
|
|
|
|
:props="treeProps"
|
|
|
|
|
:default-expanded-keys="defaultExpandedKeys"
|
|
|
|
|
:current-node-key="selectedDeviceId"
|
|
|
|
|
node-key="id"
|
|
|
|
|
highlight-current
|
|
|
|
|
@node-click="handleNodeClick"
|
|
|
|
|
>
|
|
|
|
|
<template #default="{ data }">
|
|
|
|
|
<span class="production-report-tree-node">
|
|
|
|
|
<Icon
|
|
|
|
|
:icon="data.tag === 'equipment' ? 'ep:monitor' : 'ep:office-building'"
|
|
|
|
|
class="production-report-tree-node__icon"
|
|
|
|
|
/>
|
|
|
|
|
<span class="production-report-tree-node__label">{{ data.label }}</span>
|
|
|
|
|
<el-tag
|
|
|
|
|
v-if="data.tag === 'equipment'"
|
|
|
|
|
type="success"
|
|
|
|
|
size="small"
|
|
|
|
|
effect="plain"
|
|
|
|
|
>
|
|
|
|
|
{{ t('ProductionReport.Index.deviceTag') }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-tree>
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
<el-col :span="18" :xs="24">
|
|
|
|
|
<ContentWrap class="production-report-calendar-panel">
|
|
|
|
|
<el-calendar v-model="calendarDate" class="production-report-calendar">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="production-report-calendar-header">
|
|
|
|
|
@ -99,7 +60,11 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template #date-cell="{ data }">
|
|
|
|
|
<div class="production-report-day" :class="{ 'is-other-month': data.type !== 'current-month' }">
|
|
|
|
|
<div
|
|
|
|
|
class="production-report-day"
|
|
|
|
|
:class="{ 'is-other-month': data.type !== 'current-month' }"
|
|
|
|
|
@click="handleCalendarDayClick(data.day)"
|
|
|
|
|
>
|
|
|
|
|
<div class="production-report-day__number">{{ Number(data.day.slice(-2)) }}</div>
|
|
|
|
|
<div v-if="getPlansByDay(data.day).length" class="production-report-day__plans">
|
|
|
|
|
<el-tooltip
|
|
|
|
|
@ -123,16 +88,107 @@
|
|
|
|
|
</el-calendar>
|
|
|
|
|
|
|
|
|
|
<div v-if="calendarLoading" class="production-report-calendar-loading" v-loading="calendarLoading"></div>
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
<el-col :span="11" :xs="24">
|
|
|
|
|
<ContentWrap class="production-report-file-panel">
|
|
|
|
|
<div class="production-report-file-panel__header">
|
|
|
|
|
<span>{{ t('ProductionReport.Index.generatedReportList') }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<el-table v-loading="reportListLoading" :data="reportFileList" :stripe="true" :show-overflow-tooltip="true">
|
|
|
|
|
<el-table-column :label="t('ProductionReport.Index.fileName')" prop="fileName" min-width="240" />
|
|
|
|
|
<el-table-column :label="t('ProductionReport.Index.reportDate')" width="120">
|
|
|
|
|
<template #default="{ row }">{{ formatReportDate(row.reportDate) }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column :label="t('ProductionReport.Index.workTeam')" prop="workTeamName" width="120" />
|
|
|
|
|
<el-table-column :label="t('ProductionReport.Index.devices')" min-width="180">
|
|
|
|
|
<template #default="{ row }">{{ getDeviceNames(row.deviceIds) }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column :label="t('ProductionReport.Index.createTime')" width="180">
|
|
|
|
|
<template #default="{ row }">{{ formatGeneratedTime(row.createTime) }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column :label="t('ProductionReport.Index.tableOperate')" width="90" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" @click="downloadReport(row)">
|
|
|
|
|
{{ t('ProductionReport.Index.download') }}
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<Pagination
|
|
|
|
|
:total="reportQueryParams.total"
|
|
|
|
|
v-model:page="reportQueryParams.pageNo"
|
|
|
|
|
v-model:limit="reportQueryParams.pageSize"
|
|
|
|
|
@pagination="loadReportFileList"
|
|
|
|
|
/>
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<Dialog :title="t('ProductionReport.Index.generateDailyReport')" v-model="generateDialogVisible">
|
|
|
|
|
<el-form label-width="120px" v-loading="generateLoading">
|
|
|
|
|
<el-form-item :label="t('ProductionReport.Index.reportDate')">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="reportDate"
|
|
|
|
|
type="date"
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
:clearable="false"
|
|
|
|
|
class="!w-full"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item :label="t('ProductionReport.Index.workTeam')">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="selectedWorkTeamId"
|
|
|
|
|
class="!w-full"
|
|
|
|
|
:loading="workTeamLoading"
|
|
|
|
|
:placeholder="t('ProductionReport.Index.workTeamPlaceholder')"
|
|
|
|
|
>
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="team in workTeamList"
|
|
|
|
|
:key="String(team.id)"
|
|
|
|
|
:label="team.teamName"
|
|
|
|
|
:value="String(team.id)"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item :label="t('ProductionReport.Index.devices')">
|
|
|
|
|
<el-tree-select
|
|
|
|
|
v-model="selectedDeviceIds"
|
|
|
|
|
v-loading="treeLoading"
|
|
|
|
|
:data="treeData"
|
|
|
|
|
:props="treeProps"
|
|
|
|
|
:default-expanded-keys="defaultExpandedKeys"
|
|
|
|
|
node-key="id"
|
|
|
|
|
multiple
|
|
|
|
|
show-checkbox
|
|
|
|
|
check-strictly
|
|
|
|
|
collapse-tags
|
|
|
|
|
collapse-tags-tooltip
|
|
|
|
|
class="!w-full"
|
|
|
|
|
:placeholder="t('ProductionReport.Index.selectDeviceForReport')"
|
|
|
|
|
@change="handleDeviceSelectionChange"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button type="primary" :loading="generateLoading" @click="handleGenerateReport">
|
|
|
|
|
{{ t('common.ok') }}
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button @click="generateDialogVisible = false">{{ t('common.cancel') }}</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
import { OrganizationApi, DeviceParameterAnalysisNodeVO } from '@/api/mes/organization'
|
|
|
|
|
import { DevicePlanGanttPlanVO, DevicePlanGanttRespVO, PlanApi } from '@/api/mes/plan'
|
|
|
|
|
import { handleTree } from '@/utils/tree'
|
|
|
|
|
import { WorkTeamApi, WorkTeamVO } from '@/api/mes/workteam'
|
|
|
|
|
import { DeviceApi, ProductionDailyReportFileVO } from '@/api/iot/device'
|
|
|
|
|
import { downloadByUrl } from '@/utils/filt'
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'ProductionReport' })
|
|
|
|
|
|
|
|
|
|
@ -142,27 +198,34 @@ type ProductionTreeNode = {
|
|
|
|
|
tag?: 'equipment'
|
|
|
|
|
deviceId?: string
|
|
|
|
|
children?: ProductionTreeNode[]
|
|
|
|
|
disabled?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
const message = useMessage()
|
|
|
|
|
|
|
|
|
|
const treeRef = ref()
|
|
|
|
|
const treeLoading = ref(false)
|
|
|
|
|
const keyword = ref('')
|
|
|
|
|
const treeData = ref<ProductionTreeNode[]>([])
|
|
|
|
|
const treeProps = { children: 'children', label: 'label' }
|
|
|
|
|
const treeProps = { value: 'id', children: 'children', label: 'label', disabled: 'disabled' }
|
|
|
|
|
const defaultExpandedKeys = ref<string[]>([])
|
|
|
|
|
const selectedDevice = ref<ProductionTreeNode>()
|
|
|
|
|
const selectedDeviceId = computed(() => selectedDevice.value?.id)
|
|
|
|
|
const selectedDeviceIds = ref<string[]>([])
|
|
|
|
|
const generateDialogVisible = ref(false)
|
|
|
|
|
|
|
|
|
|
const reportDate = ref(dayjs().format('YYYY-MM-DD'))
|
|
|
|
|
const workTeamLoading = ref(false)
|
|
|
|
|
const workTeamList = ref<WorkTeamVO[]>([])
|
|
|
|
|
const selectedWorkTeamId = ref<string>()
|
|
|
|
|
const generateLoading = ref(false)
|
|
|
|
|
const reportListLoading = ref(false)
|
|
|
|
|
const reportFileList = ref<ProductionDailyReportFileVO[]>([])
|
|
|
|
|
const reportQueryParams = reactive({ pageNo: 1, pageSize: 10, total: 0 })
|
|
|
|
|
|
|
|
|
|
const calendarDate = ref(new Date())
|
|
|
|
|
const calendarLoading = ref(false)
|
|
|
|
|
const scheduleList = ref<DevicePlanGanttRespVO[]>([])
|
|
|
|
|
const MAX_VISIBLE_PLANS = 3
|
|
|
|
|
|
|
|
|
|
let keywordTimer: number | undefined
|
|
|
|
|
|
|
|
|
|
const calendarTitle = computed(() => {
|
|
|
|
|
const date = dayjs(calendarDate.value)
|
|
|
|
|
return t('ProductionReport.Index.calendarMonthTitle', {
|
|
|
|
|
@ -178,102 +241,150 @@ const yearOptions = computed(() => {
|
|
|
|
|
return Array.from({ length: 21 }, (_, index) => currentYear - 10 + index)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const extractApiOrgs = (res: any): DeviceParameterAnalysisNodeVO[] => {
|
|
|
|
|
if (Array.isArray(res)) return res
|
|
|
|
|
if (Array.isArray(res?.data)) return res.data
|
|
|
|
|
if (Array.isArray(res?.result)) return res.result
|
|
|
|
|
if (Array.isArray(res?.list)) return res.list
|
|
|
|
|
return []
|
|
|
|
|
const buildTreeFromDevices = (devices: any[]): ProductionTreeNode[] => {
|
|
|
|
|
const children = devices.map((device) => ({
|
|
|
|
|
id: String(device.id),
|
|
|
|
|
label: device.deviceName || device.deviceCode || String(device.id),
|
|
|
|
|
tag: 'equipment' as const,
|
|
|
|
|
deviceId: String(device.id)
|
|
|
|
|
}))
|
|
|
|
|
return [{
|
|
|
|
|
id: 'iot-device-root',
|
|
|
|
|
label: t('ProductionReport.Index.deviceTag'),
|
|
|
|
|
children,
|
|
|
|
|
disabled: true
|
|
|
|
|
}]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildTreeFromApi = (orgs: DeviceParameterAnalysisNodeVO[]): ProductionTreeNode[] => {
|
|
|
|
|
const normalizeOrg = (org: DeviceParameterAnalysisNodeVO): DeviceParameterAnalysisNodeVO => ({
|
|
|
|
|
...org,
|
|
|
|
|
parentId: org.parentId ?? 0,
|
|
|
|
|
children: Array.isArray(org.children) ? org.children.map(normalizeOrg) : undefined
|
|
|
|
|
})
|
|
|
|
|
const loadTree = async () => {
|
|
|
|
|
treeLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const data = await DeviceApi.getDeviceList()
|
|
|
|
|
const devices = Array.isArray(data) ? data : data?.list || data?.data || []
|
|
|
|
|
treeData.value = buildTreeFromDevices(devices)
|
|
|
|
|
defaultExpandedKeys.value = ['iot-device-root']
|
|
|
|
|
} catch {
|
|
|
|
|
message.error(t('ProductionReport.Index.loadTreeFailed'))
|
|
|
|
|
} finally {
|
|
|
|
|
treeLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const normalizedOrgs = orgs.map(normalizeOrg)
|
|
|
|
|
const isNestedTree = normalizedOrgs.some((org) => Array.isArray(org.children))
|
|
|
|
|
const orgTree = isNestedTree
|
|
|
|
|
? normalizedOrgs
|
|
|
|
|
: (handleTree(normalizedOrgs, 'id', 'parentId', 'children') as DeviceParameterAnalysisNodeVO[])
|
|
|
|
|
|
|
|
|
|
const toTreeNode = (org: DeviceParameterAnalysisNodeVO): ProductionTreeNode => {
|
|
|
|
|
const orgChildren = Array.isArray(org.children) ? org.children.map(toTreeNode) : []
|
|
|
|
|
const equipmentChildren = (org.equipments || []).map((equipment) => ({
|
|
|
|
|
id: `equipment-${equipment.id}`,
|
|
|
|
|
label: equipment.name || String(equipment.id),
|
|
|
|
|
tag: 'equipment' as const,
|
|
|
|
|
deviceId: String(equipment.id)
|
|
|
|
|
}))
|
|
|
|
|
const children = [...orgChildren, ...equipmentChildren]
|
|
|
|
|
const handleOpenGenerateDialog = async () => {
|
|
|
|
|
generateDialogVisible.value = true
|
|
|
|
|
if (!treeData.value.length) await loadTree()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: `org-${org.id}`,
|
|
|
|
|
label: org.name || String(org.id),
|
|
|
|
|
children: children.length ? children : undefined
|
|
|
|
|
const handleDeviceSelectionChange = (deviceIds: string[]) => {
|
|
|
|
|
const findDevice = (nodes: ProductionTreeNode[]): ProductionTreeNode | undefined => {
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
|
if (node.tag === 'equipment' && deviceIds.includes(node.id)) return node
|
|
|
|
|
const device = node.children && findDevice(node.children)
|
|
|
|
|
if (device) return device
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return orgTree.map(toTreeNode)
|
|
|
|
|
selectedDevice.value = findDevice(treeData.value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hasEquipmentInSubtree = (node: ProductionTreeNode): boolean => {
|
|
|
|
|
if (node.tag === 'equipment') return true
|
|
|
|
|
return (node.children || []).some(hasEquipmentInSubtree)
|
|
|
|
|
const loadWorkTeams = async () => {
|
|
|
|
|
workTeamLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const data = await WorkTeamApi.getWorkTeamList()
|
|
|
|
|
workTeamList.value = Array.isArray(data) ? data : data.list || []
|
|
|
|
|
if (!selectedWorkTeamId.value && workTeamList.value.length) {
|
|
|
|
|
selectedWorkTeamId.value = String(workTeamList.value[0].id)
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
message.error(t('ProductionReport.Index.loadWorkTeamsFailed'))
|
|
|
|
|
} finally {
|
|
|
|
|
workTeamLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildDefaultExpandedKeys = (nodes: ProductionTreeNode[]): string[] => {
|
|
|
|
|
const keys: string[] = []
|
|
|
|
|
const walk = (list: ProductionTreeNode[]) => {
|
|
|
|
|
list.forEach((node) => {
|
|
|
|
|
if (node.tag !== 'equipment' && hasEquipmentInSubtree(node)) keys.push(node.id)
|
|
|
|
|
if (node.children?.length) walk(node.children)
|
|
|
|
|
const loadReportFileList = async () => {
|
|
|
|
|
reportListLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const data = await DeviceApi.getProductionDailyReportPage({
|
|
|
|
|
pageNo: reportQueryParams.pageNo,
|
|
|
|
|
pageSize: reportQueryParams.pageSize,
|
|
|
|
|
reportDate: reportDate.value || undefined
|
|
|
|
|
})
|
|
|
|
|
reportFileList.value = data.list || []
|
|
|
|
|
reportQueryParams.total = data.total || 0
|
|
|
|
|
} catch {
|
|
|
|
|
reportFileList.value = []
|
|
|
|
|
reportQueryParams.total = 0
|
|
|
|
|
message.error(t('ProductionReport.Index.loadReportListFailed'))
|
|
|
|
|
} finally {
|
|
|
|
|
reportListLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
walk(nodes)
|
|
|
|
|
return keys
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loadTree = async () => {
|
|
|
|
|
treeLoading.value = true
|
|
|
|
|
const handleReportQuery = () => {
|
|
|
|
|
reportQueryParams.pageNo = 1
|
|
|
|
|
loadReportFileList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleCalendarDayClick = (day: string) => {
|
|
|
|
|
reportDate.value = day
|
|
|
|
|
calendarDate.value = dayjs(day).toDate()
|
|
|
|
|
handleReportQuery()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleGenerateReport = async () => {
|
|
|
|
|
const deviceIds = selectedDeviceIds.value.filter((id): id is string => Boolean(id))
|
|
|
|
|
if (!deviceIds.length) {
|
|
|
|
|
message.warning(t('ProductionReport.Index.selectDeviceForReport'))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (!selectedWorkTeamId.value) {
|
|
|
|
|
message.warning(t('ProductionReport.Index.selectWorkTeamForReport'))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
generateLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await OrganizationApi.deviceParameterAnalysis({
|
|
|
|
|
keyword: keyword.value || undefined,
|
|
|
|
|
showDevices: 1,
|
|
|
|
|
filterNoDeviceLine: 1
|
|
|
|
|
await DeviceApi.generateProductionDailyReport({
|
|
|
|
|
deviceIds,
|
|
|
|
|
reportDate: reportDate.value,
|
|
|
|
|
workTeamId: selectedWorkTeamId.value
|
|
|
|
|
})
|
|
|
|
|
treeData.value = buildTreeFromApi(extractApiOrgs(res))
|
|
|
|
|
defaultExpandedKeys.value = buildDefaultExpandedKeys(treeData.value)
|
|
|
|
|
await nextTick()
|
|
|
|
|
treeRef.value?.setExpandedKeys?.(defaultExpandedKeys.value)
|
|
|
|
|
message.success(t('ProductionReport.Index.generateSuccess'))
|
|
|
|
|
generateDialogVisible.value = false
|
|
|
|
|
handleReportQuery()
|
|
|
|
|
} catch {
|
|
|
|
|
message.error(t('ProductionReport.Index.loadTreeFailed'))
|
|
|
|
|
message.error(t('ProductionReport.Index.generateFailed'))
|
|
|
|
|
} finally {
|
|
|
|
|
treeLoading.value = false
|
|
|
|
|
generateLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleKeywordChange = () => {
|
|
|
|
|
if (keywordTimer) window.clearTimeout(keywordTimer)
|
|
|
|
|
keywordTimer = window.setTimeout(() => {
|
|
|
|
|
keywordTimer = undefined
|
|
|
|
|
loadTree()
|
|
|
|
|
}, 300)
|
|
|
|
|
const getDeviceNames = (deviceIds: Array<string | number>) => {
|
|
|
|
|
const names = new Map<string, string>()
|
|
|
|
|
const walk = (nodes: ProductionTreeNode[]) => nodes.forEach((node) => {
|
|
|
|
|
if (node.tag === 'equipment' && node.deviceId) names.set(String(node.deviceId), node.label)
|
|
|
|
|
if (node.children) walk(node.children)
|
|
|
|
|
})
|
|
|
|
|
walk(treeData.value)
|
|
|
|
|
return (deviceIds || []).map((id) => names.get(String(id)) || String(id)).join('、') || '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleTreeRefresh = async () => {
|
|
|
|
|
if (keywordTimer) {
|
|
|
|
|
window.clearTimeout(keywordTimer)
|
|
|
|
|
keywordTimer = undefined
|
|
|
|
|
const formatReportDate = (value: unknown) => {
|
|
|
|
|
const dateParts = Array.isArray(value) ? value : String(value ?? '').split(',')
|
|
|
|
|
if (dateParts.length === 3) {
|
|
|
|
|
const [year, month, day] = dateParts.map((part) => String(part).trim())
|
|
|
|
|
if (year && month && day) return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
|
|
|
|
|
}
|
|
|
|
|
await loadTree()
|
|
|
|
|
return String(value ?? '-')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleNodeClick = (node: ProductionTreeNode) => {
|
|
|
|
|
if (node.tag !== 'equipment') return
|
|
|
|
|
selectedDevice.value = node
|
|
|
|
|
const formatGeneratedTime = (value: unknown) => {
|
|
|
|
|
const date = dayjs(value as string | number | Date)
|
|
|
|
|
return date.isValid() ? date.format('YYYY-MM-DD HH:mm:ss') : '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const downloadReport = (report: ProductionDailyReportFileVO) => {
|
|
|
|
|
if (report.fileUrl) downloadByUrl({ url: report.fileUrl, fileName: report.fileName })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getCalendarRange = () => {
|
|
|
|
|
@ -368,11 +479,9 @@ watch(
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadTree()
|
|
|
|
|
loadWorkTeams().then(loadReportFileList)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
if (keywordTimer) window.clearTimeout(keywordTimer)
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
@ -380,43 +489,38 @@ onBeforeUnmount(() => {
|
|
|
|
|
min-height: calc(100vh - 130px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-tree-panel,
|
|
|
|
|
.production-report-content-row {
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-calendar-panel {
|
|
|
|
|
min-height: calc(100vh - 130px);
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-tree-toolbar {
|
|
|
|
|
.production-report-report-toolbar {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-tree {
|
|
|
|
|
max-height: calc(100vh - 220px);
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
.production-report-work-team {
|
|
|
|
|
width: 180px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-tree-node {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
.production-report-file-panel {
|
|
|
|
|
min-height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-tree-node__icon {
|
|
|
|
|
flex: none;
|
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-tree-node__label {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-calendar-panel {
|
|
|
|
|
position: relative;
|
|
|
|
|
.production-report-file-panel__header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
color: var(--el-text-color-primary);
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-calendar-header {
|
|
|
|
|
@ -539,8 +643,8 @@ onBeforeUnmount(() => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-calendar-table .el-calendar-day) {
|
|
|
|
|
height: 120px;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
height: 86px;
|
|
|
|
|
padding: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-calendar-table td.is-selected) {
|
|
|
|
|
@ -548,13 +652,12 @@ onBeforeUnmount(() => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.production-report-tree-panel,
|
|
|
|
|
.production-report-calendar-panel {
|
|
|
|
|
min-height: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-calendar-panel {
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-calendar-header {
|
|
|
|
|
@ -565,5 +668,10 @@ onBeforeUnmount(() => {
|
|
|
|
|
.production-report-calendar-controller {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.production-report-work-team {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|