spring
昨天 fa4ea8dd99638f58da60ac283186113619cc7e6e
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-management into dev_New
已添加10个文件
已修改11个文件
已删除2个文件
5164 ■■■■ 文件已修改
src/api/basicData/enum.js 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockInRecord.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockInventory.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockOut.js 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockUninventory.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ProductSelectDialog.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/filesDia.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/index.vue 190 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/index.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/Record.vue 711 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/index.vue 884 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/Record.vue 251 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDia.vue 400 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/index.vue 548 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/New.vue 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Qualified.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Subtract.vue 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Unqualified.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/index.vue 428 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 467 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/enum.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
import request from "@/utils/request.js";
export function findAllStockRecordTypeOptions() {
    return request({
        url: '/basic/enum/stockRecordType',
        method: 'get'
    })
}
export function findAllQualifiedStockRecordTypeOptions() {
    return request({
        url: '/basic/enum/StockQualifiedRecordTypeEnum',
        method: 'get'
    })
}
export function findAllUnqualifiedStockRecordTypeOptions() {
    return request({
        url: '/basic/enum/StockUnQualifiedRecordTypeEnum',
        method: 'get'
    })
}
src/api/inventoryManagement/stockInRecord.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
import request from "@/utils/request";
// æŸ¥è¯¢å…¥åº“信息列表
export const getStockInRecordListPage = (params) => {
    return request({
        url: "/stockInRecord/listPage",
        method: "get",
        params,
    });
};
export const updateStockInRecord = (id, data) => {
    return request({
        url: "/stockInRecord/" + id,
        method: "put",
        data: data,
    });
};
export const batchDeleteStockInRecords = (ids) => {
    return request({
        url: "/stockInRecord",
        method: "delete",
        data: ids,
    });
};
src/api/inventoryManagement/stockInventory.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
import request from "@/utils/request.js";
// åˆ†é¡µæŸ¥è¯¢åº“存记录列表
export const getStockInventoryListPage = (params) => {
    return request({
        url: "/stockInventory/pagestockInventory",
        method: "get",
        params,
    });
};
// åˆ›å»ºåº“存记录
export const createStockInventory = (params) => {
    return request({
        url: "/stockInventory/addstockInventory",
        method: "post",
        data: params,
    });
};
// å‡å°‘库存记录
export const subtractStockInventory = (params) => {
    return request({
        url: "/stockInventory/subtractStockInventory",
        method: "post",
        data: params,
    });
};
export const exportStockInventory = (params) => {
    return request({
        url: "/stockInventory/exportStockInventory",
        method: "post",
        data: params,
    });
};
src/api/inventoryManagement/stockOut.js
@@ -3,44 +3,17 @@
// å‡ºåº“台账-采购出库查询出库列表
export const getStockOutPage = (params) => {
    return request({
        url: "/stockmanagement/listPage",
        url: "/stockOutRecord/listPage",
        method: "get",
        params,
    });
};
// å‡ºåº“台账-生产出库查询出库列表
export const getStockOutSemiProductPage = (params) => {
    return request({
        url: "/stockmanagement/listPageBySemiProduct",
        method: "get",
        params,
    });
};
//新增出库信息
export const addStockOut = (data) => {
    return request({
        url: '/stockout/add',
        method: 'post',
        data: data
    })
}
//修改出库信息
export const updateStockOut = (data) => {
    return request({
        url: "/stockout/update",
        method: "put",
        data,
    });
}
//删除出库信息
export const delStockOut = (ids) => {
    return request({
        url: '/stockmanagement/del',
        method: 'post',
        data: ids
    })
        url: "/stockOutRecord",
        method: "delete",
        data: ids,
    });
}
src/api/inventoryManagement/stockUninventory.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
import request from "@/utils/request.js";
// åˆ†é¡µæŸ¥è¯¢åº“存记录列表
export const getStockUninventoryListPage = (params) => {
    return request({
        url: "/stockUninventory/pagestockUninventory",
        method: "get",
        params,
    });
};
// åˆ›å»ºåº“存记录
export const createStockUnInventory = (params) => {
    return request({
        url: "/stockUninventory/addstockUninventory",
        method: "post",
        data: params,
    });
};
// å‡å°‘库存记录
export const subtractStockUnInventory = (params) => {
    return request({
        url: "/stockUninventory/subtractstockUninventory",
        method: "post",
        data: params,
    });
};
src/views/basicData/product/ProductSelectDialog.vue
@@ -1,28 +1,12 @@
<template>
  <el-dialog
      v-model="visible"
      title="选择产品"
      width="900px"
      destroy-on-close
      :close-on-click-modal="false"
  >
  <el-dialog v-model="visible" title="选择产品" width="900px" destroy-on-close :close-on-click-modal="false">
    <el-form :inline="true" :model="query" class="mb-2">
      <el-form-item label="产品大类">
        <el-input
            v-model="query.productName"
            placeholder="输入产品大类"
            clearable
            @keyup.enter="onSearch"
        />
        <el-input v-model="query.productName" placeholder="输入产品大类" clearable @keyup.enter="onSearch" />
      </el-form-item>
      <el-form-item label="型号名称">
        <el-input
            v-model="query.model"
            placeholder="输入型号名称"
            clearable
            @keyup.enter="onSearch"
        />
        <el-input v-model="query.model" placeholder="输入型号名称" clearable @keyup.enter="onSearch" />
      </el-form-item>
      <el-form-item>
@@ -32,32 +16,19 @@
    </el-form>
    <!-- åˆ—表 -->
    <el-table
        v-loading="loading"
        :data="tableData"
        height="420"
        highlight-current-row
        row-key="id"
        @selection-change="handleSelectionChange"
    >
    <el-table ref="tableRef" v-loading="loading" :data="tableData" height="420" highlight-current-row row-key="id"
      @selection-change="handleSelectionChange" @select="handleSelect">
      <el-table-column type="selection" width="55" />
      <el-table-column type="index" label="#" width="60"/>
      <el-table-column prop="productName" label="产品大类" min-width="160"/>
      <el-table-column prop="model" label="型号名称" min-width="200"/>
      <el-table-column prop="unit" label="单位" min-width="160"/>
      <el-table-column type="index" label="序号" width="60" />
      <el-table-column prop="productName" label="产品大类" min-width="160" />
      <el-table-column prop="model" label="型号名称" min-width="200" />
      <el-table-column prop="unit" label="单位" min-width="160" />
    </el-table>
    <div class="mt-3 flex justify-end">
      <el-pagination
          background
          layout="total, sizes, prev, pager, next, jumper"
          :total="total"
          v-model:page-size="page.pageSize"
          v-model:current-page="page.pageNum"
          :page-sizes="[10, 20, 50, 100]"
          @size-change="onPageChange"
          @current-change="onPageChange"
      />
      <el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total"
        v-model:page-size="page.pageSize" v-model:current-page="page.pageNum" :page-sizes="[10, 20, 50, 100]"
        @size-change="onPageChange" @current-change="onPageChange" />
    </div>
    <template #footer>
