Merge remote-tracking branch 'origin/main'

main
liutao 15 hours ago
commit ebff0725ef

@ -215,6 +215,11 @@ export const DeviceApi = {
return await request.put({ url: `/iot/device-contact-model/update`, data }) return await request.put({ url: `/iot/device-contact-model/update`, data })
}, },
// 批量修改设备属性
batchUpdateDeviceAttribute: async (data) => {
return await request.put({ url: '/iot/device/device-attribute/batch-update', data })
},
// 删除设备属性 // 删除设备属性
deleteDeviceAttribute: async (ids: string) => { deleteDeviceAttribute: async (ids: string) => {
return await request.delete({ url: `/iot/device-contact-model/delete?ids=` + ids }) return await request.delete({ url: `/iot/device-contact-model/delete?ids=` + ids })

@ -2007,7 +2007,11 @@ export default {
validatorDeviceCodeRequired: '编码不能为空', validatorDeviceCodeRequired: '编码不能为空',
gjTitle: '选择关键件', gjTitle: '选择关键件',
bjTitle: '选择备件', bjTitle: '选择备件',
all: '全部',
normal: '正常', normal: '正常',
disabled: '停用',
scheduled: '已排产',
unscheduled: '未排产',
stop: '停用', stop: '停用',
maintenance: '维修', maintenance: '维修',
scrap: '报废', scrap: '报废',
@ -2149,6 +2153,7 @@ export default {
// 任务管理 // 任务管理
TaskManagement: { TaskManagement: {
all: '全部',
name: '名称', name: '名称',
taskType: '类型', taskType: '类型',
taskTypeInspect: '点检', taskTypeInspect: '点检',
@ -2159,6 +2164,7 @@ export default {
planEndDate: '计划结束时间', planEndDate: '计划结束时间',
cronExpression: 'cron 表达式', cronExpression: 'cron 表达式',
enabled: '启用', enabled: '启用',
disabled: '未启用',
creatorName: '创建人', creatorName: '创建人',
createTime: '创建时间', createTime: '创建时间',
updateTime: '更新时间', updateTime: '更新时间',
@ -2199,11 +2205,17 @@ export default {
// 工单管理 // 工单管理
WorkOrderManagement: { WorkOrderManagement: {
all: '全部',
basicInfo: '基本信息', basicInfo: '基本信息',
planNo: '单号', planNo: '单号',
planType: '类型', planType: '类型',
planTypeInspect: '点检', planTypeInspect: '点检',
planTypeMaintain: '保养', planTypeMaintain: '保养',
unworked: '未作业',
worked: '已作业',
overtimeUnworked: '超时未作业',
overtimeWorked: '超时已作业',
canceled: '取消',
jobStatus: '作业状态', jobStatus: '作业状态',
jobResult: '结果', jobResult: '结果',
deviceName: '设备名称', deviceName: '设备名称',
@ -2253,6 +2265,7 @@ export default {
// 维修单 // 维修单
DvRepair: { DvRepair: {
all: '全部',
repairCode: '维修单号', repairCode: '维修单号',
repairName: '维修单', repairName: '维修单',
machineryName: '设备名称', machineryName: '设备名称',
@ -3115,6 +3128,7 @@ export default {
}, },
MoldRepair: { MoldRepair: {
all: '全部',
moduleName: '维修单', moduleName: '维修单',
basicInfo: '基本信息', basicInfo: '基本信息',
faultInfo: '故障信息', faultInfo: '故障信息',
@ -3322,12 +3336,18 @@ export default {
}, },
MoldWorkOrderInquiry: { MoldWorkOrderInquiry: {
all: '全部',
moduleName: '工单查询', moduleName: '工单查询',
basicInfo: '基本信息', basicInfo: '基本信息',
planNo: '单号', planNo: '单号',
planType: '类型', planType: '类型',
planTypeInspect: '点检', planTypeInspect: '点检',
planTypeMaintain: '保养', planTypeMaintain: '保养',
unworked: '未作业',
worked: '已作业',
overtimeUnworked: '超时未作业',
overtimeWorked: '超时已作业',
canceled: '取消',
jobStatus: '作业状态', jobStatus: '作业状态',
jobResult: '结果', jobResult: '结果',
jobResultOk: '通过', jobResultOk: '通过',

@ -230,9 +230,9 @@ const onKeywordClear = () => {
const statusCards = computed(() => [ const statusCards = computed(() => [
{ key: 'all', label: t('MoldManagement.MoldBrandPage.all'), value: counters.allCount, status: undefined }, { key: 'all', label: t('MoldManagement.MoldBrandPage.all'), value: counters.allCount, status: undefined },
{ key: 'onMachine', label: t('MoldManagement.MoldBrandPage.onMachine'), value: counters.onMachineCount, status: 1 }, { key: 'onMachine', label: t('MoldManagement.MoldBrandPage.onMachine'), value: counters.onMachineCount, status: 0 },
{ key: 'inStock', label: t('MoldManagement.MoldBrandPage.inStock'), value: counters.inStockCount, status: 4 }, { key: 'inStock', label: t('MoldManagement.MoldBrandPage.inStock'), value: counters.inStockCount, status: 4 },
{ key: 'standby', label: t('MoldManagement.MoldBrandPage.standby'), value: counters.standbyCount, status: 0 }, { key: 'standby', label: t('MoldManagement.MoldBrandPage.standby'), value: counters.standbyCount, status: 1 },
{ key: 'repairing', label: t('MoldManagement.MoldBrandPage.repairing'), value: counters.repairingCount, status: 2 }, { key: 'repairing', label: t('MoldManagement.MoldBrandPage.repairing'), value: counters.repairingCount, status: 2 },
{ key: 'scrapped', label: t('MoldManagement.MoldBrandPage.scrapped'), value: counters.scrappedCount, status: 3 } { key: 'scrapped', label: t('MoldManagement.MoldBrandPage.scrapped'), value: counters.scrappedCount, status: 3 }
]) ])
@ -458,12 +458,12 @@ onBeforeUnmount(() => {
<style scoped> <style scoped>
.mold-brand-page__header { .mold-brand-page__header {
display: grid; display: grid;
grid-template-columns: repeat(6, 10vw); grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 12px; gap: 12px;
width: 100%;
} }
.mold-brand-page__stat { .mold-brand-page__stat {
width: 10vw;
padding: 16px 18px; padding: 16px 18px;
border: 1px solid var(--el-border-color-light); border: 1px solid var(--el-border-color-light);
border-radius: 12px; border-radius: 12px;
@ -553,13 +553,13 @@ onBeforeUnmount(() => {
@media (max-width: 1200px) { @media (max-width: 1200px) {
.mold-brand-page__header { .mold-brand-page__header {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.mold-brand-page__header { .mold-brand-page__header {
grid-template-columns: 1fr; grid-template-columns: repeat(2, minmax(0, 1fr));
} }
} }
</style> </style>

@ -33,8 +33,7 @@
</ContentWrap> </ContentWrap>
<div v-loading="cardsLoading" class="stock-overview__cards"> <div v-loading="cardsLoading" class="stock-overview__cards">
<ContentWrap v-for="item in summaryCards" :key="item.title" class="stock-overview__card-wrap" <div v-for="item in summaryCards" :key="item.title" class="stock-overview__card-wrap">
:body-style="{ padding: '22px 24px' }">
<div class="stock-overview__card"> <div class="stock-overview__card">
<div class="stock-overview__card-main"> <div class="stock-overview__card-main">
<div class="stock-overview__card-title">{{ item.title }}</div> <div class="stock-overview__card-title">{{ item.title }}</div>
@ -45,7 +44,7 @@
<Icon :icon="item.icon" :class="['stock-overview__card-icon', `is-${item.color}`]" :size="34" /> <Icon :icon="item.icon" :class="['stock-overview__card-icon', `is-${item.color}`]" :size="34" />
</div> </div>
<div v-if="item.tip" class="stock-overview__card-tip">{{ item.tip }}</div> <div v-if="item.tip" class="stock-overview__card-tip">{{ item.tip }}</div>
</ContentWrap> </div>
</div> </div>
<div class="stock-overview__tables"> <div class="stock-overview__tables">
@ -507,13 +506,22 @@ onMounted(async () => {
} }
.stock-overview__card-wrap { .stock-overview__card-wrap {
padding: 16px 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
background: #fff;
cursor: pointer;
transition: box-shadow 0.2s ease;
margin-bottom: 0 !important; margin-bottom: 0 !important;
&:hover {
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
} }
.stock-overview__card { .stock-overview__card {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
min-height: 174px;
} }
.stock-overview__card-main { .stock-overview__card-main {

@ -38,7 +38,7 @@ v-model="formData.dataUnit" clearable
<el-form-item :label="t('DataCollection.Device.ratio')" prop="ratio"> <el-form-item :label="t('DataCollection.Device.ratio')" prop="ratio">
<el-input-number <el-input-number
v-model="formData.ratio" :placeholder="t('DataCollection.Device.placeholderRatio')" :min="0.00" v-model="formData.ratio" :placeholder="t('DataCollection.Device.placeholderRatio')" :min="0.00"
:decision="2" :step="0.01" class="!w-full" :disabled="!ratioEnabled" /> :decision="2" :step="0.01" class="!w-full" />
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.Device.remark')" prop="remark"> <el-form-item :label="t('DataCollection.Device.remark')" prop="remark">
<el-input <el-input
@ -174,7 +174,8 @@ const formRules = reactive({
}, },
trigger: ['blur', 'change'] trigger: ['blur', 'change']
} }
] ],
attributeType: [{ required: true, message: '点位类型不能为空', trigger: 'change' }]
}) })
const formRef = ref() // Ref const formRef = ref() // Ref

@ -76,6 +76,9 @@
> >
<Icon icon="ep:delete" class="mr-5px" /> {{ t('DataCollection.Device.batchDelete') }} <Icon icon="ep:delete" class="mr-5px" /> {{ t('DataCollection.Device.batchDelete') }}
</el-button> </el-button>
<el-button type="primary" plain @click="openBatchEdit" v-hasPermi="['iot:device:update']">
<Icon icon="ep:edit" class="mr-5px" /> 批量编辑
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
@ -137,7 +140,11 @@ ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-to
align="center" align="center"
prop="dataUnit" prop="dataUnit"
width="80px" width="80px"
/> >
<template #default="scope">
{{ formatDataUnit(scope.row.dataUnit) }}
</template>
</el-table-column>
<el-table-column <el-table-column
:label="t('DataCollection.Device.ratio')" :label="t('DataCollection.Device.ratio')"
align="center" align="center"
@ -193,6 +200,27 @@ ref="planPaginationRef"
<DeviceAttributeForm ref="formRef" @success="getList" /> <DeviceAttributeForm ref="formRef" @success="getList" />
<!-- 单位导入对话框 --> <!-- 单位导入对话框 -->
<Dialog title="批量编辑" v-model="batchEditVisible">
<el-form ref="batchEditFormRef" :model="batchEditForm" label-width="120px">
<el-form-item :label="t('DataCollection.Device.attributeType')">
<el-select v-model="batchEditForm.attributeType" clearable filterable :placeholder="t('DataCollection.Device.placeholderAttributeType')" class="w-1/1">
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.dataUnit')">
<el-select v-model="batchEditForm.dataUnit" clearable :placeholder="t('DataCollection.DeviceModel.placeholderDataUnit')" class="w-1/1">
<el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.ratio')">
<el-input-number v-model="batchEditForm.ratio" :placeholder="t('DataCollection.Device.placeholderRatio')" :min="0" :precision="2" :step="0.01" class="!w-full" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="batchEditVisible = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" :loading="batchEditLoading" @click="handleBatchEdit">{{ t('common.ok') }}</el-button>
</template>
</Dialog>
<DeviceAttributeImportForm ref="importFormRef" @success="getList" /> <DeviceAttributeImportForm ref="importFormRef" @success="getList" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -203,6 +231,7 @@ import { DeviceApi } from '@/api/iot/device'
import DeviceAttributeForm from './DeviceAttributeForm.vue' import DeviceAttributeForm from './DeviceAttributeForm.vue'
import DeviceAttributeImportForm from './DeviceAttributeImportForm.vue' import DeviceAttributeImportForm from './DeviceAttributeImportForm.vue'
import { DeviceAttributeTypeApi, DeviceAttributeTypeVO } from '@/api/iot/deviceattributetype' import { DeviceAttributeTypeApi, DeviceAttributeTypeVO } from '@/api/iot/deviceattributetype'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
@ -225,6 +254,7 @@ const props = defineProps<{
}>() }>()
const typeList = ref<DeviceAttributeTypeVO[]>([]) const typeList = ref<DeviceAttributeTypeVO[]>([])
const unitList = ref<ProductUnitVO[]>([])
const loading = ref(false) // const loading = ref(false) //
const list = ref([]) // const list = ref([]) //
@ -252,6 +282,19 @@ const formatAddressValue = (value: unknown) => {
return '' return ''
} }
const loadUnitList = async () => {
if (unitList.value.length) {
return
}
unitList.value = await ProductUnitApi.getProductUnitSimpleList()
}
const formatDataUnit = (value: unknown) => {
if (value === null || value === undefined || value === '') {
return '-'
}
return unitList.value.find((unit) => String(unit.id) === String(value))?.name ?? String(value)
}
const selectedIds = ref<number[]>([]) const selectedIds = ref<number[]>([])
const handleSelectionChange = (rows: any[]) => { const handleSelectionChange = (rows: any[]) => {
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? [] selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? []
@ -353,6 +396,51 @@ const handleBatchDelete = async () => {
await handleDelete(selectedIds.value) await handleDelete(selectedIds.value)
} }
const batchEditVisible = ref(false)
const batchEditLoading = ref(false)
const batchEditFormRef = ref()
const batchEditForm = reactive({
attributeType: undefined as number | undefined,
dataUnit: undefined as string | undefined,
ratio: undefined as number | undefined
})
const resetBatchEditForm = () => {
batchEditForm.attributeType = undefined
batchEditForm.dataUnit = undefined
batchEditForm.ratio = undefined
batchEditFormRef.value?.resetFields()
}
const openBatchEdit = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要编辑的数据')
return
}
resetBatchEditForm()
await loadUnitList()
batchEditVisible.value = true
}
const handleBatchEdit = async () => {
try {
batchEditLoading.value = true
await DeviceApi.batchUpdateDeviceAttribute({
ids: selectedIds.value,
attributeType: batchEditForm.attributeType,
dataUnit: batchEditForm.dataUnit,
ratio: batchEditForm.ratio
})
message.success(t('common.updateSuccess'))
batchEditVisible.value = false
selectedIds.value = []
tableRef.value?.clearSelection?.()
await getList()
} finally {
batchEditLoading.value = false
}
}
/** 导入 */ /** 导入 */
const importFormRef = ref() const importFormRef = ref()
const handleImport = () => { const handleImport = () => {
@ -393,6 +481,11 @@ onMounted(async () => {
} catch { } catch {
typeList.value = [] typeList.value = []
} }
try {
await loadUnitList()
} catch {
unitList.value = []
}
updatePlanTableMaxHeight() updatePlanTableMaxHeight()
window.addEventListener('resize', updatePlanTableMaxHeight) window.addEventListener('resize', updatePlanTableMaxHeight)
}) })

@ -63,8 +63,7 @@
:min="0.00" :min="0.00"
:decision="2" :decision="2"
:step="0.01" :step="0.01"
class="!w-full" class="!w-full"/>
:disabled = "!ratioEnabled"/>
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.remark')" prop="remark"> <el-form-item :label="t('DataCollection.DeviceModel.remark')" prop="remark">
<el-input <el-input

@ -143,12 +143,19 @@
<div class="device-ledger-right"> <div class="device-ledger-right">
<ContentWrap> <ContentWrap>
<div class="device-ledger__header">
<div v-for="card in statusCards" :key="card.key" class="device-ledger__stat"
:class="{ 'is-active': currentStatusKey === card.key }" @click="changeStatus(card.key, card.status, card.isSchedueld)">
<div class="device-ledger__stat-title">{{ card.label }}</div>
<div class="device-ledger__stat-value">{{ card.value }}</div>
</div>
</div>
<el-form <el-form
class="-mb-15px" class="-mb-15px"
:model="queryParams" :model="queryParams"
ref="queryFormRef" ref="queryFormRef"
:inline="true" :inline="true"
label-width="120px" label-width="68px"
> >
<el-form-item <el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceCode')" :label="t('EquipmentManagement.EquipmentLedger.deviceCode')"
@ -177,6 +184,7 @@
<el-form-item <el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceStatus')" :label="t('EquipmentManagement.EquipmentLedger.deviceStatus')"
prop="deviceStatus" prop="deviceStatus"
v-show="showAllFilters"
> >
<el-select <el-select
v-model="queryParams.deviceStatus" v-model="queryParams.deviceStatus"
@ -195,6 +203,7 @@
<el-form-item <el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceType')" :label="t('EquipmentManagement.EquipmentLedger.deviceType')"
prop="deviceType" prop="deviceType"
v-show="showAllFilters"
> >
<el-tree-select <el-tree-select
v-model="queryParams.deviceType" v-model="queryParams.deviceType"
@ -211,6 +220,7 @@
<el-form-item <el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceBrand')" :label="t('EquipmentManagement.EquipmentLedger.deviceBrand')"
prop="deviceBrand" prop="deviceBrand"
v-show="showAllFilters"
> >
<el-input <el-input
v-model="queryParams.deviceBrand" v-model="queryParams.deviceBrand"
@ -220,7 +230,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.sn')" prop="sn"> <el-form-item :label="t('EquipmentManagement.EquipmentLedger.sn')" prop="sn" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.sn" v-model="queryParams.sn"
:placeholder="t('EquipmentManagement.EquipmentLedger.placeholderSn')" :placeholder="t('EquipmentManagement.EquipmentLedger.placeholderSn')"
@ -232,6 +242,7 @@
<el-form-item <el-form-item
:label="t('EquipmentManagement.EquipmentLedger.outgoingTime')" :label="t('EquipmentManagement.EquipmentLedger.outgoingTime')"
prop="outgoingTime" prop="outgoingTime"
v-show="showAllFilters"
> >
<el-date-picker <el-date-picker
v-model="queryParams.outgoingTime" v-model="queryParams.outgoingTime"
@ -242,6 +253,12 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 2">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</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" /> <Icon icon="ep:search" class="mr-5px" />
@ -678,20 +695,47 @@ const loading = ref(true) // 列表的加载中
const list = ref<DeviceLedgerVO[]>([]) // const list = ref<DeviceLedgerVO[]>([]) //
const total = ref(0) // const total = ref(0) //
const selectedDeviceLineId = ref<string | undefined>(undefined) const selectedDeviceLineId = ref<string | undefined>(undefined)
const currentStatusKey = ref('all')
const counters = reactive({
allCount: 0,
normalCount: 0,
disabledCount: 0,
scheduledCount: 0,
unscheduledCount: 0
})
const statusCards = computed(() => [
{ key: 'all', label: t('EquipmentManagement.EquipmentLedger.all'), value: counters.allCount, status: undefined, isSchedueld: undefined },
{ key: 'normal', label: t('EquipmentManagement.EquipmentLedger.normal'), value: counters.normalCount, status: 0, isSchedueld: undefined },
{ key: 'disabled', label: t('EquipmentManagement.EquipmentLedger.disabled'), value: counters.disabledCount, status: 1, isSchedueld: undefined },
{ key: 'scheduled', label: t('EquipmentManagement.EquipmentLedger.scheduled'), value: counters.scheduledCount, status: undefined, isSchedueld: 1 },
{ key: 'unscheduled', label: t('EquipmentManagement.EquipmentLedger.unscheduled'), value: counters.unscheduledCount, status: undefined, isSchedueld: 0 }
])
const changeStatus = (key: string, status?: number, isSchedueld?: number) => {
currentStatusKey.value = key
queryParams.deviceStatus = status
queryParams.isSchedueld = isSchedueld
handleQuery()
}
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
deviceCode: undefined, deviceCode: undefined,
deviceName: undefined, deviceName: undefined,
deviceStatus: undefined, deviceStatus: undefined as number | undefined,
deviceBrand: undefined, deviceBrand: undefined,
sn: undefined, sn: undefined,
outgoingTime: undefined, outgoingTime: undefined,
deviceType: undefined as string | undefined, deviceType: undefined as string | undefined,
deviceLine: undefined as string | undefined deviceLine: undefined as string | undefined,
isSchedueld: undefined as number | undefined
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) const exportLoading = ref(false)
const showAllFilters = ref(false)
const filterCount = 7
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const tableRef = ref() const tableRef = ref()
const planTableMaxHeight = ref(520) const planTableMaxHeight = ref(520)
@ -1012,10 +1056,16 @@ const getList = async () => {
sn: queryParams.sn, sn: queryParams.sn,
outgoingTime: queryParams.outgoingTime, outgoingTime: queryParams.outgoingTime,
deviceType: queryParams.deviceType, deviceType: queryParams.deviceType,
deviceLine: queryParams.deviceLine deviceLine: queryParams.deviceLine,
isScheduled: queryParams.isSchedueld
}) })
list.value = data.list list.value = data.list ?? []
total.value = data.total total.value = data.total ?? 0
counters.allCount = Number(data?.allCount ?? data?.total ?? 0)
counters.normalCount = Number(data?.normalCount ?? 0)
counters.disabledCount = Number(data?.disabledCount ?? 0)
counters.scheduledCount = Number(data?.scheduledCount ?? 0)
counters.unscheduledCount = Number(data?.unscheduledCount ?? 0)
} finally { } finally {
loading.value = false loading.value = false
updatePlanTableMaxHeight() updatePlanTableMaxHeight()
@ -1031,6 +1081,9 @@ const handleQuery = () => {
/** 重置按钮操作 */ /** 重置按钮操作 */
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
currentStatusKey.value = 'all'
queryParams.deviceStatus = undefined
queryParams.isSchedueld = undefined
handleQuery() handleQuery()
} }
@ -1122,6 +1175,45 @@ onBeforeUnmount(() => {
min-width: 0; min-width: 0;
} }
.device-ledger__header {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 12px;
width: 100%;
margin-bottom: 16px;
}
.device-ledger__stat {
padding: 16px 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
transition: box-shadow 0.2s ease;
background: #fff;
&:hover {
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
&.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
}
.device-ledger__stat-title {
color: var(--el-text-color-regular);
font-size: 14px;
}
.device-ledger__stat-value {
margin-top: 8px;
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
line-height: 1;
}
.simple-grid-view { .simple-grid-view {
background-color: #fff; background-color: #fff;
border-radius: 4px; border-radius: 4px;

@ -727,14 +727,14 @@ const ensureDeviceOptionsLoaded = async () => {
} }
} }
const deviceTypeNameMap = ref<Record<number, string>>({}) const deviceTypeNameMap = ref<Record<string, string>>({})
const buildDeviceTypeNameMap = (nodes: DeviceTypeTreeVO[]) => { const buildDeviceTypeNameMap = (nodes: DeviceTypeTreeVO[]) => {
const map: Record<number, string> = {} const map: Record<string, string> = {}
const walk = (list: DeviceTypeTreeVO[]) => { const walk = (list: DeviceTypeTreeVO[]) => {
list.forEach((node) => { list.forEach((node) => {
if (typeof node?.id === 'number') { if (node?.id != null) {
map[node.id] = node.name ?? '' map[String(node.id)] = node.name ?? ''
} }
if (Array.isArray(node?.children)) walk(node.children) if (Array.isArray(node?.children)) walk(node.children)
}) })
@ -744,8 +744,9 @@ const buildDeviceTypeNameMap = (nodes: DeviceTypeTreeVO[]) => {
} }
const getDeviceTypeName = (value: any) => { const getDeviceTypeName = (value: any) => {
const id = typeof value === 'number' ? value : Number(value) if (value == null || value === '') return '-'
if (!Number.isNaN(id) && deviceTypeNameMap.value[id]) return deviceTypeNameMap.value[id] const key = String(value)
if (deviceTypeNameMap.value[key]) return deviceTypeNameMap.value[key]
return value ?? '-' return value ?? '-'
} }

@ -2,6 +2,13 @@
<div class="dv-repair-page"> <div class="dv-repair-page">
<template v-if="!formVisible"> <template v-if="!formVisible">
<ContentWrap> <ContentWrap>
<div class="dv-repair__header">
<div v-for="card in statusCards" :key="card.key" class="dv-repair__stat"
:class="{ 'is-active': currentStatusKey === card.key }" @click="changeStatus(card.key, card.status, card.repairStatus)">
<div class="dv-repair__stat-title">{{ card.label }}</div>
<div class="dv-repair__stat-value">{{ card.value }}</div>
</div>
</div>
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px"> <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px">
<el-form-item :label="t('EquipmentManagement.DvRepair.repairCode')" prop="repairCode"> <el-form-item :label="t('EquipmentManagement.DvRepair.repairCode')" prop="repairCode">
@ -21,7 +28,7 @@
class="!w-240px" /> class="!w-240px" />
</el-form-item> </el-form-item>
<el-form-item :label="t('EquipmentManagement.DvRepair.machineryName')" prop="machineryName"> <el-form-item :label="t('EquipmentManagement.DvRepair.machineryName')" prop="machineryName" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.machineryName" v-model="queryParams.machineryName"
:placeholder="t('EquipmentManagement.DvRepair.placeholderMachineryName')" :placeholder="t('EquipmentManagement.DvRepair.placeholderMachineryName')"
@ -86,7 +93,7 @@
<el-option v-for="opt in repairStatusOptions" :key="opt.value" :label="opt.label" :value="opt.value" /> <el-option v-for="opt in repairStatusOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3"> <el-form-item v-if="filterCount > 2">
<el-button type="text" class="text-primary" @click="toggleFilters"> <el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" /> <Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }} {{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
@ -255,6 +262,29 @@ const queryParams = reactive({
remark: undefined, remark: undefined,
createTime: [], createTime: [],
}) })
const currentStatusKey = ref('all')
const counters = reactive({
allCount: 0,
pendingCount: 0,
finishedCount: 0,
pendingRepairCount: 0,
passedCount: 0,
failedCount: 0
})
const statusCards = computed(() => [
{ key: 'all', label: t('EquipmentManagement.DvRepair.all'), value: counters.allCount, status: undefined, repairStatus: undefined },
{ key: 'pending', label: t('EquipmentManagement.DvRepair.statusPending'), value: counters.pendingCount, status: '0', repairStatus: undefined },
{ key: 'finished', label: t('EquipmentManagement.DvRepair.statusFinished'), value: counters.finishedCount, status: '1', repairStatus: undefined },
{ key: 'pendingRepair', label: t('EquipmentManagement.DvRepair.repairResultPending'), value: counters.pendingRepairCount, status: undefined, repairStatus: '0' },
{ key: 'passed', label: t('EquipmentManagement.DvRepair.repairResultOk'), value: counters.passedCount, status: undefined, repairStatus: '1' },
{ key: 'failed', label: t('EquipmentManagement.DvRepair.repairResultNg'), value: counters.failedCount, status: undefined, repairStatus: '2' }
])
const changeStatus = (key: string, status?: string, repairStatus?: string) => {
currentStatusKey.value = key
queryParams.status = status
queryParams.repairStatus = repairStatus
handleQuery()
}
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false) const showAllFilters = ref(false)
@ -332,8 +362,14 @@ const getList = async () => {
loading.value = true loading.value = true
try { try {
const data = await DvRepairApi.getDvRepairPage(queryParams) const data = await DvRepairApi.getDvRepairPage(queryParams)
list.value = data.list list.value = data.list ?? []
total.value = data.total total.value = data.total ?? 0
counters.allCount = Number(data?.allCount ?? data?.total ?? 0)
counters.pendingCount = Number(data?.pendingCount ?? 0)
counters.finishedCount = Number(data?.finishedCount ?? 0)
counters.pendingRepairCount = Number(data?.pendingRepairCount ?? 0)
counters.passedCount = Number(data?.passedCount ?? 0)
counters.failedCount = Number(data?.failedCount ?? 0)
} finally { } finally {
loading.value = false loading.value = false
updatePlanTableMaxHeight() updatePlanTableMaxHeight()
@ -349,6 +385,9 @@ const handleQuery = () => {
/** 重置按钮操作 */ /** 重置按钮操作 */
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
currentStatusKey.value = 'all'
queryParams.status = undefined
queryParams.repairStatus = undefined
handleQuery() handleQuery()
} }
@ -422,3 +461,44 @@ onBeforeUnmount(() => {
window.removeEventListener("resize", updatePlanTableMaxHeight) window.removeEventListener("resize", updatePlanTableMaxHeight)
}) })
</script> </script>
<style scoped lang="scss">
.dv-repair__header {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 12px;
width: 100%;
margin-bottom: 16px;
}
.dv-repair__stat {
padding: 16px 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
transition: box-shadow 0.2s ease;
background: #fff;
&:hover {
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
&.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
}
.dv-repair__stat-title {
color: var(--el-text-color-regular);
font-size: 14px;
}
.dv-repair__stat-value {
margin-top: 8px;
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
line-height: 1;
}
</style>

@ -162,10 +162,14 @@
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceName')" prop="deviceName" min-width="150" /> <el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceName')" prop="deviceName" min-width="150" />
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceType')" prop="deviceTypeName" min-width="120"> <el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceType')" prop="deviceTypeName" min-width="120">
<template #default="{ row }"> <template #default="{ row }">
{{ row.deviceTypeName ?? row.deviceType ?? '-' }} {{ getDeviceTypeName(row.deviceTypeName ?? row.deviceType) }}
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceStatus')" prop="deviceStatus" width="100">
<template #default="{ row }">
<dict-tag type="mes_tz_status" :value="row.deviceStatus ?? ''" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceStatus')" prop="deviceStatus" width="100" />
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceBrand')" prop="deviceBrand" min-width="120" /> <el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceBrand')" prop="deviceBrand" min-width="120" />
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.sn')" prop="sn" min-width="140" /> <el-table-column :label="t('EquipmentManagement.EquipmentLedger.sn')" prop="sn" min-width="140" />
</el-table> </el-table>
@ -283,6 +287,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { TaskManagementApi, TaskManagementVO } from '@/api/mes/taskManagement' import { TaskManagementApi, TaskManagementVO } from '@/api/mes/taskManagement'
import { DeviceLedgerApi } from '@/api/mes/deviceledger' import { DeviceLedgerApi } from '@/api/mes/deviceledger'
import { DeviceTypeApi, DeviceTypeTreeVO } from '@/api/mes/devicetype'
import { PlanMaintenanceApi, PlanMaintenanceVO } from '@/api/mes/planmaintenance' import { PlanMaintenanceApi, PlanMaintenanceVO } from '@/api/mes/planmaintenance'
import { getSimpleUserList, UserVO } from '@/api/system/user' import { getSimpleUserList, UserVO } from '@/api/system/user'
@ -332,6 +337,29 @@ const deviceDialogQueryParams = reactive({
deviceName: undefined as string | undefined deviceName: undefined as string | undefined
}) })
const deviceTypeNameMap = ref<Record<string, string>>({})
const buildDeviceTypeNameMap = (nodes: DeviceTypeTreeVO[]) => {
const map: Record<string, string> = {}
const walk = (list: DeviceTypeTreeVO[]) => {
list.forEach((node) => {
if (node?.id != null) {
map[String(node.id)] = node.name ?? ''
}
if (Array.isArray(node?.children)) walk(node.children)
})
}
walk(nodes)
deviceTypeNameMap.value = map
}
const getDeviceTypeName = (value: any) => {
if (value == null || value === '') return '-'
const key = String(value)
if (deviceTypeNameMap.value[key]) return deviceTypeNameMap.value[key]
return value ?? '-'
}
const planDialogVisible = ref(false) const planDialogVisible = ref(false)
const planDialogLoading = ref(false) const planDialogLoading = ref(false)
const planDialogList = ref<PlanOption[]>([]) const planDialogList = ref<PlanOption[]>([])
@ -546,6 +574,12 @@ const openDeviceDialog = async () => {
deviceDialogQueryParams.pageNo = 1 deviceDialogQueryParams.pageNo = 1
deviceDialogQueryParams.deviceCode = undefined deviceDialogQueryParams.deviceCode = undefined
deviceDialogQueryParams.deviceName = undefined deviceDialogQueryParams.deviceName = undefined
if (!Object.keys(deviceTypeNameMap.value).length) {
try {
const treeData = await DeviceTypeApi.getDeviceTypeTree({})
buildDeviceTypeNameMap(treeData ?? [])
} catch (e) {}
}
await getDeviceDialogList() await getDeviceDialogList()
} }

@ -1,5 +1,12 @@
<template> <template>
<ContentWrap> <ContentWrap>
<div class="task-management__header">
<div v-for="card in statusCards" :key="card.key" class="task-management__stat"
:class="{ 'is-active': currentStatusKey === card.key }" @click="changeStatus(card.key, card.taskType, card.enabled)">
<div class="task-management__stat-title">{{ card.label }}</div>
<div class="task-management__stat-value">{{ card.value }}</div>
</div>
</div>
<el-form <el-form
class="-mb-15px" class="-mb-15px"
:model="queryParams" :model="queryParams"
@ -26,7 +33,7 @@
<el-option v-for="opt in taskTypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" /> <el-option v-for="opt in taskTypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('EquipmentManagement.TaskManagement.projectForm')" prop="projectForm"> <el-form-item :label="t('EquipmentManagement.TaskManagement.projectForm')" prop="projectForm" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.projectForm" v-model="queryParams.projectForm"
multiple multiple
@ -38,7 +45,7 @@
<el-option v-for="item in planOptions" :key="String(item.id)" :label="item.planName" :value="String(item.id)" /> <el-option v-for="item in planOptions" :key="String(item.id)" :label="item.planName" :value="String(item.id)" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('EquipmentManagement.TaskManagement.deviceList')" prop="deviceIds"> <el-form-item :label="t('EquipmentManagement.TaskManagement.deviceList')" prop="deviceIds" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.deviceIds" v-model="queryParams.deviceIds"
multiple multiple
@ -57,7 +64,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('EquipmentManagement.TaskManagement.operableUsers')" prop="operator"> <el-form-item :label="t('EquipmentManagement.TaskManagement.operableUsers')" prop="operator" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.operator" v-model="queryParams.operator"
filterable filterable
@ -68,7 +75,7 @@
<el-option v-for="item in users" :key="String(item.id)" :label="item.nickname" :value="String(item.id)" /> <el-option v-for="item in users" :key="String(item.id)" :label="item.nickname" :value="String(item.id)" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('EquipmentManagement.TaskManagement.taskTime')" prop="taskTime"> <el-form-item :label="t('EquipmentManagement.TaskManagement.taskTime')" prop="taskTime" v-show="showAllFilters">
<el-date-picker <el-date-picker
v-model="queryParams.taskTime" v-model="queryParams.taskTime"
value-format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
@ -78,6 +85,12 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 2">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</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" /> <Icon icon="ep:search" class="mr-5px" />
@ -312,9 +325,36 @@ const queryParams = reactive({
projectForm: [] as string[], projectForm: [] as string[],
deviceIds: [] as string[], deviceIds: [] as string[],
operator: undefined as string | undefined, operator: undefined as string | undefined,
taskTime: undefined as string | undefined taskTime: undefined as string | undefined,
enabled: undefined as boolean | undefined
}) })
const queryFormRef = ref() const queryFormRef = ref()
const currentStatusKey = ref('all')
const counters = reactive({
allCount: 0,
inspectCount: 0,
maintainCount: 0,
enabledCount: 0,
disabledCount: 0
})
const statusCards = computed(() => [
{ key: 'all', label: t('EquipmentManagement.TaskManagement.all'), value: counters.allCount, taskType: undefined, enabled: undefined },
{ key: 'inspect', label: t('EquipmentManagement.TaskManagement.taskTypeInspect'), value: counters.inspectCount, taskType: 1, enabled: undefined },
{ key: 'maintain', label: t('EquipmentManagement.TaskManagement.taskTypeMaintain'), value: counters.maintainCount, taskType: 2, enabled: undefined },
{ key: 'enabled', label: t('EquipmentManagement.TaskManagement.enabled'), value: counters.enabledCount, taskType: undefined, enabled: true },
{ key: 'disabled', label: t('EquipmentManagement.TaskManagement.disabled'), value: counters.disabledCount, taskType: undefined, enabled: false }
])
const changeStatus = (key: string, taskType?: number, enabled?: boolean) => {
currentStatusKey.value = key
queryParams.taskType = taskType
queryParams.enabled = enabled
handleQuery()
}
const showAllFilters = ref(false)
const filterCount = 6
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const parseIdsValue = (value: any): string[] => { const parseIdsValue = (value: any): string[] => {
if (!value) return [] if (!value) return []
@ -376,6 +416,11 @@ const getList = async () => {
const data = await TaskManagementApi.getTaskManagementPage(buildQueryParams()) const data = await TaskManagementApi.getTaskManagementPage(buildQueryParams())
list.value = data.list list.value = data.list
total.value = data.total total.value = data.total
counters.allCount = Number(data?.allCount ?? data?.total ?? 0)
counters.inspectCount = Number(data?.inspectCount ?? 0)
counters.maintainCount = Number(data?.maintainCount ?? 0)
counters.enabledCount = Number(data?.enabledCount ?? 0)
counters.disabledCount = Number(data?.disabledCount ?? 0)
} finally { } finally {
loading.value = false loading.value = false
updatePlanTableMaxHeight() updatePlanTableMaxHeight()
@ -389,6 +434,9 @@ const handleQuery = () => {
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
currentStatusKey.value = 'all'
queryParams.taskType = undefined
queryParams.enabled = undefined
handleQuery() handleQuery()
} }
@ -502,3 +550,44 @@ onBeforeUnmount(() => {
window.removeEventListener('resize', updatePlanTableMaxHeight) window.removeEventListener('resize', updatePlanTableMaxHeight)
}) })
</script> </script>
<style scoped lang="scss">
.task-management__header {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 12px;
width: 100%;
margin-bottom: 16px;
}
.task-management__stat {
padding: 16px 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
transition: box-shadow 0.2s ease;
background: #fff;
&:hover {
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
&.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
}
.task-management__stat-title {
color: var(--el-text-color-regular);
font-size: 14px;
}
.task-management__stat-value {
margin-top: 8px;
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
line-height: 1;
}
</style>

@ -252,8 +252,8 @@ const currentDeviceRelationRow = ref<any>()
const currentProductData = ref<any>() const currentProductData = ref<any>()
const selectedDeviceRows = ref<any[]>([]) const selectedDeviceRows = ref<any[]>([])
const deviceRelationForm = reactive({ const deviceRelationForm = reactive({
productId: undefined as number | undefined, productId: undefined as string | undefined,
devices: [] as { id: number; name: string }[] devices: [] as { id: string; name: string }[]
}) })
const deviceColumns = computed(() => [ const deviceColumns = computed(() => [
@ -304,7 +304,7 @@ const taskDetailRequestMap = new Map<string, Promise<any[]>>()
const currentDetailRequestId = ref(0) const currentDetailRequestId = ref(0)
const taskTableBusy = computed(() => taskLoading.value || taskSelectionLoading.value) const taskTableBusy = computed(() => taskLoading.value || taskSelectionLoading.value)
const getRelationName = (item: Record<string, any>, nameKeys: string[], fallbackPrefix: string, id: number) => { const getRelationName = (item: Record<string, any>, nameKeys: string[], fallbackPrefix: string, id: string) => {
for (const key of nameKeys) { for (const key of nameKeys) {
const value = item[key] const value = item[key]
if (value !== undefined && value !== null && String(value).trim() !== '') { if (value !== undefined && value !== null && String(value).trim() !== '') {
@ -318,11 +318,11 @@ const normalizeRelationList = (
fallbackRows: any[] | undefined, fallbackRows: any[] | undefined,
nameKeys: string[], nameKeys: string[],
fallbackPrefix: string fallbackPrefix: string
): { id: number; name: string }[] => { ): { id: string; name: string }[] => {
const fallbackMap = new Map<number, string>() const fallbackMap = new Map<string, string>()
;(fallbackRows || []).forEach((row) => { ;(fallbackRows || []).forEach((row) => {
const id = Number(row?.id) const id = String(row?.id)
if (!Number.isFinite(id)) return if (!id || id === 'undefined' || id === 'null') return
fallbackMap.set(id, getRelationName(row, nameKeys, fallbackPrefix, id)) fallbackMap.set(id, getRelationName(row, nameKeys, fallbackPrefix, id))
}) })
const parseArray = (source: unknown): any[] => { const parseArray = (source: unknown): any[] => {
@ -342,23 +342,23 @@ const normalizeRelationList = (
return parseArray(value) return parseArray(value)
.map((item) => { .map((item) => {
if (typeof item === 'object' && item !== null) { if (typeof item === 'object' && item !== null) {
const id = Number((item as any).id) const id = String((item as any).id)
if (!Number.isFinite(id)) return undefined if (!id || id === 'undefined' || id === 'null') return undefined
return { return {
id, id,
name: getRelationName(item as any, nameKeys, fallbackPrefix, id) name: getRelationName(item as any, nameKeys, fallbackPrefix, id)
} }
} }
const id = Number(item) const id = String(item)
if (!Number.isFinite(id)) return undefined if (!id || id === 'undefined' || id === 'null') return undefined
return { return {
id, id,
name: fallbackMap.get(id) || `${fallbackPrefix}ID:${id}` name: fallbackMap.get(id) || `${fallbackPrefix}ID:${id}`
} }
}) })
.filter((item): item is { id: number; name: string } => Boolean(item)) .filter((item): item is { id: string; name: string } => Boolean(item))
} }
const toDeviceRows = (devices: { id: number; name: string }[]) => { const toDeviceRows = (devices: { id: string; name: string }[]) => {
return devices.map((item) => ({ return devices.map((item) => ({
id: item.id, id: item.id,
deviceName: item.name, deviceName: item.name,
@ -625,7 +625,7 @@ const openDeviceRelationDialog = async (row: any) => {
deviceRelationLoading.value = true deviceRelationLoading.value = true
selectedDeviceRows.value = [] selectedDeviceRows.value = []
try { try {
const productData = await ProductApi.getProduct(Number(row.productId)) const productData = await ProductApi.getProduct(String(row.productId))
currentProductData.value = productData currentProductData.value = productData
const devices = normalizeRelationList( const devices = normalizeRelationList(
(productData as any).devices ?? (productData as any).deviceIds, (productData as any).devices ?? (productData as any).deviceIds,
@ -633,7 +633,7 @@ const openDeviceRelationDialog = async (row: any) => {
['name', 'deviceName', 'code'], ['name', 'deviceName', 'code'],
t('ProductionPlan.TaskSummary.deviceColumnName') t('ProductionPlan.TaskSummary.deviceColumnName')
) )
deviceRelationForm.productId = Number(row.productId) deviceRelationForm.productId = String(row.productId)
deviceRelationForm.devices = devices deviceRelationForm.devices = devices
selectedDeviceRows.value = toDeviceRows(devices) selectedDeviceRows.value = toDeviceRows(devices)
} finally { } finally {
@ -656,7 +656,7 @@ const handleDeviceSelectConfirm = (payload: { ids: (number | string)[]; rows: an
name: item.deviceName || item.name || item.code || `${t('ProductionPlan.TaskSummary.deviceColumnName')}ID:${id}` name: item.deviceName || item.name || item.code || `${t('ProductionPlan.TaskSummary.deviceColumnName')}ID:${id}`
} }
}) })
.filter((item): item is { id: number; name: string } => Boolean(item)) .filter((item): item is { id: string; name: string } => Boolean(item))
selectedDeviceRows.value = payload.rows selectedDeviceRows.value = payload.rows
} }
const refreshDetailListAfterDeviceSaved = async () => { const refreshDetailListAfterDeviceSaved = async () => {

@ -1,6 +1,13 @@
<template> <template>
<template v-if="!resultVisible"> <template v-if="!resultVisible">
<ContentWrap> <ContentWrap>
<div class="work-order__header">
<div v-for="card in statusCards" :key="card.key" class="work-order__stat"
:class="{ 'is-active': currentStatusKey === card.key }" @click="changeStatus(card.key, card.planType, card.jobStatus)">
<div class="work-order__stat-title">{{ card.label }}</div>
<div class="work-order__stat-value">{{ card.value }}</div>
</div>
</div>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px"> <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px">
<el-form-item :label="t('EquipmentManagement.WorkOrderManagement.planNo')" prop="planNo"> <el-form-item :label="t('EquipmentManagement.WorkOrderManagement.planNo')" prop="planNo">
<el-input <el-input
@ -288,6 +295,33 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() const queryFormRef = ref()
const currentStatusKey = ref('all')
const counters = reactive({
allCount: 0,
inspectCount: 0,
maintainCount: 0,
unworkedCount: 0,
workedCount: 0,
overtimeUnworkedCount: 0,
overtimeWorkedCount: 0,
canceledCount: 0
})
const statusCards = computed(() => [
{ key: 'all', label: t('EquipmentManagement.WorkOrderManagement.all'), value: counters.allCount, planType: undefined, jobStatus: undefined },
{ key: 'inspect', label: t('EquipmentManagement.WorkOrderManagement.planTypeInspect'), value: counters.inspectCount, planType: '1', jobStatus: undefined },
{ key: 'maintain', label: t('EquipmentManagement.WorkOrderManagement.planTypeMaintain'), value: counters.maintainCount, planType: '2', jobStatus: undefined },
{ key: 'unworked', label: t('EquipmentManagement.WorkOrderManagement.unworked'), value: counters.unworkedCount, planType: undefined, jobStatus: '0' },
{ key: 'worked', label: t('EquipmentManagement.WorkOrderManagement.worked'), value: counters.workedCount, planType: undefined, jobStatus: '1' },
{ key: 'overtimeUnworked', label: t('EquipmentManagement.WorkOrderManagement.overtimeUnworked'), value: counters.overtimeUnworkedCount, planType: undefined, jobStatus: '2' },
{ key: 'overtimeWorked', label: t('EquipmentManagement.WorkOrderManagement.overtimeWorked'), value: counters.overtimeWorkedCount, planType: undefined, jobStatus: '3' },
{ key: 'canceled', label: t('EquipmentManagement.WorkOrderManagement.canceled'), value: counters.canceledCount, planType: undefined, jobStatus: '4' }
])
const changeStatus = (key: string, planType?: string, jobStatus?: string) => {
currentStatusKey.value = key
queryParams.planType = planType
queryParams.jobStatus = jobStatus
handleQuery()
}
const showAllFilters = ref(false) const showAllFilters = ref(false)
const filterCount = 6 const filterCount = 6
const UNWORKED_JOB_STATUS = '0' const UNWORKED_JOB_STATUS = '0'
@ -317,6 +351,14 @@ const getList = async () => {
const data = await TicketManagementApi.getTicketManagementPage(buildQueryParams()) const data = await TicketManagementApi.getTicketManagementPage(buildQueryParams())
list.value = data.list list.value = data.list
total.value = data.total total.value = data.total
counters.allCount = Number(data?.allCount ?? data?.total ?? 0)
counters.inspectCount = Number(data?.inspectCount ?? 0)
counters.maintainCount = Number(data?.maintainCount ?? 0)
counters.unworkedCount = Number(data?.unworkedCount ?? 0)
counters.workedCount = Number(data?.workedCount ?? 0)
counters.overtimeUnworkedCount = Number(data?.overtimeUnworkedCount ?? 0)
counters.overtimeWorkedCount = Number(data?.overtimeWorkedCount ?? 0)
counters.canceledCount = Number(data?.canceledCount ?? 0)
} finally { } finally {
loading.value = false loading.value = false
updatePlanTableMaxHeight() updatePlanTableMaxHeight()
@ -330,6 +372,9 @@ const handleQuery = () => {
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
currentStatusKey.value = 'all'
queryParams.planType = undefined
queryParams.jobStatus = '0'
handleQuery() handleQuery()
} }
@ -418,3 +463,44 @@ onBeforeUnmount(() => {
window.removeEventListener("resize", updatePlanTableMaxHeight) window.removeEventListener("resize", updatePlanTableMaxHeight)
}) })
</script> </script>
<style scoped lang="scss">
.work-order__header {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
gap: 12px;
width: 100%;
margin-bottom: 16px;
}
.work-order__stat {
padding: 12px 14px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
transition: box-shadow 0.2s ease;
background: #fff;
&:hover {
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
&.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
}
.work-order__stat-title {
color: var(--el-text-color-regular);
font-size: 12px;
}
.work-order__stat-value {
margin-top: 6px;
color: var(--el-text-color-primary);
font-size: 24px;
font-weight: 700;
line-height: 1;
}
</style>

@ -1,6 +1,13 @@
<template> <template>
<ContentWrap v-if="!formVisible"> <ContentWrap v-if="!formVisible">
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<div class="mold-repair__header">
<div v-for="card in statusCards" :key="card.key" class="mold-repair__stat"
:class="{ 'is-active': currentStatusKey === card.key }" @click="changeStatus(card.key, card.status, card.repairStatus)">
<div class="mold-repair__stat-title">{{ card.label }}</div>
<div class="mold-repair__stat-value">{{ card.value }}</div>
</div>
</div>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px"> <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px">
<el-form-item :label="t('MoldManagement.MoldRepair.repairCode')" prop="repairCode"> <el-form-item :label="t('MoldManagement.MoldRepair.repairCode')" prop="repairCode">
<el-input <el-input
@ -19,7 +26,7 @@
class="!w-240px" /> class="!w-240px" />
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.MoldRepair.moldCode')" prop="moldCode"> <el-form-item :label="t('MoldManagement.MoldRepair.moldCode')" prop="moldCode" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.moldCode" v-model="queryParams.moldCode"
:placeholder="t('MoldManagement.MoldRepair.placeholderMoldCode')" :placeholder="t('MoldManagement.MoldRepair.placeholderMoldCode')"
@ -79,7 +86,7 @@
<el-option v-for="opt in statusOptions" :key="opt.value" :label="opt.label" :value="opt.value" /> <el-option v-for="opt in statusOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3"> <el-form-item v-if="filterCount > 2">
<el-button type="text" class="text-primary" @click="toggleFilters"> <el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" /> <Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }} {{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
@ -242,10 +249,34 @@ const queryParams = reactive({
acceptedBy: undefined, acceptedBy: undefined,
confirmBy: undefined, confirmBy: undefined,
status: undefined, status: undefined,
repairStatus: undefined,
remark: undefined, remark: undefined,
createTime: [], createTime: [],
}) })
const queryFormRef = ref() const queryFormRef = ref()
const currentStatusKey = ref('all')
const counters = reactive({
allCount: 0,
pendingCount: 0,
finishedCount: 0,
pendingRepairCount: 0,
passedCount: 0,
failedCount: 0
})
const statusCards = computed(() => [
{ key: 'all', label: t('MoldManagement.MoldRepair.all'), value: counters.allCount, status: undefined, repairStatus: undefined },
{ key: 'pending', label: t('MoldManagement.MoldRepair.statusPending'), value: counters.pendingCount, status: '0', repairStatus: undefined },
{ key: 'finished', label: t('MoldManagement.MoldRepair.statusDone'), value: counters.finishedCount, status: '1', repairStatus: undefined },
{ key: 'pendingRepair', label: t('MoldManagement.MoldRepair.repairResultPending'), value: counters.pendingRepairCount, status: undefined, repairStatus: '0' },
{ key: 'passed', label: t('MoldManagement.MoldRepair.repairResultOk'), value: counters.passedCount, status: undefined, repairStatus: '1' },
{ key: 'failed', label: t('MoldManagement.MoldRepair.repairResultNg'), value: counters.failedCount, status: undefined, repairStatus: '2' }
])
const changeStatus = (key: string, status?: string, repairStatus?: string) => {
currentStatusKey.value = key
queryParams.status = status
queryParams.repairStatus = repairStatus
handleQuery()
}
const exportLoading = ref(false) const exportLoading = ref(false)
const statusOptions = [ const statusOptions = [
@ -318,6 +349,12 @@ const getList = async () => {
const data = await MoldRepairApi.getMoldRepairPage(params) const data = await MoldRepairApi.getMoldRepairPage(params)
list.value = data.list list.value = data.list
total.value = data.total total.value = data.total
counters.allCount = Number(data?.allCount ?? data?.total ?? 0)
counters.pendingCount = Number(data?.pendingCount ?? 0)
counters.finishedCount = Number(data?.finishedCount ?? 0)
counters.pendingRepairCount = Number(data?.pendingRepairCount ?? 0)
counters.passedCount = Number(data?.passedCount ?? 0)
counters.failedCount = Number(data?.failedCount ?? 0)
} finally { } finally {
loading.value = false loading.value = false
updatePlanTableMaxHeight() updatePlanTableMaxHeight()
@ -331,6 +368,9 @@ const handleQuery = () => {
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
currentStatusKey.value = 'all'
queryParams.status = undefined
queryParams.repairStatus = undefined
handleQuery() handleQuery()
} }
@ -400,3 +440,44 @@ onBeforeUnmount(() => {
window.removeEventListener("resize", updatePlanTableMaxHeight) window.removeEventListener("resize", updatePlanTableMaxHeight)
}) })
</script> </script>
<style scoped lang="scss">
.mold-repair__header {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 12px;
width: 100%;
margin-bottom: 16px;
}
.mold-repair__stat {
padding: 16px 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
transition: box-shadow 0.2s ease;
background: #fff;
&:hover {
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
&.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
}
.mold-repair__stat-title {
color: var(--el-text-color-regular);
font-size: 14px;
}
.mold-repair__stat-value {
margin-top: 8px;
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
line-height: 1;
}
</style>

@ -1,5 +1,12 @@
<template> <template>
<ContentWrap> <ContentWrap>
<div class="task-config__header">
<div v-for="card in statusCards" :key="card.key" class="task-config__stat"
:class="{ 'is-active': currentStatusKey === card.key }" @click="changeStatus(card.key, card.taskType, card.enabled)">
<div class="task-config__stat-title">{{ card.label }}</div>
<div class="task-config__stat-value">{{ card.value }}</div>
</div>
</div>
<el-form <el-form
class="-mb-15px" class="-mb-15px"
:model="queryParams" :model="queryParams"
@ -186,12 +193,34 @@ type PlanOption = {
const planOptions = ref<PlanOption[]>([]) const planOptions = ref<PlanOption[]>([])
const currentStatusKey = ref('all')
const counters = reactive({
allCount: 0,
inspectCount: 0,
maintainCount: 0,
enabledCount: 0,
disabledCount: 0
})
const statusCards = computed(() => [
{ key: 'all', label: t('EquipmentManagement.TaskManagement.all'), value: counters.allCount, taskType: undefined, enabled: undefined },
{ key: 'inspect', label: t('EquipmentManagement.TaskManagement.taskTypeInspect'), value: counters.inspectCount, taskType: 1, enabled: undefined },
{ key: 'maintain', label: t('EquipmentManagement.TaskManagement.taskTypeMaintain'), value: counters.maintainCount, taskType: 2, enabled: undefined },
{ key: 'enabled', label: t('EquipmentManagement.TaskManagement.enabled'), value: counters.enabledCount, taskType: undefined, enabled: true },
{ key: 'disabled', label: t('EquipmentManagement.TaskManagement.disabled'), value: counters.disabledCount, taskType: undefined, enabled: false }
])
const changeStatus = (key: string, taskType?: number, enabled?: boolean) => {
currentStatusKey.value = key
queryParams.taskType = taskType
queryParams.enabled = enabled
handleQuery()
}
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
name: undefined as string | undefined, name: undefined as string | undefined,
taskType: undefined as number | undefined, taskType: undefined as number | undefined,
projectForm: [] as string[] projectForm: [] as string[],
enabled: undefined as boolean | undefined
}) })
const queryFormRef = ref() const queryFormRef = ref()
const showAllFilters = ref(false) const showAllFilters = ref(false)
@ -233,6 +262,11 @@ const getList = async () => {
const data = await TaskManagementApi.getTaskManagementPage(buildQueryParams()) const data = await TaskManagementApi.getTaskManagementPage(buildQueryParams())
list.value = data.list list.value = data.list
total.value = data.total total.value = data.total
counters.allCount = Number(data?.allCount ?? data?.total ?? 0)
counters.inspectCount = Number(data?.inspectCount ?? 0)
counters.maintainCount = Number(data?.maintainCount ?? 0)
counters.enabledCount = Number(data?.enabledCount ?? 0)
counters.disabledCount = Number(data?.disabledCount ?? 0)
} finally { } finally {
loading.value = false loading.value = false
updatePlanTableMaxHeight() updatePlanTableMaxHeight()
@ -246,6 +280,9 @@ const handleQuery = () => {
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
currentStatusKey.value = 'all'
queryParams.taskType = undefined
queryParams.enabled = undefined
handleQuery() handleQuery()
} }
@ -358,3 +395,44 @@ onBeforeUnmount(() => {
window.removeEventListener("resize", updatePlanTableMaxHeight) window.removeEventListener("resize", updatePlanTableMaxHeight)
}) })
</script> </script>
<style scoped lang="scss">
.task-config__header {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 12px;
width: 100%;
margin-bottom: 16px;
}
.task-config__stat {
padding: 16px 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
transition: box-shadow 0.2s ease;
background: #fff;
&:hover {
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
&.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
}
.task-config__stat-title {
color: var(--el-text-color-regular);
font-size: 14px;
}
.task-config__stat-value {
margin-top: 8px;
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
line-height: 1;
}
</style>

@ -1,6 +1,13 @@
<template> <template>
<template v-if="!resultVisible"> <template v-if="!resultVisible">
<ContentWrap> <ContentWrap>
<div class="work-order__header">
<div v-for="card in statusCards" :key="card.key" class="work-order__stat"
:class="{ 'is-active': currentStatusKey === card.key }" @click="changeStatus(card.key, card.planType, card.jobStatus)">
<div class="work-order__stat-title">{{ card.label }}</div>
<div class="work-order__stat-value">{{ card.value }}</div>
</div>
</div>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px"> <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px">
<el-form-item :label="t('MoldManagement.MoldWorkOrderInquiry.planNo')" prop="planNo"> <el-form-item :label="t('MoldManagement.MoldWorkOrderInquiry.planNo')" prop="planNo">
<el-input <el-input
@ -209,6 +216,33 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() const queryFormRef = ref()
const currentStatusKey = ref('all')
const counters = reactive({
allCount: 0,
inspectCount: 0,
maintainCount: 0,
unworkedCount: 0,
workedCount: 0,
overtimeUnworkedCount: 0,
overtimeWorkedCount: 0,
canceledCount: 0
})
const statusCards = computed(() => [
{ key: 'all', label: t('MoldManagement.MoldWorkOrderInquiry.all'), value: counters.allCount, planType: undefined, jobStatus: undefined },
{ key: 'inspect', label: t('MoldManagement.MoldWorkOrderInquiry.planTypeInspect'), value: counters.inspectCount, planType: '1', jobStatus: undefined },
{ key: 'maintain', label: t('MoldManagement.MoldWorkOrderInquiry.planTypeMaintain'), value: counters.maintainCount, planType: '2', jobStatus: undefined },
{ key: 'unworked', label: t('MoldManagement.MoldWorkOrderInquiry.unworked'), value: counters.unworkedCount, planType: undefined, jobStatus: '0' },
{ key: 'worked', label: t('MoldManagement.MoldWorkOrderInquiry.worked'), value: counters.workedCount, planType: undefined, jobStatus: '1' },
{ key: 'overtimeUnworked', label: t('MoldManagement.MoldWorkOrderInquiry.overtimeUnworked'), value: counters.overtimeUnworkedCount, planType: undefined, jobStatus: '2' },
{ key: 'overtimeWorked', label: t('MoldManagement.MoldWorkOrderInquiry.overtimeWorked'), value: counters.overtimeWorkedCount, planType: undefined, jobStatus: '3' },
{ key: 'canceled', label: t('MoldManagement.MoldWorkOrderInquiry.canceled'), value: counters.canceledCount, planType: undefined, jobStatus: '4' }
])
const changeStatus = (key: string, planType?: string, jobStatus?: string) => {
currentStatusKey.value = key
queryParams.planType = planType
queryParams.jobStatus = jobStatus
handleQuery()
}
const showAllFilters = ref(false) const showAllFilters = ref(false)
const filterCount = 6 const filterCount = 6
const UNWORKED_JOB_STATUS = '0' const UNWORKED_JOB_STATUS = '0'
@ -234,6 +268,14 @@ const getList = async () => {
const data = await TicketManagementApi.getTicketManagementPage(buildQueryParams()) const data = await TicketManagementApi.getTicketManagementPage(buildQueryParams())
list.value = data.list list.value = data.list
total.value = data.total total.value = data.total
counters.allCount = Number(data?.allCount ?? data?.total ?? 0)
counters.inspectCount = Number(data?.inspectCount ?? 0)
counters.maintainCount = Number(data?.maintainCount ?? 0)
counters.unworkedCount = Number(data?.unworkedCount ?? 0)
counters.workedCount = Number(data?.workedCount ?? 0)
counters.overtimeUnworkedCount = Number(data?.overtimeUnworkedCount ?? 0)
counters.overtimeWorkedCount = Number(data?.overtimeWorkedCount ?? 0)
counters.canceledCount = Number(data?.canceledCount ?? 0)
} finally { } finally {
loading.value = false loading.value = false
updatePlanTableMaxHeight() updatePlanTableMaxHeight()
@ -247,6 +289,9 @@ const handleQuery = () => {
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
currentStatusKey.value = 'all'
queryParams.planType = undefined
queryParams.jobStatus = '0'
handleQuery() handleQuery()
} }
@ -338,3 +383,44 @@ onBeforeUnmount(() => {
}) })
</script> </script>
<style scoped lang="scss">
.work-order__header {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
gap: 12px;
width: 100%;
margin-bottom: 16px;
}
.work-order__stat {
padding: 12px 14px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
transition: box-shadow 0.2s ease;
background: #fff;
&:hover {
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
&.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
}
.work-order__stat-title {
color: var(--el-text-color-regular);
font-size: 12px;
}
.work-order__stat-value {
margin-top: 6px;
color: var(--el-text-color-primary);
font-size: 24px;
font-weight: 700;
line-height: 1;
}
</style>

Loading…
Cancel
Save