| | |
| | | |
| | | <template> |
| | | <view-grid |
| | | ref="grid" |
| | | :columns="columns" |
| | | :detail="detail" |
| | | :editFormFields="editFormFields" |
| | | :editFormOptions="editFormOptions" |
| | | :searchFormFields="searchFormFields" |
| | | :searchFormOptions="searchFormOptions" |
| | | :table="table" |
| | | :extend="extend" |
| | | > |
| | | </view-grid> |
| | | </template> |
| | | <script> |
| | | import extend from "@/extension/stock/stockInfo.js"; |
| | | import { ref, defineComponent } from "vue"; |
| | | export default defineComponent({ |
| | | setup() { |
| | | const table = ref({ |
| | | key: "id", |
| | | footer: "Foots", |
| | | cnName: "库存信息", |
| | | name: "stockInfo", |
| | | url: "/StockInfo/", |
| | | sortName: "id", |
| | | }); |
| | | const editFormFields = ref({ |
| | | deviceCode: "", |
| | | deviceName: "", |
| | | deviceType: "", |
| | | deviceStatus: "", |
| | | deviceIp: "", |
| | | devicePort: "", |
| | | devicePlcType: "", |
| | | deviceRemark: "", |
| | | }); |
| | | const editFormOptions = ref([ |
| | | [ |
| | | {field:'palletCode',title:'托盘编号',type:'string'}, |
| | | {field:'locationCode',title:'货位编号',type:'string'}, |
| | | ] |
| | | <view-grid |
| | | ref="grid" |
| | | :columns="columns" |
| | | :detail="detail" |
| | | :editFormFields="editFormFields" |
| | | :editFormOptions="editFormOptions" |
| | | :searchFormFields="searchFormFields" |
| | | :searchFormOptions="searchFormOptions" |
| | | :table="table" |
| | | :tableExpand="tableExpand" |
| | | :extend="extend" |
| | | > |
| | | </view-grid> |
| | | </template> |
| | | |
| | | <script> |
| | | import extend from "@/extension/stock/stockInfo.js"; |
| | | import { |
| | | defineComponent, |
| | | getCurrentInstance, |
| | | h, |
| | | reactive, |
| | | ref, |
| | | resolveComponent, |
| | | } from "vue"; |
| | | |
| | | const TEXT = { |
| | | pageName: "库存信息", |
| | | palletCode: "托盘编号", |
| | | locationCode: "货位编号", |
| | | warehouse: "仓库", |
| | | creator: "创建人", |
| | | createDate: "创建时间", |
| | | modifier: "修改人", |
| | | modifyDate: "修改时间", |
| | | detailName: "库存明细", |
| | | materielName: "物料名称", |
| | | serialNumber: "电芯码", |
| | | stockQuantity: "库存数量", |
| | | status: "状态", |
| | | inboundOrderRowNo: "通道号", |
| | | detailLoading: "库存明细加载中...", |
| | | detailLoadFailed: "库存明细加载失败", |
| | | detailEmpty: "当前库存头暂无明细数据", |
| | | expandPrefix: "托盘:", |
| | | expandMiddle: " / ", |
| | | expandLocation: "货位:", |
| | | }; |
| | | |
| | | export default defineComponent({ |
| | | setup() { |
| | | const { proxy } = getCurrentInstance(); |
| | | const ElTable = resolveComponent("el-table"); |
| | | const ElTableColumn = resolveComponent("el-table-column"); |
| | | |
| | | const table = ref({ |
| | | key: "id", |
| | | footer: "Foots", |
| | | cnName: TEXT.pageName, |
| | | name: "stockInfo", |
| | | url: "/StockInfo/", |
| | | sortName: "id", |
| | | }); |
| | | |
| | | const editFormFields = ref({ |
| | | palletCode: "", |
| | | locationCode: "", |
| | | }); |
| | | |
| | | const editFormOptions = ref([ |
| | | [ |
| | | { field: "palletCode", title: TEXT.palletCode, type: "string" }, |
| | | { field: "locationCode", title: TEXT.locationCode, type: "string" }, |
| | | ], |
| | | ]); |
| | | |
| | | const searchFormFields = ref({ |
| | | palletCode: "", |
| | | locationCode: "", |
| | | }); |
| | | |
| | | const searchFormOptions = ref([ |
| | | [ |
| | | { title: TEXT.palletCode, field: "palletCode", type: "like" }, |
| | | { title: TEXT.locationCode, field: "locationCode", type: "like" }, |
| | | ], |
| | | ]); |
| | | |
| | | const columns = ref([ |
| | | { |
| | | field: "id", |
| | | title: "Id", |
| | | type: "int", |
| | | width: 90, |
| | | hidden: true, |
| | | readonly: true, |
| | | require: true, |
| | | align: "left", |
| | | }, |
| | | { |
| | | field: "palletCode", |
| | | title: TEXT.palletCode, |
| | | type: "string", |
| | | width: 120, |
| | | align: "left", |
| | | }, |
| | | { |
| | | field: "locationCode", |
| | | title: TEXT.locationCode, |
| | | type: "string", |
| | | width: 150, |
| | | align: "left", |
| | | }, |
| | | { |
| | | field: "warehouseId", |
| | | title: TEXT.warehouse, |
| | | type: "select", |
| | | width: 100, |
| | | align: "left", |
| | | bind: { key: "warehouseEnum", data: [] }, |
| | | }, |
| | | { |
| | | field: "creater", |
| | | title: TEXT.creator, |
| | | type: "string", |
| | | width: 90, |
| | | align: "left", |
| | | }, |
| | | { |
| | | field: "createDate", |
| | | title: TEXT.createDate, |
| | | type: "datetime", |
| | | width: 160, |
| | | align: "left", |
| | | }, |
| | | { |
| | | field: "modifier", |
| | | title: TEXT.modifier, |
| | | type: "string", |
| | | width: 100, |
| | | align: "left", |
| | | hidden: true, |
| | | }, |
| | | { |
| | | field: "modifyDate", |
| | | title: TEXT.modifyDate, |
| | | type: "datetime", |
| | | width: 160, |
| | | align: "left", |
| | | hidden: true, |
| | | }, |
| | | ]); |
| | | |
| | | const detail = ref({ |
| | | cnName: "#detailCnName", |
| | | table: "", |
| | | columns: [], |
| | | sortName: "", |
| | | }); |
| | | |
| | | const detailState = reactive({ |
| | | rowsMap: {}, |
| | | loadingMap: {}, |
| | | errorMap: {}, |
| | | }); |
| | | |
| | | const stockStatusOptions = ref([]); |
| | | |
| | | const detailColumns = [ |
| | | { field: "materielName", title: TEXT.materielName, minWidth: 160 }, |
| | | { field: "serialNumber", title: TEXT.serialNumber, minWidth: 160 }, |
| | | { field: "stockQuantity", title: TEXT.stockQuantity, minWidth: 120 }, |
| | | { field: "status", title: TEXT.status, minWidth: 120 }, |
| | | { field: "inboundOrderRowNo", title: TEXT.inboundOrderRowNo, minWidth: 120 }, |
| | | ]; |
| | | |
| | | const normalizeValue = (value) => { |
| | | return value === null || value === undefined || value === "" ? "--" : value; |
| | | }; |
| | | |
| | | const formatStatusText = (value) => { |
| | | const matched = stockStatusOptions.value.find((item) => `${item.key}` === `${value}`); |
| | | return matched ? matched.value || matched.label : normalizeValue(value); |
| | | }; |
| | | |
| | | const getDetailRows = (stockId) => { |
| | | return detailState.rowsMap[stockId] || []; |
| | | }; |
| | | |
| | | const loadDetailRows = async (row) => { |
| | | if (!row || !row.id || detailState.loadingMap[row.id]) { |
| | | return; |
| | | } |
| | | if (detailState.rowsMap[row.id]) { |
| | | return; |
| | | } |
| | | |
| | | detailState.loadingMap[row.id] = true; |
| | | detailState.errorMap[row.id] = ""; |
| | | try { |
| | | const result = await proxy.http.post("/api/StockInfoDetail/getPageData", { |
| | | page: 1, |
| | | rows: 200, |
| | | sort: "id", |
| | | order: "asc", |
| | | wheres: JSON.stringify([ |
| | | { |
| | | name: "stockId", |
| | | value: String(row.id), |
| | | displayType: "int", |
| | | }, |
| | | ]), |
| | | }); |
| | | detailState.rowsMap[row.id] = (result && result.rows) || []; |
| | | } catch (error) { |
| | | detailState.rowsMap[row.id] = null; |
| | | detailState.errorMap[row.id] = error?.message || TEXT.detailLoadFailed; |
| | | } finally { |
| | | detailState.loadingMap[row.id] = false; |
| | | } |
| | | }; |
| | | |
| | | const loadStockStatusOptions = async () => { |
| | | try { |
| | | const result = await proxy.http.post("/api/Sys_Dictionary/GetVueDictionary", ["stockStatusEmun"]); |
| | | const matched = (result || []).find((item) => item.dicNo === "stockStatusEmun"); |
| | | stockStatusOptions.value = matched ? matched.data || [] : []; |
| | | } catch (error) { |
| | | stockStatusOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | loadStockStatusOptions(); |
| | | |
| | | const renderStatus = (row) => { |
| | | if (detailState.loadingMap[row.id]) { |
| | | return h("div", { class: "stock-detail-status" }, TEXT.detailLoading); |
| | | } |
| | | if (detailState.errorMap[row.id]) { |
| | | return h( |
| | | "div", |
| | | { class: "stock-detail-status stock-detail-status--error" }, |
| | | detailState.errorMap[row.id] |
| | | ); |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const renderDetailTable = (row) => { |
| | | const statusNode = renderStatus(row); |
| | | if (statusNode) { |
| | | return statusNode; |
| | | } |
| | | |
| | | const rows = getDetailRows(row.id); |
| | | if (!rows.length) { |
| | | return h("div", { class: "stock-detail-status" }, TEXT.detailEmpty); |
| | | } |
| | | |
| | | return h("div", { class: "stock-detail-table-wrapper" }, [ |
| | | h("div", { class: "stock-detail-toolbar" }, [ |
| | | h("div", { class: "stock-detail-toolbar__left" }, TEXT.detailName), |
| | | h("div", { class: "stock-detail-toolbar__right" }, [ |
| | | h("span", { class: "stock-detail-count" }, `${rows.length} 条`), |
| | | ]), |
| | | ]), |
| | | h( |
| | | ElTable, |
| | | { |
| | | data: rows, |
| | | border: true, |
| | | stripe: true, |
| | | size: "small", |
| | | class: "stock-detail-el-table", |
| | | maxHeight: 420, |
| | | emptyText: TEXT.detailEmpty, |
| | | }, |
| | | () => |
| | | detailColumns.map((column) => |
| | | h(ElTableColumn, { |
| | | key: column.field, |
| | | prop: column.field, |
| | | label: column.title, |
| | | minWidth: column.minWidth, |
| | | showOverflowTooltip: true, |
| | | formatter: (detailRow) => |
| | | column.field === "status" |
| | | ? formatStatusText(detailRow[column.field]) |
| | | : normalizeValue(detailRow[column.field]), |
| | | }) |
| | | ) |
| | | ), |
| | | ]); |
| | | const searchFormFields = ref({ |
| | | palletCode: "", |
| | | locationCode: "", |
| | | }); |
| | | const searchFormOptions = ref([ |
| | | [ |
| | | { title: "托盘编号", field: "palletCode" }, |
| | | { title: "货位编号", field: "locationCode" }, |
| | | ], |
| | | ]); |
| | | const columns = ref([ |
| | | { |
| | | field: "id", |
| | | title: "Id", |
| | | type: "int", |
| | | width: 90, |
| | | hidden: true, |
| | | readonly: true, |
| | | require: true, |
| | | align: "left", |
| | | }, |
| | | { |
| | | field: "palletCode", |
| | | title: "托盘编号", |
| | | type: "string", |
| | | width: 90, |
| | | align: "left", |
| | | }, |
| | | { |
| | | field: "locationCode", |
| | | title: "货位编号", |
| | | type: "string", |
| | | width: 150, |
| | | align: "left", |
| | | }, |
| | | // { |
| | | // field: "isFull", |
| | | // title: "是否满盘", |
| | | // type: "string", |
| | | // width: 150, |
| | | // align: "left", |
| | | // bind: { key: "yesno", data: [] }, |
| | | // }, |
| | | { |
| | | field: "warehouseId", |
| | | title: "仓库", |
| | | type: "select", |
| | | width: 100, |
| | | align: "left", |
| | | bind: { key: "warehouseEnum", data: [] }, |
| | | }, |
| | | { |
| | | field: "creater", |
| | | title: "创建人", |
| | | type: "string", |
| | | width: 90, |
| | | align: "left", |
| | | }, |
| | | { |
| | | field: "createDate", |
| | | title: "创建时间", |
| | | type: "datetime", |
| | | width: 160, |
| | | align: "left", |
| | | }, |
| | | { |
| | | field: "modifier", |
| | | title: "修改人", |
| | | type: "string", |
| | | width: 100, |
| | | align: "left", |
| | | hidden:true |
| | | }, |
| | | { |
| | | field: "modifyDate", |
| | | title: "修改时间", |
| | | type: "datetime", |
| | | width: 160, |
| | | align: "left", |
| | | hidden:true |
| | | }, |
| | | { |
| | | field: "remark", |
| | | title: "备注", |
| | | type: "string", |
| | | width: 100, |
| | | align: "left", |
| | | hidden:true |
| | | }, |
| | | ]); |
| | | const detail = ref({ |
| | | cnName: "#detailCnName", |
| | | table: "", |
| | | columns: [], |
| | | sortName: "", |
| | | }); |
| | | return { |
| | | table, |
| | | extend, |
| | | editFormFields, |
| | | editFormOptions, |
| | | searchFormFields, |
| | | searchFormOptions, |
| | | columns, |
| | | detail, |
| | | }; |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | }; |
| | | |
| | | const tableExpand = ref({ |
| | | width: 55, |
| | | onChange(row, expandedRows) { |
| | | const isExpanded = expandedRows.some((item) => item.id === row.id); |
| | | if (isExpanded) { |
| | | loadDetailRows(row); |
| | | } |
| | | }, |
| | | render(render, { row }) { |
| | | return render("div", { class: "stock-detail-panel" }, [ |
| | | render("div", { class: "stock-detail-header" }, [ |
| | | render("div", { class: "stock-detail-header__main" }, [ |
| | | render("div", { class: "stock-detail-title" }, TEXT.detailName), |
| | | render( |
| | | "div", |
| | | { class: "stock-detail-subtitle" }, |
| | | `${TEXT.expandPrefix}${normalizeValue(row.palletCode)}${TEXT.expandMiddle}${TEXT.expandLocation}${normalizeValue(row.locationCode)}` |
| | | ), |
| | | ]), |
| | | // render("div", { class: "stock-detail-tags" }, [ |
| | | // render("span", { class: "stock-detail-tag" }, normalizeValue(row.palletCode)), |
| | | // render( |
| | | // "span", |
| | | // { class: "stock-detail-tag stock-detail-tag--muted" }, |
| | | // normalizeValue(row.locationCode) |
| | | // ), |
| | | // ]), |
| | | ]), |
| | | renderDetailTable(row), |
| | | ]); |
| | | }, |
| | | }); |
| | | |
| | | return { |
| | | table, |
| | | extend, |
| | | editFormFields, |
| | | editFormOptions, |
| | | searchFormFields, |
| | | searchFormOptions, |
| | | columns, |
| | | detail, |
| | | tableExpand, |
| | | }; |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .stock-detail-panel { |
| | | margin: 4px 8px 12px; |
| | | padding: 14px 16px 16px; |
| | | background: linear-gradient(180deg, #ffffff 0%, #fafbfc 100%); |
| | | border: 1px solid #e8edf3; |
| | | border-radius: 10px; |
| | | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7); |
| | | } |
| | | |
| | | .stock-detail-header { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | margin-bottom: 12px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid #edf1f5; |
| | | } |
| | | |
| | | .stock-detail-header__main { |
| | | min-width: 0; |
| | | } |
| | | |
| | | .stock-detail-title { |
| | | margin-bottom: 4px; |
| | | font-size: 15px; |
| | | font-weight: 700; |
| | | color: #303133; |
| | | } |
| | | |
| | | .stock-detail-subtitle { |
| | | font-size: 13px; |
| | | color: #606266; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .stock-detail-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | justify-content: flex-end; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .stock-detail-tag { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | height: 28px; |
| | | padding: 0 10px; |
| | | color: #1f5eff; |
| | | background: #edf4ff; |
| | | border: 1px solid #d8e6ff; |
| | | border-radius: 999px; |
| | | font-size: 12px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .stock-detail-tag--muted { |
| | | color: #4e5969; |
| | | background: #f4f6f8; |
| | | border-color: #e5e9ef; |
| | | } |
| | | |
| | | .stock-detail-status { |
| | | padding: 14px 12px; |
| | | color: #606266; |
| | | background: #f8fafc; |
| | | border: 1px dashed #d9e2ec; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .stock-detail-status--error { |
| | | color: #f56c6c; |
| | | background: #fef0f0; |
| | | border-color: #fde2e2; |
| | | } |
| | | |
| | | .stock-detail-table-wrapper { |
| | | overflow-x: auto; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 8px; |
| | | background: #fff; |
| | | } |
| | | |
| | | .stock-detail-toolbar { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | padding: 12px 14px; |
| | | background: #f8fafc; |
| | | border-bottom: 1px solid #edf1f5; |
| | | } |
| | | |
| | | .stock-detail-toolbar__left { |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .stock-detail-count { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | height: 24px; |
| | | padding: 0 10px; |
| | | color: #606266; |
| | | background: #fff; |
| | | border: 1px solid #e5e9ef; |
| | | border-radius: 999px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | :deep(.stock-detail-el-table) { |
| | | border-top: none; |
| | | } |
| | | |
| | | :deep(.stock-detail-el-table .el-table__inner-wrapper::before) { |
| | | display: none; |
| | | } |
| | | |
| | | :deep(.stock-detail-el-table th.el-table__cell) { |
| | | background: #f5f7fa; |
| | | color: #303133; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | :deep(.stock-detail-el-table td.el-table__cell) { |
| | | color: #606266; |
| | | } |
| | | |
| | | :deep(.stock-detail-el-table .el-table__body tr:hover > td.el-table__cell) { |
| | | background: #f0f7ff; |
| | | } |
| | | </style> |