695593266@qq.com 6 месяцев назад
Родитель
Сommit
9d425513a6

+ 63 - 8
src/api/workOrderHandover/index.js

@@ -1,13 +1,68 @@
 import request from '@/utils/request';
 
-/**
- * 工单交接分页列表
- * @param {Object} data - { pageNum, size, ...where }
- */
-export async function handoverPage(data) {
-  const res = await request.post('/mes/workorder/handover/page', data);
-  if (res.data.code === 0) {
+//分页
+export async function handoverPage(params) {
+  const res = await request.get('/mes/passonorder/page', {
+    params
+  });
+  if (res.data.code == 0) {
     return res.data.data;
   }
-  return Promise.reject(new Error(res.data.message || '请求失败'));
+  return Promise.reject(new Error(res.data.message));
+}
+
+//保存
+export async function saveHandover(data) {
+  const res = await request.post(`/mes/passonorder/save`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+//修改
+export async function updateHandover(data) {
+  const res = await request.put(`/mes/passonorder/update`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+//详情
+export async function getHandoverById(id) {
+  const res = await request.get(`/mes/passonorder/getById/${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+//删除
+export async function deleteHandover(data) {
+  const res = await request.delete(`/mes/passonorder/delete`, {
+    data
+  });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+//拒绝或接收
+export async function acceptOrRejectHandover(data) {
+  const res = await request.post(`/mes/passonorder/accept`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+//查询班组的班组长
+export async function queryTeamLeader(params) {
+  const res = await request.get('/main/workcenter/page', { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
 }

+ 662 - 293
src/views/produce/components/workOrderHandover/index.vue

@@ -4,8 +4,22 @@
     <div class="top_fixed rx-bc">
       <div></div>
       <div class="rx-bc action_btns">
-        <el-button type="primary" size="small" @click="onSubmit">提交</el-button>
-        <el-button type="primary" size="small" @click="onSave">保存</el-button>
+        <el-button
+          type="primary"
+          size="small"
+          :loading="submitHandoverLoading && submitHandoverAction === 1"
+          :disabled="submitHandoverLoading"
+          @click="submitHandover(1)"
+          >提交</el-button
+        >
+        <el-button
+          type="primary"
+          size="small"
+          :loading="submitHandoverLoading && submitHandoverAction === 0"
+          :disabled="submitHandoverLoading"
+          @click="submitHandover(0)"
+          >保存</el-button
+        >
         <el-button size="small" @click="onCancel">取消</el-button>
       </div>
     </div>
@@ -21,49 +35,62 @@
             <div class="item rx-sc">
               <div class="rx ww16">
                 <div class="lable rx-cc">工单编码</div>
-                <div class="content">{{ workOrderInfo.code || '自动带出' }}</div>
+                <div class="content">{{ workOrderInfo.code }}</div>
               </div>
               <div class="rx ww16">
                 <div class="lable rx-cc">产品编码</div>
-                <div class="content">{{ workOrderInfo.productCode || '自动带出' }}</div>
+                <div class="content">{{ workOrderInfo.productCode }}</div>
               </div>
               <div class="rx ww16">
                 <div class="lable rx-cc">批次号</div>
-                <div class="content">{{ workOrderInfo.batchNo || '自动带出' }}</div>
+                <div class="content">{{ workOrderInfo.batchNo }}</div>
               </div>
             </div>
             <div class="item rx-sc">
               <div class="rx ww16">
                 <div class="lable rx-cc">产品名称</div>
-                <div class="content">{{ workOrderInfo.productName || '自动带出' }}</div>
+                <div class="content">{{ workOrderInfo.productName }}</div>
               </div>
               <div class="rx ww16">
                 <div class="lable rx-cc">要求生产数量</div>
-                <div class="content">{{ workOrderInfo.formingNum || '自动带出' }}</div>
+                <div class="content"
+                  >{{ workOrderInfo.formingNum }}{{ workOrderInfo.unit }}</div
+                >
               </div>
               <div class="rx ww16">
                 <div class="lable rx-cc">要求生产重量</div>
-                <div class="content">{{ workOrderInfo.formingWeight || '自动带出' }}</div>
+                <div class="content"
+                  >{{ workOrderInfo.formingWeight
+                  }}{{ workOrderInfo.weightUnit }}</div
+                >
               </div>
             </div>
             <div class="item rx-sc">
               <div class="rx ww16">
                 <div class="lable rx-cc">生产编号</div>
-                <div class="content">{{ workOrderInfo.productionCodes || '自动带出' }}</div>
+                <div class="content">{{ workOrderInfo.productionCodes }}</div>
               </div>
               <div class="rx ww16">
                 <div class="lable rx-cc">已生产数量</div>
-                <div class="content">{{ workOrderInfo.producedNum || '自动带出' }}</div>
+                <div class="content"
+                  >{{ workOrderInfo.formedNum || 0
+                  }}{{ workOrderInfo.unit }}</div
+                >
               </div>
               <div class="rx ww16">
                 <div class="lable rx-cc">已生产重量</div>
-                <div class="content">{{ workOrderInfo.producedWeight || '自动带出' }}</div>
+                <div class="content"
+                  >{{ workOrderInfo.formedWeight || 0
+                  }}{{ workOrderInfo.weightUnit }}</div
+                >
               </div>
             </div>
             <div class="item rx-sc">
               <div class="rx ww16">
                 <div class="lable rx-cc">当前工序</div>
-                <div class="content">{{ workOrderInfo.currentProcess || '自动带出' }}</div>
+                <div class="content">{{
+                  (workOrderInfo.currentTaskDiagram || {}).taskTypeName || ''
+                }}</div>
               </div>
               <div class="rx ww16">
                 <div class="lable rx-cc"></div>
@@ -91,35 +118,57 @@
           class="handover_form"
           size="small"
         >
-          <div class="form_row rx-bc">
-            <el-form-item label="交接类型" prop="handoverType" class="form_item" required>
+          <div class="form_row">
+            <el-form-item
+              label="交接类型"
+              prop="type"
+              class="form_item"
+              required
+            >
               <el-select
-                v-model="handoverForm.handoverType"
+                v-model="handoverForm.type"
                 placeholder="请选择"
                 style="width: 100%"
               >
-                <el-option label="物品移交" value="item" />
+                <el-option label="物品移交" :value="1" />
+                <el-option label="班次交接" :value="2" />
               </el-select>
             </el-form-item>
-            <el-form-item label="交接时间" prop="handoverTime" class="form_item" required>
+            <el-form-item
+              label="交接时间"
+              prop="initiateTime"
+              class="form_item"
+              required
+            >
               <el-date-picker
-                v-model="handoverForm.handoverTime"
-                type="date"
-                placeholder="年/月/日"
-                value-format="yyyy-MM-dd"
+                v-model="handoverForm.initiateTime"
+                type="datetime"
+                format="yyyy-MM-dd HH:mm:ss"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="选择交接时间"
                 style="width: 100%"
               />
             </el-form-item>
             <el-form-item label="交接单编码" class="form_item">
-              <el-input v-model="handoverForm.handoverCode" disabled placeholder="系统自动生成" />
+              <el-input
+                v-model="handoverForm.handoverCode"
+                disabled
+                placeholder="系统自动生成"
+              />
             </el-form-item>
           </div>
-          <div class="form_row rx-bc">
-            <el-form-item label="交接班组" prop="handoverTeam" class="form_item" required>
+          <div class="form_row">
+            <el-form-item
+              label="交接班组"
+              prop="acceptTeamId"
+              class="form_item"
+              required
+              v-if="handoverForm.type == 1"
+            >
               <el-select
-                v-model="handoverForm.handoverTeam"
-                placeholder="默认下道工序班组,可修改"
+                v-model="handoverForm.acceptTeamId"
                 filterable
+                @change="onHandoverTeamChange"
                 style="width: 100%"
               >
                 <el-option
@@ -130,11 +179,17 @@
                 />
               </el-select>
             </el-form-item>
-            <el-form-item label="交接人" prop="handoverPerson" class="form_item" required>
+            <el-form-item
+              label="交接人"
+              prop="acceptId"
+              class="form_item"
+              required
+              v-if="handoverForm.type == 1"
+            >
               <el-select
-                v-model="handoverForm.handoverPerson"
-                placeholder="自动带出,交接班组长"
+                v-model="handoverForm.acceptId"
                 filterable
+                @change="onAcceptPersonChange"
                 style="width: 100%"
               >
                 <el-option
@@ -147,21 +202,35 @@
             </el-form-item>
             <div class="form_item form_item_empty"></div>
           </div>
-          <div class="form_row rx-bc">
-            <el-form-item label="交接数量" prop="handoverQuantity" class="form_item form_item_with_unit" required>
+          <div class="form_row">
+            <el-form-item
+              label="交接数量"
+              prop="handoverQuantity"
+              class="form_item"
+              required
+            >
               <el-input
                 v-model="handoverForm.handoverQuantity"
                 placeholder="请输入"
-                type="number"
+                @input="
+                  batchInput(handoverForm.handoverQuantity, 'handoverQuantity')
+                "
               >
                 <template slot="append">PCS</template>
               </el-input>
             </el-form-item>
-            <el-form-item label="交接重量" prop="handoverWeight" class="form_item form_item_with_unit" required>
+            <el-form-item
+              label="交接重量"
+              prop="handoverWeight"
+              class="form_item"
+              required
+            >
               <el-input
                 v-model="handoverForm.handoverWeight"
                 placeholder="请输入"
-                type="number"
+                @input="
+                  batchInput(handoverForm.handoverWeight, 'handoverWeight')
+                "
               >
                 <template slot="append">KG</template>
               </el-input>
@@ -169,9 +238,13 @@
             <div class="form_item form_item_empty"></div>
           </div>
           <div class="form_row_full">
-            <el-form-item label="交接注意事项" prop="handoverNotes" class="form_item_full">
+            <el-form-item
+              label="交接注意事项"
+              prop="remark"
+              class="form_item_full"
+            >
               <el-input
-                v-model="handoverForm.handoverNotes"
+                v-model="handoverForm.remark"
                 type="textarea"
                 :rows="3"
                 placeholder="请输入"
@@ -185,312 +258,608 @@
       <div class="card_box">
         <div class="title_box rx-bc">
           <div class="name">物料清单</div>
-          <el-button type="primary" size="small" @click="addMaterial">+ 物料</el-button>
+          <div class="rx-bc action_btns">
+            <el-button type="primary" size="small" @click="addMaterial"
+              >添加物料</el-button
+            >
+            <el-button type="danger" size="small" @click="batchDeleteMaterial"
+              >批量删除</el-button
+            >
+          </div>
         </div>
-        <el-table
-          :data="materialList"
-          border
-          stripe
-          class="material_table"
-          max-height="400"
+        <ele-pro-table
+          ref="table"
+          :needPage="false"
+          :columns="columns"
+          :datasource="list"
+          class="time-form"
+          max-height="600"
+          @columns-change="handleColumnChange"
+          :cache-key="cacheKeyUrl"
+          :selection.sync="selection"
+          row-key="id"
         >
-          <el-table-column type="selection" width="45" align="center" />
-          <el-table-column type="index" label="序号" width="55" align="center" />
-          <el-table-column prop="code" label="编码" min-width="90" show-overflow-tooltip />
-          <el-table-column prop="name" label="名称" min-width="100" show-overflow-tooltip />
-          <el-table-column prop="batchNo" label="批次号" min-width="90" />
-          <el-table-column prop="type" label="类型" width="80" />
-          <el-table-column label="刻码" min-width="90">
-            <template slot-scope="{ row }">
-              <el-input v-model="row.engrave" size="mini" placeholder="请输入" />
-            </template>
-          </el-table-column>
-          <el-table-column prop="serialNo" label="序列号" min-width="90" />
-          <el-table-column label="物料代号" min-width="90">
-            <template slot-scope="{ row }">
-              <el-input v-model="row.materialAlias" size="mini" placeholder="请输入" />
-            </template>
-          </el-table-column>
-          <el-table-column label="客户代号" min-width="90">
-            <template slot-scope="{ row }">
-              <el-input v-model="row.customerAlias" size="mini" placeholder="请输入" />
-            </template>
-          </el-table-column>
-          <el-table-column prop="equipment" label="设备" min-width="90" />
-          <el-table-column label="炉次" min-width="90">
-            <template slot-scope="{ row }">
-              <el-input v-model="row.heatNo" size="mini" placeholder="请输入" />
-            </template>
-          </el-table-column>
-        </el-table>
+          <template v-slot:action="{ row }">
+            <el-popconfirm
+              class="ele-action"
+              title="确定要删除此物料吗?"
+              @confirm="remove(row)"
+            >
+              <template v-slot:reference>
+                <el-link type="danger" :underline="false"> 删除 </el-link>
+              </template>
+            </el-popconfirm>
+          </template>
+        </ele-pro-table>
       </div>
     </div>
+
+    <pickingList
+      isType="pick"
+      ref="pickingListRef"
+      @allSelection="allSelection"
+    ></pickingList>
   </div>
 </template>
 
 <script>
-export default {
-  name: 'WorkOrderHandover',
-  props: {
-    workListIds: {
-      type: Array,
-      default: () => []
-    }
-  },
-  data() {
-    return {
-      workOrderInfo: {},
-      handoverForm: {
-        handoverType: 'item',
-        handoverTime: '',
-        handoverCode: '',
-        handoverTeam: '',
-        handoverPerson: '',
-        handoverQuantity: '',
-        handoverWeight: '',
-        handoverNotes: ''
-      },
-      handoverRules: {
-        handoverType: [{ required: true, message: '请选择交接类型', trigger: 'change' }],
-        handoverTime: [{ required: true, message: '请选择交接时间', trigger: 'change' }],
-        handoverTeam: [{ required: true, message: '请选择交接班组', trigger: 'change' }],
-        handoverPerson: [{ required: true, message: '请选择交接人', trigger: 'change' }],
-        handoverQuantity: [{ required: true, message: '请输入交接数量', trigger: 'blur' }],
-        handoverWeight: [{ required: true, message: '请输入交接重量', trigger: 'blur' }]
+  import { workorderList } from '@/api/produce/workOrder';
+  import { getteampage } from '@/api/workforceManagement/team';
+  import { saveHandover } from '@/api/workOrderHandover';
+  import pickingList from '../picking/newPickingList.vue';
+  export default {
+    name: 'WorkOrderHandover',
+
+    components: {
+      pickingList
+    },
+
+    props: {
+      workListIds: {
+        type: Array,
+        default: () => []
       },
-      teamList: [],
-      personList: [],
-      materialList: [
-        {
-          code: '905',
-          name: '哈密瓜...',
-          batchNo: '905',
-          type: '产品',
-          engrave: '',
-          serialNo: '',
-          materialAlias: '',
-          customerAlias: '请输入',
-          equipment: '',
-          heatNo: ''
-        }
-      ]
-    };
-  },
-  watch: {
-    workListIds: {
-      immediate: true,
-      handler(ids) {
-        if (ids && ids.length) {
-          this.loadWorkOrderInfo();
-        }
+      workData: {
+        type: Object,
+        default: () => []
       }
-    }
-  },
-  methods: {
-    loadWorkOrderInfo() {
-      // TODO: 根据 workListIds 请求工单详情,赋值 workOrderInfo
-      this.workOrderInfo = {};
     },
-    onSubmit() {
-      this.$refs.handoverForm.validate(valid => {
-        if (valid) {
-          // TODO: 提交接口
-          this.$message.success('提交成功');
-        }
-      });
+    data() {
+      const formatDateTimeShort = (d) => {
+        const p = (n) => String(n).padStart(2, '0');
+        return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(
+          d.getHours()
+        )}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
+      };
+      const handoverTimeDefault = formatDateTimeShort(new Date());
+
+      return {
+        workOrderInfo: {},
+        handoverForm: {
+          type: 1,
+          initiateTime: handoverTimeDefault,
+          handoverCode: '',
+          acceptTeamId: '',
+          acceptTeamName: '',
+          acceptId: '',
+          acceptName: '',
+          handoverQuantity: '',
+          handoverWeight: '',
+          remark: ''
+        },
+        handoverRules: {
+          type: [
+            { required: true, message: '请选择交接类型', trigger: 'change' }
+          ],
+          initiateTime: [
+            { required: true, message: '请选择交接时间', trigger: 'change' }
+          ],
+          acceptTeamId: [
+            { required: true, message: '请选择交接班组', trigger: 'change' }
+          ],
+          acceptId: [
+            { required: true, message: '请选择交接人', trigger: 'change' }
+          ],
+          handoverQuantity: [
+            { required: true, message: '请选择交接数量', trigger: 'change' }
+          ],
+          handoverWeight: [
+            { required: true, message: '请选择交接重量', trigger: 'change' }
+          ]
+        },
+        teamList: [],
+        personList: [],
+        selection: [],
+        cacheKeyUrl: 'mes_produce_workOrderHandover_materialTable',
+        materialList: [],
+        submitHandoverLoading: false,
+        submitHandoverAction: null
+      };
     },
-    onSave() {
-      this.$refs.handoverForm.validate(valid => {
-        if (valid) {
-          // TODO: 保存接口
-          this.$message.success('保存成功');
+    watch: {
+      workListIds: {
+        immediate: true,
+        handler(ids) {
+          if (ids && ids.length == 1) {
+            this.loadWorkOrderInfo(ids);
+          }
         }
-      });
+      }
     },
-    onCancel() {
-      this.$emit('cancel');
+
+    computed: {
+      taskObj() {
+        return this.$store.state.user.taskObj;
+      },
+      list() {
+        return this.materialList;
+      },
+      columns() {
+        return [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left',
+            reserveSelection: true
+          },
+          {
+            width: 55,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center',
+            label: '序号',
+            fixed: 'left'
+          },
+          {
+            prop: 'categoryCode',
+            label: '编码',
+            align: 'center',
+            showOverflowTooltip: true,
+            sortable: true,
+            minWidth: 110
+          },
+          {
+            prop: 'categoryName',
+            label: '名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'categoryLevelPath',
+            label: '分类',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'brandNum',
+            align: 'center',
+            label: '牌号',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'modelType',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+
+          {
+            prop: 'measureUnit',
+            label: '计量单位',
+            showOverflowTooltip: true,
+            minWidth: 90
+          },
+          {
+            prop: 'weightUnit',
+            label: '重量单位',
+            showOverflowTooltip: true,
+            minWidth: 90
+          },
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 100,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            fixed: 'right'
+          }
+        ];
+      }
     },
-    addMaterial() {
-      this.materialList.push({
-        code: '',
-        name: '',
-        batchNo: '',
-        type: '',
-        engrave: '',
-        serialNo: '',
-        materialAlias: '',
-        customerAlias: '',
-        equipment: '',
-        heatNo: ''
-      });
+
+    mounted() {
+      this.getTeamList();
+    },
+
+    methods: {
+      loadWorkOrderInfo(ids) {
+        let param = {
+          ids: ids,
+          taskId: this.taskObj.id
+        };
+        workorderList(param).then((res) => {
+          this.workOrderInfo = res[0];
+        });
+      },
+
+      async getTeamList() {
+        const factoryId = this.$store.state.user.info.factoryId;
+        const { list } = await getteampage({
+          pageNum: 1,
+          size: -1,
+          factoryId
+        });
+        this.teamList = Array.isArray(list) ? list : [];
+        if (this.teamList.length) {
+          const firstTeamId = this.teamList[0].id;
+          this.handoverForm.acceptTeamId = firstTeamId;
+          this.syncTeamUsersAndLeader(firstTeamId);
+        } else {
+          this.handoverForm.acceptTeamId = '';
+          this.handoverForm.acceptTeamName = '';
+          this.personList = [];
+          this.handoverForm.acceptId = '';
+          this.handoverForm.acceptName = '';
+        }
+      },
+      onHandoverTeamChange(teamId) {
+        this.syncTeamUsersAndLeader(teamId);
+      },
+      onAcceptPersonChange(personId) {
+        const person =
+          this.personList.find(
+            (item) => String(item.id) === String(personId)
+          ) || {};
+        this.handoverForm.acceptName = person.name || '';
+      },
+      syncTeamUsersAndLeader(teamId) {
+        const currentTeam =
+          this.teamList.find((item) => String(item.id) === String(teamId)) ||
+          {};
+        this.handoverForm.acceptTeamName = currentTeam.name || '';
+        const users = Array.isArray(currentTeam.userVOList)
+          ? currentTeam.userVOList
+          : [];
+        this.personList = users.map((user) => ({
+          id: user.id,
+          name: user.name
+        }));
+        if (!this.personList.length) {
+          this.handoverForm.acceptId = '';
+          this.handoverForm.acceptName = '';
+          return;
+        }
+        const leader =
+          this.personList.find(
+            (user) => String(user.id) === String(currentTeam.leaderUserId)
+          ) || this.personList[0];
+        this.handoverForm.acceptId = leader.id;
+        this.handoverForm.acceptName = leader.name || '';
+      },
+
+      batchInput(val, type) {
+        console.log(val, 'val==>', type, 'type');
+        let newVal = val.replace(/[^\d.]/g, '');
+
+        if (newVal.startsWith('.')) {
+          newVal = '';
+        }
+
+        const firstDotIndex = newVal.indexOf('.');
+        if (firstDotIndex !== -1) {
+          newVal =
+            newVal.slice(0, firstDotIndex + 1) +
+            newVal.slice(firstDotIndex + 1).replace(/\./g, '');
+        }
+
+        const match = newVal.match(/^(\d+)(\.\d{0,4})?/);
+
+        if (type == 'handoverQuantity') {
+          this.handoverForm.handoverQuantity = match ? match[0] : '';
+        } else if (type == 'handoverWeight') {
+          this.handoverForm.handoverWeight = match ? match[0] : '';
+        }
+      },
+
+      submitHandover(isRelease) {
+        if (this.submitHandoverLoading) return;
+        const param = {
+          ...this.handoverForm,
+          ...this.workOrderInfo,
+          detailList: this.materialList,
+          isRelease,
+          categoryCode: this.workOrderInfo.productCode,
+          categoryName: this.workOrderInfo.productName,
+          initiateId: this.$store.state.user.info.id,
+          initiateName: this.$store.state.user.info.name,
+          initiateTaskId: this.workOrderInfo.currentTaskDiagram.taskId,
+          initiateTaskName: this.workOrderInfo.currentTaskDiagram.taskTypeName,
+          workOrderId: this.workOrderInfo.id,
+          workOrderCode: this.workOrderInfo.code
+        };
+
+        // return;
+
+        this.submitHandoverLoading = true;
+        this.submitHandoverAction = isRelease;
+        saveHandover(param)
+          .then((res) => {
+            this.$message.success(isRelease === 1 ? '提交成功' : '保存成功');
+          })
+          .finally(() => {
+            this.submitHandoverLoading = false;
+            this.submitHandoverAction = null;
+          });
+      },
+      onCancel() {
+        this.$emit('cancel');
+      },
+      handleColumnChange() {},
+      addMaterial() {
+        this.$refs.pickingListRef.open(
+          '',
+          {
+            pickList: this.list
+          },
+          '选择列表'
+        );
+      },
+      batchDeleteMaterial() {
+        const selected = Array.isArray(this.selection) ? this.selection : [];
+        if (!selected.length) {
+          this.$message.warning('请先选择要删除的物料');
+          return;
+        }
+        this.$confirm(
+          `确定删除已选中的 ${selected.length} 条物料吗?`,
+          '提示',
+          {
+            type: 'warning'
+          }
+        )
+          .then(() => {
+            const selectedIds = new Set(
+              selected
+                .map((item) => (item && item.id != null ? String(item.id) : ''))
+                .filter(Boolean)
+            );
+            if (selectedIds.size) {
+              this.materialList = this.materialList.filter(
+                (item) => !selectedIds.has(String(item.id))
+              );
+            } else {
+              this.materialList = this.materialList.filter(
+                (item) => !selected.includes(item)
+              );
+            }
+            this.selection = [];
+            this.$nextTick(() => {
+              this.$refs.table && this.$refs.table.clearSelection();
+            });
+            this.$message.success('删除成功');
+          })
+          .catch(() => {});
+      },
+      allSelection(id, list) {
+        this.materialList = list;
+      },
+      /* 删除 */
+      remove(row) {
+        if (!row) return;
+        this.materialList = this.materialList.filter((item) => {
+          if (row.id != null && item.id != null) {
+            return String(item.id) !== String(row.id);
+          }
+          return item !== row;
+        });
+        this.selection = this.selection.filter((item) => {
+          if (row.id != null && item.id != null) {
+            return String(item.id) !== String(row.id);
+          }
+          return item !== row;
+        });
+        this.$nextTick(() => {
+          this.$refs.table && this.$refs.table.clearSelection();
+          this.selection.forEach((item) => {
+            this.$refs.table && this.$refs.table.toggleRowSelection(item, true);
+          });
+        });
+        this.$message.success('删除成功');
+      }
     }
-  }
-};
+  };
 </script>
 
 <style scoped lang="scss">
-.work-order-handover {
-  background: #fff;
-  padding: 12px;
-  min-height: 100%;
-}
-
-.top_fixed {
-  margin-bottom: 12px;
-  .action_btns {
-    gap: 10px;
+  .work-order-handover {
+    background: #fff;
+    padding: 12px;
+    min-height: 100%;
   }
-}
-
-.handover_content {
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-}
-
-.card_box {
-  background: #fff;
-  padding: 12px;
-  border-radius: 2px;
-  border: 1px solid #e3e5e5;
-}
-
-.title_box {
-  padding: 10px 0;
-  margin-bottom: 8px;
-
-  .name {
-    font-size: 14px;
-    font-style: normal;
-    font-weight: 400;
-    color: #157a2c;
-    padding-left: 5px;
-    position: relative;
-
-    &::before {
-      position: absolute;
-      content: '';
-      left: 0;
-      top: 0;
-      bottom: 0;
-      width: 2px;
-      height: 14px;
-      background: #157a2c;
-      margin: auto;
+
+  /* 随右侧内容区滚动时始终贴顶(父级 .split-layout-right-content 为 overflow:auto) */
+  .top_fixed {
+    position: sticky;
+    top: 0;
+    z-index: 20;
+    margin: -12px -12px 12px -12px;
+    padding: 10px 12px;
+    background: #fff;
+    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
+    .action_btns {
+      gap: 10px;
     }
   }
-}
 
-/* 工单信息表格 */
-.material {
-  margin-top: 6px;
+  .handover_content {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
 
-  .content_table {
-    width: 100%;
+  .card_box {
+    background: #fff;
+    padding: 12px;
+    border-radius: 2px;
     border: 1px solid #e3e5e5;
+  }
 
-    .item {
-      display: flex;
-      border-bottom: 1px solid #e3e5e5;
+  .title_box {
+    padding: 10px 0;
+    margin-bottom: 8px;
 
-      &:last-child {
-        border-bottom: none;
+    .name {
+      font-size: 14px;
+      font-style: normal;
+      font-weight: 400;
+      color: #157a2c;
+      padding-left: 5px;
+      position: relative;
+
+      &::before {
+        position: absolute;
+        content: '';
+        left: 0;
+        top: 0;
+        bottom: 0;
+        width: 2px;
+        height: 14px;
+        background: #157a2c;
+        margin: auto;
       }
+    }
+  }
+
+  /* 工单信息表格 */
+  .material {
+    margin-top: 6px;
 
-      .rx {
+    .content_table {
+      width: 100%;
+      max-width: 100%;
+      box-sizing: border-box;
+      border: 1px solid #e3e5e5;
+      overflow: hidden;
+
+      .item {
         display: flex;
-        flex: 1;
         min-width: 0;
-      }
+        border-bottom: 1px solid #e3e5e5;
 
-      .ww16 {
-        width: 33.33%;
-        flex: 0 0 33.33%;
-      }
+        &:last-child {
+          border-bottom: none;
+        }
 
-      .lable {
-        width: 100px;
-        min-width: 100px;
-        text-align: center;
-        background-color: #f7f9fa;
-        font-size: 12px;
-        border-right: 1px solid #e3e5e5;
-        padding: 8px 4px;
-        box-sizing: border-box;
-      }
+        .rx {
+          display: flex;
+          flex: 1;
+          min-width: 0;
+        }
 
-      .content {
-        flex: 1;
-        min-height: 32px;
-        font-size: 13px;
-        padding: 8px 10px;
-        background: #fff;
-        border-right: 1px solid #e3e5e5;
-        white-space: nowrap;
-        overflow: hidden;
-        text-overflow: ellipsis;
+        /* 三等分:避免 33.33%×3 舍入与 flex-shrink:0 撑破卡片 */
+        .ww16 {
+          flex: 1 1 0;
+          min-width: 0;
+        }
 
-        &:last-child {
-          border-right: none;
+        .lable {
+          width: 100px;
+          min-width: 100px;
+          flex-shrink: 0;
+          text-align: center;
+          background-color: #f7f9fa;
+          font-size: 12px;
+          border-right: 1px solid #e3e5e5;
+          padding: 8px 4px;
+          box-sizing: border-box;
+        }
+
+        .content {
+          flex: 1;
+          min-width: 0;
+          min-height: 32px;
+          font-size: 13px;
+          padding: 8px 10px;
+          box-sizing: border-box;
+          background: #fff;
+          border-right: 1px solid #e3e5e5;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+
+          &:last-child {
+            border-right: none;
+          }
         }
       }
     }
   }
-}
 
-/* 交接信息表单 */
-.handover_form {
-  .form_row,
-  .form_row_full {
-    display: flex;
-    flex-wrap: wrap;
-    margin-bottom: 0;
-  }
+  /* 交接信息表单:三列等宽网格,与第一行对齐;避免 space-between 拉散两列行 */
+  .handover_form {
+    .form_row_full {
+      margin-bottom: 12px;
+    }
 
-  .form_row {
-    align-items: flex-start;
-    gap: 16px;
-    margin-bottom: 12px;
-  }
+    .form_row {
+      display: grid;
+      grid-template-columns: repeat(3, minmax(0, 1fr));
+      column-gap: 16px;
+      margin-bottom: 12px;
+      align-items: start;
+    }
 
-  .form_row_full {
-    margin-bottom: 12px;
-  }
+    .form_item {
+      width: 100%;
+      min-width: 0;
+      margin-bottom: 0;
+      display: flex;
+      align-items: center;
 
-  .form_item {
-    flex: 1;
-    min-width: 200px;
-    margin-bottom: 12px;
-    display: flex;
-    align-items: center;
+      ::v-deep .el-form-item__label {
+        padding-right: 8px;
+      }
 
-    ::v-deep .el-form-item__label {
-      padding-right: 4px;
-    }
+      ::v-deep .el-form-item__content {
+        flex: 1;
+        min-width: 0;
+        margin-left: 0 !important;
+      }
+
+      ::v-deep .el-select,
+      ::v-deep .el-date-editor.el-input {
+        width: 100% !important;
+      }
 
-    ::v-deep .el-form-item__content {
-      flex: 1;
-      margin-left: 0 !important;
+      ::v-deep .el-select .el-input__inner,
+      ::v-deep .el-date-editor .el-input__inner {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
     }
-  }
 
-  .form_item_empty {
-    min-width: 0;
-    flex: 1;
-    max-width: 200px;
-  }
+    .form_item_empty {
+      min-width: 0;
+    }
 
-  .form_item_with_unit {
-    max-width: 280px;
+    .form_item_full {
+      width: 100%;
+      margin-bottom: 0;
+    }
   }
 
-  .form_item_full {
-    width: 100%;
-    margin-bottom: 0;
+  .material_table {
+    margin-top: 8px;
   }
-}
-
-.material_table {
-  margin-top: 8px;
-}
 </style>

+ 15 - 0
src/views/produce/index.vue

@@ -203,6 +203,7 @@
               <div v-if="operationType == 'workOrder'">
                 <workOrderHandover
                   :workListIds="workListIds"
+                  :workData="workData"
                   ref="workOrderHandoverRef"
                 ></workOrderHandover>
               </div>
@@ -959,6 +960,20 @@
 
         if (['work', 'workStep', 'task'].includes(t)) {
           this.popupType = t;
+        } else if (t == 'workOrder') {
+          console.log(this.workData, 'workData');
+
+          if (this.workListIds.length == 0) {
+            this.$message.warning('请选择工单');
+            return;
+          }
+
+          if (this.workListIds.length > 1) {
+            this.$message.warning('工单交接只能选择一个工单');
+            return;
+          }
+
+          this.operationType = t;
         } else {
           this.operationType = t;
         }

+ 1156 - 0
src/views/workOrderHandover/components/edit.vue

@@ -0,0 +1,1156 @@
+<template>
+  <ele-modal
+    v-if="visible"
+    custom-class="ele-dialog-form long-dialog-form"
+    :centered="true"
+    :append-to-body="true"
+    :visible.sync="dialogVisible"
+    :close-on-click-modal="false"
+    width="70%"
+    :maxable="true"
+    :resizable="true"
+    :title="title"
+    @close="onModalClose"
+  >
+    <div class="work-order-handover" v-loading="loading">
+      <div class="handover_content">
+        <!-- 工单信息 -->
+        <div class="card_box">
+          <div class="title_box rx-bc">
+            <div class="name">工单信息</div>
+          </div>
+          <div class="material">
+            <div class="content_table">
+              <div class="item rx-sc">
+                <div class="rx ww16">
+                  <div class="lable rx-cc">工单编码</div>
+                  <div class="content">{{ workOrderInfo.workOrderCode }}</div>
+                </div>
+                <div class="rx ww16">
+                  <div class="lable rx-cc">产品编码</div>
+                  <div class="content">{{ workOrderInfo.categoryCode }}</div>
+                </div>
+                <div class="rx ww16">
+                  <div class="lable rx-cc">批次号</div>
+                  <div class="content">{{ workOrderInfo.batchNo }}</div>
+                </div>
+              </div>
+              <div class="item rx-sc">
+                <div class="rx ww16">
+                  <div class="lable rx-cc">产品名称</div>
+                  <div class="content">{{ workOrderInfo.categoryName }}</div>
+                </div>
+                <div class="rx ww16">
+                  <div class="lable rx-cc">要求生产数量</div>
+                  <div class="content"
+                    >{{ workOrderInfo.formingNum }}{{ workOrderInfo.unit }}</div
+                  >
+                </div>
+                <div class="rx ww16">
+                  <div class="lable rx-cc">要求生产重量</div>
+                  <div class="content"
+                    >{{ workOrderInfo.formingWeight
+                    }}{{ workOrderInfo.weightUnit }}</div
+                  >
+                </div>
+              </div>
+              <div class="item rx-sc">
+                <div class="rx ww16">
+                  <div class="lable rx-cc">生产编号</div>
+                  <div class="content">{{ workOrderInfo.productionCodes }}</div>
+                </div>
+                <div class="rx ww16">
+                  <div class="lable rx-cc">已生产数量</div>
+                  <div class="content"
+                    >{{ workOrderInfo.formedNum || 0
+                    }}{{ workOrderInfo.unit }}</div
+                  >
+                </div>
+                <div class="rx ww16">
+                  <div class="lable rx-cc">已生产重量</div>
+                  <div class="content"
+                    >{{ workOrderInfo.formedWeight || 0
+                    }}{{ workOrderInfo.weightUnit }}</div
+                  >
+                </div>
+              </div>
+              <div class="item rx-sc">
+                <div class="rx ww16">
+                  <div class="lable rx-cc">当前工序</div>
+                  <div class="content">{{
+                    (workOrderInfo.currentTaskDiagram || {}).taskTypeName ||
+                    workOrderInfo.initiateTaskName ||
+                    ''
+                  }}</div>
+                </div>
+                <div class="rx ww16">
+                  <div class="lable rx-cc"></div>
+                  <div class="content"></div>
+                </div>
+                <div class="rx ww16">
+                  <div class="lable rx-cc"></div>
+                  <div class="content"></div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 交接信息 -->
+        <div class="card_box">
+          <div class="title_box rx-bc">
+            <div class="name">交接信息</div>
+          </div>
+          <el-form
+            ref="handoverForm"
+            :model="workOrderInfo"
+            :rules="handoverRules"
+            label-width="100px"
+            class="handover_form"
+            size="small"
+          >
+            <div class="form_row">
+              <el-form-item
+                label="交接类型"
+                prop="type"
+                class="form_item"
+                required
+              >
+                <el-select
+                  v-model="workOrderInfo.type"
+                  placeholder="请选择"
+                  style="width: 100%"
+                  :disabled="formLocked"
+                >
+                  <el-option label="物品移交" :value="1" />
+                  <el-option label="班次交接" :value="2" />
+                </el-select>
+              </el-form-item>
+              <el-form-item
+                label="交接时间"
+                prop="initiateTime"
+                class="form_item"
+                required
+              >
+                <el-date-picker
+                  v-model="workOrderInfo.initiateTime"
+                  type="datetime"
+                  format="yyyy-MM-dd HH:mm:ss"
+                  value-format="yyyy-MM-dd HH:mm:ss"
+                  placeholder="选择交接时间"
+                  style="width: 100%"
+                  :disabled="formLocked"
+                />
+              </el-form-item>
+              <el-form-item label="交接单编码" class="form_item">
+                <el-input
+                  v-model="handoverCodeDisplay"
+                  disabled
+                  placeholder="系统自动生成"
+                />
+              </el-form-item>
+            </div>
+            <div class="form_row">
+              <el-form-item
+                label="交接班组"
+                prop="acceptTeamId"
+                class="form_item"
+                required
+                v-if="workOrderInfo.type == 1"
+              >
+                <el-select
+                  v-model="workOrderInfo.acceptTeamId"
+                  filterable
+                  :disabled="formLocked"
+                  @change="onHandoverTeamChange"
+                  style="width: 100%"
+                >
+                  <el-option
+                    v-for="item in teamList"
+                    :key="item.id"
+                    :label="item.name"
+                    :value="item.id"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item
+                label="交接人"
+                prop="acceptId"
+                class="form_item"
+                required
+                v-if="workOrderInfo.type == 1"
+              >
+                <el-select
+                  v-model="workOrderInfo.acceptId"
+                  filterable
+                  :disabled="formLocked"
+                  @change="onAcceptPersonChange"
+                  style="width: 100%"
+                >
+                  <el-option
+                    v-for="item in personList"
+                    :key="item.id"
+                    :label="item.name"
+                    :value="item.id"
+                  />
+                </el-select>
+              </el-form-item>
+              <div class="form_item form_item_empty"></div>
+            </div>
+            <div class="form_row">
+              <el-form-item
+                label="交接数量"
+                prop="handoverQuantity"
+                class="form_item"
+                required
+              >
+                <el-input
+                  v-model="workOrderInfo.handoverQuantity"
+                  placeholder="请输入"
+                  :disabled="formLocked"
+                  @input="
+                    batchInput(
+                      workOrderInfo.handoverQuantity,
+                      'handoverQuantity'
+                    )
+                  "
+                >
+                  <template slot="append">PCS</template>
+                </el-input>
+              </el-form-item>
+              <el-form-item
+                label="交接重量"
+                prop="handoverWeight"
+                class="form_item"
+                required
+              >
+                <el-input
+                  v-model="workOrderInfo.handoverWeight"
+                  placeholder="请输入"
+                  :disabled="formLocked"
+                  @input="
+                    batchInput(workOrderInfo.handoverWeight, 'handoverWeight')
+                  "
+                >
+                  <template slot="append">KG</template>
+                </el-input>
+              </el-form-item>
+              <div class="form_item form_item_empty"></div>
+            </div>
+            <div class="form_row_full">
+              <el-form-item
+                label="交接注意事项"
+                prop="remark"
+                class="form_item_full"
+              >
+                <el-input
+                  v-model="workOrderInfo.remark"
+                  type="textarea"
+                  :rows="3"
+                  placeholder="请输入"
+                  :disabled="formLocked"
+                />
+              </el-form-item>
+            </div>
+          </el-form>
+        </div>
+
+        <!-- 物料清单 -->
+        <div class="card_box">
+          <div class="title_box rx-bc">
+            <div class="name">物料清单</div>
+            <div class="rx-bc action_btns" v-if="!formLocked">
+              <el-button type="primary" size="small" @click="addMaterial"
+                >添加物料</el-button
+              >
+              <el-button type="danger" size="small" @click="batchDeleteMaterial"
+                >批量删除</el-button
+              >
+            </div>
+          </div>
+          <ele-pro-table
+            ref="table"
+            :key="'handover-material-' + (formLocked ? 'ro' : 'rw')"
+            :needPage="false"
+            :columns="columns"
+            :datasource="workOrderInfo.detailList"
+            class="time-form"
+            max-height="600"
+            @columns-change="handleColumnChange"
+            :cache-key="cacheKeyUrl"
+            :selection.sync="selection"
+            row-key="id"
+          >
+            <template v-slot:action="{ row }">
+              <el-popconfirm
+                class="ele-action"
+                title="确定要删除此物料吗?"
+                @confirm="remove(row)"
+              >
+                <template v-slot:reference>
+                  <el-link type="danger" :underline="false"> 删除 </el-link>
+                </template>
+              </el-popconfirm>
+            </template>
+          </ele-pro-table>
+        </div>
+
+        <!-- 接收信息:status 为 1(待接收)且非详情时展示 -->
+        <div class="card_box receive_card" v-if="showReceiveBlock">
+          <div class="title_box rx-bc">
+            <div class="name">接收信息</div>
+          </div>
+          <el-form label-width="100px" size="small" class="receive_form">
+            <el-form-item label="接收建议" required>
+              <el-input
+                v-model="workOrderInfo.acceptRemark"
+                type="textarea"
+                :rows="4"
+                placeholder="请输入"
+              />
+            </el-form-item>
+            <div class="receive_actions">
+              <el-button
+                type="success"
+                size="small"
+                :loading="receiveSubmitting"
+                @click="handleReceiveDecision(2)"
+                >同意</el-button
+              >
+              <el-button
+                type="danger"
+                size="small"
+                :loading="receiveSubmitting"
+                @click="handleReceiveDecision(3)"
+                >驳回</el-button
+              >
+            </div>
+          </el-form>
+        </div>
+      </div>
+
+      <pickingList
+        v-if="!formLocked"
+        isType="pick"
+        ref="pickingListRef"
+        @allSelection="allSelection"
+      ></pickingList>
+    </div>
+
+    <div slot="footer" class="dialog-footer" v-if="showMainFooter">
+      <el-button
+        type="primary"
+        size="small"
+        :loading="submitLoading"
+        @click="submitHandover(1)"
+        >提交</el-button
+      >
+      <el-button
+        type="primary"
+        size="small"
+        :loading="submitLoading"
+        @click="submitHandover(0)"
+        >保存</el-button
+      >
+      <el-button size="small" :disabled="submitLoading" @click="onCancel"
+        >取 消</el-button
+      >
+    </div>
+  </ele-modal>
+</template>
+
+<script>
+  import { getteampage } from '@/api/workforceManagement/team';
+  import {
+    getHandoverById,
+    updateHandover,
+    acceptOrRejectHandover
+  } from '@/api/workOrderHandover';
+  import pickingList from '@/views/produce/components/picking/newPickingList.vue';
+  export default {
+    name: 'WorkOrderHandoverEdit',
+
+    components: {
+      pickingList
+    },
+
+    props: {
+      visible: {
+        type: Boolean,
+        default: true
+      },
+      title: {
+        type: String,
+        default: '工单交接'
+      },
+      /** 交接单主键 id,编辑/查看时传入;新增不传或传空 */
+      workOrderId: {
+        type: [String, Number],
+        default: ''
+      },
+      /** 列表行状态:0 待提交 1 待接收 2 已接收 3 已驳回 */
+      status: {
+        type: [Number, String],
+        default: 0
+      },
+      /** 仅查看详情:全部只读且不展示任何操作按钮 */
+      detail: {
+        type: Boolean,
+        default: false
+      }
+    },
+    data() {
+      return {
+        loading: false,
+        dialogVisible: false,
+        workOrderInfo: {
+          acceptRemark: '',
+          detailList: []
+        },
+        handoverRules: {
+          type: [
+            { required: true, message: '请选择交接类型', trigger: 'change' }
+          ],
+          initiateTime: [
+            { required: true, message: '请选择交接时间', trigger: 'change' }
+          ],
+          acceptTeamId: [
+            { required: true, message: '请选择交接班组', trigger: 'change' }
+          ],
+          acceptId: [
+            { required: true, message: '请选择交接人', trigger: 'change' }
+          ],
+          handoverQuantity: [
+            { required: true, message: '请选择交接数量', trigger: 'change' }
+          ],
+          handoverWeight: [
+            { required: true, message: '请选择交接重量', trigger: 'change' }
+          ]
+        },
+        teamList: [],
+        personList: [],
+        selection: [],
+        cacheKeyUrl: 'mes_workOrderHandover_materialTable',
+        receiveSubmitting: false,
+        submitLoading: false
+      };
+    },
+    watch: {
+      visible: {
+        immediate: true,
+        handler(v) {
+          this.dialogVisible = v;
+        }
+      },
+      /** 与 ele-modal 同步:弹窗真正显示为 true 时再拉取详情(不监听 workOrderId) */
+      dialogVisible(v) {
+        if (v) {
+          this.$nextTick(() => {
+            this.bootstrapDialog();
+          });
+        }
+      }
+    },
+
+    computed: {
+      numericStatus() {
+        const s = this.status;
+        if (s === '' || s === null || s === undefined) return 0;
+        const n = Number(s);
+        return Number.isNaN(n) ? 0 : n;
+      },
+      /** 详情 或 待接收:交接信息与物料不可编辑 */
+      formLocked() {
+        if (this.detail) return true;
+        return this.numericStatus === 1;
+      },
+      /** 展示底部 提交 / 保存 / 取消 */
+      showMainFooter() {
+        if (this.detail) return false;
+        return this.numericStatus !== 1;
+      },
+      /** 展示「接收信息」区块(同意 / 驳回) */
+      showReceiveBlock() {
+        if (this.detail) return false;
+        return this.numericStatus === 1;
+      },
+      handoverCodeDisplay() {
+        const w = this.workOrderInfo || {};
+        return w.handoverCode || w.code || '';
+      },
+      columns() {
+        const cols = [];
+        if (!this.formLocked) {
+          cols.push({
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left',
+            reserveSelection: true
+          });
+        }
+        cols.push(
+          {
+            width: 55,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center',
+            label: '序号',
+            fixed: 'left'
+          },
+          {
+            prop: 'categoryCode',
+            label: '编码',
+            align: 'center',
+            showOverflowTooltip: true,
+            sortable: true,
+            minWidth: 110
+          },
+          {
+            prop: 'categoryName',
+            label: '名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'categoryLevelPath',
+            label: '分类',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'brandNum',
+            align: 'center',
+            label: '牌号',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'modelType',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'measureUnit',
+            label: '计量单位',
+            showOverflowTooltip: true,
+            minWidth: 90
+          },
+          {
+            prop: 'weightUnit',
+            label: '重量单位',
+            showOverflowTooltip: true,
+            minWidth: 90
+          }
+        );
+        if (!this.formLocked) {
+          cols.push({
+            columnKey: 'action',
+            label: '操作',
+            width: 100,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            fixed: 'right'
+          });
+        }
+        return cols;
+      }
+    },
+
+    methods: {
+      hasValidId(val) {
+        return (
+          val !== '' &&
+          val !== null &&
+          val !== undefined &&
+          String(val).length > 0
+        );
+      },
+      getSafeDetailList() {
+        return Array.isArray(this.workOrderInfo.detailList)
+          ? this.workOrderInfo.detailList
+          : [];
+      },
+      closeDialogWithSuccess(res) {
+        this.dialogVisible = false;
+        this.$emit('update:visible', false);
+        this.$emit('success', res);
+      },
+      closeDialogByCancel() {
+        this.dialogVisible = false;
+        this.$emit('update:visible', false);
+        this.$emit('cancel');
+      },
+      clearAndRestoreTableSelection() {
+        this.$nextTick(() => {
+          this.$refs.table && this.$refs.table.clearSelection();
+          this.selection.forEach((item) => {
+            this.$refs.table && this.$refs.table.toggleRowSelection(item, true);
+          });
+        });
+      },
+      formatDateTimeNow() {
+        const d = new Date();
+        const p = (n) => String(n).padStart(2, '0');
+        return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(
+          d.getHours()
+        )}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
+      },
+
+      /** 防止 acceptRemark 未定义导致模板报错 */
+      ensureAcceptRemarkShape() {
+        const w = this.workOrderInfo;
+        if (!w || typeof w !== 'object') return;
+        if (w.acceptRemark === undefined) {
+          this.$set(w, 'acceptRemark', '');
+        }
+      },
+
+      pickAcceptRemarkFromRaw(raw) {
+        if (!raw || typeof raw !== 'object') return '';
+        if (raw.acceptRemark !== undefined && raw.acceptRemark !== null) {
+          return raw.acceptRemark;
+        }
+        if (
+          raw.receiveAdvice &&
+          typeof raw.receiveAdvice === 'object' &&
+          raw.receiveAdvice !== null
+        ) {
+          return (
+            raw.receiveAdvice.acceptRemark || raw.receiveAdvice.remark || ''
+          );
+        }
+        if (typeof raw.receiveAdvice === 'string') {
+          return raw.receiveAdvice;
+        }
+        return raw.receiveRemark || '';
+      },
+
+      getEmptyWorkOrderState() {
+        return {
+          type: 1,
+          initiateTime: this.formatDateTimeNow(),
+          handoverCode: '',
+          workOrderCode: '',
+          categoryCode: '',
+          categoryName: '',
+          productCode: '',
+          productName: '',
+          batchNo: '',
+          unit: '',
+          weightUnit: '',
+          formingNum: '',
+          formingWeight: '',
+          productionCodes: '',
+          formedNum: 0,
+          formedWeight: 0,
+          currentTaskDiagram: {},
+          initiateTaskId: '',
+          initiateTaskName: '',
+          acceptTeamId: '',
+          acceptTeamName: '',
+          acceptId: '',
+          acceptName: '',
+          handoverQuantity: '',
+          handoverWeight: '',
+          remark: '',
+          acceptRemark: '',
+          detailList: [],
+          workOrderId: ''
+        };
+      },
+
+      normalizeHandoverDetail(raw) {
+        if (!raw || typeof raw !== 'object') {
+          return this.getEmptyWorkOrderState();
+        }
+        const detailList = Array.isArray(raw.detailList) ? raw.detailList : [];
+        const task = raw.currentTaskDiagram || {};
+        return {
+          ...raw,
+          detailList,
+          categoryCode: raw.categoryCode || raw.productCode || '',
+          categoryName: raw.categoryName || raw.productName || '',
+          workOrderCode:
+            raw.workOrderCode ||
+            raw.workOrderNum ||
+            raw.workOrderEntityCode ||
+            '',
+          handoverCode:
+            raw.handoverCode || raw.passonCode || raw.passonOrderCode || '',
+          acceptRemark: this.pickAcceptRemarkFromRaw(raw),
+          currentTaskDiagram:
+            raw.currentTaskDiagram ||
+            (raw.initiateTaskId || raw.initiateTaskName
+              ? {
+                  taskId: raw.initiateTaskId,
+                  taskTypeName: raw.initiateTaskName
+                }
+              : task)
+        };
+      },
+
+      async loadTeamListOnly() {
+        const factoryId = this.$store.state.user.info.factoryId;
+        const { list } = await getteampage({
+          pageNum: 1,
+          size: -1,
+          factoryId
+        });
+        this.teamList = Array.isArray(list) ? list : [];
+        if (!this.teamList.length) {
+          this.personList = [];
+        }
+      },
+
+      /** 弹窗打开(visible 为 true)时统一拉取/初始化,不依赖对 workOrderId 的 watch */
+      async bootstrapDialog() {
+        if (!this.visible) return;
+        this.loading = true;
+        try {
+          const id = this.workOrderId;
+          const hasId = this.hasValidId(id);
+
+          await this.loadTeamListOnly();
+
+          if (hasId) {
+            const res = await getHandoverById(id);
+            this.workOrderInfo = this.normalizeHandoverDetail(res);
+            const preferredAcceptId = this.workOrderInfo.acceptId;
+            if (
+              this.workOrderInfo.acceptTeamId != null &&
+              this.workOrderInfo.acceptTeamId !== ''
+            ) {
+              this.syncTeamUsersAndLeader(
+                this.workOrderInfo.acceptTeamId,
+                preferredAcceptId
+              );
+            } else if (this.teamList.length) {
+              const first = this.teamList[0].id;
+              this.workOrderInfo.acceptTeamId = first;
+              this.syncTeamUsersAndLeader(first);
+            }
+          } else {
+            this.workOrderInfo = this.getEmptyWorkOrderState();
+            if (this.teamList.length) {
+              const first = this.teamList[0].id;
+              this.workOrderInfo.acceptTeamId = first;
+              this.syncTeamUsersAndLeader(first);
+            }
+          }
+        } catch (e) {
+          this.$message.error((e && e.message) || '加载交接单失败');
+          this.workOrderInfo = this.getEmptyWorkOrderState();
+        } finally {
+          this.loading = false;
+          this.ensureAcceptRemarkShape();
+          this.$nextTick(() => {
+            this.$refs.handoverForm && this.$refs.handoverForm.clearValidate();
+          });
+        }
+      },
+
+      onHandoverTeamChange(teamId) {
+        this.syncTeamUsersAndLeader(teamId);
+      },
+      onAcceptPersonChange(personId) {
+        const person =
+          this.personList.find(
+            (item) => String(item.id) === String(personId)
+          ) || {};
+        this.workOrderInfo.acceptName = person.name || '';
+      },
+      /**
+       * @param preferredAcceptId 详情接口已返回的交接人 id,若在班组人员中则优先保留
+       */
+      syncTeamUsersAndLeader(teamId, preferredAcceptId) {
+        const currentTeam =
+          this.teamList.find((item) => String(item.id) === String(teamId)) ||
+          {};
+        this.workOrderInfo.acceptTeamName = currentTeam.name || '';
+        const users = Array.isArray(currentTeam.userVOList)
+          ? currentTeam.userVOList
+          : [];
+        this.personList = users.map((user) => ({
+          id: user.id,
+          name: user.name
+        }));
+        if (!this.personList.length) {
+          this.workOrderInfo.acceptId = '';
+          this.workOrderInfo.acceptName = '';
+          return;
+        }
+        if (
+          preferredAcceptId != null &&
+          preferredAcceptId !== '' &&
+          this.personList.some(
+            (u) => String(u.id) === String(preferredAcceptId)
+          )
+        ) {
+          const pick =
+            this.personList.find(
+              (u) => String(u.id) === String(preferredAcceptId)
+            ) || {};
+          this.workOrderInfo.acceptId = pick.id;
+          this.workOrderInfo.acceptName = pick.name || '';
+          return;
+        }
+        const leader =
+          this.personList.find(
+            (user) => String(user.id) === String(currentTeam.leaderUserId)
+          ) || this.personList[0];
+        this.workOrderInfo.acceptId = leader.id;
+        this.workOrderInfo.acceptName = leader.name || '';
+      },
+
+      batchInput(val, type) {
+        let newVal = String(val == null ? '' : val).replace(/[^\d.]/g, '');
+
+        if (newVal.startsWith('.')) {
+          newVal = '';
+        }
+
+        const firstDotIndex = newVal.indexOf('.');
+        if (firstDotIndex !== -1) {
+          newVal =
+            newVal.slice(0, firstDotIndex + 1) +
+            newVal.slice(firstDotIndex + 1).replace(/\./g, '');
+        }
+
+        const match = newVal.match(/^(\d+)(\.\d{0,4})?/);
+
+        if (type === 'handoverQuantity') {
+          this.workOrderInfo.handoverQuantity = match ? match[0] : '';
+        } else if (type === 'handoverWeight') {
+          this.workOrderInfo.handoverWeight = match ? match[0] : '';
+        }
+      },
+
+      submitHandover(isRelease) {
+        if (this.formLocked || this.detail || this.submitLoading) return;
+        this.$refs.handoverForm.validate((valid) => {
+          if (!valid) return;
+          const detailList = this.getSafeDetailList().map((row) => {
+            const { id, ...rest } = row;
+            return rest;
+          });
+          const param = {
+            ...this.workOrderInfo,
+            detailList,
+            isRelease
+          };
+
+          this.submitLoading = true;
+          updateHandover(param)
+            .then((res) => {
+              this.$message.success(isRelease === 1 ? '提交成功' : '保存成功');
+              this.closeDialogWithSuccess(res);
+            })
+            .catch((e) => {
+              this.$message.error((e && e.message) || '提交失败');
+            })
+            .finally(() => {
+              this.submitLoading = false;
+            });
+        });
+      },
+
+      /** 同意 status=2,驳回 status=3 */
+      async handleReceiveDecision(status) {
+        const advice = (this.workOrderInfo.acceptRemark || '').trim();
+        if (!advice) {
+          this.$message.warning('请填写接收建议');
+          return;
+        }
+        if (!this.hasValidId(this.workOrderInfo.id)) {
+          this.$message.warning('缺少交接单信息');
+          return;
+        }
+        this.receiveSubmitting = true;
+        try {
+          const res = await acceptOrRejectHandover({
+            id: this.workOrderInfo.id,
+            status,
+            acceptRemark: advice
+          });
+          this.$message.success(status === 2 ? '接收成功' : '已驳回');
+          this.closeDialogWithSuccess(res);
+        } catch (e) {
+          this.$message.error((e && e.message) || '操作失败');
+        } finally {
+          this.receiveSubmitting = false;
+        }
+      },
+      onModalClose() {
+        this.closeDialogByCancel();
+      },
+      onCancel() {
+        this.closeDialogByCancel();
+      },
+      handleColumnChange() {},
+      addMaterial() {
+        const pickList = this.getSafeDetailList();
+        this.$refs.pickingListRef.open(
+          '',
+          {
+            pickList
+          },
+          '选择列表'
+        );
+      },
+      batchDeleteMaterial() {
+        const selected = Array.isArray(this.selection) ? this.selection : [];
+        if (!selected.length) {
+          this.$message.warning('请先选择要删除的物料');
+          return;
+        }
+        this.$confirm(
+          `确定删除已选中的 ${selected.length} 条物料吗?`,
+          '提示',
+          {
+            type: 'warning'
+          }
+        )
+          .then(() => {
+            const detailList = this.getSafeDetailList();
+            const selectedIds = new Set(
+              selected
+                .map((item) => (item && item.id != null ? String(item.id) : ''))
+                .filter(Boolean)
+            );
+            let next;
+            if (selectedIds.size) {
+              next = detailList.filter(
+                (item) => !selectedIds.has(String(item.id))
+              );
+            } else {
+              next = detailList.filter((item) => !selected.includes(item));
+            }
+            this.$set(this.workOrderInfo, 'detailList', next);
+            this.selection = [];
+            this.$nextTick(() => {
+              this.$refs.table && this.$refs.table.clearSelection();
+            });
+            this.$message.success('删除成功');
+          })
+          .catch(() => {});
+      },
+      allSelection(id, list) {
+        this.$set(this.workOrderInfo, 'detailList', list || []);
+      },
+      /* 删除 */
+      remove(row) {
+        if (!row) return;
+        const detailList = this.getSafeDetailList();
+        const next = detailList.filter((item) => {
+          if (row.id != null && item.id != null) {
+            return String(item.id) !== String(row.id);
+          }
+          return item !== row;
+        });
+        this.$set(this.workOrderInfo, 'detailList', next);
+        this.selection = this.selection.filter((item) => {
+          if (row.id != null && item.id != null) {
+            return String(item.id) !== String(row.id);
+          }
+          return item !== row;
+        });
+        this.clearAndRestoreTableSelection();
+        this.$message.success('删除成功');
+      }
+    }
+  };
+</script>
+
+<style scoped lang="scss">
+  .work-order-handover {
+    background: #fff;
+    padding: 0;
+    max-height: calc(100vh - 220px);
+    overflow-y: auto;
+  }
+
+  .handover_content {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .card_box {
+    background: #fff;
+    padding: 12px;
+    border-radius: 2px;
+    border: 1px solid #e3e5e5;
+  }
+
+  .title_box {
+    padding: 10px 0;
+    margin-bottom: 8px;
+
+    .name {
+      font-size: 14px;
+      font-style: normal;
+      font-weight: 400;
+      color: #157a2c;
+      padding-left: 5px;
+      position: relative;
+
+      &::before {
+        position: absolute;
+        content: '';
+        left: 0;
+        top: 0;
+        bottom: 0;
+        width: 2px;
+        height: 14px;
+        background: #157a2c;
+        margin: auto;
+      }
+    }
+  }
+
+  /* 工单信息表格 */
+  .material {
+    margin-top: 6px;
+
+    .content_table {
+      width: 100%;
+      max-width: 100%;
+      box-sizing: border-box;
+      border: 1px solid #e3e5e5;
+      overflow: hidden;
+
+      .item {
+        display: flex;
+        min-width: 0;
+        border-bottom: 1px solid #e3e5e5;
+
+        &:last-child {
+          border-bottom: none;
+        }
+
+        .rx {
+          display: flex;
+          flex: 1;
+          min-width: 0;
+        }
+
+        /* 三等分:避免 33.33%×3 舍入与 flex-shrink:0 撑破卡片 */
+        .ww16 {
+          flex: 1 1 0;
+          min-width: 0;
+        }
+
+        .lable {
+          width: 100px;
+          min-width: 100px;
+          flex-shrink: 0;
+          text-align: center;
+          background-color: #f7f9fa;
+          font-size: 12px;
+          border-right: 1px solid #e3e5e5;
+          padding: 8px 4px;
+          box-sizing: border-box;
+        }
+
+        .content {
+          flex: 1;
+          min-width: 0;
+          min-height: 32px;
+          font-size: 13px;
+          padding: 8px 10px;
+          box-sizing: border-box;
+          background: #fff;
+          border-right: 1px solid #e3e5e5;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+
+          &:last-child {
+            border-right: none;
+          }
+        }
+      }
+    }
+  }
+
+  /* 交接信息表单:三列等宽网格,与第一行对齐;避免 space-between 拉散两列行 */
+  .handover_form {
+    .form_row_full {
+      margin-bottom: 12px;
+    }
+
+    .form_row {
+      display: grid;
+      grid-template-columns: repeat(3, minmax(0, 1fr));
+      column-gap: 16px;
+      margin-bottom: 12px;
+      align-items: start;
+    }
+
+    .form_item {
+      width: 100%;
+      min-width: 0;
+      margin-bottom: 0;
+      display: flex;
+      align-items: center;
+
+      ::v-deep .el-form-item__label {
+        padding-right: 8px;
+      }
+
+      ::v-deep .el-form-item__content {
+        flex: 1;
+        min-width: 0;
+        margin-left: 0 !important;
+      }
+
+      ::v-deep .el-select,
+      ::v-deep .el-date-editor.el-input {
+        width: 100% !important;
+      }
+
+      ::v-deep .el-select .el-input__inner,
+      ::v-deep .el-date-editor .el-input__inner {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+    }
+
+    .form_item_empty {
+      min-width: 0;
+    }
+
+    .form_item_full {
+      width: 100%;
+      margin-bottom: 0;
+    }
+  }
+
+  .material_table {
+    margin-top: 8px;
+  }
+
+  .receive_card {
+    .receive_form {
+      margin-top: 4px;
+    }
+
+    .receive_actions {
+      display: flex;
+      gap: 12px;
+      padding-left: 100px;
+      margin-top: 4px;
+    }
+  }
+</style>

+ 0 - 177
src/views/workOrderHandover/components/handover-search.vue

@@ -1,177 +0,0 @@
-<!-- 工单交接 - 搜索表单 -->
-<template>
-  <el-form
-    label-width="120px"
-    class="ele-form-search handover-form-search"
-    @keyup.enter.native="search"
-    @submit.native.prevent
-  >
-    <el-row :gutter="15">
-      <el-col :span="6">
-        <el-form-item label="生产工单号">
-          <el-input
-            v-model="where.workOrderCode"
-            clearable
-            placeholder="请输入"
-          />
-        </el-form-item>
-      </el-col>
-      <el-col :span="6">
-        <el-form-item label="交接类型">
-          <el-select
-            v-model="where.handoverType"
-            placeholder="请选择"
-            clearable
-            style="width: 100%"
-          >
-            <el-option
-              v-for="item in handoverTypeOptions"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            />
-          </el-select>
-        </el-form-item>
-      </el-col>
-      <el-col :span="6">
-        <el-form-item label="交接时间">
-          <el-date-picker
-            v-model="where.handoverTime"
-            type="daterange"
-            range-separator="至"
-            start-placeholder="开始日期"
-            end-placeholder="结束日期"
-            value-format="yyyy-MM-dd HH:mm:ss"
-            :default-time="['00:00:00', '23:59:59']"
-            style="width: 100%"
-          />
-        </el-form-item>
-      </el-col>
-      <el-col :span="6">
-        <el-form-item label-width="0">
-          <div class="search-actions">
-            <el-link
-              type="primary"
-              :underline="false"
-              class="more-query"
-              @click="expandMore = !expandMore"
-            >
-              {{ expandMore ? '收起' : '+ 更多查询' }}
-            </el-link>
-            <el-button
-              type="primary"
-              icon="el-icon-search"
-              class="ele-btn-icon"
-              @click="search"
-            >
-              查询
-            </el-button>
-            <el-button
-              icon="el-icon-refresh-left"
-              type="primary"
-              plain
-              @click="reset"
-            >
-              重置
-            </el-button>
-          </div>
-        </el-form-item>
-      </el-col>
-    </el-row>
-    <el-row v-show="expandMore" :gutter="15">
-      <el-col :span="6">
-        <el-form-item label="产品编码">
-          <el-input
-            v-model="where.productCode"
-            clearable
-            placeholder="请输入"
-          />
-        </el-form-item>
-      </el-col>
-      <el-col :span="6">
-        <el-form-item label="产品名称">
-          <el-input
-            v-model="where.productName"
-            clearable
-            placeholder="请输入"
-          />
-        </el-form-item>
-      </el-col>
-      <el-col :span="6">
-        <el-form-item label="批次号">
-          <el-input
-            v-model="where.batchNo"
-            clearable
-            placeholder="请输入"
-          />
-        </el-form-item>
-      </el-col>
-      <el-col :span="6">
-        <el-form-item label="交接班组/班次">
-          <el-input
-            v-model="where.handoverTeamShift"
-            clearable
-            placeholder="请输入"
-          />
-        </el-form-item>
-      </el-col>
-    </el-row>
-  </el-form>
-</template>
-
-<script>
-export default {
-  data() {
-    const defaultWhere = {
-      workOrderCode: '',
-      handoverType: '',
-      handoverTime: [],
-      productCode: '',
-      productName: '',
-      batchNo: '',
-      handoverTeamShift: ''
-    };
-    return {
-      where: { ...defaultWhere },
-      defaultWhere,
-      expandMore: false,
-      handoverTypeOptions: [
-        { label: '物品移交', value: 'item' },
-        { label: '班次交接', value: 'shift' },
-        { label: '工序交接', value: 'process' }
-      ]
-    };
-  },
-  methods: {
-    search() {
-      const where = { ...this.where };
-      if (where.handoverTime && where.handoverTime.length === 2) {
-        where.handoverTimeStart = where.handoverTime[0];
-        where.handoverTimeEnd = where.handoverTime[1];
-      }
-      delete where.handoverTime;
-      this.$emit('search', where);
-    },
-    reset() {
-      this.where = { ...this.defaultWhere };
-      this.search();
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.handover-form-search {
-  .search-actions {
-    display: flex;
-    align-items: center;
-    justify-content: flex-end;
-    flex-wrap: wrap;
-    gap: 8px;
-  }
-  .more-query {
-    margin-right: 8px;
-    font-size: 14px;
-  }
-}
-</style>

+ 224 - 30
src/views/workOrderHandover/index.vue

@@ -1,22 +1,22 @@
 <template>
   <div class="ele-body">
     <el-card shadow="never" v-loading="loading">
-      <!-- <handover-search ref="searchRef" @search="reload" /> -->
       <seek-page :seekList="seekList" @search="search"></seek-page>
 
       <ele-pro-table
         ref="table"
         :columns="columns"
         :datasource="datasource"
+        :init-load="false"
+        :selection.sync="selection"
         row-key="id"
         cache-key="workOrderHandoverTable"
-        :init-load="true"
         :height="tableHeight"
-        :pageSize="10"
+        :pageSize="20"
         @fullscreen-change="fullscreenChange"
       >
         <template v-slot:toolbar>
-          <el-button
+          <!-- <el-button
             size="small"
             type="primary"
             icon="el-icon-plus"
@@ -24,36 +24,152 @@
             @click="handleAdd"
           >
             新增
+          </el-button> -->
+          <el-button
+            size="small"
+            type="danger"
+            icon="el-icon-delete"
+            class="ele-btn-icon"
+            @click="handleBatchDelete"
+          >
+            批量删除
           </el-button>
         </template>
+
+        <template v-slot:code="{ row }">
+          <el-link type="primary" :underline="false" @click="handleDetail(row)">
+            {{ row.code }}
+          </el-link>
+        </template>
+
+        <template v-slot:status="{ row }">
+          <el-tag v-if="getStatusMeta(row.status).text" :type="getStatusMeta(row.status).type">
+            {{ getStatusMeta(row.status).text }}
+          </el-tag>
+        </template>
+
+        <template v-slot:action="{ row }">
+          <el-link
+            type="primary"
+            size="small"
+            :underline="false"
+            @click="handleAccept(row)"
+            v-if="row.status == 1"
+            >接收</el-link
+          >
+
+          <el-link
+            type="primary"
+            size="small"
+            :underline="false"
+            @click="handleDetail(row)"
+            v-if="row.status == 2"
+            >详情</el-link
+          >
+
+          <el-link
+            type="primary"
+            size="small"
+            :underline="false"
+            @click="handleEdit(row)"
+            v-if="isEditableStatus(row.status)"
+            >编辑</el-link
+          >
+          <el-link
+            type="danger"
+            size="small"
+            :underline="false"
+            @click="handleDelete(row)"
+            v-if="isEditableStatus(row.status)"
+            >删除</el-link
+          >
+        </template>
       </ele-pro-table>
     </el-card>
+
+    <handover-edit
+      :visible.sync="editVisible"
+      :title="editDetail ? '工单交接详情' : '工单交接'"
+      :workOrderId="editWorkOrderId"
+      :status="editStatus"
+      :detail="editDetail"
+      @success="onHandoverSuccess"
+    />
   </div>
 </template>
 
 <script>
-  import handoverSearch from './components/handover-search.vue';
-  import { handoverPage } from '@/api/workOrderHandover/index.js';
+  import handoverEdit from './components/edit.vue';
+  import {
+    handoverPage,
+    deleteHandover
+  } from '@/api/workOrderHandover/index.js';
+
+  const STATUS = {
+    DRAFT: 0,
+    PENDING_ACCEPT: 1,
+    ACCEPTED: 2,
+    REJECTED: 3
+  };
+
+  const STATUS_META = {
+    [STATUS.DRAFT]: { type: 'warning', text: '待提交' },
+    [STATUS.PENDING_ACCEPT]: { type: 'warning', text: '待接收' },
+    [STATUS.ACCEPTED]: { type: 'success', text: '已接收' },
+    [STATUS.REJECTED]: { type: 'success', text: '已驳回' }
+  };
+
+  const EDITABLE_STATUS_LIST = [STATUS.DRAFT, STATUS.REJECTED];
+
+  const HANDOVER_TYPE_MAP = {
+    1: '班组移交',
+    2: '班次交接'
+  };
 
   export default {
     name: 'WorkOrderHandover',
     components: {
-      handoverSearch
+      handoverEdit
     },
     data() {
       return {
         loading: false,
         tableHeight: 'calc(100vh - 340px)',
+        editVisible: false,
+        editWorkOrderId: '',
+        /** 列表行 status:0 待提交 1 待接收 2 已接收 3 已驳回;新增时为 0 */
+        editStatus: 0,
+        /** true 时仅查看,全部只读且不展示操作按钮 */
+        editDetail: false,
+        selection: [],
         handoverTypeOptions: [
-          { label: '物品移交', value: 'item' },
-          { label: '班次交接', value: 'shift' },
-          { label: '工序交接', value: 'process' }
-        ]
+          { label: '班组移交', value: 1 },
+          { label: '班次交接', value: 2 }
+        ],
+        /** keep-alive 下避免与 mounted 重复请求;第二次 activated 再拉列表 */
+        _handoverListHasActivated: false
       };
     },
+    mounted() {
+      this.reload();
+    },
+    activated() {
+      if (this._handoverListHasActivated) {
+        this.reload();
+      }
+      this._handoverListHasActivated = true;
+    },
     computed: {
       columns() {
         return [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            selectable: this.selectableRow,
+            fixed: true
+          },
           {
             columnKey: 'index',
             label: '序号',
@@ -63,29 +179,25 @@
             showOverflowTooltip: true
           },
           {
-            prop: 'handoverType',
+            prop: 'type',
             label: '交接类型',
             align: 'center',
             minWidth: 100,
             showOverflowTooltip: true,
             formatter: (row, column, cellValue) => {
-              const map = {
-                item: '物品移交',
-                shift: '班次交接',
-                process: '工序交接'
-              };
-              return map[cellValue] || cellValue;
+              return HANDOVER_TYPE_MAP[cellValue] || cellValue;
             }
           },
           {
-            prop: 'handoverCode',
+            prop: 'code',
+            slot: 'code',
             label: '交接单编码',
             align: 'center',
             minWidth: 120,
             showOverflowTooltip: true
           },
           {
-            prop: 'handoverTime',
+            prop: 'initiateTime',
             label: '交接时间',
             align: 'center',
             minWidth: 160,
@@ -99,14 +211,14 @@
             showOverflowTooltip: true
           },
           {
-            prop: 'productCode',
+            prop: 'categoryCode',
             label: '产品编码',
             align: 'center',
             minWidth: 120,
             showOverflowTooltip: true
           },
           {
-            prop: 'productName',
+            prop: 'categoryName',
             label: '产品名称',
             align: 'center',
             minWidth: 120,
@@ -120,7 +232,7 @@
             showOverflowTooltip: true
           },
           {
-            prop: 'processName',
+            prop: 'initiateTaskName',
             label: '工序名称',
             align: 'center',
             minWidth: 100,
@@ -155,14 +267,14 @@
             showOverflowTooltip: true
           },
           {
-            prop: 'handoverTeamShift',
+            prop: 'acceptTeamName',
             label: '交接班组/班次',
             align: 'center',
             minWidth: 120,
             showOverflowTooltip: true
           },
           {
-            prop: 'handoverUser',
+            prop: 'acceptName',
             label: '交接人',
             align: 'center',
             minWidth: 120,
@@ -182,10 +294,18 @@
             minWidth: 120,
             showOverflowTooltip: true
           },
+          {
+            prop: 'status',
+            slot: 'status',
+            label: '交接单状态',
+            align: 'center',
+            minWidth: 120,
+            showOverflowTooltip: true
+          },
           {
             columnKey: 'action',
             label: '操作',
-            width: 90,
+            width: 160,
             align: 'center',
             resizable: false,
             fixed: 'right',
@@ -206,7 +326,7 @@
           },
           {
             label: '交接类型:',
-            value: 'planType',
+            value: 'type',
             type: 'select',
             placeholder: '',
             planList: this.handoverTypeOptions
@@ -222,6 +342,15 @@
       }
     },
     methods: {
+      getStatusMeta(status) {
+        return STATUS_META[status] || {};
+      },
+      isEditableStatus(status) {
+        return EDITABLE_STATUS_LIST.includes(status);
+      },
+      selectableRow(row) {
+        return this.isEditableStatus(row.status);
+      },
       datasource({ page, limit, where }) {
         return handoverPage({
           pageNum: page,
@@ -239,11 +368,76 @@
         this.reload(e);
       },
 
+      openEditor(row = {}, detail = false) {
+        this.editWorkOrderId = row.id || '';
+        this.editStatus = row.status ?? STATUS.DRAFT;
+        this.editDetail = detail;
+        this.editVisible = true;
+      },
       handleAdd() {
-        // 跳转至生产-工单交接新增或打开弹窗,按业务接入
-        this.$message.info(
-          '请在生产-工单详情中发起交接,或在此处接入新增弹窗/路由'
+        this.openEditor({}, false);
+      },
+
+      handleEdit(row) {
+        this.openEditor(row, false);
+      },
+
+      handleDetail(row) {
+        this.openEditor(row, true);
+      },
+
+      handleAccept(row) {
+        this.openEditor(row, false);
+      },
+
+      async handleDelete(row) {
+        try {
+          await this.$confirm('确定删除该交接单?', '提示', {
+            type: 'warning'
+          });
+          await deleteHandover([row.id]);
+          this.$message.success('删除成功');
+          this.reload();
+        } catch (e) {
+          if (e !== 'cancel') {
+            this.$message.error((e && e.message) || '删除失败');
+          }
+        }
+      },
+      async handleBatchDelete() {
+        if (!this.selection.length) {
+          this.$message.warning('请选择需要删除的数据');
+          return;
+        }
+        const invalidRows = this.selection.filter(
+          (item) => !this.isEditableStatus(item.status)
         );
+        if (invalidRows.length) {
+          this.$message.warning('仅待提交或已驳回状态可删除');
+          return;
+        }
+        const ids = this.selection.map((item) => item.id);
+        try {
+          await this.$confirm(
+            `确定删除选中的 ${ids.length} 条交接单记录吗?`,
+            '提示',
+            {
+              type: 'warning'
+            }
+          );
+          await deleteHandover(ids);
+          this.$message.success('批量删除成功');
+          this.selection = [];
+          this.reload();
+        } catch (e) {
+          if (e !== 'cancel') {
+            this.$message.error((e && e.message) || '批量删除失败');
+          }
+        }
+      },
+
+      onHandoverSuccess() {
+        this.reload();
       },
       fullscreenChange(fullscreen) {
         this.tableHeight = fullscreen