gaoluyang
昨天 f0457608d7c8c32d3534d6fa1c8632bd38ce24b9
Merge remote-tracking branch 'origin/dev_New' into dev_新疆_大罗素马铃薯
已添加1个文件
已修改15个文件
793 ■■■■ 文件已修改
src/api/equipmentManagement/sparePartsUsage.js 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/feedbackRegistration/components/formDia.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/feedbackRegistration/index.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/index.vue 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/MaintainModal.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/spareParts/index.vue 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/transportTaskManagement/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/vehicleFuelManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/attendanceCheckin/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/index.vue 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentEntry/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/invoiceRegistration/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPayment/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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;
}