From 09b9bff70fe5d5d5121c1ca6ecbe5ab07da1949a Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期三, 22 四月 2026 14:16:55 +0800
Subject: [PATCH] 生产计划模块引入
---
src/api/productionPlan/productionPlan.js | 79 ++
src/views/productionManagement/processRoute/processRouteItem/index.vue | 2
src/views/productionPlan/productionPlan/components/PIMTable.vue | 470 ++++++++++++
src/views/productionPlan/productionPlan/index.vue | 1688 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 2,238 insertions(+), 1 deletions(-)
diff --git a/src/api/productionPlan/productionPlan.js b/src/api/productionPlan/productionPlan.js
new file mode 100644
index 0000000..a284f21
--- /dev/null
+++ b/src/api/productionPlan/productionPlan.js
@@ -0,0 +1,79 @@
+// 鐢熶骇璁㈠崟椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+export function productionPlanListPage(query) {
+ return request({
+ url: "/productionPlan/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鎷夊彇鏁版嵁
+export function loadProdData(query) {
+ return request({
+ url: "/productionPlan/loadProdData",
+ method: "get",
+ params: query,
+ });
+}
+
+export function summaryByProductType(query) {
+ return request({
+ url: "/productionPlan/summaryByProductType",
+ method: "get",
+ params: query,
+ });
+}
+
+// 瀵煎嚭鐢熶骇璁″垝
+export function exportProductionPlan(bomId) {
+ return request({
+ url: "/productionPlan/export",
+ method: "post",
+ params: { bomId },
+ responseType: "blob",
+ });
+}
+
+// 鐢熶骇璁″垝-鏂板淇敼
+export function productionPlanAdd(query) {
+ return request({
+ url: "/productionPlan",
+ method: "post",
+ data: query,
+ });
+}
+export function productionPlanUpdate(query) {
+ return request({
+ url: "/productionPlan",
+ method: "put",
+ data: query,
+ });
+}
+
+// 鐢熶骇璁″垝-鍒犻櫎
+export function productionPlanDelete(data) {
+ return request({
+ url: "/productionPlan",
+ method: "delete",
+ data,
+ });
+}
+// 鍚堝苟涓嬪彂
+export function productionPlanCombine(query) {
+ return request({
+ url: "/productionPlan/combine",
+ method: "post",
+ data: query,
+ });
+}
+
+// 杩借釜杩涘害
+export function trackProgressByNo(query) {
+ return request({
+ url: "/track/trackProgressByNo",
+ method: "get",
+ params: query,
+ });
+}
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 6fbaa2c..edd582b 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -220,7 +220,7 @@
:default-expand-all="true"
style="width: 100%">
<el-table-column type="expand">
- <template #default="props">
+ <template #default>
<el-form ref="form"
:model="bomDataValue">
<el-table :data="bomDataValue.dataList"
diff --git a/src/views/productionPlan/productionPlan/components/PIMTable.vue b/src/views/productionPlan/productionPlan/components/PIMTable.vue
new file mode 100644
index 0000000..9431002
--- /dev/null
+++ b/src/views/productionPlan/productionPlan/components/PIMTable.vue
@@ -0,0 +1,470 @@
+<template>
+ <el-table ref="multipleTable"
+ v-loading="tableLoading"
+ :border="border"
+ :data="tableData"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ :height="height"
+ :highlight-current-row="highlightCurrentRow"
+ :row-class-name="rowClassName"
+ :row-style="rowStyle"
+ :row-key="rowKey"
+ :style="tableStyle"
+ tooltip-effect="dark"
+ :expand-row-keys="expandRowKeys"
+ :show-summary="isShowSummary"
+ :summary-method="summaryMethod"
+ @row-click="rowClick"
+ @current-change="currentChange"
+ @selection-change="handleSelectionChange"
+ @expand-change="expandChange"
+ @select-all="handleSelectAll"
+ class="lims-table">
+ <el-table-column align="center"
+ type="selection"
+ width="55"
+ v-if="isSelection"
+ :selectable="selectable" />
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column v-for="(item, index) in column"
+ :key="index"
+ :column-key="item.columnKey"
+ :filter-method="item.filterHandler"
+ :filter-multiple="item.filterMultiple"
+ :filtered-value="item.filteredValue"
+ :filters="item.filters"
+ :fixed="item.fixed"
+ :label="item.label"
+ :prop="item.prop"
+ :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
+ :align="item.align"
+ :sortable="!!item.sortable"
+ :type="item.type"
+ :width="item.width"
+ :class-name="item.className || ''">
+ <template #header="scope">
+ <div class="pim-table-header-cell">
+ <div class="pim-table-header-title">
+ {{ item.label }}
+ </div>
+ <div v-if="item.headerSlot"
+ class="pim-table-header-extra">
+ <slot :name="item.headerSlot"
+ :column="scope.column" />
+ </div>
+ </div>
+ </template>
+ <template v-if="item.hasOwnProperty('colunmTemplate')"
+ #[item.colunmTemplate]="scope">
+ <slot v-if="item.theadSlot"
+ :name="item.theadSlot"
+ :index="scope.$index"
+ :row="scope.row" />
+ </template>
+ <template #default="scope">
+ <!-- 鎻掓Ы -->
+ <div v-if="item.dataType == 'slot'"
+ :class="item.className || ''">
+ <slot v-if="item.slot"
+ :index="scope.$index"
+ :name="item.slot"
+ :row="scope.row" />
+ </div>
+ <!-- 杩涘害鏉� -->
+ <div v-else-if="item.dataType == 'progress'"
+ :class="item.className || ''">
+ <el-progress :percentage="Number(scope.row[item.prop])" />
+ </div>
+ <!-- 鍥剧墖 -->
+ <div v-else-if="item.dataType == 'image'"
+ :class="item.className || ''">
+ <img :src="javaApi + '/img/' + scope.row[item.prop]"
+ alt=""
+ style="width: 40px; height: 40px; margin-top: 10px" />
+ </div>
+ <!-- tag -->
+ <div v-else-if="item.dataType == 'tag'"
+ :class="item.className || ''">
+ <el-tag v-if="
+ typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
+ 'string'
+ "
+ :title="formatters(scope.row[item.prop], item.formatData)"
+ :type="formatType(scope.row[item.prop], item.formatType)">
+ {{ formatters(scope.row[item.prop], item.formatData) }}
+ </el-tag>
+ <el-tag v-for="(tag, index) in dataTypeFn(
+ scope.row[item.prop],
+ item.formatData
+ )"
+ v-else-if="
+ typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
+ 'object'
+ "
+ :key="index"
+ :title="formatters(scope.row[item.prop], item.formatData)"
+ :type="formatType(tag, item.formatType)">
+ {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }}
+ </el-tag>
+ <el-tag v-else
+ :title="formatters(scope.row[item.prop], item.formatData)"
+ :type="formatType(scope.row[item.prop], item.formatType)">
+ {{ formatters(scope.row[item.prop], item.formatData) }}
+ </el-tag>
+ </div>
+ <!-- 鎸夐挳 -->
+ <div v-else-if="item.dataType == 'action'"
+ :class="item.className || ''"
+ @click.stop>
+ <template v-for="(o, key) in item.operation"
+ :key="key">
+ <el-button v-show="o.type != 'upload'"
+ v-if="o.showHide ? o.showHide(scope.row) : true"
+ :disabled="o.disabled ? o.disabled(scope.row) : false"
+ :plain="o.plain"
+ type="primary"
+ :style="{
+ color:
+ o.name === '鍒犻櫎' || o.name === 'delete'
+ ? '#f56c6c'
+ : o.color,
+ }"
+ link
+ @click.stop="o.clickFun(scope.row)"
+ :key="key">
+ {{ o.name }}
+ </el-button>
+ <el-upload :action="
+ javaApi +
+ o.url +
+ '?id=' +
+ (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
+ "
+ ref="uploadRef"
+ :multiple="o.multiple ? o.multiple : false"
+ :limit="1"
+ :disabled="o.disabled ? o.disabled(scope.row) : false"
+ :accept="
+ o.accept
+ ? o.accept
+ : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
+ "
+ v-if="o.type == 'upload'"
+ style="display: inline-block; width: 50px"
+ v-show="o.showHide ? o.showHide(scope.row) : true"
+ :headers="uploadHeader"
+ :before-upload="(file) => beforeUpload(file, scope.$index)"
+ :on-change="
+ (file, fileList) => handleChange(file, fileList, scope.$index)
+ "
+ :on-error="
+ (error, file, fileList) =>
+ onError(error, file, fileList, scope.$index)
+ "
+ :on-success="
+ (response, file, fileList) =>
+ handleSuccessUp(response, file, fileList, scope.$index)
+ "
+ :on-exceed="onExceed"
+ :show-file-list="false">
+ <el-button link
+ type="primary"
+ :disabled="o.disabled ? o.disabled(scope.row) : false">{{ o.name }}</el-button>
+ </el-upload>
+ </template>
+ </div>
+ <!-- 鍙偣鍑荤殑鏂囧瓧 -->
+ <div v-else-if="item.dataType == 'link'"
+ :class="item.className || ''"
+ class="cell link"
+ style="width: 100%"
+ @click="goLink(scope.row, item.linkMethod)">
+ <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
+ </div>
+ <!-- 榛樿绾睍绀烘暟鎹� -->
+ <div v-else
+ class="cell"
+ :class="item.className || ''"
+ style="width: 100%">
+ <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
+ <span v-else>{{
+ formatters(scope.row[item.prop], item.formatData)
+ }}</span>
+ </div>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination v-if="isShowPagination"
+ :total="page.total"
+ :layout="page.layout"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationSearch" />
+</template>
+
+<script setup>
+ import pagination from "../../../../components/PIMTable/Pagination.vue";
+ import { ref, inject, getCurrentInstance } from "vue";
+ import { ElMessage } from "element-plus";
+
+ // 鑾峰彇鍏ㄥ眬鐨� uploadHeader
+ const { proxy } = getCurrentInstance();
+ const uploadHeader = proxy.uploadHeader;
+ const javaApi = proxy.javaApi;
+
+ const emit = defineEmits([
+ "pagination",
+ "expand-change",
+ "selection-change",
+ "row-click",
+ ]);
+
+ // Filters
+ const typeFn = (val, row) => {
+ return typeof val === "function" ? val(row) : val;
+ };
+
+ const formatters = (val, format) => {
+ return typeof format === "function" ? format(val) : val;
+ };
+
+ // Props锛堜娇鐢� defineProps 鐨勯潪 TS 褰㈠紡锛�
+ const props = defineProps({
+ tableLoading: {
+ type: Boolean,
+ default: false,
+ },
+ height: {
+ type: [Number, String],
+ default: "calc(100vh - 22em)",
+ },
+ expandRowKeys: {
+ type: Array,
+ default: () => [],
+ },
+ summaryMethod: {
+ type: Function,
+ default: () => {},
+ },
+ rowClick: {
+ type: Function,
+ default: () => {},
+ },
+ currentChange: {
+ type: Function,
+ default: () => {},
+ },
+ border: {
+ type: Boolean,
+ default: true,
+ },
+ isSelection: {
+ type: Boolean,
+ default: false,
+ },
+ selectable: {
+ type: Function,
+ default: () => true,
+ },
+ isShowPagination: {
+ type: Boolean,
+ default: true,
+ },
+ isShowSummary: {
+ type: Boolean,
+ default: false,
+ },
+ highlightCurrentRow: {
+ type: Boolean,
+ default: false,
+ },
+ headerCellStyle: {
+ type: Object,
+ default: () => ({}),
+ },
+ column: {
+ type: Array,
+ default: () => [],
+ },
+ rowClassName: {
+ type: Function,
+ default: () => "",
+ },
+ rowStyle: {
+ type: [Object, Function],
+ default: () => ({}),
+ },
+ tableData: {
+ type: Array,
+ default: () => [],
+ },
+ rowKey: {
+ type: String,
+ default: "id",
+ },
+ page: {
+ type: Object,
+ default: () => ({
+ total: 0,
+ current: 0,
+ size: 10,
+ layout: "total, sizes, prev, pager, next, jumper",
+ }),
+ },
+ total: {
+ type: Number,
+ default: 0,
+ },
+ tableStyle: {
+ type: [String, Object],
+ default: () => ({ width: "100%" }),
+ },
+ });
+
+ // Data
+ const multipleTable = ref(null);
+ const uploadRefs = ref([]);
+ const currentFiles = ref({});
+ const uploadKeys = ref({});
+
+ const indexMethod = index => {
+ return (props.page.current - 1) * props.page.size + index + 1;
+ };
+
+ // 鐐瑰嚮 link 浜嬩欢
+ const goLink = (row, linkMethod) => {
+ if (!linkMethod) {
+ return ElMessage.warning("璇烽厤缃� link 浜嬩欢");
+ }
+ const parentMethod = getParentMethod(linkMethod);
+ if (typeof parentMethod === "function") {
+ parentMethod(row);
+ } else {
+ console.warn(`鐖剁粍浠朵腑鏈壘鍒版柟娉�: ${linkMethod}`);
+ }
+ };
+
+ // 鑾峰彇鐖剁粍浠舵柟娉曪紙绀轰緥瀹炵幇锛�
+ const getParentMethod = methodName => {
+ const parentMethods = inject("parentMethods", {});
+ return parentMethods[methodName];
+ };
+
+ const dataTypeFn = (val, format) => {
+ if (typeof format === "function") {
+ return format(val);
+ } else return val;
+ };
+
+ const formatType = (val, format) => {
+ if (typeof format === "function") {
+ return format(val);
+ } else return "";
+ };
+
+ // 鏂囦欢鍙樺寲澶勭悊
+ const handleChange = (file, fileList, index) => {
+ if (fileList.length > 1) {
+ const earliestFile = fileList[0];
+ uploadRefs.value[index]?.handleRemove(earliestFile);
+ }
+ currentFiles.value[index] = file;
+ };
+
+ // 鏂囦欢涓婁紶鍓嶆牎楠�
+ const beforeUpload = (rawFile, index) => {
+ currentFiles.value[index] = {};
+ if (rawfile.size > 1024 * 1024 * 10 * 10) {
+ ElMessage.error("涓婁紶鏂囦欢涓嶈秴杩�10M");
+ return false;
+ }
+ return true;
+ };
+
+ // 涓婁紶鎴愬姛
+ const handleSuccessUp = (response, file, fileList, index) => {
+ if (response.code == 200) {
+ if (uploadRefs[index]) {
+ uploadRefs[index].clearFiles();
+ }
+ currentFiles[index] = file;
+ ElMessage.success("涓婁紶鎴愬姛");
+ resetUploadComponent(index);
+ } else {
+ ElMessage.error(response.message);
+ }
+ };
+
+ const resetUploadComponent = index => {
+ uploadKeys[index] = Date.now();
+ };
+
+ // 涓婁紶澶辫触
+ const onError = (error, file, fileList, index) => {
+ ElMessage.error("鏂囦欢涓婁紶澶辫触锛岃閲嶈瘯");
+ if (uploadRefs.value[index]) {
+ uploadRefs.value[index].clearFiles();
+ }
+ };
+
+ // 鏂囦欢鏁伴噺瓒呴檺鎻愮ず
+ const onExceed = () => {
+ ElMessage.warning("瓒呭嚭鏂囦欢涓暟");
+ };
+
+ const paginationSearch = ({ page, limit }) => {
+ emit("pagination", { page: page, limit: limit });
+ };
+
+ const rowClick = row => {
+ emit("row-click", row);
+ };
+
+ const expandChange = (row, expandedRows) => {
+ emit("expand-change", row, expandedRows);
+ };
+
+ const handleSelectionChange = newSelection => {
+ emit("selection-change", newSelection);
+ };
+
+ // 澶勭悊鍏ㄩ�夋搷浣�
+ const handleSelectAll = selection => {
+ if (selection.length) {
+ console.log(selection, "selection");
+ // 鍏ㄩ�夋椂锛屽彧閫夋嫨鍙�夋嫨鐨勮
+ const selectableRows = props.tableData.filter(row => props.selectable(row));
+ // 娓呯┖褰撳墠閫夋嫨
+ multipleTable.value.clearSelection();
+ // 鍙�夋嫨鍙�夋嫨鐨勮
+ selectableRows.forEach(row => {
+ multipleTable.value.toggleRowSelection(row, true);
+ });
+ } else {
+ // 鍙栨秷鍏ㄩ�夋椂锛屽彧鍙栨秷鍙�夋嫨鐨勮鐨勯�夋嫨
+ props.tableData.forEach(row => {
+ if (props.selectable(row)) {
+ multipleTable.value.toggleRowSelection(row, false);
+ }
+ });
+ }
+ };
+</script>
+
+<style scoped lang="scss">
+ .cell {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .pim-table-header-extra :deep(.el-input),
+ .pim-table-header-extra :deep(.el-select) {
+ width: 100%;
+ }
+</style>
diff --git a/src/views/productionPlan/productionPlan/index.vue b/src/views/productionPlan/productionPlan/index.vue
new file mode 100644
index 0000000..53a99f5
--- /dev/null
+++ b/src/views/productionPlan/productionPlan/index.vue
@@ -0,0 +1,1688 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm"
+ :inline="true">
+ <!-- 绠�鍖栫増鎼滅储鏉′欢 -->
+ <el-form-item label="浜у搧鍚嶇О:">
+ <el-input v-model="searchForm.productName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 160px;"
+ @keyup.enter="handleQuery" />
+ </el-form-item>
+ <el-form-item label="璁″垝鏃ユ湡鑼冨洿:">
+ <el-date-picker v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 240px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="涓嬪彂鐘舵��:">
+ <el-select v-model="searchForm.status"
+ placeholder="璇烽�夋嫨鐘舵��"
+ clearable
+ filterable
+ style="width: 100px">
+ <el-option label="寰呬笅鍙�"
+ value="0" />
+ <el-option label="閮ㄥ垎涓嬪彂"
+ value="1" />
+ <el-option label="宸蹭笅鍙�"
+ value="2" />
+ </el-select>
+ </el-form-item>
+ <!-- 灞曞紑鐗堟悳绱㈡潯浠� -->
+ <template v-if="searchFormExpanded">
+ <el-form-item label="瀹㈡埛鍚嶇О:">
+ <el-input v-model="searchForm.customerName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 160px;"
+ @keyup.enter="handleQuery" />
+ </el-form-item>
+ <el-form-item label="浜у搧瑙勬牸:">
+ <el-input v-model="searchForm.model"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 160px;"
+ @keyup.enter="handleQuery" />
+ </el-form-item>
+ <el-form-item label="鐗╂枡缂栫爜:">
+ <el-input v-model="searchForm.materialCode"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 160px;"
+ @keyup.enter="handleQuery" />
+ </el-form-item>
+ <el-form-item label="鐢宠鍗曠紪鍙�:">
+ <el-input v-model="searchForm.applyNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 160px;"
+ @keyup.enter="handleQuery" />
+ </el-form-item>
+ </template>
+ <el-form-item>
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ <el-button type="info"
+ @click="handleReset">閲嶇疆</el-button>
+ <el-button type="primary"
+ @click="handleAdd">鏂板</el-button>
+ <el-button type="warning"
+ @click="getLoadProdData"
+ :loading="loadProdDataLoading">鎷夊彇鏁版嵁</el-button>
+ <el-button type="warning"
+ @click="handleMerge">鍚堝苟涓嬪彂</el-button>
+ <el-button type="warning"
+ @click="handleImport">瀵煎叆</el-button>
+ <el-button type="warning"
+ @click="handleExport">瀵煎嚭</el-button>
+ </el-form-item>
+ </el-form>
+ <div>
+ </div>
+ </div>
+ <div class="search-header">
+ <el-button type="text"
+ @click="toggleSearchForm">
+ <el-icon>
+ <ArrowUp v-if="searchFormExpanded" />
+ <ArrowDown v-else />
+ </el-icon>
+ {{ searchFormExpanded ? '鏀惰捣鎼滅储鏉′欢' : '灞曞紑鎼滅储鏉′欢' }}
+ </el-button>
+ </div>
+ <div class="table_list">
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ height="calc(100vh - 350px)"
+ :tableLoading="tableLoading"
+ :isSelection="true"
+ :selectable="isSelectable"
+ @selection-change="handleSelectionChange"
+ @pagination="pagination">
+ <template #quantity="{ row }">
+ {{ row.quantity || '-' }}<span style="color:rgb(63, 95, 211)"> 鍧�</span>
+ </template>
+ <template #volume="{ row }">
+ {{ row.volume || '-' }}<span style="color:rgba(12, 46, 40, 0.76)"> 鏂�</span>
+ </template>
+ </PIMTable>
+ </div>
+ <!-- 鍚堝苟涓嬪彂寮圭獥 -->
+ <el-dialog v-model="isShowNewModal"
+ destroy-on-close
+ title="鍚堝苟涓嬪彂"
+ width="600px">
+ <el-form :model="mergeForm"
+ label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="10">
+ <el-form-item label="鐗╂枡缂栫爜">
+ <div class="info-display">{{ mergeForm.materialCode || '-' }}</div>
+ </el-form-item>
+ </el-col>
+ <el-col :span="10">
+ <el-form-item label="浜у搧鍚嶇О">
+ <el-tag class="info-display">{{ mergeForm.productName || '-' }}</el-tag>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="10">
+ <el-form-item label="浜у搧瑙勬牸">
+ <div class="info-display">{{ mergeForm.model || '-' }}</div>
+ </el-form-item>
+ </el-col>
+ <el-col :span="10">
+ <el-form-item label="闀�*瀹�*楂�">
+ <div class="info-display">{{ mergeForm.length || '-' }}*{{ mergeForm.width || '-' }}*{{ mergeForm.height || '-' }}</div>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="璁″垝瀹屾垚鏃堕棿">
+ <el-date-picker v-model="mergeForm.planCompleteTime"
+ type="date"
+ value-format="YYYY-MM-DD"
+ style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="寮哄害"
+ v-if="mergeForm.productName === '鐮屽潡'">
+ <div v-if="strengthError"
+ class="strength-error"
+ style="color: red; margin-bottom: 8px;">{{ strengthError }}</div>
+ <el-select v-model="mergeForm.strength"
+ placeholder="璇烽�夋嫨寮哄害"
+ style="width: 100%"
+ required>
+ <el-option v-for="item in block_strength"
+ :key="item.id"
+ :label="item.label"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐢熶骇鏂规暟">
+ <el-input-number v-model="mergeForm.totalAssignedQuantity"
+ :min="0"
+ :max="sumAssignedQuantity"
+ @change="onBlur"
+ style="width: 100%" />
+ </el-form-item>
+ <!-- <el-form-item label="澶囨敞">
+ <el-input v-model="mergeForm.remark"
+ type="textarea" />
+ </el-form-item> -->
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="isShowNewModal = false">鍙栨秷</el-button>
+ <el-button type="primary"
+ @click="handleMergeSubmit">纭畾涓嬪彂</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <!-- 瀵煎叆寮圭獥 -->
+ <ImportDialog ref="importDialogRef"
+ v-model="importDialogVisible"
+ title="瀵煎叆鐢熶骇璁″垝"
+ :action="importAction"
+ :headers="importHeaders"
+ :auto-upload="false"
+ :on-success="handleImportSuccess"
+ :on-error="handleImportError"
+ @confirm="handleImportConfirm"
+ @download-template="handleDownloadTemplate"
+ @close="handleImportClose" />
+ <!-- 鏂板/缂栬緫寮圭獥 -->
+ <el-dialog v-model="dialogVisible"
+ destroy-on-close
+ :title="operationType === 'add' ? '鏂板鐢熶骇璁″垝' : '缂栬緫鐢熶骇璁″垝'"
+ width="600px">
+ <el-form ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="120px">
+ <el-form-item label="鐢宠鍗曠紪鍙�"
+ prop="applyNo">
+ <el-input v-model="form.applyNo"
+ placeholder="璇疯緭鍏ョ敵璇峰崟缂栧彿" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛鍚嶇О"
+ prop="customerName">
+ <el-input v-model="form.customerName"
+ placeholder="璇疯緭鍏ュ鎴峰悕绉�" />
+ </el-form-item>
+ <el-form-item label="浜у搧鍚嶇О"
+ prop="productMaterialId">
+ <el-tree-select v-model="form.productMaterialId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :data="productOptions"
+ :render-after-expand="false"
+ filterable
+ @change="handleProductChange"
+ style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="浜у搧瑙勬牸"
+ prop="productMaterialSkuId">
+ <el-select v-model="form.productMaterialSkuId"
+ @change="handleChangeSpecification"
+ filterable
+ placeholder="璇烽�夋嫨">
+ <el-option v-for="item in specificationOptions"
+ :key="item.id"
+ :label="item.model"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍧楁暟"
+ prop="quantity">
+ <el-input-number v-model="form.quantity"
+ :min="0"
+ placeholder="璇疯緭鍏ュ潡鏁�" />
+ </el-form-item>
+ <el-form-item label="鏂规暟"
+ prop="volume">
+ <el-input-number v-model="form.volume"
+ :min="0"
+ placeholder="璇疯緭鍏ユ柟鏁�" />
+ </el-form-item>
+ <el-form-item label="闀�"
+ prop="length">
+ <el-input-number v-model="form.length"
+ :min="0"
+ placeholder="璇疯緭鍏ラ暱搴�" />
+ </el-form-item>
+ <el-form-item label="瀹�"
+ prop="width">
+ <el-input-number v-model="form.width"
+ :min="0"
+ placeholder="璇疯緭鍏ュ搴�" />
+ </el-form-item>
+ <el-form-item label="楂�"
+ prop="height">
+ <el-input-number v-model="form.height"
+ :min="0"
+ placeholder="璇疯緭鍏ラ珮搴�" />
+ </el-form-item>
+ <el-form-item label="璁″垝寮�濮嬫棩鏈�"
+ prop="startDate">
+ <el-date-picker v-model="form.startDate"
+ type="date"
+ value-format="YYYY-MM-DD"
+ placeholder="璇烽�夋嫨璁″垝寮�濮嬫棩鏈�" />
+ </el-form-item>
+ <el-form-item label="璁″垝缁撴潫鏃ユ湡"
+ prop="endDate">
+ <el-date-picker v-model="form.endDate"
+ type="date"
+ value-format="YYYY-MM-DD"
+ placeholder="璇烽�夋嫨璁″垝缁撴潫鏃ユ湡" />
+ </el-form-item>
+ <el-form-item label="寮哄害"
+ prop="strength"
+ v-if="form.productName === '鐮屽潡'">
+ <el-select v-model="form.strength"
+ placeholder="璇烽�夋嫨寮哄害"
+ style="width: 100%">
+ <el-option v-for="item in block_strength"
+ :key="item.label"
+ :label="item.label"
+ :value="item.label" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞 1"
+ prop="remarkOne">
+ <el-input v-model="form.remarkOne"
+ placeholder="璇疯緭鍏ュ娉� 1" />
+ </el-form-item>
+ <el-form-item label="澶囨敞 2"
+ prop="remarkTwo">
+ <el-input v-model="form.remarkTwo"
+ placeholder="璇疯緭鍏ュ娉� 2" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary"
+ @click="handleSubmit">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue";
+ import { ElMessage } from "element-plus";
+ import { ArrowUp, ArrowDown } from "@element-plus/icons-vue";
+ import dayjs from "dayjs";
+ import ImportDialog from "@/components/Dialog/ImportDialog.vue";
+ import { getToken } from "@/utils/auth";
+ import { useDict } from "@/utils/dict";
+ import { useRouter } from "vue-router";
+ // import {
+ // productionPlanListPage,
+ // loadProdData,
+ // exportProductionPlan,
+ // productionPlanAdd,
+ // productionPlanUpdate,
+ // productionPlanDelete,
+ // productionPlanCombine,
+ // } from "@/api/productionPlan/productionPlan.js";
+
+ // Mock data and functions
+ const productionPlanListPage = params => {
+ console.log("Mock productionPlanListPage called with:", params);
+ return Promise.resolve({
+ data: {
+ records: [
+ {
+ id: 1,
+ dataSourceType: 1,
+ applyNo: "SQ20260422001",
+ customerName: "妯℃嫙瀹㈡埛A",
+ productName: "鏉挎潗",
+ model: "100*200*300",
+ materialCode: "MAT001",
+ quantity: 100,
+ volume: 50,
+ status: 0,
+ assignedQuantity: 0,
+ length: 100,
+ width: 200,
+ height: 300,
+ startDate: "2026-04-22",
+ endDate: "2026-04-25",
+ strength: "C20",
+ remarkOne: "澶囨敞1",
+ remarkTwo: "澶囨敞2",
+ },
+ {
+ id: 2,
+ dataSourceType: 2,
+ applyNo: "SQ20260422002",
+ customerName: "妯℃嫙瀹㈡埛B",
+ productName: "鐮屽潡",
+ model: "200*200*600",
+ materialCode: "MAT002",
+ quantity: 200,
+ volume: 80,
+ status: 1,
+ assignedQuantity: 30,
+ length: 200,
+ width: 200,
+ height: 600,
+ startDate: "2026-04-23",
+ endDate: "2026-04-26",
+ strength: "C25",
+ remarkOne: "澶囨敞1",
+ remarkTwo: "澶囨敞2",
+ },
+ {
+ id: 3,
+ dataSourceType: 1,
+ applyNo: "SQ20260422003",
+ customerName: "妯℃嫙瀹㈡埛C",
+ productName: "鐮屽潡",
+ model: "240*115*53",
+ materialCode: "MAT003",
+ quantity: 1000,
+ volume: 1.46,
+ status: 2,
+ assignedQuantity: 1.46,
+ length: 240,
+ width: 115,
+ height: 53,
+ startDate: "2026-04-20",
+ endDate: "2026-04-21",
+ strength: "MU10",
+ remarkOne: "宸蹭笅鍙戞暟鎹�",
+ remarkTwo: "",
+ },
+ ],
+ total: 3,
+ },
+ });
+ };
+
+ const loadProdData = () => {
+ console.log("Mock loadProdData called");
+ return Promise.resolve({ code: 200, msg: "鍚屾鎴愬姛" });
+ };
+
+ const exportProductionPlan = () => {
+ console.log("Mock exportProductionPlan called");
+ return Promise.resolve();
+ };
+
+ const productionPlanAdd = payload => {
+ console.log("Mock productionPlanAdd called with:", payload);
+ return Promise.resolve({ code: 200, msg: "鏂板鎴愬姛" });
+ };
+
+ const productionPlanUpdate = payload => {
+ console.log("Mock productionPlanUpdate called with:", payload);
+ return Promise.resolve({ code: 200, msg: "淇敼鎴愬姛" });
+ };
+
+ const productionPlanDelete = ids => {
+ console.log("Mock productionPlanDelete called with ids:", ids);
+ return Promise.resolve({ code: 200, msg: "鍒犻櫎鎴愬姛" });
+ };
+
+ const productionPlanCombine = payload => {
+ console.log("Mock productionPlanCombine called with:", payload);
+ return Promise.resolve({ code: 200, msg: "鍚堝苟涓嬪彂鎴愬姛" });
+ };
+ import PIMTable from "./components/PIMTable.vue";
+ // import {
+ // modelListPage,
+ // productTreeList,
+ // productTreeListQuery,
+ // } from "@/api/basicData/newProduct.js";
+
+ const { proxy } = getCurrentInstance();
+ const router = useRouter();
+
+ const tableColumn = ref([
+ {
+ label: "鏁版嵁鏉ユ簮",
+ width: "100px",
+ prop: "dataSourceType",
+ dataType: "tag",
+ formatType: params => {
+ const typeMap = {
+ 2: "warning",
+ 1: "primary",
+ };
+ return typeMap[params] || "info";
+ },
+ formatData: cell => (cell == 1 ? "閽夐拤鍚屾" : "鎵嬪姩鏂板"),
+ },
+ {
+ label: "鐢宠鍗曠紪鍙�",
+ prop: "applyNo",
+ width: "150px",
+ },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width: "150px",
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productName",
+ width: "200px",
+ dataType: "tag",
+ formatType: params => {
+ // const typeMap = {
+ // 鏉挎潗: "primary",
+ // 鐮屽潡: "warning",
+ // };
+ // return typeMap[params] || "info";
+ return "primary";
+ },
+ },
+ {
+ label: "浜у搧瑙勬牸",
+ prop: "model",
+ width: "150px",
+ className: "spec-cell",
+ },
+ {
+ label: "鐗╂枡缂栫爜",
+ prop: "materialCode",
+ width: "150px",
+ },
+ {
+ label: "鍧楁暟",
+ prop: "quantity",
+ align: "right",
+ dataType: "slot",
+ slot: "quantity",
+ },
+ {
+ label: "鏂规暟",
+ prop: "volume",
+ width: "150px",
+ align: "right",
+ dataType: "slot",
+ slot: "volume",
+ className: "volume-cell",
+ },
+ {
+ label: "涓嬪彂鐘舵��",
+ prop: "status",
+ width: "150px",
+ className: "status-cell",
+ dataType: "tag",
+ formatType: params => {
+ const typeMap = {
+ 0: "warning",
+ 1: "primary",
+ 2: "info",
+ };
+ return typeMap[params] || "info";
+ },
+ formatData: cell => {
+ const statusMap = {
+ 0: "寰呬笅鍙�",
+ 1: "閮ㄥ垎涓嬪彂",
+ 2: "宸蹭笅鍙�",
+ };
+ return statusMap[cell] || "";
+ },
+ },
+ {
+ label: "宸蹭笅鍙戞柟鏁�",
+ prop: "assignedQuantity",
+ width: "150px",
+ className: "spec-cell",
+ formatData: cell => (cell ? `${cell}鏂筦 : 0),
+ },
+ {
+ label: "闀�",
+ prop: "length",
+ className: "dimension-cell",
+ formatData: cell => (cell ? `${cell}mm` : ""),
+ },
+ {
+ label: "瀹�",
+ prop: "width",
+ className: "dimension-cell",
+ formatData: cell => (cell ? `${cell}mm` : ""),
+ },
+ {
+ label: "楂�",
+ prop: "height",
+ className: "dimension-cell",
+ formatData: cell => (cell ? `${cell}mm` : ""),
+ },
+ // {
+ // label: "娴佹按鍙�",
+ // prop: "serialNo",
+ // width: "150px",
+ // className: "code-cell",
+ // },
+ {
+ label: "璁″垝寮�濮嬫棩鏈�",
+ prop: "startDate",
+ width: "150px",
+ className: "date-cell",
+ formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
+ },
+ {
+ label: "璁″垝缁撴潫鏃ユ湡",
+ prop: "endDate",
+ width: "150px",
+ className: "date-cell",
+ formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
+ },
+ {
+ label: "寮哄害",
+ prop: "strength",
+ formatData: cell => {
+ if (!cell) return "";
+ const strengthItem = block_strength.value.find(item => item.id === cell);
+ return strengthItem ? strengthItem.label : cell;
+ },
+ },
+
+ {
+ label: "澶囨敞 1",
+ width: "150px",
+ prop: "remarkOne",
+ },
+ {
+ label: "澶囨敞 2",
+ width: "150px",
+ prop: "remarkTwo",
+ },
+
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 300,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "primary",
+ link: true,
+ showHide: row => {
+ return row.status == 0 && row.dataSourceType != 1;
+ //status锛�0锛氬緟涓嬪彂锛�1锛氶儴鍒嗕笅鍙戯紝2锛氬凡涓嬪彂
+ },
+ clickFun: row => {
+ handleEdit(row);
+ },
+ },
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ link: true,
+ showHide: row => {
+ return row.status == 0;
+ },
+ clickFun: row => {
+ handleDelete(row);
+ },
+ },
+ {
+ name: "涓嬪彂",
+ type: "text",
+ showHide: row => {
+ // 璁$畻鍓╀綑鏂规暟
+ const remainingVolume =
+ (row.volume || 0) - (row.assignedQuantity || 0);
+ // 濡傛灉鍓╀綑鏂规暟灏忎簬绛変簬0锛岀姝㈤�夋嫨
+ return remainingVolume > 0;
+ },
+ clickFun: row => {
+ // 鍗曠嫭涓嬪彂鎿嶄綔
+ // 璁剧疆琛ㄥ崟鏁版嵁
+ strengthError.value = "";
+ mergeForm.ids = [row.id];
+ mergeForm.materialCode = row.materialCode;
+ mergeForm.productName = row.productName || "";
+ mergeForm.model = row.model || "";
+ mergeForm.length = row.length || 0;
+ mergeForm.width = row.width || 0;
+ mergeForm.height = row.height || 0;
+ mergeForm.totalAssignedQuantity =
+ (Number(row.volume) - Number(row.assignedQuantity)).toFixed(4) || 0;
+ mergeForm.planCompleteTime = row.planCompleteTime || "";
+ mergeForm.productMaterialId = row.productMaterialId || "";
+ mergeForm.strength = row.strength || "";
+ sumAssignedQuantity.value = mergeForm.totalAssignedQuantity;
+ // 鎵撳紑寮圭獥
+ isShowNewModal.value = true;
+ },
+ },
+ // {
+ // name: "杩借釜杩涘害",
+ // type: "text",
+ // clickFun: row => {
+ // handleTrackProgress(row);
+ // },
+ // },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+ const selectedRows = ref([]);
+
+ // 浜у搧绫诲埆姹囨�荤粺璁℃暟鎹�
+ const categorySummary = ref([]);
+ // 浜у搧绫诲埆姹囨�诲脊绐楁帶鍒�
+ const showCategorySummaryDialog = ref(false);
+
+ // 鍚堝苟涓嬪彂寮圭獥鎺у埗
+ const isShowNewModal = ref(false);
+ // 鍚堝苟涓嬪彂琛ㄥ崟鏁版嵁
+ const mergeForm = reactive({
+ materialCode: "",
+ productName: "",
+ model: "",
+ length: 0,
+ width: 0,
+ height: 0,
+ totalAssignedQuantity: 0,
+ planCompleteTime: "",
+ strength: "",
+ productMaterialId: "",
+ });
+
+ // 瀵煎叆鐩稿叧
+ const importDialogRef = ref(null);
+ const importDialogVisible = ref(false);
+ const importAction =
+ import.meta.env.VITE_APP_BASE_API + "/productionPlan/import";
+ const importHeaders = ref({
+ Authorization: `Bearer ${getToken()}`,
+ });
+
+ // 鏂板/缂栬緫鐩稿叧
+ const dialogVisible = ref(false);
+ const operationType = ref("add"); // add | edit
+ const productOptions = ref([]);
+ const specificationOptions = ref([]);
+ const formRef = ref(null);
+ // 鑾峰彇寮哄害瀛楀吀
+ const { block_strength } = useDict("block_strength");
+ const form = reactive({
+ id: undefined,
+ applyNo: "",
+ customerName: "",
+ productMaterialId: undefined,
+ productMaterialSkuId: undefined,
+ productName: "",
+ model: "",
+ materialCode: "",
+ quantity: 0,
+ volume: 0,
+ length: 0,
+ width: 0,
+ height: 0,
+ startDate: "",
+ endDate: "",
+ status: "",
+ strength: "",
+ remarkOne: "",
+ remarkTwo: "",
+ });
+ const rules = reactive({
+ applyNo: [{ required: true, message: "璇疯緭鍏ョ敵璇峰崟缂栧彿", trigger: "blur" }],
+ customerName: [
+ { required: true, message: "璇疯緭鍏ュ鎴峰悕绉�", trigger: "blur" },
+ ],
+ productMaterialSkuId: [
+ { required: true, message: "璇烽�夋嫨浜у搧瑙勬牸", trigger: "change" },
+ ],
+ volume: [{ required: true, message: "璇疯緭鍏ユ柟鏁�", trigger: "blur" }],
+ productMaterialId: [
+ { required: true, message: "璇烽�夋嫨浜у搧", trigger: "change" },
+ ],
+ strength: [
+ {
+ validator: (rule, value, callback) => {
+ if (form.productName === "鐮屽潡" && !value) {
+ callback(new Error("鐮屽潡浜у搧鐨勫己搴︿负蹇呭~椤�"));
+ } else {
+ callback();
+ }
+ },
+ trigger: ["blur", "change"],
+ required: false,
+ },
+ ],
+ });
+
+ // 澶勭悊杩借釜杩涘害鎸夐挳鐐瑰嚮
+ const handleTrackProgress = row => {
+ // 璺宠浆鍒拌拷韪繘搴﹂〉闈�
+ router.push({
+ path: "/productionPlan/trackProgress",
+ query: {
+ id: row.id,
+ applyNo: row.applyNo,
+ productName: row.productName,
+ model: row.model,
+ },
+ });
+ };
+ const onBlur = value => {
+ // 闄愬埗鍥涗綅灏忔暟
+ mergeForm.totalAssignedQuantity = Number(value.toFixed(4));
+ };
+
+ const fetchProductOptions = () => {
+ // return productTreeList({ type: 2 }).then(res => {
+ // productOptions.value = convertIdToValue(res.data);
+ // return res;
+ // });
+ };
+
+ const convertIdToValue = data => {
+ return data.map(item => {
+ const newItem = {
+ value: `config_${item.configId}`, // 浣跨敤config_鍓嶇紑纭繚鍞竴鎬�
+ label: item.configName,
+ disabled: item.materialList.length === 0,
+ };
+ if (item.materialList && item.materialList.length > 0) {
+ newItem.children = item.materialList.map(material => ({
+ value: material.id, // 浣跨敤material鐨刬d浣滀负value
+ label: material.productName, // 浣跨敤materialName浣滀负label
+ }));
+ }
+
+ return newItem;
+ });
+ };
+
+ const handleProductChange = value => {
+ form.productMaterialSkuId = undefined;
+ // 鏌ユ壘閫変腑鐨勪骇鍝佸悕绉�
+ const findProductName = (options, value) => {
+ for (const option of options) {
+ if (option.value === value) {
+ return option.label;
+ }
+ if (option.children) {
+ const found = findProductName(option.children, value);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ return "";
+ };
+ form.productName = findProductName(productOptions.value, value);
+ // 瑙﹀彂寮哄害瀛楁楠岃瘉
+ if (formRef.value) {
+ formRef.value.validateField("strength");
+ }
+ fetchSpecificationOptions(value);
+ };
+
+ const fetchSpecificationOptions = productId => {
+ specificationOptions.value = [];
+ if (productId) {
+ // modelListPage({ productId: productId, size: -1, current: -1 }).then(res => {
+ // specificationOptions.value = res.data.records;
+ // });
+ }
+ };
+
+ const handleChangeSpecification = value => {
+ form.materialCode = undefined;
+ const selectedModel = specificationOptions.value.find(
+ item => item.id === value
+ );
+ if (selectedModel) {
+ form.materialCode = selectedModel.materialCode;
+ // 瑙f瀽瑙勬牸瀛楃涓茶幏鍙栭暱瀹介珮
+ const model = selectedModel.model;
+ if (model) {
+ const dimensions = model.match(/^(\d+)\*(\d+)\*(\d+)$/);
+ if (dimensions && dimensions.length === 4) {
+ form.length = parseInt(dimensions[1]);
+ form.width = parseInt(dimensions[2]);
+ form.height = parseInt(dimensions[3]);
+ }
+ }
+ }
+ };
+
+ const data = reactive({
+ searchForm: {
+ customerName: "",
+ productName: "",
+ model: "",
+ materialCode: "",
+ applyNo: "",
+ dateRange: [],
+ },
+ searchFormExpanded: false,
+ });
+ const { searchForm, searchFormExpanded } = toRefs(data);
+
+ // 鍒囨崲鎼滅储琛ㄥ崟灞曞紑/鏀惰捣鐘舵��
+ const toggleSearchForm = () => {
+ data.searchFormExpanded = !data.searchFormExpanded;
+ };
+
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+
+ /** 閲嶇疆鎸夐挳鎿嶄綔 */
+ const handleReset = () => {
+ Object.assign(searchForm.value, {
+ customerName: "",
+ productName: "",
+ model: "",
+ materialCode: "",
+ applyNo: "",
+ dateRange: [],
+ });
+ page.current = 1;
+ getList();
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ // 璁$畻浜у搧绫诲埆姹囨�荤粺璁�
+ const calculateCategorySummary = () => {
+ const summary = {};
+
+ // 閬嶅巻琛ㄦ牸鏁版嵁锛屾寜浜у搧绫诲埆姹囨��
+ tableData.value.forEach(row => {
+ const category = row.materialCode;
+ if (!summary[category]) {
+ summary[category] = {
+ materialCode: category,
+ totalAssignedQuantity: 0,
+ };
+ }
+ summary[category].totalAssignedQuantity += (
+ Number(row.volume) - Number(row.assignedQuantity)
+ ).toFixed(4);
+ });
+
+ // 杞崲涓烘暟缁勬牸寮�
+ categorySummary.value = Object.values(summary);
+ };
+
+ const getList = () => {
+ tableLoading.value = true;
+ // 鏋勯�犳悳绱㈠弬鏁�
+ const params = { ...searchForm.value, ...page };
+ params.startDate = params.dateRange ? params.dateRange[0] : "";
+ params.endDate = params.dateRange ? params.dateRange[1] : "";
+ delete params.dateRange;
+ productionPlanListPage(params)
+ .then(res => {
+ tableLoading.value = false;
+
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ // 璁$畻浜у搧绫诲埆姹囨�荤粺璁�
+ calculateCategorySummary();
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+ };
+
+ // 閫変腑鐨勫簭鍒楀彿
+ const selectedserialNo = ref("");
+
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ selectedRows.value = selection;
+ // 濡傛灉鏈夐�変腑鐨勮锛岃褰曠涓�涓�変腑琛岀殑搴忓垪鍙�
+ if (selection.length > 0) {
+ selectedserialNo.value = selection[0].materialCode;
+ } else {
+ // 濡傛灉娌℃湁閫変腑鐨勮锛屾竻绌哄簭鍒楀彿
+ selectedserialNo.value = "";
+ }
+ };
+
+ // 鍒ゆ柇琛屾槸鍚﹀彲閫夋嫨
+ const isSelectable = row => {
+ // 璁$畻鍓╀綑鏂规暟
+ const remainingVolume = (row.volume || 0) - (row.assignedQuantity || 0);
+ // 濡傛灉鍓╀綑鏂规暟灏忎簬绛変簬0锛岀姝㈤�夋嫨
+ if (remainingVolume <= 0) {
+ return false;
+ }
+ // 濡傛灉娌℃湁閫変腑鐨勮锛屾墍鏈夎閮藉彲閫夋嫨
+ if (!selectedserialNo.value) {
+ return true;
+ }
+ // 濡傛灉鏈夐�変腑鐨勮锛屽彧鏈夊簭鍒楀彿鐩稿悓鐨勮鎵嶅彲閫夋嫨
+ return row.materialCode === selectedserialNo.value;
+ };
+ // 鎷夊彇鏁版嵁鎸夐挳鎿嶄綔
+ const loadProdDataLoading = ref(false);
+ const getLoadProdData = () => {
+ // 鏄剧ず鍔犺浇鎻愮ず
+ loadProdDataLoading.value = true;
+ proxy.$modal.loading("姝e湪鎷夊彇鏁版嵁锛岃绋嶅��...");
+
+ loadProdData()
+ .then(res => {
+ proxy.$modal.closeLoading();
+ getList();
+ proxy.$modal.msgSuccess("鏁版嵁鎷夊彇鎴愬姛");
+ })
+ .catch(err => {
+ proxy.$modal.closeLoading();
+ console.error("鎷夊彇澶辫触:", err);
+ proxy.$modal.msgError("鏁版嵁鎷夊彇澶辫触锛岃閲嶈瘯");
+ })
+ .finally(() => {
+ loadProdDataLoading.value = false;
+ });
+ };
+ const sumAssignedQuantity = ref(0);
+ // 鍝嶅簲寮忔暟鎹�
+ const strengthError = ref("");
+
+ // 澶勭悊鍚堝苟涓嬪彂鎸夐挳鐐瑰嚮
+ const handleMerge = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨瑕佸悎骞朵笅鍙戠殑鐢熶骇璁″垝");
+ return;
+ }
+ console.log(selectedRows.value);
+ // 妫�鏌ュ己搴︿竴鑷存��
+ const firstRow = selectedRows.value[0];
+ const productName = firstRow.productName || "";
+ let strengthConsistent = true;
+ let firstStrength = firstRow.strength || "";
+ strengthError.value = "";
+
+ if (productName === "鐮屽潡") {
+ for (const row of selectedRows.value) {
+ if (row.strength !== firstStrength) {
+ strengthConsistent = false;
+ break;
+ }
+ }
+
+ if (!strengthConsistent) {
+ strengthError.value = "閫夋嫨鐨勭爩鍧楀己搴︿笉涓�鑷达紝璇烽噸鏂伴�夋嫨";
+ }
+ }
+
+ // 璁$畻鎬诲埗閫犳暟閲�
+ const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => {
+ return (
+ sum +
+ (row.volume == null
+ ? 0
+ : Number(Number(row.volume) - Number(row.assignedQuantity).toFixed(4)))
+ );
+ }, 0);
+ sumAssignedQuantity.value = totalAssignedQuantity;
+ console.log(totalAssignedQuantity);
+ // 璁剧疆琛ㄥ崟鏁版嵁
+ mergeForm.materialCode = selectedserialNo.value;
+ mergeForm.productName = productName;
+ mergeForm.model = firstRow.model || "";
+ mergeForm.length = firstRow.length || 0;
+ mergeForm.width = firstRow.width || 0;
+ mergeForm.height = firstRow.height || 0;
+ mergeForm.totalAssignedQuantity = totalAssignedQuantity;
+ mergeForm.planCompleteTime = firstRow.planCompleteTime || "";
+ mergeForm.productMaterialId = firstRow.productMaterialId || "";
+ mergeForm.strength = firstStrength;
+ mergeForm.ids = selectedRows.value.map(row => row.id);
+
+ // 鎵撳紑寮圭獥
+ isShowNewModal.value = true;
+ };
+
+ // 澶勭悊鍚堝苟涓嬪彂鎻愪氦
+ const handleMergeSubmit = () => {
+ if (mergeForm.totalAssignedQuantity === 0) {
+ ElMessage.warning("璇疯緭鍏ョ敓浜ф柟鏁�");
+ return;
+ }
+ // 楠岃瘉鐮屽潡浜у搧鐨勫己搴�
+ if (mergeForm.productName === "鐮屽潡" && !mergeForm.strength) {
+ ElMessage.error("鐮屽潡浜у搧鐨勫己搴︿负蹇呭~椤�");
+ return;
+ }
+ if (mergeForm.productName != "鐮屽潡") {
+ mergeForm.strength = "";
+ }
+ console.log(sumAssignedQuantity.value, "sumAssignedQuantity");
+ // 璁$畻褰撳墠閫変腑琛岀殑鎬绘柟鏁�
+ const totalVolume = selectedRows.value.reduce((sum, row) => {
+ return sum + (Number(row.volume) - Number(row.assignedQuantity) || 0);
+ }, 0);
+
+ // 楠岃瘉totalAssignedQuantity涓嶈兘澶т簬鎬绘柟鏁�
+ if (mergeForm.totalAssignedQuantity > sumAssignedQuantity.value) {
+ ElMessage.error("鐢熶骇鏂规暟涓嶈兘澶т簬褰撳墠璁$畻鐨勬�诲��");
+ return;
+ }
+
+ console.log(mergeForm, "mergeForm");
+ const strengthItem = block_strength.value.find(
+ item => item.id === mergeForm.strength
+ );
+ const payload = {
+ ...mergeForm,
+ strength: strengthItem ? strengthItem.label : mergeForm.strength,
+ };
+ productionPlanCombine(payload)
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success("涓嬪彂鎴愬姛");
+ getList();
+ isShowNewModal.value = false;
+ // 鍙互閫夋嫨鍒锋柊鍒楄〃鎴栧叾浠栨搷浣�
+ getList();
+ } else {
+ ElMessage.error(res.message || "涓嬪彂澶辫触");
+ }
+ })
+ .catch(err => {
+ console.error("鍚堝苟涓嬪彂寮傚父锛�", err);
+ ElMessage.error("绯荤粺寮傚父锛屽悎骞朵笅鍙戝け璐�");
+ });
+ // 鍙互閫夋嫨鍒锋柊鍒楄〃鎴栧叾浠栨搷浣�
+ };
+
+ // 瀵煎叆
+ const handleImport = () => {
+ importDialogVisible.value = true;
+ };
+
+ // 瀵煎嚭
+ const handleExport = () => {
+ const fileName = `鐢熶骇璁″垝.xlsx`;
+ exportProductionPlan()
+ .then(res => {
+ // 杩斿洖鐨勬暟鎹槸鍚︿负绌�
+ if (!res) {
+ proxy.$modal.msgError("瀵煎嚭澶辫触锛岃繑鍥炴暟鎹负绌�");
+ return;
+ }
+
+ const blob = new Blob([res], {
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ });
+ const downloadElement = document.createElement("a");
+ const href = window.URL.createObjectURL(blob);
+
+ downloadElement.style.display = "none";
+ downloadElement.href = href;
+ downloadElement.download = fileName;
+
+ document.body.appendChild(downloadElement);
+ downloadElement.click();
+
+ document.body.removeChild(downloadElement);
+ window.URL.revokeObjectURL(href);
+
+ proxy.$modal.msgSuccess("瀵煎嚭鎴愬姛");
+ })
+ .catch(err => {
+ console.error("瀵煎嚭寮傚父锛�", err);
+ proxy.$modal.msgError("绯荤粺寮傚父锛屽鍑哄け璐�");
+ });
+ };
+
+ // 瀵煎叆鎴愬姛
+ const handleImportSuccess = response => {
+ if (response.code === 200) {
+ ElMessage.success("瀵煎叆鎴愬姛");
+ importDialogVisible.value = false;
+ getList();
+ } else {
+ ElMessage.error(response.msg || "瀵煎叆澶辫触");
+ }
+ };
+
+ // 瀵煎叆澶辫触
+ const handleImportError = error => {
+ ElMessage.error("瀵煎叆澶辫触锛岃妫�鏌ユ枃浠舵牸寮忔槸鍚︽纭�");
+ };
+
+ // 纭瀵煎叆
+ const handleImportConfirm = () => {
+ if (importDialogRef.value) {
+ importDialogRef.value.submit();
+ }
+ };
+
+ // 涓嬭浇妯℃澘
+ const handleDownloadTemplate = () => {
+ proxy.download(
+ "/productionPlan/downloadTemplate",
+ {},
+ "鐢熶骇璁″垝瀵煎叆妯℃澘.xlsx"
+ );
+ };
+
+ // 鍏抽棴瀵煎叆寮圭獥
+ const handleImportClose = () => {
+ importDialogVisible.value = false;
+ };
+
+ // 鏂板
+ const handleAdd = () => {
+ operationType.value = "add";
+ Object.assign(form, {
+ applyNo: "",
+ customerName: "",
+ productName: "",
+ productMaterialId: undefined,
+ productMaterialSkuId: undefined,
+ model: "",
+ materialCode: "",
+ quantity: 0,
+ volume: 0,
+ length: 0,
+ width: 0,
+ height: 0,
+ startDate: "",
+ endDate: "",
+ strength: "",
+ remarkOne: "",
+ remarkTwo: "",
+ });
+ dialogVisible.value = true;
+ fetchProductOptions();
+ };
+
+ // 缂栬緫
+ const handleEdit = row => {
+ operationType.value = "edit";
+ Object.assign(form, {
+ id: row.id,
+ applyNo: row.applyNo || "",
+ customerName: row.customerName || "",
+ productName: row.productName || "",
+ productMaterialId: row.productMaterialId || undefined,
+ productMaterialSkuId: row.productMaterialSkuId || undefined,
+ model: row.model || "",
+ materialCode: row.materialCode || "",
+ quantity: row.quantity || 0,
+ volume: row.volume || 0,
+ length: row.length || 0,
+ width: row.width || 0,
+ height: row.height || 0,
+ startDate: row.startDate || "",
+ endDate: row.endDate || "",
+ strength: row.strength || "",
+ remarkOne: row.remarkOne || "",
+ remarkTwo: row.remarkTwo || "",
+ });
+ dialogVisible.value = true;
+ fetchProductOptions();
+ fetchSpecificationOptions(row.productMaterialId);
+ };
+
+ // 鍒犻櫎
+ const handleDelete = row => {
+ proxy.$modal
+ .confirm("纭鍒犻櫎璇ョ敓浜ц鍒掞紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ productionPlanDelete([row.id])
+ .then(() => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ });
+ })
+ .catch(() => {});
+ };
+
+ // 鎻愪氦琛ㄥ崟
+ const handleSubmit = () => {
+ formRef.value.validate(valid => {
+ if (valid) {
+ if (form.volume === 0) {
+ proxy.$modal.msgError("鏂规暟涓嶈兘涓�0");
+ return;
+ }
+ if (form.v === "add") {
+ payload.id = null;
+ }
+ const payload = { ...form };
+ if (operationType.value === "add") {
+ payload.id = null;
+ productionPlanAdd(payload)
+ .then(() => {
+ proxy.$modal.msgSuccess(
+ operationType.value === "add" ? "鏂板鎴愬姛" : "淇敼鎴愬姛"
+ );
+ dialogVisible.value = false;
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError(
+ operationType.value === "add" ? "鏂板澶辫触" : "淇敼澶辫触"
+ );
+ });
+ }
+ if (operationType.value === "edit") {
+ productionPlanUpdate(payload)
+ .then(() => {
+ proxy.$modal.msgSuccess(
+ operationType.value === "add" ? "鏂板鎴愬姛" : "淇敼鎴愬姛"
+ );
+ dialogVisible.value = false;
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError(
+ operationType.value === "add" ? "鏂板澶辫触" : "淇敼澶辫触"
+ );
+ });
+ }
+ }
+ });
+ };
+
+ onMounted(() => {
+ getList();
+ });
+</script>
+
+<style scoped lang="scss">
+ .app-container {
+ padding: 24px;
+ background-color: #f0f2f5;
+ min-height: calc(100vh - 48px);
+ }
+
+ .search_form {
+ // margin-bottom: 24px;
+ padding: 20px;
+ background-color: #ffffff;
+ border-radius: 6px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+ transition: all 0.3s ease;
+
+ &:hover {
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
+ }
+ }
+
+ .search-header {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ // margin-bottom: 5px;
+ // padding-bottom: 5px;
+ position: relative;
+ bottom: 35px;
+ // border-bottom: 1px solid #ebeef5;
+ }
+
+ .search-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ }
+
+ .search-header .el-button {
+ color: #606266;
+ transition: all 0.3s ease;
+ }
+
+ .search-header .el-button:hover {
+ color: #409eff;
+ }
+
+ .search-header .el-icon {
+ margin-right: 4px;
+ }
+
+ .table_list {
+ // margin-bottom: 24px;
+ background-color: #ffffff;
+ border-radius: 6px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+ overflow: hidden;
+ height: calc(100vh - 250px);
+ margin-top: 0px !important;
+ }
+
+ :deep(.el-table) {
+ border: none;
+ border-radius: 6px;
+ overflow: hidden;
+ box-shadow: 0 4px 16px rgba(102, 126, 234, 0.1);
+
+ .el-table__header-wrapper {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+
+ th {
+ background: transparent;
+ font-weight: 600;
+ color: #ffffff;
+ border-bottom: none;
+ padding: 16px 0;
+ letter-spacing: 0.5px;
+ }
+ }
+
+ .el-table__body-wrapper {
+ tr {
+ transition: all 0.3s ease;
+
+ &:hover {
+ background: linear-gradient(
+ 90deg,
+ rgba(102, 126, 234, 0.05) 0%,
+ rgba(118, 75, 162, 0.05) 100%
+ );
+ transform: scale(1.002);
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
+ }
+
+ td {
+ border-bottom: 1px solid #f0f0f0;
+ padding: 14px 0;
+ color: #303133;
+ }
+ }
+
+ tr.current-row {
+ background: linear-gradient(
+ 90deg,
+ rgba(102, 126, 234, 0.08) 0%,
+ rgba(118, 75, 162, 0.08) 100%
+ );
+ }
+
+ // 鏁板�煎瓧娈垫牱寮�
+ .quantity-cell,
+ .volume-cell,
+ .dimension-cell {
+ font-weight: 600;
+ color: #409eff;
+ font-family: "Courier New", monospace;
+ text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
+ }
+
+ // 瑙勬牸瀛楁鏍峰紡
+ .spec-cell {
+ color: #67c23a;
+ font-weight: 500;
+
+ padding: 4px 8px;
+ border-radius: 4px;
+ }
+
+ // 缂栫爜瀛楁鏍峰紡
+ .code-cell {
+ color: #e6a23c;
+ font-family: "Courier New", monospace;
+ font-weight: 500;
+ padding: 4px 8px;
+ border-radius: 4px;
+ }
+
+ // 鏃ユ湡瀛楁鏍峰紡
+ .date-cell {
+ color: #909399;
+ font-style: italic;
+ }
+
+ // 鐘舵�佹爣绛炬牱寮�
+ .status-tag {
+ &.pending {
+ background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
+ color: #d63031;
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-weight: 500;
+ box-shadow: 0 2px 4px rgba(253, 203, 110, 0.3);
+ }
+
+ &.processing {
+ background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
+ color: #ffffff;
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-weight: 500;
+ box-shadow: 0 2px 4px rgba(9, 132, 227, 0.3);
+ }
+
+ &.completed {
+ background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);
+ color: #ffffff;
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-weight: 500;
+ box-shadow: 0 2px 4px rgba(0, 184, 148, 0.3);
+ }
+ }
+ }
+
+ .el-table__empty-block {
+ padding: 60px 0;
+ background-color: #fafafa;
+ }
+ }
+
+ // 鎿嶄綔鎸夐挳鏍峰紡
+ :deep(.el-table .cell .el-button--text) {
+ padding: 6px 10px;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+ font-weight: 500;
+
+ &:hover {
+ background-color: rgba(64, 158, 255, 0.1);
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
+ }
+
+ &:nth-of-type(1) {
+ color: #409eff;
+ background: linear-gradient(
+ 135deg,
+ rgba(64, 158, 255, 0.1) 0%,
+ rgba(64, 158, 255, 0.05) 100%
+ );
+ }
+
+ &:nth-of-type(2) {
+ color: #67c23a;
+ background: linear-gradient(
+ 135deg,
+ rgba(103, 194, 58, 0.1) 0%,
+ rgba(103, 194, 58, 0.05) 100%
+ );
+ }
+ }
+
+ // 淇℃伅灞曠ず鏍峰紡
+ .info-display {
+ border-radius: 6px;
+ color: #303133;
+ font-size: 14px;
+ min-height: 32px;
+ display: flex;
+ align-items: center;
+ }
+
+ .pagination-container {
+ display: flex;
+ justify-content: flex-end;
+ padding: 16px 20px;
+ background-color: #ffffff;
+ border-top: 1px solid #ebeef5;
+ border-radius: 0 0 12px 12px;
+ }
+
+ :deep(.el-button) {
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-1px);
+ }
+ }
+
+ :deep(.el-dialog) {
+ border-radius: 6px;
+ overflow: hidden;
+
+ .el-dialog__header {
+ background-color: #fafafa;
+ border-bottom: 1px solid #ebeef5;
+ padding: 20px 24px;
+
+ .el-dialog__title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ }
+ }
+
+ .el-dialog__body {
+ padding: 24px;
+ }
+
+ .el-dialog__footer {
+ padding: 16px 24px;
+ border-top: 1px solid #ebeef5;
+ background-color: #fafafa;
+ }
+ }
+
+ :deep(.el-form) {
+ .el-form-item {
+ margin-bottom: 20px;
+
+ .el-form-item__label {
+ font-weight: 500;
+ color: #303133;
+ }
+
+ .el-input,
+ .el-select,
+ .el-date-picker,
+ .el-input-number {
+ width: 100%;
+
+ // .el-input__inner {
+ // border-radius: 6px;
+ // border: 1px solid #dcdfe6;
+ // transition: all 0.3s ease;
+
+ // &:focus {
+ // border-color: #409eff;
+ // box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+ // }
+ // }
+ }
+ }
+ }
+
+ :deep(.el-tag) {
+ border-radius: 4px;
+ padding: 2px 8px;
+ font-size: 12px;
+ }
+
+ @media (max-width: 768px) {
+ .app-container {
+ padding: 16px;
+ }
+
+ .search_form {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
+
+ .el-form {
+ width: 100%;
+
+ .el-form-item {
+ width: 100%;
+ }
+ }
+
+ > div {
+ width: 100%;
+ display: flex;
+ gap: 12px;
+
+ .el-button {
+ flex: 1;
+ }
+ }
+ }
+
+ :deep(.el-table) {
+ th,
+ td {
+ padding: 10px 0;
+ font-size: 12px;
+ }
+ }
+
+ :deep(.el-dialog) {
+ width: 90% !important;
+ margin: 20px auto !important;
+ }
+ }
+ .consumption-value {
+ font-weight: bold;
+ color: #409eff;
+ }
+
+ .consumption-unit {
+ font-size: 12px;
+ color: #909399;
+ margin-left: 4px;
+ }
+ // .search_form {
+ // :deep(.el-form-item) {
+ // margin-bottom: 0px !important;
+ // }
+ // }
+ :deep(.el-table .el-table__body-wrapper tr td) {
+ background-color: #fff;
+ }
+</style>
--
Gitblit v1.9.3