@@ -70,9 +41,9 @@
</template>
<script setup lang="ts">
import {computed, onMounted, reactive, ref, watch} from "vue";
import {ElMessage} from "element-plus";
import {productModelList} from '@/api/basicData/productModel'
import { computed, onMounted, reactive, ref, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
import { productModelList } from '@/api/basicData/productModel'
export type ProductRow = {
  id: number;
@@ -83,6 +54,7 @@
const props = defineProps<{
  modelValue: boolean;
  single?: boolean; // æ˜¯å¦åªèƒ½é€‰æ‹©ä¸€ä¸ªï¼Œé»˜è®¤false(可选择多个)
}>();
const emit = defineEmits(['update:modelValue', 'confirm']);
@@ -105,14 +77,48 @@
const loading = ref(false);
const tableData = ref<ProductRow[]>([]);
const total = ref(0);
const multipleSelection = ref<ProductRow[]>([])
const multipleSelection = ref<ProductRow[]>([]);
const tableRef = ref();
function close() {
  visible.value = false;
}
const handleSelectionChange = (val: ProductRow[]) => {
  multipleSelection.value = val
  if (props.single && val.length > 1) {
    // å¦‚果限制为单个选择,只保留最后一个选中的
    const lastSelected = val[val.length - 1];
    multipleSelection.value = [lastSelected];
    // æ¸…空表格选中状态,然后重新选中最后一个
    nextTick(() => {
      if (tableRef.value) {
        tableRef.value.clearSelection();
        tableRef.value.toggleRowSelection(lastSelected, true);
      }
    });
  } else {
    multipleSelection.value = val;
  }
}
// å¤„理单个选择
const handleSelect = (selection: ProductRow[], row: ProductRow) => {
  if (props.single) {
    // å¦‚果限制为单个,清空其他选择,只保留当前行
    if (selection.includes(row)) {
      // é€‰ä¸­å½“前行时,清空其他选中
      multipleSelection.value = [row];
      nextTick(() => {
        if (tableRef.value) {
          tableData.value.forEach((item) => {
            if (item.id !== row.id) {
              tableRef.value.toggleRowSelection(item, false);
            }
          });
        }
      });
    }
  }
}
function onSearch() {
@@ -136,7 +142,11 @@
    ElMessage.warning("请选择一条产品");
    return;
  }
  emit("confirm", multipleSelection.value);
  if (props.single && multipleSelection.value.length > 1) {
    ElMessage.warning("只能选择一个产品");
    return;
  }
  emit("confirm", props.single ? [multipleSelection.value[0]] : multipleSelection.value);
  close();
}
@@ -144,7 +154,7 @@
  loading.value = true;
  try {
    multipleSelection.value = []; // ç¿»é¡µ/搜索后清空选择更符合预期
    const res = await productModelList({
    const res: any = await productModelList({
      productName: query.productName.trim(),
      model: query.model.trim(),
      current: page.pageNum,
@@ -157,6 +167,13 @@
  }
}
// ç›‘听弹窗打开,重置选择
watch(() => props.modelValue, (visible) => {
  if (visible) {
    multipleSelection.value = [];
  }
});
onMounted(() => {
  loadData()
})
src/views/basicData/supplierManage/filesDia.vue
@@ -30,6 +30,10 @@
          :isSelection="true"
          @selection-change="handleSelectionChange"
          height="500"
          @pagination-change="paginationSearch"
          :total="total"
          :page="page.current"
          :limit="page.size"
      >
      </PIMTable>
            <pagination
src/views/basicData/supplierManage/index.vue
@@ -26,6 +26,15 @@
    return {
      activeTab: 'home'
    }
  },
  watch: {
    activeTab(newVal) {
      if (newVal === 'home') {
        this.$refs.homeTab && this.$refs.homeTab.getList()
      } else if (newVal === 'blacklist') {
        this.$refs.blacklistTab && this.$refs.blacklistTab.getList()
      }
    }
  }
}
</script>
src/views/equipmentManagement/repair/index.vue
@@ -85,17 +85,17 @@
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          rowKey="id"
          isSelection
          :column="columns"
          :tableData="dataList"
          :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
          @selection-change="handleSelectionChange"
          @pagination="changePage"
      >
        <template #statusRef="{ row }">
          <el-tag v-if="row.status === 2" type="danger">失败</el-tag>
@@ -130,17 +130,18 @@
        </template>
      </PIMTable>
    </div>
    <RepairModal ref="repairModalRef" @ok="getTableData" />
    <MaintainModal ref="maintainModalRef" @ok="getTableData" />
    <RepairModal ref="repairModalRef" @ok="getTableData"/>
    <MaintainModal ref="maintainModalRef" @ok="getTableData"/>
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { getRepairPage, delRepair } from "@/api/equipmentManagement/repair";
import { onMounted, getCurrentInstance, computed } from "vue";
import {usePaginationApi} from "@/hooks/usePaginationApi";
import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair";
import RepairModal from "./Modal/RepairModal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import {ElMessageBox, ElMessage} from "element-plus";
import dayjs from "dayjs";
import MaintainModal from "./Modal/MaintainModal.vue";
@@ -148,7 +149,7 @@
  name: "设备报修",
});
const { proxy } = getCurrentInstance();
const {proxy} = getCurrentInstance();
// æ¨¡æ€æ¡†å®žä¾‹
const repairModalRef = ref();
@@ -167,85 +168,85 @@
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  getRepairPage,
  {
    deviceName: undefined,
    deviceModel: undefined,
    remark: undefined,
    maintenanceName: undefined,
    repairTimeStr: undefined,
    maintenanceTimeStr: undefined,
  },
  [
    getRepairPage,
    {
      label: "设备名称",
      align: "center",
      prop: "deviceName",
      deviceName: undefined,
      deviceModel: undefined,
      remark: undefined,
      maintenanceName: undefined,
      repairTimeStr: undefined,
      maintenanceTimeStr: undefined,
    },
    {
      label: "规格型号",
      align: "center",
      prop: "deviceModel",
    },
    {
      label: "报修日期",
      align: "center",
      prop: "repairTime",
      formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
    },
    {
      label: "报修人",
      align: "center",
      prop: "repairName",
    },
    {
      label: "故障现象",
      align: "center",
      prop: "remark",
    },
    {
      label: "维修人",
      align: "center",
      prop: "maintenanceName",
    },
    {
      label: "维修结果",
      align: "center",
      prop: "maintenanceResult",
    },
    {
      label: "维修日期",
      align: "center",
      prop: "maintenanceTime",
      formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
    },
    {
      label: "状态",
      align: "center",
      prop: "status",
      dataType: "slot",
      slot: "statusRef",
    },
    {
      fixed: "right",
      label: "操作",
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "200px",
    },
  ]
    [
      {
        label: "设备名称",
        align: "center",
        prop: "deviceName",
      },
      {
        label: "规格型号",
        align: "center",
        prop: "deviceModel",
      },
      {
        label: "报修日期",
        align: "center",
        prop: "repairTime",
        formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
      },
      {
        label: "报修人",
        align: "center",
        prop: "repairName",
      },
      {
        label: "故障现象",
        align: "center",
        prop: "remark",
      },
      {
        label: "维修人",
        align: "center",
        prop: "maintenanceName",
      },
      {
        label: "维修结果",
        align: "center",
        prop: "maintenanceResult",
      },
      {
        label: "维修日期",
        align: "center",
        prop: "maintenanceTime",
        formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
      },
      {
        label: "状态",
        align: "center",
        prop: "status",
        dataType: "slot",
        slot: "statusRef",
      },
      {
        fixed: "right",
        label: "操作",
        dataType: "slot",
        slot: "operation",
        align: "center",
        width: "300px",
      },
    ]
);
// type === 1 ç»´ä¿® 2报修间
const handleDateChange = (value,type) => {
const handleDateChange = (value, type) => {
  filters.maintenanceTimeStr = null
  filters.c = null
  if(type === 1){
  if (type === 1) {
    if (value) {
      filters.maintenanceTimeStr = dayjs(value).format("YYYY-MM-DD");
    }
  }else{
  } else {
    if (value) {
      filters.repairTimeStr = dayjs(value).format("YYYY-MM-DD");
    }
@@ -278,10 +279,10 @@
  maintainModalRef.value.open(row.id, row);
};
const changePage = ({ page, limit }) => {
    pagination.currentPage = page;
    pagination.pageSize = limit;
    onCurrentChange(page);
const changePage = ({page, limit}) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  onCurrentChange(page);
};
// å•行删除
@@ -292,18 +293,18 @@
    const record = dataList.value.find(item => item.id === id);
    return record && record.status === 1;
  });
  if (hasFinished) {
    ElMessage.warning('不能删除状态为完结的记录');
    return;
  }
  ElMessageBox.confirm("确认删除报修数据, æ­¤æ“ä½œä¸å¯é€†?", "警告", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    const { code } = await delRepair(ids);
    const {code} = await delRepair(ids);
    if (code === 200) {
      ElMessage.success("删除成功");
      getTableData();
@@ -318,12 +319,12 @@
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download("/device/repair/export", {}, "设备报修.xlsx");
    })
    .catch(() => {
      ElMessage.info("已取消");
    });
      .then(() => {
        proxy.download("/device/repair/export", {}, "设备报修.xlsx");
      })
      .catch(() => {
        ElMessage.info("已取消");
      });
};
onMounted(() => {
@@ -335,6 +336,7 @@
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
src/views/equipmentManagement/upkeep/index.vue
@@ -172,6 +172,13 @@
        </template>
        <template #operation="{ row }">
          <el-button
              type="primary"
              text
              @click="addMaintain(row)"
          >
            æ–°å¢žä¿å…»
          </el-button>
          <el-button
            type="primary"
            link
            :disabled="row.status === 1"
@@ -377,7 +384,7 @@
        dataType: "slot",
        slot: "operation",
        align: "center",
        width: "200px",
        width: "300px",
    },
])
src/views/inventoryManagement/dispatchLog/Record.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,711 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title ml10">出库日期:</span>
                <el-date-picker
                    v-model="searchForm.timeStr"
                    type="date"
                    placeholder="请选择日期"
                    value-format="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    clearable
                    @change="handleQuery"
                />
        <span class="search_title ml10">来源:</span>
        <el-select v-model="searchForm.recordType"
                   style="width: 240px"
                   placeholder="请选择"
                   clearable>
          <el-option v-for="item in stockRecordTypeOptions"
                     :key="item.value"
                     :label="item.label"
                     :value="item.value"/>
        </el-select>
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <el-button @click="handleOut">导出</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
                <el-button type="primary" plain @click="handlePrint">打印</el-button>
            </div>
        </div>
        <div class="table_list">
            <el-table
                :data="tableData"
                border
                v-loading="tableLoading"
                @selection-change="handleSelectionChange"
                :expand-row-keys="expandedRowKeys"
                :row-key="(row) => row.id"
                style="width: 100%"
                height="calc(100vh - 18.5em)"
            >
                <el-table-column align="center" type="selection" width="55" />
                <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column
            label="出库批次"
            prop="outboundBatches"
            min-width="100"
            show-overflow-tooltip
        />
                <el-table-column
                    label="出库日期"
                    prop="createTime"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="产品大类"
                    prop="productName"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="规格型号"
                    prop="model"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="单位"
                    prop="unit"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="出库数量"
                    prop="stockOutNum"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="出库人"
                    prop="createBy"
                    show-overflow-tooltip
                />
        <el-table-column label="来源"
                         prop="recordType"
                         show-overflow-tooltip>
          <template #default="scope">
            {{ getRecordType(scope.row.recordType) }}
          </template>
        </el-table-column>
            </el-table>
            <pagination
                v-show="total > 0"
                :total="total"
                layout="total, sizes, prev, pager, next, jumper"
                :page="page.current"
                :limit="page.size"
                @pagination="paginationChange"
            />
        </div>
    </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import { ref } from "vue";
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user";
import { getCurrentDate } from "@/utils/index.js";
import {
    getStockOutPage,
    delStockOut,
} from "@/api/inventoryManagement/stockOut.js";
import {
  findAllQualifiedStockRecordTypeOptions,
  findAllStockRecordTypeOptions,
  findAllUnqualifiedStockRecordTypeOptions
} from "@/api/basicData/enum.js";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
// æ¥æºç±»åž‹é€‰é¡¹
const stockRecordTypeOptions = ref([]);
const page = reactive({
    current: 1,
    size: 100,
});
const total = ref(0);
const props = defineProps({
  type: {
    type: String,
    required: true,
    default: '0'
  }
})
// æ‰“印相关
const printPreviewVisible = ref(false);
const printData = ref([]);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const data = reactive({
    searchForm: {
        supplierName: "",
        timeStr: "",
    recordType: "",
    }
});
const { searchForm } = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const paginationChange = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    getStockOutPage({ ...searchForm.value, ...page, type: props.type })
        .then((res) => {
            tableLoading.value = false;
            tableData.value = res.data.records;
            tableData.value.map((item) => {
                item.children = [];
            });
            total.value = res.data.total;
        })
        .catch(() => {
            tableLoading.value = false;
        });
};
const getRecordType = (recordType) => {
  return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
}
// èŽ·å–æ¥æºç±»åž‹é€‰é¡¹
const fetchStockRecordTypeOptions = () => {
  if (props.type === '0') {
    findAllQualifiedStockRecordTypeOptions()
        .then(res => {
          stockRecordTypeOptions.value = res.data;
        })
    return
  }
  findAllUnqualifiedStockRecordTypeOptions()
      .then(res => {
        stockRecordTypeOptions.value = res.data;
      })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    // è¿‡æ»¤æŽ‰å­æ•°æ®
    selectedRows.value = selection.filter((item) => item.id);
    console.log("selection", selectedRows.value);
};
const expandedRowKeys = ref([]);
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/stockmanagement/export", {}, "出库台账.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
// åˆ é™¤
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            delStockOut(ids).then((res) => {
                proxy.$modal.msgSuccess("删除成功");
                getList();
            });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
