|
|
@@ -1,501 +1,379 @@
|
|
|
<template>
|
|
|
- <Dialog v-model="dialogVisible" :title="t('iotMaintain.selectMaterials')"
|
|
|
- style="width: 1300px; min-height: 300px" :close-on-click-modal="false" @close="handleClose">
|
|
|
-
|
|
|
- <!-- 设备分类和BOM节点信息显示 -->
|
|
|
- <div style="margin: 0 0px 15px; background: #f5f7fa; padding: 12px 20px; border-radius: 4px;">
|
|
|
- <el-row>
|
|
|
- <el-col :span="12">
|
|
|
- <span style="font-weight: bold; margin-right: 10px;">设备分类:</span>
|
|
|
- <span>{{ deviceCategoryName }}</span>
|
|
|
- </el-col>
|
|
|
- <el-col :span="12">
|
|
|
- <span style="font-weight: bold; margin-right: 10px;">BOM节点:</span>
|
|
|
- <span>{{ bomNodeName }}</span>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
-
|
|
|
- <ContentWrap>
|
|
|
- <el-form
|
|
|
- class="-mb-15px"
|
|
|
- :model="queryParams"
|
|
|
- ref="queryFormRef"
|
|
|
- :inline="true"
|
|
|
- label-width="68px"
|
|
|
- >
|
|
|
- <el-form-item :label="t('workOrderMaterial.materialName')" prop="name" style="margin-left: 28px">
|
|
|
- <el-input
|
|
|
- v-model="queryParams.name"
|
|
|
- :placeholder="t('workOrderMaterial.nameHolder')"
|
|
|
- clearable
|
|
|
- @keyup.enter="handleQuery"
|
|
|
- class="!w-200px"
|
|
|
- />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item :label="t('workOrderMaterial.materialCode')" prop="code">
|
|
|
- <el-input
|
|
|
- v-model="queryParams.code"
|
|
|
- :placeholder="t('workOrderMaterial.codeHolder')"
|
|
|
- clearable
|
|
|
- @keyup.enter="handleQuery"
|
|
|
- class="!w-200px"
|
|
|
- />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />
|
|
|
- {{ t('workOrderMaterial.search') }}</el-button>
|
|
|
- <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />
|
|
|
- {{ t('workOrderMaterial.reset') }}</el-button>
|
|
|
- <el-button @click="handleConfirm" class="custom-green-button"><Icon icon="ep:check" class="mr-5px" /> {{ t('workOrderMaterial.confirm') }}</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- </ContentWrap>
|
|
|
-
|
|
|
- <!-- 已选物料标签区域 -->
|
|
|
- <ContentWrap v-if="selectedRows.length > 0"
|
|
|
- style="margin: -10px 0px 10px; padding: 10px 15px; background: #f8fafc; border-radius: 4px; border: 1px solid #ebeef5;">
|
|
|
- <div style="display: flex; align-items: center; flex-wrap: wrap; gap: 8px;">
|
|
|
- <div style="font-weight: bold; color: #606266; margin-right: 10px;">已选物料:</div>
|
|
|
- <el-tag
|
|
|
- v-for="item in selectedRows"
|
|
|
- :key="item.id"
|
|
|
- @close="removeTag(item)"
|
|
|
- style="margin-bottom: 5px; position: relative; padding-right: 25px;"
|
|
|
- >
|
|
|
- {{ item.name }}-{{ item.code }}
|
|
|
- <!-- 自定义关闭按钮 -->
|
|
|
- <span class="close-icon" @click.stop="removeTag(item)">
|
|
|
- <Icon icon="ep:close" style="font-size: 12px; position: absolute; right: 8px; top: 50%; transform: translateY(-50%);"/>
|
|
|
- </span>
|
|
|
- </el-tag>
|
|
|
- </div>
|
|
|
- </ContentWrap>
|
|
|
-
|
|
|
- <ContentWrap class="table-content-wrap">
|
|
|
- <div class="flex-table-container">
|
|
|
- <el-table
|
|
|
+ <Dialog
|
|
|
+ v-model="dialogVisible"
|
|
|
+ :title="t('iotMaintain.selectMaterials')"
|
|
|
+ width="1200px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ @close="handleClose">
|
|
|
+ <div class="material-picker">
|
|
|
+ <section class="context-panel">
|
|
|
+ <div class="context-item">
|
|
|
+ <span class="context-label">设备分类</span>
|
|
|
+ <span class="context-value">{{ deviceCategoryName || '暂无' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="context-item">
|
|
|
+ <span class="context-label">BOM节点</span>
|
|
|
+ <span class="context-value">{{ bomNodeName || '暂无' }}</span>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="filter-panel">
|
|
|
+ <el-form
|
|
|
+ ref="queryFormRef"
|
|
|
+ :model="queryParams"
|
|
|
+ :inline="true"
|
|
|
+ label-width="78px"
|
|
|
+ size="default"
|
|
|
+ class="filter-form">
|
|
|
+ <el-form-item :label="t('workOrderMaterial.materialName')" prop="name">
|
|
|
+ <el-input
|
|
|
+ v-model="queryParams.name"
|
|
|
+ :placeholder="t('workOrderMaterial.nameHolder')"
|
|
|
+ clearable
|
|
|
+ class="filter-input"
|
|
|
+ @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item :label="t('workOrderMaterial.materialCode')" prop="code">
|
|
|
+ <el-input
|
|
|
+ v-model="queryParams.code"
|
|
|
+ :placeholder="t('workOrderMaterial.codeHolder')"
|
|
|
+ clearable
|
|
|
+ class="filter-input"
|
|
|
+ @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item class="filter-actions">
|
|
|
+ <el-button type="primary" size="default" @click="handleQuery">
|
|
|
+ <Icon icon="ep:search" class="mr-5px" />
|
|
|
+ {{ t('workOrderMaterial.search') }}
|
|
|
+ </el-button>
|
|
|
+ <el-button size="default" @click="resetQuery">
|
|
|
+ <Icon icon="ep:refresh" class="mr-5px" />
|
|
|
+ {{ t('workOrderMaterial.reset') }}
|
|
|
+ </el-button>
|
|
|
+ <el-button type="success" size="default" @click="handleConfirm">
|
|
|
+ <Icon icon="ep:check" class="mr-5px" />
|
|
|
+ {{ t('workOrderMaterial.confirm') }}
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="table-panel">
|
|
|
+ <ZmTable
|
|
|
ref="tableRef"
|
|
|
- v-loading="loading"
|
|
|
- :data="list"
|
|
|
- :stripe="true"
|
|
|
- :row-key="rowKey"
|
|
|
- :show-overflow-tooltip="true"
|
|
|
- table-layout="auto"
|
|
|
- @row-click="handleRowClick"
|
|
|
- class="full-width-table" >
|
|
|
- <el-table-column :label="t('workOrderMaterial.select')" class-name="select-column" min-width="30">
|
|
|
+ :data="list"
|
|
|
+ :loading="loading"
|
|
|
+ :show-border="true"
|
|
|
+ class="material-table"
|
|
|
+ settings-cache-key="pms-bom-material-select"
|
|
|
+ row-key="id"
|
|
|
+ @row-click="handleRowClick"
|
|
|
+ @selection-change="handleSelectionChange">
|
|
|
+ <ZmTableColumn
|
|
|
+ type="selection"
|
|
|
+ width="56"
|
|
|
+ :reserve-selection="true"
|
|
|
+ hide-in-column-settings />
|
|
|
+ <ZmTableColumn :label="t('workOrderMaterial.materialName')" prop="name" min-width="220" />
|
|
|
+ <ZmTableColumn :label="t('workOrderMaterial.materialCode')" prop="code" min-width="160" />
|
|
|
+ <ZmTableColumn :label="t('workOrderMaterial.unit')" prop="unit" width="110" />
|
|
|
+ <ZmTableColumn :label="t('route.quantity')" prop="quantity" width="150">
|
|
|
<template #default="{ row }">
|
|
|
- <el-checkbox
|
|
|
- :model-value="selectedRows.some(item => item.id === row.id)"
|
|
|
- @click.stop="selectRow(row)"
|
|
|
- class="no-label-radio"
|
|
|
- />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column :label="t('workOrderMaterial.materialName')" align="center" prop="name"
|
|
|
- class-name="name-column" min-width="180"/>
|
|
|
- <el-table-column :label="t('workOrderMaterial.materialCode')" align="center" prop="code" min-width="130"/>
|
|
|
- <el-table-column :label="t('workOrderMaterial.unit')" align="center" prop="unit" min-width="50"/>
|
|
|
- <el-table-column :label="t('route.quantity')" align="center" prop="quantity" min-width="80" class-name="quantity-column">
|
|
|
- <template #default="scope">
|
|
|
- <el-input
|
|
|
- type="number"
|
|
|
+ <el-input-number
|
|
|
+ v-model="row.quantity"
|
|
|
:controls="false"
|
|
|
- v-model="scope.row.quantity"
|
|
|
- @click.stop=""
|
|
|
- @focus="handleInputFocus(scope.row)"
|
|
|
- @blur="(event) => handleQuantityBlur(event, scope.row)"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ size="default"
|
|
|
class="quantity-input"
|
|
|
- />
|
|
|
+ @click.stop
|
|
|
+ @focus="selectIfNeeded(row)"
|
|
|
+ @change="() => syncSelectedQuantity(row)" />
|
|
|
</template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
+ </ZmTableColumn>
|
|
|
+ <ZmTableColumn
|
|
|
:label="t('deviceList.createTime')"
|
|
|
- align="center"
|
|
|
prop="createTime"
|
|
|
- min-width="100"
|
|
|
- :formatter="dateFormatter"
|
|
|
- />
|
|
|
- </el-table>
|
|
|
- </div>
|
|
|
- <!-- 分页 -->
|
|
|
- <Pagination
|
|
|
- :total="total"
|
|
|
- v-model:page="queryParams.pageNo"
|
|
|
- v-model:limit="queryParams.pageSize"
|
|
|
- @pagination="getList"
|
|
|
- />
|
|
|
- </ContentWrap>
|
|
|
-
|
|
|
+ min-width="170"
|
|
|
+ :formatter="dateFormatter" />
|
|
|
+ </ZmTable>
|
|
|
+
|
|
|
+ <div class="pagination-row">
|
|
|
+ <Pagination
|
|
|
+ :total="total"
|
|
|
+ v-model:page="queryParams.pageNo"
|
|
|
+ v-model:limit="queryParams.pageSize"
|
|
|
+ @pagination="getList" />
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
</Dialog>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { DictDataVO } from '@/api/system/dict/dict.data'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
|
|
|
import { dateFormatter } from '@/utils/formatTime'
|
|
|
import * as MaterialApi from '@/api/pms/material'
|
|
|
-import {defaultProps, handleTree} from "@/utils/tree";
|
|
|
-import * as MaterialGroupApi from '@/api/pms/materialgroup'
|
|
|
-import { shallowRef, nextTick } from 'vue'
|
|
|
|
|
|
-defineOptions({
|
|
|
- name: 'MaterialList'
|
|
|
-})
|
|
|
-const { t } = useI18n() // 国际化
|
|
|
-const materialGroupList = ref<Tree[]>([]) // 物料组树形结构
|
|
|
+defineOptions({ name: 'MaterialList' })
|
|
|
|
|
|
+type MaterialRow = Omit<MaterialApi.MaterialVO, 'quantity'> & {
|
|
|
+ quantity?: number
|
|
|
+}
|
|
|
+
|
|
|
+const { t } = useI18n()
|
|
|
+const { ZmTable, ZmTableColumn } = useTableComponents<MaterialRow>()
|
|
|
const emit = defineEmits<{
|
|
|
(e: 'choose', value: MaterialApi.MaterialVO[]): void
|
|
|
(e: 'close'): void
|
|
|
}>()
|
|
|
|
|
|
-const dialogVisible = ref(false) // 弹窗的是否展示
|
|
|
-const loading = ref(true) // 列表的加载中
|
|
|
-const queryFormRef = ref() // 搜索的表单
|
|
|
-const list = ref<MaterialApi.MaterialVO[]>([]) // 列表的数据
|
|
|
-const total = ref(0) // 列表的总页数
|
|
|
-const selectedRows = ref<MaterialApi.MaterialVO[]>([]); // 多选数据(存储所有选中行的数组)
|
|
|
-const tableRef = ref<InstanceType<typeof ElTable>>();
|
|
|
-
|
|
|
-const deviceCategoryName = ref('') // 存储设备分类名称
|
|
|
-const bomNodeName = ref('') // 存储BOM节点名称
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const loading = ref(false)
|
|
|
+const queryFormRef = ref()
|
|
|
+const tableRef = ref()
|
|
|
+const list = ref<MaterialRow[]>([])
|
|
|
+const total = ref(0)
|
|
|
+const selectedRows = ref<MaterialRow[]>([])
|
|
|
+const syncingSelection = ref(false)
|
|
|
+const deviceCategoryName = ref('')
|
|
|
+const bomNodeName = ref('')
|
|
|
|
|
|
const queryParams = reactive({
|
|
|
pageNo: 1,
|
|
|
pageSize: 10,
|
|
|
- materialGroupId: '',
|
|
|
code: '',
|
|
|
- name: '',
|
|
|
- model: '',
|
|
|
- unit: ''
|
|
|
+ name: ''
|
|
|
})
|
|
|
|
|
|
-const selectedRow = ref(null);
|
|
|
+const selectedIdSet = computed(() => new Set(selectedRows.value.map((item) => item.id)))
|
|
|
|
|
|
-// 更新选中物料数量的方法
|
|
|
-const updateSelectedRowQuantity = (row: MaterialApi.MaterialVO) => {
|
|
|
- const selectedItem = selectedRows.value.find(item => item.id === row.id);
|
|
|
- if (selectedItem) {
|
|
|
- selectedItem.quantity = row.quantity;
|
|
|
- }
|
|
|
-};
|
|
|
+const getSelectedRow = (row: MaterialRow) => {
|
|
|
+ return selectedRows.value.find((item) => item.id === row.id)
|
|
|
+}
|
|
|
|
|
|
-const adjustColumnWidths = () => {
|
|
|
- nextTick(() => {
|
|
|
- if (tableRef.value) {
|
|
|
- tableRef.value.doLayout();
|
|
|
- }
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-// 处理单选逻辑
|
|
|
-const selectRow = (row) => {
|
|
|
- const index = selectedRows.value.findIndex(item => item.id === row.id);
|
|
|
- if (index > -1) {
|
|
|
- selectedRows.value.splice(index, 1); // 取消选中
|
|
|
- row.quantity = undefined;
|
|
|
- } else {
|
|
|
- selectedRows.value.push({ ...row }); // 选中
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-// 点击整行选中
|
|
|
-const handleRowClick = (row) => {
|
|
|
- selectRow(row);
|
|
|
-};
|
|
|
-const open = async (row: any) => {
|
|
|
- // 设置传递过来的设备分类名称和BOM节点名称
|
|
|
- deviceCategoryName.value = row.deviceCategoryName || ''
|
|
|
- bomNodeName.value = row.name || ''
|
|
|
+const normalizeQuantity = (quantity?: number) => {
|
|
|
+ const value = Number(quantity)
|
|
|
+ if (!Number.isFinite(value) || value <= 0) return undefined
|
|
|
+ return Number(value.toFixed(2))
|
|
|
+}
|
|
|
|
|
|
- queryParams.name = '';
|
|
|
- queryParams.code = '';
|
|
|
- dialogVisible.value = true
|
|
|
+const applySelectedQuantities = () => {
|
|
|
+ list.value.forEach((row) => {
|
|
|
+ const selected = getSelectedRow(row)
|
|
|
+ if (selected) {
|
|
|
+ row.quantity = selected.quantity
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
- await getList();
|
|
|
+const syncCurrentPageSelection = async () => {
|
|
|
+ await nextTick()
|
|
|
+ const elTableRef = tableRef.value?.elTableRef
|
|
|
+ if (!elTableRef) return
|
|
|
+
|
|
|
+ syncingSelection.value = true
|
|
|
+ list.value.forEach((row) => {
|
|
|
+ elTableRef.toggleRowSelection(row, selectedIdSet.value.has(row.id))
|
|
|
+ })
|
|
|
+ await nextTick()
|
|
|
+ syncingSelection.value = false
|
|
|
}
|
|
|
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
|
|
+
|
|
|
const getList = async () => {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
const data = await MaterialApi.getMaterialPage(queryParams)
|
|
|
- list.value = data.list
|
|
|
- total.value = data.total
|
|
|
-
|
|
|
- // 同步selectedRows中的数量到当前页数据
|
|
|
- list.value.forEach(row => {
|
|
|
- const selectedItem = selectedRows.value.find(item => item.id === row.id);
|
|
|
- if (selectedItem && selectedItem.quantity !== undefined) {
|
|
|
- row.quantity = selectedItem.quantity;
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // 数据加载后自动调整列宽
|
|
|
- adjustColumnWidths();
|
|
|
+ list.value = data.list || []
|
|
|
+ total.value = data.total || 0
|
|
|
+ applySelectedQuantities()
|
|
|
+ await syncCurrentPageSelection()
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 关闭时清空选择
|
|
|
-const handleClose = () => {
|
|
|
- tableRef.value?.clearSelection();
|
|
|
+const open = async (row: { deviceCategoryName?: string; name?: string }) => {
|
|
|
+ deviceCategoryName.value = row.deviceCategoryName || ''
|
|
|
+ bomNodeName.value = row.name || ''
|
|
|
+ queryParams.pageNo = 1
|
|
|
+ queryParams.name = ''
|
|
|
+ queryParams.code = ''
|
|
|
selectedRows.value = []
|
|
|
- emit('close')
|
|
|
-};
|
|
|
-
|
|
|
-// 移除标签方法
|
|
|
-const removeTag = (material: MaterialApi.MaterialVO) => {
|
|
|
- // 从已选列表中移除
|
|
|
- const index = selectedRows.value.findIndex(item => item.id === material.id);
|
|
|
- if (index !== -1) {
|
|
|
- selectedRows.value.splice(index, 1);
|
|
|
- }
|
|
|
+ tableRef.value?.elTableRef?.clearSelection()
|
|
|
+ dialogVisible.value = true
|
|
|
+ await getList()
|
|
|
+}
|
|
|
|
|
|
- // 如果当前行在当前页,取消其选中状态
|
|
|
- const rowInTable = list.value.find(item => item.id === material.id);
|
|
|
- if (rowInTable) {
|
|
|
- const rowIndex = list.value.indexOf(rowInTable);
|
|
|
- // 这里可以添加逻辑强制更新行状态(如果需要)
|
|
|
- rowInTable.quantity = undefined;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-// 处理 消耗数量 quantity 输入框失焦事件
|
|
|
-const handleQuantityBlur = (event: Event, row: any) => {
|
|
|
- const inputValue = (event.target as HTMLInputElement).value;
|
|
|
- let num = parseFloat(inputValue);
|
|
|
- // 处理无效值
|
|
|
- if (isNaN(num)) {
|
|
|
- row.quantity = 0;
|
|
|
- return;
|
|
|
- }
|
|
|
- // 处理小于等于0的情况
|
|
|
- if (num <= 0) {
|
|
|
- row.quantity = 0;
|
|
|
- return;
|
|
|
- }
|
|
|
- // 保留两位小数
|
|
|
- row.quantity = parseFloat(num.toFixed(2));
|
|
|
- // 实时更新selectedRows
|
|
|
- updateSelectedRowQuantity(row);
|
|
|
-};
|
|
|
-
|
|
|
-// 处理输入框焦点事件(自动选中当前行)
|
|
|
-const handleInputFocus = (row: MaterialApi.MaterialVO) => {
|
|
|
- const getRowUniqueKey = (row) => {
|
|
|
- return `${row.code}_${row.name}`
|
|
|
- }
|
|
|
- const currentKey = getRowUniqueKey(row)
|
|
|
- // 如果未选中则添加到选中列表
|
|
|
- const exists = selectedRows.value.some(item => getRowUniqueKey(item) === currentKey)
|
|
|
- if (!exists) {
|
|
|
- selectedRows.value.push(row)
|
|
|
+defineExpose({ open })
|
|
|
+
|
|
|
+const selectIfNeeded = (row: MaterialRow) => {
|
|
|
+ if (!selectedIdSet.value.has(row.id)) {
|
|
|
+ tableRef.value?.elTableRef?.toggleRowSelection(row, true)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 计算列宽
|
|
|
-const flexColumnWidth = (prop: string, label: string, data: any[]) => {
|
|
|
- if (!data || !data.length) return 'auto';
|
|
|
-
|
|
|
- const contentWidths = data.map(item => {
|
|
|
- const value = prop && item[prop] ? String(item[prop]) : '';
|
|
|
- return getTextWidth(value);
|
|
|
- });
|
|
|
-
|
|
|
- const labelWidth = getTextWidth(label);
|
|
|
- const maxContentWidth = Math.max(...contentWidths);
|
|
|
- const maxWidth = Math.max(labelWidth, maxContentWidth);
|
|
|
-
|
|
|
- return Math.min(Math.max(maxWidth + 10, 120), 600) + 'px'; // 10px内边距,120px最小宽度
|
|
|
-};
|
|
|
-
|
|
|
-// 获取文本宽度
|
|
|
-const getTextWidth = (text: string) => {
|
|
|
- if (!text) return 0;
|
|
|
-
|
|
|
- const span = document.createElement('span');
|
|
|
- span.style.visibility = 'hidden';
|
|
|
- span.style.position = 'absolute';
|
|
|
- span.style.whiteSpace = 'nowrap';
|
|
|
- span.style.font = '14px Microsoft YaHei'; // 匹配表格字体
|
|
|
- span.textContent = text;
|
|
|
- document.body.appendChild(span);
|
|
|
-
|
|
|
- const width = span.offsetWidth;
|
|
|
- document.body.removeChild(span);
|
|
|
- return width;
|
|
|
-};
|
|
|
-
|
|
|
-// 生成行唯一标识
|
|
|
-const rowKey = (row: any) => {
|
|
|
- return `${row.id}`; // 确保行更新时重新渲染
|
|
|
-};
|
|
|
-
|
|
|
-// 确认选择
|
|
|
-const handleConfirm = () => {
|
|
|
- // 同步当前页所有选中行的最新数量
|
|
|
- list.value.forEach(row => {
|
|
|
- if (selectedRows.value.some(item => item.id === row.id)) {
|
|
|
- updateSelectedRowQuantity(row);
|
|
|
+const syncSelectedQuantity = (row: MaterialRow) => {
|
|
|
+ const selected = getSelectedRow(row)
|
|
|
+ if (!selected) return
|
|
|
+ const quantity = normalizeQuantity(row.quantity)
|
|
|
+ selected.quantity = quantity
|
|
|
+ row.quantity = quantity
|
|
|
+}
|
|
|
+
|
|
|
+const handleSelectionChange = (selection: MaterialRow[]) => {
|
|
|
+ if (syncingSelection.value) return
|
|
|
+
|
|
|
+ const currentPageIds = new Set(list.value.map((row) => row.id))
|
|
|
+ const currentSelectionIds = new Set(selection.map((row) => row.id))
|
|
|
+ const previousOtherPageRows = selectedRows.value.filter((row) => !currentPageIds.has(row.id))
|
|
|
+ const currentPageRows = selection.map((row) => {
|
|
|
+ const quantity = normalizeQuantity(row.quantity) || 1
|
|
|
+ row.quantity = quantity
|
|
|
+ return { ...row, quantity }
|
|
|
+ })
|
|
|
+
|
|
|
+ selectedRows.value = [...previousOtherPageRows, ...currentPageRows]
|
|
|
+ list.value.forEach((row) => {
|
|
|
+ if (!currentSelectionIds.has(row.id)) {
|
|
|
+ row.quantity = undefined
|
|
|
}
|
|
|
- });
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const handleRowClick = (row: MaterialRow) => {
|
|
|
+ tableRef.value?.elTableRef?.toggleRowSelection(row)
|
|
|
+}
|
|
|
|
|
|
- if (selectedRows.value.length === 0) {
|
|
|
- ElMessage.warning('请至少选择一个物料')
|
|
|
- return
|
|
|
- }
|
|
|
- emit('choose', selectedRows.value.map(row => ({
|
|
|
- ...row,
|
|
|
- // 确保返回必要字段
|
|
|
- code: row.code,
|
|
|
- name: row.name,
|
|
|
- unit: row.unit,
|
|
|
- quantity: row.quantity,
|
|
|
- })))
|
|
|
- dialogVisible.value = false;
|
|
|
- handleClose()
|
|
|
-};
|
|
|
-
|
|
|
-/** 搜索按钮操作 */
|
|
|
const handleQuery = () => {
|
|
|
queryParams.pageNo = 1
|
|
|
getList()
|
|
|
}
|
|
|
-const choose = (row: DictDataVO) => {
|
|
|
- emit('choose', row)
|
|
|
- dialogVisible.value = false
|
|
|
-}
|
|
|
-/** 重置按钮操作 */
|
|
|
+
|
|
|
const resetQuery = () => {
|
|
|
- queryFormRef.value.resetFields()
|
|
|
+ queryFormRef.value?.resetFields()
|
|
|
handleQuery()
|
|
|
}
|
|
|
-/** 初始化 **/
|
|
|
-onMounted(async () => {
|
|
|
- materialGroupList.value = handleTree(await MaterialGroupApi.getSimpleMaterialGroupList())
|
|
|
- await getList()
|
|
|
-})
|
|
|
|
|
|
-// 清除事件监听
|
|
|
-onUnmounted(() => {
|
|
|
- window.removeEventListener('resize', adjustColumnWidths);
|
|
|
-});
|
|
|
+const handleConfirm = () => {
|
|
|
+ if (!selectedRows.value.length) {
|
|
|
+ ElMessage.warning('请至少选择一个物料')
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
-</script>
|
|
|
-<style lang="scss" scoped>
|
|
|
-.no-label-radio .el-radio__label {
|
|
|
- display: none;
|
|
|
-}
|
|
|
-.no-label-radio .el-radio__inner {
|
|
|
- margin-right: 0;
|
|
|
+ const invalidMaterial = selectedRows.value.find((item) => !normalizeQuantity(item.quantity))
|
|
|
+ if (invalidMaterial) {
|
|
|
+ ElMessage.warning('请填写大于0的物料数量')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ emit(
|
|
|
+ 'choose',
|
|
|
+ selectedRows.value.map((row) => ({
|
|
|
+ ...row,
|
|
|
+ quantity: normalizeQuantity(row.quantity)
|
|
|
+ })) as MaterialApi.MaterialVO[]
|
|
|
+ )
|
|
|
+ dialogVisible.value = false
|
|
|
}
|
|
|
|
|
|
-.table-content-wrap {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- flex: 1;
|
|
|
- min-height: 300px; // 确保有最小高度
|
|
|
+const handleClose = () => {
|
|
|
+ selectedRows.value = []
|
|
|
+ list.value = []
|
|
|
+ emit('close')
|
|
|
}
|
|
|
+</script>
|
|
|
|
|
|
-.flex-table-container {
|
|
|
- flex: 1;
|
|
|
+<style lang="scss" scoped>
|
|
|
+.material-picker {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- overflow: hidden;
|
|
|
-
|
|
|
- // 确保表格填满容器
|
|
|
- .full-width-table {
|
|
|
- width: 100%;
|
|
|
- flex: 1;
|
|
|
-
|
|
|
- // 让表格头与内容同步宽度
|
|
|
- :deep(.el-table__header) {
|
|
|
- width: 100% !important;
|
|
|
- }
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
|
|
|
- :deep(.el-table__body) {
|
|
|
- width: 100% !important;
|
|
|
- }
|
|
|
- }
|
|
|
+.context-panel,
|
|
|
+.filter-panel,
|
|
|
+.table-panel {
|
|
|
+ border: 1px solid var(--el-border-color-lighter);
|
|
|
+ border-radius: 6px;
|
|
|
}
|
|
|
|
|
|
-// 名称列:优先分配剩余空间
|
|
|
-:deep(.name-column) {
|
|
|
- flex-grow: 1 !important;
|
|
|
- min-width: 180px;
|
|
|
+.context-panel {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ gap: 16px;
|
|
|
+ padding: 14px 16px;
|
|
|
+ background: var(--el-fill-color-lighter);
|
|
|
+}
|
|
|
|
|
|
- .cell {
|
|
|
- white-space: nowrap;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- max-width: 100%;
|
|
|
- }
|
|
|
+.context-item {
|
|
|
+ min-width: 0;
|
|
|
}
|
|
|
|
|
|
-/* 表格容器样式 */
|
|
|
-.table-container {
|
|
|
- width: 100%;
|
|
|
- overflow-x: auto;
|
|
|
+.context-label {
|
|
|
+ margin-right: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
}
|
|
|
|
|
|
-/* 自定义淡绿色按钮 */
|
|
|
-:deep(.custom-green-button) {
|
|
|
- background-color: #e1f3d8;
|
|
|
- border-color: #e1f3d8;
|
|
|
- color: #67c23a;
|
|
|
+.context-value {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
}
|
|
|
|
|
|
-/* 悬停效果 */
|
|
|
-:deep(.custom-green-button:hover) {
|
|
|
- background-color: #d1e8c0;
|
|
|
- border-color: #d1e8c0;
|
|
|
- color: #5daf34;
|
|
|
+.filter-panel {
|
|
|
+ padding: 16px 16px 0;
|
|
|
+ background: var(--el-bg-color);
|
|
|
}
|
|
|
|
|
|
-/* 点击效果 */
|
|
|
-:deep(.custom-green-button:active) {
|
|
|
- background-color: #c2dca8;
|
|
|
- border-color: #c2dca8;
|
|
|
+.filter-form {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 0 12px;
|
|
|
}
|
|
|
|
|
|
-// 标签样式
|
|
|
-.close-icon {
|
|
|
- cursor: pointer;
|
|
|
- color: #999;
|
|
|
- transition: color 0.2s;
|
|
|
+.filter-input {
|
|
|
+ width: 200px;
|
|
|
+}
|
|
|
|
|
|
- &:hover {
|
|
|
- color: #ff4d4f;
|
|
|
- }
|
|
|
+.filter-actions {
|
|
|
+ margin-left: auto;
|
|
|
}
|
|
|
|
|
|
-.el-tag {
|
|
|
- position: relative;
|
|
|
- padding-right: 25px;
|
|
|
- transition: all 0.3s;
|
|
|
+.table-panel {
|
|
|
+ padding: 16px 16px 0;
|
|
|
+ background: var(--el-bg-color);
|
|
|
+}
|
|
|
|
|
|
- &:hover {
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
+.material-table {
|
|
|
+ width: 100%;
|
|
|
}
|
|
|
|
|
|
-/* 调整输入框宽度 */
|
|
|
-:deep(.el-input) {
|
|
|
- width: 95% !important; /* 保留5%空间避免溢出 */
|
|
|
+.quantity-input {
|
|
|
+ width: 96px;
|
|
|
}
|
|
|
|
|
|
-/* 确保输入框获得焦点时保持可见 */
|
|
|
-:deep(.el-input__inner:focus) {
|
|
|
- border-color: #409eff !important;
|
|
|
- box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2) !important;
|
|
|
+.quantity-input :deep(.el-input__inner) {
|
|
|
+ text-align: center;
|
|
|
}
|
|
|
|
|
|
-/* 输入框获得焦点时提升层级 */
|
|
|
-.quantity-input:deep(.el-input__inner) {
|
|
|
- position: relative;
|
|
|
- z-index: 2;
|
|
|
+.pagination-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding: 12px 0 0;
|
|
|
}
|
|
|
|
|
|
+@media (width <= 768px) {
|
|
|
+ .context-panel {
|
|
|
+ grid-template-columns: minmax(0, 1fr);
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-actions {
|
|
|
+ width: 100%;
|
|
|
+ margin-left: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|