Compare commits

...

25 Commits
ck ... main

Author SHA1 Message Date
黄伟杰 2a6b73e566 fix:修复runoverview api文件缺失问题 1 day ago
黄伟杰 1bdd5c276f style:产能报表-删除状态字段 1 day ago
黄伟杰 e68b934593 feat:生产管理模块-筛选条件添加“更多”按钮 1 day ago
黄伟杰 cb0ade38e0 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 day ago
黄伟杰 152a906c16 feat:添加产能报表页面 1 day ago
黄伟杰 e13fdef1f6 feat:工厂建模模块-筛选条件添加“更多”按钮 1 day ago
liutao 2295788977 Merge remote-tracking branch 'origin/main' 2 days ago
liutao d9b062419d 设备管理调整 2 days ago
liutao d0db5f5a90 设备管理调整 2 days ago
黄伟杰 80e2cda424 style:搜索菜单按钮,点击后自动获取焦点 2 days ago
黄伟杰 176a3dc607 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 days ago
黄伟杰 182e9e9712 feat:排产-甘特图添加双击弹框-调整任务 2 days ago
liutao 84101b7866 设备运行概览 2 days ago
liutao 2872f52d57 设备运行概览调整 2 days ago
黄伟杰 8c3f7a5aec feat:排产-甘特图添加锁定、撤回操作按钮 2 days ago
黄伟杰 fc6f348ad6 style:排产甘特图i18适配 2 days ago
黄伟杰 0ff6eaef69 style:甘特图-计划明细字段调整 2 days ago
liutao f88765448b Merge remote-tracking branch 'origin/main' 3 days ago
liutao bedd8d9bca 切换时真加点击事件 3 days ago
liutao 4abb93deab 样式优化 4 days ago
黄伟杰 21fdceb26f Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 4 days ago
黄伟杰 d8a88b6fd7 feat:甘特图修改-多个计划展示在一行显示 4 days ago
黄伟杰 0dbdf57204 style:任务制单-查询条件样式优化 4 days ago
黄伟杰 da56f8d359 style:设备台账-移除每日报工平均值、数据采集产能 4 days ago
黄伟杰 85b759406d style:任务单排产-排产-产能来源改成从字典获取 4 days ago

@ -0,0 +1,24 @@
import request from '@/config/axios'
export interface RunOverviewRequestParams {
ids: string
startTime: string
endTime: string
timelinePageNo: number
timelinePageSize: number
}
export interface RunOverviewResponse {
metrics?: any[]
hourlyStatus?: any[]
summary?: any[]
summaryTotalHours?: number
timelineRows?: any[]
totalDevices?: number
}
export const DeviceOperationOverviewApi = {
getRunOverview: async (params: RunOverviewRequestParams) => {
return await request.get({ url: `/iot/deviceOperationOverview/getRunOverview`, params })
}
}

@ -0,0 +1,47 @@
import request from '@/config/axios'
export interface CapacityReportVO {
id: number
deviceCode: string
deviceName: string
typeName: string
deviceStatus: number
ratedCapacity: number
reportCapacity: number
actualCapacity: number
workshopName: string
}
export interface CapacityReportQuery {
pageNo: number
pageSize: number
deviceCode?: string
deviceName?: string
deviceType?: string
deviceStatus?: string
workshop?: string
ids?: string
}
export const DeviceLedgerApi = {
/**
*
*/
getCapacityReportPage: async (params: CapacityReportQuery) => {
return await request.get({ url: '/mes/device-ledger/capacity-report/page', params })
},
/**
*
*/
exportCapacityReport: async (params: { ids?: string }) => {
return await request.download({ url: '/mes/device-ledger/capacity-report/export-excel', params })
},
/**
*
*/
updateDeviceLedger: async (data: { id: number; deviceStatus: number }) => {
return await request.put({ url: '/mes/device-ledger/update', data })
}
}