// æ‰“印功能
const handlePrint = () => {
    if (selectedRows.value.length === 0) {
        proxy.$modal.msgWarning("请选择要打印的数据");
        return;
    }
    printData.value = [...selectedRows.value];
    console.log('打印数据:', printData.value);
    printPreviewVisible.value = true;
};
// æ‰§è¡Œæ‰“印
const executePrint = () => {
    console.log('开始执行打印,数据条数:', printData.value.length);
    console.log('打印数据:', printData.value);
    // åˆ›å»ºä¸€ä¸ªæ–°çš„æ‰“印窗口
    const printWindow = window.open('', '_blank', 'width=800,height=600');
    // æž„建打印内容
    let printContent = `
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>打印预览</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          font-family: "SimSun", serif;
          background: white;
        }
                                                     .print-page {
            width: 200mm;
            height: 75mm;
            padding: 10mm;
            padding-left: 20mm;
            background: white;
            box-sizing: border-box;
            page-break-after: always;
            page-break-inside: avoid;
          }
         .print-page:last-child {
           page-break-after: avoid;
         }
        .delivery-note {
          width: 100%;
          height: 100%;
          font-size: 12px;
          line-height: 1.2;
          display: flex;
          flex-direction: column;
          color: #000;
        }
        .header {
          text-align: center;
          margin-bottom: 8px;
        }
        .company-name {
          font-size: 18px;
          font-weight: bold;
          margin-bottom: 4px;
        }
        .document-title {
          font-size: 16px;
          font-weight: bold;
        }
        .info-section {
          margin-bottom: 8px;
          display: flex;
          justify-content: space-between;
          align-items: center;
        }
        .info-row {
          line-height: 20px;
        }
        .label {
          font-weight: bold;
          width: 60px;
          font-size: 12px;
        }
        .value {
          margin-right: 20px;
          min-width: 80px;
          font-size: 12px;
        }
                 .table-section {
                 margin-bottom: 40px;
          //  flex: 0.6;
         }
        .product-table {
          width: 100%;
          border-collapse: collapse;
          border: 1px solid #000;
        }
                 .product-table th, .product-table td {
           border: 1px solid #000;
           padding: 6px;
           text-align: center;
           font-size: 12px;
           line-height: 1.4;
         }
        .product-table th {
          font-weight: bold;
        }
        .total-value {
          font-weight: bold;
        }
        .footer-section {
          margin-top: auto;
        }
        .footer-row {
          display: flex;
          margin-bottom: 3px;
          line-height: 22px;
          justify-content: space-between;
        }
        .footer-item {
          display: flex;
          margin-right: 20px;
        }
        .footer-item .label {
          font-weight: bold;
          width: 80px;
          font-size: 12px;
        }
        .footer-item .value {
          min-width: 80px;
          font-size: 12px;
        }
        .address-item .address-value {
          min-width: 200px;
        }
        @media print {
          body {
            margin: 0;
            padding: 0;
          }
                     .print-page {
             margin: 0;
             padding: 10mm;
             /* padding-left: 20mm; */
             page-break-inside: avoid;
             page-break-after: always;
           }
           .print-page:last-child {
             page-break-after: avoid;
           }
        }
      </style>
    </head>
    <body>
  `;
    // ä¸ºæ¯æ¡æ•°æ®ç”Ÿæˆæ‰“印页面
    printData.value.forEach((item, index) => {
        printContent += `
      <div class="print-page">
        <div class="delivery-note">
          <div class="header">
            <div class="company-name">鼎诚瑞实业有限责任公司</div>
            <div class="document-title">零售发货单</div>
          </div>
          <div class="info-section">
            <div class="info-row">
              <div>
                <span class="label">发货日期:</span>
                <span class="value">${formatDate(item.createTime)}</span>
              </div>
              <div>
                <span class="label">客户名称:</span>
                <span class="value">${item.supplierName || '张爱有'}</span>
              </div>
            </div>
            <div class="info-row">
              <span class="label">单号:</span>
              <span class="value">${item.code || ''}</span>
            </div>
          </div>
          <div class="table-section">
            <table class="product-table">
              <thead>
                <tr>
                  <th>产品名称</th>
                  <th>规格型号</th>
                  <th>单位</th>
                  <th>单价</th>
                  <th>零售数量</th>
                  <th>零售金额</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>${item.productName || '砂灰砖'}</td>
                  <td>${item.model || '标准'}</td>
                  <td>${item.unit || '块'}</td>
                  <td>${item.taxInclusiveUnitPrice || '0'}</td>
                  <td>${item.inboundNum || '2000'}</td>
                  <td>${item.taxInclusiveTotalPrice || '0'}</td>
                </tr>
              </tbody>
              <tfoot>
                <tr>
                  <td class="label">合计</td>
                  <td class="total-value"></td>
                  <td class="total-value"></td>
                  <td class="total-value"></td>
                  <td class="total-value">${item.inboundNum || '2000'}</td>
                  <td class="total-value">${item.taxInclusiveTotalPrice || '0'}</td>
                </tr>
              </tfoot>
            </table>
          </div>
          <div class="footer-section">
            <div class="footer-row">
              <div class="footer-item">
                <span class="label">收货电话:</span>
                <span class="value"></span>
              </div>
              <div class="footer-item">
                <span class="label">收货人:</span>
                <span class="value"></span>
              </div>
              <div class="footer-item address-item">
                <span class="label">收货地址:</span>
                <span class="value address-value"></span>
              </div>
            </div>
            <div class="footer-row">
              <div class="footer-item">
                <span class="label">操作员:</span>
                <span class="value">${userStore.nickName || '撕开前'}</span>
              </div>
              <div class="footer-item">
                <span class="label">打印日期:</span>
                <span class="value">${formatDateTime(new Date())}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    `;
    });
    printContent += `
    </body>
    </html>
  `;
    // å†™å…¥å†…容到新窗口
    printWindow.document.write(printContent);
    printWindow.document.close();
    // ç­‰å¾…内容加载完成后打印
    printWindow.onload = () => {
        setTimeout(() => {
            printWindow.print();
            printWindow.close();
            printPreviewVisible.value = false;
        }, 500);
    };
};
// æ ¼å¼åŒ–日期
const formatDate = (dateString) => {
    if (!dateString) return getCurrentDate();
    const date = new Date(dateString);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    return `${year}/${month}/${day}`;
};
// æ ¼å¼åŒ–日期时间
const formatDateTime = (date) => {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    const hours = String(date.getHours()).padStart(2, "0");
    const minutes = String(date.getMinutes()).padStart(2, "0");
    const seconds = String(date.getSeconds()).padStart(2, "0");
    return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
};
onMounted(() => {
    getList();
  fetchStockRecordTypeOptions();
});
</script>
<style scoped lang="scss">
.print-preview-dialog {
    .el-dialog__body {
        padding: 0;
        max-height: 80vh;
        overflow-y: auto;
    }
}
.print-preview-container {
    .print-preview-header {
        padding: 15px;
        border-bottom: 1px solid #e4e7ed;
        text-align: center;
        .el-button {
            margin: 0 10px;
        }
    }
    .print-preview-content {
        padding: 20px;
        background-color: #f5f5f5;
        min-height: 400px;
    }
}
.print-page {
    width: 220mm;
    height: 90mm;
    padding: 10mm;
    margin: 0 auto;
    background: white;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    margin-bottom: 10px;
    box-sizing: border-box;
}
.delivery-note {
    width: 100%;
    height: 100%;
    font-family: "SimSun", serif;
    font-size: 10px;
    line-height: 1.2;
    display: flex;
    flex-direction: column;
}
.header {
    text-align: center;
    margin-bottom: 8px;
    .company-name {
        font-size: 18px;
        font-weight: bold;
        margin-bottom: 4px;
    }
    .document-title {
        font-size: 16px;
        font-weight: bold;
    }
}
.info-section {
    margin-bottom: 8px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .info-row {
        line-height: 20px;
        .label {
            font-weight: bold;
            width: 60px;
            font-size: 14px;
        }
        .value {
            margin-right: 20px;
            min-width: 80px;
            font-size: 14px;
        }
    }
}
.table-section {
    margin-bottom: 4px;
    flex: 1;
    .product-table {
        width: 100%;
        border-collapse: collapse;
        border: 1px solid #000;
        th, td {
            border: 1px solid #000;
            padding: 6px;
            text-align: center;
            font-size: 14px;
            line-height: 1.4;
        }
        th {
            font-weight: bold;
        }
        .total-label {
            text-align: right;
            font-weight: bold;
        }
        .total-value {
            font-weight: bold;
        }
    }
}
.footer-section {
    .footer-row {
        display: flex;
        margin-bottom: 3px;
        line-height: 20px;
        justify-content: space-between;
        .footer-item {
            display: flex;
            margin-right: 20px;
            .label {
                font-weight: bold;
                width: 80px;
                font-size: 14px;
            }
            .value {
                min-width: 80px;
                font-size: 14px;
            }
            &.address-item {
                .address-value {
                    min-width: 200px;
                }
            }
        }
    }
}
@media print {
    .app-container {
        display: none;
    }
    .print-page {
        box-shadow: none;
        margin: 0;
        padding: 10mm;
        padding-left: 20mm;
        page-break-inside: avoid;
        page-break-after: always;
    }
    .print-page:last-child {
        page-break-after: avoid;
    }
}
</style>
src/views/inventoryManagement/dispatchLog/index.vue
@@ -1,858 +1,38 @@
<!-- åœ¨ä½ çš„主页面中 -->
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">供应商名称:</span>
                <el-input
                    v-model="searchForm.supplierName"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search"
                />
                <span class="search_title ml10">出库日期:</span>
                <el-date-picker
                    v-model="searchForm.timeStr"
                    type="date"
                    placeholder="请选择日期"
                    value-format="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    clearable
                    @change="handleQuery"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <!-- <el-button type="primary" @click="openForm('add')">新增</el-button> -->
                <el-button @click="handleOut">导出</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
                <el-button type="primary" plain @click="handlePrint">打印</el-button>
            </div>
        </div>
        <div class="table_list">
            <el-table
                :data="tableData"
                border
                v-loading="tableLoading"
                @selection-change="handleSelectionChange"
                :expand-row-keys="expandedRowKeys"
                :row-key="(row) => row.id"
                show-summary
                style="width: 100%"
                :summary-method="summarizeMainTable"
                height="calc(100vh - 18.5em)"
            >
                <el-table-column align="center" type="selection" width="55" />
                <el-table-column align="center" label="序号" type="index" width="60" />
                <el-table-column
                    label="出库日期"
                    prop="createTime"
                    min-width="250"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="供应商名称"
                    prop="supplierName"
                    width="250"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="产品大类"
                    prop="productCategory"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="规格型号"
                    prop="specificationModel"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="单位"
                    prop="unit"
                    width="80"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="出库数量"
                    prop="inboundNum"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="含税单价(元)"
                    prop="taxInclusiveUnitPrice"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="含税总价(元)"
                    prop="taxInclusiveTotalPrice"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="税率(%)"
                    prop="taxRate"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="不含税总价(元)"
                    prop="taxExclusiveTotalPrice"
                    width="180"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="出库人"
                    prop="createBy"
                    width="80"
                    show-overflow-tooltip
                />
                <!-- <el-table-column
                    fixed="right"
                    label="操作"
                    min-width="60"
                    align="center"
                >
                    <template #default="scope">
                        <el-button
                            link
                            type="primary"
                            size="small"
                            @click="openForm('edit', scope.row)"
                            >编辑</el-button
                        >
                    </template>
                </el-table-column> -->
            </el-table>
            <pagination
                v-show="total > 0"
                :total="total"
                layout="total, sizes, prev, pager, next, jumper"
                :page="page.current"
                :limit="page.size"
                @pagination="paginationChange"
            />
        </div>
        <!-- æ‰“印预览弹窗 -->
        <el-dialog
            v-model="printPreviewVisible"
            title="打印预览"
            width="90%"
            :close-on-click-modal="false"
            class="print-preview-dialog"
        >
            <div class="print-preview-container">
                <div class="print-preview-header">
                    <el-button type="primary" @click="executePrint">执行打印</el-button>
                    <el-button @click="printPreviewVisible = false">关闭预览</el-button>
                </div>
                <div class="print-preview-content">
                    <div v-if="printData.length === 0" style="text-align: center; padding: 50px; color: #999;">
                        æš‚无打印数据
                    </div>
                    <div v-else style="text-align: center; padding: 10px; color: #666; font-size: 14px; background: #e8f4fd; margin-bottom: 10px;">
                        å…± {{ printData.length }} æ¡æ•°æ®å¾…打印
                    </div>
                    <div v-for="(item, index) in printData" :key="index" class="print-page">
                        <div class="delivery-note">
                            <div class="header">
                                <div class="company-name">鼎诚瑞实业有限责任公司</div>
                                <div class="document-title">零售发货单</div>
                            </div>
                            <div class="info-section">
                                <div class="info-row">
                                    <div>
                                        <span class="label">发货日期:</span>
                                        <span class="value">{{ formatDate(item.createTime) }}</span>
                                    </div>
                                    <div>
                                        <span class="label">客户名称:</span>
                                        <span class="value">{{ item.supplierName || '张爱有' }}</span>
                                    </div>
                                </div>
                                <div class="info-row">
                                    <span class="label">单号:</span>
                                    <span class="value">{{ item.code }}</span>
                                </div>
                            </div>
                            <div class="table-section">
                                <table class="product-table">
                                    <thead>
                                    <tr>
                                        <th>产品名称</th>
                                        <th>规格型号</th>
                                        <th>单位</th>
                                        <th>单价</th>
                                        <th>零售数量</th>
                                        <th>零售金额</th>
                                    </tr>
                                    </thead>
                                    <tbody>
                                    <tr>
                                        <td>{{ item.productCategory || '砂灰砖' }}</td>
                                        <td>{{ item.specificationModel || '标准' }}</td>
                                        <td>{{ item.unit || '块' }}</td>
                                        <td>{{ item.taxInclusiveUnitPrice || '0' }}</td>
                                        <td>{{ item.inboundNum || '2000' }}</td>
                                        <td>{{ item.taxInclusiveTotalPrice || '0' }}</td>
                                    </tr>
                                    </tbody>
                                    <tfoot>
                                    <tr>
                                        <td class="label">合计</td>
                                        <td class="total-value"></td>
                                        <td class="total-value"></td>
                                        <td class="total-value"></td>
                                        <td class="total-value">{{ item.inboundNum || '2000' }}</td>
                                        <td class="total-value">{{ item.taxInclusiveTotalPrice || '0' }}</td>
                                    </tr>
                                    </tfoot>
                                </table>
                            </div>
                            <div class="footer-section">
                                <div class="footer-row">
                                    <div class="footer-item">
                                        <span class="label">收货电话:</span>
                                        <span class="value"></span>
                                    </div>
                                    <div class="footer-item">
                                        <span class="label">收货人:</span>
                                        <span class="value"></span>
                                    </div>
                                    <div class="footer-item address-item">
                                        <span class="label">收货地址:</span>
                                        <span class="value address-value"></span>
                                    </div>
                                </div>
                                <div class="footer-row">
                                    <div class="footer-item">
                                        <span class="label">操作员:</span>
                                        <span class="value">{{ userStore.nickName || '撕开前' }}</span>
                                    </div>
                                    <div class="footer-item">
                                        <span class="label">打印日期:</span>
                                        <span class="value">{{ formatDateTime(new Date()) }}</span>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </el-dialog>
    </div>
  <div class="app-container">
    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
      <el-tab-pane v-for="tab in tabs"
                   :label="tab.label"
                   :name="tab.name"
                   :key="tab.name">
        <record :type="tab.type" v-if="activeTab === tab.name" />
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import { ref } from "vue";
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user";
import { getCurrentDate } from "@/utils/index.js";
import {
    getStockOutPage,
    delStockOut,
} from "@/api/inventoryManagement/stockOut.js";
import Record from "@/views/inventoryManagement/dispatchLog/Record.vue";
const activeTab = ref('qualified')
const type = ref(0)
const tabs = computed(() => {
  return [
    {
      label: '合格出库',
      name: 'qualified',
      type: '0'
    },
    {
      label: '不合格出库',
      name: 'unqualified',
      type: '1'
    }
  ]
})
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
});
const total = ref(0);
// æ‰“印相关
const printPreviewVisible = ref(false);
const printData = ref([]);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const data = reactive({
    searchForm: {
        supplierName: "",
        timeStr: "",
    },
    form: {
        supplierId: null,
        supplierName: '',
        productId: null,
        productName: '',
        userId: userStore.userId,
        nickName: '',
        model: '',
        productModelId: null,
        unit: '',
        productrecordId: null,
        taxInclusiveUnitPrice: '',
        taxInclusiveTotalPrice: '',
        taxRate: '',
        taxExclusiveTotalPrice: '',
        inboundTime: '',
        inboundBatch: '',
        inboundQuantity: ''
    },
});
const { searchForm } = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const paginationChange = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    getStockOutPage({ ...searchForm.value, ...page })
        .then((res) => {
            tableLoading.value = false;
            tableData.value = res.data.records;
            tableData.value.map((item) => {
                item.children = [];
            });
            total.value = res.data.total;
        })
        .catch(() => {
            tableLoading.value = false;
        });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    // è¿‡æ»¤æŽ‰å­æ•°æ®
    selectedRows.value = selection.filter((item) => item.id);
    console.log("selection", selectedRows.value);
};
const expandedRowKeys = ref([]);
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
    return proxy.summarizeTable(param, [
        "contractAmount",
        "taxInclusiveTotalPrice",
        "taxExclusiveTotalPrice",
    ]);
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/stockmanagement/export", {}, "出库台账.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
// åˆ é™¤
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            delStockOut({ids:ids}).then((res) => {
                proxy.$modal.msgSuccess("删除成功");
                getList();
            });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
