diff --git a/src/locales/en-US.js b/src/locales/en-US.js index 04f1f87..5590b17 100644 --- a/src/locales/en-US.js +++ b/src/locales/en-US.js @@ -474,7 +474,8 @@ export default { searchPlaceholder: 'Enter task name', empty: 'No equipment inspection tasks', createTicketSuccess: 'Work order created successfully', - createTicketFail: 'Work order creation failed' + createTicketFail: 'Work order creation failed', + lineFilter: 'Line', }, equipmentInspectionRecord: { moduleName: 'Equipment Inspection Records', @@ -515,7 +516,8 @@ export default { images: 'Images', maxUploadCount: 'You can upload up to 9 images', selectAllDecisionError: 'Please choose a result for all pending items', - noResultData: 'No inspection items' + noResultData: 'No inspection items', + lineFilter: 'Line', }, moldWorkOrder: { moduleName: 'Inspection Records', diff --git a/src/locales/zh-CN.js b/src/locales/zh-CN.js index 91f0e4d..a1250c2 100644 --- a/src/locales/zh-CN.js +++ b/src/locales/zh-CN.js @@ -474,7 +474,8 @@ export default { searchPlaceholder: '请输入任务名称', empty: '暂无设备点检任务数据', createTicketSuccess: '工单创建成功', - createTicketFail: '工单创建失败' + createTicketFail: '工单创建失败', + lineFilter: '产线', }, equipmentInspectionRecord: { moduleName: '设备点检记录', @@ -515,7 +516,8 @@ export default { images: '图片', maxUploadCount: '最多上传 9 张图片', selectAllDecisionError: '请为所有待检项选择结果', - noResultData: '暂无点检项数据' + noResultData: '暂无点检项数据', + lineFilter: '产线', }, moldWorkOrder: { moduleName: '点检记录', diff --git a/src/pages_function/pages/equipmentInspectionRecord/index.vue b/src/pages_function/pages/equipmentInspectionRecord/index.vue index 0f090b8..832da54 100644 --- a/src/pages_function/pages/equipmentInspectionRecord/index.vue +++ b/src/pages_function/pages/equipmentInspectionRecord/index.vue @@ -3,6 +3,10 @@ + + {{ selectedLineLabel }} + + + + + @@ -81,10 +100,21 @@ import { onLoad, onReachBottom, onShow, onUnload } from '@dcloudio/uni-app' import { useI18n } from 'vue-i18n' import NavBar from '@/components/common/NavBar.vue' import { getEquipmentInspectionRecordPage } from '@/api/mes/equipmentInspectionRecord' +import { getDeviceLineTree } from '@/api/mes/deviceLine' import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict' const { t } = useI18n() +const selectedLineId = ref('') +const lineTree = ref([]) +const lineCascaderShow = ref(false) +const lineCascaderValue = ref([]) +const lineCascaderKey = ref(0) +const lineCascaderOptions = computed(() => [ + { label: t('functionCommon.all'), value: '' }, + ...normalizeLineTreeForCascader(lineTree.value) +]) + const searchKeyword = ref('') const selectedTaskType = ref('') const selectedJobStatus = ref('') @@ -100,6 +130,11 @@ const keywordFocus = ref(false) let searchTimer = null +const selectedLineLabel = computed(() => { + if (selectedLineId.value === '') return t('equipmentInspectionRecord.lineFilter') + const found = lineOptions.value.find(item => String(item.id) === String(selectedLineId.value)) + return found?.name || t('equipmentInspectionRecord.lineFilter') +}) const taskTypeOptions = computed(() => [ { label: t('functionCommon.all'), value: '' }, { label: t('equipmentInspectionRecord.taskTypeInspect'), value: '1' }, @@ -135,19 +170,114 @@ const currentJobStatusLabel = computed(() => { }) onLoad(async () => { - activateKeywordFocus() await initAllDict() + await loadLineTree() await fetchList(true) }) + onShow(() => { activateKeywordFocus() + if (!lineTree.value.length) loadLineTree() }) onUnload(() => { clearSearchTimer() }) +async function loadLineTree() { + try { + const res = await getDeviceLineTree() + const root = res && res.data !== undefined ? res.data : res + const treeData = Array.isArray(root) ? root : (Array.isArray(root?.data) ? root.data : []) + lineTree.value = normalizeLineTree(treeData) + } catch (_) { + lineTree.value = [] + } +} +function openLineCascader() { + lineCascaderShow.value = true +} + +function onLineCascaderConfirm(values) { + const selectedValues = Array.isArray(values) ? values : [] + const nextValue = selectedValues[selectedValues.length - 1] ?? '' + selectedLineId.value = nextValue === '' ? '' : String(nextValue) + lineCascaderValue.value = nextValue === '' ? [] : selectedValues.map(item => String(item)) + fetchList(true) +} + +async function resetLineFilter() { + selectedLineId.value = '' + lineCascaderValue.value = [] + lineCascaderShow.value = false + lineCascaderKey.value += 1 +} + + +const lineOptions = computed(() => { + return flattenLineTree(lineTree.value, 1) +}) + +function flattenLineTree(nodes, level) { + const result = [] + ;(Array.isArray(nodes) ? nodes : []).forEach((node) => { + result.push({ + id: node.id, + name: node.name || node.label || String(node.id || ''), + level + }) + if (Array.isArray(node.children) && node.children.length) { + result.push(...flattenLineTree(node.children, level + 1)) + } + }) + return result +} + +function normalizeLineTreeForCascader(nodes) { + return (Array.isArray(nodes) ? nodes : []).map((node) => { + const children = normalizeLineTreeForCascader(node.children) + const item = { + label: node.name || node.label || String(node.id || ''), + value: String(node.id ?? '') + } + if (children.length) item.children = children + return item + }) +} + +function normalizeLineTree(nodes) { + return (Array.isArray(nodes) ? nodes : []).map((node) => { + const children = normalizeLineTree(node.children) + const item = { + ...node, + id: String(node.id ?? ''), + children + } + if (!children.length) delete item.children + return item + }) +} + +function normalizeListData(res) { + const root = res && res.data !== undefined ? res.data : res + if (Array.isArray(root)) return root + if (Array.isArray(res)) return res + if (root?.list && Array.isArray(root.list)) return root.list + if (root?.rows && Array.isArray(root.rows)) return root.rows + if (root?.records && Array.isArray(root.records)) return root.records + if (root?.data?.list && Array.isArray(root.data.list)) return root.data.list + if (root?.data?.rows && Array.isArray(root.data.rows)) return root.data.rows + if (root?.data?.records && Array.isArray(root.data.records)) return root.data.records + if (Array.isArray(root?.children)) return root.children + if (Array.isArray(root?.data)) return root.data + const keys = root ? Object.keys(root) : [] + for (let i = 0; i < keys.length; i++) { + if (Array.isArray(root[keys[i]])) return root[keys[i]] + } + return [] +} + onReachBottom(() => { loadMore() }) @@ -168,9 +298,10 @@ async function fetchList(reset) { const params = { pageNo: pageNo.value, pageSize: pageSize.value, - planNo: searchKeyword.value.trim() || undefined, + planNo: searchKeyword.value.trim() || undefined, planType: selectedTaskType.value || undefined, - jobStatus: selectedJobStatus.value || undefined + jobStatus: selectedJobStatus.value || undefined, + deviceLineId: selectedLineId.value || undefined } const res = await getEquipmentInspectionRecordPage(params) const page = normalizePageData(res) @@ -213,6 +344,7 @@ async function resetFilters() { searchKeyword.value = '' selectedTaskType.value = '' selectedJobStatus.value = '' + resetLineFilter() activateKeywordFocus() await nextTick() await fetchList(true) @@ -329,23 +461,45 @@ function formatDateTime(value) { .page-container { min-height: 100vh; background: #f4f5f7; } .filter-bar { display: grid; - grid-template-columns: minmax(0, 0.9fr) 136rpx 136rpx 88rpx; + grid-template-columns: minmax(0, 1fr) 136rpx 136rpx 88rpx; align-items: center; gap: 10rpx; - padding: 18rpx 20rpx 20rpx; + padding: 18rpx 4rpx 0rpx; } +.line-filter, .keyword-box, .status-box, .reset-filter-btn { - height: 64rpx; + height: 66rpx; background: #ffffff; border: 1rpx solid #d9dde5; box-sizing: border-box; display: flex; align-items: center; } + +.line-filter { + grid-column: 1 / -1; + justify-content: space-between; + padding: 0 28rpx; + border-radius: 8rpx; +} + +.line-filter-text { + font-size: 26rpx; + color: #374151; + max-width: 85%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.line-filter-text.placeholder { + color: #a8adb7; +} + .keyword-box { - padding: 0 16rpx; + padding: 0 28rpx; } .keyword-input { width: 100%; @@ -354,7 +508,7 @@ function formatDateTime(value) { } .status-box { justify-content: space-between; - padding: 0 14rpx; + padding: 0 28rpx; } .status-box-text { font-size: 24rpx; @@ -370,7 +524,7 @@ function formatDateTime(value) { color: #4b5563; } .list-scroll { height: calc(100vh - 194rpx); } -.list-wrap { padding: 0 24rpx 32rpx; } +.list-wrap { padding: 0 4rpx 32rpx; } .task-card { position: relative; margin-top: 20rpx; padding: 28rpx; background: #fff; border-radius: 22rpx; box-shadow: 0 8rpx 28rpx rgba(15, 23, 42, 0.06); } .card-header { margin-bottom: 18rpx; } .header-main { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; } diff --git a/src/pages_function/pages/equipmentInspectionTasks/index.vue b/src/pages_function/pages/equipmentInspectionTasks/index.vue index 88236ec..ba726ed 100644 --- a/src/pages_function/pages/equipmentInspectionTasks/index.vue +++ b/src/pages_function/pages/equipmentInspectionTasks/index.vue @@ -3,6 +3,10 @@ + + {{ selectedLineLabel }} + + + @@ -69,6 +86,7 @@ import { onLoad, onReachBottom, onShow, onUnload } from '@dcloudio/uni-app' import { useI18n } from 'vue-i18n' import NavBar from '@/components/common/NavBar.vue' import { getTaskManagementPage, createTaskManagementTicket } from '@/api/mes/taskManagement' +import { getDeviceLineTree } from '@/api/mes/deviceLine' import { getDeviceLedgerList } from '@/api/mes/deviceLedger' const { t } = useI18n() @@ -86,6 +104,26 @@ const showGoTop = ref(false) const ticketLoadingId = ref(null) const deviceOptions = ref([]) const keywordFocus = ref(false) +const selectedLineId = ref('') +const lineTree = ref([]) +const lineCascaderShow = ref(false) +const lineCascaderValue = ref([]) +const lineCascaderKey = ref(0) + +const lineCascaderOptions = computed(() => [ + { label: t('functionCommon.all'), value: '' }, + ...normalizeLineTreeForCascader(lineTree.value) +]) + +const selectedLineLabel = computed(() => { + if (selectedLineId.value === '') return t('equipmentInspectionTasks.lineFilter') + const found = lineOptions.value.find(item => String(item.id) === String(selectedLineId.value)) + return found?.name || t('equipmentInspectionTasks.lineFilter') +}) + +const lineOptions = computed(() => { + return flattenLineTree(lineTree.value, 1) +}) let searchTimer = null @@ -107,7 +145,7 @@ const currentTaskTypeLabel = computed(() => { onLoad(async () => { activateKeywordFocus() - await Promise.all([ensureDeviceOptionsLoaded(), fetchList(true)]) + await Promise.all([loadLineTree(), ensureDeviceOptionsLoaded(), fetchList(true)]) }) onShow(() => { @@ -157,7 +195,8 @@ async function fetchList(reset) { pageNo: pageNo.value, pageSize: pageSize.value, name: searchKeyword.value.trim() || undefined, - taskType: selectedTaskType.value || undefined + taskType: selectedTaskType.value || undefined, + deviceLineId: selectedLineId.value || undefined } const res = await getTaskManagementPage(params) const page = normalizePageData(res) @@ -172,6 +211,76 @@ async function fetchList(reset) { } } +async function loadLineTree() { + try { + const res = await getDeviceLineTree() + const root = res && res.data !== undefined ? res.data : res + const treeData = Array.isArray(root) ? root : (Array.isArray(root?.data) ? root.data : []) + lineTree.value = normalizeLineTree(treeData) + } catch (_) { + lineTree.value = [] + } +} + +function openLineCascader() { + lineCascaderShow.value = true +} + +function onLineCascaderConfirm(values) { + const selectedValues = Array.isArray(values) ? values : [] + const nextValue = selectedValues[selectedValues.length - 1] ?? '' + selectedLineId.value = nextValue === '' ? '' : String(nextValue) + lineCascaderValue.value = nextValue === '' ? [] : selectedValues.map(item => String(item)) + fetchList(true) +} + +async function resetLineFilter() { + selectedLineId.value = '' + lineCascaderValue.value = [] + lineCascaderShow.value = false + lineCascaderKey.value += 1 +} + +function flattenLineTree(nodes, level) { + const result = [] + ;(Array.isArray(nodes) ? nodes : []).forEach((node) => { + result.push({ + id: node.id, + name: node.name || node.label || String(node.id || ''), + level + }) + if (Array.isArray(node.children) && node.children.length) { + result.push(...flattenLineTree(node.children, level + 1)) + } + }) + return result +} + +function normalizeLineTreeForCascader(nodes) { + return (Array.isArray(nodes) ? nodes : []).map((node) => { + const children = normalizeLineTreeForCascader(node.children) + const item = { + label: node.name || node.label || String(node.id || ''), + value: String(node.id ?? '') + } + if (children.length) item.children = children + return item + }) +} + +function normalizeLineTree(nodes) { + return (Array.isArray(nodes) ? nodes : []).map((node) => { + const children = normalizeLineTree(node.children) + const item = { + ...node, + id: String(node.id ?? ''), + children + } + if (!children.length) delete item.children + return item + }) +} + function normalizePageData(res) { const root = res && res.data !== undefined ? res.data : res const candidateList = root?.list || root?.rows || root?.records || root?.data?.list || root?.data?.rows || root?.data?.records || [] @@ -199,6 +308,7 @@ async function resetFilters() { clearSearchTimer() searchKeyword.value = '' selectedTaskType.value = '' + resetLineFilter() activateKeywordFocus() await nextTick() await fetchList(true) @@ -320,8 +430,9 @@ function formatDate(value) { grid-template-columns: minmax(0, 1fr) 150rpx 96rpx; align-items: center; gap: 14rpx; - padding: 18rpx 28rpx 20rpx; + padding: 18rpx 4rpx 0rpx; } +.line-filter, .keyword-box, .status-box, .reset-filter-btn { @@ -332,6 +443,26 @@ function formatDate(value) { display: flex; align-items: center; } +.line-filter { + grid-column: 1 / -1; + justify-content: space-between; + padding: 0 28rpx; + border-radius: 8rpx; +} + +.line-filter-text { + font-size: 26rpx; + color: #374151; + max-width: 85%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.line-filter-text.placeholder { + color: #a8adb7; +} + .keyword-box { padding: 0 20rpx; } @@ -354,7 +485,7 @@ function formatDate(value) { color: #4b5563; } .list-scroll { height: calc(100vh - 194rpx); } -.list-wrap { padding: 0 24rpx 32rpx; } +.list-wrap { padding: 0 4rpx 32rpx; } .task-card { position: relative; margin-top: 20rpx; padding: 28rpx; background: #fff; border-radius: 22rpx; box-shadow: 0 8rpx 28rpx rgba(15, 23, 42, 0.06); } .card-header { margin-bottom: 18rpx; } .header-main { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; }