@ -17,9 +17,10 @@
/> />
</el-select> </el-select>
</ElDialog> </ElDialog>
<div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch"> <div v-else class="custom-hover" @click.stop="toggleTopSearch">
<Icon icon="ep:search" /> <Icon icon="ep:search" />
<el-select <el-select
ref="topSelectRef"
@click.stop @click.stop
filterable filterable
:reserve-keyword="false" :reserve-keyword="false"
@ -51,7 +52,8 @@ defineProps({
const router = useRouter() // const router = useRouter() //
const showSearch = ref(false) // const showSearch = ref(false) //
const showTopSearch = ref(false) // const showTopSearch = ref(false) //
const value: Ref = ref('') // const value: Ref = ref('')
const topSelectRef = ref<InstanceType<typeof ElSelect>>()
const routers = router.getRoutes() // const routers = router.getRoutes() //
const options = computed(() => { const options = computed(() => {
@ -86,6 +88,15 @@ function hiddenTopSearch() {
showTopSearch.value = false showTopSearch.value = false
} }
function toggleTopSearch() {
showTopSearch.value = !showTopSearch.value
if (showTopSearch.value) {
setTimeout(() => {
topSelectRef.value?.focus()
}, 600)
}
}
onMounted(() => { onMounted(() => {
window.addEventListener('keydown', listenKey) window.addEventListener('keydown', listenKey)
window.addEventListener('click', hiddenTopSearch) window.addEventListener('click', hiddenTopSearch)

@ -1335,10 +1335,34 @@ export default {
validatorDeviceCodeRequired: 'Code can not be empty', validatorDeviceCodeRequired: 'Code can not be empty',
gjTitle: 'Select key components', gjTitle: 'Select key components',
bjTitle: 'Select spare parts', bjTitle: 'Select spare parts',
normal: 'Normal',
stop: 'Stopped',
maintenance: 'Maintenance',
scrap: 'Scrap',
placeholderWorkshop: 'Please input workshop',
reportCapacity: 'Report Capacity',
actualCapacity: 'Actual Capacity',
Detail: { Detail: {
invalidId: 'Invalid device ID' invalidId: 'Invalid device ID'
} }
}, },
// Capacity Report
CapacityReport: {
deviceCode: 'Device Code',
deviceName: 'Device Name',
deviceType: 'Device Type',
deviceStatus: 'Device Status',
workshop: 'Workshop',
ratedCapacity: 'Rated Capacity',
reportCapacity: 'Report Capacity',
actualCapacity: 'Actual Capacity',
placeholderDeviceCode: 'Please input device code',
placeholderDeviceName: 'Please input device name',
placeholderDeviceType: 'Please input device type',
placeholderDeviceStatus: 'Please select device status',
placeholderWorkshop: 'Please input workshop',
exportFilename: 'Capacity Report.xls'
},
// Critical Component // Critical Component
EquipmentKeyItems: { EquipmentKeyItems: {
code: 'Code', code: 'Code',
@ -4575,6 +4599,7 @@ export default {
planCountLabel: 'Plan Count', planCountLabel: 'Plan Count',
planDetailTitle: 'Plan Details', planDetailTitle: 'Plan Details',
planCodeColon: 'Plan Code: ', planCodeColon: 'Plan Code: ',
taskCodeColon: 'Task Code: ',
planNumberColon: 'Plan Qty: ', planNumberColon: 'Plan Qty: ',
deliveryDateColon: 'Delivery Date: ', deliveryDateColon: 'Delivery Date: ',
startColon: 'Start: ', startColon: 'Start: ',
@ -4582,19 +4607,27 @@ export default {
latestStartColon: 'Latest Start: ', latestStartColon: 'Latest Start: ',
emptyDescription: 'No Schedule Info', emptyDescription: 'No Schedule Info',
adjustTaskTitle: 'Adjust Task', adjustTaskTitle: 'Adjust Task',
taskLabel: 'Task',
taskPlaceholder: 'Please select task',
deviceLabel: 'Device', deviceLabel: 'Device',
devicePlaceholder: 'Please select device', devicePlaceholder: 'Please select device',
startDateLabel: 'Start Date', startDateLabel: 'Start Date',
startDatePlaceholder: 'Please select start date', startDatePlaceholder: 'Please select plan start date',
endDateLabel: 'End Date',
endDatePlaceholder: 'Please select plan end date',
durationLabel: 'Days', durationLabel: 'Days',
buttonCancel: 'Cancel', buttonCancel: 'Cancel',
buttonConfirm: 'Confirm', buttonConfirm: 'Confirm',
editStartDateTitle: 'Edit Start Time', editStartDateTitle: 'Edit Start Time',
startTimeLabel: 'Start Time', startTimeLabel: 'Start Time',
startTimePlaceholder: 'Please select start time', startTimePlaceholder: 'Please select start time',
warningCompleteDeviceDate: 'Please complete device and start date', capacityTypeLabel: 'Capacity Source',
warningCompleteDeviceDate: 'Please complete device, start date and end date',
warningValidTime: 'Please select a valid time', warningValidTime: 'Please select a valid time',
warningEndBeforeStart: 'End time cannot be earlier than start time',
columnTaskName: 'Task Name', columnTaskName: 'Task Name',
columnDeviceName: 'Device Name',
columnPlanInfo: 'Plan Info',
columnStartTime: 'Start Time', columnStartTime: 'Start Time',
columnDays: 'Days', columnDays: 'Days',
scaleMonthFormat: 'MMM YYYY', scaleMonthFormat: 'MMM YYYY',
@ -4616,7 +4649,18 @@ export default {
statusStarted: 'Started', statusStarted: 'Started',
statusPaused: 'Paused', statusPaused: 'Paused',
statusPendingStorage: 'Pending Storage', statusPendingStorage: 'Pending Storage',
statusStored: 'Stored' statusStored: 'Stored',
dialogTitle: 'Schedule Gantt Preview',
workerLabel: 'Worker',
workerPlaceholder: 'Please select worker',
calcLossLabel: 'Calculate Loss',
lockBtn: 'Lock',
unlockBtn: 'Unlock',
undoBtn: 'Undo',
buttonSave: 'Save',
buttonClose: 'Close',
warningNoPlanData: 'No plan data to save',
saveSuccess: 'Schedule saved successfully'
} }
} }
} }

@ -1325,10 +1325,34 @@ export default {
validatorDeviceCodeRequired: '编码不能为空', validatorDeviceCodeRequired: '编码不能为空',
gjTitle: '选择关键件', gjTitle: '选择关键件',
bjTitle: '选择备件', bjTitle: '选择备件',
normal: '正常',
stop: '停用',
maintenance: '维修',
scrap: '报废',
placeholderWorkshop: '请输入所属车间',
reportCapacity: '报工产能',
actualCapacity: '实际产能',
Detail: { Detail: {
invalidId: '无效的设备ID' invalidId: '无效的设备ID'
} }
}, },
// 产能报表
CapacityReport: {
deviceCode: '设备编码',
deviceName: '设备名称',
deviceType: '设备类型',
deviceStatus: '设备状态',
workshop: '所属车间',
ratedCapacity: '额定产能',
reportCapacity: '报工产能',
actualCapacity: '实际产能',
placeholderDeviceCode: '请输入设备编码',
placeholderDeviceName: '请输入设备名称',
placeholderDeviceType: '请输入设备类型',
placeholderDeviceStatus: '请选择设备状态',
placeholderWorkshop: '请输入所属车间',
exportFilename: '产能报表.xls'
},
// 设备关键件 // 设备关键件
EquipmentKeyItems: { EquipmentKeyItems: {
count: '数量', count: '数量',
@ -4786,6 +4810,7 @@ export default {
planCountLabel: '计划条数', planCountLabel: '计划条数',
planDetailTitle: '计划明细', planDetailTitle: '计划明细',
planCodeColon: '计划编码:', planCodeColon: '计划编码:',
taskCodeColon: '任务编码:',
planNumberColon: '计划数量:', planNumberColon: '计划数量:',
deliveryDateColon: '交货日期:', deliveryDateColon: '交货日期:',
startColon: '开始:', startColon: '开始:',
@ -4793,19 +4818,27 @@ export default {
latestStartColon: '最晚开工:', latestStartColon: '最晚开工:',
emptyDescription: '暂无计划信息', emptyDescription: '暂无计划信息',
adjustTaskTitle: '调整任务', adjustTaskTitle: '调整任务',
taskLabel: '任务',
taskPlaceholder: '请选择任务',
deviceLabel: '设备', deviceLabel: '设备',
devicePlaceholder: '请选择设备', devicePlaceholder: '请选择设备',
startDateLabel: '开始日期', startDateLabel: '开始日期',
startDatePlaceholder: '请选择开始日期', startDatePlaceholder: '请选择计划开始日期',
endDateLabel: '结束日期',
endDatePlaceholder: '请选择计划结束日期',
durationLabel: '天数', durationLabel: '天数',
buttonCancel: '取消', buttonCancel: '取消',
buttonConfirm: '确定', buttonConfirm: '确定',
editStartDateTitle: '修改开始时间', editStartDateTitle: '修改开始时间',
startTimeLabel: '开始时间', startTimeLabel: '开始时间',
startTimePlaceholder: '请选择开始时间', startTimePlaceholder: '请选择开始时间',
warningCompleteDeviceDate: '请完善设备和开始日期', capacityTypeLabel: '产能来源',
warningCompleteDeviceDate: '请完善设备、计划开始日期和计划结束日期',
warningValidTime: '请选择有效的时间', warningValidTime: '请选择有效的时间',
warningEndBeforeStart: '结束时间不能早于开始时间',
columnTaskName: '任务名称', columnTaskName: '任务名称',
columnDeviceName: '设备名称',
columnPlanInfo: '计划信息',
columnStartTime: '开始时间', columnStartTime: '开始时间',
columnDays: '天数', columnDays: '天数',
scaleMonthFormat: 'YYYY年M月', scaleMonthFormat: 'YYYY年M月',
@ -4827,7 +4860,18 @@ export default {
statusStarted: '已开工', statusStarted: '已开工',
statusPaused: '暂停', statusPaused: '暂停',
statusPendingStorage: '待入库', statusPendingStorage: '待入库',
statusStored: '已入库' statusStored: '已入库',
dialogTitle: '排产甘特图预览',
workerLabel: '领料人',
workerPlaceholder: '请选择领料人',
calcLossLabel: '是否计算损耗',
lockBtn: '锁定',
unlockBtn: '解锁',
undoBtn: '撤回',
buttonSave: '保存',
buttonClose: '关闭',
warningNoPlanData: '暂无可保存的计划数据',
saveSuccess: '排产计划保存成功'
} }
} }
} }

@ -36,7 +36,7 @@
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRemarkLabel')" prop="remark"> <el-form-item :label="t('FactoryModeling.AutocodeRule.searchRemarkLabel')" prop="remark" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.remark" v-model="queryParams.remark"
:placeholder="t('FactoryModeling.AutocodeRule.searchRemarkPlaceholder')" :placeholder="t('FactoryModeling.AutocodeRule.searchRemarkPlaceholder')"
@ -45,7 +45,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchIsEnableLabel')" prop="isEnable"> <el-form-item :label="t('FactoryModeling.AutocodeRule.searchIsEnableLabel')" prop="isEnable" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.isEnable" v-model="queryParams.isEnable"
:placeholder="t('FactoryModeling.AutocodeRule.searchIsEnablePlaceholder')" :placeholder="t('FactoryModeling.AutocodeRule.searchIsEnablePlaceholder')"
@ -60,6 +60,14 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button plain @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? '收起' : '更多' }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.searchButtonText') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.searchButtonText') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.resetButtonText') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.resetButtonText') }}</el-button>
@ -200,6 +208,13 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false) //
const filterCount = 5 // ruleCoderuleNameruleDescremarkisEnable
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const getBarcodeTypeLabel = (value: any) => { const getBarcodeTypeLabel = (value: any) => {
const str = value === undefined || value === null ? '' : String(value) const str = value === undefined || value === null ? '' : String(value)

@ -11,7 +11,7 @@
</el-button> </el-button>
</div> </div>
</div> </div>
<el-tabs v-model="deviceTabActive"> <el-tabs v-model="deviceTabActive" :onclick="changeClick">
<el-tab-pane :label="deviceAttributeTabLabel" name="deviceAttribute"> <el-tab-pane :label="deviceAttributeTabLabel" name="deviceAttribute">
<DeviceAttributeList :device-id="attributeDeviceId" /> <DeviceAttributeList :device-id="attributeDeviceId" />
</el-tab-pane> </el-tab-pane>
@ -599,6 +599,9 @@ const resetDeviceAlarmQuery = () => {
deviceAlarmQueryParams.pageNo = 1 deviceAlarmQueryParams.pageNo = 1
getDeviceAlarmList() getDeviceAlarmList()
} }
const changeClick = () => {
deviceTabActive.value==='deviceRule' ? getRuleList() : getDeviceAlarmList()
}
const openCreateRuleForm = () => { const openCreateRuleForm = () => {
if (!attributeDeviceId.value) { if (!attributeDeviceId.value) {

@ -38,7 +38,7 @@
</div> </div>
</div> </div>
<div v-for="row in pagedRows" :key="row.id" class="timeline-table__row"> <div v-for="row in timelineRows" :key="row.id" class="timeline-table__row">
<div class="timeline-table__meta"> <div class="timeline-table__meta">
<div class="timeline-table__device">{{ row.name }}</div> <div class="timeline-table__device">{{ row.name }}</div>
<div class="timeline-table__rate">{{ row.utilizationRate.toFixed(2) }}%</div> <div class="timeline-table__rate">{{ row.utilizationRate.toFixed(2) }}%</div>
@ -116,12 +116,20 @@ const legendItems = computed(() => [
{ status: 'offline' as const, color: statusColors.offline, label: t('DataCollection.RunOverview.legend.offline') } { status: 'offline' as const, color: statusColors.offline, label: t('DataCollection.RunOverview.legend.offline') }
]) ])
const hourTicks = Array.from({ length: 13 }, (_, index) => `${String(index * 2).padStart(2, '0')}:00`) const hourTicks = Array.from({ length: 24 }, (_, index) => `${String(index).padStart(2, '0')}:00`)
const pagedRows = computed(() => { const timelineRows = computed(() =>
const start = (props.pageNo - 1) * props.pageSize props.rows.map((row) => ({
return props.rows.slice(start, start + props.pageSize) ...row,
}) segments: (row.segments || []).filter(
(segment) =>
!!statusColors[segment.status] &&
Number.isFinite(segment.startHour) &&
Number.isFinite(segment.endHour) &&
segment.endHour > segment.startHour
)
}))
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -239,7 +247,6 @@ const pagedRows = computed(() => {
.timeline-track { .timeline-track {
height: 20px; height: 20px;
background: #f5f7fb;
border-radius: 999px; border-radius: 999px;
overflow: hidden; overflow: hidden;
} }

@ -2,22 +2,28 @@
<ContentWrap class="run-overview-filter"> <ContentWrap class="run-overview-filter">
<el-form :model="modelValue" inline label-width="auto"> <el-form :model="modelValue" inline label-width="auto">
<el-form-item :label="t('DataCollection.RunOverview.groupLabel')"> <el-form-item :label="t('DataCollection.RunOverview.groupLabel')">
<el-select <el-tree-select
:model-value="modelValue.groupId" :model-value="modelValue.groupId"
:placeholder="t('DataCollection.RunOverview.groupPlaceholder')" :data="groupOptions"
:props="treeProps"
check-strictly
clearable
default-expand-all
class="!w-220px" class="!w-220px"
@update:model-value="updateField('groupId', $event)" :placeholder="t('DataCollection.RunOverview.groupPlaceholder')"
> @update:model-value="handleGroupChange"
<el-option v-for="item in groupOptions" :key="item.value" :label="item.label" :value="item.value" /> />
</el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.RunOverview.deviceLabel')"> <el-form-item :label="t('DataCollection.RunOverview.deviceLabel')">
<el-select <el-select
:model-value="modelValue.deviceId" :model-value="modelValue.deviceId"
multiple
collapse-tags
collapse-tags-tooltip
clearable clearable
:placeholder="t('DataCollection.RunOverview.devicePlaceholder')" :placeholder="t('DataCollection.RunOverview.devicePlaceholder')"
class="!w-240px" class="!w-240px"
@update:model-value="updateField('deviceId', $event || '')" @update:model-value="updateField('deviceId', $event || [])"
> >
<el-option v-for="item in deviceOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in deviceOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
@ -52,21 +58,26 @@
<Icon icon="ep:refresh" class="mr-5px" /> <Icon icon="ep:refresh" class="mr-5px" />
{{ t('DataCollection.RunOverview.resetButtonText') }} {{ t('DataCollection.RunOverview.resetButtonText') }}
</el-button> </el-button>
<el-button type="success" plain @click="emit('export')"> <!-- <el-button type="success" plain @click="emit('export')">
<Icon icon="ep:download" class="mr-5px" /> <Icon icon="ep:download" class="mr-5px" />
{{ t('DataCollection.RunOverview.exportButtonText') }} {{ t('DataCollection.RunOverview.exportButtonText') }}
</el-button> </el-button>-->
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { OverviewOption, QuickRangeKey, RunOverviewQueryParams } from './types' import type {
OrganizationTreeOption,
OverviewOption,
QuickRangeKey,
RunOverviewQueryParams
} from './types'
const props = defineProps<{ const props = defineProps<{
modelValue: RunOverviewQueryParams modelValue: RunOverviewQueryParams
groupOptions: OverviewOption[] groupOptions: OrganizationTreeOption[]
deviceOptions: OverviewOption[] deviceOptions: OverviewOption[]
quickRanges: Array<{ label: string; value: QuickRangeKey }> quickRanges: Array<{ label: string; value: QuickRangeKey }>
}>() }>()
@ -80,6 +91,11 @@ const emit = defineEmits<{
}>() }>()
const { t } = useI18n() const { t } = useI18n()
const treeProps = {
value: 'id',
label: 'name',
children: 'children'
}
const updateField = <K extends keyof RunOverviewQueryParams>(key: K, value: RunOverviewQueryParams[K]) => { const updateField = <K extends keyof RunOverviewQueryParams>(key: K, value: RunOverviewQueryParams[K]) => {
emit('update:modelValue', { emit('update:modelValue', {
@ -87,6 +103,15 @@ const updateField = <K extends keyof RunOverviewQueryParams>(key: K, value: RunO
[key]: value [key]: value
}) })
} }
const handleGroupChange = (value: string | number | undefined) => {
const nextGroupId = value == null ? '' : String(value)
emit('update:modelValue', {
...props.modelValue,
groupId: nextGroupId,
deviceId: props.modelValue.deviceId
})
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

@ -23,12 +23,17 @@
</div> </div>
<div class="chart-panel__side"> <div class="chart-panel__side">
<div class="chart-panel__side-title">{{ t('DataCollection.RunOverview.summaryTitle') }}</div> <div class="chart-panel__side-title">{{ t('DataCollection.RunOverview.summaryTitle') }}</div>
<Echart :options="pieOption" height="320px" /> <div class="chart-panel__summary-layout">
<div class="chart-panel__legend"> <!-- <div class="chart-panel__pie">
<div v-for="item in summary" :key="item.status" class="chart-panel__legend-item"> <Echart :options="pieOption" height="280px" />
<span class="dot" :style="{ background: statusColors[item.status] }"></span> </div>&ndash;&gt;-->
<span class="label">{{ statusLabelMap[item.status] }}</span> <Echart :options="pieOption" height="280px" />
<span class="value">{{ item.percent.toFixed(2) }}% ({{ item.hours.toFixed(2) }}h)</span> <div class="chart-panel__legend">
<div v-for="item in summary" :key="item.status" class="chart-panel__legend-item">
<span class="dot" :style="{ background: statusColors[item.status] }"></span>
<span class="label">{{ statusLabelMap[item.status] }}</span>
<span class="value">{{ item.percent.toFixed(2) }}% ({{ item.hours.toFixed(2) }}h)</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -47,6 +52,7 @@ const Echart = EchartChart
const props = defineProps<{ const props = defineProps<{
hourlyStatus: HourlyStatusItem[] hourlyStatus: HourlyStatusItem[]
summary: StatusSummaryItem[] summary: StatusSummaryItem[]
summaryTotalHours?: number
}>() }>()
const { t } = useI18n() const { t } = useI18n()
@ -132,11 +138,11 @@ const pieOption = computed<EChartsOption>(() => ({
tooltip: { trigger: 'item', formatter: '{b}: {c}%' }, tooltip: { trigger: 'item', formatter: '{b}: {c}%' },
graphic: [ graphic: [
{ {
type: 'text', type: 'text',
left: 'center', left: 'center',
top: '42%', top: '42%',
style: { style: {
text: `${t('DataCollection.RunOverview.totalTimeLabel')}\n24.00 h`, text: `${t('DataCollection.RunOverview.totalTimeLabel')}\n${(props.summaryTotalHours ?? 0).toFixed(2)} h`,
textAlign: 'center', textAlign: 'center',
fill: '#101828', fill: '#101828',
fontSize: 14, fontSize: 14,
@ -198,20 +204,34 @@ const pieOption = computed<EChartsOption>(() => ({
} }
.chart-panel__side { .chart-panel__side {
padding: 8px 8px 0; padding: 8px 12px 0;
}
.chart-panel__summary-layout {
display: grid;
grid-template-columns: minmax(180px, 220px) minmax(180px, 1fr);
align-items: center;
gap: 8px 16px;
min-height: 280px;
}
.chart-panel__pie {
display: flex;
align-items: center;
justify-content: center;
} }
.chart-panel__legend { .chart-panel__legend {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 14px;
margin-top: -8px; justify-content: center;
} }
.chart-panel__legend-item { .chart-panel__legend-item {
display: grid; display: grid;
grid-template-columns: 12px 1fr auto; grid-template-columns: 12px 52px 1fr;
gap: 8px; gap: 10px;
align-items: center; align-items: center;
color: #344054; color: #344054;
font-size: 13px; font-size: 13px;
@ -230,11 +250,22 @@ const pieOption = computed<EChartsOption>(() => ({
.value { .value {
color: #101828; color: #101828;
font-weight: 600; font-weight: 600;
text-align: left;
} }
@media (max-width: 1100px) { @media (max-width: 1100px) {
.chart-panel__body { .chart-panel__body {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.chart-panel__summary-layout {
grid-template-columns: 1fr;
justify-items: center;
}
.chart-panel__legend {
width: 100%;
max-width: 320px;
}
} }
</style> </style>

@ -1,15 +1,31 @@
export type RunStatus = 'running' | 'standby' | 'fault' | 'offline' export type RunStatus = 'running' | 'standby' | 'fault' | 'offline'
export type QuickRangeKey = 'today' | 'yesterday' | 'last7Days' | 'last30Days' | 'custom' export type QuickRangeKey = 'today' | 'yesterday' | 'last7Days' | 'last30Days'
export interface OverviewOption { export interface OverviewOption {
label: string label: string
value: string value: string
} }
export interface OrganizationFilterItem {
id: number | string
name: string
parentId?: number | string | null
dvId?: number | string | null
machineName?: string
}
export interface OrganizationTreeOption {
id: number | string
name: string
parentId?: number | string | null
dvId?: number | string | null
children?: OrganizationTreeOption[]
}
export interface RunOverviewQueryParams { export interface RunOverviewQueryParams {
groupId: string groupId: string
deviceId: string deviceId: string[]
quickRange: QuickRangeKey quickRange: QuickRangeKey
timeRange: [string, string] timeRange: [string, string]
} }
@ -53,6 +69,7 @@ export interface RunOverviewData {
metrics: RunOverviewMetric[] metrics: RunOverviewMetric[]
hourlyStatus: HourlyStatusItem[] hourlyStatus: HourlyStatusItem[]
summary: StatusSummaryItem[] summary: StatusSummaryItem[]
summaryTotalHours: number
timelineRows: DeviceTimelineRow[] timelineRows: DeviceTimelineRow[]
totalDevices: number totalDevices: number
} }

@ -22,12 +22,15 @@
@quick-range-change="handleQuickRangeChange" @quick-range-change="handleQuickRangeChange"
@query="handleQuery" @query="handleQuery"
@reset="resetQuery" @reset="resetQuery"
@export="handleExport"
/> />
<OverviewMetricCards :metrics="overviewData.metrics" /> <OverviewMetricCards :metrics="overviewData.metrics" />
<StatusDistributionChart :hourly-status="overviewData.hourlyStatus" :summary="overviewData.summary" /> <StatusDistributionChart
:hourly-status="overviewData.hourlyStatus"
:summary="overviewData.summary"
:summary-total-hours="overviewData.summaryTotalHours"
/>
<OperationTimelineChart <OperationTimelineChart
:rows="overviewData.timelineRows" :rows="overviewData.timelineRows"
@ -45,12 +48,22 @@
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useFullscreen } from '@vueuse/core' import { useFullscreen } from '@vueuse/core'
import { DeviceOperationOverviewApi } from '@/api/iot/deviceOperationOverview'
import { OrganizationApi } from '@/api/mes/organization'
import { handleTree } from '@/utils/tree'
import OverviewFilterBar from './components/OverviewFilterBar.vue' import OverviewFilterBar from './components/OverviewFilterBar.vue'
import OverviewMetricCards from './components/OverviewMetricCards.vue' import OverviewMetricCards from './components/OverviewMetricCards.vue'
import OperationTimelineChart from './components/OperationTimelineChart.vue' import OperationTimelineChart from './components/OperationTimelineChart.vue'
import StatusDistributionChart from './components/StatusDistributionChart.vue' import StatusDistributionChart from './components/StatusDistributionChart.vue'
import { buildDefaultQueryParams, buildRunOverviewData, DEVICE_OPTIONS, GROUP_OPTIONS } from './mock' import { buildDefaultQueryParams } from './mock'
import type { QuickRangeKey, RunOverviewData, RunOverviewQueryParams } from './components/types' import type {
OrganizationFilterItem,
OrganizationTreeOption,
OverviewOption,
QuickRangeKey,
RunOverviewData,
RunOverviewQueryParams
} from './components/types'
defineOptions({ name: 'IotRunOverview' }) defineOptions({ name: 'IotRunOverview' })
@ -58,9 +71,8 @@ const { t } = useI18n()
const message = useMessage() const message = useMessage()
const fullscreenTargetRef = ref() const fullscreenTargetRef = ref()
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen(fullscreenTargetRef) const { isFullscreen, toggle: toggleFullscreen } = useFullscreen(fullscreenTargetRef)
const organizationList = ref<OrganizationFilterItem[]>([])
const groupOptions = GROUP_OPTIONS const groupOptions = ref<OrganizationTreeOption[]>([])
const deviceOptions = DEVICE_OPTIONS
const createDateRangeByQuickKey = (key: QuickRangeKey): [string, string] => { const createDateRangeByQuickKey = (key: QuickRangeKey): [string, string] => {
const format = 'YYYY-MM-DD HH:mm:ss' const format = 'YYYY-MM-DD HH:mm:ss'
@ -83,40 +95,119 @@ const quickRanges = computed(() => [
{ label: t('DataCollection.RunOverview.quickRange.yesterday'), value: 'yesterday' as const }, { label: t('DataCollection.RunOverview.quickRange.yesterday'), value: 'yesterday' as const },
{ label: t('DataCollection.RunOverview.quickRange.last7Days'), value: 'last7Days' as const }, { label: t('DataCollection.RunOverview.quickRange.last7Days'), value: 'last7Days' as const },
{ label: t('DataCollection.RunOverview.quickRange.last30Days'), value: 'last30Days' as const }, { label: t('DataCollection.RunOverview.quickRange.last30Days'), value: 'last30Days' as const },
{ label: t('DataCollection.RunOverview.quickRange.custom'), value: 'custom' as const } /* { label: t('DataCollection.RunOverview.quickRange.custom'), value: 'custom' as const }*/
]) ])
const queryParams = ref<RunOverviewQueryParams>(buildDefaultQueryParams()) const queryParams = ref<RunOverviewQueryParams>(buildDefaultQueryParams())
const overviewData = ref<RunOverviewData>(buildRunOverviewData(queryParams.value))
const pageNo = ref(1) const pageNo = ref(1)
const pageSize = ref(10) const pageSize = ref(10)
const refreshData = () => { const organizationMap = computed(() => {
overviewData.value = buildRunOverviewData(queryParams.value) return organizationList.value.reduce<Record<string, OrganizationFilterItem>>((acc, item) => {
acc[String(item.id)] = item
return acc
}, {})
})
const childrenByParentId = computed(() => {
return organizationList.value.reduce<Record<string, OrganizationFilterItem[]>>((acc, item) => {
const parentKey = String(item.parentId ?? 0)
if (!acc[parentKey]) acc[parentKey] = []
acc[parentKey].push(item)
return acc
}, {})
})
const collectDeviceOptionsByGroup = (groupId?: string) => {
const deviceMap = new Map<string, OverviewOption>()
const pushDevice = (node?: OrganizationFilterItem) => {
if (!node?.dvId || !node.machineName?.trim()) return
const deviceId = String(node.dvId)
if (!deviceMap.has(deviceId)) {
deviceMap.set(deviceId, {
label: node.machineName.trim(),
value: deviceId
})
}
}
if (!groupId) {
organizationList.value.forEach(pushDevice)
return Array.from(deviceMap.values())
}
const queue = [groupId]
while (queue.length > 0) {
const currentId = queue.shift() as string
pushDevice(organizationMap.value[currentId])
const children = childrenByParentId.value[currentId] || []
children.forEach((child) => queue.push(String(child.id)))
}
return Array.from(deviceMap.values())
}
const deviceOptions = computed(() => collectDeviceOptionsByGroup(queryParams.value.groupId))
const overviewData = ref<RunOverviewData>({
metrics: [],
hourlyStatus: [],
summary: [],
summaryTotalHours: 0,
timelineRows: [],
totalDevices: 0
})
const currentDeviceIds = computed(() =>
queryParams.value.deviceId.length > 0 ? queryParams.value.deviceId : deviceOptions.value.map((item) => item.value)
)
const buildOverviewRequestParams = () => ({
ids: currentDeviceIds.value.join(','),
startTime: queryParams.value.timeRange[0],
endTime: queryParams.value.timeRange[1],
timelinePageNo: pageNo.value,
timelinePageSize: pageSize.value
})
const refreshData = async () => {
const response = await DeviceOperationOverviewApi.getRunOverview(buildOverviewRequestParams())
overviewData.value = {
metrics: response?.metrics || [],
hourlyStatus: response?.hourlyStatus || [],
summary: response?.summary || [],
summaryTotalHours: response?.summaryTotalHours || 0,
timelineRows: response?.timelineRows || [],
totalDevices: response?.totalDevices || 0
}
const maxPage = Math.max(1, Math.ceil(overviewData.value.totalDevices / pageSize.value)) const maxPage = Math.max(1, Math.ceil(overviewData.value.totalDevices / pageSize.value))
if (pageNo.value > maxPage) pageNo.value = maxPage if (pageNo.value > maxPage) pageNo.value = maxPage
} }
const resetToFirstPageAndRefresh = () => {
if (pageNo.value !== 1) {
pageNo.value = 1
return
}
void refreshData()
}
const handleQuickRangeChange = (key: QuickRangeKey) => { const handleQuickRangeChange = (key: QuickRangeKey) => {
queryParams.value = { queryParams.value = {
...queryParams.value, ...queryParams.value,
quickRange: key, quickRange: key,
timeRange: key === 'custom' ? queryParams.value.timeRange : createDateRangeByQuickKey(key) timeRange: createDateRangeByQuickKey(key)
} }
pageNo.value = 1 pageNo.value = 1
refreshData()
} }
const handleQuery = () => { const handleQuery = () => {
pageNo.value = 1 resetToFirstPageAndRefresh()
refreshData()
} }
const resetQuery = () => { const resetQuery = () => {
queryParams.value = buildDefaultQueryParams() queryParams.value = buildDefaultQueryParams()
pageNo.value = 1
pageSize.value = 10 pageSize.value = 10
refreshData() resetToFirstPageAndRefresh()
} }
const handleExport = () => { const handleExport = () => {
@ -126,7 +217,52 @@ const handleExport = () => {
const handlePageSizeChange = (size: number) => { const handlePageSizeChange = (size: number) => {
pageSize.value = size pageSize.value = size
pageNo.value = 1 pageNo.value = 1
void refreshData()
}
watch(pageNo, () => {
void refreshData()
})
const getOrganizationOptions = async () => {
const data = await OrganizationApi.getOrganizationList()
organizationList.value = Array.isArray(data)
? data.map((item) => ({
id: String(item.id),
name: item.name,
parentId: item.parentId != null ? String(item.parentId) : item.parentId,
dvId: item.dvId,
machineName: item.machineName
}))
: []
groupOptions.value = handleTree(
organizationList.value.map((item) => ({ ...item })),
'id',
'parentId'
) as OrganizationTreeOption[]
} }
watch(
deviceOptions,
(options) => {
if (queryParams.value.deviceId.length === 0) return
const validDeviceIds = queryParams.value.deviceId.filter((deviceId) =>
options.some((item) => item.value === deviceId)
)
if (validDeviceIds.length !== queryParams.value.deviceId.length) {
queryParams.value = {
...queryParams.value,
deviceId: validDeviceIds
}
}
},
{ immediate: true }
)
onMounted(async () => {
await getOrganizationOptions()
await refreshData()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

@ -1,178 +1,15 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { import type { RunOverviewQueryParams } from './components/types'
DeviceTimelineRow,
HourlyStatusItem,
OverviewOption,
RunOverviewData,
RunOverviewQueryParams,
RunOverviewMetric,
RunStatus
} from './components/types'
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
export const GROUP_OPTIONS: OverviewOption[] = [
{ label: 'SMT一组', value: 'group-1' },
{ label: '成型二组', value: 'group-2' },
{ label: '总装三组', value: 'group-3' }
]
export const DEVICE_OPTIONS: OverviewOption[] = [
{ label: '模拟干燥设备04', value: 'device-01' },
{ label: '模拟干燥设备03', value: 'device-02' },
{ label: '模拟干燥设备02', value: 'device-03' },
{ label: '成型模拟设备05', value: 'device-04' },
{ label: '成型模拟设备04', value: 'device-05' },
{ label: '模拟干燥设备', value: 'device-06' },
{ label: '成型模拟设备02', value: 'device-07' },
{ label: '成型模拟设备01', value: 'device-08' },
{ label: '物流输送设备01', value: 'device-09' },
{ label: '包装工作站02', value: 'device-10' },
{ label: '包装工作站03', value: 'device-11' },
{ label: '拧紧设备01', value: 'device-12' },
{ label: '视觉检测设备01', value: 'device-13' }
]
const shiftTimeline = (segments: Array<{ status: RunStatus; startHour: number; endHour: number }>, offset: number) =>
segments.map((segment, index) => {
let nextStart = segment.startHour + offset
let nextEnd = segment.endHour + offset
if (index === 0 && nextStart < 0) nextStart = 0
if (index === segments.length - 1 && nextEnd > 24) nextEnd = 24
return {
...segment,
startHour: Math.max(0, Math.min(24, Number(nextStart.toFixed(2)))),
endHour: Math.max(0, Math.min(24, Number(nextEnd.toFixed(2))))
}
})
const BASE_SEGMENTS = [
{ status: 'running' as const, startHour: 0, endHour: 2.7 },
{ status: 'standby' as const, startHour: 2.7, endHour: 3.1 },
{ status: 'running' as const, startHour: 3.1, endHour: 6.6 },
{ status: 'standby' as const, startHour: 6.6, endHour: 7.15 },
{ status: 'running' as const, startHour: 7.15, endHour: 13.9 },
{ status: 'standby' as const, startHour: 13.9, endHour: 14.25 },
{ status: 'offline' as const, startHour: 14.25, endHour: 14.95 },
{ status: 'running' as const, startHour: 14.95, endHour: 17.45 },
{ status: 'fault' as const, startHour: 17.45, endHour: 18.15 },
{ status: 'standby' as const, startHour: 18.15, endHour: 18.8 },
{ status: 'running' as const, startHour: 18.8, endHour: 20.55 },
{ status: 'standby' as const, startHour: 20.55, endHour: 20.95 },
{ status: 'running' as const, startHour: 20.95, endHour: 22.25 },
{ status: 'offline' as const, startHour: 22.25, endHour: 23.05 },
{ status: 'running' as const, startHour: 23.05, endHour: 23.7 },
{ status: 'offline' as const, startHour: 23.7, endHour: 24 }
]
const TIMELINE_OFFSETS = [0, -0.3, 0.8, -0.5, 0.45, 1.05, -1.1, 0.25, -0.75, 0.6, -0.2, 1.2, -0.45]
export const buildDefaultQueryParams = (): RunOverviewQueryParams => { export const buildDefaultQueryParams = (): RunOverviewQueryParams => {
const start = dayjs().startOf('day') const start = dayjs().startOf('day')
const end = dayjs().endOf('day') const end = dayjs().endOf('day')
return { return {
groupId: GROUP_OPTIONS[0].value, groupId: '',
deviceId: '', deviceId: [],
quickRange: 'today', quickRange: 'today',
timeRange: [start.format(DATE_TIME_FORMAT), end.format(DATE_TIME_FORMAT)] timeRange: [start.format(DATE_TIME_FORMAT), end.format(DATE_TIME_FORMAT)]
} }
} }
const toTimelineRows = (): DeviceTimelineRow[] =>
DEVICE_OPTIONS.map((device, index) => ({
id: device.value,
name: device.label,
utilizationRate: [82.35, 76.12, 68.54, 75.63, 72.18, 91.24, 65.32, 78.44, 71.05, 83.67, 69.18, 87.42, 74.56][index],
segments: shiftTimeline(BASE_SEGMENTS, TIMELINE_OFFSETS[index] || 0)
}))
const toHourlyStatus = (summaryFactor: number): HourlyStatusItem[] => {
return Array.from({ length: 24 }, (_, hour) => {
const runningBase = 74 + Math.sin((hour / 24) * Math.PI * 3) * 14 + summaryFactor * 2
const standbyBase = 10 + Math.cos((hour / 24) * Math.PI * 4) * 7
const faultBase = 2 + Math.max(0, Math.sin((hour - 5) / 2.2)) * 5
const offlineBase = 100 - runningBase - standbyBase - faultBase
const running = Math.max(48, Math.min(88, Number(runningBase.toFixed(2))))
const standby = Math.max(4, Math.min(26, Number(standbyBase.toFixed(2))))
const fault = Math.max(1, Math.min(8, Number(faultBase.toFixed(2))))
const offline = Number(Math.max(4, 100 - running - standby - fault).toFixed(2))
return {
hour: `${String(hour).padStart(2, '0')}:00`,
running,
standby,
fault,
offline
}
})
}
const createMetrics = (summaryFactor: number): RunOverviewMetric[] => [
{
key: 'utilizationRate',
icon: 'ep:pie-chart',
value: Number((75.42 + summaryFactor * 1.2).toFixed(2)),
unit: '%',
change: 4.32
},
{
key: 'powerOnRate',
icon: 'ep:video-play',
value: Number((90.12 + summaryFactor * 0.7).toFixed(2)),
unit: '%',
change: 2.15
},
{
key: 'faultRate',
icon: 'ep:warning',
value: Number(Math.max(1.5, 3.21 - summaryFactor * 0.35).toFixed(2)),
unit: '%',
change: -1.03
},
{
key: 'standbyRate',
icon: 'ep:timer',
value: Number(Math.max(4, 6.67 - summaryFactor * 0.25).toFixed(2)),
unit: '%',
change: -1.14
}
]
const createSummary = (summaryFactor: number) => {
const running = Number((75.42 + summaryFactor * 1.2).toFixed(2))
const standby = Number(Math.max(4, 6.67 - summaryFactor * 0.25).toFixed(2))
const fault = Number(Math.max(1.5, 3.21 - summaryFactor * 0.35).toFixed(2))
const offline = Number((100 - running - standby - fault).toFixed(2))
return [
{ status: 'running' as const, percent: running, hours: Number(((24 * running) / 100).toFixed(2)) },
{ status: 'standby' as const, percent: standby, hours: Number(((24 * standby) / 100).toFixed(2)) },
{ status: 'fault' as const, percent: fault, hours: Number(((24 * fault) / 100).toFixed(2)) },
{ status: 'offline' as const, percent: offline, hours: Number(((24 * offline) / 100).toFixed(2)) }
]
}
export const buildRunOverviewData = (query: RunOverviewQueryParams): RunOverviewData => {
const summaryFactor =
query.quickRange === 'today'
? 0
: query.quickRange === 'yesterday'
? -0.8
: query.quickRange === 'last7Days'
? 1.1
: query.quickRange === 'last30Days'
? 0.45
: 0.15
const rows = toTimelineRows()
const filteredRows = query.deviceId ? rows.filter((row) => row.id === query.deviceId) : rows
return {
metrics: createMetrics(summaryFactor),
hourlyStatus: toHourlyStatus(summaryFactor),
summary: createSummary(summaryFactor),
timelineRows: filteredRows,
totalDevices: filteredRows.length
}
}

@ -37,7 +37,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('FactoryModeling.ProductBOM.searchEnableLabel')" prop="isEnable"> <el-form-item :label="t('FactoryModeling.ProductBOM.searchEnableLabel')" prop="isEnable" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.isEnable" v-model="queryParams.isEnable"
:placeholder="t('FactoryModeling.ProductBOM.searchEnablePlaceholder')" :placeholder="t('FactoryModeling.ProductBOM.searchEnablePlaceholder')"
@ -54,6 +54,13 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button plain @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? '收起' : '更多' }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductBOM.searchButtonText') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductBOM.searchButtonText') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductBOM.resetButtonText') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductBOM.resetButtonText') }}</el-button>
@ -178,6 +185,13 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false) //
const filterCount = 4 // codeproductIdremarkisEnable
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {

@ -0,0 +1,191 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
min-label-width="80px"
>
<el-form-item :label="t('EquipmentManagement.CapacityReport.deviceCode')" prop="deviceCode">
<el-input
v-model="queryParams.deviceCode"
:placeholder="t('EquipmentManagement.CapacityReport.placeholderDeviceCode')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.CapacityReport.deviceName')" prop="deviceName">
<el-input
v-model="queryParams.deviceName"
:placeholder="t('EquipmentManagement.CapacityReport.placeholderDeviceName')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.CapacityReport.deviceType')" prop="deviceType">
<el-input
v-model="queryParams.deviceType"
:placeholder="t('EquipmentManagement.CapacityReport.placeholderDeviceType')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.CapacityReport.workshop')" prop="workshop" v-show="showAllFilters">
<el-input
v-model="queryParams.workshop"
:placeholder="t('EquipmentManagement.CapacityReport.placeholderWorkshop')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button plain @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? '收起' : '更多' }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading">
<Icon icon="ep:download" class="mr-5px" /> {{ t('EquipmentManagement.EquipmentLedger.export') }}
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
:row-key="(row) => row.id"
@select-all="handleSelectAll"
@select="handleSelect"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column :label="t('EquipmentManagement.CapacityReport.deviceCode')" align="center" prop="deviceCode" sortable />
<el-table-column :label="t('EquipmentManagement.CapacityReport.deviceName')" align="center" prop="deviceName" sortable />
<el-table-column :label="t('EquipmentManagement.CapacityReport.deviceType')" align="center" prop="typeName" sortable />
<el-table-column :label="t('EquipmentManagement.CapacityReport.ratedCapacity')" align="center" prop="ratedCapacity" sortable />
<el-table-column :label="t('EquipmentManagement.CapacityReport.reportCapacity')" align="center" prop="reportCapacity" sortable />
<el-table-column :label="t('EquipmentManagement.CapacityReport.actualCapacity')" align="center" prop="actualCapacity" sortable />
<el-table-column :label="t('EquipmentManagement.CapacityReport.workshop')" align="center" prop="workshopName" sortable />
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import download from '@/utils/download'
import { DeviceLedgerApi, CapacityReportVO } from '@/api/mes/device-ledger'
defineOptions({ name: 'CapacityReport' })
const message = useMessage()
const { t } = useI18n()
const loading = ref(true)
const list = ref<CapacityReportVO[]>([])
const total = ref(0)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
deviceCode: undefined,
deviceName: undefined,
deviceType: undefined,
workshop: undefined
})
const queryFormRef = ref()
const exportLoading = ref(false)
const showAllFilters = ref(false) //
const filterCount = 4 //
const selectedIds = ref<number[]>([])
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const getList = async () => {
loading.value = true
try {
const data = await DeviceLedgerApi.getCapacityReportPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
selectedIds.value = []
getList()
}
const resetQuery = () => {
queryFormRef.value?.resetFields()
selectedIds.value = []
handleQuery()
}
const handleSelectAll = (val: boolean) => {
if (val) {
selectedIds.value = list.value.map(item => item.id)
} else {
selectedIds.value = []
}
}
const handleSelect = (val: boolean, row: CapacityReportVO) => {
if (val) {
selectedIds.value.push(row.id)
} else {
const index = selectedIds.value.indexOf(row.id)
if (index > -1) {
selectedIds.value.splice(index, 1)
}
}
}
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const params: { ids?: string } = {}
if (selectedIds.value.length > 0) {
params.ids = selectedIds.value.join(',')
}
const data = await DeviceLedgerApi.exportCapacityReport(params)
download.excel(data, t('EquipmentManagement.CapacityReport.exportFilename'))
} catch {
} finally {
exportLoading.value = false
}
}
onMounted(() => {
getList()
})
</script>

File diff suppressed because it is too large Load Diff

@ -63,18 +63,7 @@
class="!w-full" :placeholder="t('EquipmentManagement.EquipmentLedger.placeholderRatedCapacity')" /> class="!w-full" :placeholder="t('EquipmentManagement.EquipmentLedger.placeholderRatedCapacity')" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col v-if="isScheduledEnabled" :span="12">
<el-form-item label="每日报工平均值" prop="dailyAverageValue" :required="isScheduledEnabled">
<el-input-number v-model="formData.dailyAverageValue" :min="0" :precision="0" controls-position="right"
class="!w-full" placeholder="请输入每日报工平均值" />
</el-form-item>
</el-col>
<el-col v-if="isScheduledEnabled" :span="12">
<el-form-item label="数据采集产能" prop="dataCollectionCapacity" :required="isScheduledEnabled">
<el-input-number v-model="formData.dataCollectionCapacity" :min="0" :precision="0"
controls-position="right" class="!w-full" placeholder="请输入数据采集产能" />
</el-form-item>
</el-col>
</el-row> </el-row>
@ -510,9 +499,7 @@ const initFormData = () => ({
fileUrl: undefined, fileUrl: undefined,
qrcodeUrl: undefined, qrcodeUrl: undefined,
sort: undefined, sort: undefined,
dvId: undefined, dvId: undefined
dailyAverageValue: undefined,
dataCollectionCapacity: undefined
}) })
const formData = ref({ const formData = ref({
@ -547,8 +534,6 @@ const formRules = reactive<FormRules>({
deviceName: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceName'), trigger: 'blur' }], deviceName: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceName'), trigger: 'blur' }],
deviceType: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceType'), trigger: 'change' }], deviceType: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceType'), trigger: 'change' }],
ratedCapacity: [{ validator: validateScheduledRequired('额定产能'), trigger: ['blur', 'change'] }], ratedCapacity: [{ validator: validateScheduledRequired('额定产能'), trigger: ['blur', 'change'] }],
dailyAverageValue: [{ validator: validateScheduledRequired('每日报工平均值'), trigger: ['blur', 'change'] }],
dataCollectionCapacity: [{ validator: validateScheduledRequired('数据采集产能'), trigger: ['blur', 'change'] }],
productionDate: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderProductionDate'), trigger: 'change' }], productionDate: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderProductionDate'), trigger: 'change' }],
factoryEntryDate: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderFactoryEntryDate'), trigger: 'change' }] factoryEntryDate: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderFactoryEntryDate'), trigger: 'change' }]
}) })
@ -557,7 +542,7 @@ const formRef = ref() // 表单 Ref
watch( watch(
() => formData.value.isScheduled, () => formData.value.isScheduled,
() => { () => {
formRef.value?.clearValidate?.(['ratedCapacity', 'dailyAverageValue', 'dataCollectionCapacity']) formRef.value?.clearValidate?.(['ratedCapacity'])
} }
) )
@ -705,8 +690,6 @@ const open = async (type: string, id?: number, defaultDeviceTypeId?: number) =>
componentIds: parseIdsValue((detail as any)?.componentId), componentIds: parseIdsValue((detail as any)?.componentId),
beijianIds: parseIdsValue((detail as any)?.beijianId), beijianIds: parseIdsValue((detail as any)?.beijianId),
qrcodeUrl: (detail as any)?.qrcodeUrl, qrcodeUrl: (detail as any)?.qrcodeUrl,
dailyAverageValue: normalizeNumberish((detail as any)?.dailyAverageValue),
dataCollectionCapacity: normalizeNumberish((detail as any)?.dataCollectionCapacity),
} }
} finally { } finally {
formLoading.value = false formLoading.value = false
@ -934,9 +917,7 @@ const submitForm = async () => {
deviceManager: formData.value.deviceManagerIds?.length ? formData.value.deviceManagerIds.join(',') : undefined, deviceManager: formData.value.deviceManagerIds?.length ? formData.value.deviceManagerIds.join(',') : undefined,
componentId: formData.value.componentIds?.length ? formData.value.componentIds.join(',') : undefined, componentId: formData.value.componentIds?.length ? formData.value.componentIds.join(',') : undefined,
beijianId: formData.value.beijianIds?.length ? formData.value.beijianIds.join(',') : undefined, beijianId: formData.value.beijianIds?.length ? formData.value.beijianIds.join(',') : undefined,
fileUrl: normalizeFileUrlAsJsonArrayString((formData.value as any).fileUrl), fileUrl: normalizeFileUrlAsJsonArrayString((formData.value as any).fileUrl)
dailyAverageValue: normalizeNumberish((formData.value as any).dailyAverageValue),
dataCollectionCapacity: normalizeNumberish((formData.value as any).dataCollectionCapacity)
} as unknown as DeviceLedgerVO } as unknown as DeviceLedgerVO
delete (data as any).deviceManagerIds delete (data as any).deviceManagerIds
delete (data as any).componentIds delete (data as any).componentIds

@ -40,7 +40,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.FeedingRecord.searchUserLabel')" prop="userId"> <el-form-item :label="t('ProductionPlan.FeedingRecord.searchUserLabel')" prop="userId" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.userId" v-model="queryParams.userId"
:placeholder="t('ProductionPlan.FeedingRecord.searchUserPlaceholder')" :placeholder="t('ProductionPlan.FeedingRecord.searchUserPlaceholder')"
@ -49,7 +49,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.FeedingRecord.searchRemarkLabel')" prop="remark"> <el-form-item :label="t('ProductionPlan.FeedingRecord.searchRemarkLabel')" prop="remark" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.remark" v-model="queryParams.remark"
:placeholder="t('ProductionPlan.FeedingRecord.searchRemarkPlaceholder')" :placeholder="t('ProductionPlan.FeedingRecord.searchRemarkPlaceholder')"
@ -58,6 +58,14 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button plain @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? '收起' : '更多' }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.FeedingRecord.buttonSearchText') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.FeedingRecord.buttonSearchText') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('ProductionPlan.FeedingRecord.buttonResetText') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('ProductionPlan.FeedingRecord.buttonResetText') }}</el-button>
@ -214,6 +222,13 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false) //
const filterCount = 4 //
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {

@ -54,7 +54,7 @@
/> />
</el-select> </el-select>
</el-form-item> --> </el-form-item> -->
<el-form-item :label="t('FactoryModeling.FactoryStructure.searchOrgClassLabel')" prop="orgClass"> <el-form-item :label="t('FactoryModeling.FactoryStructure.searchOrgClassLabel')" prop="orgClass" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.orgClass" v-model="queryParams.orgClass"
:placeholder="t('FactoryModeling.FactoryStructure.searchOrgClassPlaceholder')" :placeholder="t('FactoryModeling.FactoryStructure.searchOrgClassPlaceholder')"
@ -70,6 +70,13 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button plain @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? '收起' : '更多' }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.FactoryStructure.searchButtonText') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.FactoryStructure.searchButtonText') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.FactoryStructure.resetButtonText') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.FactoryStructure.resetButtonText') }}</el-button>
@ -219,6 +226,13 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false) //
const filterCount = 4 // codeparentIdnameorgClass
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {

@ -18,22 +18,30 @@
<el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.searchRemarkLabel')" prop="remark"> <el-form-item :label="t('ProductionPlan.Plan.searchRemarkLabel')" prop="remark" v-show="showAllFilters">
<el-input v-model="queryParams.remark" :placeholder="t('ProductionPlan.Plan.searchRemarkPlaceholder')" clearable <el-input v-model="queryParams.remark" :placeholder="t('ProductionPlan.Plan.searchRemarkPlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-180px" /> @keyup.enter="handleQuery" class="!w-180px" />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.searchPlanStartLabel')" prop="planStartTime"> <el-form-item :label="t('ProductionPlan.Plan.searchPlanStartLabel')" prop="planStartTime" v-show="showAllFilters">
<el-date-picker v-model="queryParams.planStartTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" <el-date-picker v-model="queryParams.planStartTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
@change="handleQuery" :start-placeholder="t('ProductionPlan.Plan.searchPlanStartStartPlaceholder')" @change="handleQuery" :start-placeholder="t('ProductionPlan.Plan.searchPlanStartStartPlaceholder')"
:end-placeholder="t('ProductionPlan.Plan.searchPlanStartEndPlaceholder')" :end-placeholder="t('ProductionPlan.Plan.searchPlanStartEndPlaceholder')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" /> :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.searchPlanEndLabel')" prop="planEndTime"> <el-form-item :label="t('ProductionPlan.Plan.searchPlanEndLabel')" prop="planEndTime" v-show="showAllFilters">
<el-date-picker v-model="queryParams.planEndTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" <el-date-picker v-model="queryParams.planEndTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
@change="handleQuery" :start-placeholder="t('ProductionPlan.Plan.searchPlanEndStartPlaceholder')" @change="handleQuery" :start-placeholder="t('ProductionPlan.Plan.searchPlanEndStartPlaceholder')"
:end-placeholder="t('ProductionPlan.Plan.searchPlanEndEndPlaceholder')" :end-placeholder="t('ProductionPlan.Plan.searchPlanEndEndPlaceholder')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" /> :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button plain @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? '收起' : '更多' }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"> <el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.Plan.buttonSearchText') }} <Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.Plan.buttonSearchText') }}
@ -321,6 +329,14 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false) //
const filterCount = 4 //
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const warehouseList = ref<WarehouseVO[]>([]) // const warehouseList = ref<WarehouseVO[]>([]) //
const storeDialogVisible = ref(false) // const storeDialogVisible = ref(false) //
const storeWarehouseId = ref<number>() // ID const storeWarehouseId = ref<number>() // ID

