liujt 2 недель назад
Родитель
Сommit
bfe0e070fe

+ 539 - 0
src/views/warehouseManagement/stocktaking/differences/components/assignDialog.vue

@@ -0,0 +1,539 @@
+<template>
+  <div>
+    <el-dialog
+      :title="`派单`"
+      append-to-body
+      :visible="visibleDialog"
+      :before-close="closeWindows"
+      width="80vw"
+    >
+      <div class="search_box">
+        <HeaderTitle title="基本信息" size="16px"></HeaderTitle>
+        <el-form :model="formData" :rules="rules" ref="formRef" label-width="110px">
+          <el-row :gutter="15">
+            <el-col :span="8">
+              <el-form-item label="差异确认单编码" prop="code">
+                <el-input
+                  disabled
+                  v-model="formData.code"
+                  placeholder="请输入"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="关联盘点工单号" prop="name">
+                <el-input
+                  disabled
+                  v-model="formData.name"
+                  placeholder="请输入"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="盘点类型" prop="inventoryType">
+                <el-select
+                  v-model="formData.inventoryType"
+                  class="w100"
+                  placeholder="请选择盘点类型"
+                  disabled
+                  filterable
+                >
+                  <el-option
+                    v-for="item in inventoryTypeOp"
+                    :key="item.value"
+                    :value="item.value"
+                    :label="item.label"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="盘点精度" prop="dimension">
+                <el-select
+                  style="width: 100%"
+                  disabled
+                  v-model="formData.dimension"
+                  placeholder="请选择维度"
+                >
+                  <el-option label="物品维度" :value="1"> </el-option>
+                  <el-option label="批次维度" :value="2"> </el-option>
+                  <el-option label="包装维度" :value="3"> </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="冻结策略" prop="strategy">
+                <el-select
+                  v-model="formData.strategy"
+                  class="w100"
+                  placeholder="请选择"
+                  disabled
+                  filterable
+                >
+                  <el-option :value="1" label="库存快照(不锁库)"></el-option>
+                  <el-option :value="2" label="仓库锁"></el-option>
+                  <el-option :value="3" label="库区锁"></el-option>
+                  <el-option :value="4" label="货架锁"></el-option>
+                  <el-option :value="5" label="货位锁"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="仓库" prop="warehouseName">
+                <!-- <el-select
+                      style="width: 100%"
+                      v-model="formData.warehouseId"
+                      class="w100"
+                      placeholder="选择仓库"
+                      disabled
+                      filterable
+                    >
+                      <el-option v-for="item in warehouseList" :key="item.id" :value="item.id" :label="item.name"></el-option>
+                    </el-select> -->
+                    <el-input
+                      disabled
+                      v-model="formData.warehouseName"
+                      placeholder="请输入"
+                    ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="库区" prop="areaId">
+                <el-select
+                      style="width: 100%"
+                      v-model="formData.areaId"
+                      class="w100"
+                      placeholder="选择库区"
+                      filterable
+                      @change="changeArea"
+                    >
+                      <el-option v-for="item in areaList" :key="item.id" :value="item.id" :label="item.name"></el-option>
+                    </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="货架" prop="shelfId">
+                <el-select
+                      style="width: 100%"
+                      v-model="formData.shelfId"
+                      class="w100"
+                      placeholder="选择货架"
+                      filterable
+                      @change="changeShelf"
+                    >
+                      <el-option v-for="item in shelfList" :key="item.id" :value="item.id" :label="item.goodsshelvesCode"></el-option>
+                    </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="货位" prop="locationId">
+                <el-select
+                      style="width: 100%"
+                      v-model="formData.locationId"
+                      class="w100"
+                      placeholder="选择货位"
+                      filterable
+                    >
+                      <el-option v-for="item in locationList" :key="item.id" :value="item.id" :label="item.goodsAllocationCode"></el-option>
+                    </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="执行部门" prop="executeGroupId">
+                <deptSelect
+                  v-model="formData.executeGroupId"
+                  @changeGroup="searchDeptNodeClick"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="执行人员" prop="executorId">
+                <el-select
+                  v-model="formData.executorId"
+                  @change="executorChange"
+                  size="small"
+                  style="width: 100%"
+                  filterable
+                >
+                  <el-option
+                    v-for="item in executorList"
+                    :key="item.id"
+                    :value="item.id"
+                    :label="item.name"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+      </div>
+      <HeaderTitle title="差异清单" size="16px"></HeaderTitle>
+      <div class="flex_info">
+        <div class="flex_right">
+          <el-table
+            ref="multipleTable"
+            :data="tableData"
+            border
+            tooltip-effect="dark"
+            height="455px"
+            style="width: 100%"
+            @selection-change="handleSelectionChange"
+          >
+            <el-table-column
+              type="selection"
+              width="55"
+              align="center"
+              fixed="left"
+            >
+            </el-table-column>
+            <el-table-column label="序号" type="index" width="50">
+            </el-table-column>
+            <el-table-column
+              label="实盘数"
+              prop="info.actualCount"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="盘点结果"
+              prop="info.actualCount"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="盘点说明"
+              prop="info.actualCount"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="盘点差异数"
+              prop="info.actualCount"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="差异原因"
+              prop="info.actualCount"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="差异拍照"
+              prop="info.actualCount"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="物品编码"
+              prop="info.categoryCode"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="物品编码"
+              prop="info.categoryCode"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="物品名称"
+              prop="info.categoryName"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              width="150"
+              prop="info.categoryLevelPath"
+              label="产品分类"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="批次号"
+              prop="info.batchNo"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="牌号"
+              prop="info.brandNum"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="型号"
+              prop="info.categoryModel"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="规格"
+              prop="info.specification"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="计量数量"
+              prop="info.measureQuantity"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="计量单位"
+              prop="info.measureUnit"
+              :show-overflow-tooltip="true"
+            ></el-table-column>
+            <el-table-column
+              label="重量"
+              prop="info.weight"
+              :show-overflow-tooltip="true"
+            >
+            </el-table-column>
+            <el-table-column
+              label="重量单位"
+              prop="info.weightUnit"
+              :show-overflow-tooltip="true"
+            >
+            </el-table-column>
+            <el-table-column
+              label="仓库名称"
+              prop="info.warehouseName"
+              :show-overflow-tooltip="true"
+            >
+            </el-table-column>
+            <el-table-column
+              label="库区"
+              prop="info.warehouseName"
+              :show-overflow-tooltip="true"
+            >
+            </el-table-column>
+            <el-table-column
+              label="货架"
+              prop="info.warehouseName"
+              :show-overflow-tooltip="true"
+            >
+            </el-table-column>
+            <el-table-column
+              label="货位"
+              prop="info.warehouseName"
+              :show-overflow-tooltip="true"
+            >
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+      <div class="select-btn">
+        <el-button type="primary" @click="onConfirm">确定</el-button>
+        <el-button @click="closeWindows">关闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  import { getInventoryPlanDetailById } from '@/api/inventory';
+  import deptSelect from '@/components/CommomSelect/new-dept-select.vue';
+  import { getUserPage } from '@/api/system/organization';
+  import { sendOrders } from '@/api/inventory';
+  import { fieldModel } from '@/api/codeManagement';
+  import warehouseDefinition from '@/api/warehouseManagement/warehouseDefinition';
+  export default {
+    props: {
+      visibleDialog: {
+        type: Boolean,
+        default: false
+      },
+      planId: {
+        type: String,
+        default: ''
+      }
+    },
+    watch: {
+      tableData() {
+        this.doLayout();
+      },
+      visibleDialog(val) {
+        if (val) {
+          this._getInformation();
+        }
+      }
+    },
+    components: { deptSelect },
+    data() {
+      return {
+        inventoryTypeOp: [{
+          label: '库内盘点',
+          value: 1
+        },{
+          label: '库外盘点',
+          value: 2
+        }],
+        formData: {},
+        executorList: [],
+        factoryList: [],
+        factoryId: '',
+        value: '',
+        treeType: '',
+        treeList: [],
+        tableData: [],
+        multipleSelection: [],
+        pages1: { classificationIdList: [] },
+        pages: {
+          pageNum: 1,
+          size: 15
+        },
+        total: 0,
+        curType: '',
+        classificationId: '',
+        memoSelection: [],
+        newColumns: [], // 动态表头
+        rules: {
+          executeGroupId: [{ required: true, message: '请选择执行部门', trigger: 'blur' }],
+          executorId: [{ required: true, message: '请选择执行人员', trigger: 'blur' }],
+        },
+        warehouseList: [], // 盘点仓库
+        areaList: [], // 库区
+        shelfList: [], // 货架
+        locationList: [], // 货位
+      };
+    },
+    created() {
+      this.getFieldModel();
+    },
+    methods: {
+      async getAreaList() {
+        const res = await warehouseDefinition.getListByWarehouseId(this.formData.warehouseId);
+        console.log('getAreaList~~~', res);
+        this.areaList = res || [];
+      },
+      async getShelfList() {
+        const rep = await warehouseDefinition.getListByAreaId(this.formData.areaId);
+        this.shelfList = rep || [];
+        console.log('getShelfList~~~', this.shelfList);
+      },
+      async getLocationList() {
+        const rep = await warehouseDefinition.getListByGoodId(this.formData.shelfId);
+        this.locationList = rep || [];
+        console.log('getLocationList~~~', this.locationList);
+      },
+      changeArea(val) {
+        this.getShelfList();
+      },
+      changeShelf(val) {
+        this.getLocationList();
+      },
+      executorChange(val) {
+        this.formData.executorName = this.executorList.filter(
+          (item) => item.id == val
+        )[0].name;
+      },
+      //选择部门(搜索)
+      searchDeptNodeClick(info, row) {
+        this.formData.executorId = '';
+        if (info) {
+          const params = { groupId: info };
+          this.getUserList(params);
+          this.formData.executeGroupName = row.name;
+        } else {
+          this.formData.executeGroupId = null;
+        }
+      },
+      // 获取审核人列表、巡点检人员
+      async getUserList(params) {
+        try {
+          let data = { pageNum: 1, size: -1 };
+          if (params) {
+            data = Object.assign(data, params);
+          }
+          const res = await getUserPage(data);
+          this.executorList = res.list;
+        } catch (error) {}
+      },
+      doLayout() {
+        let that = this;
+        this.$nextTick(() => {
+          that.$refs.multipleTable.doLayout();
+        });
+      },
+      // 获取动态表头
+      getFieldModel() {
+        fieldModel({ fieldModel: 't_main_category' }).then((res) => {
+          let newRes = res.map((m) => {
+            return {
+              prop: 'extField.' + m.prop,
+              label: m.label,
+              align: 'center',
+              showOverflowTooltip: true
+            };
+          });
+          this.newColumns = [...newRes];
+        });
+      },
+      onConfirm() {
+        this.$refs.formRef.validate((valid) => {
+          if (valid) {
+            if (this.multipleSelection.length > 0) {
+              let params = {
+                executeGroupId: this.formData.executeGroupId,
+                executeGroupName: this.formData.executeGroupName,
+                executorId: this.formData.executorId,
+                executorName: this.formData.executorName,
+                planDetailId: this.multipleSelection.map((item) => item.id),
+                planId: this.planId
+              };
+              sendOrders(params).then(() => {
+                this.$message.success('派单成功!');
+                this.$emit('flushed');
+                this.closeWindows();
+              });
+            } else {
+              this.$message.error('请选择物品!');
+            }
+          }
+        })
+      },
+      handleSelectionChange(val) {
+        this.multipleSelection = val;
+      },
+      closeWindows() {
+        this.multipleSelection = [];
+        this.$emit('update:visibleDialog', false);
+      },
+      async _getInformation() {
+        getInventoryPlanDetailById(this.planId).then((data) => {
+          this.formData = data;
+          console.log('getInventoryPlanDetailById~~~', data);
+          if(data.warehouseId) {
+            this.getAreaList();
+          }
+          if(data.executeGroupId) {
+            this.getUserList({ groupId: data.executeGroupId });
+          }
+          
+          this.tableData = data.planDetailVOS;
+        });
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-checkbox__input.is-disabled .el-checkbox__inner) {
+    background-color: #eee !important;
+  }
+  .flex_info {
+    display: flex;
+    margin-top: 20px;
+    margin-bottom: 20px;
+    .flex_left {
+      width: 202px;
+      height: 434px;
+      padding: 10px;
+      border: 1px solid #d7d7d7;
+      overflow-y: auto;
+    }
+    .flex_right {
+      flex: 1;
+      margin-left: 10px;
+      overflow: hidden;
+    }
+  }
+  .search_box {
+    overflow: hidden;
+  }
+  .select-btn {
+    text-align: right;
+  }
+  .right {
+    text-align: right;
+  }
+  .mt10 {
+    margin-top: 10px;
+  }
+</style>

+ 832 - 0
src/views/warehouseManagement/stocktaking/differences/components/detailDialog.vue

@@ -0,0 +1,832 @@
+<template>
+  <div>
+    <ele-modal
+      :title="`盘点报工`"
+      append-to-body
+      :visible.sync="visibleDialog"
+      destroy-on-close
+      @close="closeWindows"
+      width="90vw"
+      :maxable="true"
+      :resizable="true"
+    >
+      <div class="dispose-dialog">
+        <HeaderTitle title="基本信息" size="16px"></HeaderTitle>
+        <!-- 搜索表单 -->
+        <el-form
+          label-width="90px"
+          class="ele-form-search"
+          @keyup.enter.native="search"
+          @submit.native.prevent
+        >
+          <el-row :gutter="15">
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="复盘工单编码" prop="code">
+                <el-input
+                  disabled
+                  v-model="filterForm.code"
+                  placeholder="请输入"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="关联盘点工单号" prop="name">
+                <el-input
+                  disabled
+                  v-model="filterForm.name"
+                  placeholder="请输入"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="盘点类型" prop="inventoryType">
+                <el-select
+                  v-model="filterForm.inventoryType"
+                  class="w100"
+                  placeholder="请选择盘点类型"
+                  disabled
+                  filterable
+                >
+                  <el-option
+                    v-for="item in inventoryTypeOp"
+                    :key="item.value"
+                    :value="item.value"
+                    :label="item.label"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="盘点精度" prop="dimension">
+                <el-select
+                  style="width: 100%"
+                  disabled
+                  v-model="filterForm.dimension"
+                  placeholder="请选择维度"
+                >
+                  <el-option label="物品维度" :value="1"> </el-option>
+                  <el-option label="批次维度" :value="2"> </el-option>
+                  <el-option label="包装维度" :value="3"> </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="冻结策略" prop="strategy">
+                <el-select
+                  v-model="filterForm.strategy"
+                  class="w100"
+                  placeholder="请选择"
+                  disabled
+                  filterable
+                >
+                  <el-option :value="1" label="库存快照(不锁库)"></el-option>
+                  <el-option :value="2" label="仓库锁"></el-option>
+                  <el-option :value="3" label="库区锁"></el-option>
+                  <el-option :value="4" label="货架锁"></el-option>
+                  <el-option :value="5" label="货位锁"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="仓库">
+                <el-input disabled v-model="filterForm.warehouseName" />
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="库区">
+                <el-select
+                  v-model="filterForm.areaId"
+                  class="w100"
+                  placeholder="选择库区"
+                  filterable
+                  @change="changeArea"
+                >
+                  <el-option v-for="item in areaList" :key="item.id" :value="item.id" :label="item.name" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="货架">
+                <el-select
+                  v-model="filterForm.shelfId"
+                  class="w100"
+                  placeholder="选择货架"
+                  filterable
+                  @change="changeShelf"
+                >
+                  <el-option v-for="item in shelfList" :key="item.id" :value="item.id" :label="item.goodsshelvesCode" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="货架">
+                <el-select
+                  v-model="filterForm.shelfId"
+                  class="w100"
+                  placeholder="选择货架"
+                  filterable
+                  @change="changeShelf"
+                >
+                  <el-option v-for="item in shelfList" :key="item.id" :value="item.id" :label="item.goodsshelvesCode" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="执行部门" prop="executeGroupId">
+                <deptSelect
+                  v-model="filterForm.executeGroupId"
+                  @changeGroup="searchDeptNodeClick"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="执行人员" prop="executorId">
+                <el-select
+                  v-model="filterForm.executorId"
+                  @change="executorChange"
+                  size="small"
+                  style="width: 100%"
+                  filterable
+                >
+                  <el-option
+                    v-for="item in executorList"
+                    :key="item.id"
+                    :value="item.id"
+                    :label="item.name"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 12, md: 12 } : { span: 12 }">
+              <div class="ele-form-actions">
+                <el-button
+                  type="primary"
+                  icon="el-icon-search"
+                  class="ele-btn-icon"
+                  @click="search"
+                  size="small"
+                >查询</el-button>
+                <el-button
+                  @click="resetFilter"
+                  icon="el-icon-refresh-left"
+                  size="small"
+                  type="primary"
+                >重置</el-button>
+              </div>
+            </el-col>
+          </el-row>
+        </el-form>
+
+        <!-- 差异清单 -->
+         <HeaderTitle title="差异清单" size="16px"></HeaderTitle>
+         <el-form
+          label-width="90px"
+          class="ele-form-search"
+          @keyup.enter.native="search"
+          @submit.native.prevent
+        >
+          <el-row :gutter="15">
+  
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="处置类型" prop="disposeType">
+                <el-select
+                  v-model="filterForm.disposeType"
+                  class="w100"
+                  placeholder="请选择"
+                  disabled
+                  filterable
+                >
+                  <el-option :value="1" label="补录入库"></el-option>
+                  <el-option :value="2" label="报损出库"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="处置备注">
+                <el-input disabled v-model="filterForm.remark" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+        <ele-pro-table
+          ref="table"
+          :columns="columns"
+          height="calc(70vh - 120px)"
+          :page-size="20"
+          :page-sizes="[10, 20, 50, 100]"
+          :datasource="datasource"
+          row-key="rowKey"
+          :where="searchWhere"
+          :needPage="false"
+          :init-load="false"
+        >
+
+          <!-- 表头工具栏 -->
+          <template v-slot:toolbar>
+
+            <!-- <div style="display: flex; justify-content: space-between; align-items: center;">
+              <div>
+                <span class="dispose-stat">
+                  总行数:<b>{{ statistics.total }}</b> |
+                  已盘行数:<b>{{ statistics.confirmed }}</b> |
+                  未盘行数:<b>{{ statistics.unconfirmed }}</b> |
+                  正常:<b>{{ statistics.normal }}</b> |
+                  盘盈:<b>{{ statistics.surplus }}</b> |
+                  盘亏:<b>{{ statistics.lose }}</b> |
+                  无账货:<b>{{ statistics.unlisted }}</b>
+                </span>
+              </div>
+              <div style="padding-right: 10px;">
+                <el-button type="primary" @click="confirmAll">全部确认</el-button>
+                <el-button type="primary" @click="cancelAll">全部取消</el-button>
+                <el-button @click="addUnlisted">新增无账货</el-button>
+              </div>
+            </div> -->
+          </template>
+          <template v-slot:categoryName="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.categoryName" size="small" placeholder="请输入" />
+            </template>
+            <span v-else>{{ row.categoryName }}</span>
+          </template>
+
+          <template v-slot:categoryCode="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.categoryCode" size="small" placeholder="编码" />
+            </template>
+            <span v-else>{{ row.info?.categoryCode || row.categoryCode || '' }}</span>
+          </template>
+
+          <template v-slot:batchNo="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.batchNo" size="small" placeholder="请输入" />
+            </template>
+            <span v-else>{{ row.batchNo }}</span>
+          </template>
+
+          <template v-slot:packageNo="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.packageNo" size="small" placeholder="包装号" />
+            </template>
+            <span v-else>{{ row.packageNo }}</span>
+          </template>
+
+          <template v-slot:measureQuantity="{ row }">
+            <span v-if="row._isUnlisted">-</span>
+            <span v-else>{{ formatNumber(row.info?.measureQuantity ?? row.measureQuantity) }}</span>
+          </template>
+
+          <template v-slot:measureUnit="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.measureUnit" size="small" placeholder="单位" />
+            </template>
+            <span v-else>{{ row.info?.measureUnit || row.measureUnit || '' }}</span>
+          </template>
+
+          <template v-slot:actualQuantity="{ row }">
+            <el-input-number
+              v-if="row._status === 0"
+              v-model="row.actualQuantity"
+              :min="0"
+              :precision="2"
+              size="small"
+              controls-position="right"
+              style="width: 100%"
+              @change="onActualChange(row)"
+            />
+            <span v-else>{{ formatNumber(row.actualQuantity) }}</span>
+          </template>
+
+          <template v-slot:result="{ row }">
+            <el-select
+              v-model="row.result"
+              placeholder="请选择"
+              size="small"
+              clearable
+              :disabled="row._status === 1"
+              @change="onFieldChange(row)"
+            >
+              <el-option
+                v-for="opt in resultOptions"
+                :key="opt.value"
+                :label="opt.label"
+                :value="opt.value"
+              />
+            </el-select>
+          </template>
+
+          <template v-slot:diffQuantity="{ row }">
+            <span>{{ calcDiffQuantity(row) }}</span>
+          </template>
+
+          <template v-slot:status="{ row }">
+            <el-tag
+              :type="row._status === 1 ? 'success' : 'warning'"
+              size="small"
+              effect="light"
+            >{{ row._status === 1 ? '已盘' : '未盘' }}</el-tag>
+          </template>
+
+          <template v-slot:description="{ row }">
+            <el-input
+              v-model="row.description"
+              placeholder="盘点说明"
+              size="small"
+              clearable
+              :disabled="row._status === 1"
+              @change="onFieldChange(row)"
+            />
+          </template>
+
+          <template v-slot:diffReason="{ row }">
+            <el-select
+              v-model="row.diffReason"
+              placeholder="请选择"
+              size="small"
+              clearable
+              filterable
+              :disabled="row._status === 1"
+              @change="onFieldChange(row)"
+            >
+              <el-option
+                v-for="opt in diffReasonOptions"
+                :key="opt.value"
+                :label="opt.label"
+                :value="opt.value"
+              />
+            </el-select>
+          </template>
+
+          <template v-slot:diffImages="{ row }">
+            <!-- <file-upload
+              v-model="row.diffImages"
+              :module="'stocktakingReport'"
+              :show-lib="false"
+              :limit="3"
+              :size="5"
+            /> -->
+            <fileMain v-model="row.diffImages"></fileMain>
+
+          </template>
+
+          <template v-slot:action="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-button type="text" size="mini" @click="removeUnlisted(row)">删除</el-button>
+            </template>
+            <template v-else>
+              <el-button
+                v-if="row._status === 0"
+                type="text"
+                size="mini"
+                @click="confirmRow(row)"
+              >确认</el-button>
+              <el-button
+                v-else
+                type="text"
+                size="mini"
+                @click="cancelRow(row)"
+              >取消</el-button>
+            </template>
+          </template>
+        </ele-pro-table>
+      </div>
+
+      <div slot="footer" class="dispose-footer">
+        <el-button @click="closeWindows">关闭</el-button>
+        <el-button @click="onSave">保存</el-button>
+        <el-button type="primary" :loading="reportLoading" @click="onReport">报工</el-button>
+      </div>
+    </ele-modal>
+  </div>
+</template>
+
+<script>
+import { getPlanDetailList, reportWork, saveReportWork } from '@/api/inventory';
+import warehouseDefinition from '@/api/warehouseManagement/warehouseDefinition';
+import deptSelect from '@/components/CommomSelect/new-dept-select.vue';
+
+export default {
+  components: { deptSelect },
+  data() {
+    return {
+      inventoryTypeOp: [{
+          label: '库内盘点',
+          value: 1
+        },{
+          label: '库外盘点',
+          value: 2
+        }],
+        executorList: [],
+      visibleDialog: false,
+      planOrderId: '',
+      // 表单查询条件
+      filterForm: {
+        warehouseName: '',
+        dimension: '',
+        areaId: '',
+        shelfId: '',
+        keyWord: '',
+        statusFilter: ''
+      },
+      areaList: [],
+      shelfList: [],
+      locationList: [],
+      reportLoading: false,
+      // 接口返回的原始行(保留编辑状态)
+      serverRows: [],
+      // 无账货行
+      unlistedRows: [],
+      // 差异原因字典
+      diffReasonOptions: [
+        { value: 1, label: '漏记账' },
+        { value: 2, label: '错记账' },
+        { value: 3, label: '丢失' },
+        { value: 4, label: '被盗' },
+        { value: 5, label: '毁损' },
+        { value: 6, label: '串货' },
+        { value: 7, label: '入库未确认' },
+        { value: 8, label: '出库未登记' },
+        { value: 9, label: '其他' }
+      ],
+      // 盘点结果选项
+      resultOptions: [
+        { value: 'normal', label: '正常' },
+        { value: 'surplus', label: '盘盈' },
+        { value: 'lose', label: '盘亏' },
+        { value: 'unlisted', label: '无账货' }
+      ],
+      // 表格列配置
+      columns: [
+        { columnKey: 'index', label: '序号', type: 'index', width: 55, align: 'center', fixed: 'left' },
+        { slot: 'actualQuantity', label: '实盘数', align: 'center', minWidth: 120 },
+        { slot: 'result', label: '盘点结果', align: 'center', minWidth: 100 },
+        { slot: 'diffQuantity', label: '盘点差异数', align: 'center', minWidth: 100 },
+        { slot: 'description', label: '盘点说明', align: 'center', minWidth: 140 },
+        { slot: 'diffReason', label: '差异原因', align: 'center', minWidth: 160 },
+        { slot: 'diffImages', label: '差异拍照', align: 'center', minWidth: 150 },
+        { slot: 'status', label: '盘点状态', align: 'center', minWidth: 90 },
+        { prop: 'categoryName', label: '物品名称', align: 'center', minWidth: 160, showOverflowTooltip: true },
+        { prop: 'info.categoryCode', label: '物品编码', align: 'center', minWidth: 130, showOverflowTooltip: true },
+        { prop: 'batchNo', label: '批次号', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.categoryModel', label: '型号', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.specification', label: '规格', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.measureQuantity', label: '计量数量', align: 'center', minWidth: 110, showOverflowTooltip: true },
+        { prop: 'info.measureUnit', label: '计量单位', align: 'center', minWidth: 100, showOverflowTooltip: true },
+        { prop: 'info.brandNum', label: '牌号', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.colorKey', label: '颜色', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.weight', label: '重量', align: 'center', minWidth: 110, showOverflowTooltip: true },
+        { prop: 'info.weightUnit', label: '重量单位', align: 'center', minWidth: 100, showOverflowTooltip: true },
+        { prop: 'info.warehouseName', label: '仓库名称', align: 'center', minWidth: 110, showOverflowTooltip: true },
+        { prop: 'info.warehouseArea', label: '库区', align: 'center', minWidth: 100, showOverflowTooltip: true },
+        { prop: 'info.shelf', label: '货架', align: 'center', minWidth: 110, showOverflowTooltip: true },
+        { prop: 'info.location', label: '货位', align: 'center', minWidth: 100, showOverflowTooltip: true },
+        { prop: 'info.reportTime', label: '报告时间', align: 'center', minWidth: 180, showOverflowTooltip: true },
+        { slot: 'action', label: '操作', align: 'center', width: 80, fixed: 'right', resizable: false }
+      ]
+    };
+  },
+  computed: {
+    styleResponsive() {
+      return this.$store.state.theme.styleResponsive;
+    },
+    searchWhere() {
+      const w = {};
+      if (this.filterForm.keyWord) w.keyWord = this.filterForm.keyWord;
+      return w;
+    },
+    statistics() {
+      const list = this.serverRows;
+      const unlisted = this.unlistedRows;
+      const total = list.length + unlisted.length;
+      const confirmed = list.filter((r) => r._status === 1).length;
+      const unconfirmed = list.filter((r) => r._status === 0).length;
+      const normal = list.filter((r) => r.result === 'normal').length;
+      const surplus = list.filter((r) => r.result === 'surplus').length;
+      const lose = list.filter((r) => r.result === 'lose').length;
+      return { total, confirmed, unconfirmed, normal, surplus, lose, unlisted: unlisted.length };
+    }
+  },
+  methods: {
+    //选择部门(搜索)
+    searchDeptNodeClick(info, row) {
+      this.formData.executorId = '';
+      if (info) {
+        const params = { groupId: info };
+        this.getUserList(params);
+        this.formData.executeGroupName = row.name;
+      } else {
+        this.formData.executeGroupId = null;
+      }
+    },
+    // 获取审核人列表、巡点检人员
+    async getUserList(params) {
+      try {
+        let data = { pageNum: 1, size: -1 };
+        if (params) {
+          data = Object.assign(data, params);
+        }
+        const res = await getUserPage(data);
+        this.executorList = res.list;
+      } catch (error) {}
+    },
+    executorChange(val) {
+      this.formData.executorName = this.executorList.filter(
+        (item) => item.id == val
+      )[0].name;
+    },
+    async getAreaList(warehouseId) {
+      const res = await warehouseDefinition.getListByWarehouseId(warehouseId);
+      this.areaList = res || [];
+    },
+    async getShelfList(areaId) {
+      const rep = await warehouseDefinition.getListByAreaId(areaId);
+      this.shelfList = rep || [];
+    },
+    changeArea(val) {
+      this.filterForm.shelfId = '';
+      this.getShelfList(val);
+    },
+    changeShelf() {},
+    // ============= 工具方法 =============
+    formatNumber(val) {
+      if (val == null || val === '') return '';
+      const n = parseFloat(val);
+      if (isNaN(n)) return val;
+      return n.toFixed(2).replace(/\.?0+$/, '') || '0';
+    },
+    // 盘点差异数 = 实盘数 - 计量数量
+    calcDiffQuantity(row) {
+      const actual = parseFloat(row.actualQuantity);
+      const expect = parseFloat(row._measure);
+      if (isNaN(actual) || isNaN(expect)) return '-';
+      const diff = actual - expect;
+      return (diff >= 0 ? '+' : '') + diff.toFixed(2).replace(/\.?0+$/, '') || '0';
+    },
+    onFieldChange(row) { row._dirty = true; },
+    // 实盘数变化时,自动计算盘点结果
+    onActualChange(row) {
+      row._dirty = true;
+      if (row._isUnlisted) {
+        row.result = 'unlisted';
+      } else if (row.actualQuantity != null && row.actualQuantity !== '') {
+        row.result = this.autoCalcResult(row);
+      }
+      this.$forceUpdate();
+    },
+    // 根据实盘数与账面数自动计算结果
+    autoCalcResult(row) {
+      const actual = parseFloat(row.actualQuantity);
+      const expect = parseFloat(row._measure);
+      if (isNaN(actual) || isNaN(expect)) return null;
+      if (actual > expect) return 'surplus';
+      if (actual < expect) return 'lose';
+      return 'normal';
+    },
+    confirmRow(row) {
+      if (row.actualQuantity == null || row.actualQuantity === '') {
+        this.$message.warning('请先填写实盘数'); return;
+      }
+      const key = row._isUnlisted ? 'unlisted' : row.result;
+      const map = { normal: '正常', surplus: '盘盈', lose: '盘亏', unlisted: '无账货' };
+      if (key && key !== 'normal' && !row.diffReason) {
+        this.$message.warning(`盘点结果为${map[key]},请选择差异原因`); return;
+      }
+      row._status = 1;
+      row._dirty = true;
+      this.$forceUpdate();
+    },
+    cancelRow(row) {
+      row._status = 0;
+      row._dirty = true;
+      this.$forceUpdate();
+    },
+    confirmAll() {
+      const allRows = [...this.serverRows, ...this.unlistedRows];
+      const emptyRows = allRows.filter((r) => r.actualQuantity == null || r.actualQuantity === '');
+      if (emptyRows.length > 0) {
+        const nums = emptyRows.map((r, i) => r._index || i + 1).join('、');
+        this.$message.warning(`序号:${nums},未盘,请录入实盘数。`); return;
+      }
+      const missingReason = this.serverRows.filter((r) => {
+        if (r._status === 1) return false;
+        const key = r.result;
+        return key && key !== 'normal' && !r.diffReason;
+      });
+      if (missingReason.length > 0) {
+        this.$message.warning('存在差异行未填写差异原因,请先确认或补录'); return;
+      }
+      this.serverRows.forEach((r) => { if (r._status === 0) r._status = 1; });
+      this.unlistedRows.forEach((r) => { r._status = 1; });
+      this.$message.success('全部确认成功');
+      this.$forceUpdate();
+    },
+    cancelAll() {
+      this.serverRows.forEach((r) => { r._status = 0; r._dirty = true; });
+      this.unlistedRows.forEach((r) => { r._status = 0; r._dirty = true; });
+      this.$message.success('已全部取消确认');
+      this.$forceUpdate();
+    },
+    addUnlisted() {
+      const row = {
+        rowKey: `unlisted-${Date.now()}-${Math.random()}`,
+        categoryName: '', categoryCode: '', batchNo: '', packageNo: '', measureUnit: '',
+        actualQuantity: null, result: 'unlisted', description: '', diffReason: null, diffImages: [],
+        _isUnlisted: true, _status: 0, _dirty: true, _measure: 0
+      };
+      this.unlistedRows.push(row);
+      // 触发 ele-pro-table 重载让无账货行显示
+      this._reloadTable();
+    },
+    removeUnlisted(row) {
+      const idx = this.unlistedRows.indexOf(row);
+      if (idx > -1) {
+        this.unlistedRows.splice(idx, 1);
+        this._reloadTable();
+      }
+    },
+    // ============= 数据加载 =============
+    datasource({ page, limit, where }) {
+      return getPlanDetailList({
+        pageNum: page,
+        size: limit,
+        planOrderId: this.planOrderId,
+        ...where
+      }).then((res) => {
+        const list = (res.list || []).map((item, idx) => {
+          const existing = this.serverRows.find((r) => r.id === item.id && !r._isUnlisted);
+          const row = existing ? { ...item, ...existing } : { ...item };
+          row.rowKey = item.id || `server-${idx}`;
+          row._status = row._status != null ? row._status : 0;
+          row._measure = parseFloat(item.info?.measureQuantity ?? item.measureQuantity) || 0;
+          if (!existing && row.actualQuantity != null && row.actualQuantity !== '') {
+            row.result = this.autoCalcResult(row);
+          }
+          return row;
+        });
+        this.serverRows = list;
+        const merged = [...list];
+        if (page === 1 && this.unlistedRows.length) {
+          merged.push(...this.unlistedRows);
+        }
+        return { list: merged, count: res.count || 0 };
+      });
+    },
+    search() {
+      this.$nextTick(() => this._reloadTable());
+    },
+    resetFilter() {
+      this.filterForm.keyWord = '';
+      this.filterForm.statusFilter = '';
+      this.search();
+    },
+    onFilterChange() {
+      this.search();
+    },
+    _reloadTable() {
+      if (this.$refs.table && this.$refs.table.reload) {
+        this.$refs.table.reload({ page: 1 });
+      }
+    },
+    // ============= 保存 / 报工 =============
+    buildReportPayload() {
+      const payload = [];
+      this.serverRows.forEach((row) => {
+        if (row._status !== 1) return;
+        const key = row.result;
+        if (!key) return;
+        const item = { id: row.id, planOrderId: this.planOrderId, planDetailId: row.id,
+          categoryId: row.categoryId, categoryName: row.categoryName, batchNo: row.batchNo,
+          packageNo: row.packageNo,
+          status: key === 'surplus' ? 2 : key === 'lose' ? 3 : 1,
+          description: row.description || '', diffReason: row.diffReason, diffImages: this.toImageUrls(row.diffImages) };
+        if (key === 'surplus') item.surplusQuantity = row.actualQuantity;
+        else if (key === 'lose') item.loseQuantity = row.actualQuantity;
+        else item.normalQuantity = row.actualQuantity;
+        payload.push(item);
+      });
+      this.unlistedRows.forEach((row) => {
+        if (!row.categoryName || row.actualQuantity == null) return;
+        payload.push({
+          planOrderId: this.planOrderId, categoryName: row.categoryName, categoryCode: row.categoryCode,
+          batchNo: row.batchNo, packageNo: row.packageNo, measureUnit: row.measureUnit,
+          status: 4, surplusQuantity: row.actualQuantity,
+          description: row.description || '', diffReason: row.diffReason, diffImages: this.toImageUrls(row.diffImages)
+        });
+      });
+      return payload;
+    },
+    buildSavePayload() {
+      const payload = [];
+      this.serverRows
+        .filter((r) => r._dirty && r.actualQuantity != null && r.actualQuantity !== '')
+        .forEach((r) => {
+          payload.push({ id: r.id, planOrderId: this.planOrderId, planDetailId: r.id,
+            actualQuantity: r.actualQuantity, status: r._status, description: r.description || '', diffReason: r.diffReason,
+            diffImages: this.toImageUrls(r.diffImages) });
+        });
+      return payload;
+    },
+    toImageUrls(images) {
+      if (!Array.isArray(images)) return [];
+      return images.map((i) => (typeof i === 'string' ? i : i.url || i.storePath || '')).filter(Boolean);
+    },
+    onSave() {
+      const payload = this.buildSavePayload();
+      if (payload.length === 0) { this.$message.info('没有需要保存的数据'); return; }
+      saveReportWork(payload, { planOrderId: this.planOrderId })
+        .then(() => { this.$message.success('保存成功'); this.serverRows.forEach((r) => (r._dirty = false)); })
+        .catch((e) => { this.$message.error(e?.message || '保存失败'); });
+    },
+    onReport() {
+      const allRows = [...this.serverRows, ...this.unlistedRows];
+      const noActual = allRows.filter((r) => r.actualQuantity == null || r.actualQuantity === '');
+      if (noActual.length > 0) {
+        const nums = noActual.map((r, i) => r._index || i + 1).join('、');
+        this.$message.warning(`序号:${nums},未盘,请录入实盘数。`); return;
+      }
+      const notConfirmed = allRows.filter((r) => r._status !== 1);
+      if (notConfirmed.length > 0) {
+        const nums = notConfirmed.map((r, i) => r._index || i + 1).join('、');
+        this.$message.warning(`序号:${nums},未盘,请先确认。`); return;
+      }
+      const noReason = this.serverRows.filter((r) => {
+        const key = r.result;
+        return key && key !== 'normal' && !r.diffReason;
+      });
+      if (noReason.length > 0) { this.$message.warning('存在盘点差异未填写差异原因,请补充'); return; }
+      const invalidUnlisted = this.unlistedRows.filter((r) => !r.categoryName || r.actualQuantity == null || !r.diffReason);
+      if (invalidUnlisted.length > 0) { this.$message.warning('请完善无账货行:物品名称、实盘数、差异原因'); return; }
+      const payload = this.buildReportPayload();
+      if (payload.length === 0) { this.$message.info('没有需要报工的数据'); return; }
+      this.reportLoading = true;
+      reportWork(payload, { planOrderId: this.planOrderId })
+        .then(() => { this.$message.success('报工成功'); this.$emit('flushed'); this.closeWindows(); })
+        .catch((e) => { this.$message.error(e?.message || '报工失败'); })
+        .finally(() => { this.reportLoading = false; });
+    },
+    open(id) {
+      this.planOrderId = id;
+      this.serverRows = [];
+      this.unlistedRows = [];
+      this.filterForm = {
+        warehouseName: '',
+        dimension: '',
+        areaId: '',
+        shelfId: '',
+        keyWord: '',
+        statusFilter: ''
+      };
+      this.areaList = [];
+      this.shelfList = [];
+      this.visibleDialog = true;
+      this.$nextTick(() => this._reloadTable());
+    },
+    closeWindows() {
+      this.visibleDialog = false;
+      this.serverRows = [];
+      this.unlistedRows = [];
+      this.planOrderId = '';
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox__input.is-disabled .el-checkbox__inner) {
+  background-color: #eee !important;
+}
+
+.dispose-dialog {
+  display: flex;
+  flex-direction: column;
+}
+
+.ele-form-actions {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+}
+
+.dispose-stat {
+  flex: 1;
+  min-width: 580px;
+  margin-left: 8px;
+  font-size: 13px;
+  color: #606266;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+
+  b {
+    color: #409eff;
+    font-weight: 600;
+    margin: 0 2px;
+  }
+}
+
+
+.dispose-footer {
+  text-align: right;
+  margin-top: 12px;
+}
+</style>

+ 832 - 0
src/views/warehouseManagement/stocktaking/differences/components/disposeDialog.vue

@@ -0,0 +1,832 @@
+<template>
+  <div>
+    <ele-modal
+      :title="`盘点报工`"
+      append-to-body
+      :visible.sync="visibleDialog"
+      destroy-on-close
+      @close="closeWindows"
+      width="90vw"
+      :maxable="true"
+      :resizable="true"
+    >
+      <div class="dispose-dialog">
+        <HeaderTitle title="基本信息" size="16px"></HeaderTitle>
+        <!-- 搜索表单 -->
+        <el-form
+          label-width="90px"
+          class="ele-form-search"
+          @keyup.enter.native="search"
+          @submit.native.prevent
+        >
+          <el-row :gutter="15">
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="复盘工单编码" prop="code">
+                <el-input
+                  disabled
+                  v-model="filterForm.code"
+                  placeholder="请输入"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="关联盘点工单号" prop="name">
+                <el-input
+                  disabled
+                  v-model="filterForm.name"
+                  placeholder="请输入"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="盘点类型" prop="inventoryType">
+                <el-select
+                  v-model="filterForm.inventoryType"
+                  class="w100"
+                  placeholder="请选择盘点类型"
+                  disabled
+                  filterable
+                >
+                  <el-option
+                    v-for="item in inventoryTypeOp"
+                    :key="item.value"
+                    :value="item.value"
+                    :label="item.label"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="盘点精度" prop="dimension">
+                <el-select
+                  style="width: 100%"
+                  disabled
+                  v-model="filterForm.dimension"
+                  placeholder="请选择维度"
+                >
+                  <el-option label="物品维度" :value="1"> </el-option>
+                  <el-option label="批次维度" :value="2"> </el-option>
+                  <el-option label="包装维度" :value="3"> </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="冻结策略" prop="strategy">
+                <el-select
+                  v-model="filterForm.strategy"
+                  class="w100"
+                  placeholder="请选择"
+                  disabled
+                  filterable
+                >
+                  <el-option :value="1" label="库存快照(不锁库)"></el-option>
+                  <el-option :value="2" label="仓库锁"></el-option>
+                  <el-option :value="3" label="库区锁"></el-option>
+                  <el-option :value="4" label="货架锁"></el-option>
+                  <el-option :value="5" label="货位锁"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="仓库">
+                <el-input disabled v-model="filterForm.warehouseName" />
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="库区">
+                <el-select
+                  v-model="filterForm.areaId"
+                  class="w100"
+                  placeholder="选择库区"
+                  filterable
+                  @change="changeArea"
+                >
+                  <el-option v-for="item in areaList" :key="item.id" :value="item.id" :label="item.name" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="货架">
+                <el-select
+                  v-model="filterForm.shelfId"
+                  class="w100"
+                  placeholder="选择货架"
+                  filterable
+                  @change="changeShelf"
+                >
+                  <el-option v-for="item in shelfList" :key="item.id" :value="item.id" :label="item.goodsshelvesCode" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="货架">
+                <el-select
+                  v-model="filterForm.shelfId"
+                  class="w100"
+                  placeholder="选择货架"
+                  filterable
+                  @change="changeShelf"
+                >
+                  <el-option v-for="item in shelfList" :key="item.id" :value="item.id" :label="item.goodsshelvesCode" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="执行部门" prop="executeGroupId">
+                <deptSelect
+                  v-model="filterForm.executeGroupId"
+                  @changeGroup="searchDeptNodeClick"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="执行人员" prop="executorId">
+                <el-select
+                  v-model="filterForm.executorId"
+                  @change="executorChange"
+                  size="small"
+                  style="width: 100%"
+                  filterable
+                >
+                  <el-option
+                    v-for="item in executorList"
+                    :key="item.id"
+                    :value="item.id"
+                    :label="item.name"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 12, md: 12 } : { span: 12 }">
+              <div class="ele-form-actions">
+                <el-button
+                  type="primary"
+                  icon="el-icon-search"
+                  class="ele-btn-icon"
+                  @click="search"
+                  size="small"
+                >查询</el-button>
+                <el-button
+                  @click="resetFilter"
+                  icon="el-icon-refresh-left"
+                  size="small"
+                  type="primary"
+                >重置</el-button>
+              </div>
+            </el-col>
+          </el-row>
+        </el-form>
+
+        <!-- 差异清单 -->
+         <HeaderTitle title="差异清单" size="16px"></HeaderTitle>
+         <el-form
+          label-width="90px"
+          class="ele-form-search"
+          @keyup.enter.native="search"
+          @submit.native.prevent
+        >
+          <el-row :gutter="15">
+  
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="处置类型" prop="disposeType">
+                <el-select
+                  v-model="filterForm.disposeType"
+                  class="w100"
+                  placeholder="请选择"
+                  disabled
+                  filterable
+                >
+                  <el-option :value="1" label="补录入库"></el-option>
+                  <el-option :value="2" label="报损出库"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
+              <el-form-item label="处置备注">
+                <el-input disabled v-model="filterForm.remark" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+        <ele-pro-table
+          ref="table"
+          :columns="columns"
+          height="calc(70vh - 120px)"
+          :page-size="20"
+          :page-sizes="[10, 20, 50, 100]"
+          :datasource="datasource"
+          row-key="rowKey"
+          :where="searchWhere"
+          :needPage="false"
+          :init-load="false"
+        >
+
+          <!-- 表头工具栏 -->
+          <template v-slot:toolbar>
+
+            <!-- <div style="display: flex; justify-content: space-between; align-items: center;">
+              <div>
+                <span class="dispose-stat">
+                  总行数:<b>{{ statistics.total }}</b> |
+                  已盘行数:<b>{{ statistics.confirmed }}</b> |
+                  未盘行数:<b>{{ statistics.unconfirmed }}</b> |
+                  正常:<b>{{ statistics.normal }}</b> |
+                  盘盈:<b>{{ statistics.surplus }}</b> |
+                  盘亏:<b>{{ statistics.lose }}</b> |
+                  无账货:<b>{{ statistics.unlisted }}</b>
+                </span>
+              </div>
+              <div style="padding-right: 10px;">
+                <el-button type="primary" @click="confirmAll">全部确认</el-button>
+                <el-button type="primary" @click="cancelAll">全部取消</el-button>
+                <el-button @click="addUnlisted">新增无账货</el-button>
+              </div>
+            </div> -->
+          </template>
+          <template v-slot:categoryName="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.categoryName" size="small" placeholder="请输入" />
+            </template>
+            <span v-else>{{ row.categoryName }}</span>
+          </template>
+
+          <template v-slot:categoryCode="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.categoryCode" size="small" placeholder="编码" />
+            </template>
+            <span v-else>{{ row.info?.categoryCode || row.categoryCode || '' }}</span>
+          </template>
+
+          <template v-slot:batchNo="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.batchNo" size="small" placeholder="请输入" />
+            </template>
+            <span v-else>{{ row.batchNo }}</span>
+          </template>
+
+          <template v-slot:packageNo="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.packageNo" size="small" placeholder="包装号" />
+            </template>
+            <span v-else>{{ row.packageNo }}</span>
+          </template>
+
+          <template v-slot:measureQuantity="{ row }">
+            <span v-if="row._isUnlisted">-</span>
+            <span v-else>{{ formatNumber(row.info?.measureQuantity ?? row.measureQuantity) }}</span>
+          </template>
+
+          <template v-slot:measureUnit="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-input v-model="row.measureUnit" size="small" placeholder="单位" />
+            </template>
+            <span v-else>{{ row.info?.measureUnit || row.measureUnit || '' }}</span>
+          </template>
+
+          <template v-slot:actualQuantity="{ row }">
+            <el-input-number
+              v-if="row._status === 0"
+              v-model="row.actualQuantity"
+              :min="0"
+              :precision="2"
+              size="small"
+              controls-position="right"
+              style="width: 100%"
+              @change="onActualChange(row)"
+            />
+            <span v-else>{{ formatNumber(row.actualQuantity) }}</span>
+          </template>
+
+          <template v-slot:result="{ row }">
+            <el-select
+              v-model="row.result"
+              placeholder="请选择"
+              size="small"
+              clearable
+              :disabled="row._status === 1"
+              @change="onFieldChange(row)"
+            >
+              <el-option
+                v-for="opt in resultOptions"
+                :key="opt.value"
+                :label="opt.label"
+                :value="opt.value"
+              />
+            </el-select>
+          </template>
+
+          <template v-slot:diffQuantity="{ row }">
+            <span>{{ calcDiffQuantity(row) }}</span>
+          </template>
+
+          <template v-slot:status="{ row }">
+            <el-tag
+              :type="row._status === 1 ? 'success' : 'warning'"
+              size="small"
+              effect="light"
+            >{{ row._status === 1 ? '已盘' : '未盘' }}</el-tag>
+          </template>
+
+          <template v-slot:description="{ row }">
+            <el-input
+              v-model="row.description"
+              placeholder="盘点说明"
+              size="small"
+              clearable
+              :disabled="row._status === 1"
+              @change="onFieldChange(row)"
+            />
+          </template>
+
+          <template v-slot:diffReason="{ row }">
+            <el-select
+              v-model="row.diffReason"
+              placeholder="请选择"
+              size="small"
+              clearable
+              filterable
+              :disabled="row._status === 1"
+              @change="onFieldChange(row)"
+            >
+              <el-option
+                v-for="opt in diffReasonOptions"
+                :key="opt.value"
+                :label="opt.label"
+                :value="opt.value"
+              />
+            </el-select>
+          </template>
+
+          <template v-slot:diffImages="{ row }">
+            <!-- <file-upload
+              v-model="row.diffImages"
+              :module="'stocktakingReport'"
+              :show-lib="false"
+              :limit="3"
+              :size="5"
+            /> -->
+            <fileMain v-model="row.diffImages"></fileMain>
+
+          </template>
+
+          <template v-slot:action="{ row }">
+            <template v-if="row._isUnlisted">
+              <el-button type="text" size="mini" @click="removeUnlisted(row)">删除</el-button>
+            </template>
+            <template v-else>
+              <el-button
+                v-if="row._status === 0"
+                type="text"
+                size="mini"
+                @click="confirmRow(row)"
+              >确认</el-button>
+              <el-button
+                v-else
+                type="text"
+                size="mini"
+                @click="cancelRow(row)"
+              >取消</el-button>
+            </template>
+          </template>
+        </ele-pro-table>
+      </div>
+
+      <div slot="footer" class="dispose-footer">
+        <el-button @click="closeWindows">关闭</el-button>
+        <el-button @click="onSave">保存</el-button>
+        <el-button type="primary" :loading="reportLoading" @click="onReport">报工</el-button>
+      </div>
+    </ele-modal>
+  </div>
+</template>
+
+<script>
+import { getPlanDetailList, reportWork, saveReportWork } from '@/api/inventory';
+import warehouseDefinition from '@/api/warehouseManagement/warehouseDefinition';
+import deptSelect from '@/components/CommomSelect/new-dept-select.vue';
+
+export default {
+  components: { deptSelect },
+  data() {
+    return {
+      inventoryTypeOp: [{
+          label: '库内盘点',
+          value: 1
+        },{
+          label: '库外盘点',
+          value: 2
+        }],
+        executorList: [],
+      visibleDialog: false,
+      planOrderId: '',
+      // 表单查询条件
+      filterForm: {
+        warehouseName: '',
+        dimension: '',
+        areaId: '',
+        shelfId: '',
+        keyWord: '',
+        statusFilter: ''
+      },
+      areaList: [],
+      shelfList: [],
+      locationList: [],
+      reportLoading: false,
+      // 接口返回的原始行(保留编辑状态)
+      serverRows: [],
+      // 无账货行
+      unlistedRows: [],
+      // 差异原因字典
+      diffReasonOptions: [
+        { value: 1, label: '漏记账' },
+        { value: 2, label: '错记账' },
+        { value: 3, label: '丢失' },
+        { value: 4, label: '被盗' },
+        { value: 5, label: '毁损' },
+        { value: 6, label: '串货' },
+        { value: 7, label: '入库未确认' },
+        { value: 8, label: '出库未登记' },
+        { value: 9, label: '其他' }
+      ],
+      // 盘点结果选项
+      resultOptions: [
+        { value: 'normal', label: '正常' },
+        { value: 'surplus', label: '盘盈' },
+        { value: 'lose', label: '盘亏' },
+        { value: 'unlisted', label: '无账货' }
+      ],
+      // 表格列配置
+      columns: [
+        { columnKey: 'index', label: '序号', type: 'index', width: 55, align: 'center', fixed: 'left' },
+        { slot: 'actualQuantity', label: '实盘数', align: 'center', minWidth: 120 },
+        { slot: 'result', label: '盘点结果', align: 'center', minWidth: 100 },
+        { slot: 'diffQuantity', label: '盘点差异数', align: 'center', minWidth: 100 },
+        { slot: 'description', label: '盘点说明', align: 'center', minWidth: 140 },
+        { slot: 'diffReason', label: '差异原因', align: 'center', minWidth: 160 },
+        { slot: 'diffImages', label: '差异拍照', align: 'center', minWidth: 150 },
+        { slot: 'status', label: '盘点状态', align: 'center', minWidth: 90 },
+        { prop: 'categoryName', label: '物品名称', align: 'center', minWidth: 160, showOverflowTooltip: true },
+        { prop: 'info.categoryCode', label: '物品编码', align: 'center', minWidth: 130, showOverflowTooltip: true },
+        { prop: 'batchNo', label: '批次号', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.categoryModel', label: '型号', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.specification', label: '规格', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.measureQuantity', label: '计量数量', align: 'center', minWidth: 110, showOverflowTooltip: true },
+        { prop: 'info.measureUnit', label: '计量单位', align: 'center', minWidth: 100, showOverflowTooltip: true },
+        { prop: 'info.brandNum', label: '牌号', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.colorKey', label: '颜色', align: 'center', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'info.weight', label: '重量', align: 'center', minWidth: 110, showOverflowTooltip: true },
+        { prop: 'info.weightUnit', label: '重量单位', align: 'center', minWidth: 100, showOverflowTooltip: true },
+        { prop: 'info.warehouseName', label: '仓库名称', align: 'center', minWidth: 110, showOverflowTooltip: true },
+        { prop: 'info.warehouseArea', label: '库区', align: 'center', minWidth: 100, showOverflowTooltip: true },
+        { prop: 'info.shelf', label: '货架', align: 'center', minWidth: 110, showOverflowTooltip: true },
+        { prop: 'info.location', label: '货位', align: 'center', minWidth: 100, showOverflowTooltip: true },
+        { prop: 'info.reportTime', label: '报告时间', align: 'center', minWidth: 180, showOverflowTooltip: true },
+        { slot: 'action', label: '操作', align: 'center', width: 80, fixed: 'right', resizable: false }
+      ]
+    };
+  },
+  computed: {
+    styleResponsive() {
+      return this.$store.state.theme.styleResponsive;
+    },
+    searchWhere() {
+      const w = {};
+      if (this.filterForm.keyWord) w.keyWord = this.filterForm.keyWord;
+      return w;
+    },
+    statistics() {
+      const list = this.serverRows;
+      const unlisted = this.unlistedRows;
+      const total = list.length + unlisted.length;
+      const confirmed = list.filter((r) => r._status === 1).length;
+      const unconfirmed = list.filter((r) => r._status === 0).length;
+      const normal = list.filter((r) => r.result === 'normal').length;
+      const surplus = list.filter((r) => r.result === 'surplus').length;
+      const lose = list.filter((r) => r.result === 'lose').length;
+      return { total, confirmed, unconfirmed, normal, surplus, lose, unlisted: unlisted.length };
+    }
+  },
+  methods: {
+    //选择部门(搜索)
+    searchDeptNodeClick(info, row) {
+      this.formData.executorId = '';
+      if (info) {
+        const params = { groupId: info };
+        this.getUserList(params);
+        this.formData.executeGroupName = row.name;
+      } else {
+        this.formData.executeGroupId = null;
+      }
+    },
+    // 获取审核人列表、巡点检人员
+    async getUserList(params) {
+      try {
+        let data = { pageNum: 1, size: -1 };
+        if (params) {
+          data = Object.assign(data, params);
+        }
+        const res = await getUserPage(data);
+        this.executorList = res.list;
+      } catch (error) {}
+    },
+    executorChange(val) {
+      this.formData.executorName = this.executorList.filter(
+        (item) => item.id == val
+      )[0].name;
+    },
+    async getAreaList(warehouseId) {
+      const res = await warehouseDefinition.getListByWarehouseId(warehouseId);
+      this.areaList = res || [];
+    },
+    async getShelfList(areaId) {
+      const rep = await warehouseDefinition.getListByAreaId(areaId);
+      this.shelfList = rep || [];
+    },
+    changeArea(val) {
+      this.filterForm.shelfId = '';
+      this.getShelfList(val);
+    },
+    changeShelf() {},
+    // ============= 工具方法 =============
+    formatNumber(val) {
+      if (val == null || val === '') return '';
+      const n = parseFloat(val);
+      if (isNaN(n)) return val;
+      return n.toFixed(2).replace(/\.?0+$/, '') || '0';
+    },
+    // 盘点差异数 = 实盘数 - 计量数量
+    calcDiffQuantity(row) {
+      const actual = parseFloat(row.actualQuantity);
+      const expect = parseFloat(row._measure);
+      if (isNaN(actual) || isNaN(expect)) return '-';
+      const diff = actual - expect;
+      return (diff >= 0 ? '+' : '') + diff.toFixed(2).replace(/\.?0+$/, '') || '0';
+    },
+    onFieldChange(row) { row._dirty = true; },
+    // 实盘数变化时,自动计算盘点结果
+    onActualChange(row) {
+      row._dirty = true;
+      if (row._isUnlisted) {
+        row.result = 'unlisted';
+      } else if (row.actualQuantity != null && row.actualQuantity !== '') {
+        row.result = this.autoCalcResult(row);
+      }
+      this.$forceUpdate();
+    },
+    // 根据实盘数与账面数自动计算结果
+    autoCalcResult(row) {
+      const actual = parseFloat(row.actualQuantity);
+      const expect = parseFloat(row._measure);
+      if (isNaN(actual) || isNaN(expect)) return null;
+      if (actual > expect) return 'surplus';
+      if (actual < expect) return 'lose';
+      return 'normal';
+    },
+    confirmRow(row) {
+      if (row.actualQuantity == null || row.actualQuantity === '') {
+        this.$message.warning('请先填写实盘数'); return;
+      }
+      const key = row._isUnlisted ? 'unlisted' : row.result;
+      const map = { normal: '正常', surplus: '盘盈', lose: '盘亏', unlisted: '无账货' };
+      if (key && key !== 'normal' && !row.diffReason) {
+        this.$message.warning(`盘点结果为${map[key]},请选择差异原因`); return;
+      }
+      row._status = 1;
+      row._dirty = true;
+      this.$forceUpdate();
+    },
+    cancelRow(row) {
+      row._status = 0;
+      row._dirty = true;
+      this.$forceUpdate();
+    },
+    confirmAll() {
+      const allRows = [...this.serverRows, ...this.unlistedRows];
+      const emptyRows = allRows.filter((r) => r.actualQuantity == null || r.actualQuantity === '');
+      if (emptyRows.length > 0) {
+        const nums = emptyRows.map((r, i) => r._index || i + 1).join('、');
+        this.$message.warning(`序号:${nums},未盘,请录入实盘数。`); return;
+      }
+      const missingReason = this.serverRows.filter((r) => {
+        if (r._status === 1) return false;
+        const key = r.result;
+        return key && key !== 'normal' && !r.diffReason;
+      });
+      if (missingReason.length > 0) {
+        this.$message.warning('存在差异行未填写差异原因,请先确认或补录'); return;
+      }
+      this.serverRows.forEach((r) => { if (r._status === 0) r._status = 1; });
+      this.unlistedRows.forEach((r) => { r._status = 1; });
+      this.$message.success('全部确认成功');
+      this.$forceUpdate();
+    },
+    cancelAll() {
+      this.serverRows.forEach((r) => { r._status = 0; r._dirty = true; });
+      this.unlistedRows.forEach((r) => { r._status = 0; r._dirty = true; });
+      this.$message.success('已全部取消确认');
+      this.$forceUpdate();
+    },
+    addUnlisted() {
+      const row = {
+        rowKey: `unlisted-${Date.now()}-${Math.random()}`,
+        categoryName: '', categoryCode: '', batchNo: '', packageNo: '', measureUnit: '',
+        actualQuantity: null, result: 'unlisted', description: '', diffReason: null, diffImages: [],
+        _isUnlisted: true, _status: 0, _dirty: true, _measure: 0
+      };
+      this.unlistedRows.push(row);
+      // 触发 ele-pro-table 重载让无账货行显示
+      this._reloadTable();
+    },
+    removeUnlisted(row) {
+      const idx = this.unlistedRows.indexOf(row);
+      if (idx > -1) {
+        this.unlistedRows.splice(idx, 1);
+        this._reloadTable();
+      }
+    },
+    // ============= 数据加载 =============
+    datasource({ page, limit, where }) {
+      return getPlanDetailList({
+        pageNum: page,
+        size: limit,
+        planOrderId: this.planOrderId,
+        ...where
+      }).then((res) => {
+        const list = (res.list || []).map((item, idx) => {
+          const existing = this.serverRows.find((r) => r.id === item.id && !r._isUnlisted);
+          const row = existing ? { ...item, ...existing } : { ...item };
+          row.rowKey = item.id || `server-${idx}`;
+          row._status = row._status != null ? row._status : 0;
+          row._measure = parseFloat(item.info?.measureQuantity ?? item.measureQuantity) || 0;
+          if (!existing && row.actualQuantity != null && row.actualQuantity !== '') {
+            row.result = this.autoCalcResult(row);
+          }
+          return row;
+        });
+        this.serverRows = list;
+        const merged = [...list];
+        if (page === 1 && this.unlistedRows.length) {
+          merged.push(...this.unlistedRows);
+        }
+        return { list: merged, count: res.count || 0 };
+      });
+    },
+    search() {
+      this.$nextTick(() => this._reloadTable());
+    },
+    resetFilter() {
+      this.filterForm.keyWord = '';
+      this.filterForm.statusFilter = '';
+      this.search();
+    },
+    onFilterChange() {
+      this.search();
+    },
+    _reloadTable() {
+      if (this.$refs.table && this.$refs.table.reload) {
+        this.$refs.table.reload({ page: 1 });
+      }
+    },
+    // ============= 保存 / 报工 =============
+    buildReportPayload() {
+      const payload = [];
+      this.serverRows.forEach((row) => {
+        if (row._status !== 1) return;
+        const key = row.result;
+        if (!key) return;
+        const item = { id: row.id, planOrderId: this.planOrderId, planDetailId: row.id,
+          categoryId: row.categoryId, categoryName: row.categoryName, batchNo: row.batchNo,
+          packageNo: row.packageNo,
+          status: key === 'surplus' ? 2 : key === 'lose' ? 3 : 1,
+          description: row.description || '', diffReason: row.diffReason, diffImages: this.toImageUrls(row.diffImages) };
+        if (key === 'surplus') item.surplusQuantity = row.actualQuantity;
+        else if (key === 'lose') item.loseQuantity = row.actualQuantity;
+        else item.normalQuantity = row.actualQuantity;
+        payload.push(item);
+      });
+      this.unlistedRows.forEach((row) => {
+        if (!row.categoryName || row.actualQuantity == null) return;
+        payload.push({
+          planOrderId: this.planOrderId, categoryName: row.categoryName, categoryCode: row.categoryCode,
+          batchNo: row.batchNo, packageNo: row.packageNo, measureUnit: row.measureUnit,
+          status: 4, surplusQuantity: row.actualQuantity,
+          description: row.description || '', diffReason: row.diffReason, diffImages: this.toImageUrls(row.diffImages)
+        });
+      });
+      return payload;
+    },
+    buildSavePayload() {
+      const payload = [];
+      this.serverRows
+        .filter((r) => r._dirty && r.actualQuantity != null && r.actualQuantity !== '')
+        .forEach((r) => {
+          payload.push({ id: r.id, planOrderId: this.planOrderId, planDetailId: r.id,
+            actualQuantity: r.actualQuantity, status: r._status, description: r.description || '', diffReason: r.diffReason,
+            diffImages: this.toImageUrls(r.diffImages) });
+        });
+      return payload;
+    },
+    toImageUrls(images) {
+      if (!Array.isArray(images)) return [];
+      return images.map((i) => (typeof i === 'string' ? i : i.url || i.storePath || '')).filter(Boolean);
+    },
+    onSave() {
+      const payload = this.buildSavePayload();
+      if (payload.length === 0) { this.$message.info('没有需要保存的数据'); return; }
+      saveReportWork(payload, { planOrderId: this.planOrderId })
+        .then(() => { this.$message.success('保存成功'); this.serverRows.forEach((r) => (r._dirty = false)); })
+        .catch((e) => { this.$message.error(e?.message || '保存失败'); });
+    },
+    onReport() {
+      const allRows = [...this.serverRows, ...this.unlistedRows];
+      const noActual = allRows.filter((r) => r.actualQuantity == null || r.actualQuantity === '');
+      if (noActual.length > 0) {
+        const nums = noActual.map((r, i) => r._index || i + 1).join('、');
+        this.$message.warning(`序号:${nums},未盘,请录入实盘数。`); return;
+      }
+      const notConfirmed = allRows.filter((r) => r._status !== 1);
+      if (notConfirmed.length > 0) {
+        const nums = notConfirmed.map((r, i) => r._index || i + 1).join('、');
+        this.$message.warning(`序号:${nums},未盘,请先确认。`); return;
+      }
+      const noReason = this.serverRows.filter((r) => {
+        const key = r.result;
+        return key && key !== 'normal' && !r.diffReason;
+      });
+      if (noReason.length > 0) { this.$message.warning('存在盘点差异未填写差异原因,请补充'); return; }
+      const invalidUnlisted = this.unlistedRows.filter((r) => !r.categoryName || r.actualQuantity == null || !r.diffReason);
+      if (invalidUnlisted.length > 0) { this.$message.warning('请完善无账货行:物品名称、实盘数、差异原因'); return; }
+      const payload = this.buildReportPayload();
+      if (payload.length === 0) { this.$message.info('没有需要报工的数据'); return; }
+      this.reportLoading = true;
+      reportWork(payload, { planOrderId: this.planOrderId })
+        .then(() => { this.$message.success('报工成功'); this.$emit('flushed'); this.closeWindows(); })
+        .catch((e) => { this.$message.error(e?.message || '报工失败'); })
+        .finally(() => { this.reportLoading = false; });
+    },
+    open(id) {
+      this.planOrderId = id;
+      this.serverRows = [];
+      this.unlistedRows = [];
+      this.filterForm = {
+        warehouseName: '',
+        dimension: '',
+        areaId: '',
+        shelfId: '',
+        keyWord: '',
+        statusFilter: ''
+      };
+      this.areaList = [];
+      this.shelfList = [];
+      this.visibleDialog = true;
+      this.$nextTick(() => this._reloadTable());
+    },
+    closeWindows() {
+      this.visibleDialog = false;
+      this.serverRows = [];
+      this.unlistedRows = [];
+      this.planOrderId = '';
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox__input.is-disabled .el-checkbox__inner) {
+  background-color: #eee !important;
+}
+
+.dispose-dialog {
+  display: flex;
+  flex-direction: column;
+}
+
+.ele-form-actions {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+}
+
+.dispose-stat {
+  flex: 1;
+  min-width: 580px;
+  margin-left: 8px;
+  font-size: 13px;
+  color: #606266;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+
+  b {
+    color: #409eff;
+    font-weight: 600;
+    margin: 0 2px;
+  }
+}
+
+
+.dispose-footer {
+  text-align: right;
+  margin-top: 12px;
+}
+</style>

+ 95 - 0
src/views/warehouseManagement/stocktaking/differences/components/work-search.vue

@@ -0,0 +1,95 @@
+<!-- 搜索表单 -->
+<template>
+  <seek-page
+    :seekList="seekList"
+    :formLength="4"
+    keyValue="workSearch"
+    @search="handleSearch"
+  />
+</template>
+
+<script>
+  import SeekPage from '@/components/seekPage.vue';
+  import storageApi from '@/api/warehouseManagement/index.js';
+  import warehouseDefinition from '@/api/warehouseManagement/warehouseDefinition';
+  export default {
+    components: { SeekPage },
+    data() {
+      return {
+        factoryList: [],
+        warehouseList: [],
+        userList: [],
+        seekList: [
+          { label: '关键词', value: 'keyword', type: 'input', placeholder: '请输入', width: 220 },
+          { label: '盘点类型', value: 'type', type: 'select', planList: [],  placeholder: '请选择', width: 220 },
+          { label: '盘点精度', value: 'precision', type: 'select', planList: [],  placeholder: '请选择', width: 220 },
+          { label: '状态', value: 'status', type: 'select', planList: [], placeholder: '请选择', width: 220 },
+          { label: '执行部门', value: 'executeGroupId', type: 'input', placeholder: '请输入', width: 220 },
+          { label: '执行人员', value: 'executorId', type: 'select', planList: [], placeholder: '请选择', width: 220 },
+          { label: '冻结策略', value: 'executorId', type: 'select', planList: [], placeholder: '请选择', width: 220 },
+          // { label: '所属工厂', value: 'factoryId', type: 'select', planList: [], placeholder: '请选择', width: 220 },
+          { label: '仓库', value: 'warehouseId', type: 'select', planList: [], placeholder: '请选择', width: 220 },
+          { label: '库区', value: 'warehouseId', type: 'select', planList: [], placeholder: '请选择', width: 220 },
+          { label: '货架', value: 'warehouseId', type: 'select', planList: [], placeholder: '请选择', width: 220 },
+          { label: '货位', value: 'warehouseId', type: 'select', planList: [], placeholder: '请选择', width: 220 },
+          { label: '创建时间', value: 'warehouseId', type: 'select', planList: [], placeholder: '请选择', width: 220 },
+          // { label: '计划单号', value: 'planCode', type: 'input', placeholder: '请输入', width: 220 },
+          // { label: '盘点单号', value: 'code', type: 'input', placeholder: '请输入', width: 220 },
+          // { label: '计划名称', value: 'planName', type: 'input', placeholder: '请输入', width: 220 },
+        ]
+      };
+    },
+    watch: {
+      factoryList: {
+        handler(arr) { this._updatePlanList('factoryId', arr); },
+        immediate: true
+      },
+      warehouseList: {
+        handler(arr) { this._updatePlanList('warehouseId', arr); },
+        immediate: true
+      },
+      userList: {
+        handler(arr) { this._updatePlanList('executorId', arr); },
+        immediate: true
+      }
+    },
+    created() {
+      this.seekList.find((s) => s.value === 'status').planList = [
+        { label: '待处理', value: 0 },
+        { label: '执行中', value: 1 },
+        { label: '已完成', value: 2 }
+      ];
+      this.getFactoryList();
+      this.getWarehouseList();
+      this.getuserList();
+    },
+    methods: {
+      _updatePlanList(field, arr) {
+        const item = this.seekList.find((s) => s.value === field);
+        if (item) item.planList = arr.map((v) => ({ label: v.name, value: v.id }));
+      },
+      handleSearch(where) {
+        this.$emit('search', where);
+      },
+      async getuserList() {
+        const data = await warehouseDefinition.getUserPage({ size: 9999, pageNum: 1 });
+        this.userList = data.list || [];
+      },
+      async getWarehouseList() {
+        try {
+          const res = await storageApi.getWarehouseTrees({});
+          if (res?.code === '0') {
+            this.warehouseList = res.data || [];
+          }
+        } catch (error) {
+          console.log(error);
+        }
+      },
+      async getFactoryList() {
+        const res = await warehouseDefinition.getFactoryarea({ pageNum: 1, size: 9999, type: 1 });
+        this.factoryList = res.list || [];
+      }
+    }
+  };
+</script>
+

+ 348 - 0
src/views/warehouseManagement/stocktaking/differences/index.vue

@@ -0,0 +1,348 @@
+<template>
+  <div id="stocktakingWork_index">
+    <el-card shadow="never" v-loading="loading">
+      <work-search @search="reload"> </work-search>
+      <!-- 数据表格 -->
+      <ele-pro-table
+        ref="table"
+        :columns="columns"
+        height="calc(100vh-300px)"
+        :pageSize="20"
+        :datasource="datasource"
+      > 
+        <!-- 表头工具栏 -->
+        <template v-slot:toolbar>
+          <el-button
+            size="small"
+            type="primary"
+            icon="el-icon-plus"
+            class="ele-btn-icon"
+            @click="openEdit()"
+          >
+            新增
+          </el-button>
+        </template>
+        <template v-slot:code="{ row }">
+          <el-link type="primary" @click="goDetail(row)">
+            {{ row.code }}
+          </el-link>
+        </template>
+        <template v-slot:action="{ row }">
+          <el-link
+            v-if="row.status == 0"
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            @click="assign(row)"
+          >
+            派单
+          </el-link>
+          <el-link
+            v-if="row.status == 0"
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            @click="control(row)"
+          >
+            报工
+          </el-link>
+        </template>
+      </ele-pro-table>
+    </el-card>
+    <DisposeDialog ref="disposeDialogRef" @flushed="reload"></DisposeDialog>
+    <!-- 派单 -->
+    <AssignDialog
+      :visibleDialog.sync="assignVisibleDialog"
+      :orderId="orderId"
+      @flushed="reload"
+    ></AssignDialog>
+  </div>
+</template>
+
+<script>
+  import DisposeDialog from './components/disposeDialog.vue';
+  import AssignDialog from './components/assignDialog.vue';
+  import WorkSearch from './components/work-search.vue';
+  import { getPlanOrderList } from '@/api/inventory';
+  export default {
+    components: {
+      WorkSearch,
+      DisposeDialog,
+      AssignDialog
+    },
+    data() {
+      return {
+        assignVisibleDialog: false,
+        visibleDialog: false,
+        // 	0-待处理,1-执行中,2-已完成
+        statusObj: {
+          0: '待处理',
+          1: '执行中',
+          2: '已完成'
+        },
+        // 表格列配置
+        columns: [
+          {
+            columnKey: 'index',
+            label: '序号',
+            type: 'index',
+            width: 55,
+            align: 'center',
+            showOverflowTooltip: true,
+            fixed: 'left'
+          },
+          {
+            slot: 'code',
+            prop: 'code',
+            label: '盘点差异单号',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 200
+          },
+          {
+            prop: 'planCode',
+            label: '来源盘点工单号',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 200
+          },
+          {
+            prop: 'planName',
+            label: '盘点结果',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'executeGroupName',
+            label: '执行部门',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'executorName',
+            label: '执行人',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'status',
+            label: '是否复盘确认',
+            align: 'center',
+            showOverflowTooltip: true,
+            slot: 'status',
+            minWidth: 110,
+            formatter: (row) => {
+              return this.statusObj[Number(row.status)];
+            }
+          },
+          {
+            prop: 'status',
+            label: '审批状态',
+            align: 'center',
+            showOverflowTooltip: true,
+            slot: 'status',
+            minWidth: 110,
+            formatter: (row) => {
+              return this.statusObj[Number(row.status)];
+            }
+          },
+          {
+            prop: 'status',
+            label: '派单状态',
+            align: 'center',
+            showOverflowTooltip: true,
+            slot: 'status',
+            minWidth: 110,
+            formatter: (row) => {
+              return this.statusObj[Number(row.status)];
+            }
+          },
+          {
+            prop: 'warehouseName',
+            label: '盘点类型',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'warehouseName',
+            label: '盘点精度',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'warehouseName',
+            label: '盘点仓库',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'warehouseName',
+            label: '盘点范围描述',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'warehouseName',
+            label: '冻结策略',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'createUserName',
+            label: '创建人',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'createTime',
+            label: '创建时间',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'createTime',
+            label: '报工时间',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'createTime',
+            label: '审批时间',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 200,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true,
+            fixed: 'right'
+          }
+        ],
+        // 加载状态
+        loading: false,
+        orderId: '',
+        userInfo: {}
+      };
+    },
+    created() {
+      let info = JSON.parse(localStorage.getItem('info'));
+      this.userInfo = info;
+    },
+    methods: {
+      accept(row) {
+        this.$confirm('是否接收该工单?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+          .then(() => {
+            this.$message({
+              type: 'success',
+              message: '接收成功!',
+              customClass: 'ele-message-border'
+            });
+            this.reload();
+          })
+          .catch(() => {});
+      },
+      assign(row) {
+        this.orderId = row.id;
+        this.assignVisibleDialog = true;
+      },
+      openEdit() {
+
+      },
+      control(row) {
+        console.log(row, this.userInfo);
+        // if (row.executorId != this.userInfo.userId) {
+        //   this.$message({
+        //     type: 'error',
+        //     message: '您不是盘点人员,不能报工!',
+        //   });
+        //   return;
+        // }
+        this.$refs.disposeDialogRef.open(row.id);
+      },
+      /* 表格数据源 */
+      datasource({ page, limit, where, order }) {
+        return getPlanOrderList({ pageNum: page, size: limit, ...where });
+      },
+      async changeEnable(row) {
+        const res = await putRoles(row);
+        if (res.code == 0) {
+          this.$message({
+            type: 'success',
+            message: '修改成功',
+            customClass: 'ele-message-border'
+          });
+          this.reload();
+        }
+      },
+      /* 刷新表格 */
+      reload(where) {
+        this.$refs.table.reload({ page: 1, where });
+      },
+      // 详情
+      goDetail(row) {
+        this.$router.push({
+          path: '/warehouseManagement/stocktaking/work/details',
+          query: {
+            id: row.id,
+            planId: row.planId
+          }
+        });
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  #stocktakingWork_index {
+    height: calc(100vh - 96px);
+    width: 100%;
+    padding: 10px;
+    box-sizing: border-box;
+    // element-ui样式穿透
+    :deep(.el-card) {
+      height: 100%;
+      .el-card__body {
+        height: 100%;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        .ele-pro-table {
+          flex: 1;
+          display: flex;
+          flex-direction: column;
+          .el-table {
+            flex: 1;
+            // display: flex;
+            // flex-direction: column;
+            // .el-table__body-wrapper {
+            //   flex: 1;
+            // }
+          }
+        }
+      }
+      .el-form-item {
+        margin-bottom: 5px !important;
+      }
+    }
+  }
+</style>