// æ‰“印功能
const handlePrint = () => {
    if (selectedRows.value.length === 0) {
        proxy.$modal.msgWarning("请选择要打印的数据");
        return;
    }
    printData.value = [...selectedRows.value];
    console.log('打印数据:', printData.value);
    printPreviewVisible.value = true;
};
// æ‰§è¡Œæ‰“印
const executePrint = () => {
    console.log('开始执行打印,数据条数:', printData.value.length);
    console.log('打印数据:', printData.value);
    // åˆ›å»ºä¸€ä¸ªæ–°çš„æ‰“印窗口
    const printWindow = window.open('', '_blank', 'width=800,height=600');
    // æž„建打印内容
    let printContent = `
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>打印预览</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          font-family: "SimSun", serif;
          background: white;
        }
                                                     .print-page {
            width: 200mm;
            height: 75mm;
            padding: 10mm;
            padding-left: 20mm;
            background: white;
            box-sizing: border-box;
            page-break-after: always;
            page-break-inside: avoid;
          }
         .print-page:last-child {
           page-break-after: avoid;
         }
        .delivery-note {
          width: 100%;
          height: 100%;
          font-size: 12px;
          line-height: 1.2;
          display: flex;
          flex-direction: column;
          color: #000;
        }
        .header {
          text-align: center;
          margin-bottom: 8px;
        }
        .company-name {
          font-size: 18px;
          font-weight: bold;
          margin-bottom: 4px;
        }
        .document-title {
          font-size: 16px;
          font-weight: bold;
        }
        .info-section {
          margin-bottom: 8px;
          display: flex;
          justify-content: space-between;
          align-items: center;
        }
        .info-row {
          line-height: 20px;
        }
        .label {
          font-weight: bold;
          width: 60px;
          font-size: 12px;
        }
        .value {
          margin-right: 20px;
          min-width: 80px;
          font-size: 12px;
        }
                 .table-section {
                 margin-bottom: 40px;
          //  flex: 0.6;
         }
        .product-table {
          width: 100%;
          border-collapse: collapse;
          border: 1px solid #000;
        }
                 .product-table th, .product-table td {
           border: 1px solid #000;
           padding: 6px;
           text-align: center;
           font-size: 12px;
           line-height: 1.4;
         }
        .product-table th {
          font-weight: bold;
        }
        .total-value {
          font-weight: bold;
        }
        .footer-section {
          margin-top: auto;
        }
        .footer-row {
          display: flex;
          margin-bottom: 3px;
          line-height: 22px;
          justify-content: space-between;
        }
        .footer-item {
          display: flex;
          margin-right: 20px;
        }
        .footer-item .label {
          font-weight: bold;
          width: 80px;
          font-size: 12px;
        }
        .footer-item .value {
          min-width: 80px;
          font-size: 12px;
        }
        .address-item .address-value {
          min-width: 200px;
        }
        @media print {
          body {
            margin: 0;
            padding: 0;
          }
                     .print-page {
             margin: 0;
             padding: 10mm;
             /* padding-left: 20mm; */
             page-break-inside: avoid;
             page-break-after: always;
           }
           .print-page:last-child {
             page-break-after: avoid;
           }
        }
      </style>
    </head>
    <body>
  `;
    // ä¸ºæ¯æ¡æ•°æ®ç”Ÿæˆæ‰“印页面
    printData.value.forEach((item, index) => {
        printContent += `
      <div class="print-page">
        <div class="delivery-note">
          <div class="header">
            <div class="company-name">鼎诚瑞实业有限责任公司</div>
            <div class="document-title">零售发货单</div>
          </div>
          <div class="info-section">
            <div class="info-row">
              <div>
                <span class="label">发货日期:</span>
                <span class="value">${formatDate(item.createTime)}</span>
              </div>
              <div>
                <span class="label">客户名称:</span>
                <span class="value">${item.supplierName || '张爱有'}</span>
              </div>
            </div>
            <div class="info-row">
              <span class="label">单号:</span>
              <span class="value">${item.code || ''}</span>
            </div>
          </div>
          <div class="table-section">
            <table class="product-table">
              <thead>
                <tr>
                  <th>产品名称</th>
                  <th>规格型号</th>
                  <th>单位</th>
                  <th>单价</th>
                  <th>零售数量</th>
                  <th>零售金额</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>${item.productCategory || '砂灰砖'}</td>
                  <td>${item.specificationModel || '标准'}</td>
                  <td>${item.unit || '块'}</td>
                  <td>${item.taxInclusiveUnitPrice || '0'}</td>
                  <td>${item.inboundNum || '2000'}</td>
                  <td>${item.taxInclusiveTotalPrice || '0'}</td>
                </tr>
              </tbody>
              <tfoot>
                <tr>
                  <td class="label">合计</td>
                  <td class="total-value"></td>
                  <td class="total-value"></td>
                  <td class="total-value"></td>
                  <td class="total-value">${item.inboundNum || '2000'}</td>
                  <td class="total-value">${item.taxInclusiveTotalPrice || '0'}</td>
                </tr>
              </tfoot>
            </table>
          </div>
          <div class="footer-section">
            <div class="footer-row">
              <div class="footer-item">
                <span class="label">收货电话:</span>
                <span class="value"></span>
              </div>
              <div class="footer-item">
                <span class="label">收货人:</span>
                <span class="value"></span>
              </div>
              <div class="footer-item address-item">
                <span class="label">收货地址:</span>
                <span class="value address-value"></span>
              </div>
            </div>
            <div class="footer-row">
              <div class="footer-item">
                <span class="label">操作员:</span>
                <span class="value">${userStore.nickName || '撕开前'}</span>
              </div>
              <div class="footer-item">
                <span class="label">打印日期:</span>
                <span class="value">${formatDateTime(new Date())}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    `;
    });
    printContent += `
    </body>
    </html>
  `;
    // å†™å…¥å†…容到新窗口
    printWindow.document.write(printContent);
    printWindow.document.close();
    // ç­‰å¾…内容加载完成后打印
    printWindow.onload = () => {
        setTimeout(() => {
            printWindow.print();
            printWindow.close();
            printPreviewVisible.value = false;
        }, 500);
    };
};
// æ ¼å¼åŒ–日期
const formatDate = (dateString) => {
    if (!dateString) return getCurrentDate();
    const date = new Date(dateString);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    return `${year}/${month}/${day}`;
};
// æ ¼å¼åŒ–日期时间
const formatDateTime = (date) => {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    const hours = String(date.getHours()).padStart(2, "0");
    const minutes = String(date.getMinutes()).padStart(2, "0");
    const seconds = String(date.getSeconds()).padStart(2, "0");
    return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
};
onMounted(() => {
    getList();
});
const handleTabChange = (tabName) => {
  activeTab.value = tabName;
  type.value = tabName === 'qualified' ? 0 : 1
}
</script>
<style scoped lang="scss">
.print-preview-dialog {
    .el-dialog__body {
        padding: 0;
        max-height: 80vh;
        overflow-y: auto;
    }
}
.print-preview-container {
    .print-preview-header {
        padding: 15px;
        border-bottom: 1px solid #e4e7ed;
        text-align: center;
        .el-button {
            margin: 0 10px;
        }
    }
    .print-preview-content {
        padding: 20px;
        background-color: #f5f5f5;
        min-height: 400px;
    }
}
.print-page {
    width: 220mm;
    height: 90mm;
    padding: 10mm;
    margin: 0 auto;
    background: white;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    margin-bottom: 10px;
    box-sizing: border-box;
}
.delivery-note {
    width: 100%;
    height: 100%;
    font-family: "SimSun", serif;
    font-size: 10px;
    line-height: 1.2;
    display: flex;
    flex-direction: column;
}
.header {
    text-align: center;
    margin-bottom: 8px;
    .company-name {
        font-size: 18px;
        font-weight: bold;
        margin-bottom: 4px;
    }
    .document-title {
        font-size: 16px;
        font-weight: bold;
    }
}
.info-section {
    margin-bottom: 8px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .info-row {
        line-height: 20px;
        .label {
            font-weight: bold;
            width: 60px;
            font-size: 14px;
        }
        .value {
            margin-right: 20px;
            min-width: 80px;
            font-size: 14px;
        }
    }
}
.table-section {
    margin-bottom: 4px;
    flex: 1;
    .product-table {
        width: 100%;
        border-collapse: collapse;
        border: 1px solid #000;
        th, td {
            border: 1px solid #000;
            padding: 6px;
            text-align: center;
            font-size: 14px;
            line-height: 1.4;
        }
        th {
            font-weight: bold;
        }
        .total-label {
            text-align: right;
            font-weight: bold;
        }
        .total-value {
            font-weight: bold;
        }
    }
}
.footer-section {
    .footer-row {
        display: flex;
        margin-bottom: 3px;
        line-height: 20px;
        justify-content: space-between;
        .footer-item {
            display: flex;
            margin-right: 20px;
            .label {
                font-weight: bold;
                width: 80px;
                font-size: 14px;
            }
            .value {
                min-width: 80px;
                font-size: 14px;
            }
            &.address-item {
                .address-value {
                    min-width: 200px;
                }
            }
        }
    }
}
@media print {
    .app-container {
        display: none;
    }
    .print-page {
        box-shadow: none;
        margin: 0;
        padding: 10mm;
        padding-left: 20mm;
        page-break-inside: avoid;
        page-break-after: always;
    }
    .print-page:last-child {
        page-break-after: avoid;
    }
}
</style>
src/views/inventoryManagement/receiptManagement/Record.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,251 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title ml10">入库日期:</span>
        <el-date-picker v-model="searchForm.timeStr"
                        type="date"
                        placeholder="请选择日期"
                        value-format="YYYY-MM-DD"
                        format="YYYY-MM-DD"
                        clearable
                        @change="handleQuery"/>
        <span class="search_title ml10">产品大类:</span>
        <el-input v-model="searchForm.productName"
                  style="width: 240px"
                  placeholder="请输入"
                  clearable/>
        <span class="search_title ml10">来源:</span>
        <el-select v-model="searchForm.recordType"
                  style="width: 240px"
                  placeholder="请选择"
                  clearable>
          <el-option v-for="item in stockRecordTypeOptions"
                     :key="item.value"
                     :label="item.label"
                     :value="item.value"/>
        </el-select>
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索
        </el-button>
      </div>
      <div>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger"
                   plain
                   @click="handleDelete">删除
        </el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table :data="tableData"
                border
                v-loading="tableLoading"
                @selection-change="handleSelectionChange"
                :expand-row-keys="expandedRowKeys"
                :row-key="row => row.id"
                style="width: 100%"
                height="calc(100vh - 18.5em)">
        <el-table-column align="center"
                         type="selection"
                         width="55"/>
        <el-table-column align="center"
                         label="序号"
                         type="index"
                         width="60"/>
        <el-table-column label="入库批次"
                         prop="inboundBatches"
                         width="280"
                         show-overflow-tooltip/>
        <el-table-column label="入库时间"
                         prop="createTime"
                         show-overflow-tooltip/>
        <el-table-column label="产品大类"
                         prop="productName"
                         show-overflow-tooltip/>
        <el-table-column label="规格型号"
                         prop="model"
                         show-overflow-tooltip/>
        <el-table-column label="单位"
                         prop="unit"
                         show-overflow-tooltip/>
        <el-table-column label="入库数量"
                         prop="stockInNum"
                         show-overflow-tooltip/>
        <el-table-column label="入库人"
                         prop="createBy"
                         show-overflow-tooltip/>
        <el-table-column label="来源"
                         prop="recordType"
                         show-overflow-tooltip>
          <template #default="scope">
            {{ getRecordType(scope.row.recordType) }}
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0"
                  :total="total"
                  layout="total, sizes, prev, pager, next, jumper"
                  :page="page.current"
                  :limit="page.size"
                  @pagination="pageProductChange"/>
    </div>
  </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import {
  ref,
  reactive,
  toRefs,
  onMounted,
  getCurrentInstance,
} from "vue";
import {ElMessageBox} from "element-plus";
import {
  getStockInRecordListPage,
  batchDeleteStockInRecords,
} from "@/api/inventoryManagement/stockInRecord.js";
import {
  findAllQualifiedStockRecordTypeOptions,
  findAllUnqualifiedStockRecordTypeOptions
} from "@/api/basicData/enum.js";
const {proxy} = getCurrentInstance();
const props = defineProps({
  type: {
    type: String,
    required: true,
    default: '0'
  }
})
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
// æ¥æºç±»åž‹é€‰é¡¹
const stockRecordTypeOptions = ref([]);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const data = reactive({
  searchForm: {
    productName: "",
    timeStr: "",
    recordType: "",
  },
});
const {searchForm} = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const getRecordType = (recordType) => {
  return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
}
const pageProductChange = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const params = {...page, type: props.type};
  params.timeStr = searchForm.value.timeStr;
  params.productName = searchForm.value.productName;
  getStockInRecordListPage(params)
      .then(res => {
        tableData.value = res.data.records;
      }).finally(() => {
    tableLoading.value = false;
  })
};
// èŽ·å–æ¥æºç±»åž‹é€‰é¡¹
const fetchStockRecordTypeOptions = () => {
  if (props.type === '0') {
    findAllQualifiedStockRecordTypeOptions()
        .then(res => {
          stockRecordTypeOptions.value = res.data;
        })
    return
  }
  findAllUnqualifiedStockRecordTypeOptions()
      .then(res => {
        stockRecordTypeOptions.value = res.data;
      })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = selection => {
  selectedRows.value = selection.filter(item => item.id);
};
const expandedRowKeys = ref([]);
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹è°ƒç”¨ä¸åŒçš„导出接口
        let exportUrl = "/stockin/export";
        proxy.download(exportUrl, {}, "入库台账.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// åˆ é™¤
const handleDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  const ids = selectedRows.value.map(item => item.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        batchDeleteStockInRecords(ids)
            .then(() => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("删除失败");
            });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
onMounted(() => {
  getList();
  fetchStockRecordTypeOptions();
});
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/receiptManagement/components/formDia.vue
ÎļþÒÑɾ³ý
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue
ÎļþÒÑɾ³ý
src/views/inventoryManagement/receiptManagement/index.vue
@@ -1,538 +1,36 @@
<template>
  <div class="app-container">
    <el-tabs v-model="activeTab"
             @tab-change="handleTabChange">
      <el-tab-pane label="成品入库"
                   name="production">
        <div class="search_form">
          <div>
            <span class="search_title ml10">入库日期:</span>
            <el-date-picker v-model="searchForm.timeStr"
                            type="date"
                            placeholder="请选择日期"
                            value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD"
                            clearable
                            @change="handleQuery"/>
            <span class="search_title ml10">产品大类:</span>
            <el-input v-model="searchForm.productCategory"
                      style="width: 240px"
                      placeholder="请输入"
                      clearable/>
            <el-button type="primary"
                       @click="handleQuery"
                       style="margin-left: 10px">搜索
            </el-button>
          </div>
          <div>
            <el-button @click="handleOut">导出</el-button>
            <el-button type="danger"
                       plain
                       @click="handleDelete">删除
            </el-button>
          </div>
        </div>
        <div class="table_list">
          <el-table :data="tableData"
                    border
                    v-loading="tableLoading"
                    @selection-change="handleSelectionChange"
                    :expand-row-keys="expandedRowKeys"
                    :row-key="row => row.id"
                    show-summary
                    style="width: 100%"
                    :summary-method="summarizeMainTable"
                    height="calc(100vh - 18.5em)">
            <el-table-column align="center"
                             type="selection"
                             width="55"/>
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60"/>
            <el-table-column label="入库时间"
                             prop="createTime"
                             show-overflow-tooltip/>
            <el-table-column label="销售合同号"
                             prop="salesContractNo"
                             width="180"
                             show-overflow-tooltip/>
            <el-table-column label="产品大类"
                             prop="productCategory"
                             show-overflow-tooltip/>
            <el-table-column label="规格型号"
                             prop="specificationModel"
                             show-overflow-tooltip/>
            <el-table-column label="单位"
                             prop="unit"
                             width="70"
                             show-overflow-tooltip/>
            <el-table-column label="入库数量"
                             prop="inboundNum"
                             width="100"
                             show-overflow-tooltip/>
            <el-table-column label="单价(元)"
                             prop="unitPrice"
                             width="150"></el-table-column>
            <el-table-column label="总价(元)"
                             prop="totalPrice"
                             width="150"></el-table-column>
            <!-- <el-table-column fixed="right"
                             label="操作"
                             min-width="60"
                             align="center">
              <template #default="scope">
                <el-button link
                           type="primary"
                           size="small"
                           @click="openForm('edit', scope.row, 'production');">编辑</el-button>
              </template>
            </el-table-column> -->
          </el-table>
          <pagination v-show="total > 0"
                      :total="total"
                      layout="total, sizes, prev, pager, next, jumper"
                      :page="page.current"
                      :limit="page.size"
                      @pagination="paginationChange"/>
        </div>
      </el-tab-pane>
      <el-tab-pane label="原料入库"
                   name="purchase">
        <div class="search_form">
          <div>
            <span class="search_title ml10">入库日期:</span>
            <el-date-picker v-model="searchForm.timeStr"
                            type="date"
                            placeholder="请选择日期"
                            value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD"
                            clearable
                            @change="handleQuery"/>
            <span class="search_title ml10">产品大类:</span>
            <el-input v-model="searchForm.productCategory"
                      style="width: 240px"
                      placeholder="请输入"
                      clearable/>
            <el-button type="primary"
                       @click="handleQuery"
                       style="margin-left: 10px">搜索
            </el-button>
          </div>
          <div>
            <el-button type="primary"
                       @click="openForm('add', 'purchase')">新增入库
            </el-button>
            <el-button @click="handleOut">导出</el-button>
            <el-button type="danger"
                       plain
                       @click="handleDelete">删除
            </el-button>
          </div>
        </div>
        <div class="table_list">
          <el-table :data="tableData"
                    border
                    v-loading="tableLoading"
                    @selection-change="handleSelectionChange"
                    :expand-row-keys="expandedRowKeys"
                    :row-key="row => row.id"
                    show-summary
                    style="width: 100%"
                    :summary-method="summarizeMainTable"
                    height="calc(100vh - 18.5em)">
            <el-table-column align="center"
                             type="selection"
                             width="55"/>
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60"/>
            <el-table-column label="入库时间"
                             prop="createTime"
                             width="100"
                             show-overflow-tooltip/>
            <el-table-column label="采购合同号"
                             prop="purchaseContractNumber"
                             width="180"
                             show-overflow-tooltip/>
            <el-table-column label="供应商名称"
                             prop="supplierName"
                             width="240"
                             show-overflow-tooltip/>
            <el-table-column label="产品大类"
                             prop="productCategory"
                             show-overflow-tooltip/>
            <el-table-column label="规格型号"
                             prop="specificationModel"
                             show-overflow-tooltip/>
            <el-table-column label="单位"
                             prop="unit"
                             width="70"
                             show-overflow-tooltip/>
            <el-table-column label="入库数量"
                             prop="inboundNum"
                             width="100"
                             show-overflow-tooltip/>
            <el-table-column label="含税单价(元)"
                             prop="taxInclusiveUnitPrice"
                             width="150"></el-table-column>
            <el-table-column label="含税总价(元)"
                             prop="taxInclusiveTotalPrice"
                             width="150"></el-table-column>
            <el-table-column label="入库人"
                             prop="createBy"
                             width="80"
                             show-overflow-tooltip/>
            <el-table-column fixed="right"
                             label="操作"
                             min-width="60"
                             align="center">
              <template #default="scope">
                <el-button link
                           :disabled="scope.row.type == 1"
                           type="primary"
                           size="small"
                           @click="openForm('edit', scope.row, 'purchase');">编辑
                </el-button>
              </template>
            </el-table-column>
          </el-table>
          <pagination v-show="total > 0"
                      :total="total"
                      layout="total, sizes, prev, pager, next, jumper"
                      :page="page.current"
                      :limit="page.size"
                      @pagination="paginationChange"/>
        </div>
      </el-tab-pane>
      <el-tab-pane label="生产入库"
                   name="product">
        <div class="search_form">
          <div>
            <span class="search_title ml10">入库日期:</span>
            <el-date-picker v-model="searchForm.timeStr"
                            type="date"
                            placeholder="请选择日期"
                            value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD"
                            clearable
                            @change="handleQuery"/>
            <span class="search_title ml10">产品大类:</span>
            <el-input v-model="searchForm.productCategory"
                      style="width: 240px"
                      placeholder="请输入"
                      clearable/>
            <el-button type="primary"
                       @click="handleQuery"
                       style="margin-left: 10px">搜索
            </el-button>
          </div>
          <div>
            <el-button type="primary"
                       @click="openForm('add', 'purchase')">新增入库
            </el-button>
            <el-button @click="handleOut">导出</el-button>
            <el-button type="danger"
                       plain
                       @click="handleDelete">删除
            </el-button>
          </div>
        </div>
        <div class="table_list">
          <el-table :data="tableData"
                    border
                    v-loading="tableLoading"
                    @selection-change="handleSelectionChange"
                    :expand-row-keys="expandedRowKeys"
                    :row-key="row => row.id"
                    show-summary
                    style="width: 100%"
                    :summary-method="summarizeMainTable"
                    height="calc(100vh - 18.5em)">
            <el-table-column align="center"
                             type="selection"
                             width="55"/>
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60"/>
            <el-table-column label="入库时间"
                             prop="createTime"
                             width="100"
                             show-overflow-tooltip/>
            <el-table-column label="产品大类"
                             prop="productCategory"
                             show-overflow-tooltip/>
            <el-table-column label="规格型号"
                             prop="specificationModel"
                             show-overflow-tooltip/>
            <el-table-column label="单位"
                             prop="unit"
                             width="220"
                             show-overflow-tooltip/>
            <el-table-column label="入库数量"
                             prop="inboundNum"
                             width="220"
                             show-overflow-tooltip/>
            <el-table-column label="入库人"
                             prop="createBy"
                             width="220"
                             show-overflow-tooltip/>
          </el-table>
          <pagination v-show="total > 0"
                      :total="total"
                      layout="total, sizes, prev, pager, next, jumper"
                      :page="page.current"
                      :limit="page.size"
                      @pagination="pageProductChange"/>
        </div>
    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
      <el-tab-pane v-for="tab in tabs"
                   :label="tab.label"
                   :name="tab.name"
                   :key="tab.name">
        <record :type="tab.type" v-if="activeTab === tab.name" />
      </el-tab-pane>
    </el-tabs>
    <form-dia ref="formDia"
              @close="handleQuery"
              @success="handleQuery"></form-dia>
    <form-dia-product ref="formDiaProduct"
                      @close="handleQuery"
                      @success="handleQuery"></form-dia-product>
  </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import {
  ref,
  reactive,
  toRefs,
  onMounted,
  getCurrentInstance,
  nextTick,
} from "vue";
import {ElMessageBox} from "element-plus";
import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
import {
  getStockInPage,
  getStockInPageByProduction,
  getStockInPageByProductProduction,
  delStockIn,
} from "@/api/inventoryManagement/stockIn.js";
import FormDia from "./components/formDia.vue";
import FormDiaProduct from "./components/formDiaProduct.vue";
import Record from "@/views/inventoryManagement/receiptManagement/Record.vue";
// èŽ·å–å½“å‰æ—¥æœŸ
function getCurrentDate() {
  return dayjs().format("YYYY-MM-DD");
}
const {proxy} = getCurrentInstance();
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const formDia = ref();
const formDiaProduct = ref();
const activeTab = ref("production"); // å½“前激活的 tab
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const data = reactive({
  searchForm: {
    supplierName: "",
    customerName: "",
    productCategory: "",
    timeStr: "",
const activeTab = ref('qualified')
const type = ref(0)
const tabs = ref([
  {
    label: '合格入库',
    name: 'qualified',
    type: '0'
  },
});
const {searchForm} = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const paginationChange = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const pageProductChange = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const params = {...page};
  // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹ä¼ é€’不同的查询参数
  if (activeTab.value === "production") {
    params.customerName = searchForm.value.customerName;
    params.timeStr = searchForm.value.timeStr;
  } else {
    params.supplierName = searchForm.value.supplierName;
    params.timeStr = searchForm.value.timeStr;
  {
    label: '不合格入库',
    name: 'unqualified',
    type: '1'
  }
  params.productCategory = searchForm.value.productCategory;
  if (activeTab.value === "product") {
    getStockInPageByProductProduction(params)
        .then(res => {
          tableLoading.value = false;
          tableData.value = res.data.records;
        });
])
  }else {
    // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹è°ƒç”¨ä¸åŒçš„æŽ¥å£
    const apiCall =
        activeTab.value === "production"
            ? getStockInPageByProduction(params)
            : getStockInPage(params);
    apiCall
        .then(res => {
          tableLoading.value = false;
          tableData.value = res.data.records;
          // å‰ç«¯è®¡ç®—总价:总价 = unitPrice * inboundNum
          tableData.value = tableData.value.map(item => {
            // ä½¿ç”¨å…¥åº“数量计算总价
            const inboundNum = Number(item.inboundNum) || 0;
            const unitPrice = Number(item.unitPrice) || 0;
            const taxInclusiveUnitPrice = Number(item.taxInclusiveUnitPrice) || 0;
            // æ ¹æ®æ ‡ç­¾é¡µç±»åž‹è®¡ç®—不同的总价
            if (activeTab.value === "production") {
              // æˆå“åº“存:总价 = unitPrice * å…¥åº“数量
              item.totalPrice = (unitPrice * inboundNum).toFixed(2);
            } else {
              // åŽŸæ–™å’Œææ–™åº“å­˜ï¼šå«ç¨Žæ€»ä»· = taxInclusiveUnitPrice * å…¥åº“数量
              item.taxInclusiveTotalPrice = (
                  taxInclusiveUnitPrice * inboundNum
              ).toFixed(2);
            }
            return item;
          });
          total.value = res.data.total;
        })
        .catch(() => {
          tableLoading.value = false;
        });
  }
};
// åˆ‡æ¢ tab
const handleTabChange = tabName => {
  page.current = 1;
  // åˆ‡æ¢ tab æ—¶æ¸…空搜索条件
  searchForm.value.supplierName = "";
  searchForm.value.customerName = "";
  searchForm.value.timeStr = "";
  searchForm.value.productCategory = "";
  getList();
};
// æ‰“开弹框
const openForm = async (type, row, tabType) => {
  const currentTab = tabType || activeTab.value;
  await nextTick(() => {
    if (currentTab === "production") {
      formDiaProduct.value?.openDialog(type, row);
    } else {
      formDia.value?.openDialog(type, row);
    }
  });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = selection => {
  selectedRows.value = selection.filter(item => item.id);
};
const expandedRowKeys = ref([]);
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = param => {
  return proxy.summarizeTable(param, [
    "contractAmount",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
  ]);
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹è°ƒç”¨ä¸åŒçš„导出接口
        let exportUrl = "/stockin/export";
        if (activeTab.value === "production") {
          exportUrl = "/stockin/exportOne";
        }
        proxy.download(exportUrl, {}, "入库台账.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// åˆ é™¤
const handleDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  const ids = selectedRows.value.map(item => item.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        // æ ¹æ®å½“前 tab ç±»åž‹é€‰æ‹©ä¸åŒçš„删除接口和type参数
        let deleteApi, deleteParams;
        if (activeTab.value === "production") {
          // æˆå“åˆ é™¤ï¼Œtypeä¼ 2
          deleteApi = delStockIn;
          deleteParams = {ids, type: 2};
        } else {
          // åŽŸæ–™åˆ é™¤ï¼Œtypeä¼ 1
          deleteApi = delStockIn;
          deleteParams = {ids, type: 1};
        }
        deleteApi(deleteParams)
            .then(() => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("删除失败");
            });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
onMounted(() => {
  getList();
});
const handleTabChange = (tabName) => {
  activeTab.value = tabName;
  type.value = tabName === 'qualified' ? 0 : 1
}
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/stockManagement/New.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,180 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="新增库存"
        width="800"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true">
            {{ formState.productName ? formState.productName : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.productModelName"  disabled />
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
        >
          <el-input v-model="formState.unit"  disabled />
        </el-form-item>
        <el-form-item
            label="数量"
            prop="qualitity"
        >
          <el-input-number v-model="formState.qualitity" :step="1" :min="0" style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import {createStockInventory} from "@/api/inventoryManagement/stockInventory.js";
import {createStockUnInventory} from "@/api/inventoryManagement/stockUninventory.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  type: {
    type: String,
    required: true,
    default: 'qualified',
  },
});
const emit = defineEmits(['update:visible', 'completed']);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  productModelName: "",
  unit: "",
  qualitity: 0,
  remark: '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
let { proxy } = getCurrentInstance()
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
    description: '',
  };
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择规格");
        return;
      }
      if (props.type === 'qualified') {
        createStockInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      } else {
        createStockUnInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      }
    }
  })
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/inventoryManagement/stockManagement/Qualified.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title ml10">产品大类:</span>
        <el-input v-model="searchForm.productName"
                  style="width: 240px"
                  placeholder="请输入"
                  clearable/>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
         <el-button type="primary" @click="isShowNewModal = true">新增库存</el-button>
        <el-button @click="handleOut">导出</el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="row => row.id" style="width: 100%"
        :row-class-name="tableRowClassName" height="calc(100vh - 18.5em)">
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="产品大类" prop="productName" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="model" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" show-overflow-tooltip />
        <el-table-column label="库存数量" prop="qualitity" show-overflow-tooltip />
        <el-table-column label="库存预警数量" prop="warnNum"  show-overflow-tooltip />
        <el-table-column label="备注" prop="remark"  show-overflow-tooltip />
        <el-table-column label="最近更新时间" prop="updateTime" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="60" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.qualitity === 0">领用</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper"
        :page="page.current" :limit="page.size" @pagination="paginationChange" />
    </div>
    <new-stock-inventory v-if="isShowNewModal"
                 v-model:visible="isShowNewModal"
                 type="qualified"
                 @completed="handleQuery" />
    <subtract-stock-inventory v-if="isShowSubtractModal"
                 v-model:visible="isShowSubtractModal"
                 :record="record"
                 @completed="handleQuery" />
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from "element-plus";
import { getStockInventoryListPage } from "@/api/inventoryManagement/stockInventory.js";
const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue"));
const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue"));
const { proxy } = getCurrentInstance()
const tableData = ref([])
const selectedRows = ref([])
const record = ref({})
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
// æ˜¯å¦æ˜¾ç¤ºæ–°å¢žå¼¹æ¡†
const isShowNewModal = ref(false)
// æ˜¯å¦æ˜¾ç¤ºé¢†ç”¨å¼¹æ¡†
const isShowSubtractModal = ref(false)
const data = reactive({
  searchForm: {
    productName: '',
  }
})
const { searchForm } = toRefs(data)
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1
  getList()
}
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList()
}
const getList = () => {
  tableLoading.value = true
  getStockInventoryListPage({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
    // æ•°æ®åŠ è½½å®ŒæˆåŽæ£€æŸ¥åº“å­˜
    // checkStockAndCreatePurchase();
  }).catch(() => {
    tableLoading.value = false
  })
}
// ç‚¹å‡»é¢†ç”¨
const showSubtractModal = (row) => {
  record.value = row
  isShowSubtractModal.value = true
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
  selectedRows.value = selection.filter(item => item.id);
  console.log('selection', selectedRows.value)
}
const expandedRowKeys = ref([])
// è¡¨æ ¼è¡Œç±»å
const tableRowClassName = ({ row }) => {
  const stock = Number(row?.inboundNum0 ?? 0);
  const warn = Number(row?.warnNum ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
    return '';
  }
  return stock < warn ? 'row-low-stock' : '';
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm(
    '是否确认导出?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    proxy.download("/stockInventory/exportStockInventory", {}, '库存信息.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
onMounted(() => {
  getList()
})
</script>
<style scoped lang="scss">
:deep(.row-low-stock td) {
  background-color: #fde2e2;
  color: #c45656;
}
:deep(.row-low-stock:hover > td) {
  background-color: #fcd4d4;
}
</style>
src/views/inventoryManagement/stockManagement/Subtract.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,199 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="领用"
        width="800"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true" disabled>
            {{ formState.productName ? formState.productName : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.model"  disabled />
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
        >
          <el-input v-model="formState.unit"  disabled />
        </el-form-item>
        <el-form-item
            label="数量"
            prop="qualitity"
        >
          <el-input-number v-model="formState.qualitity" :step="1" :min="1" :max="maxQuality" style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import {subtractStockInventory} from "@/api/inventoryManagement/stockInventory.js";
import {subtractStockUnInventory} from "@/api/inventoryManagement/stockUninventory.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  record: {
    type: Object,
    default: () => {},
  },
  type: {
    type: String,
    required: true,
    default: 'qualified',
  },
});
const emit = defineEmits(['update:visible', 'completed']);
onMounted(() => {
  initFormData()
})
const maxQuality = computed(() => {
  return props.record.qualitity ? props.record.qualitity :  0;
})
const initFormData = () => {
  if (props.record) {
    formState.value = {
      ...props.record,
    }
  }
}
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  model: "",
  unit: "",
  qualitity: 0,
  remark: '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
let { proxy } = getCurrentInstance()
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
    description: '',
  };
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    console.log(product)
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择规格");
        return;
      }
      if (props.type === 'qualified') {
        subtractStockInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      } else {
        subtractStockUnInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      }
    }
  })
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/inventoryManagement/stockManagement/Unqualified.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title ml10">产品大类:</span>
        <el-input v-model="searchForm.productName"
                  style="width: 240px"
                  placeholder="请输入"
                  clearable/>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
         <el-button type="primary" @click="isShowNewModal = true">新增库存</el-button>
        <el-button @click="handleOut">导出</el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="row => row.id" style="width: 100%"
        :row-class-name="tableRowClassName" height="calc(100vh - 18.5em)">
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="产品大类" prop="productName" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="model" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" show-overflow-tooltip />
        <el-table-column label="库存数量" prop="qualitity" show-overflow-tooltip />
        <el-table-column label="库存预警数量" prop="warnNum"  show-overflow-tooltip />
        <el-table-column label="备注" prop="remark"  show-overflow-tooltip />
        <el-table-column label="最近更新时间" prop="updateTime" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="60" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.qualitity === 0">领用</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper"
        :page="page.current" :limit="page.size" @pagination="paginationChange" />
    </div>
    <new-stock-inventory v-if="isShowNewModal"
                 v-model:visible="isShowNewModal"
                 type="unqualified"
                 @completed="handleQuery" />
    <subtract-stock-inventory v-if="isShowSubtractModal"
                 v-model:visible="isShowSubtractModal"
                 :record="record"
                 @completed="handleQuery" />
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from "element-plus";
import { getStockUninventoryListPage } from "@/api/inventoryManagement/stockUninventory.js";
const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue"));
const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue"));
const { proxy } = getCurrentInstance()
const tableData = ref([])
const selectedRows = ref([])
const record = ref({})
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
// æ˜¯å¦æ˜¾ç¤ºæ–°å¢žå¼¹æ¡†
const isShowNewModal = ref(false)
// æ˜¯å¦æ˜¾ç¤ºé¢†ç”¨å¼¹æ¡†
const isShowSubtractModal = ref(false)
const data = reactive({
  searchForm: {
    productName: '',
  }
})
const { searchForm } = toRefs(data)
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1
  getList()
}
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList()
}
const getList = () => {
  tableLoading.value = true
  getStockUninventoryListPage({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
    // æ•°æ®åŠ è½½å®ŒæˆåŽæ£€æŸ¥åº“å­˜
    // checkStockAndCreatePurchase();
  }).catch(() => {
    tableLoading.value = false
  })
}
// ç‚¹å‡»é¢†ç”¨
const showSubtractModal = (row) => {
  record.value = row
  isShowSubtractModal.value = true
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
  selectedRows.value = selection.filter(item => item.id);
  console.log('selection', selectedRows.value)
}
const expandedRowKeys = ref([])
// è¡¨æ ¼è¡Œç±»å
const tableRowClassName = ({ row }) => {
  const stock = Number(row?.inboundNum0 ?? 0);
  const warn = Number(row?.warnNum ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
    return '';
  }
  return stock < warn ? 'row-low-stock' : '';
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm(
    '是否确认导出?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    proxy.download("/stockin/exportCopy", {}, '库存信息.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
onMounted(() => {
  getList()
})
</script>
<style scoped lang="scss">
:deep(.row-low-stock td) {
  background-color: #fde2e2;
  color: #c45656;
}
:deep(.row-low-stock:hover > td) {
  background-color: #fcd4d4;
}
</style>
src/views/inventoryManagement/stockManagement/index.vue
@@ -1,419 +1,33 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">供应商名称:</span>
        <el-input v-model="searchForm.supplierName" style="width: 240px" placeholder="请输入" @change="handleQuery"
          clearable prefix-icon="Search" />
                <span class="search_title ml10">入库日期:</span>
                <el-date-picker
                    v-model="searchForm.timeStr"
                    type="date"
                    placeholder="请选择日期"
                    value-format="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    clearable
                    @change="handleQuery"
                />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
        <!-- <el-button type="primary" @click="openForm('add')">新增</el-button> -->
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="row => row.id" show-summary style="width: 100%"
        :row-class-name="tableRowClassName"
        :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)">
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="入库日期" prop="createTime" width="100" show-overflow-tooltip />
        <el-table-column label="供应商名称" prop="supplierName" width="240" show-overflow-tooltip />
        <el-table-column label="产品大类" prop="productCategory" width="100" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" width="200" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="80" show-overflow-tooltip />
        <el-table-column label="库存数量" prop="inboundNum0" width="100" show-overflow-tooltip />
        <el-table-column label="库存预警数量" prop="warnNum" width="130" show-overflow-tooltip />
        <el-table-column label="含税单价" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip />
        <el-table-column label="含税总价" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="税率(%)" prop="taxRate" width="100" show-overflow-tooltip />
        <el-table-column label="不含税总价" prop="taxExclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="入库人" prop="createBy" width="80" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="60" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row);">编辑</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper"
        :page="page.current" :limit="page.size" @pagination="paginationChange" />
    </div>
    <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增库存' : '编辑库存'" width="70%"
      @close="closeDia">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input disabled v-model="form.supplierName" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="产品大类:" prop="productId">
              <el-select disabled v-model="form.productCategory" placeholder="请选择" clearable filterable>
                <el-option v-for="item in productList" :key="item.id" :label="item.productName"
                           :value="item.productName" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="productManageId">
              <el-select disabled v-model="form.specificationModel" placeholder="请先选择产品大类" clearable filterable :disabled="!form.productCategory">
                <el-option v-for="item in productModelList" :key="item.id" :label="item.model"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item  label="单位:" prop="customerId">
              <el-input disabled v-model="form.unit" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="库存时间:" prop="projectName">
              <el-date-picker style="width: 100%" v-model="form.updateTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                type="date" placeholder="请选择" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="入库时间:" prop="projectName">
              <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                type="date" placeholder="请选择" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item  label="含税单价:" prop="customerId">
              <el-input disabled v-model="form.taxInclusiveUnitPrice" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item  label="含税总价:" prop="customerContractNo">
              <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item  label="税率:" prop="customerId">
              <el-input disabled v-model="form.taxRate" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="不含税总价:" prop="entryDate">
              <el-input disabled v-model="form.taxExclusiveTotalPrice" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="出库人:" prop="entryPerson">
              <el-select v-model="form.createUser" placeholder="请选择" clearable>
                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
              </el-select>
            </el-form-item>
          </el-col>
<!--          <el-col :span="12">-->
<!--          <el-form-item label="库存预警数量:" prop="warnNum">-->
<!--            <el-input v-model="form.warnNum" placeholder="请输入最低库存" clearable />-->
<!--          </el-form-item>-->
<!--        </el-col>-->
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
      <el-tab-pane v-for="tab in tabs"
                   :label="tab.label"
                   :name="tab.name"
                   :key="tab.name">
        <component :is="tab.name === 'qualified' ? QualifiedRecord : UnqualifiedRecord" />
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { productTreeList,modelList } from "@/api/basicData/product.js"
import { getCurrentDate } from "@/utils/index.js";
import {
  getStockManagePage,
  delStockManage,
} from "@/api/inventoryManagement/stockManage.js";
import {
  updateManagement,updateStockIn
} from "@/api/inventoryManagement/stockIn.js";
import QualifiedRecord from "@/views/inventoryManagement/stockManagement/Qualified.vue";
import UnqualifiedRecord from "@/views/inventoryManagement/stockManagement/Unqualified.vue";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const tableData = ref([])
const productData = ref([])
const selectedRows = ref([])
const userList = ref([])
const productList = ref([])
const productModelList = ref([])
// const customerOption = ref([])
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
const fileList = ref([])
const loading = ref(false);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref('')
const dialogFormVisible = ref(false)
const data = reactive({
  searchForm: {
    supplierName: '',
        timeStr: '',
const activeTab = ref('qualified')
const tabs = ref([
  {
    label: '合格库存',
    name: 'qualified'
  },
  form: {
    supplierId: null,
    supplierName: '',
    productId: null,
    productName: '',
    userId: userStore.userId,
    nickName: '',
    productModelId: null,
    model: '',
    unit: '',
    productrecordId: null,
    taxInclusiveUnitPrice: '',
    taxInclusiveTotalPrice: '',
    taxRate: '',
    taxExclusiveTotalPrice: '',
    inboundTime: '',
    inboundBatch: '',
    stockQuantity: '',
    boundTime: '',
        warnNum: '', // æ–°å¢žæœ€ä½Žåº“存字段
    salesLedgerProductId: null,
  },
  rules: {
    supplierName: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }],
    productCategory: [{ required: true, message: '请选择产品大类', trigger: 'change' }],
    specificationModel: [{ required: true, message: '请输入规格型号', trigger: 'blur' }],
    unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
    stockQuantity: [{ required: true, message: '请输入出库数量', trigger: 'blur' }],
    taxInclusiveUnitPrice: [{ required: true, message: '请输入含税单价', trigger: 'blur' }],
    taxInclusiveTotalPrice: [{ required: true, message: '请输入含税总价', trigger: 'blur' }],
    taxRate: [{ required: true, message: '请输入税率', trigger: 'blur' }],
    taxExclusiveTotalPrice: [{ required: true, message: '请输入不含税总价', trigger: 'blur' }],
    boundTime: [{ required: true, message: '请选择库存时间', trigger: 'change' }],
    inboundTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundPerson: [{ required: true, message: '请选择出库人', trigger: 'change' }],
        warnNum: [{ required: true, message: '请输入最低库存', trigger: 'blur' }],
  {
    label: '不合格库存',
    name: 'unqualified'
  }
})
const { searchForm, form, rules } = toRefs(data)
])
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1
  getList()
const handleTabChange = (tabName) => {
  activeTab.value = tabName;
}
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList()
}
const getList = () => {
  tableLoading.value = true
  getStockManagePage({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
    // æ•°æ®åŠ è½½å®ŒæˆåŽæ£€æŸ¥åº“å­˜
    // checkStockAndCreatePurchase();
  }).catch(() => {
    tableLoading.value = false
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
  selectedRows.value = selection.filter(item => item.id);
  console.log('selection', selectedRows.value)
}
const expandedRowKeys = ref([])
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']);
};
// è¡¨æ ¼è¡Œç±»å
const tableRowClassName = ({ row }) => {
  const stock = Number(row?.inboundNum0 ?? 0);
  const warn = Number(row?.warnNum ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
    return '';
  }
  return stock < warn ? 'row-low-stock' : '';
};
// æ‰“开弹框
const openForm = async (type, row) => {
  operationType.value = type
  form.value = {}
  productData.value = []
  let userLists = await userListNoPageByTenantId()
  userList.value = userLists.data
  if (type === 'edit') {
    form.value = { ...row }
    productTreeList().then(res =>{
      productList.value = res
      productList.value.forEach(i =>{
        if (i.label === row.productCategory) {
          modelList({ id: i.id }).then((res) => {
            productModelList.value = res;
          });
        }
      })
    })
  }
  form.value.entryDate = getCurrentDate() // è®¾ç½®é»˜è®¤å½•入日期为当前日期
  dialogFormVisible.value = true
}
// æäº¤è¡¨å•
const submitForm = () => {
  console.log(form.value)
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      updateManagement(form.value).then(res => {
        proxy.$modal.msgSuccess("提交成功")
        closeDia()
        getList()
        // æäº¤åŽæ£€æŸ¥åº“存并尝试创建请购单
        // checkStockAndCreatePurchase();
      })
    }
  })
}
// æ£€æŸ¥åº“存并创建请购单
// const checkStockAndCreatePurchase = async () => {
//   const stockList = tableData.value;
//   // handList()
//   for (const item of stockList) {
//     if (item.inboundNum0 < item.warnNum) {
//       try {
//                 const stockInData = {
//                     id: item.id,
//                     quantityStock: item.warnNum + item.totalInboundNum,// ä½¿ç”¨æ–°æ ¼å¼åŒ–函数
//                 };
//                 loading.value = true
//                 await updateStockIn(stockInData)
//                 proxy.$modal.msgSuccess(`产品 ${item.productCategory} ä¿®æ”¹å…¥åº“成功`)
//                 loading.value = false
//       } catch (error) {
//         proxy.$modal.msgError(`产品 ${item.productCategory} ç”Ÿæˆè¯·è´­å•失败,请手动处理`);
//
//       }
//     }
//   }
// };
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef")
  dialogFormVisible.value = false
}
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm(
    '是否确认导出?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    proxy.download("/stockin/exportCopy", {}, '库存信息.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// åˆ é™¤
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
        // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
        const unauthorizedData = selectedRows.value.filter(item => item.createUser !== userStore.id);
        if (unauthorizedData.length > 0) {
            proxy.$modal.msgWarning("不可删除他人维护的数据");
            return;
        }
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
    '选中的内容将被删除,是否确认删除?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    delStockManage({ids:ids}).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
onMounted(() => {
  getList()
  // checkStockAndCreatePurchase();
    // æ¯å°æ—¶æ£€æŸ¥ä¸€æ¬¡åº“å­˜
    // const intervalId = setInterval(checkStockAndCreatePurchase, 60 * 60 * 1000);
// onUnmounted(() => {
//   // ç»„件卸载时清除定时器
//   clearInterval(intervalId);
// });
})
</script>
<style scoped lang="scss">
:deep(.row-low-stock td) {
  background-color: #fde2e2;
  color: #c45656;
}
:deep(.row-low-stock:hover > td) {
  background-color: #fcd4d4;
}
</style>
src/views/productionManagement/productStructure/Detail/index.vue
@@ -2,133 +2,85 @@
  <div class="app-container">
    <PageHeader content="产品结构详情">
      <template #right-button>
        <el-button v-if="dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="addItem">添加
        <el-button v-if="!dataValue.isEdit && !isOrderPage" type="primary" @click="dataValue.isEdit = true">编辑
        </el-button>
        <el-button v-if="!dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="dataValue.isEdit = true">编辑
        <el-button v-if="dataValue.isEdit && !isOrderPage" type="primary" @click="cancelEdit">取消
        </el-button>
        <el-button v-if="dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="cancelEdit">取消
        </el-button>
        <el-button v-if="!isOrderPage"
                   type="primary"
                   :loading="dataValue.loading"
                   @click="submit"
                   :disabled="!dataValue.isEdit">确认
        <el-button v-if="!isOrderPage" type="primary" :loading="dataValue.loading" @click="submit"
          :disabled="!dataValue.isEdit">确认
        </el-button>
      </template>
    </PageHeader>
    <el-table
        :data="tableData"
        border
        :preserve-expanded-content="false"
        :default-expand-all="true"
        style="width: 100%"
    >
    <el-table :data="tableData" border :preserve-expanded-content="false" :default-expand-all="true"
      style="width: 100%">
      <el-table-column type="expand">
        <template #default="props">
          <el-form ref="form"
                   :model="dataValue">
            <el-table :data="dataValue.dataList"
                      style="width: 100%">
              <el-table-column prop="productName"
                               label="产品"/>
              <el-table-column prop="model"
                               label="规格">
          <el-form ref="form" :model="dataValue">
            <el-table :data="dataValue.dataList" row-key="tempId" default-expand-all
              :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" style="width: 100%">
              <el-table-column prop="productName" label="产品" />
              <el-table-column prop="model" label="规格">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                                :prop="`dataList.${$index}.model`"
                                :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-select v-model="row.model"
                               placeholder="请选择规格"
                               clearable
                               :disabled="!dataValue.isEdit"
                               style="width: 100%"
                               @visible-change="(v) => { if (v) openDialog($index) }">
                      <el-option v-if="row.model"
                                 :label="row.model"
                                 :value="row.model" />
                    :rules="[{ required: true, message: '请选择规格', trigger: ['blur', 'change'] }]" style="margin: 0">
                    <el-select v-model="row.model" placeholder="请选择规格" clearable
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                      style="width: 100%" @visible-change="(v) => { if (v) openDialog(row.tempId) }">
                      <el-option v-if="row.model" :label="row.model" :value="row.model" />
                    </el-select>
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="processId"
                               label="消耗工序">
              <el-table-column prop="processName" label="消耗工序">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.processId`"
                                :rules="[{ required: true, message: '请选择消耗工序', trigger: 'change' }]"
                                style="margin: 0">
                    <el-select v-model="row.processId"
                               placeholder="请选择"
                               filterable
                               clearable
                               style="width: 100%"
                               :disabled="!dataValue.isEdit">
                      <el-option v-for="item in dataValue.processOptions"
                                 :key="item.id"
                                 :label="item.name"
                                 :value="item.id" />
                  <el-form-item v-if="dataValue.isEdit"
                    :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '请选择消耗工序', trigger: 'change' }]"
                    style="margin: 0">
                    <el-select v-model="row.processId" placeholder="请选择" filterable clearable style="width: 100%"
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)">
                      <el-option v-for="item in dataValue.processOptions" :key="item.id" :label="item.name"
                        :value="item.id" />
                    </el-select>
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="unitQuantity"
                               label="单位产出所需数量">
              <el-table-column prop="unitQuantity" label="单位产出所需数量">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.unitQuantity`"
                                :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!dataValue.isEdit" />
                  <el-form-item v-if="dataValue.isEdit"
                    :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur', 'change'] }]"
                    style="margin: 0">
                    <el-input-number v-model="row.unitQuantity" :min="0" :precision="2" :step="1"
                      controls-position="right" style="width: 100%"
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column v-if="isOrderPage"
                               prop="demandedQuantity"
                               label="需求总量">
              <el-table-column v-if="isOrderPage" prop="demandedQuantity" label="需求总量">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.demandedQuantity`"
                                :rules="[{ required: true, message: '请输入需求总量', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input-number v-model="row.demandedQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!dataValue.isEdit" />
                  <el-form-item v-if="dataValue.isEdit"
                    :rules="[{ required: true, message: '请输入需求总量', trigger: ['blur', 'change'] }]" style="margin: 0">
                    <el-input-number v-model="row.demandedQuantity" :min="0" :precision="2" :step="1"
                      controls-position="right" style="width: 100%"
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="unit"
                               label="单位">
              <el-table-column prop="unit" label="单位">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.unit`"
                                :rules="[{ required: true, message: '请输入单位', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input v-model="row.unit"
                              placeholder="请输入单位"
                              clearable
                              :disabled="!dataValue.isEdit" />
                  <el-form-item v-if="dataValue.isEdit"
                    :rules="[{ required: true, message: '请输入单位', trigger: ['blur', 'change'] }]" style="margin: 0">
                    <el-input v-model="row.unit" placeholder="请输入单位" clearable
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column label="操作" fixed="right" width="100">
              <el-table-column label="操作" fixed="right" width="200">
                <template #default="{ row, $index }">
                  <el-button v-if="dataValue.isEdit"
                             type="danger"
                             text
                             @click="dataValue.dataList.splice($index, 1)">删除
                  <el-button
                    v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                    type="danger" text @click="removeItem(row.tempId)">删除
                  </el-button>
                  <el-button v-if="dataValue.isEdit" type="primary" text @click="addItem2(row.tempId)">添加
                  </el-button>
                </template>
              </el-table-column>
@@ -140,10 +92,8 @@
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="规格型号" prop="model" />
    </el-table>
    <product-select-dialog v-if="dataValue.showProductDialog"
                           v-model:model-value="dataValue.showProductDialog"
                           @confirm="handleProduct" />
    <product-select-dialog v-if="dataValue.showProductDialog" v-model:model-value="dataValue.showProductDialog"
      @confirm="handleProduct" />
  </div>
</template>
@@ -160,36 +110,41 @@
import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
import { list } from "@/api/productionManagement/productionProcess";
import { ElMessage } from "element-plus";
import {useRoute, useRouter} from "vue-router";
import { useRoute, useRouter } from "vue-router";
defineComponent({
  name: "StructureEdit",
});
const ProductSelectDialog = defineAsyncComponent(
    () => import("@/views/basicData/product/ProductSelectDialog.vue")
  () => import("@/views/basicData/product/ProductSelectDialog.vue")
);
const emit = defineEmits(["update:router"]);
const form = ref();
const route = useRoute()
const router = useRouter()
const route = useRoute();
const router = useRouter();
const routeId = computed({
  get() {
    return route.query.id;
  },
  set(val) {
    emit('update:router', val)
  }
    emit("update:router", val);
  },
});
// ä»Žè·¯ç”±å‚数获取产品信息
const routeBomNo = computed(() => route.query.bomNo || '');
const routeProductName = computed(() => route.query.productName || '');
const routeProductModelName = computed(() => route.query.productModelName || '');
const routeBomNo = computed(() => route.query.bomNo || "");
const routeProductName = computed(() => route.query.productName || "");
const routeProductModelName = computed(
  () => route.query.productModelName || ""
);
const routeOrderId = computed(() => route.query.orderId);
const pageType = computed(() => route.query.type);
const isOrderPage = computed(() => pageType.value === 'order' && routeOrderId.value);
const isOrderPage = computed(
  () => pageType.value === "order" && routeOrderId.value
);
const dataValue = reactive({
  dataList: [],
@@ -197,6 +152,7 @@
  processOptions: [],
  showProductDialog: false,
  currentRowIndex: null,
  currentRowName: null,
  loading: false,
  isEdit: false,
});
@@ -206,11 +162,12 @@
    productName: "",
    model: "",
    bomNo: "",
  }
])
  },
]);
const openDialog = index => {
  dataValue.currentRowIndex = index;
const openDialog = (tempId: any) => {
  console.log(tempId, "tempId");
  dataValue.currentRowName = tempId;
  dataValue.showProductDialog = true;
};
@@ -218,83 +175,275 @@
  if (isOrderPage.value) {
    // è®¢å•情况:使用订单的产品结构接口
    const { data } = await listProcessBom({ orderId: routeOrderId.value });
    dataValue.dataList = data || [];
    dataValue.dataList = (data as any) || [];
  } else {
    // éžè®¢å•情况:使用原来的接口
    const { data } = await queryList(routeId.value);
    dataValue.dataList = data || [];
    dataValue.dataList = (data as any) || [];
    // ä¸ºæ‰€æœ‰é¡¹åŠå…¶å­é¡¹è®¾ç½®name属性
    const setNameRecursively = (items: any[]) => {
      items.forEach((item: any) => {
        item.tempId = item.id;
        item.processName =
          dataValue.processOptions.find(option => option.id === item.processId)
            ?.name || "";
        if (item.children && item.children.length > 0) {
          setNameRecursively(item.children);
        }
      });
    };
    setNameRecursively(dataValue.dataList);
    console.log(dataValue.dataList, "dataValue.dataList");
  }
};
const fetchProcessOptions = async () => {
  const { data } = await list(routeId.value);
  dataValue.processOptions = data;
  const { data } = await list();
  dataValue.processOptions = data as any;
};
const handleProduct = row => {
const handleProduct = (row: any) => {
  if (row?.length > 1) {
    ElMessage.error("只能选择一个产品");
  }
  dataValue.dataList[dataValue.currentRowIndex].productName =
      row[0].productName;
  dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
  dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
  dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
  const productData = row[0];
  //  æœ€å¤–层组件中,与当前产品相同的产品只能有一个
  const isTopLevel = dataValue.dataList.some(item => (item as any).tempId === dataValue.currentRowName);
  if (isTopLevel) {
    if (productData.productName === tableData[0].productName &&
      productData.model === tableData[0].model) {
      //  æŸ¥æ‰¾æ˜¯å¦å·²ç»æœ‰å…¶ä»–顶层行已经是这个产品
      const hasOther = dataValue.dataList.some(item =>
        (item as any).tempId !== dataValue.currentRowName &&
        (item as any).productName === tableData[0].productName &&
        (item as any).model === tableData[0].model
      );
      if (hasOther) {
        ElMessage.warning("最外层和当前产品一样的一级只能有一个");
        return;
      }
    }
  }
  // dataValue.dataList[dataValue.currentRowIndex].productName =
  //   row[0].productName;
  // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
  // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
  // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
  dataValue.dataList.map(item => {
    if (item.tempId === dataValue.currentRowName) {
      item.productName = productData.productName;
      item.model = productData.model;
      item.productModelId = productData.id;
      item.unit = productData.unit || "";
      return;
    }
    childItem(item, dataValue.currentRowName, productData);
  });
  dataValue.showProductDialog = false;
};
const childItem = (item: any, tempId: any, productData: any) => {
  if (item.tempId === tempId) {
    item.productName = productData.productName;
    item.model = productData.model;
    item.productModelId = productData.id;
    item.unit = productData.unit || "";
    return true;
  }
  if (item.children && item.children.length > 0) {
    for (let child of item.children) {
      if (childItem(child, tempId, productData)) {
        return true;
      }
    }
  }
  return false;
};
// é€’归校验所有层级的表单数据
const validateAll = () => {
  let isValid = true;
  // æ ¡éªŒå‡½æ•°
  const validateItem = (item: any, isTopLevel = false) => {
    // æ ¡éªŒå½“前项的必填字段
    if (!item.model) {
      ElMessage.error("请选择规格");
      isValid = false;
      return;
    }
    if (!isTopLevel && !item.processId) {
      ElMessage.error("请选择消耗工序");
      isValid = false;
      return;
    }
    if (!item.unitQuantity) {
      ElMessage.error("请输入单位产出所需数量");
      isValid = false;
      return;
    }
    if (isOrderPage.value && !item.demandedQuantity) {
      ElMessage.error("请输入需求总量");
      isValid = false;
      return;
    }
    if (!item.unit) {
      ElMessage.error("请输入单位");
      isValid = false;
      return;
    }
    // é€’归校验子项
    if (item.children && item.children.length > 0) {
      item.children.forEach(child => {
        validateItem(child, false);
      });
    }
  };
  // éåŽ†æ‰€æœ‰é¡¶å±‚é¡¹
  dataValue.dataList.forEach(item => {
    validateItem(item, true);
  });
  return isValid;
};
const submit = () => {
  form.value
      .validate(valid => {
        dataValue.loading = true;
        if (valid) {
          add({
            bomId: routeId.value,
            productStructureList: dataValue.dataList || [],
          }).then(res => {
            router.push({
              path: '/productionManagement/productionManagement/productStructure/index',
            })
            ElMessage.success("保存成功");
            dataValue.loading = false;
          });
        }
  dataValue.loading = true;
  // å…ˆè¿›è¡Œè¡¨å•校验
  const valid = validateAll();
  console.log(dataValue.dataList, "dataValue.dataList");
  if (valid) {
    add({
      bomId: routeId.value,
      children: dataValue.dataList || [],
    })
      .then(res => {
        router.push({
          path: "/productionManagement/productionManagement/productStructure/index",
        });
        ElMessage.success("保存成功");
        dataValue.loading = false;
      })
      .finally(() => {
      .catch(() => {
        dataValue.loading = false;
      });
  } else {
    dataValue.loading = false;
  }
};
const addItem = () => {
  dataValue.dataList.push({
    productName: "",
    productId: "",
    model: undefined,
    productModelId: undefined,
    processId: "",
    unitQuantity: 0,
    demandedQuantity: 0,
    unit: "",
const removeItem = (tempId: string) => {
  // å…ˆå°è¯•从顶层删除
  const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId);
  if (topIndex !== -1) {
    dataValue.dataList.splice(topIndex, 1);
    return;
  }
  // é€’归删除子项
  const delchildItem = (items: any[], tempId: any) => {
    for (let i = 0; i < items.length; i++) {
      const item = items[i];
      if (item.tempId === tempId) {
        items.splice(i, 1);
        return true;
      }
      if (item.children && item.children.length > 0) {
        if (delchildItem(item.children, tempId)) {
          return true;
        }
      }
    }
    return false;
  };
  dataValue.dataList.forEach(item => {
    if (item.children && item.children.length > 0) {
      delchildItem(item.children, tempId);
    }
  });
};
const addItem2 = tempId => {
  dataValue.dataList.map(item => {
    if (item.tempId === tempId) {
      if (!item.children) {
        item.children = [];
      }
      item.children.push({
        parentId: item.id || "",
        parentTempId: item.tempId || "",
        productName: "",
        productId: "",
        model: undefined,
        productModelId: undefined,
        processId: "",
        processName: "",
        unitQuantity: 0,
        demandedQuantity: 0,
        unit: "",
        children: [],
        tempId: new Date().getTime(),
      });
      return;
    }
    addchildItem(item, tempId);
  });
};
const addchildItem = (item: any, tempId: any) => {
  if (item.tempId === tempId) {
    console.log(item, "item");
    if (!item.children) {
      item.children = [];
    }
    item.children.push({
      parentId: item.id || "",
      parentTempId: item.tempId || "",
      productName: "",
      productId: "",
      model: undefined,
      productModelId: undefined,
      processId: "",
      unitQuantity: 0,
      demandedQuantity: 0,
      children: [],
      unit: "",
      tempId: new Date().getTime(),
    });
    return true;
  }
  if (item.children && item.children.length > 0) {
    for (let child of item.children) {
      if (addchildItem(child, tempId)) {
        return true;
      }
    }
  }
  return false;
};
const cancelEdit = () => {
  dataValue.isEdit = false;
  dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
  // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
  fetchData();
};
onMounted(() => {
onMounted(async () => {
  // ä»Žè·¯ç”±å‚数回显数据
  tableData[0].productName = routeProductName.value;
  tableData[0].model = routeProductModelName.value;
  tableData[0].bomNo = routeBomNo.value;
  tableData[0].productName = routeProductName.value as string;
  tableData[0].model = routeProductModelName.value as string;
  tableData[0].bomNo = routeBomNo.value as string;
  // è®¢å•情况下禁用编辑
  if (isOrderPage.value) {
    dataValue.isEdit = false;
  }
  fetchData();
  fetchProcessOptions();
  // å…ˆåŠ è½½å·¥åºé€‰é¡¹ï¼Œå†åŠ è½½æ•°æ®ï¼Œç¡®ä¿el-select能够正确回显
  await fetchProcessOptions();
  await fetchData();
});
</script>
src/views/salesManagement/salesLedger/index.vue
@@ -554,6 +554,7 @@
import { modelList, productTreeList } from "@/api/basicData/product.js";
import useFormData from "@/hooks/useFormData.js";
import dayjs from "dayjs";
import FileListDialog from "@/components/Dialog/FileListDialog.vue";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
@@ -1675,6 +1676,7 @@
 * @param row ä¸‹è½½æ–‡ä»¶çš„相关信息对象
 */
const fileListRef = ref(null)
const fileListDialogVisible = ref(false);
const downLoadFile = (row) => {
  getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
    fileListRef.value.open(res.salesLedgerFiles)