@ -41,7 +41,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionReport.Index.searchRemarkLabel')" prop="remark"> <el-form-item :label="t('ProductionReport.Index.searchRemarkLabel')" prop="remark" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.remark" v-model="queryParams.remark"
:placeholder="t('ProductionReport.Index.searchRemarkPlaceholder')" :placeholder="t('ProductionReport.Index.searchRemarkPlaceholder')"
@ -50,7 +50,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionReport.Index.searchCreateTimeLabel')" prop="createTime"> <el-form-item :label="t('ProductionReport.Index.searchCreateTimeLabel')" prop="createTime" v-show="showAllFilters">
<el-date-picker <el-date-picker
v-model="queryParams.createTime" v-model="queryParams.createTime"
@change="handleQuery" @change="handleQuery"
@ -62,6 +62,14 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button plain @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? '收起' : '更多' }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"> <el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionReport.Index.buttonSearch') }} <Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionReport.Index.buttonSearch') }}
@ -170,6 +178,13 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() const queryFormRef = ref()
const exportLoading = ref(false) const exportLoading = ref(false)
const showAllFilters = ref(false) //
const filterCount = 4 //
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const activeStatusTab = ref('') const activeStatusTab = ref('')

@ -26,7 +26,7 @@
:start-placeholder="t('ProductionPlan.Task.searchOrderStartPlaceholder')" :start-placeholder="t('ProductionPlan.Task.searchOrderStartPlaceholder')"
:end-placeholder="t('ProductionPlan.Task.searchOrderEndPlaceholder')" :end-placeholder="t('ProductionPlan.Task.searchOrderEndPlaceholder')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-180px" class="!w-280px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.Task.searchDeliveryLabel')" prop="deliveryDate"> <el-form-item :label="t('ProductionPlan.Task.searchDeliveryLabel')" prop="deliveryDate">
@ -38,11 +38,11 @@
:start-placeholder="t('ProductionPlan.Task.searchDeliveryStartPlaceholder')" :start-placeholder="t('ProductionPlan.Task.searchDeliveryStartPlaceholder')"
:end-placeholder="t('ProductionPlan.Task.searchDeliveryEndPlaceholder')" :end-placeholder="t('ProductionPlan.Task.searchDeliveryEndPlaceholder')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-180px" class="!w-280px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.Task.searchRemarkLabel')" prop="remark"> <el-form-item :label="t('ProductionPlan.Task.searchRemarkLabel')" prop="remark" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.remark" v-model="queryParams.remark"
:placeholder="t('ProductionPlan.Task.searchRemarkPlaceholder')" :placeholder="t('ProductionPlan.Task.searchRemarkPlaceholder')"
@ -51,7 +51,7 @@
class="!w-180px" class="!w-180px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.Task.searchCreateTimeLabel')" prop="createTime"> <el-form-item :label="t('ProductionPlan.Task.searchCreateTimeLabel')" prop="createTime" v-show="showAllFilters">
<el-date-picker <el-date-picker
v-model="queryParams.createTime" v-model="queryParams.createTime"
@change="handleQuery" @change="handleQuery"
@ -60,9 +60,17 @@
:start-placeholder="t('ProductionPlan.Task.searchCreateTimeStartPlaceholder')" :start-placeholder="t('ProductionPlan.Task.searchCreateTimeStartPlaceholder')"
:end-placeholder="t('ProductionPlan.Task.searchCreateTimeEndPlaceholder')" :end-placeholder="t('ProductionPlan.Task.searchCreateTimeEndPlaceholder')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px" class="!w-280px"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button plain @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? '收起' : '更多' }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.Task.buttonSearchText') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.Task.buttonSearchText') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('ProductionPlan.Task.buttonResetText') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('ProductionPlan.Task.buttonResetText') }}</el-button>
@ -221,6 +229,13 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false) //
const filterCount = 4 //
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
@ -326,3 +341,13 @@ const changeStatus = async (type:string, status: number, id: number) => {
let activeListName = 'taskDetail' let activeListName = 'taskDetail'
</script> </script>
<style scoped>
.task-search-form {
display: flex;
flex-wrap: wrap;
}
.task-search-form :deep(.el-form-item) {
margin-right: 16px;
}
</style>

@ -216,7 +216,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { useDictStoreWithOut } from '@/store/modules/dict'
import { TaskApi } from '@/api/mes/task' import { TaskApi } from '@/api/mes/task'
import { ProductApi } from '@/api/erp/product/product' import { ProductApi } from '@/api/erp/product/product'
import { DeviceLedgerApi } from '@/api/mes/deviceledger' import { DeviceLedgerApi } from '@/api/mes/deviceledger'
@ -228,8 +229,10 @@ import TaskSchedulePreviewDialog from './TaskSchedulePreviewDialog.vue'
defineOptions({ name: 'TaskScheduleDialog' }) defineOptions({ name: 'TaskScheduleDialog' })
const message = useMessage() const message = useMessage()
const { t } = useI18n() // const { t } = useI18n()
const emit = defineEmits(['success']) const emit = defineEmits(['success'])
const dictStore = useDictStoreWithOut()
const dictReady = ref(false)
const dialogVisible = ref(false) const dialogVisible = ref(false)
const taskLoading = ref(false) const taskLoading = ref(false)
@ -270,11 +273,10 @@ const scheduleRuleOptions = computed(() => [
{ label: t('ProductionPlan.TaskSummary.scheduleRuleCategory'), value: 3 }, { label: t('ProductionPlan.TaskSummary.scheduleRuleCategory'), value: 3 },
{ label: t('ProductionPlan.TaskSummary.scheduleRuleDelivery'), value: 4 } { label: t('ProductionPlan.TaskSummary.scheduleRuleDelivery'), value: 4 }
]) ])
const capacityTypeOptions = computed(() => [ const capacityTypeOptions = computed(() => {
{ label: t('ProductionPlan.TaskSummary.capacityTypeRated'), value: 1 }, if (!dictReady.value) return []
{ label: t('ProductionPlan.TaskSummary.capacityTypeDailyAvg'), value: 2 }, return getIntDictOptions('capacity_sources')
{ label: t('ProductionPlan.TaskSummary.capacityTypeDataCollection'), value: 3 } })
])
const searchForm = reactive({ const searchForm = reactive({
inventoryTaskSchedule: false, inventoryTaskSchedule: false,
@ -774,6 +776,8 @@ const handleSubmit = async () => {
} }
const open = async () => { const open = async () => {
await dictStore.setDictMap()
dictReady.value = true
dialogVisible.value = true dialogVisible.value = true
await loadTaskList() await loadTaskList()
} }

@ -1,21 +1,33 @@
<template> <template>
<Dialog v-model="previewVisible" title="排产甘特图预览" width="100%" align-center> <Dialog v-model="previewVisible" :title="t('GanttChart.GanttPanel.dialogTitle')" width="100%" align-center>
<div class="preview-options"> <div class="preview-options">
<el-form :inline="true"> <el-form :inline="true">
<el-form-item label="领料人"> <el-form-item :label="t('GanttChart.GanttPanel.workerLabel')">
<el-select v-model="scheduleOptions.workerId" clearable filterable placeholder="请选择领料人" style="width: 200px"> <el-select v-model="scheduleOptions.workerId" clearable filterable :placeholder="t('GanttChart.GanttPanel.workerPlaceholder')" style="width: 200px">
<el-option v-for="item in workerList" :key="item.id" :label="item.nickname" :value="item.id" /> <el-option v-for="item in workerList" :key="item.id" :label="item.nickname" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="是否计算损耗"> <el-form-item :label="t('GanttChart.GanttPanel.calcLossLabel')">
<el-switch v-model="scheduleOptions.isCalculateLoss" /> <el-switch v-model="scheduleOptions.isCalculateLoss" />
</el-form-item> </el-form-item>
<el-form-item>
<el-button :type="ganttEditable ? 'primary' : 'default'" @click="toggleGanttEditable">
<Icon :icon="ganttEditable ? 'ep:unlock' : 'ep:lock'" class="mr-4px" />
{{ ganttEditable ? t('GanttChart.GanttPanel.unlockBtn') : t('GanttChart.GanttPanel.lockBtn') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button :disabled="!ganttEditable" @click="handleUndo">
<Icon icon="ep:refresh-left" class="mr-4px" />
{{ t('GanttChart.GanttPanel.undoBtn') }}
</el-button>
</el-form-item>
</el-form> </el-form>
</div> </div>
<ScheduleGanttPanelEditable :schedule-list="previewScheduleList" height="800px" /> <ScheduleGanttPanelEditable ref="ganttPanelRef" :schedule-list="previewScheduleList" :editable="ganttEditable" height="800px" />
<template #footer> <template #footer>
<el-button type="primary" :loading="previewSaveLoading" @click="handlePreviewSave"></el-button> <el-button type="primary" :loading="previewSaveLoading" @click="handlePreviewSave">{{ t('GanttChart.GanttPanel.buttonSave') }}</el-button>
<el-button @click="previewVisible = false">关闭</el-button> <el-button @click="previewVisible = false">{{ t('GanttChart.GanttPanel.buttonClose') }}</el-button>
</template> </template>
</Dialog> </Dialog>
</template> </template>
@ -25,9 +37,12 @@ import { PlanApi } from '@/api/mes/plan'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import ScheduleGanttPanelEditable from './ScheduleGanttPanelEditable.vue' import ScheduleGanttPanelEditable from './ScheduleGanttPanelEditable.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useI18n } from '@/hooks/web/useI18n'
defineOptions({ name: 'TaskSchedulePreviewDialog' }) defineOptions({ name: 'TaskSchedulePreviewDialog' })
const { t } = useI18n()
const props = defineProps<{ const props = defineProps<{
modelValue: boolean modelValue: boolean
scheduleList: any[] scheduleList: any[]
@ -40,6 +55,16 @@ const emit = defineEmits<{
const message = useMessage() const message = useMessage()
const previewSaveLoading = ref(false) const previewSaveLoading = ref(false)
const ganttEditable = ref(false)
const ganttPanelRef = ref<InstanceType<typeof ScheduleGanttPanelEditable>>()
const toggleGanttEditable = () => {
ganttEditable.value = !ganttEditable.value
}
const handleUndo = () => {
ganttPanelRef.value?.undo()
}
const workerList = ref<UserApi.UserVO[]>([]) const workerList = ref<UserApi.UserVO[]>([])
const scheduleOptions = reactive({ const scheduleOptions = reactive({
@ -61,8 +86,11 @@ const previewVisible = computed({
}) })
watch(previewVisible, (visible) => { watch(previewVisible, (visible) => {
if (visible && workerList.value.length === 0) { if (visible) {
loadWorkerList() if (workerList.value.length === 0) {
loadWorkerList()
}
ganttEditable.value = false
} }
}) })
@ -100,14 +128,14 @@ const handlePreviewSave = async () => {
}) })
if (!createReqVOList.length) { if (!createReqVOList.length) {
message.warning('暂无可保存的计划数据') message.warning(t('GanttChart.GanttPanel.warningNoPlanData'))
return return
} }
previewSaveLoading.value = true previewSaveLoading.value = true
try { try {
await PlanApi.createBatch({ createReqVOList }) await PlanApi.createBatch({ createReqVOList })
message.success('排产计划保存成功') message.success(t('GanttChart.GanttPanel.saveSuccess'))
previewVisible.value = false previewVisible.value = false
emit('saved') emit('saved')
} finally { } finally {

@ -23,18 +23,26 @@ v-model="queryParams.deliveryDate" value-format="YYYY-MM-DD HH:mm:ss" @change="h
:end-placeholder="t('ProductionPlan.TaskSummary.searchDeliveryEndPlaceholder')" :end-placeholder="t('ProductionPlan.TaskSummary.searchDeliveryEndPlaceholder')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" /> :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.TaskSummary.searchRemarkLabel')" prop="remark"> <el-form-item :label="t('ProductionPlan.TaskSummary.searchRemarkLabel')" prop="remark" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.remark" :placeholder="t('ProductionPlan.TaskSummary.searchRemarkPlaceholder')" v-model="queryParams.remark" :placeholder="t('ProductionPlan.TaskSummary.searchRemarkPlaceholder')"
clearable @keyup.enter="handleQuery" class="!w-240px" /> clearable @keyup.enter="handleQuery" class="!w-240px" />
</el-form-item> </el-form-item>
<el-form-item :label="t('ProductionPlan.TaskSummary.searchCreateTimeLabel')" prop="createTime"> <el-form-item :label="t('ProductionPlan.TaskSummary.searchCreateTimeLabel')" prop="createTime" v-show="showAllFilters">
<el-date-picker <el-date-picker
v-model="queryParams.createTime" @change="handleQuery" value-format="YYYY-MM-DD HH:mm:ss" v-model="queryParams.createTime" @change="handleQuery" value-format="YYYY-MM-DD HH:mm:ss"
type="daterange" :start-placeholder="t('ProductionPlan.TaskSummary.searchCreateTimeStartPlaceholder')" type="daterange" :start-placeholder="t('ProductionPlan.TaskSummary.searchCreateTimeStartPlaceholder')"
:end-placeholder="t('ProductionPlan.TaskSummary.searchCreateTimeEndPlaceholder')" :end-placeholder="t('ProductionPlan.TaskSummary.searchCreateTimeEndPlaceholder')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" /> :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button plain @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? '收起' : '更多' }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"> <el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.TaskSummary.buttonSearchText') }} <Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.TaskSummary.buttonSearchText') }}
@ -171,6 +179,14 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false) //
const filterCount = 4 //
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const { push } = useRouter() const { push } = useRouter()
const taskScheduleDialogRef = ref() const taskScheduleDialogRef = ref()
const deliveryDateFormatter = (_row: any, _column: any, value: any) => { const deliveryDateFormatter = (_row: any, _column: any, value: any) => {

Loading…
Cancel
Save