From f0457608d7c8c32d3534d6fa1c8632bd38ce24b9 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 07 四月 2026 10:28:21 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_New' into dev_新疆_大罗素马铃薯
---
src/views/customerService/feedbackRegistration/components/formDia.vue | 32 ++
src/views/equipmentManagement/spareParts/index.vue | 288 ++++++++++++++------
src/views/salesManagement/receiptPayment/index.vue | 2
src/views/equipmentManagement/repair/Modal/MaintainModal.vue | 115 ++++++++
src/views/personnelManagement/employeeRecord/index.vue | 81 +++++
src/views/personnelManagement/attendanceCheckin/index.vue | 2
src/views/procurementManagement/paymentEntry/index.vue | 2
src/views/salesManagement/salesLedger/index.vue | 8
src/views/salesManagement/invoiceRegistration/index.vue | 2
src/api/equipmentManagement/sparePartsUsage.js | 36 ++
src/views/inventoryManagement/vehicleFuelManagement/index.vue | 2
src/views/customerService/feedbackRegistration/index.vue | 14
src/views/productionManagement/productionOrder/index.vue | 8
src/views/inventoryManagement/transportTaskManagement/index.vue | 4
src/views/equipmentManagement/ledger/index.vue | 82 +++++
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue | 115 ++++++++
16 files changed, 675 insertions(+), 118 deletions(-)
diff --git a/src/api/equipmentManagement/sparePartsUsage.js b/src/api/equipmentManagement/sparePartsUsage.js
new file mode 100644
index 0000000..e9384aa
--- /dev/null
+++ b/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,
+ });
+};
+
diff --git a/src/views/customerService/feedbackRegistration/components/formDia.vue b/src/views/customerService/feedbackRegistration/components/formDia.vue
index 8f9bb91..93e5c6b 100644
--- a/src/views/customerService/feedbackRegistration/components/formDia.vue
+++ b/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) {
diff --git a/src/views/customerService/feedbackRegistration/index.vue b/src/views/customerService/feedbackRegistration/index.vue
index 3a2d362..e307dda 100644
--- a/src/views/customerService/feedbackRegistration/index.vue
+++ b/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);
}
});
}
diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index 62f0c6a..5555182 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/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>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</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();
});
diff --git a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue b/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
index 496b072..b0b09f0 100644
--- a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
+++ b/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();
// 淇濆瓨鎶ヤ慨璁板綍鐨刬d
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({
diff --git a/src/views/equipmentManagement/spareParts/index.vue b/src/views/equipmentManagement/spareParts/index.vue
index 4a48d28..06ca37d 100644
--- a/src/views/equipmentManagement/spareParts/index.vue
+++ b/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="姝e父" value="姝e父"></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="姝e父" value="姝e父"></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;
diff --git a/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue b/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
index c660840..e86b64a 100644
--- a/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
+++ b/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; // 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
visible.value = true;
await nextTick();
+ fetchSparePartOptions()
setForm(row);
};
diff --git a/src/views/inventoryManagement/transportTaskManagement/index.vue b/src/views/inventoryManagement/transportTaskManagement/index.vue
index 1feb54b..8e73004 100644
--- a/src/views/inventoryManagement/transportTaskManagement/index.vue
+++ b/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>
diff --git a/src/views/inventoryManagement/vehicleFuelManagement/index.vue b/src/views/inventoryManagement/vehicleFuelManagement/index.vue
index 8579cba..eaf543c 100644
--- a/src/views/inventoryManagement/vehicleFuelManagement/index.vue
+++ b/src/views/inventoryManagement/vehicleFuelManagement/index.vue
@@ -549,7 +549,7 @@
text-align: right;
}
-::v-deep(.row-abnormal) {
+:deep(.row-abnormal) {
background-color: #fff5f5;
}
</style>
diff --git a/src/views/personnelManagement/attendanceCheckin/index.vue b/src/views/personnelManagement/attendanceCheckin/index.vue
index b7b0f92..6e4a3ea 100644
--- a/src/views/personnelManagement/attendanceCheckin/index.vue
+++ b/src/views/personnelManagement/attendanceCheckin/index.vue
@@ -497,7 +497,7 @@
color: #333;
}
- ::v-deep(.row-abnormal) {
+ :deep(.row-abnormal) {
background-color: #fff5f5;
}
diff --git a/src/views/personnelManagement/employeeRecord/index.vue b/src/views/personnelManagement/employeeRecord/index.vue
index 16445de..9249a6b 100644
--- a/src/views/personnelManagement/employeeRecord/index.vue
+++ b/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>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</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();
});
diff --git a/src/views/procurementManagement/paymentEntry/index.vue b/src/views/procurementManagement/paymentEntry/index.vue
index cb93562..73c5a1a 100644
--- a/src/views/procurementManagement/paymentEntry/index.vue
+++ b/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 {
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index fc64063..260b2c3 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/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>
diff --git a/src/views/salesManagement/invoiceRegistration/index.vue b/src/views/salesManagement/invoiceRegistration/index.vue
index 2f6e60c..44e1c4e 100644
--- a/src/views/salesManagement/invoiceRegistration/index.vue
+++ b/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>
diff --git a/src/views/salesManagement/receiptPayment/index.vue b/src/views/salesManagement/receiptPayment/index.vue
index b56abd6..25bd280 100644
--- a/src/views/salesManagement/receiptPayment/index.vue
+++ b/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 {
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index ca7ab04..16fde49 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/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;
}
--
Gitblit v1.9.3