# Conflicts:
#	src/locales/en.ts
ck
黄伟杰 3 weeks ago
commit 3d42371613

4
.gitignore vendored

@ -9,3 +9,7 @@ auto-*.d.ts
.history
pnpm-lock.yaml
/.vscode
.env.cklocal
.vscode/*
.vscode/settings.json
launch.json

@ -1,144 +0,0 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"npm.packageManager": "pnpm",
"editor.tabSize": 2,
"prettier.printWidth": 100, //
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n",
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
"**/*.log*": true,
"**/bower_components": true,
"**/dist": true,
"**/elehukouben": true,
"**/.git": true,
"**/.gitignore": true,
"**/.svn": true,
"**/.DS_Store": true,
"**/.idea": true,
"**/.vscode": false,
"**/yarn.lock": true,
"**/tmp": true,
"out": true,
"dist": true,
"node_modules": true,
"CHANGELOG.md": true,
"examples": true,
"res": true,
"screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
},
"files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
"**/.idea": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.vscode/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true,
"**/yarn.lock": true
},
"stylelint.enable": true,
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"path-intellisense.mappings": {
"@/": "${workspaceRoot}/src"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": false,
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"brotli",
"browserslist",
"codemirror",
"commitlint",
"cropperjs",
"echart",
"echarts",
"esnext",
"esno",
"iconify",
"INTLIFY",
"lintstagedrc",
"logicflow",
"nprogress",
"pinia",
"pnpm",
"qrcode",
"sider",
"sortablejs",
"stylelint",
"svgs",
"unocss",
"unplugin",
"unref",
"videojs",
"VITE",
"vitejs",
"vueuse",
"wangeditor",
"xingyu",
"yudao",
"zxcvbn"
],
//
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
"*.env": "$(capture).env.*",
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
},
"terminal.integrated.scrollback": 10000,
"nuxt.isNuxtApp": false
}

@ -27,6 +27,7 @@ export interface DeviceVO {
certificate?: string // 证书
secretKey?: string // 秘钥
collectionTime?: string | number
images: string,//图片
}
export interface DeviceConnectParams {
@ -102,6 +103,9 @@ export const DeviceApi = {
getAvailableList: async () => {
return await request.get({ url: `/iot/device/available-list` })
},
getAvailableListPage: async (params: any) => {
return await request.get({ url: `/iot/device/available-page` , params})
},
// 新增物联设备
createDevice: async (data: DeviceVO) => {
return await request.post({ url: `/iot/device/create`, data })
@ -147,6 +151,10 @@ export const DeviceApi = {
return await request.get({ url: `/iot/device/singleDevice`, params })
},
getSingleDeviceFrom: async (params: SingleDeviceParams) => {
return await request.get({ url: `/iot/device/singleDeviceFrom`, params })
},
getHistoryRecord: async (params: HistoryRecordParams) => {
return await request.get({
url: `/iot/device/historyRecord`,
@ -155,6 +163,15 @@ export const DeviceApi = {
})
},
getHistoryAnalyse: async (params: HistoryRecordParams) => {
return await request.get({
url: `/iot/device/historyAnalyse`,
params,
paramsSerializer: (p) => qs.stringify(p, { allowDots: true, arrayFormat: 'repeat' })
})
},
devicePointList: async () => {
return await request.get({ url: `/iot/device/devicePointList` })
},
@ -170,6 +187,10 @@ export const DeviceApi = {
getDeviceAttributePage: async (params) => {
return await request.get({ url: `/iot/device/device-attribute/page`, params })
},
// 获得设备属性按类型分租
getDeviceAttributeGroupList: async (params) => {
return await request.get({ url: `/iot/device/device-attribute/groupList`, params })
},
// 获得设备属性列表
getDeviceAttributeList: async (deviceId: number | string) => {
return await request.get({ url: `/iot/device/device-attribute/list?deviceId=` + deviceId })

@ -64,13 +64,16 @@ const props = withDefaults(
pageSize?: number
initialRows?: any[]
queryParams?: Record<string, any> //
/** ✅ 新增 */
defaultSelectedKeys?: (string | number)[]
}>(),
{
selectionType: 'multiple',
rowKey: 'id',
pageSize: 10,
initialRows: () => [],
queryParams: () => ({}) //
queryParams: () => ({}), //
defaultSelectedKeys: () => []
}
)
@ -111,6 +114,16 @@ const refreshSelectionOnTable = async () => {
syncingSelection.value = false
}
}
/**
* 新增根据 defaultSelectedKeys 初始化选中状态
*/
const initDefaultSelection = () => {
if (!props.defaultSelectedKeys.length) return
props.defaultSelectedKeys.forEach((key) => {
selectedMap.value.set(key, { [props.rowKey]: key })
})
}
const getList = async () => {
loading.value = true
@ -161,12 +174,30 @@ const handleRowClick = (row: any) => {
}
const open = async (rows?: any[]) => {
/* selectedMap.value.clear()
const initialRows = rows || props.initialRows
const nextRows = isSingleSelect.value ? initialRows.slice(0, 1) : initialRows
nextRows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
queryParams.pageNo = 1
queryParams.pageSize = props.pageSize
dialogVisible.value = true
await getList()*/
selectedMap.value.clear()
// 使 defaultSelectedKeys
if (props.defaultSelectedKeys.length) {
initDefaultSelection()
}
// initialRows / rows
const initialRows = rows || props.initialRows
const nextRows = isSingleSelect.value ? initialRows.slice(0, 1) : initialRows
nextRows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
queryParams.pageNo = 1
queryParams.pageSize = props.pageSize
dialogVisible.value = true

@ -202,6 +202,35 @@ $prefix-cls: #{$namespace}-menu;
.#{$elNamespace}-menu-item {
padding-right: 0;
}
&:not(.#{$elNamespace}-menu--collapse) {
.#{$elNamespace}-sub-menu__title,
.#{$elNamespace}-menu-item {
display: flex;
align-items: flex-start;
min-height: var(--el-menu-item-height);
height: auto;
padding-top: 10px;
padding-bottom: 10px;
white-space: normal;
}
.#{$elNamespace}-sub-menu__icon-arrow {
margin-top: 8px;
}
.#{$prefix-cls}__title {
display: block;
flex: 1;
min-width: 0;
overflow: visible;
text-overflow: initial;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
line-height: 1.4;
}
}
}
}
@ -228,6 +257,10 @@ $prefix-cls: #{$namespace}-menu;
}
.#{$prefix-cls}__title {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* stylelint-disable-next-line */
max-height: calc(var(--top-tool-height) - 2px) !important;
/* stylelint-disable-next-line */

