| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" |
| | | ref="queryRef" |
| | | :inline="true"> |
| | | <!-- ç®åçæç´¢æ¡ä»¶ --> |
| | | <el-form-item label="主ç产计åå·:" |
| | | prop="mpsNo"> |
| | | <el-input v-model="searchForm.mpsNo" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 160px;" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="éå®ååå·:" |
| | | prop="salesContractNo"> |
| | | <el-input v-model="searchForm.salesContractNo" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 160px;" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="éæ±æ¥æèå´:" |
| | | prop="dateRange"> |
| | | <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="ä¸åç¶æ:" |
| | | prop="status"> |
| | | <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> |
| | | <!-- å±å¼çæç´¢æ¡ä»¶ --> |
| | | <el-form-item label="产ååç§°:" |
| | | prop="productName"> |
| | | <el-input v-model="searchForm.productName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 160px;" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼åå·:" |
| | | prop="model"> |
| | | <el-input v-model="searchForm.model" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 160px;" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <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="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="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 #qtyRequired="{ row }"> |
| | | {{ row.qtyRequired || '-' }}<span style="color:rgba(12, 46, 40, 0.76)"> {{ row.unit || 'æ¹' }}</span> |
| | | </template> |
| | | <template #salesContractNo="{ row }"> |
| | | <el-button type="primary" |
| | | text |
| | | link |
| | | @click="showDetail(row)">{{ row.salesContractNo }} |
| | | </el-button> |
| | | </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="产ååç§°"> |
| | | <el-tag class="info-display">{{ mergeForm.productName || '-' }}</el-tag> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col> |
| | | <el-form-item label="è§æ ¼åå·"> |
| | | <div class="info-display">{{ mergeForm.model || '-' }}</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="ç产æ°é"> |
| | | <el-input-number v-model="mergeForm.totalAssignedQuantity" |
| | | :min="0" |
| | | :max="sumAssignedQuantity" |
| | | @change="onBlur" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="å建æ¶é´"> |
| | | <el-date-picker v-model="mergeCreateTimeDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="handleMergeSubmit">ç¡®å®ä¸å</el-button> |
| | | <el-button @click="isShowNewModal = false">åæ¶</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="mpsNo"> |
| | | <el-input v-model="form.mpsNo" |
| | | disabled |
| | | placeholder="ä¿ååèªå¨çæ" /> |
| | | </el-form-item> |
| | | <el-form-item label="产ååç§°" |
| | | prop="productId"> |
| | | <el-tree-select v-model="form.productId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | filterable |
| | | @change="handleProductChange" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼åå·" |
| | | prop="productModelId"> |
| | | <el-select v-model="form.productModelId" |
| | | @change="handleChangeSpecification" |
| | | filterable |
| | | style="width: 100%" |
| | | 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="qtyRequired"> |
| | | <el-input-number v-model="form.qtyRequired" |
| | | :min="0" |
| | | style="width: 100%" |
| | | placeholder="请è¾å
¥æ°é" /> |
| | | </el-form-item> |
| | | <el-form-item label="åä½" |
| | | prop="unit"> |
| | | <el-input v-model="form.unit" |
| | | placeholder="请è¾å
¥åä½" /> |
| | | </el-form-item> |
| | | <el-form-item label="éæ±æ¥æ" |
| | | prop="requiredDate"> |
| | | <el-date-picker v-model="form.requiredDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | placeholder="è¯·éæ©éæ±æ¥æ" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¿è¯ºæ¥æ" |
| | | prop="promisedDeliveryDate"> |
| | | <el-date-picker v-model="form.promisedDeliveryDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | placeholder="è¯·éæ©æ¿è¯ºæ¥æ" /> |
| | | </el-form-item> |
| | | <el-form-item label="å建æ¶é´" |
| | | prop="createTime"> |
| | | <el-date-picker v-model="formCreateTimeDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | placeholder="è¯·éæ©å建æ¶é´" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | type="textarea" |
| | | placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="handleSubmit">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { |
| | | reactive, |
| | | ref, |
| | | onMounted, |
| | | toRefs, |
| | | getCurrentInstance, |
| | | computed, |
| | | } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import dayjs from "dayjs"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { ArrowUp, ArrowDown } from "@element-plus/icons-vue"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import { useDict } from "@/utils/dict"; |
| | | import { |
| | | productionPlanListPage, |
| | | productionPlanAdd, |
| | | productionPlanUpdate, |
| | | productionPlanDelete, |
| | | productionPlanCombine, |
| | | exportProductionPlan, |
| | | } from "@/api/productionPlan/productionPlan.js"; |
| | | import { productTreeList, modelListPage } from "@/api/basicData/product.js"; |
| | | import PIMTable from "./components/PIMTable.vue"; |
| | | import ImportDialog from "@/components/Dialog/ImportDialog.vue"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const router = useRouter(); |
| | | |
| | | const loadProdData = () => { |
| | | console.log("Mock loadProdData called"); |
| | | return Promise.resolve({ code: 200, msg: "忥æå" }); |
| | | }; |
| | | |
| | | // const productionPlanCombine = payload => { |
| | | // console.log("Mock productionPlanCombine called with:", payload); |
| | | // return Promise.resolve({ code: 200, msg: "åå¹¶ä¸åæå" }); |
| | | // }; |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "主ç产计åå·", |
| | | prop: "mpsNo", |
| | | width: "150px", |
| | | }, |
| | | { |
| | | label: "æ¥æº", |
| | | prop: "source", |
| | | width: "150px", |
| | | dataType: "tag", |
| | | formatType: params => { |
| | | return params == "éå®" ? "primary" : "info"; |
| | | }, |
| | | formatData: params => { |
| | | return params == "éå®" ? "éå®" : "å
é¨"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "éå®ååå·", |
| | | prop: "salesContractNo", |
| | | width: "200px", |
| | | dataType: "slot", |
| | | slot: "salesContractNo", |
| | | }, |
| | | |
| | | { |
| | | label: "产ååç§°", |
| | | prop: "productName", |
| | | width: "200px", |
| | | dataType: "tag", |
| | | formatType: params => { |
| | | return "primary"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "model", |
| | | width: "150px", |
| | | className: "spec-cell", |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | width: "100px", |
| | | }, |
| | | { |
| | | label: "æéæ°é", |
| | | prop: "qtyRequired", |
| | | width: "150px", |
| | | dataType: "slot", |
| | | slot: "qtyRequired", |
| | | className: "volume-cell", |
| | | }, |
| | | { |
| | | label: "ä¸åç¶æ", |
| | | prop: "status", |
| | | width: "120px", |
| | | 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: "quantityIssued", |
| | | width: "120px", |
| | | className: "spec-cell", |
| | | // formatData: (cell, row) => (cell ? `${cell}${row.unit || "æ¹"}` : 0), |
| | | }, |
| | | { |
| | | label: "éæ±æ¥æ", |
| | | prop: "requiredDate", |
| | | width: "160px", |
| | | className: "date-cell", |
| | | formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""), |
| | | }, |
| | | { |
| | | label: "æ¿è¯ºæ¥æ", |
| | | prop: "promisedDeliveryDate", |
| | | width: "160px", |
| | | className: "date-cell", |
| | | formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""), |
| | | }, |
| | | |
| | | { |
| | | label: "客æ·åç§°", |
| | | prop: "customerName", |
| | | width: "150px", |
| | | }, |
| | | { |
| | | label: "项ç®åç§°", |
| | | prop: "projectName", |
| | | width: "150px", |
| | | }, |
| | | { |
| | | label: "夿³¨", |
| | | width: "150px", |
| | | prop: "remark", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 250, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "primary", |
| | | link: true, |
| | | showHide: row => { |
| | | return row.status == 0 && row.source != "éå®"; |
| | | }, |
| | | clickFun: row => { |
| | | handleEdit(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ä¸å", |
| | | type: "text", |
| | | showHide: row => { |
| | | return row.status != 2; |
| | | }, |
| | | clickFun: row => { |
| | | mergeForm.productName = row.productName || ""; |
| | | mergeForm.model = row.model || ""; |
| | | mergeForm.totalAssignedQuantity = |
| | | Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0); |
| | | mergeForm.planCompleteTime = row.requiredDate || ""; |
| | | mergeForm.productId = row.productId || ""; |
| | | mergeForm.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | mergeForm.ids = [row.id]; |
| | | sumAssignedQuantity.value = |
| | | Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0); |
| | | isShowNewModal.value = true; |
| | | }, |
| | | }, |
| | | { |
| | | name: "å é¤", |
| | | type: "danger", |
| | | link: true, |
| | | showHide: row => { |
| | | return row.status == 0 && row.source != "éå®"; |
| | | }, |
| | | clickFun: row => { |
| | | handleDelete(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({ |
| | | productName: "", |
| | | model: "", |
| | | totalAssignedQuantity: 0, |
| | | planCompleteTime: "", |
| | | productId: "", |
| | | createTime: "", |
| | | }); |
| | | const mergeCreateTimeDate = computed({ |
| | | get: () => |
| | | mergeForm.createTime ? String(mergeForm.createTime).split(" ")[0] : "", |
| | | set: value => { |
| | | mergeForm.createTime = value |
| | | ? `${value} ${dayjs().format("HH:mm:ss")}` |
| | | : ""; |
| | | }, |
| | | }); |
| | | |
| | | // 导å
¥ç¸å
³ |
| | | 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 queryRef = ref(null); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | id: undefined, |
| | | mpsNo: "", |
| | | productId: undefined, |
| | | productModelId: undefined, |
| | | productName: "", |
| | | model: "", |
| | | qtyRequired: 0, |
| | | unit: "æ¹", |
| | | requiredDate: "", |
| | | promisedDeliveryDate: "", |
| | | remark: "", |
| | | createTime: "", |
| | | }); |
| | | const formCreateTimeDate = computed({ |
| | | get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""), |
| | | set: value => { |
| | | form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : ""; |
| | | }, |
| | | }); |
| | | const rules = reactive({ |
| | | productId: [{ required: true, message: "è¯·éæ©äº§å", trigger: "change" }], |
| | | productModelId: [ |
| | | { required: true, message: "è¯·éæ©è§æ ¼åå·", trigger: "change" }, |
| | | ], |
| | | qtyRequired: [{ required: true, message: "请è¾å
¥æ°é", trigger: "blur" }], |
| | | requiredDate: [ |
| | | { required: true, message: "è¯·éæ©éæ±æ¥æ", trigger: "change" }, |
| | | ], |
| | | }); |
| | | |
| | | // å¤ç追踪è¿åº¦æé®ç¹å» |
| | | const handleTrackProgress = row => { |
| | | // 跳转å°è¿½è¸ªè¿åº¦é¡µé¢ |
| | | router.push({ |
| | | path: "/productionPlan/trackProgress", |
| | | query: { |
| | | id: row.id, |
| | | productName: row.productName, |
| | | model: row.model, |
| | | }, |
| | | }); |
| | | }; |
| | | const onBlur = value => { |
| | | // éå¶åä½å°æ° |
| | | mergeForm.totalAssignedQuantity = Number(value.toFixed(4)); |
| | | }; |
| | | |
| | | const fetchProductOptions = () => { |
| | | return productTreeList().then(res => { |
| | | productOptions.value = convertIdToValue(res || []); |
| | | }); |
| | | }; |
| | | |
| | | const convertIdToValue = data => { |
| | | return data.map(item => { |
| | | const newItem = { |
| | | value: item.id, |
| | | label: item.label, |
| | | }; |
| | | if (item.children && item.children.length > 0) { |
| | | newItem.children = convertIdToValue(item.children); |
| | | } |
| | | return newItem; |
| | | }); |
| | | }; |
| | | |
| | | const handleProductChange = value => { |
| | | form.productModelId = undefined; |
| | | form.model = undefined; |
| | | // æ¥æ¾éä¸ç产ååç§° |
| | | const findProductName = (options, val) => { |
| | | for (const option of options) { |
| | | if (option.value === val) { |
| | | return option.label; |
| | | } |
| | | if (option.children) { |
| | | const found = findProductName(option.children, val); |
| | | if (found) { |
| | | return found; |
| | | } |
| | | } |
| | | } |
| | | return ""; |
| | | }; |
| | | form.productName = findProductName(productOptions.value, value); |
| | | fetchSpecificationOptions(value); |
| | | }; |
| | | |
| | | const fetchSpecificationOptions = productId => { |
| | | specificationOptions.value = []; |
| | | if (productId) { |
| | | modelListPage({ id: productId, size: 1000, current: 1 }).then(res => { |
| | | specificationOptions.value = res.records || []; |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const handleChangeSpecification = value => { |
| | | form.model = undefined; |
| | | form.unit = ""; |
| | | const selectedModel = specificationOptions.value.find( |
| | | item => item.id === value |
| | | ); |
| | | if (selectedModel) { |
| | | form.model = selectedModel.model; |
| | | form.unit = selectedModel.unit || "æ¹"; |
| | | } |
| | | }; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | mpsNo: "", |
| | | salesContractNo: "", |
| | | productName: "", |
| | | model: "", |
| | | status: "", |
| | | dateRange: [], |
| | | }, |
| | | searchFormExpanded: false, |
| | | }); |
| | | const { searchForm, searchFormExpanded } = toRefs(data); |
| | | |
| | | // 忢æç´¢è¡¨åå±å¼/æ¶èµ·ç¶æ |
| | | const toggleSearchForm = () => { |
| | | data.searchFormExpanded = !data.searchFormExpanded; |
| | | }; |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | /** éç½®æé®æä½ */ |
| | | const handleReset = () => { |
| | | if (proxy.resetForm) { |
| | | proxy.resetForm("queryRef"); |
| | | } |
| | | Object.assign(searchForm.value, { |
| | | mpsNo: "", |
| | | salesContractNo: "", |
| | | productName: "", |
| | | model: "", |
| | | status: "", |
| | | 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.productName || "æªç¥äº§å"; |
| | | if (!summary[category]) { |
| | | summary[category] = { |
| | | materialCode: category, |
| | | totalAssignedQuantity: 0, |
| | | }; |
| | | } |
| | | summary[category].totalAssignedQuantity += Number( |
| | | (Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0)).toFixed( |
| | | 4 |
| | | ) |
| | | ); |
| | | }); |
| | | |
| | | // 转æ¢ä¸ºæ°ç»æ ¼å¼ |
| | | categorySummary.value = Object.values(summary); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | // æé æç´¢åæ° |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.requiredDateStart = |
| | | params.dateRange && params.dateRange.length > 0 ? params.dateRange[0] : ""; |
| | | params.requiredDateEnd = |
| | | params.dateRange && params.dateRange.length > 1 ? 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; |
| | | }); |
| | | }; |
| | | |
| | | // éä¸çè§æ ¼åå·ID |
| | | const selectedProductModelId = ref(""); |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | // 妿æéä¸çè¡ï¼è®°å½ç¬¬ä¸ä¸ªéä¸è¡çè§æ ¼åå·ID |
| | | if (selection.length > 0) { |
| | | selectedProductModelId.value = selection[0].productModelId; |
| | | } else { |
| | | // å¦ææ²¡æéä¸çè¡ï¼æ¸
ç©ºè§æ ¼åå·ID |
| | | selectedProductModelId.value = ""; |
| | | } |
| | | }; |
| | | |
| | | // å¤æè¡æ¯å¦å¯éæ© |
| | | const isSelectable = row => { |
| | | // 妿æ¯å·²ä¸åç¶æï¼ç¦æ¢å¾é |
| | | if (row.status == 2) { |
| | | return false; |
| | | } |
| | | // 计ç®å©ä½æ°é |
| | | const remainingQty = (row.qtyRequired || 0) - (row.quantityIssued || 0); |
| | | // 妿å©ä½æ°éå°äºçäº0ï¼ç¦æ¢éæ© |
| | | if (remainingQty <= 0) { |
| | | return false; |
| | | } |
| | | // å¦ææ²¡æéä¸çè¡ï¼ææè¡é½å¯éæ© |
| | | if (!selectedProductModelId.value) { |
| | | return true; |
| | | } |
| | | // 妿æéä¸çè¡ï¼åªæè§æ ¼åå·IDç¸åçè¡æå¯éæ© |
| | | return row.productModelId === selectedProductModelId.value; |
| | | }; |
| | | // æåæ°æ®æé®æä½ |
| | | const loadProdDataLoading = ref(false); |
| | | const sumAssignedQuantity = ref(0); |
| | | |
| | | // å¤çåå¹¶ä¸åæé®ç¹å» |
| | | const handleMerge = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | ElMessage.warning("è¯·éæ©è¦åå¹¶ä¸åçç产计å"); |
| | | return; |
| | | } |
| | | console.log(selectedRows.value); |
| | | const firstRow = selectedRows.value[0]; |
| | | const productName = firstRow.productName || ""; |
| | | |
| | | // è®¡ç®æ»å¶é æ°é (é»è®¤qtyRequiredçå) |
| | | const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => { |
| | | return sum + Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0); |
| | | }, 0); |
| | | sumAssignedQuantity.value = totalAssignedQuantity; |
| | | console.log(totalAssignedQuantity); |
| | | // è®¾ç½®è¡¨åæ°æ® |
| | | mergeForm.productName = productName; |
| | | mergeForm.model = firstRow.model || ""; |
| | | mergeForm.totalAssignedQuantity = totalAssignedQuantity; |
| | | mergeForm.planCompleteTime = firstRow.requiredDate || ""; |
| | | mergeForm.productId = firstRow.productId || ""; |
| | | mergeForm.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | mergeForm.ids = selectedRows.value.map(row => row.id); |
| | | |
| | | // æå¼å¼¹çª |
| | | isShowNewModal.value = true; |
| | | }; |
| | | const showDetail = row => { |
| | | router.push({ |
| | | path: "/salesManagement/salesLedger", |
| | | query: { |
| | | salesContractNo: row.salesContractNo, |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // å¤çåå¹¶ä¸åæäº¤ |
| | | const handleMergeSubmit = () => { |
| | | if (mergeForm.totalAssignedQuantity === 0) { |
| | | ElMessage.warning("请è¾å
¥ç产æ°é"); |
| | | return; |
| | | } |
| | | console.log(sumAssignedQuantity.value, "sumAssignedQuantity"); |
| | | |
| | | // éªè¯totalAssignedQuantityä¸è½å¤§äºæ»æ¹æ° |
| | | if (mergeForm.totalAssignedQuantity > sumAssignedQuantity.value) { |
| | | ElMessage.error("ç产æ°éä¸è½å¤§äºå½å计ç®çæ»å¼"); |
| | | return; |
| | | } |
| | | |
| | | console.log(mergeForm, "mergeForm"); |
| | | const payload = { |
| | | ...mergeForm, |
| | | }; |
| | | 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, { |
| | | id: undefined, |
| | | mpsNo: "", |
| | | productName: "", |
| | | productId: undefined, |
| | | productModelId: undefined, |
| | | model: "", |
| | | qtyRequired: 0, |
| | | unit: "æ¹", |
| | | requiredDate: "", |
| | | promisedDeliveryDate: "", |
| | | remark: "", |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | dialogVisible.value = true; |
| | | fetchProductOptions(); |
| | | }; |
| | | |
| | | // ç¼è¾ |
| | | const handleEdit = row => { |
| | | operationType.value = "edit"; |
| | | Object.assign(form, { |
| | | id: row.id, |
| | | mpsNo: row.mpsNo || "", |
| | | productName: row.productName || "", |
| | | productId: row.productId || undefined, |
| | | productModelId: row.productModelId || undefined, |
| | | model: row.model || "", |
| | | qtyRequired: row.qtyRequired || 0, |
| | | unit: row.unit || "æ¹", |
| | | requiredDate: row.requiredDate || "", |
| | | promisedDeliveryDate: row.promisedDeliveryDate || "", |
| | | remark: row.remark || "", |
| | | createTime: row.createTime || "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | fetchProductOptions(); |
| | | fetchSpecificationOptions(row.productId); |
| | | }; |
| | | |
| | | // å é¤ |
| | | 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.qtyRequired === 0) { |
| | | proxy.$modal.msgError("æ°éä¸è½ä¸º0"); |
| | | return; |
| | | } |
| | | const payload = { ...form }; |
| | | if (operationType.value === "add") { |
| | | payload.id = null; |
| | | productionPlanAdd(payload) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("æ°å¢æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("æ°å¢å¤±è´¥"); |
| | | }); |
| | | } |
| | | if (operationType.value === "edit") { |
| | | productionPlanUpdate(payload) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("ä¿®æ¹å¤±è´¥"); |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | 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% |
| | | ); |
| | | } |
| | | |
| | | // æ°å¼åæ®µæ ·å¼ |
| | | .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; |
| | | width: 100%; |
| | | 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> |