src/api/equipmentManagement/sparePartsUsage.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,36 @@ import request from "@/utils/request"; /** * å¤ä»¶é¢ç¨è®°å½ - å页æ¥è¯¢ * params: { current, size, sparePartId?, sparePartName?, source?, deviceId?, startTime?, endTime? } */ export const getSparePartsUsagePage = (params) => { return request({ url: "/sparePartsRequisitionRecord/listPage", method: "get", params, }); }; /** * å¤ä»¶é¢ç¨è®°å½ - æ°å¢ * data 示ä¾ï¼ * { * source: "repair" | "upkeep" | "manual", * sourceId?: number | string, * deviceId?: number | string, * deviceName?: string, * operatorId?: number | string, * operator?: string, * useTime?: string, // YYYY-MM-DD HH:mm:ss * items: [{ sparePartId: number|string, qty: number }] * } */ export const addSparePartsUsage = (data) => { return request({ url: "/sparePartsUsage/add", method: "post", data, }); }; src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -106,6 +106,11 @@ :column="tableColumn" :tableData="tableData" > <template #approveStatus="{ row }"> <el-tag :type="getApproveStatusType(row)" size="small"> {{ getApproveStatusText(row) }} </el-tag> </template> <template #shippingStatus="{ row }"> <el-tag :type="getShippingStatusType(row)" size="small"> {{ getShippingStatusText(row) }} @@ -219,9 +224,8 @@ prop: "approveStatus", width: 100, align: "center", dataType: "tag", formatData: (v) => (v === 1 ? "å è¶³" : "ä¸è¶³"), formatType: (v) => (v === 1 ? "success" : "danger"), dataType: "slot", slot: "approveStatus", }, { label: "åè´§ç¶æ", @@ -304,9 +308,15 @@ }) const customerNameChange = (val) => { form.value.salesContractNo = ""; form.value.salesLedgerId = null; tableData.value = []; associatedSalesOrderNumberOptions.value = []; const opt = customerNameOptions.value.find(item => item.value === val); if (opt) { form.value.customerId = opt.id; } else { form.value.customerId = null; } getSalesLedger({ customerName: form.value.customerName @@ -322,6 +332,22 @@ }) } const getApproveStatusText = (row) => { if (!row) return 'ä¸è¶³' if (row.approveStatus === 1 && (!row.shippingDate || !row.shippingCarNumber)) { return 'å è¶³' } if (row.approveStatus === 0 && (row.shippingDate || row.shippingCarNumber)) { return 'å·²åºåº' } return 'ä¸è¶³' } const getApproveStatusType = (row) => { const statusText = getApproveStatusText(row) return statusText === 'ä¸è¶³' ? 'danger' : 'success' } const getShippingStatusText = (row) => { if (!row) return 'å¾ åè´§' if (row.shippingDate || row.shippingCarNumber) { src/views/customerService/feedbackRegistration/index.vue
@@ -404,15 +404,19 @@ }); }; const getStatsCountByStatus = (list, status) => { if (!Array.isArray(list)) return 0; return list.find((item) => item?.status === status)?.count || 0; }; // è·åç»è®¡æ°æ®å¹¶å·æ°é¡¶é¨å¡ç const getSalesLedgerDetails = () => { getSalesLedgerDetail({}).then((res) => { if (res.code === 200) { statsList.value[0].count = res.data.filter((item) => item.status === 3)[0].count; statsList.value[1].count = res.data.filter((item) => item.status === 2)[0].count; statsList.value[2].count = res.data.filter((item) => item.status === 1)[0].count; // }); const statsData = Array.isArray(res.data) ? res.data : []; statsList.value[0].count = getStatsCountByStatus(statsData, 3); statsList.value[1].count = getStatsCountByStatus(statsData, 2); statsList.value[2].count = getStatsCountByStatus(statsData, 1); } }); } src/views/equipmentManagement/ledger/index.vue
@@ -42,6 +42,7 @@ <div></div> <div> <el-button type="primary" @click="add" icon="Plus"> æ°å¢ </el-button> <el-button type="info" @click="handleImport" icon="Upload">å¯¼å ¥</el-button> <el-button @click="handleOut" icon="download">导åº</el-button> <el-button type="danger" @@ -77,6 +78,37 @@ </div> </div> </el-dialog> <!-- å¯¼å ¥å¯¹è¯æ¡ --> <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body> <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag > <el-icon class="el-icon--upload"><upload-filled /></el-icon> <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> <template #tip> <div class="el-upload__tip text-center"> <span>ä» å è®¸å¯¼å ¥xlsãxlsxæ ¼å¼æä»¶ã</span> <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline; margin-left: 5px;" @click="importTemplate">ä¸è½½æ¨¡æ¿</el-link> </div> </template> </el-upload> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> <el-button @click="upload.open = false">å æ¶</el-button> </div> </template> </el-dialog> </div> </template> @@ -84,12 +116,13 @@ import { usePaginationApi } from "@/hooks/usePaginationApi"; // import { Search } from "@element-plus/icons-vue"; import { getLedgerPage, delLedger } from "@/api/equipmentManagement/ledger"; import { onMounted, getCurrentInstance } from "vue"; import { onMounted, getCurrentInstance, ref, reactive } from "vue"; import Modal from "./Modal.vue"; import { ElMessageBox, ElMessage } from "element-plus"; import { UploadFilled } from "@element-plus/icons-vue"; import { getToken } from "@/utils/auth"; import dayjs from "dayjs"; import QRCode from "qrcode"; import { ref } from "vue"; defineOptions({ name: "设å¤å°è´¦", @@ -102,6 +135,21 @@ const qrDialogVisible = ref(false); const qrCodeUrl = ref(""); const qrRowData = ref(null); // å¯¼å ¥ç¸å ³ const uploadRef = ref(null) const upload = reactive({ // æ¯å¦æ¾ç¤ºå¼¹åºå± open: false, // å¼¹åºå±æ é¢ title: "", // æ¯å¦ç¦ç¨ä¸ä¼ isUploading: false, // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import" }) const { filters, @@ -262,6 +310,36 @@ a.click(); }; // å¯¼å ¥æé®æä½ const handleImport = () => { upload.title = "设å¤å°è´¦å¯¼å ¥" upload.open = true } // ä¸è½½æ¨¡æ¿æä½ const importTemplate = () => { proxy.download("/device/ledger/downloadTemplate", {}, `设å¤å°è´¦å¯¼å ¥æ¨¡æ¿_${new Date().getTime()}.xlsx`) } // æä»¶ä¸ä¼ ä¸å¤ç const handleFileUploadProgress = (event, file, fileList) => { upload.isUploading = true } // æä»¶ä¸ä¼ æåå¤ç const handleFileSuccess = (response, file, fileList) => { upload.open = false upload.isUploading = false proxy.$refs["uploadRef"].handleRemove(file) proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "å¯¼å ¥ç»æ", { dangerouslyUseHTMLString: true }) getTableData() } // æäº¤ä¸ä¼ æä»¶ const submitFileForm = () => { proxy.$refs["uploadRef"].submit() } onMounted(() => { getTableData(); }); src/views/equipmentManagement/repair/Modal/MaintainModal.vue
@@ -32,23 +32,61 @@ style="width: 100%" /> </el-form-item> <el-form-item label="设å¤å¤ä»¶"> <el-select v-model="form.sparePartsIds" :loading="loadingSparePartOptions" placeholder="è¯·éæ©è®¾å¤å¤ä»¶" multiple filterable> <el-option v-for="item in sparePartOptions" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </el-form-item> <el-form-item v-if="selectedSpareParts.length" label="é¢ç¨æ°é"> <div style="width: 100%"> <div v-for="item in selectedSpareParts" :key="item.id" style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;" > <div style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> {{ item.name }} <span v-if="item.quantity !== null && item.quantity !== undefined" style="color: #909399;"> ï¼åºåï¼{{ item.quantity }}ï¼ </span> </div> <el-input-number v-model="sparePartQtyMap[item.id]" :min="1" :max="item.quantity !== null && item.quantity !== undefined ? Number(item.quantity) : undefined" :step="1" controls-position="right" style="width: 180px" /> </div> </div> </el-form-item> </el-form> </FormDialog> </template> <script setup> import { computed, getCurrentInstance, nextTick, ref } from "vue"; import FormDialog from "@/components/Dialog/FormDialog.vue"; import { addMaintain } from "@/api/equipmentManagement/repair"; import useFormData from "@/hooks/useFormData"; import useUserStore from "@/store/modules/user"; import dayjs from "dayjs"; import { ElMessage } from "element-plus"; import { getSparePartsList } from "@/api/equipmentManagement/spareParts"; defineOptions({ name: "ç»´ä¿®æ¨¡ææ¡", }); const emits = defineEmits(["ok"]); const { proxy } = getCurrentInstance(); // ä¿åæ¥ä¿®è®°å½çid const repairId = ref(); @@ -61,6 +99,16 @@ maintenanceResult: undefined, // ç»´ä¿®ç»æ maintenanceTime: undefined, // ç»´ä¿®æ¥æ status: 0, sparePartsIds: [], }); const sparePartOptions = ref([]) const loadingSparePartOptions = ref(true) const sparePartQtyMap = ref({}) const selectedSpareParts = computed(() => { const ids = Array.isArray(form.sparePartsIds) ? form.sparePartsIds : []; const set = new Set(ids.map((i) => String(i))); return (sparePartOptions.value || []).filter((p) => set.has(String(p.id))); }); const setForm = (data) => { @@ -71,16 +119,59 @@ ? dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss") : dayjs().format("YYYY-MM-DD HH:mm:ss"); form.status = 1; // é»è®¤ç¶æä¸ºå®ç» // multiple éæ©å¨è¦æ±æ°ç»ï¼å端常è¿å "1,2,3" if (Array.isArray(data?.sparePartsIds)) { form.sparePartsIds = data.sparePartsIds.map((v) => Number(v)).filter((v) => Number.isFinite(v)); } else if (typeof data?.sparePartsIds === "string") { form.sparePartsIds = data.sparePartsIds .split(",") .map((s) => Number(String(s).trim())) .filter((v) => Number.isFinite(v)); } else if (typeof data?.sparePartsIds === "number") { form.sparePartsIds = [data.sparePartsIds]; } else { form.sparePartsIds = []; } }; const sendForm = async () => { loading.value = true; try { const { code } = await addMaintain({ id: repairId.value, ...form }); // é¢ç¨æ°éæ ¡éª if (Array.isArray(form.sparePartsIds) && form.sparePartsIds.length > 0) { for (const partId of form.sparePartsIds) { const qty = Number(sparePartQtyMap.value?.[partId]); if (!Number.isFinite(qty) || qty <= 0) { proxy?.$modal?.msgError?.("请填åå¤ä»¶é¢ç¨æ°é"); return; } const part = sparePartOptions.value.find((p) => String(p.id) === String(partId)); const stock = part?.quantity; if (stock !== null && stock !== undefined && Number.isFinite(Number(stock))) { if (qty > Number(stock)) { proxy?.$modal?.msgError?.(`å¤ä»¶ã${part?.name || ""}ãé¢ç¨æ°éä¸è½è¶ è¿åºåï¼${stock}ï¼`); return; } } } } const data = { id: repairId.value, ...form, sparePartsIds: form.sparePartsIds ? form.sparePartsIds.join(",") : "", sparePartsQty: form.sparePartsIds ? form.sparePartsIds.map((id) => sparePartQtyMap.value?.[id] ?? 1).join(",") : "", sparePartsUseList: form.sparePartsIds ? form.sparePartsIds.map((id) => ({ id, quantity: sparePartQtyMap.value?.[id] ?? 1 })) : [], } const { code } = await addMaintain(data); if (code == 200) { ElMessage.success("ç»´ä¿®æå"); emits("ok"); resetForm(); sparePartQtyMap.value = {}; visible.value = false; } } finally { @@ -88,13 +179,34 @@ } }; const fetchSparePartOptions = () => { loadingSparePartOptions.value = true; // åå¤ä»¶ç®¡ç页ä¸è´ï¼/spareParts/listPage â res.data.records getSparePartsList({ current: 1, size: 1000 }) .then((res) => { if (res.code === 200) { sparePartOptions.value = res?.data?.records || []; } else { sparePartOptions.value = []; } }) .catch(() => { sparePartOptions.value = []; }) .finally(() => { loadingSparePartOptions.value = false; }); } const handleCancel = () => { resetForm(); sparePartQtyMap.value = {}; visible.value = false; }; const handleClose = () => { resetForm(); sparePartQtyMap.value = {}; visible.value = false; }; @@ -103,6 +215,7 @@ visible.value = true; await nextTick(); setForm(row); fetchSparePartOptions() }; defineExpose({ src/views/equipmentManagement/spareParts/index.vue
@@ -1,107 +1,144 @@ <template> <div class="spare-part-category"> <div class="search_form"> <el-form :inline="true" :model="queryParams" class="search-form"> <el-form-item label="å¤ä»¶åç§°"> <el-input v-model="queryParams.name" placeholder="请è¾å ¥å¤ä»¶åç§°" clearable style="width: 240px" /> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">æ¥è¯¢</el-button> <el-button @click="resetQuery">éç½®</el-button> </el-form-item> </el-form> <div> <el-button type="primary" @click="addCategory" >æ°å¢</el-button> </div> </div> <el-tabs v-model="activeTab" @tab-change="handleTabChange"> <el-tab-pane label="å¤ä»¶å表" name="list"> <div class="search_form"> <el-form :inline="true" :model="queryParams" class="search-form"> <el-form-item label="å¤ä»¶åç§°"> <el-input v-model="queryParams.name" placeholder="请è¾å ¥å¤ä»¶åç§°" clearable style="width: 240px" /> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">æ¥è¯¢</el-button> <el-button @click="resetQuery">éç½®</el-button> </el-form-item> </el-form> <div> <el-button type="primary" @click="addCategory">æ°å¢</el-button> </div> </div> <PIMTable rowKey="id" :column="columns" :tableData="renderTableData" :tableLoading="loading" :page="pagination" :isShowPagination="true" @pagination="handleSizeChange" > <template #status="{ row }"> <el-tag type="success" size="small">{{ row.status }}</el-tag> </template> </PIMTable> <el-dialog title="å类管ç" v-model="dialogVisible" width="60%"> <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> <el-form-item label="设å¤" prop="deviceLedgerIds"> <el-select v-model="form.deviceLedgerIds" placeholder="è¯·éæ©è®¾å¤" filterable default-first-option :reserve-keyword="false" multiple style="width: 100%" > <el-option v-for="(item, index) in deviceOptions" :key="index" :label="item.deviceName" :value="item.id" ></el-option> </el-select> </el-form-item> <el-form-item label="å¤ä»¶åç§°" prop="name"> <el-input v-model="form.name"></el-input> </el-form-item> <el-form-item label="å¤ä»¶ç¼å·" prop="sparePartsNo"> <el-input v-model="form.sparePartsNo"></el-input> </el-form-item> <el-form-item label="æ°é" prop="quantity"> <el-input type="number" v-model="form.quantity"></el-input> </el-form-item> <el-form-item label="ç¶æ" prop="status"> <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ"> <el-option label="æ£å¸¸" value="æ£å¸¸"></el-option> <el-option label="ç¦ç¨" value="ç¦ç¨"></el-option> </el-select> </el-form-item> <el-form-item label="æè¿°" prop="description"> <el-input v-model="form.description"></el-input> </el-form-item> <el-form-item label="ä»·æ ¼" prop="price"> <el-input-number v-model="form.price" placeholder="请è¾å ¥ä»·æ ¼" :min="0" :step="0.01" :precision="2" style="width: 100%" ></el-input-number> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false" :disabled="formLoading">åæ¶</el-button> <el-button type="primary" @click="submitForm" :loading="formLoading">ç¡®å®</el-button> </span> </template> </el-dialog> <PIMTable rowKey="id" :column="columns" :tableData="renderTableData" :tableLoading="loading" :page="pagination" :isShowPagination="true" @pagination="handleSizeChange" > <template #status="{ row }"> <el-tag type="success" size="small">{{ row.status }}</el-tag> </template> </PIMTable> <el-dialog title="å类管ç" v-model="dialogVisible" width="60%"> <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> <el-form-item label="设å¤" prop="deviceLedgerIds"> <el-select v-model="form.deviceLedgerIds" placeholder="è¯·éæ©è®¾å¤" filterable default-first-option :reserve-keyword="false" multiple style="width: 100%" > <el-option v-for="(item, index) in deviceOptions" :key="index" :label="item.deviceName" :value="item.id" ></el-option> </el-select> </el-form-item> <el-form-item label="å¤ä»¶åç§°" prop="name"> <el-input v-model="form.name"></el-input> </el-form-item> <el-form-item label="å¤ä»¶ç¼å·" prop="sparePartsNo"> <el-input v-model="form.sparePartsNo"></el-input> </el-form-item> <el-form-item label="æ°é" prop="quantity"> <el-input type="number" v-model="form.quantity"></el-input> </el-form-item> <el-form-item label="ç¶æ" prop="status"> <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ"> <el-option label="æ£å¸¸" value="æ£å¸¸"></el-option> <el-option label="ç¦ç¨" value="ç¦ç¨"></el-option> </el-select> </el-form-item> <el-form-item label="æè¿°" prop="description"> <el-input v-model="form.description"></el-input> </el-form-item> <el-form-item label="ä»·æ ¼" prop="price"> <el-input-number v-model="form.price" placeholder="请è¾å ¥ä»·æ ¼" :min="0" :step="0.01" :precision="2" style="width: 100%" ></el-input-number> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false" :disabled="formLoading">åæ¶</el-button> <el-button type="primary" @click="submitForm" :loading="formLoading">ç¡®å®</el-button> </span> </template> </el-dialog> </el-tab-pane> <el-tab-pane label="å¤ä»¶é¢ç¨è®°å½" name="usage"> <div class="search_form"> <el-form :inline="true" :model="usageQuery" class="search-form"> <el-form-item label="å¤ä»¶åç§°"> <el-input v-model="usageQuery.sparePartsName" placeholder="请è¾å ¥å¤ä»¶åç§°" clearable style="width: 240px" /> </el-form-item> <el-form-item label="æ¥æº"> <el-select v-model="usageQuery.sourceType" placeholder="è¯·éæ©" clearable style="width: 200px"> <el-option label="ç»´ä¿®" :value="0" /> <el-option label="ä¿å »" :value="1" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="handleUsageQuery">æ¥è¯¢</el-button> <el-button @click="resetUsageQuery">éç½®</el-button> </el-form-item> </el-form> </div> <PIMTable rowKey="rowKey" :column="usageColumns" :tableData="usageTableData" :tableLoading="usageLoading" :page="usagePagination" :isShowPagination="true" @pagination="handleUsagePageChange" /> </el-tab-pane> </el-tabs> </div> </template> <script setup> import { ref, computed, onMounted, reactive, watch } from 'vue'; import { ref, computed, onMounted, reactive } from 'vue'; import { ElMessage, ElMessageBox } from 'element-plus'; import { getSparePartsList, addSparePart, editSparePart, delSparePart } from "@/api/equipmentManagement/spareParts"; import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; import PIMTable from "@/components/PIMTable/PIMTable.vue"; import { getSparePartsUsagePage } from "@/api/equipmentManagement/sparePartsUsage"; // å è½½ç¶æ const loading = ref(false); const formLoading = ref(false); const activeTab = ref("list"); // å¯¹è¯æ¡æ¾ç¤ºç¶æ const dialogVisible = ref(false); // ç¼è¾ ID @@ -126,6 +163,35 @@ size: 10, total: 0 }); // å¤ä»¶é¢ç¨è®°å½ const usageLoading = ref(false); const usageQuery = reactive({ sparePartsName: "", sourceType: "", }); const usagePagination = reactive({ current: 1, size: 10, total: 0, }); const usageTableData = ref([]); const usageColumns = ref([ { label: "æ¥æº", prop: "sourceText" }, { label: "åæ®/è®°å½ID", prop: "sourceId" }, { label: "设å¤åç§°", prop: "deviceName" }, { label: "å¤ä»¶åç§°", prop: "sparePartsName" }, { label: "é¢ç¨æ°é", prop: "quantity" }, { label: "æä½äºº", prop: "operator" }, { label: "æ¶é´", prop: "createTime" }, ]); const handleTabChange = async (name) => { if (name === "usage") { usagePagination.current = 1; await fetchUsageData(); } }; const columns = ref([ { label: "设å¤åç§°", @@ -268,6 +334,48 @@ } } const fetchUsageData = async () => { usageLoading.value = true; try { const res = await getSparePartsUsagePage({ current: usagePagination.current, size: usagePagination.size, sparePartsName: usageQuery.sparePartsName || undefined, sourceType: usageQuery.sourceType || undefined, }); if (res?.code === 200) { const records = res?.data?.records || []; usagePagination.total = res?.data?.total || 0; usageTableData.value = records.map((r, idx) => ({ rowKey: r.id ?? `${usagePagination.current}-${idx}`, ...r, sourceText: r.sourceText === "" ? "-" : r.sourceText, })); } else { usagePagination.total = 0; usageTableData.value = []; } } finally { usageLoading.value = false; } }; const handleUsageQuery = () => { usagePagination.current = 1; fetchUsageData(); }; const resetUsageQuery = () => { usageQuery.sparePartsName = ""; usageQuery.sourceType = ""; usagePagination.current = 1; fetchUsageData(); }; const handleUsagePageChange = (obj) => { usagePagination.current = obj.page; usagePagination.size = obj.limit; fetchUsageData(); }; // æ¥è¯¢ const handleQuery = () => { pagination.current = 1; src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
@@ -38,6 +38,41 @@ placeholder="请è¾å ¥ä¿å »ç»æ" type="text" /> </el-form-item> <el-form-item label="设å¤å¤ä»¶"> <el-select v-model="form.sparePartsIds" :loading="loadingSparePartOptions" placeholder="è¯·éæ©è®¾å¤å¤ä»¶" multiple filterable> <el-option v-for="item in sparePartOptions" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </el-form-item> <el-form-item v-if="selectedSpareParts.length" label="é¢ç¨æ°é"> <div style="width: 100%"> <div v-for="item in selectedSpareParts" :key="item.id" style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;" > <div style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> {{ item.name }} <span v-if="item.quantity !== null && item.quantity !== undefined" style="color: #909399;"> ï¼åºåï¼{{ item.quantity }}ï¼ </span> </div> <el-input-number v-model="sparePartQtyMap[item.id]" :min="1" :max="item.quantity !== null && item.quantity !== undefined ? Number(item.quantity) : undefined" :step="1" controls-position="right" style="width: 180px" /> </div> </div> </el-form-item> </el-form> </FormDialog> </template> @@ -49,6 +84,8 @@ import dayjs from "dayjs"; import useUserStore from "@/store/modules/user"; import { ElMessage } from "element-plus"; import {computed, ref} from "vue"; import {getSparePartsList} from "@/api/equipmentManagement/spareParts.js"; defineOptions({ name: "ä¿å »æ¨¡ææ¡", @@ -67,6 +104,17 @@ maintenanceActuallyTime: undefined, // å®é ä¿å »æ¥æ maintenanceResult: undefined, // ä¿å »ç»æ status: 0, // ä¿å »ç¶æ sparePartsIds: [], }); const sparePartOptions = ref([]) const loadingSparePartOptions = ref(true) const sparePartQtyMap = ref({}) const selectedSpareParts = computed(() => { const ids = Array.isArray(form.sparePartsIds) ? form.sparePartsIds : []; const set = new Set(ids.map((i) => String(i))); return (sparePartOptions.value || []).filter((p) => set.has(String(p.id))); }); const setForm = (data) => { @@ -78,6 +126,19 @@ : dayjs().format("YYYY-MM-DD HH:mm:ss"); form.maintenanceResult = data.maintenanceResult; form.status = 1; // é»è®¤ç¶æä¸ºå®ç» // multiple éæ©å¨è¦æ±æ°ç»ï¼å端常è¿å "1,2,3" if (Array.isArray(data?.sparePartsIds)) { form.sparePartsIds = data.sparePartsIds.map((v) => Number(v)).filter((v) => Number.isFinite(v)); } else if (typeof data?.sparePartsIds === "string") { form.sparePartsIds = data.sparePartsIds .split(",") .map((s) => Number(String(s).trim())) .filter((v) => Number.isFinite(v)); } else if (typeof data?.sparePartsIds === "number") { form.sparePartsIds = [data.sparePartsIds]; } else { form.sparePartsIds = []; } }; /** @@ -86,11 +147,41 @@ const sendForm = async () => { loading.value = true; try { const { code } = await addMaintenance({ id: planId.value, ...form }); // é¢ç¨æ°éæ ¡éª if (Array.isArray(form.sparePartsIds) && form.sparePartsIds.length > 0) { for (const partId of form.sparePartsIds) { const qty = Number(sparePartQtyMap.value?.[partId]); if (!Number.isFinite(qty) || qty <= 0) { proxy?.$modal?.msgError?.("请填åå¤ä»¶é¢ç¨æ°é"); return; } const part = sparePartOptions.value.find((p) => String(p.id) === String(partId)); const stock = part?.quantity; if (stock !== null && stock !== undefined && Number.isFinite(Number(stock))) { if (qty > Number(stock)) { proxy?.$modal?.msgError?.(`å¤ä»¶ã${part?.name || ""}ãé¢ç¨æ°éä¸è½è¶ è¿åºåï¼${stock}ï¼`); return; } } } } const data = { id: planId.value, ...form, sparePartsIds: form.sparePartsIds ? form.sparePartsIds.join(",") : "", sparePartsQty: form.sparePartsIds ? form.sparePartsIds.map((id) => sparePartQtyMap.value?.[id] ?? 1).join(",") : "", sparePartsUseList: form.sparePartsIds ? form.sparePartsIds.map((id) => ({ id, quantity: sparePartQtyMap.value?.[id] ?? 1 })) : [], } const { code } = await addMaintenance(data); if (code == 200) { ElMessage.success("ä¿å »æå"); emits("ok"); resetForm(); sparePartQtyMap.value = {}; visible.value = false; } } finally { @@ -98,13 +189,34 @@ } }; const fetchSparePartOptions = () => { loadingSparePartOptions.value = true; // åå¤ä»¶ç®¡ç页ä¸è´ï¼/spareParts/listPage â res.data.records getSparePartsList({ current: 1, size: 1000 }) .then((res) => { if (res.code === 200) { sparePartOptions.value = res?.data?.records || []; } else { sparePartOptions.value = []; } }) .catch(() => { sparePartOptions.value = []; }) .finally(() => { loadingSparePartOptions.value = false; }); } const handleCancel = () => { resetForm(); sparePartQtyMap.value = {}; visible.value = false; }; const handleClose = () => { resetForm(); sparePartQtyMap.value = {}; visible.value = false; }; @@ -112,6 +224,7 @@ planId.value = id; // ä¿å计åä¿å »è®°å½çid visible.value = true; await nextTick(); fetchSparePartOptions() setForm(row); }; src/views/inventoryManagement/transportTaskManagement/index.vue
@@ -681,11 +681,11 @@ text-align: right; } ::v-deep(.row-finished) { :deep(.row-finished) { background-color: #f6ffed; } ::v-deep(.row-running) { :deep(.row-running) { background-color: #fffbe6; } </style> src/views/inventoryManagement/vehicleFuelManagement/index.vue
@@ -549,7 +549,7 @@ text-align: right; } ::v-deep(.row-abnormal) { :deep(.row-abnormal) { background-color: #fff5f5; } </style> src/views/personnelManagement/attendanceCheckin/index.vue
@@ -497,7 +497,7 @@ color: #333; } ::v-deep(.row-abnormal) { :deep(.row-abnormal) { background-color: #fff5f5; } src/views/personnelManagement/employeeRecord/index.vue
@@ -36,6 +36,7 @@ </div> <div> <el-button type="primary" @click="openFormNewOrEditFormDia('add')">æ°å¢å ¥è</el-button> <el-button type="info" @click="handleImport">å¯¼å ¥</el-button> <el-button @click="handleOut">导åº</el-button> <!-- <el-button type="danger" plain @click="handleDelete">å é¤</el-button> --> </div> @@ -61,15 +62,47 @@ :id="id" @completed="handleQuery" /> <!-- å¯¼å ¥å¯¹è¯æ¡ --> <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body> <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag > <el-icon class="el-icon--upload"><upload-filled /></el-icon> <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> <template #tip> <div class="el-upload__tip text-center"> <span>ä» å è®¸å¯¼å ¥xlsãxlsxæ ¼å¼æä»¶ã</span> <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline; margin-left: 5px;" @click="importTemplate">ä¸è½½æ¨¡æ¿</el-link> </div> </template> </el-upload> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> <el-button @click="upload.open = false">å æ¶</el-button> </div> </template> </el-dialog> </div> </template> <script setup> import { Search } from "@element-plus/icons-vue"; import { Search, UploadFilled } from "@element-plus/icons-vue"; import {onMounted, ref} from "vue"; import {ElMessageBox} from "element-plus"; import { deptTreeSelect } from "@/api/system/user.js"; import {batchDeleteStaffOnJobs, staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js"; import { getToken } from "@/utils/auth"; import dayjs from "dayjs"; const NewOrEditFormDia = defineAsyncComponent(() => import("@/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue")); @@ -206,6 +239,21 @@ const formDiaNewOrEditFormDia = ref() const { proxy } = getCurrentInstance() // å¯¼å ¥ç¸å ³ const uploadRef = ref(null) const upload = reactive({ // æ¯å¦æ¾ç¤ºå¼¹åºå± open: false, // å¼¹åºå±æ é¢ title: "", // æ¯å¦ç¦ç¨ä¸ä¼ isUploading: false, // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/staff/staffOnJob/import" }) const fetchDeptOptions = () => { deptTreeSelect().then(response => { console.log(response.data) @@ -314,6 +362,37 @@ proxy.$modal.msg("已忶"); }); }; // å¯¼å ¥æé®æä½ const handleImport = () => { upload.title = "åå·¥å¯¼å ¥" upload.open = true } // ä¸è½½æ¨¡æ¿æä½ const importTemplate = () => { proxy.download("/staff/staffOnJob/downloadTemplate", {}, `åå·¥å¯¼å ¥æ¨¡æ¿_${new Date().getTime()}.xlsx`) } // æä»¶ä¸ä¼ ä¸å¤ç const handleFileUploadProgress = (event, file, fileList) => { upload.isUploading = true } // æä»¶ä¸ä¼ æåå¤ç const handleFileSuccess = (response, file, fileList) => { upload.open = false upload.isUploading = false proxy.$refs["uploadRef"].handleRemove(file) proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "å¯¼å ¥ç»æ", { dangerouslyUseHTMLString: true }) getList() } // æäº¤ä¸ä¼ æä»¶ const submitFileForm = () => { proxy.$refs["uploadRef"].submit() } onMounted(() => { getList(); }); src/views/procurementManagement/paymentEntry/index.vue
@@ -569,7 +569,7 @@ .table_list { margin-top: unset; } ::v-deep(.el-checkbox__label) { :deep(.el-checkbox__label) { font-weight: bold; } .empty-tip { src/views/productionManagement/productionOrder/index.vue
@@ -455,19 +455,19 @@ align-items: start; } ::v-deep .yellow { :deep(.yellow) { background-color: #FAF0DE; } ::v-deep .pink { :deep(.pink) { background-color: #FAE1DE; } ::v-deep .red { :deep(.red) { background-color: #f80202; } ::v-deep .purple{ :deep(.purple){ background-color: #F4DEFA; } </style> src/views/salesManagement/invoiceRegistration/index.vue
@@ -803,7 +803,7 @@ .justify-between { justify-content: space-between; } ::v-deep(.el-checkbox__label) { :deep(.el-checkbox__label) { font-weight: bold; } </style> src/views/salesManagement/receiptPayment/index.vue
@@ -589,7 +589,7 @@ .table_list { margin-top: unset; } ::v-deep(.el-checkbox__label) { :deep(.el-checkbox__label) { font-weight: bold; } .actions { src/views/salesManagement/salesLedger/index.vue
@@ -2192,19 +2192,19 @@ margin-left: 10px; } ::v-deep .yellow { :deep(.yellow) { background-color: #FAF0DE; } ::v-deep .pink { :deep(.pink) { background-color: #FAE1DE; } ::v-deep .red { :deep(.red) { background-color: #FAE1DE; } ::v-deep .purple{ :deep(.purple){ background-color: #F4DEFA; }