@ -10,14 +10,10 @@ export const useRenderMenuTitle = () => {
return icon ? (
<>
<Icon icon={meta.icon}></Icon>
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
{t(title as string)}
</span>
<span class="v-menu__title">{t(title as string)}</span>
</>
) : (
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
{t(title as string)}
</span>
<span class="v-menu__title">{t(title as string)}</span>
)
}

@ -62,6 +62,7 @@ export default {
yes: 'Yes',
no: 'No',
code:'Auto-generate on Save'
code: 'Auto-generate on Save'
},
ReportDashboard: {
DashboardList: {
@ -600,7 +601,8 @@ export default {
},
home: {
welcome: 'Welcome to use the Besure Digital Intelligent Central Control Platform',
message: 'The Besure Digital Intelligent Central Control Platform takes production operation as the core, uniformly integrating key business modules such as production planning, data collection, warehousing, equipment, energy, molds, quality, formulas and report analysis, to achieve full-process digital management from planning to execution, from equipment to products, and from data to decision-making.',
message:
'The Besure Digital Intelligent Central Control Platform takes production operation as the core, uniformly integrating key business modules such as production planning, data collection, warehousing, equipment, energy, molds, quality, formulas and report analysis, to achieve full-process digital management from planning to execution, from equipment to products, and from data to decision-making.',
productTitle: 'Overall production overview',
placeholderCreateTimeStart: 'Start Date',
placeholderCreateTimeEnd: 'End Date',
@ -633,7 +635,8 @@ export default {
failureRate: 'failure rate',
equipmentTitle: 'Statistics of equipment maintenance quantities',
equipmentClass: 'Equipment classification statistics',
equipmentMessage:'Classify and count by mold type, and select the top 10 items with the largest number of molds.',
equipmentMessage:
'Classify and count by mold type, and select the top 10 items with the largest number of molds.'
},
login: {
welcome: 'Welcome to the system',
@ -1053,7 +1056,8 @@ export default {
emailInvalid: 'Please input a valid email address',
mobileInvalid: 'Please input a valid mobile number',
importFileRequired: 'Please upload file',
importSuccessTip: 'Upload success: {createCount}; Update success: {updateCount}; Update failed: {failureCount};',
importSuccessTip:
'Upload success: {createCount}; Update success: {updateCount}; Update failed: {failureCount};',
importFailed: 'Upload failed, please upload again!',
importFileLimit: 'Only one file can be uploaded!',
resetPasswordTitle: 'Please input new password for "{username}"',
@ -1331,7 +1335,7 @@ export default {
qrcodeEmpty: 'No QR code',
validatorDeviceCodeRequired: 'Code can not be empty',
gjTitle: 'Select key components',
bjTitle: 'Select spare parts',
bjTitle: 'Select spare parts'
},
// Critical Component
EquipmentKeyItems: {
@ -1381,7 +1385,7 @@ export default {
placeholderInspectionMethod: 'Please select inspection method',
placeholderValueType: 'Please select value type',
placeholderUpperVal: 'Please input upper value',
placeholderLowerVal: 'Please input lower value',
placeholderLowerVal: 'Please input lower value'
},
// Plan Maintenance
PlanMaintenance: {
@ -1872,8 +1876,8 @@ export default {
addButtonText: 'Add',
exportButtonText: 'Export',
expandButtonText: 'Expand/Collapse',
tableCodeColumn: 'Category Code',
tableNameColumn: 'Category Name',
tableCodeColumn: 'Code',
tableNameColumn: 'Name',
tableSortColumn: 'Sort',
tableStatusColumn: 'Status',
tableCreateTimeColumn: 'Create Time',
@ -1902,6 +1906,8 @@ export default {
moduleName: 'Product Material Information',
searchNameLabel: 'Name',
searchNamePlaceholder: 'Please enter name',
searchCodeLabel: 'Code',
searchCodePlaceholder: 'Please enter code',
searchButtonText: 'Search',
resetButtonText: 'Reset',
addButtonText: 'Add',
@ -2203,9 +2209,10 @@ export default {
importFormatLimit: 'Only xls, xlsx format files are allowed.',
importDownloadTemplate: 'Download template',
importFileRequired: 'Please upload file',
importSuccessTip: 'Upload success: {createCount}; Update success: {updateCount}; Update failed: {failureCount};',
importSuccessTip:
'Upload success: {createCount}; Update success: {updateCount}; Update failed: {failureCount};',
importFailed: 'Upload failed, please upload again!',
importFileLimit: 'Only one file can be uploaded!',
importFileLimit: 'Only one file can be uploaded!'
},
CalHoliday: {
setWorkingDays: 'set working days',
@ -2268,7 +2275,6 @@ export default {
validatorClassificationRequired: 'File classification can not be empty',
validatorTypeRequired: 'File type can not be empty',
validatorStatusRequired: 'File status can not be empty'
}
},
@ -2815,7 +2821,7 @@ export default {
validatorSampleMethodRequired: 'Sampling method can not be empty',
validatorValRequired: 'Value can not be empty',
atIntervals: 'at intervals',
extraction:'extraction',
extraction: 'extraction'
},
ZjTask: {
moduleName: 'Inspection Task',
@ -3077,7 +3083,8 @@ export default {
readDialogSubmitButtonText: 'Read',
readDialogCancelButton: 'Cancel',
readDeviceConfirmMessage: 'Do you want to read device data?',
readDialogOverwriteTip: 'Reading again will overwrite existing device data and manual parameters',
readDialogOverwriteTip:
'Reading again will overwrite existing device data and manual parameters',
detailTabDeviceDataLabel: 'Device Data',
detailTabManualLabel: 'Manual Parameters',
detailDevicePointNameColumn: 'Point Name',
@ -3103,7 +3110,7 @@ export default {
nanualFormRemarkLabel: 'Remark',
nanualFormRemarkPlaceholder: 'Please enter remark',
nanualFormInputTimeLabel: 'Input Time',
nanualFormInputTimePlaceholder:'Please select input time',
nanualFormInputTimePlaceholder: 'Please select input time'
}
},
EnergyManagement: {
@ -3150,7 +3157,7 @@ export default {
validatorUnitRequired: 'Unit can not be empty',
validatorIsEnableRequired: 'Enabled can not be empty',
exportFilename: 'EnergyType.xls',
exportFilename: 'EnergyType.xls'
},
EnergyDeviceCheck: {
moduleName: 'Energy Report',
@ -3566,7 +3573,8 @@ export default {
capacityTypeDataCollection: 'Data Collection Capacity',
inventoryTaskType: 'Inventory Task',
taskNoDetailWarning: 'Task order {code} has no detail data, cannot be selected',
partialTaskNoDetailWarning: 'Some task orders have no detail data, selection has been automatically cancelled',
partialTaskNoDetailWarning:
'Some task orders have no detail data, selection has been automatically cancelled',
taskItemNeedPrefix: 'Task Order-',
detailNoProductWarning: 'Current detail has no related product, cannot set device',
deviceRelationSaved: 'Device relation saved',
@ -4189,7 +4197,7 @@ export default {
messageDeviceInfoMissingForRules: 'Device information is missing, unable to load point rules',
gridView: 'gridView',
sudoku:'sudoku',
sudoku: 'sudoku'
},
RunReport: {
moduleName: 'Device Operation Report',
@ -4281,9 +4289,11 @@ export default {
tableCollectionTimeColumn: 'Collection Time',
tableOperateColumn: 'Operation',
tableActionHistoryLabel: 'History',
tableActionHistoryAnalyseLabel: 'History Data Analysis',
dialogTitlePrefix: 'History: ',
dialogCollectionTimeLabel: 'Collection Time',
dialogPointFilterLabel: 'Point Filter',
dialogPointFilterPlaceholder: 'Please enter point name',
dialogCollectionTimeStartPlaceholder: 'Start Time',
dialogCollectionTimeEndPlaceholder: 'End Time',
dialogSearchButtonText: 'Search',

@ -601,7 +601,8 @@ export default {
},
home: {
welcome: '欢迎您使用必硕数字化智能中控平台',
message: '必硕数字化智能中控平台以生产运营为核心,统一整合生产计划、数据采集、仓储、设备、能源、模具、质量、配方及报表分析等关键业务模块,实现从计划到执行、从设备到制品、从数据到决策的全流程数字化管理。',
message:
'必硕数字化智能中控平台以生产运营为核心,统一整合生产计划、数据采集、仓储、设备、能源、模具、质量、配方及报表分析等关键业务模块,实现从计划到执行、从设备到制品、从数据到决策的全流程数字化管理。',
productTitle: '整体生产概况',
placeholderCreateTimeStart: '开始日期',
placeholderCreateTimeEnd: '结束日期',
@ -1323,7 +1324,7 @@ export default {
qrcodeEmpty: '暂无二维码',
validatorDeviceCodeRequired: '编码不能为空',
gjTitle: '选择关键件',
bjTitle: '选择备件',
bjTitle: '选择备件'
},
// 设备关键件
EquipmentKeyItems: {
@ -2323,7 +2324,7 @@ export default {
validatorSampleMethodRequired: '抽检方式不能为空',
validatorValRequired: '值不能为空',
atIntervals: '每间隔',
extraction:'抽取',
extraction: '抽取'
},
ZjTask: {
moduleName: '检验任务',
@ -2609,8 +2610,7 @@ export default {
nanualFormReferLabel: '参考值',
nanualFormReferPlaceholder: '请输入参考值',
nanualFormRemarkLabel: '备注',
nanualFormRemarkPlaceholder: '请输入备注',
nanualFormRemarkPlaceholder: '请输入备注'
}
},
FactoryModeling: {
@ -2704,8 +2704,8 @@ export default {
addButtonText: '新增',
exportButtonText: '导出',
expandButtonText: '展开/折叠',
tableCodeColumn: '分类编码',
tableNameColumn: '分类名称',
tableCodeColumn: '编码',
tableNameColumn: '名称',
tableSortColumn: '排序',
tableStatusColumn: '状态',
tableCreateTimeColumn: '创建时间',
@ -2734,6 +2734,8 @@ export default {
moduleName: '产品物料信息',
searchNameLabel: '名称',
searchNamePlaceholder: '请输入名称',
searchCodeLabel: '编码',
searchCodePlaceholder: '请输入编码',
searchButtonText: '搜索',
resetButtonText: '重置',
addButtonText: '新增',
@ -3038,7 +3040,7 @@ export default {
importSuccessTip:
'上传成功数量:{createCount};更新成功数量:{updateCount};更新失败数量:{failureCount}',
importFailed: '上传失败,请您重新上传!',
importFileLimit: '最多只能上传一个文件!',
importFileLimit: '最多只能上传一个文件!'
},
//假日管理
CalHoliday: {
@ -3098,7 +3100,6 @@ export default {
validatorNameRequired: '文件名不能为空',
validatorStatusRequired: '文件状态不能为空'
}
},
ProductionPlan: {
Task: {
@ -4032,7 +4033,7 @@ export default {
messageDeviceInfoMissingForRules: '设备信息缺失,无法加载点位规则',
gridView: '表格视图',
sudoku:'九宫格',
sudoku: '九宫格'
},
RunReport: {
moduleName: '设备运行报表',
@ -4124,9 +4125,11 @@ export default {
tableCollectionTimeColumn: '采集时间',
tableOperateColumn: '操作',
tableActionHistoryLabel: '历史记录',
tableActionHistoryAnalyseLabel: '数据分析',
dialogTitlePrefix: '历史记录:',
dialogCollectionTimeLabel: '采集时间',
dialogPointFilterLabel: '点位筛选',
dialogPointFilterPlaceholder: '请输入点位名称',
dialogCollectionTimeStartPlaceholder: '开始时间',
dialogCollectionTimeEndPlaceholder: '结束时间',
dialogSearchButtonText: '查询',
@ -4496,9 +4499,11 @@ export default {
tableCollectionTimeColumn: '采集时间',
tableOperateColumn: '操作',
tableActionHistoryLabel: '历史记录',
tableActionHistoryAnalyseLabel: '数据分析',
dialogTitlePrefix: '历史记录:',
dialogCollectionTimeLabel: '采集时间',
dialogPointFilterLabel: '点位筛选',
dialogPointFilterPlaceholder: '请输入点位名称',
dialogCollectionTimeStartPlaceholder: '开始时间',
dialogCollectionTimeEndPlaceholder: '结束时间',
dialogSearchButtonText: '查询',
@ -4538,8 +4543,6 @@ export default {
messageDeviceNoParams: '该设备下没有参数',
messageFetchChartFailed: '获取图表数据失败'
}
}
},
ProductionReport: {

@ -35,3 +35,19 @@
border-left-color: var(--el-color-primary);
}
}
/* 全局生效,所有表格的悬停色都会改变 */
.el-table--enable-row-hover .el-table__body tr:hover > td {
background-color: #f0f9eb !important;
}
/* 2. 关键补全:针对展开行内的 hover 状态 */
/* 展开行tr.expanded内部的 td 需要单独设置 */
.el-table__body .el-table__expanded-cell:hover > td {
background-color: #f0f9eb !important;
}
/* 3. 针对展开后触发的 hover-row 类(某些版本生效) */
.el-table__body tr.hover-row > td {
background-color: #f0f9eb !important;
}

@ -2,41 +2,29 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="120px"
@submit.prevent
>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto" @submit.prevent>
<el-form-item :label="t('FactoryModeling.ProductInformation.searchCodeLabel')" prop="barCode">
<el-input v-model="queryParams.barCode"
:placeholder="t('FactoryModeling.ProductInformation.searchCodePlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductInformation.searchNameLabel')" prop="name">
<el-input
v-model="queryParams.name"
:placeholder="t('FactoryModeling.ProductInformation.searchNamePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
<el-input v-model="queryParams.name"
:placeholder="t('FactoryModeling.ProductInformation.searchNamePlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.searchButtonText') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.resetButtonText') }}</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:product:create']"
>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.searchButtonText') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.resetButtonText') }}
</el-button>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:product:create']">
<Icon icon="ep:plus" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.addButtonText') }}
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:product:export']"
>
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['erp:product:export']">
<Icon icon="ep:download" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.exportButtonText') }}
</el-button>
</el-form-item>
@ -47,12 +35,7 @@
<ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<!-- 使用 v-for 动态生成 el-tab-pane -->
<el-tab-pane
v-for="item in parentList"
:key="item.id"
:label="item.name"
:name="item.id.toString()"
/>
<el-tab-pane v-for="item in parentList" :key="item.id" :label="item.name" :name="item.id.toString()" />
<!-- <el-tab-pane label="产品" name="2" />
<el-tab-pane label="原料" name="1" />
<el-tab-pane label="备件" name="5" />
@ -62,24 +45,24 @@
<el-tab-pane label="其他" name="0" /> -->
</el-tabs>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('FactoryModeling.ProductInformation.tableBarCodeColumn')" align="center" prop="barCode" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableNameColumn')" align="left" prop="name" width="220px" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStandardColumn')" align="center" prop="standard" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCategoryColumn')" align="center" prop="subCategoryName" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableUnitColumn')" align="center" prop="unitName" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStatusColumn')" align="center" prop="status" sortable>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableBarCodeColumn')" align="center" prop="barCode"
sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableNameColumn')" align="left" prop="name"
width="220px" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStandardColumn')" align="center"
prop="standard" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCategoryColumn')" align="center"
prop="subCategoryName" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableUnitColumn')" align="center" prop="unitName"
sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStatusColumn')" align="center" prop="status"
sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
:label="t('FactoryModeling.ProductInformation.tableCreateTimeColumn')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable
/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCreateTimeColumn')" align="center"
prop="createTime" :formatter="dateFormatter" width="180px" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableOperateColumn')" align="center" width="150px">
<template #default="scope">
<!-- <el-button-->
@ -90,32 +73,18 @@
<!-- >-->
<!-- BOM-->
<!-- </el-button>-->
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:product:update']"
>
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:product:update']">
{{ t('FactoryModeling.ProductInformation.tableEditAction') }}
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:product:delete']"
>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:product:delete']">
{{ t('FactoryModeling.ProductInformation.tableDeleteAction') }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
@ -148,6 +117,7 @@ const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
barCode: undefined,
categoryId: undefined
})
const queryFormRef = ref() //

@ -43,6 +43,9 @@
<el-option v-for="item in modelList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.images')" prop="images">
<UploadImg style="height: 100px" v-model="formData.images" />
</el-form-item>
</template>
<!-- <el-form-item label="设备类型" prop="deviceType">
<el-select v-model="formData.deviceType" placeholder="请选择设备类型">
@ -134,6 +137,7 @@ const formData = ref({
username: undefined,
password: undefined,
topic: undefined,
images: undefined,
})
const formRules = reactive({
create: {
@ -188,7 +192,7 @@ const submitForm = async () => {
//
formLoading.value = true
try {
const { id, deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable, topic } = formData.value as any
const { id, deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable, topic,images } = formData.value as any
if (formType.value === 'create') {
const data: Partial<DeviceVO> = {
@ -197,16 +201,17 @@ const submitForm = async () => {
deviceModelId,
sampleCycle,
remark,
isEnable: false
isEnable: false,
images
}
await DeviceApi.createDevice(data as DeviceVO)
message.success(t('common.createSuccess'))
} else if (formType.value === 'update') {
const data: any = { id, deviceCode, deviceName, deviceModelId, sampleCycle, isEnable }
const data: any = { id, deviceCode, deviceName, deviceModelId, sampleCycle, isEnable,images}
await DeviceApi.updateDevice(data)
message.success(t('common.updateSuccess'))
} else {
const data: any = { id, deviceCode, deviceName, deviceModelId, isEnable, topic }
const data: any = { id, deviceCode, deviceName, deviceModelId, isEnable, topic,images}
await DeviceApi.updateDevice(data)
message.success(t('common.updateSuccess'))
}
@ -246,3 +251,17 @@ const resetForm = () => {
/** 初始化 **/
</script>
<style scoped>
:deep(.upload-box[data-v-57417a1d] .upload .el-upload .el-upload-dragger ){
display: flex;
align-items: center;
justify-content: center;
width: 70%;
height: 70%;
padding: 0;
overflow: hidden;
background-color: transparent;
border: 1px dashed var(--el-border-color-darker);
border-radius: var(--57417a1d-borderradius);
}
</style>

File diff suppressed because it is too large Load Diff

@ -210,8 +210,17 @@
<div class="device-card">
<div class="header">
<div class="device-icon">
<i>📊</i>
<div class="device-icon" @click.stop>
<!-- <i>📊</i>-->
<el-image
style="width: 60px; height: 60px"
:src="item.images || 'https://p119-minio-upload.ngsk.tech:7001/besure/998a6fc5d7da4ad079cc5afaa1ca5293917c17080c9805dc791039d3534e9dba.jpeg'"
:preview-src-list="[
item.images || 'https://p119-minio-upload.ngsk.tech:7001/besure/998a6fc5d7da4ad079cc5afaa1ca5293917c17080c9805dc791039d3534e9dba.jpeg'
]"
fit="cover"
preview-teleported
/> <!-- <img src="@/assets/images/device.png" alt="Device Icon" />-->
</div>
<div class="device-info">
<div class="device-name">{{ item.deviceName }}</div>
@ -320,9 +329,9 @@
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<DeviceForm ref="formRef" @success="getList" />
<DeviceForm ref="formRef" @success="getListOne" />
<!-- 子表的列表 -->
<ContentWrap v-if="ifShow">
<!-- <ContentWrap v-if="ifShow">
<template v-if="attributeDeviceId">
<div class="mb-10px flex items-center justify-between text-sm text-gray-500">
<div>
@ -711,7 +720,7 @@
</el-tabs>
</template>
<el-empty v-else :description="t('DataCollection.Device.emptyDescription')" />
</ContentWrap>
</ContentWrap>-->
<Dialog :title="t('DataCollection.Device.alarmHistoryTitle')" v-model="deviceAlarmDialogVisible" width="1200px">
<el-form
@ -836,6 +845,9 @@ import DetailForm from "@/views/iot/device/detailForm.vue";
import {ProductUnitApi, ProductUnitVO} from "@/api/erp/product/unit";
const ifShow =ref(true)
const currentView = ref('table') // 'table' 'grid'
// script setup
const noImageUrl =
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iODAiIGhlaWdodD0iODAiIGZpbGw9IiNFOUVDRUYiLz48dGV4dCB4PSI0MCIgeT0iNDUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiM5OTkiIGZvbnQtc2l6ZT0iMTQiPuWbvueJh+WKoOi9veWksei0pTwvdGV4dD48L3N2Zz4='
//
const router = useRouter()
//
@ -1004,6 +1016,22 @@ const getList = async (showLoading = true) => {
}
}
/** 查询列表 */
const getListOne = async (showLoading = true) => {
if (showLoading) {
loading.value = true
}
try {
const data = await DeviceApi.getDevicePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
if (showLoading) {
loading.value = false
}
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
@ -1141,6 +1169,14 @@ const deviceRuleTabLabel = computed(() => {
const handleShowAttribute = (row: any) => {
attributeDeviceId.value = row?.id
attributeDeviceName.value = row?.deviceName ?? ''
router.push({
path: '/iot/pointManagement',
query: {
id: row?.id,
name: row?.deviceName
}
});
}
const ruleLoading = ref(false)
@ -2106,17 +2142,30 @@ const handleShowDeviceAlarmHistory = async () => {
}
.device-icon {
width: 40px;
/*width: 40px;
height: 40px;
background-color: #f0f7ff;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
margin-right: 12px;*/
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 60px;
background-color: #f0f7ff;
border-radius: 8px;
overflow: hidden;
.el-image {
cursor: pointer;
}
}
.device-info {
margin-left: 6px;
flex: 1;
}

File diff suppressed because it is too large Load Diff

@ -108,12 +108,15 @@
:label="t('DataCollection.HistoryData.tableOperateColumn')"
align="center"
fixed="right"
width="150px"
width="300px"
>
<template #default="scope">
<el-button link type="primary" @click="handleSingleView(scope.row)">
{{ t('DataCollection.HistoryData.tableActionHistoryLabel') }}
</el-button>
<el-button link type="primary" @click="handleSingleAnalyseView(scope.row)">
{{ t('DataCollection.HistoryData.tableActionHistoryAnalyseLabel') }}
</el-button>
</template>
</el-table-column>
</el-table>
@ -137,9 +140,10 @@ import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { DeviceApi, LineDeviceVO, LineDevicePageParams } from '@/api/iot/device'
import HistorySingleDeviceDialog from './HistorySingleDeviceDialog.vue'
import {useRouter} from "vue-router";
//
const router = useRouter()
defineOptions({ name: 'HistoryData' })
const message = useMessage()
const { t } = useI18n()
@ -225,6 +229,27 @@ const handleSingleView = (row: LineDeviceVO) => {
singleDialogVisible.value = true
}
const handleSingleAnalyseView = (row: LineDeviceVO) => {
const deviceId = (row as any)?.deviceId ?? row?.id
if (deviceId === undefined || deviceId === null || deviceId === '') {
return
}
singleDeviceId.value = deviceId
singleDeviceName.value = row.deviceName || ''
router.push({
path: '/iot/historySingleAnalyse',
query: {
id: deviceId,
name: singleDeviceName.value,
deviceCode: row.deviceCode,
lineNode: row.lineNode,
lineName: row.lineName,
collectionTime: row.collectionTime
}
});
}
onMounted(() => {
getList()
})

@ -7,15 +7,15 @@
</div>
<ContentWrap v-loading="loading">
<div v-if="sections.length" class="single-device-dialog__table-grid">
<div v-for="section in sections" :key="section.key" class="single-device-dialog__section">
<!-- <div v-for="section in sections" :key="section.key" class="single-device-dialog__section">
<div class="single-device-dialog__section-title">
{{ section.title }}
</div>
<el-empty
v-if="!section.columns.length"
</div>-->
<!-- <el-empty
v-if="!section.length"
:description="t('DataCollection.RealTimeMonitoring.emptyDescription')"
/>
<el-table
/>-->
<!-- <el-table
v-else :data="section.rows" :border="true" :header-cell-style="headerCellStyle"
:cell-style="bodyCellStyle" size="small">
<el-table-column
@ -25,9 +25,23 @@ v-for="col in section.columns" :key="col.prop" :prop="col.prop" :label="col.labe
<span>{{ formatCell(scope.row[col.prop]) }}</span>
</template>
</el-table-column>
</el-table>
</el-table>-->
<div v-for="section in sections" :key="section.id" class="form-section">
<h3 v-if="section.title" class="section-title">{{ section.title }}</h3>
<div class="form-grid">
<div
v-for="item in section.items"
:key="item.prop"
class="form-item"
>
<div class="form-label">{{ item.label }}:</div>
<div class="form-value">{{ formatCellTwo(item.value) }}</div>
</div>
</div>
</div>
<!-- </div>-->
</div>
<el-empty
v-else
:description="t('DataCollection.RealTimeMonitoring.emptyDescription')"
@ -49,10 +63,14 @@ type SectionColumn = {
type SectionRow = Record<string, string | number | null>
type Section = {
key: string
title: string
columns: SectionColumn[]
rows: SectionRow[]
id: number
title?: string
items: FormItem[]
}
interface FormItem {
prop: string
label: string
value: any
}
const { t } = useI18n()
@ -83,8 +101,44 @@ const dialogTitle = computed(() => {
const loading = ref(false)
const deviceName = computed(() => props.deviceName)
const sections = ref<Section[]>([])
const sections = ref<Section[]>([
{
id: 1,
title: '工艺参数监控',
items: [
{ prop: 'addressValue', label: '系统运行', value: 1, unit: '℃' },
{ prop: 'attributeName', label: '烘干湿度', value: 2, unit: '%' },
{ prop: 'attributeName', label: '输送速度', value: 3, unit: 'm/min' },
{ prop: 'attributeName', label: '回风温度', value: 4, unit: '℃' },
{ prop: 'attributeName', label: '排风温度', value: 5, unit: '℃' },
{ prop: 'attributeName', label: '呼叫状态', value: 6 },
{ prop: 'attributeName', label: '在线状态', value: 7 },
{ prop: 'attributeName', label: '运行状态', value: 3 },
{ prop: 'attributeName', label: '主缸速度', value: 0, unit: '%' },
{ prop: 'attributeName', label: '提布辊速度', value: 0 },
{ prop: 'fanSpeed', label: '风机速度', value: '暂无数据' },
{ prop: 'mainLevel', label: '主缸水位', value: 1, unit: '%' },
{ prop: 'mainWater', label: '主缸水量', value: 145, unit: 'L' },
{ prop: 'setTemp', label: '设定温度', value: 0.0, unit: '℃' },
{ prop: 'actualTemp', label: '实际温度', value: 0.0, unit: '℃' },
],
},
])
const formatValue = (item: FormItem) => {
let value = item.value
if (value === null || value === undefined) return '--'
if (typeof value === 'number') {
value = Number.isInteger(value) ? value.toString() : value.toFixed(2)
}
if (item.unit) {
value += ` ${item.unit}`
}
return value
}
const toGroupMap = (value: any): Record<string, any[]> => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return {}
@ -136,6 +190,14 @@ const formatCell = (value: string | number | null | undefined) => {
return value
}
const formatCellTwo = (value: any) => {
//
if (typeof value === 'number') {
return value.toFixed(2)
}
return value
}
const buildSectionsFromGroups = (groups: Record<string, any[]>): Section[] => {
const result: Section[] = []
for (const [key, list] of Object.entries(groups)) {
@ -150,12 +212,12 @@ const buildSectionsFromGroups = (groups: Record<string, any[]>): Section[] => {
row[prop] = item?.addressValue ?? '-'
})
}
result.push({
/* result.push({
key,
title: key,
columns,
rows: columns.length ? [row] : []
})
})*/
}
return result
}
@ -167,15 +229,16 @@ const fetchList = async () => {
}
loading.value = true
try {
const res: any = await DeviceApi.getSingleDevice({ deviceId: props.deviceId })
const groups = Array.isArray(res)
const res: any = await DeviceApi.getSingleDeviceFrom({ deviceId: props.deviceId })
/* const groups = Array.isArray(res)
? { [t('DataCollection.RealTimeMonitoring.defaultGroupName')]: res }
: {
...toGroupMap(res?.data),
...toGroupMap(res?.data?.data),
...toGroupMap(res)
}
sections.value = buildSectionsFromGroups(groups)
//sections.value = buildSectionsFromGroups(groups)*/
sections.value = res
} finally {
loading.value = false
}
@ -222,4 +285,84 @@ watch(
.single-device-dialog__section :deep(.el-table__inner-wrapper) {
border-radius: 0;
}
/* 表单容器 */
.form-section {
margin-bottom: 24px;
}
/* 标题样式 */
.section-title {
font-size: 13px;
font-weight: bold;
margin-bottom: 12px;
color: var(--el-text-color-primary);
}
/* 三列网格容器 */
.form-grid {
display: grid;
grid-template-columns: repeat(4, 1fr); /* 三列等宽 */
/*gap: 16px;*/ /* 列间距和行间距 */
/* padding: 8px;*/
background-color: #fff;
/* border: 1px solid #dcdfe6; !* 外边框 *!*/
border-radius: 4px;
min-height: 35px;
}
/* 表单项:标签 + 值 */
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
min-height: 35px;
/* padding: 8px 0;*/
border: 1px solid #dcdfe6;
/* border-right: 1px solid #dcdfe6; !* *!
border-bottom: 1px solid #dcdfe6; !* 下边框 *!*/
}
/* 标签样式 */
.form-label {
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
color: #666;
height: 100%;
min-height: 35px;
width: 150px; /* 固定标签宽度,避免错位 */
background-color: #f5f7fa; /* 背景色 */
}
/* 值样式 */
.form-value {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 6px 12px;
background: white;
min-height: 35px;
box-sizing: border-box;
overflow: hidden;
cursor: default;
transition: all 0.2s;
}
/* 响应式:小屏幕下改为两列或单列 */
@media (max-width: 768px) {
.form-grid {
grid-template-columns: repeat(2, 1fr); /* 平板改为两列 */
}
}
@media (max-width: 480px) {
.form-grid {
grid-template-columns: 1fr; /* 手机改为单列 */
}
}
</style>

@ -79,7 +79,7 @@
</el-tooltip>
</span>
</template>
<el-select v-model="formData.orgType" @change="handleOrgTypeChange" :placeholder="t('FactoryModeling.FactoryStructure.dialogOrgTypePlaceholder')">
<el-select v-model="formData.orgType" :placeholder="t('FactoryModeling.FactoryStructure.dialogOrgTypePlaceholder')">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.MES_ORG_TYPE)"
:key="dict.value"
@ -89,25 +89,20 @@
</el-select>
</el-form-item>
<el-form-item :label="t('FactoryModeling.FactoryStructure.dialogMachineLabel')" v-if="formData.orgClass == 'workplace'" prop="machineId">
<!-- <el-tree-select
v-model="formData.machineId"
:data="machineComponentTree"
:props="defaultProps"
check-strictly
default-expand-all
placeholder="请选择机台"
/> -->
<el-select v-model="formData.machineId" :placeholder="t('FactoryModeling.FactoryStructure.dialogMachinePlaceholder')" clearable>
<!-- <el-select v-model="formData.machineId" :placeholder="t('FactoryModeling.FactoryStructure.dialogMachinePlaceholder')" clearable>
<el-option
v-for="item in deviceList"
:key="item.id"
:label="item.deviceName"
:value="item.id"
/>
</el-select>
</el-select>-->
<el-input :model-value="displayItemDevice" readonly clearable class="device-ledger-selection-input"
:placeholder="t('FactoryModeling.FactoryStructure.dialogMachinePlaceholder')"
@click="openCriticalComponentDialog" />
</el-form-item>
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.dvName')" prop="dvId" v-if="formData.orgClass == 'workplace'">
<el-select v-model="formData.dvId" filterable :placeholder="t('EquipmentManagement.EquipmentLedger.dvId')" clearable>
<!-- <el-select v-model="formData.dvId" filterable :placeholder="t('EquipmentManagement.EquipmentLedger.dvId')" clearable>
<el-option
v-for="item in dvList"
:key="item.id"
@ -115,7 +110,10 @@
:value="item.id"
:disabled="item.selected === true"
/>
</el-select>
</el-select>-->
<el-input :model-value="dvIdItemDevice" readonly clearable class="device-ledger-selection-input"
:placeholder="t('EquipmentManagement.EquipmentLedger.dvId')"
@click="openCaijiComponentDialog" />
</el-form-item>
<!-- <el-form-item label="组织状态" prop="status">
<el-radio-group v-model="formData.status">
@ -134,6 +132,61 @@
<el-button @click="dialogVisible = false">{{ t('FactoryModeling.FactoryStructure.dialogCancelButton') }}</el-button>
</template>
</Dialog>
<TableSelectDialog
ref="deviceSelectDialogRef"
title="选择设备"
:columns="deviceColumns"
:fetch-api="fetchDeviceLedgerPage"
row-key="id"
@confirm="handleDeviceSelectConfirm"
:query-params="mergedQueryParams"
:selection-type="'single'"
:default-selected-keys="ids"
>
<!-- 使用 header 插槽插入查询表单 -->
<template #header>
<el-form ref="searchFormRef" :model="searchParams" :inline="true" >
<el-form-item label="设备编号" prop="deviceCode">
<el-input v-model="searchParams.deviceCode" placeholder="请输入编号" clearable />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="searchParams.deviceName" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">{{ t('FactoryModeling.ProductInformation.searchButtonText') }}</el-button>
<el-button @click="resetSearch">{{ t('FactoryModeling.ProductInformation.resetButtonText') }}</el-button>
</el-form-item>
</el-form>
</template>
</TableSelectDialog>
<TableSelectDialog
ref="caiJiSelectDialogRef"
title="选择采集设备"
:columns="caiJiColumns"
:fetch-api="fetchCaiJILedgerPage"
row-key="id"
@confirm="handleCaiJISelectConfirm"
:query-params="mergedQueryParams"
:selection-type="'single'"
:default-selected-keys="CJIds"
>
<!-- 使用 header 插槽插入查询表单 -->
<template #header>
<el-form ref="searchFormRef" :model="searchParams" :inline="true" >
<el-form-item label="设备编号" prop="deviceCode">
<el-input v-model="searchParams.deviceCode" placeholder="请输入编号" clearable />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="searchParams.deviceName" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleCJSearch">{{ t('FactoryModeling.ProductInformation.searchButtonText') }}</el-button>
<el-button @click="resetCJSearch">{{ t('FactoryModeling.ProductInformation.resetButtonText') }}</el-button>
</el-form-item>
</el-form>
</template>
</TableSelectDialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
@ -142,13 +195,14 @@ import { defaultProps, handleTree } from '@/utils/tree'
import {MachineComponentApi} from "@/api/mes/machine";
import {DeviceLedgerApi, DeviceLedgerVO} from "@/api/mes/deviceledger";
import { DeviceApi,DeviceVO } from '@/api/iot/device'
import TableSelectDialog from '@/components/TableSelectDialog/TableSelectDialog.vue'
/** 产线工位 表单 */
defineOptions({ name: 'OrganizationForm' })
const loading = ref(true)
const { t } = useI18n() //
const message = useMessage() //
const deviceSelectDialogRef = ref(false)
const caiJiSelectDialogRef = ref(false)
const dvList = ref<DeviceVO[]>([]) //
const deviceList = ref<DeviceLedgerVO[]>([]) //
const dialogVisible = ref(false) //
@ -179,22 +233,70 @@ const formRules = reactive({
parentId: [{ required: true, message: t('FactoryModeling.FactoryStructure.validatorParentRequired'), trigger: 'blur' }],
orgClass: [{ required: true, message: t('FactoryModeling.FactoryStructure.validatorOrgClassRequired'), trigger: 'blur' }]
})
const itemList = ref<DeviceLedgerVO[]>([])
const CJList = ref<DeviceVO[]>([]) //
const mergedQueryParams = computed(() => ({ ...searchParams }))
const formRef = ref() // Ref
const organizationTree = ref() //
const deviceColumns = [
{ label: '设备编号', prop: 'deviceCode', minWidth: 140 },
{ label: '设备名称', prop: 'deviceName', minWidth: 160 },
{ label: '设备型号', prop: 'deviceModel', minWidth: 140 },
{ label: '所属车间', prop: 'workshop', minWidth: 140 }
]
const caiJiColumns = [
{ label: '设备编号', prop: 'deviceCode', minWidth: 140 },
{ label: '设备名称', prop: 'deviceName', minWidth: 160 },
]
// 1.
const searchParams = reactive({
deviceCode: undefined,
deviceName: undefined,
})
const handleSearch = () => {
//
deviceSelectDialogRef.value?.reload?.()
}
const searchFormRef = ref()
const resetSearch = () => {
searchFormRef.value?.resetFields()
handleSearch()
}
const handleCJSearch = () => {
//
caiJiSelectDialogRef.value?.reload?.()
}
const searchCJFormRef = ref()
const resetCJSearch = () => {
searchFormRef.value?.resetFields()
handleCJSearch()
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
initSelectedItems()
//
if (id) {
formLoading.value = true
try {
formData.value = await OrganizationApi.getOrganization(id)
ids.value=formData.value.machineId != undefined
? formData.value.machineId.toString().split(",").map(Number)
: []
CJIds.value=formData.value.dvId!=undefined?formData.value.dvId.toString().split(",").map(Number):[]
} finally {
formLoading.value = false
}
}else{
ids.value=[]
CJIds.value=[]
}
await getOrganizationTree()
if (type == 'create' || typeof formData.value.machineId != 'number') {
@ -283,8 +385,115 @@ const getMachineComponentTree = async () => {
root.children = handleTree(data, 'id', 'parentId')
machineComponentTree.value.push(root)
}
const ids = ref([])
const CJIds = ref([])
const displayItemDevice = computed( () => {
if (!ids.value.length)return ''
if (!itemList.value.length) {
return formData.value.machineId ? String(formData.value.machineId) : ''
}
const map = new Map(itemList.value.map((item) => [item.id, item.deviceName]))
const names = ids.value
.map((id) => map.get(id))
.filter((name) => name)
return names.join(',')
})
const dvIdItemDevice = computed( () => {
if (!CJIds.value.length)return ''
if (!CJList.value.length) {
return formData.value.dvId ? String(formData.value.dvId) : ''
}
const map = new Map(CJList.value.map((item) => [item.id, item.deviceName]))
const names = CJIds.value
.map((id) => map.get(id))
.filter((name) => name)
return names.join(',')
})
const fetchDeviceLedgerPage = (params: Record<string, any>) => {
return DeviceLedgerApi.getDeviceLedgerPage({
...params,
})
}
const fetchCaiJILedgerPage = (params: Record<string, any>) => {
return DeviceApi.getAvailableListPage({
...params,
})
}
const selectedDeviceRows = ref<any[]>([])
const selectedCaiJiRows = ref<any[]>([])
/*打开关联设备确认*/
const handleDeviceSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
formData.value.devices = payload.rows
.map((item) => {
const id = Number(item.id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: item.deviceName || item.name || item.code || `设备ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
selectedDeviceRows.value = payload.rows
formData.value.machineId = payload.ids.join(',')
ids.value = payload.ids.map((id) => Number(id))
}
/*打开关联设备*/
const openCriticalComponentDialog = async () => {
searchParams.deviceCode=''
searchParams.deviceName=''
const rows = selectedDeviceRows.value.map((item) => ({ ...item, id: Number(item.id) }))
deviceSelectDialogRef.value?.open(rows)
let initIds= formData.value.machineId!=undefined?formData.value.machineId.toString().split(","):[]
ids.value=initIds.map((id) => Number(id))
}
/*打开采集设备确认*/
const handleCaiJISelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
formData.value.devices = payload.rows
.map((item) => {
const id = Number(item.id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: item.deviceName || item.name || item.code || `设备ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
selectedCaiJiRows.value = payload.rows
formData.value.dvId = payload.ids.join(',')
CJIds.value = payload.ids.map((id) => Number(id))
}
/*打开采集设备*/
const openCaijiComponentDialog = async () => {
searchParams.deviceCode=''
searchParams.deviceName=''
const rows = selectedCaiJiRows.value.map((item) => ({ ...item, id: Number(item.id) }))
caiJiSelectDialogRef.value?.open(rows)
let initIds= formData.value.dvId!=undefined?formData.value.dvId.toString().split(","):[]
CJIds.value=initIds.map((id) => Number(id))
}
const initSelectedItems = async () => {
if (!itemList.value.length) {
loading.value = true
try {
itemList.value = await DeviceLedgerApi.getDeviceLedgerList()
} finally {
loading.value = false
}
}
if (!CJList.value.length) {
loading.value = true
try {
CJList.value = await DeviceApi.getAvailableList()
} finally {
loading.value = false
}
}
}
/** 初始化 **/
onMounted(async () => {
//console.log("ssss")

@ -34,7 +34,7 @@
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldInspectionPlan.subjectName')" prop="subjectIds">
<el-select
<!-- <el-select
v-model="formData.subjectIds"
multiple
filterable
@ -43,7 +43,10 @@
class="!w-full"
>
<el-option v-for="item in subjectOptions" :key="item.id" :label="item.subjectName" :value="item.id" />
</el-select>
</el-select>-->
<el-input :model-value="displayItemDevice" readonly clearable class="device-ledger-selection-input"
:placeholder="t('MoldManagement.MoldInspectionPlan.placeholderSubjectSelect')"
@click="openCriticalComponentDialog" />
</el-form-item>
</el-form>
@ -52,13 +55,40 @@
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
<TableSelectDialog
ref="deviceSelectDialogRef"
title="选择设备"
:columns="deviceColumns"
:fetch-api="fetchDeviceLedgerPage"
row-key="id"
@confirm="handleDeviceSelectConfirm"
:query-params="mergedQueryParams"
:default-selected-keys="ids"
>
<!-- 使用 header 插槽插入查询表单 -->
<template #header>
<el-form ref="searchFormRef" :model="searchParams" :inline="true" >
<el-form-item label="设备编号" prop="deviceCode">
<el-input v-model="searchParams.deviceCode" placeholder="请输入编号" clearable />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="searchParams.deviceName" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">{{ t('FactoryModeling.ProductInformation.searchButtonText') }}</el-button>
<el-button @click="resetSearch">{{ t('FactoryModeling.ProductInformation.resetButtonText') }}</el-button>
</el-form-item>
</el-form>
</template>
</TableSelectDialog>
</template>
<script setup lang="ts">
import type { FormRules } from 'element-plus'
import { DvSubjectApi, DvSubjectVO } from '@/api/mold/inspectionItems'
import { PlanMaintenanceApi, PlanMaintenanceVO } from '@/api/mold/planmaintenance'
import {DeviceLedgerApi, DeviceLedgerVO} from "@/api/mes/deviceledger";
import TableSelectDialog from '@/components/TableSelectDialog/TableSelectDialog.vue'
defineOptions({ name: 'MoldInspectionPlanForm' })
const { t } = useI18n()
@ -74,6 +104,7 @@ const ensureSubjectOptionsLoaded = async () => {
if (subjectOptions.value.length) return
const res = await DvSubjectApi.getMoldSubjectAllList()
const list = Array.isArray(res) ? res : res?.list
itemList.value = (list ?? []) as DvSubjectVO[]
subjectOptions.value = (list ?? []) as DvSubjectVO[]
}
@ -97,7 +128,94 @@ const parseIds = (value: any): Array<number | string> => {
})
.filter((v): v is number | string => v !== undefined)
}
const ids = ref([])
const itemList = ref<DvSubjectVO[]>([])
const selectedDeviceRows = ref<any[]>([])
const deviceSelectDialogRef = ref(false)
const mergedQueryParams = computed(() => ({ ...searchParams }))
const deviceColumns = [
{ label: '项目编码', prop: 'subjectCode', minWidth: 140 },
{ label: '项目名称', prop: 'subjectName', minWidth: 160 },
{ label: '项目类型', prop: 'subjectType', minWidth: 140 },
{ label: '项目内容', prop: 'subjectContent', minWidth: 140 }
]
const fetchDeviceLedgerPage = async (params: Record<string, any>) => {
const res = await DvSubjectApi.getDvSubjectPage({
...params,
})
const list = res.list || []
//
const selectedMap = new Map(
selectedDeviceRows.value.map((item) => [item.id, item])
)
return {
...res,
list: list.map((item) => ({
...item,
...selectedMap.get(item.id)
}))
}
}
// 1.
const searchParams = reactive({
subjectCode: undefined,
subjectName: undefined,
})
const displayItemDevice = computed( () => {
if (!ids.value.length)return ''
if (!itemList.value.length) {
return formData.value.subjectIds ? String(formData.value.subjectIds) : ''
}
const map = new Map(itemList.value.map((item) => [item.id, item.subjectName]))
const names = ids.value
.map((id) => map.get(id))
.filter((name) => name)
return names.join(',')
})
const openCriticalComponentDialog = async () => {
searchParams.subjectCode=''
searchParams.subjectName=''
const rows = selectedDeviceRows.value.length
//deviceSelectDialogRef.value?.open(rows)
let initIds= formData.value.subjectIds!=undefined?formData.value.subjectIds.toString().split(","):[]
deviceSelectDialogRef.value?.open([], {
defaultSelectedKeys: initIds
})
ids.value=initIds.map((id) => Number(id))
}
const handleDeviceSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
formData.value.subjectIds = payload.rows
.map((item) => {
const id = Number(item.id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: item.subjectName || item.name || item.code || `设备ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
selectedDeviceRows.value = payload.rows
formData.value.subjectIds = payload.ids.join(',')
ids.value = payload.ids.map((id) => Number(id))
}
const handleSearch = () => {
//
deviceSelectDialogRef.value?.reload?.()
}
const searchFormRef = ref()
const resetSearch = () => {
searchFormRef.value?.resetFields()
handleSearch()
}
const initFormData = () => ({
id: undefined as PlanMaintenanceVO['id'],
planName: '' as string,
@ -141,6 +259,9 @@ const open = async (type: 'create' | 'update', row?: Partial<PlanMaintenanceVO>)
description: (row.description as any) ?? '',
subjectIds: parseIds((row as any).subjectIds ?? (row as any).subjectIdS)
}
ids.value=parseIds((row as any).subjectIds ?? (row as any).subjectIdS)
}else{
ids.value=[]
}
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save