Browse Source

Merge branch 'JN' of http://110.41.163.243:9980/kd-aiot/kd-aiot-frontend into dev

yusheng 11 months ago
parent
commit
a53858b2aa
31 changed files with 7112 additions and 16 deletions
  1. 16 0
      src/api/factoryModel/index.js
  2. 55 0
      src/api/productionScheduling/productionLineBeat.js
  3. 64 0
      src/api/productionScheduling/productionShift.js
  4. 104 0
      src/api/productionScheduling/stationBeat.js
  5. 101 0
      src/api/productionScheduling/workCalendar.js
  6. 376 0
      src/components/productList/product-list.vue
  7. 139 0
      src/components/productList/searchProduct.vue
  8. 183 0
      src/components/productList/tree.vue
  9. 15 3
      src/views/factoryModel/productionLine/components/edit.vue
  10. 7 1
      src/views/factoryModel/productionLine/index.vue
  11. 148 0
      src/views/factoryModel/station/components/ProduceDialogAll.vue
  12. 35 12
      src/views/factoryModel/station/components/edit.vue
  13. 276 0
      src/views/factoryModel/station/components/stationMeter.vue
  14. 373 0
      src/views/productionScheduling/productionLineBeat/components/addOrEditDialog.vue
  15. 59 0
      src/views/productionScheduling/productionLineBeat/components/searchTable.vue
  16. 331 0
      src/views/productionScheduling/productionLineBeat/index.vue
  17. 271 0
      src/views/productionScheduling/productionShift/components/addOrEditDialog.vue
  18. 44 0
      src/views/productionScheduling/productionShift/components/searchTable.vue
  19. 382 0
      src/views/productionScheduling/productionShift/components/timeTable.vue
  20. 292 0
      src/views/productionScheduling/productionShift/index.vue
  21. 557 0
      src/views/productionScheduling/stationBeat/components/addOrEditDialog.vue
  22. 59 0
      src/views/productionScheduling/stationBeat/components/searchTable.vue
  23. 404 0
      src/views/productionScheduling/stationBeat/index.vue
  24. 530 0
      src/views/productionScheduling/totalProcessTime/components/addOrEditDialog.vue
  25. 44 0
      src/views/productionScheduling/totalProcessTime/components/searchTable.vue
  26. 258 0
      src/views/productionScheduling/totalProcessTime/index.vue
  27. 88 0
      src/views/productionScheduling/workCalendar/components/searchTable.vue
  28. 473 0
      src/views/productionScheduling/workCalendar/components/timeTable.vue
  29. 739 0
      src/views/productionScheduling/workCalendar/index copy 2.vue
  30. 681 0
      src/views/productionScheduling/workCalendar/index.vue
  31. 8 0
      src/views/technology/production/index.vue

+ 16 - 0
src/api/factoryModel/index.js

@@ -217,3 +217,19 @@ export async function codeExists(code) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+// 获取工厂下的所有产线
+export async function listFactoryLineByFactoryId(params) {
+  const res = await request.post(`/main/factoryarea/listFactoryLineByFactoryId`, params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// 获取产线下的所有工位
+export async function listByProductionLineId(productionLineId) {
+  const res = await request.get(`/main/factoryworkstation/listByProductionLineId/${productionLineId}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 55 - 0
src/api/productionScheduling/productionLineBeat.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request';
+import { download } from '@/utils/file';
+/**
+ * 获取信息列表
+ */
+export async function getTableList(params) {
+  const res = await request.post(
+    `/aps/tapsproductionschedulingconfig/selectProductionSchedulingConfigPage`,
+    params
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 新增信息
+ */
+export async function saveOrUpdate(data) {
+  const res = await request.post(
+    `/aps/tapsproductionschedulingconfig/saveOrUpdate`,
+    data
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+/**
+ * 删除
+ */
+export async function deleteList(data) {
+  const res = await request.delete(
+    '/aps/tapsproductionschedulingconfig/deleteByIds',
+    { data }
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询明细
+ */
+export async function getById(id) {
+  const res = await request.get(
+    `/aps/tapsproductionschedulingconfig/getById/` + id
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 64 - 0
src/api/productionScheduling/productionShift.js

@@ -0,0 +1,64 @@
+import request from '@/utils/request';
+import { download } from '@/utils/file';
+/**
+ * 获取信息列表
+ */
+export async function getTableList(params) {
+  const res = await request.post(`/aps/tapsproductionshift/selectProductionShiftPage`, params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 新增信息
+ */
+export async function saveOrUpdate(data) {
+  const res = await request.post(`/aps/tapsproductionshift/saveOrUpdate`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+/**
+ * 删除
+ */
+export async function deleteList(data) {
+  const res = await request.delete('/aps/tapsproductionshift/delete', { data });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询明细
+ */
+export async function getById(id) {
+  const res = await request.get(`/aps/tapsproductionshift/getById/` + id);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+/**
+ * 获取工艺总时间
+ */
+export async function selectProcessTotalDuration(params) {
+  const res = await request.post(`/aps/tapsproductionschedulingconfig/selectProcessTotalDuration`, params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+/**
+ * 下拉框根据班次编码查询班次信息
+ */
+export async function selectProductionShiftList(data) {
+  const res = await request.post(`/aps/tapsproductionshift/productionShiftList`, data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 104 - 0
src/api/productionScheduling/stationBeat.js

@@ -0,0 +1,104 @@
+import request from '@/utils/request';
+import { download } from '@/utils/file';
+/**
+ * 获取信息列表
+ */
+export async function getTableList(params) {
+  const res = await request.post(
+    `/aps/tapsproductionschedulingconfig/selectProductionSchedulingStationConfigPage`,
+    params
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 新增信息
+ */
+export async function saveOrUpdate(data) {
+  const res = await request.post(
+    `/aps/tapsproductionschedulingconfig/saveOrUpdateStationCycleTime`,
+    data
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+/**
+ * 删除
+ */
+export async function deleteList(data) {
+  const res = await request.delete(
+    '/aps/tapsproductionschedulingconfig/deleteByIds',
+    { data }
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询明细
+ */
+export async function getById(id) {
+  const res = await request.get(
+    `/aps/tapsproductionschedulingconfig/getById/` + id
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 根据产品查询bom类型
+export async function listBomType(params) {
+  const res = await request.get(`/main/bomCategory/listBomType`, { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// aps生产计划获取产品的多个bom版本
+export async function bomListByPlan(params) {
+  const res = await request.get(`/main/bomCategory/bomListByPlan`, { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// 根据bom 获取工艺路线
+
+export async function bomRoutingList(id) {
+  const res = await request.get(`/main/bomCategory/bomRoutingList/${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// 获取工序
+export async function listByRoutingIds(data) {
+  const res = await request.post(
+    `/main/producerouting/taskinstance/listByRoutingIds`,
+    data
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// 根据工序查工位
+
+export async function selectFactoryWorkstation(params) {
+  const res = await request.get(
+    `/aps/tapsproductionschedulingconfig/selectFactoryWorkstation`,
+    { params }
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 101 - 0
src/api/productionScheduling/workCalendar.js

@@ -0,0 +1,101 @@
+import request from '@/utils/request';
+import { download } from '@/utils/file';
+/**
+ * 获取信息列表
+ */
+export async function getTableList(params) {
+  const res = await request.post(`/aps/tapsworkdaycalendarmaster/page`, params);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+/**
+ * 新增信息
+ */
+export async function saveOrUpdate(data) {
+  const res = await request.post(
+    `/aps/tapsworkdaycalendarmaster/saveOrUpdateCalendarMaster`,
+    data
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+/**
+ * 删除
+ */
+export async function deleteList(data) {
+  const res = await request.delete(
+    '/aps/tapsproductionschedulingconfig/deleteByIds',
+    { data }
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+/**
+ * 查询明细
+ */
+export async function getTApsWorkdayCalendarMaster(data) {
+  const res = await request.post(
+    `/aps/tapsworkdaycalendarmaster/getTApsWorkdayCalendarMaster`,
+    data
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 根据产品查询bom类型
+export async function listBomType(params) {
+  const res = await request.get(`/main/bomCategory/listBomType`, { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// aps生产计划获取产品的多个bom版本
+export async function bomListByPlan(params) {
+  const res = await request.get(`/main/bomCategory/bomListByPlan`, { params });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// 根据bom 获取工艺路线
+
+export async function bomRoutingList(id) {
+  const res = await request.get(`/main/bomCategory/bomRoutingList/${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// 获取工序
+export async function listByRoutingIds(data) {
+  const res = await request.post(
+    `/main/producerouting/taskinstance/listByRoutingIds`,
+    data
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// 根据工序查工位
+
+export async function selectFactoryWorkstation(params) {
+  const res = await request.get(
+    `/aps/tapsproductionschedulingconfig/selectFactoryWorkstation`,
+    { params }
+  );
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 376 - 0
src/components/productList/product-list.vue

@@ -0,0 +1,376 @@
+<template>
+  <ele-modal
+    title="选择物品"
+    :visible.sync="visible"
+    v-if="visible"
+    :before-close="handleClose"
+    append-to-body
+    width="70%"
+    :maxable="true"
+  >
+    <el-card shadow="never">
+      <searchProduct @search="reload"></searchProduct>
+
+      <ele-split-layout
+        width="244px"
+        allow-collapse
+        :right-style="{ overflow: 'hidden' }"
+      >
+        <div class="ele-border-lighter split-layout-right-content">
+          <productTree
+            @handleNodeClick="handleNodeClick"
+            :isFirstRefreshTable="isFirstRefreshTable"
+            ref="treeList"
+            :ids="ids"
+            :typeIds="typeIds"
+            :itemType="itemType"
+          />
+        </div>
+        <!-- 表格 -->
+        <template v-slot:content>
+          <ele-pro-table
+            ref="table"
+            :columns="columns"
+            :datasource="datasource"
+            row-key="id"
+            height="calc(100vh - 480px)"
+            class="dict-table"
+            @cell-click="cellClick"
+            :selection.sync="selection"
+        
+            :initLoad="!isFirstRefreshTable"
+            @columns-change="handleColumnChange"
+            :rowClickChecked="true"
+            :cache-key="cacheKey"
+          >
+            <!-- 表头工具栏 -->
+            <template v-slot:action="{ row }">
+              <!-- <el-radio class="radio" v-model="radio" :label="row.code"></el-radio> -->
+              <el-radio class="radio" v-model="radio" :label="row.id">
+                <i></i>
+              </el-radio>
+            </template>
+          </ele-pro-table>
+        </template>
+      </ele-split-layout>
+    </el-card>
+
+    <div slot="footer">
+      <el-button type="primary" size="small" @click="selected">选择</el-button>
+      <el-button size="small" @click="handleClose">关闭</el-button>
+    </div>
+  </ele-modal>
+</template>
+
+<script>
+import { getMaterialList } from '@/api/material/list.js';
+
+  import searchProduct from './searchProduct.vue';
+  import productTree from './tree.vue';
+  import tabMixins from '@/mixins/tableColumnsMixin';
+
+  export default {
+    mixins: [tabMixins],
+
+    components: {
+      productTree,
+      searchProduct
+    },
+    props: {
+      /*是否需要获取仓库产品数量*/
+      isGetInventoryTotal: {
+        type: Boolean,
+        default: false
+      },
+      /*已选择的产品*/
+      data: {
+        type: Array,
+        default: () => []
+      },
+      /*是否需要cBom*/
+      isShowCBom: {
+        type: Boolean,
+        default: false
+      },
+      /*是否需要供应商*/
+      isSupplier: {
+        type: Boolean,
+        default: false
+      },
+      //获取上一次销售记录  1销售合同 2采购合同
+      isSalesRecord: {
+        default: ''
+      },
+      typeIds: {
+        type: Array,
+        default: () => []
+      },
+      isFirstRefreshTable: {
+        type: Boolean,
+        default: false
+      },
+      ids: {
+        type: Array,
+        default: () => []
+      },
+      itemType: {
+        type: String,
+        default: '1'
+      }
+    },
+
+    data() {
+      return {
+        visible: false,
+        currentIndex: null,
+        radio: null,
+        cBomListDialogFlag: null,
+        dictList: {},
+        selection: [],
+        isColorId: [],
+        dimension: '1',
+        searchForm: {
+          categoryCode: '',
+          categoryName: '',
+          batchNo: '',
+          brandNum: '',
+          engrave: '',
+          barcodes: '',
+          categoryLevelId: '',
+          warehouseId: '',
+          pageNum: 1,
+          size: 10
+        },
+        datasourceList: [],
+        columnsVersion: 1,
+        cacheKey:'product-list-cache-key'
+      };
+    },
+    watch: {},
+    computed: {
+      columns() {
+        let columnsVersion = this.columnsVersion;
+
+        let data = null;
+        if (this.currentIndex != -1) {
+          data = {
+            action: 'action',
+            slot: 'action',
+            align: 'center',
+            label: '选择',
+            reserveSelection: true
+          };
+        }
+        if (this.currentIndex == -1) {
+          data = {
+            label: '选择',
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            reserveSelection: true
+          };
+        }
+
+        return [
+          data,
+          {
+            columnKey: 'index',
+            type: 'index',
+            width: 50,
+            align: 'center',
+            showOverflowTooltip: true,
+            label: '序号'
+          },
+          {
+            prop: 'code',
+            label: '编码',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'name',
+            label: '名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'imgCode',
+            align: 'center',
+            label: '图号/件号',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+
+          {
+            prop: 'level',
+            align: 'center',
+            label: '级别',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'extField.packingSpecification',
+            align: 'center',
+            label: '包装规格',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'brandNum',
+            align: 'center',
+            label: '牌号',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'modelType',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'measuringUnit',
+            label: '计量单位',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 90
+          },
+
+          {
+            prop: 'weightUnit',
+            label: '重量单位',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 90
+          },
+
+          {
+            prop: 'roughWeight',
+            label: '毛重',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 90
+          },
+
+          {
+            prop: 'netWeight',
+            label: '净重',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 90
+          },
+
+          {
+            prop: 'packingUnit',
+            align: 'center',
+            label: '包装单位',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'categoryLevelPath',
+            label: '分类',
+            align: 'center',
+            showOverflowTooltip: true
+          }
+        ];
+      }
+    },
+    methods: {
+      open(item, currentIndex) {
+        this.currentIndex = currentIndex;
+        this.visible = true;
+      },
+      /* 表格数据源 */
+      datasource({ page, limit, where, order }) {
+        return getMaterialList({
+          pageNum: page,
+          size: limit,
+          ...where
+        });
+      },
+
+      /* 刷新表格 */
+      reload(where) {
+        where.categoryLevelId = this.curNodeData?.id;
+        this.searchForm.categoryLevelId = this.curNodeData?.id;
+        this.$refs.table.reload({ pageNum: 1, where: where });
+      },
+
+      handleNodeClick(data, node) {
+        this.curNodeData = data;
+        this.$nextTick(() => {
+          this.reload({ categoryLevelId: data.id });
+        });
+      },
+
+      // 单击获取id
+      cellClick(row) {
+        if (this.currentIndex == -1) return;
+        this.current = row;
+        this.radio = row.id;
+      },
+
+      handleClose() {
+        this.visible = false;
+        this.current = null;
+        this.selection = [];
+        this.$refs.table.clearSelection();
+        this.radio = '';
+      },
+
+      async selected() {
+        if (!this.current && !this.selection.length) {
+          return this.$message.warning('请至少选择一条数据');
+        }
+
+        let list = this.currentIndex == -1 ? this.selection : [this.current];
+
+        this.$emit('changeParent', list, this.currentIndex);
+        this.handleClose();
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.success-row) {
+    background: #f0f9eb;
+  }
+  .tree_col {
+    border: 1px solid #eee;
+    padding: 10px 0;
+    box-sizing: border-box;
+    height: 500px;
+    overflow: auto;
+  }
+
+  .table_col {
+    padding-left: 10px;
+
+    ::v-deep .el-table th.el-table__cell {
+      background: #f2f2f2;
+    }
+  }
+
+  .pagination {
+    text-align: right;
+    padding: 10px 0;
+  }
+
+  .btns {
+    text-align: center;
+    padding: 10px 0;
+  }
+
+  .topsearch {
+    margin-bottom: 15px;
+  }
+</style>

+ 139 - 0
src/components/productList/searchProduct.vue

@@ -0,0 +1,139 @@
+<!-- 搜索表单 -->
+<template>
+  <el-form
+    size="small"
+    label-width="60px"
+    class="ele-form-search"
+    @keyup.enter.native="search"
+    @submit.native.prevent
+  >
+    <el-row :gutter="10">
+      <el-col v-bind="styleResponsive ? { md: 6 } : { span: 6 }">
+        <el-form-item label="编码">
+          <el-input
+            clearable
+            size="small"
+            v-model="where.code"
+            placeholder="请输入"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col v-bind="styleResponsive ? { md: 6 } : { span: 6 }">
+        <el-form-item label="名称">
+          <el-input
+            clearable
+            size="small"
+            v-model="where.name"
+            placeholder="请输入"
+          />
+        </el-form-item>
+      </el-col>
+
+      <el-col v-bind="styleResponsive ? { md: 6 } : { span: 6 }">
+        <el-form-item label="型号">
+          <el-input
+            clearable
+            size="small"
+            v-model="where.modelType"
+            placeholder="请输入"
+          />
+        </el-form-item>
+      </el-col>
+
+      <el-col v-bind="styleResponsive ? { md: 6 } : { span: 4 }">
+        <el-form-item>
+          <el-button
+            size="small"
+            type="primary"
+            icon="el-icon-search"
+            class="ele-btn-icon"
+            @click="search"
+          >
+            查询
+          </el-button>
+
+          <el-button
+            @click="reset"
+            icon="el-icon-refresh"
+            class="ele-btn-icon"
+            size="medium"
+            >重置</el-button
+          >
+          <slot></slot>
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <el-row :gutter="10">
+      <el-col v-bind="styleResponsive ? { md: 6 } : { span: 6 }">
+        <el-form-item label="牌号">
+          <el-input
+            clearable
+            size="small"
+            v-model="where.brandNum"
+            placeholder="请输入"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col v-bind="styleResponsive ? { md: 6 } : { span: 6 }">
+        <el-form-item label="规格">
+          <el-input
+            clearable
+            size="small"
+            v-model="where.specification"
+            placeholder="请输入"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col v-bind="styleResponsive ? { md: 6 } : { span: 6 }">
+        <el-form-item label="关键字">
+          <el-input
+            clearable
+            size="small"
+            v-model="where.searchKey"
+            placeholder="请输入"
+          />
+        </el-form-item>
+      </el-col>
+      
+    </el-row>
+  </el-form>
+</template>
+
+<script>
+  export default {
+    data() {
+      // 默认表单数据
+      const defaultWhere = {
+        name: '',
+        code: '',
+        brandNum: '',
+        modelType: '',
+        specification: '',
+        searchKey:''
+      };
+      return {
+        defaultWhere,
+        // 表单数据
+        where: { ...defaultWhere },
+        loading: false
+      };
+    },
+    computed: {
+      // 是否开启响应式布局
+      styleResponsive() {
+        return this.$store.state.theme.styleResponsive;
+      }
+    },
+    methods: {
+      /* 搜索 */
+      search() {
+        this.$emit('search', this.where);
+      },
+      /*  重置 */
+      reset() {
+        this.where = { ...this.defaultWhere };
+        this.search();
+      }
+    }
+  };
+</script>

+ 183 - 0
src/components/productList/tree.vue

@@ -0,0 +1,183 @@
+<template>
+  <div class="tree-wrapper">
+    <el-tree
+      :data="treeList"
+      :props="defaultProps"
+      v-loading="treeLoading"
+      :node-key="nodeKey"
+      ref="tree"
+      :highlight-current="true"
+      :expand-on-click-node="false"
+      @node-click="handleNodeClick"
+      :default-expanded-keys="defaultExpandedKeys"
+      v-bind="$attrs"
+      :default-expand-all="defaultExpandAll"
+    >
+    </el-tree>
+  </div>
+</template>
+
+<script>
+  import { getProduceTreeByPid } from '@/api/material/list.js';
+
+  // let originId = '';
+  // let originType = '';
+  export default {
+    props: {
+      // treeList私有化处理
+      treeFormate: {
+        type: Function,
+        default: null
+      },
+      defaultProps: {
+        type: Object,
+        default: function () {
+          return {
+            children: 'children',
+            value: 'id',
+            label: 'name'
+          };
+        }
+      },
+      defaultExpandAll: {
+        type: Boolean,
+        default: function () {
+          return false;
+        }
+      },
+      // 初始请求treeList
+      init: {
+        type: Boolean,
+        default: true
+      },
+
+      isFirstRefreshTable: {
+        type: Boolean,
+        default: true
+      },
+      id: {
+        type: String,
+        default: '0'
+      },
+      nodeKey: {
+        type: String,
+        default: 'id'
+      },
+      typeIds: {
+        type: Array,
+        default: () => []
+      },
+      ids: {
+        type: Array,
+        default: () => []
+      },
+      itemType: {
+        type: String,
+        default: '1'
+      }
+      // appendRoot: {
+      //   type: Boolean,
+      //   default: false
+      // },
+    },
+    data() {
+      return {
+        defaultExpandedKeys: [],
+        treeList: [],
+        treeLoading: false,
+        parentName: '',
+        parentId: '',
+        currentKey: ''
+      };
+    },
+    mounted() {
+      if (this.init) {
+        this.getTreeData();
+      }
+    },
+    methods: {
+      getInstance() {
+        return this.$refs.tree;
+      },
+      // 获取树结构数据
+      async getTreeData(params = {}) {
+        try {
+          this.treeLoading = true;
+          let data = await getProduceTreeByPid(this.itemType);
+
+          console.log(data, 'data');
+
+          this.treeLoading = false;
+
+          this.treeList = this.$util.toTreeData({
+            data: data.data || [],
+            idField: 'id',
+            parentIdField: 'parentId'
+          });
+          this.defaultExpandedKeys = [this.treeList[0].id || ''];
+          this.$nextTick(() => {
+            // 默认高亮第一级树节点
+            if (this.treeList[0] && this.isFirstRefreshTable) {
+              this.setCurrentKey(this.treeList[0].id);
+              this.handleNodeClick(
+                this.treeList[0],
+                this.$refs.tree.getCurrentNode()
+              );
+            }
+          });
+          return this.treeList;
+        } catch (error) {
+          console.error(error);
+        }
+        this.treeLoading = false;
+      },
+      // 递归 - 往树children里面添加parentName
+      // _setParentName (tree) {
+      //   let data = tree;
+      //   for (let i = 0; i < data.length; i++) {
+      //     if (data[i].parentId === '0') {
+      //       this.parentName = data[i].name;
+      //       originId = data[i].id;
+      //       originType = data[i].type;
+      //     }
+
+      //     data[i]['originId'] = originId;
+      //     data[i]['originType'] = originType;
+      //     data[i]['parentId'] = data[i]['parentId'];
+      //     if (data[i].children && data[i].children.length > 0) {
+      //       this._setParentName(data[i].children);
+      //     }
+      //   }
+      //   return data;
+      // },
+
+      handleNodeClick(data, node) {
+        this.$emit('handleNodeClick', data, node);
+      },
+      // 设置默认高亮行
+      setCurrentKey(id) {
+        this.currentKey = id;
+        this.$refs.tree.setCurrentKey(this.currentKey);
+      },
+
+      // 获取树的选中状态
+      getSelectList() {
+        const selectList = this.$refs.tree.getCurrentNode();
+        return selectList;
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .tree-wrapper {
+    width: 100%;
+    height: 100%;
+    overflow: auto;
+
+    :deep(.el-tree) {
+      display: inline-block;
+      min-width: 100%;
+    }
+  }
+</style>

+ 15 - 3
src/views/factoryModel/productionLine/components/edit.vue

@@ -229,6 +229,17 @@
               />
             </el-form-item>
           </el-col>
+          <el-col :span="24" style="margin-bottom: 12px">
+            <el-form-item
+              label="排产维度:"
+              prop="productionSchedulingDimension"
+            >
+              <el-radio-group v-model="form.productionSchedulingDimension">
+                <el-radio :label="0">产线</el-radio>
+                <el-radio :label="1">工位</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
         </el-row>
       </el-card>
       <el-card
@@ -373,7 +384,7 @@
         areaName: '',
         factoryId: '',
         workshopPlanId: '',
-
+        productionSchedulingDimension: 0,
         extInfo: {
           // principal: '', // 负责人
           principalDep: '', // 负责人部门
@@ -409,6 +420,9 @@
           leaderId: [{ required: true, message: '请选择', trigger: 'change' }],
           'extInfo.phone': [
             { min: 11, message: '手机号格式不正确', trigger: 'blur' }
+          ],
+          productionSchedulingDimension: [
+            { required: true, message: '请选择', trigger: 'change' }
           ]
         },
         visible: false,
@@ -602,7 +616,6 @@
           size: 999,
           type: 1,
           enable: 1
-
         });
         this.factoryList = list || [];
       },
@@ -623,7 +636,6 @@
           parentId: this.form.factoryId,
           size: 9999,
           enable: 1
-
         };
         getFactoryarea(par).then((res) => {
           this.workshopPlanList = res.list;

+ 7 - 1
src/views/factoryModel/productionLine/index.vue

@@ -130,7 +130,13 @@
             label: '所属车间',
             prop: 'workshopName'
           },
-
+          {
+            label: '排产维度',
+            prop: 'productionSchedulingDimension',
+            formatter: (_row, _column, cellValue) => {
+              return cellValue==1?'工位':'产线'
+            }
+          },
           {
             label: '工作中心',
             prop: 'workCenterList',

+ 148 - 0
src/views/factoryModel/station/components/ProduceDialogAll.vue

@@ -0,0 +1,148 @@
+<template>
+  <ele-modal
+    width="70vw"
+    :visible.sync="visible"
+    :close-on-click-modal="false"
+    append-to-body
+    custom-class="ele-dialog-form"
+    title="选择工序"
+    :maxable="true"
+  >
+    <el-form label-width="120px">
+      <el-row>
+        <el-col :span="6">
+          <el-form-item label="工序编码" prop="code">
+            <el-input v-model="searchData.code"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="工序名称" prop="name">
+            <el-input v-model="searchData.name"></el-input>
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="6">
+          <div class="ele-form-actions">
+            <el-button type="primary" @click="search">搜索</el-button>
+            <el-button @click="reset">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <ele-pro-table
+      :columns="columns"
+      :datasource="datasource"
+      ref="table"
+      height="45vh"
+      :initLoad="false"
+      row-key="id"
+      :selection.sync="selection"
+    >
+    </ele-pro-table>
+
+    <div slot="footer">
+      <el-button type="primary" @click="confirm">确定</el-button>
+      <el-button @click="cancel">返回</el-button>
+    </div>
+  </ele-modal>
+</template>
+
+<script>
+  import producetask from '@/api/technology/production';
+  export default {
+    props: {
+      disabledListId: {
+        type: Array,
+        default: () => []
+      }
+    },
+    data() {
+      return {
+        visible: false,
+        selection: [],
+        searchData: {
+          code: '',
+          name: ''
+        },
+        columns: [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left',
+            selectable: (row, index) => {
+              return !this.disabledListId.includes(row.id);
+            }
+          },
+          {
+            prop: 'code',
+            label: '工序编码',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 110
+          },
+          {
+            prop: 'name',
+            label: '工序名称',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 110
+          },
+          {
+            align: 'center',
+            prop: 'controlName',
+            label: '工序控制码',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'workCenterName',
+            label: '所属工作中心',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          }
+        ]
+      };
+    },
+    methods: {
+      open() {
+        this.visible = true;
+        this.$nextTick(() => {
+          this.$refs.table.reload();
+        });
+      },
+      /* 表格数据源 */
+      async datasource({ page, limit, where, order }) {
+        const res = await producetask.list({
+          ...where,
+          ...this.searchData,
+          pageNum: page,
+          size: limit
+        });
+        return res;
+      },
+      cancel() {
+        this.searchData = {};
+        this.visible = false;
+      },
+      confirm() {
+        if (!this.selection.length) {
+          return this.$message.warning('请选择工序');
+        }
+        this.$emit('changeProduct', this.selection);
+        this.cancel();
+      },
+
+      search() {
+        this.$refs.table.reload();
+      },
+      reset() {
+        this.searchData = {};
+        this.$refs.table.reload();
+      }
+    }
+  };
+</script>

+ 35 - 12
src/views/factoryModel/station/components/edit.vue

@@ -7,7 +7,7 @@
     :before-close="handleClose"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
-    width="1200px"
+    width="70%"
     :maxable="true"
   >
     <el-form ref="form" :model="form" :rules="rules" label-width="110px">
@@ -245,7 +245,7 @@
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <!-- <el-col :span="8">
             <el-form-item label="可执行工序:" prop="taskNames">
               <el-input
                 :value="form.taskNames"
@@ -273,7 +273,7 @@
                 </el-option>
               </el-select>
             </el-form-item>
-          </el-col>
+          </el-col> -->
 
           <el-col :span="8">
             <el-form-item label="状态:" prop="enabled">
@@ -292,7 +292,7 @@
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="14">
+          <!-- <el-col :span="14">
             <el-form-item label="节拍时间:" prop="extInfo.meterTime">
               <div class="workMeter-warp">
                 <el-input
@@ -325,7 +325,7 @@
                 </el-select>
               </div>
             </el-form-item>
-          </el-col>
+          </el-col> -->
 
           <el-col :span="24">
             <el-form-item label="备注:" prop="remark">
@@ -340,13 +340,20 @@
           </el-col>
         </el-row>
       </el-card>
-      <!--      <el-card
+      <el-card
         shadow="never"
-        header="关联设备"
-        body-style="padding: 22px 22px 0 22px;"
+        header="节拍信息"
+        body-style="padding: 10px 10px 0 20px;"
       >
-        <equipmentTable ref="equipmentTable"></equipmentTable>
-      </el-card> -->
+        <el-row>
+          <el-col :span="24">
+            <stationMeter
+              ref="stationMeter"
+              :factoryWorkstationId="form.id"
+            ></stationMeter>
+          </el-col>
+        </el-row>
+      </el-card>
     </el-form>
     <template v-slot:footer>
       <el-button @click="handleClose">取消</el-button>
@@ -385,6 +392,7 @@
   import work from '@/api/technology/work';
   import ProduceDialog from './ProduceDialog';
   import CodeDialog from './codeDialog.vue';
+  import stationMeter from './stationMeter.vue';
   import AreaSelect from '@/views/enterpriseModel/regionalManage/components/area-cascader.vue';
   import { basicAreaPageAPI } from '@/api/regionalManage';
   import { listOrganizations } from '@/api/system/organization';
@@ -398,7 +406,8 @@
       AreaSelect,
       ProductModal,
       ProduceDialog,
-      CodeDialog
+      CodeDialog,
+      stationMeter
     },
 
     props: {
@@ -436,7 +445,7 @@
           remark: '',
           taskIds: [],
           taskNames: [],
-
+          activeName: '',
           workstationSubstanceList: []
         };
       };
@@ -719,6 +728,12 @@
               substanceId: this.form.extInfo.assetId
             }
           ];
+          console.log(
+            this.$refs.stationMeter.getValue(),
+            'this.$refs.stationMeter.getValue()'
+          );
+          this.form.tMainFactoryWorkstationOperationAddPOList =
+            this.$refs.stationMeter.getValue();
           saveOrUpdate_gw(this.form)
             .then((msg) => {
               this.loading = false;
@@ -740,6 +755,8 @@
       },
       handleClose() {
         this.restForm();
+        this.$refs.stationMeter.setValue([]);
+
         this.loading = false;
         this.visible = false;
       },
@@ -864,6 +881,12 @@
             //   this.$refs.equipmentTable.datasource = list;
             // }
           }
+          this.$nextTick(() => {
+            console.log(res.tMainFactoryWorkstationOperationAddPOList);
+            this.$refs.stationMeter.setValue(
+              res.tMainFactoryWorkstationOperationAddPOList
+            );
+          });
           this.form = Object.assign({}, this.form, res);
           if (
             this.form?.extInfo?.teamId &&

+ 276 - 0
src/views/factoryModel/station/components/stationMeter.vue

@@ -0,0 +1,276 @@
+<template>
+  <div>
+    <el-button type="primary" @click="handleProduce"> 添加工序 </el-button>
+    <el-tabs
+      v-model="activeName"
+      type="border-card"
+      style="width: 100%; margin-top: 10px; min-height: 300px"
+      v-if="datasource.length"
+      closable
+      @tab-remove="removeTab"
+    >
+      <el-tab-pane
+        v-for="(item, index) in datasource"
+        :label="item.operationName"
+        :name="item.operationId"
+        :key="item.operationId"
+      >
+        <ele-pro-table
+          :columns="columns"
+          :datasource="item.tMainFactoryWorkstationProductAddPOS"
+          ref="table"
+          height="300px"
+          full-height="calc(100vh - 116px)"
+          :needPage="false"
+          :selection.sync="selection"
+        >
+          <template v-slot:toolbar>
+            <el-button
+              size="small"
+              type="primary"
+              icon="el-icon-plus"
+              class="ele-btn-icon"
+              @click="handParent(item, index)"
+            >
+              选择物品
+            </el-button>
+            <el-popconfirm
+              class="ele-action"
+              title="确定要删除选中的信息吗?"
+              @confirm="removeAll(index)"
+            >
+              <template v-slot:reference>
+                <el-button
+                  type="danger"
+                  :underline="false"
+                  icon="el-icon-delete"
+                  :disabled="selection?.length === 0"
+                >
+                  删除
+                </el-button>
+              </template>
+            </el-popconfirm>
+          </template>
+
+          <template v-slot:stationCycleTime="{ row }">
+            <el-input
+              clearable
+              v-model="row.stationCycleTime"
+              type="number"
+              placeholder="请输入"
+            />
+          </template>
+          <template v-slot:action="{ row }">
+            <el-popconfirm
+              class="ele-action"
+              title="确定要删除此条信息吗?"
+              @confirm="remove(row.productId, index)"
+            >
+              <template v-slot:reference>
+                <el-link type="danger" :underline="false" icon="el-icon-delete">
+                  删除
+                </el-link>
+              </template>
+            </el-popconfirm>
+          </template>
+        </ele-pro-table>
+      </el-tab-pane>
+    </el-tabs>
+    <ProduceDialog
+      ref="produceRef"
+      @changeProduct="produceConfirm"
+      :disabledListId="this.datasource.map((item) => item.operationId)"
+    />
+    <standardOutput
+      ref="standardOutputRefs"
+      @selection="chooseStandardList"
+    ></standardOutput>
+  </div>
+</template>
+<script>
+  import { copyObj } from '@/utils/util';
+  import ProduceDialog from './ProduceDialogAll';
+  import standardOutput from '@/views/material/BOMmanage/components/standardOutput.vue';
+  import { remove } from 'nprogress';
+  export default {
+    components: {
+      ProduceDialog,
+      standardOutput
+    },
+    props: {
+      factoryWorkstationId: {
+        type: String,
+        default: ''
+      }
+    },
+    data() {
+      return {
+        datasource: [],
+        selection: [],
+        currentIndex: 0,
+        activeName: '',
+        columns: [
+          {
+            width: 60,
+            type: 'index',
+            columnKey: 'index',
+            label: '序号',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center'
+          },
+          {
+            prop: 'productCode',
+            label: '产品编码',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'productName',
+            label: '产品名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'stationCycleTime',
+            label: '工位节拍(分)',
+            align: 'center',
+            slot: 'stationCycleTime',
+            showOverflowTooltip: true,
+            minWidth: 140
+          },
+          {
+            width: 120,
+            prop: 'specification',
+            label: '规格',
+            align: 'center'
+          },
+          {
+            width: 160,
+            prop: 'brandNum',
+            label: '牌号',
+            align: 'center'
+          },
+          {
+            width: 120,
+            prop: 'modelType',
+            label: '型号',
+            align: 'center'
+          },
+          {
+            prop: 'remark',
+            slot: 'remark',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 200
+          },
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 100,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true,
+            fixed: 'right'
+          }
+        ]
+      };
+    },
+    methods: {
+      // 选择可执行工序
+      handleProduce() {
+        this.$refs.produceRef.open();
+      },
+      handParent(data, index) {
+        this.currentIndex = index;
+        let arr = copyObj(data.tMainFactoryWorkstationProductAddPOS);
+        this.$refs.standardOutputRefs.open(
+          arr.map((item) => {
+            item['id'] = item.productId;
+            return item;
+          })
+        );
+      },
+      chooseStandardList(data) {
+        this.datasource[
+          this.currentIndex
+        ].tMainFactoryWorkstationProductAddPOS.push(
+          ...data.map((item) => {
+            item['productId'] = item.id;
+            item['productName'] = item.name;
+            item['productCode'] = item.code;
+            delete item.id;
+            return item;
+          })
+        );
+      },
+      getValue() {
+        return this.datasource;
+      },
+      setValue(data) {
+        if (data.length) {
+          this.activeName = data[0].operationId;
+          this.datasource = data;
+        } else {
+          this.datasource = [];
+          this.activeName = '';
+        }
+      },
+      remove(id, index) {
+        this.$set(
+          this.datasource[index],
+          'tMainFactoryWorkstationProductAddPOS',
+          this.datasource[index].tMainFactoryWorkstationProductAddPOS.filter(
+            (item) => item.productId != id
+          )
+        );
+      },
+      removeAll(index) {
+        this.selection.forEach((item) => {
+          this.remove(item.productId, index);
+        });
+        this.selection = [];
+      },
+      removeTab(operationId) {
+        this.datasource = this.datasource.filter(
+          (item) => item.operationId != operationId
+        );
+        this.activeName = this.datasource.length
+          ? this.datasource[0].operationId
+          : 0;
+      },
+      produceConfirm(data) {
+        this.activeName = this.activeName != 0 ? this.activeName : data[0].id;
+        console.log(this.activeName, data[0].id);
+        let arr = copyObj(data);
+        this.datasource.push(
+          ...arr.map((item) => {
+            item['factoryWorkstationId'] = this.factoryWorkstationId;
+            item['operationId'] = item.id;
+            item['operationName'] = item.name;
+            item['tMainFactoryWorkstationProductAddPOS'] = [];
+            delete item.id;
+            return item;
+          })
+        );
+      }
+    }
+  };
+</script>
+<style lang="scss" scoped>
+  :deep(.el-table .el-form-item) {
+    margin-bottom: 0px !important;
+    .el-form-item__content {
+      margin-left: 0px !important;
+    }
+  }
+</style>

+ 373 - 0
src/views/productionScheduling/productionLineBeat/components/addOrEditDialog.vue

@@ -0,0 +1,373 @@
+<template>
+  <ele-modal
+    custom-class="ele-dialog-form long-dialog-form"
+    :centered="true"
+    :visible.sync="addOrEditDialogFlag"
+    :title="title"
+    :append-to-body="false"
+    :close-on-click-modal="false"
+    width="60%"
+    :before-close="cancel"
+    :maxable="true"
+    :resizable="true"
+  >
+    <el-form
+      ref="form"
+      :model="form"
+      :rules="rules"
+      label-width="120px"
+      class="el-form-box"
+      :disabled="title == '详情'"
+    >
+      <headerTitle title="产品信息"> </headerTitle>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="编码" prop="code">
+            <el-input
+              clearable
+              readonly
+              v-model="form.code"
+              @click.native="$refs.produceRef.open()"
+              placeholder="请选择"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="名称" prop="name">
+            <el-input clearable disabled v-model="form.name" placeholder=" " />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工厂名称:" prop="factoriesId">
+            <el-select
+              style="width: 100%"
+              clearable
+              v-model="form.factoriesId"
+              placeholder="请选择所属工厂"
+            >
+              <el-option
+                v-for="item in factoryList"
+                :label="item.name"
+                :value="item.id"
+                :key="item.id"
+                @click.native="change_factoryId(item)"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <headerTitle title="产线节拍"> </headerTitle>
+      <ele-pro-table
+        :columns="columns"
+        :datasource="form.list"
+        ref="table"
+        height="400px"
+        full-height="calc(100vh - 116px)"
+        :needPage="false"
+        :selection.sync="selection"
+      >
+        <template v-slot:toolbar v-if="title != '详情'">
+          <el-button
+            size="small"
+            type="primary"
+            icon="el-icon-plus"
+            class="ele-btn-icon"
+            @click="handParent()"
+          >
+            新增
+          </el-button>
+          <el-popconfirm
+            class="ele-action"
+            title="确定要删除选中的信息吗?"
+            @confirm="remove()"
+          >
+            <template v-slot:reference>
+              <el-button
+                type="danger"
+                :underline="false"
+                icon="el-icon-delete"
+                :disabled="selection?.length === 0"
+              >
+                删除
+              </el-button>
+            </template>
+          </el-popconfirm>
+        </template>
+        <template v-slot:taktTime="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + $index + '.taktTime'"
+            :rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
+          >
+            <el-input
+              clearable
+              v-model.number="row.taktTime"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </template>
+        <template v-slot:priority="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + $index + '.priority'"
+            :rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
+          >
+            <el-input
+              clearable
+              v-model.number="row.priority"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </template>
+        <template v-slot:productionLineId="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + $index + '.productionLineId'"
+            :rules="[{ required: true, message: '请选择', trigger: 'blur' }]"
+          >
+            <el-select
+              style="width: 100%"
+              clearable
+              v-model="row.productionLineId"
+              placeholder="请选择"
+            >
+              <el-option
+                v-for="item in productionLineList"
+                :label="item.name"
+                :value="item.id"
+                :key="item.id"
+                @click.native="change_productionLineId(item, $index)"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </template>
+        <template v-slot:remark="{ row }">
+          <el-input clearable v-model="row.remark" placeholder="请输入" />
+        </template>
+        <template v-slot:columnRequired="{ column }">
+          <span class="is-required">{{ column.label }}</span>
+        </template>
+      </ele-pro-table>
+    </el-form>
+    <div slot="footer">
+      <el-button
+        type="primary"
+        @click="save()"
+        v-lodading="loading"
+        v-if="title != '详情'"
+        >保存</el-button
+      >
+
+      <el-button @click="cancel">返回</el-button>
+    </div>
+    <ProductModal
+      ref="produceRef"
+      @changeParent="produceConfirm"
+    ></ProductModal>
+  </ele-modal>
+</template>
+<script>
+  const defForm = {
+    code: '', //编码
+    name: '', //名称
+    productId: '', //物品id
+    factoriesId: '', //工厂id
+    factoriesName: '', //工厂名称
+    list: [] //表格数据
+  };
+  import { getFactoryarea } from '@/api/factoryModel';
+  import ProductModal from '@/components/productList/product-list.vue';
+  import {
+    saveOrUpdate,
+    getById
+  } from '@/api/productionScheduling/productionLineBeat';
+
+  export default {
+    components: { ProductModal },
+    computed: {},
+    data() {
+      return {
+        lodading: false,
+        title: '',
+        addOrEditDialogFlag: false,
+        form: { ...defForm },
+        rules: {
+          name: [
+            { required: true, message: '名称不能为空', trigger: 'change' }
+          ],
+          factoriesId: [
+            { required: true, message: '请选择工厂', trigger: 'change' }
+          ],
+          code: [{ required: true, message: '编码不能为空', trigger: 'change' }]
+        },
+        factoryList: [],
+        selection: [],
+        productionLineList: [],
+        columns: [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            fixed: 'left',
+            align: 'center'
+          },
+          {
+            width: 60,
+            type: 'index',
+            columnKey: 'index',
+            label: '序号',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            prop: 'productionLineName',
+            slot: 'productionLineId',
+            headerSlot: 'columnRequired',
+            label: '产线名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'taktTime',
+            slot: 'taktTime',
+            headerSlot: 'columnRequired',
+            label: '产线节拍(分钟)',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'priority',
+            slot: 'priority',
+            headerSlot: 'columnRequired',
+            label: '优先顺序',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 120
+          },
+          {
+            prop: 'remark',
+            slot: 'remark',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 200
+          }
+        ],
+        loading: false
+      };
+    },
+    mounted() {},
+    created() {
+      this.getFactoryList();
+    },
+
+    methods: {
+      //初始化
+      async open(row = {}, type) {
+        this.addOrEditDialogFlag = true;
+        this.title = type == 'add' ? '新增' : type == 'edit' ? '修改' : '详情';
+
+        if (type !== 'add') {
+          this.form = await getById(row.id);
+          this.form.list.forEach((item, index) => {
+            item.key = index;
+          });
+          if (this.form.factoriesId) {
+            this.change_factoryId({ id: this.form.factoriesId });
+          }
+        }
+      },
+      handParent() {
+        this.form.list.push({
+          productionLineName: '',
+          productionLineId: '',
+          taktTime: '',
+          remark: '',
+          key: this.form.list?.length || 0
+        });
+      },
+      async getFactoryList() {
+        const { list } = await getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 1,
+          enable: 1
+        });
+        this.factoryList = list || [];
+      },
+      async change_factoryId(item) {
+        const { list } = await getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 4,
+          factoryId: item.id,
+          enable: 1
+        });
+        this.productionLineList = list || [];
+        this.form.factoriesName = item.name;
+      },
+      change_productionLineId(item, index) {
+        this.$set(this.form.list[index], 'productionLineName', item.name);
+      },
+      produceConfirm(data) {
+        this.form.name = data[0].name;
+        this.form.code = data[0].code;
+        this.form.productId = data[0].id;
+        this.form.brandNo = data[0].brandNum;
+        this.form.modelType = data[0].modelType;
+        this.form.specification = data[0].specification;
+      },
+      remove() {
+        let keys = this.selection.map((item) => item.key);
+        this.form.list = this.form.list.filter(
+          (item) => !keys.includes(item.key)
+        );
+
+        this.selection = [];
+      },
+
+      save() {
+        this.$refs.form.validate((valid) => {
+          if (!this.form.list?.length) {
+            this.$message.error('节拍信息不能为空');
+            return;
+          }
+          if (valid) {
+            let priorityList = this.form.list.map((item) => item.priority);
+
+            if (new Set(priorityList).size !== priorityList.length) {
+              this.$message.error('优先顺序不能重复');
+              return;
+            }
+            this.loading = true;
+            saveOrUpdate(this.form)
+              .then((res) => {
+                this.$message.success('操作成功');
+                this.cancel();
+                this.$emit('done');
+              })
+              .finally(() => {
+                this.loading = false;
+              });
+          }
+        });
+      },
+
+      //关闭弹窗
+      cancel() {
+        this.addOrEditDialogFlag = false;
+        this.form = {
+          ...defForm
+        };
+        this.form.list = [];
+      }
+    }
+  };
+</script>
+<style scoped lang="scss">
+  // :deep(.el-form-item--medium) {
+  //   display: none;
+  // }
+</style>

+ 59 - 0
src/views/productionScheduling/productionLineBeat/components/searchTable.vue

@@ -0,0 +1,59 @@
+<!-- 搜索表单 -->
+<template>
+  <seekPage :seekList="seekList" :formLength="3" @search="search"></seekPage>
+</template>
+<script>
+  import { getFactoryarea } from '@/api/factoryModel';
+
+  export default {
+    data() {
+      return {
+        productionLineList: []
+      };
+    },
+    computed: {
+      // 表格列配置
+      seekList() {
+        return [
+          {
+            label: '编号:',
+            value: 'code',
+            type: 'input',
+            placeholder: ' '
+          },
+          {
+            label: '产线:',
+            value: 'productionLineName',
+            type: 'select',
+            placeholder: ' ',
+            planList: this.productionLineList
+          }
+        ];
+      }
+    },
+    async created() {
+      const { list } = await getFactoryarea({
+        pageNum: 1,
+        size: 999,
+        type: 4,
+        enable: 1
+      });
+      this.productionLineList =
+        list.map((item) => {
+          return {
+            label: item.name,
+            value: item.name
+          };
+        }) || [];
+      console.log(this.productionLineList, 'this.productionLineList');
+    },
+    methods: {
+      /* 搜索 */
+      search(e) {
+        this.$emit('search', {
+          ...e
+        });
+      }
+    }
+  };
+</script>

+ 331 - 0
src/views/productionScheduling/productionLineBeat/index.vue

@@ -0,0 +1,331 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never" v-loading="loading">
+      <search-table @search="reload"></search-table>
+      <!-- 数据表格 -->
+      <ele-pro-table
+        ref="table"
+        :columns="columns"
+        :datasource="datasource"
+        height="calc(100vh - 325px)"
+        full-height="calc(100vh - 116px)"
+        tool-class="ele-toolbar-form"
+        :selection.sync="selection"
+        :page-size="20"
+        @columns-change="handleColumnChange"
+        :cache-key="cacheKeyUrl"
+        :response="{
+          dataName: 'records',
+          countName: 'total'
+        }"
+      >
+        <!-- 表头工具栏 -->
+        <template v-slot:toolbar>
+          <el-button
+            size="small"
+            type="primary"
+            icon="el-icon-plus"
+            class="ele-btn-icon"
+            @click="handleAddOrEdit('', 'add')"
+          >
+            新建
+          </el-button>
+        </template>
+        <!-- 操作 -->
+        <template v-slot:action="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            @click="handleAddOrEdit(row, 'edit')"
+          >
+            修改
+          </el-link>
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            @click="submit(row)"
+            v-if="row.reviewStatus == 0 || row.reviewStatus == 3"
+          >
+            提交
+          </el-link>
+          <el-popconfirm
+            class="ele-action"
+            title="确定要删除此信息吗?"
+            @confirm="remove([row.productionSchedulingConfigDetailId])"
+          >
+            <template v-slot:reference>
+              <el-link type="danger" :underline="false" icon="el-icon-delete">
+                删除
+              </el-link>
+            </template>
+          </el-popconfirm>
+        </template>
+        <template v-slot:code="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            @click="handleAddOrEdit(row, 'view')"
+          >
+            {{ row.code }}
+          </el-link>
+        </template>
+      </ele-pro-table>
+    </el-card>
+
+    <add-or-edit-dialog
+      ref="addOrEditDialogRef"
+      @done="reload"
+    ></add-or-edit-dialog>
+
+    <process-submit-dialog
+      :processSubmitDialogFlag.sync="processSubmitDialogFlag"
+      v-if="processSubmitDialogFlag"
+      ref="processSubmitDialogRef"
+      @reload="reload"
+      :isNotNeedProcess="false"
+    ></process-submit-dialog>
+  </div>
+</template>
+
+<script>
+  import tabMixins from '@/mixins/tableColumnsMixin';
+  import addOrEditDialog from './components/addOrEditDialog.vue';
+  import searchTable from './components/searchTable.vue';
+  import {
+    getTableList,
+    deleteList
+  } from '@/api/productionScheduling/productionLineBeat';
+  import processSubmitDialog from '@/components/processSubmitDialog/processSubmitDialog.vue';
+
+  export default {
+    mixins: [tabMixins],
+    components: {
+      processSubmitDialog,
+      addOrEditDialog,
+      searchTable
+    },
+    data() {
+      return {
+        // 加载状态
+        loading: false,
+        delVisible: false,
+        selection: [],
+        processSubmitDialogFlag: false,
+        cacheKeyUrl: 'main-productionScheduling-productionLineBeat-index',
+        columns: [
+          // {
+          //   width: 45,
+          //   type: 'selection',
+          //   columnKey: 'selection',
+          //   align: 'center',
+          //   selectable: (row, index) => {
+          //     return row.reviewStatus != 1 && row.reviewStatus != 2;
+          //   }
+          // },
+          {
+            width: 60,
+            label: '序号',
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          },
+          {
+            minWidth: 180,
+            prop: 'code',
+            label: '编码',
+            align: 'center',
+            slot: 'code',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 180,
+            prop: 'name',
+            label: '名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 130,
+            prop: 'brandNo',
+            label: '牌号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'modelType',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          // {
+          //   minWidth: 200,
+          //   prop: 'produceRoutingName',
+          //   label: '工艺路线',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // },
+          {
+            minWidth: 150,
+            prop: 'factoriesName',
+            label: '工厂名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          // {
+          //   minWidth: 150,
+          //   prop: 'bbb',
+          //   label: '班组名称',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // },
+          {
+            minWidth: 150,
+            prop: 'productionLineName',
+            label: '产线名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'taktTime',
+            label: '产线节拍(分)',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'priority',
+            label: '优先顺序',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 200,
+            prop: 'remark',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 100,
+            prop: 'updateUserName',
+            label: '设置人',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'updateTime',
+            label: '设置时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 200,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true,
+            fixed: 'right'
+          }
+        ]
+      };
+    },
+    computed: {
+      columns() {
+        return [];
+      }
+    },
+    created() {},
+    methods: {
+      //新增、修改
+      handleAddOrEdit(row = {}, type) {
+        this.$nextTick(() => {
+          this.$refs.addOrEditDialogRef.open(row, type);
+        });
+      },
+
+      /* 表格数据源 */
+      datasource({ page, limit, where, order }) {
+        return getTableList({
+          pageNum: page,
+          size: limit,
+          ...where
+        });
+      },
+      submit(res) {
+        this.processSubmitDialogFlag = true;
+        this.$nextTick(() => {
+          let params = {
+            businessId: res.id,
+            businessKey: 'market_management_approve',
+            formCreateUserId: res.createUserId,
+            pcHandle: '/bpm/handleTask/components/marketManagem/submit.vue',
+            pcView: '/bpm/handleTask/components/marketManagem/view.vue',
+            variables: {
+              businessCode: res.code,
+              businessName: res.name,
+              businessType: res.type
+            }
+          };
+          this.$refs.processSubmitDialogRef.init(params);
+        });
+      },
+      /* 刷新表格 */
+      reload(where = {}) {
+        this.$refs.table.reload({ page: 1, where });
+      },
+
+      commitBtn() {
+        const dataId = this.selection.map((v) => v.id);
+        del(dataId).then((res) => {
+          this.$message.success('删除成功!');
+          this.reload();
+        });
+      },
+      remove(row) {
+        deleteList(row).then((res) => {
+          this.$message.success('删除成功!');
+          this.reload();
+        });
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-link--inner) {
+    margin-left: 0px !important;
+  }
+
+  .sys-organization-list {
+    height: calc(100vh - 264px);
+    box-sizing: border-box;
+    border-width: 1px;
+    border-style: solid;
+    overflow: auto;
+  }
+
+  .sys-organization-list :deep(.el-tree-node__content) {
+    height: 40px;
+
+    & > .el-tree-node__expand-icon {
+      margin-left: 10px;
+    }
+  }
+</style>

+ 271 - 0
src/views/productionScheduling/productionShift/components/addOrEditDialog.vue

@@ -0,0 +1,271 @@
+<template>
+  <ele-modal
+    custom-class="ele-dialog-form long-dialog-form"
+    :centered="true"
+    :visible.sync="addOrEditDialogFlag"
+    :title="title"
+    :append-to-body="false"
+    :close-on-click-modal="false"
+    width="60%"
+    :before-close="cancel"
+    :maxable="true"
+    :resizable="true"
+  >
+    <el-form
+      ref="form"
+      :model="form"
+      :rules="rules"
+      label-width="140px"
+      class="el-form-box"
+      :disabled="title == '详情'"
+    >
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="班次编码" prop="code">
+            <el-input
+              clearable
+              disabled
+              v-model="form.code"
+              placeholder="请选择"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="班次名称" prop="shiftName">
+            <el-input clearable v-model="form.shiftName" placeholder=" " />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="出勤开始时间" prop="attendanceStartTime">
+            <el-input
+              clearable
+              disabled
+              v-model="form.attendanceStartTime"
+              placeholder=" "
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="出勤结束时间" prop="attendanceEndTime">
+            <el-input
+              clearable
+              disabled
+              v-model="form.attendanceEndTime"
+              placeholder=" "
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工作总时长(分钟)" prop="totalWorkingMinutes">
+            <el-input
+              clearable
+              disabled
+              v-model="form.totalWorkingMinutes"
+              placeholder=" "
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="休息时间(分钟)" prop="restDurationMinutes">
+            <el-input
+              clearable
+              disabled
+              v-model="form.restDurationMinutes"
+              placeholder=" "
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div style="display: flex">
+      <span style="width: 90px">时间设置:</span>
+      <timeTable
+        ref="timeTable"
+        @setFormtime="setFormtime"
+        style="width: 100%"
+      ></timeTable>
+    </div>
+    <div slot="footer">
+      <el-button
+        type="primary"
+        @click="save()"
+        v-lodading="loading"
+        v-if="title != '详情'"
+        >保存</el-button
+      >
+
+      <el-button @click="cancel">返回</el-button>
+    </div>
+  </ele-modal>
+</template>
+<script>
+  const defForm = {
+    code: '', //名称
+    shiftName: '', //类型
+    attendanceStartTime: '', //	出勤开始时间
+    attendanceEndTime: '', //	出勤结束时间
+    totalWorkingMinutes: '', //	总工作时长
+    restDurationMinutes: '', //	休息时长
+    status: 0,
+    list: [] //表格数据
+  };
+  import { getCode } from '@/api/codeManagement';
+  import {
+    saveOrUpdate,
+    getById
+  } from '@/api/productionScheduling/productionShift';
+  import timeTable from './timeTable.vue';
+
+  export default {
+    components: { timeTable },
+    computed: {},
+    data() {
+      return {
+        defTime: {
+          step: '00:01',
+          start: '00:00',
+          end: '23:59'
+        },
+        columns: [
+          {
+            width: 60,
+            type: 'index',
+            columnKey: 'index',
+            label: '序号',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center'
+          },
+          {
+            prop: 'dutyName',
+            slot: 'dutyName',
+            headerSlot: 'columnRequired',
+            label: '出勤名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            slot: 'timeType',
+            prop: 'timeType',
+            headerSlot: 'columnRequired',
+            label: '时间类型',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'startTime',
+            label: '开始时间',
+            align: 'center',
+            headerSlot: 'columnRequired',
+            slot: 'startTime',
+            showOverflowTooltip: true,
+            minWidth: 170
+          },
+          {
+            prop: 'endTime',
+            minWidth: 170,
+            headerSlot: 'columnRequired',
+            slot: 'endTime',
+            label: '结束时间',
+            align: 'center'
+          },
+          {
+            width: 160,
+            prop: 'totalDuration',
+            label: '总时长(分钟)',
+            align: 'center'
+          },
+          {
+            prop: 'note',
+            slot: 'note',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 200
+          }
+        ],
+        selection: [],
+        lodading: false,
+        title: '',
+        addOrEditDialogFlag: false,
+        form: { ...defForm },
+        loading: false,
+        rules: {
+          shiftName: [
+            { required: true, message: '名称不能为空', trigger: 'change' }
+          ]
+        }
+      };
+    },
+    mounted() {},
+    created() {},
+
+    methods: {
+      //初始化
+      async open(row = {}, type) {
+        this.addOrEditDialogFlag = true;
+        console.log(type);
+        this.title = type == 'add' ? '新增' : type == 'edit' ? '修改' : '详情';
+
+        if (type !== 'add') {
+          this.form = await getById(row.id);
+          this.$nextTick(() => {
+            this.$refs.timeTable.init(this.form, type);
+          });
+        } else {
+          this.form.code = await getCode('workShift_code');
+        }
+      },
+      setFormtime({
+        attendanceStartTime,
+        attendanceEndTime,
+        totalWorkingMinutes,
+        restDurationMinutes
+      }) {
+        this.form.attendanceStartTime = attendanceStartTime;
+        this.form.attendanceEndTime = attendanceEndTime;
+        this.form.totalWorkingMinutes = totalWorkingMinutes;
+        this.form.restDurationMinutes = restDurationMinutes;
+      },
+      save() {
+        this.$refs.form.validate(async (valid) => {
+          this.form.list = await this.$refs.timeTable.getTableValue();
+          if (!this.form.list?.length) {
+            this.$message.error('时间设置不能为空');
+            return;
+          }
+          if (valid) {
+            this.loading = true;
+            saveOrUpdate(this.form)
+              .then((res) => {
+                this.$message.success('操作成功');
+                this.cancel();
+                this.$emit('done');
+              })
+              .finally(() => {
+                this.loading = false;
+              });
+          }
+        });
+      },
+
+      //关闭弹窗
+      cancel() {
+        this.addOrEditDialogFlag = false;
+        this.form = {
+          ...defForm
+        };
+        this.form.list = [];
+        this.$refs.timeTable.form.list = [];
+      }
+    }
+  };
+</script>
+<style scoped lang="scss"></style>

+ 44 - 0
src/views/productionScheduling/productionShift/components/searchTable.vue

@@ -0,0 +1,44 @@
+<!-- 搜索表单 -->
+<template>
+  <seekPage :seekList="seekList" :formLength="3" @search="search"></seekPage>
+</template>
+<script>
+  export default {
+    data() {
+      return {};
+    },
+    computed: {
+      // 表格列配置
+      seekList() {
+        return [
+          {
+            label: '班次编号:',
+            value: 'code',
+            type: 'input',
+            placeholder: ' '
+          },
+          {
+            label: '班次名称:',
+            value: 'shiftName',
+            type: 'input',
+            placeholder: ' '
+          },
+          {
+            label: '状态:',
+            value: 'productionLineId1',
+            type: 'select',
+            placeholder: ' '
+          }
+        ];
+      }
+    },
+    methods: {
+      /* 搜索 */
+      search(e) {
+        this.$emit('search', {
+          ...e
+        });
+      }
+    }
+  };
+</script>

+ 382 - 0
src/views/productionScheduling/productionShift/components/timeTable.vue

@@ -0,0 +1,382 @@
+<template>
+  <el-form
+    ref="form"
+    :model="form"
+    :rules="rules"
+    label-width="140px"
+    class="el-form-box"
+    :disabled="title == '详情'"
+  >
+    <ele-pro-table
+      :columns="columns"
+      :datasource="form.list"
+      ref="table"
+      height="300px"
+      full-height="calc(100vh - 116px)"
+      :needPage="false"
+    >
+      <template v-slot:toolbar v-if="title != '详情'">
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-plus"
+          class="ele-btn-icon"
+          @click="handAdd()"
+          style="margin-bottom: 5px"
+        >
+          新增
+        </el-button>
+        <!-- <el-popconfirm
+          class="ele-action"
+          title="确定要删除选中的信息吗?"
+          @confirm="remove()"
+        >
+          <template v-slot:reference>
+            <el-button
+              type="danger"
+              :underline="false"
+              icon="el-icon-delete"
+              :disabled="selection?.length === 0"
+            >
+              删除
+            </el-button>
+          </template>
+        </el-popconfirm> -->
+      </template>
+      <template v-slot:dutyName="{ row, $index }">
+        <el-form-item
+          label-width="0"
+          :prop="'list.' + $index + '.dutyName'"
+          :rules="[
+            {
+              required: true,
+              message: '出勤名称不能为空',
+              trigger: 'blur'
+            }
+          ]"
+        >
+          <DictSelection
+            dictName="班次出勤名称"
+            clearable
+            v-model="row.dutyName"
+            filterable
+            valueName="dictValue"
+            allow-create
+            default-first-option
+            :isOne="$index === 0"
+          >
+          </DictSelection>
+        </el-form-item>
+      </template>
+      <template v-slot:timeType="{ row, $index }">
+        <el-form-item
+          label-width="0"
+          :prop="'list.' + $index + '.timeType'"
+          :rules="[
+            {
+              required: true,
+              message: '请选择时间类型',
+              trigger: 'blur'
+            }
+          ]"
+        >
+          <el-select
+            v-model="row.timeType"
+            clearable
+            style="width: 100%"
+            @change="setFormtime"
+          >
+            <el-option :value="'1'" label="工作时间"></el-option>
+            <el-option :value="'2'" label="休息时间"></el-option>
+          </el-select>
+        </el-form-item>
+      </template>
+      <template v-slot:startTime="{ row, $index }">
+        <el-form-item
+          label-width="0"
+          :prop="'list.' + $index + '.startTime'"
+          :rules="[
+            {
+              required: true,
+              message: '请选择开始时间',
+              trigger: 'blur'
+            }
+          ]"
+        >
+          <el-time-select
+            placeholder="开始时间"
+            v-model="row.startTime"
+            @change="changeTime(row, $index, 'startTime')"
+            :disabled="!!form.list[$index - 1]?.endTime"
+            :picker-options="{
+              ...defTime,
+              ...(form.list[$index - 1]
+                ? {
+                    minTime: form.list[$index - 1].endTime
+                  }
+                : {})
+            }"
+          >
+          </el-time-select>
+        </el-form-item>
+      </template>
+      <template v-slot:endTime="{ row, $index }">
+        <el-form-item
+          label-width="0"
+          :prop="'list.' + $index + '.endTime'"
+          :rules="[
+            {
+              required: true,
+              message: '请选择结束时间',
+              trigger: 'blur'
+            }
+          ]"
+        >
+          <el-time-select
+            placeholder="结束时间"
+            v-model="row.endTime"
+            @change="changeTime(row, $index, 'endTime')"
+            :picker-options="{
+              ...defTime,
+              ...{
+                minTime: row.startTime
+              }
+            }"
+          >
+          </el-time-select>
+        </el-form-item>
+      </template>
+      <template v-slot:columnRequired="{ column }">
+        <span class="is-required">{{ column.label }}</span>
+      </template>
+      <template v-slot:note="{ row }">
+        <el-input clearable v-model="row.note" placeholder="请输入" />
+      </template>
+      <template v-slot:action="{ row, $index }">
+        <el-popconfirm
+          class="ele-action"
+          title="确定要删除此信息吗?"
+          @confirm="remove($index)"
+        >
+          <template v-slot:reference>
+            <el-link type="danger" :underline="false" icon="el-icon-delete">
+              删除
+            </el-link>
+          </template>
+        </el-popconfirm>
+      </template>
+    </ele-pro-table>
+  </el-form>
+</template>
+<script>
+  const defForm = {
+    list: [] //表格数据
+  };
+
+  export default {
+    data() {
+      return {
+        defTime: {
+          step: '00:01',
+          start: '00:00',
+          end: '23:59'
+        },
+        columns: [
+          {
+            width: 60,
+            type: 'index',
+            columnKey: 'index',
+            label: '序号',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center'
+          },
+          {
+            prop: 'dutyName',
+            slot: 'dutyName',
+            headerSlot: 'columnRequired',
+            label: '出勤名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            slot: 'timeType',
+            prop: 'timeType',
+            headerSlot: 'columnRequired',
+            label: '时间类型',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'startTime',
+            label: '开始时间',
+            align: 'center',
+            headerSlot: 'columnRequired',
+            slot: 'startTime',
+            showOverflowTooltip: true,
+            minWidth: 170
+          },
+          {
+            prop: 'endTime',
+            minWidth: 170,
+            headerSlot: 'columnRequired',
+            slot: 'endTime',
+            label: '结束时间',
+            align: 'center'
+          },
+          {
+            width: 160,
+            prop: 'totalDuration',
+            label: '总时长(分钟)',
+            align: 'center'
+          },
+          {
+            prop: 'note',
+            slot: 'note',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 200
+          },
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 150,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true,
+            fixed: 'right'
+          }
+        ],
+        selection: [],
+
+        form: { ...defForm },
+        rules: {},
+        title: '新增'
+      };
+    },
+    mounted() {},
+    created() {},
+
+    methods: {
+      //初始化
+      async init(row = {}, type) {
+        this.title = type == 'add' ? '新增' : type == 'edit' ? '修改' : '详情';
+        this.form.list = row.list;
+        this.form.list.forEach((item, index) => {
+          item.key = index;
+        });
+      },
+      handAdd() {
+        if (
+          this.form.list?.length &&
+          !this.form.list[this.form.list?.length - 1].endTime
+        ) {
+          this.$message.error('请先填写前一条数据的结束时间');
+          return;
+        }
+        this.form.list.push({
+          startTime: this.incrementTime(
+            this.form.list[this.form.list?.length - 1]?.endTime
+          ),
+          endTime: '',
+          note: '',
+          totalDuration: '',
+          dutyName: '',
+          timeType: '1',
+          key: this.form.list?.length || 0
+        });
+      },
+      changeTime(row, index, type) {
+        if (type == 'startTime') {
+          this.$set(this.form.list[index], 'endTime', '');
+        }
+        if (type == 'endTime') {
+          if (!row.startTime) {
+            this.$set(this.form.list[index], 'endTime', '');
+            this.$message.error('请先选择开始时间');
+            return;
+          }
+        }
+        this.removeFormtime(index);
+        if (row.startTime && row.endTime) {
+          let val =
+            (new Date('2025-01-01 ' + row.endTime).getTime() -
+              new Date('2025-01-01 ' + row.startTime).getTime()) /
+            60000;
+
+          this.$set(this.form.list[index], 'totalDuration', val);
+        } else {
+          this.$set(this.form.list[index], 'totalDuration', 0);
+        }
+        this.setFormtime();
+      },
+      removeFormtime(i) {
+        this.form.list.splice(i + 1, this.form.list.length - i);
+      },
+      setFormtime() {
+        if (this.form.list?.length) {
+          let attendanceStartTime = this.form.list[0].startTime;
+          let attendanceEndTime =
+            this.form.list[this.form.list.length - 1].endTime;
+          let totalWorkingMinutes = 0;
+          let restDurationMinutes = 0;
+
+          this.form.list.forEach((v, index) => {
+            v.key = index;
+            if (v.timeType == '1') {
+              totalWorkingMinutes += v.totalDuration;
+            } else {
+              restDurationMinutes += v.totalDuration;
+            }
+          });
+          this.$emit('setFormtime', {
+            attendanceStartTime,
+            attendanceEndTime,
+            totalWorkingMinutes,
+            restDurationMinutes
+          });
+        }
+      },
+      incrementTime(time) {
+        if (!time) {
+          return '';
+        }
+        const [minutes, seconds] = time.split(':').map(Number);
+        let newSeconds = (seconds + 1) % 60;
+        let newMinutes = minutes + Math.floor((seconds + 1) / 60);
+        return `${String(newMinutes).padStart(2, '0')}:${String(
+          newSeconds
+        ).padStart(2, '0')}`;
+      },
+      remove(index) {
+        if (index != 0) {
+          this.removeFormtime(index);
+        }
+        this.form.list.splice(index, 1);
+        this.setFormtime();
+        this.selection = [];
+      },
+      getTableValue() {
+        return new Promise((resolve) => {
+          this.$refs.form.validate((valid) => {
+            if (!valid) {
+              resolve([]);
+            } else {
+              resolve(this.form.list);
+            }
+          });
+        });
+      }
+    }
+  };
+</script>
+<style scoped lang="scss"></style>

+ 292 - 0
src/views/productionScheduling/productionShift/index.vue

@@ -0,0 +1,292 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never" v-loading="loading">
+      <search-table @search="reload"></search-table>
+      <!-- 数据表格 -->
+      <ele-pro-table
+        ref="table"
+        :columns="columns"
+        :datasource="datasource"
+        height="calc(100vh - 325px)"
+        full-height="calc(100vh - 116px)"
+        tool-class="ele-toolbar-form"
+        :selection.sync="selection"
+        :page-size="20"
+        @columns-change="handleColumnChange"
+        :cache-key="cacheKeyUrl"
+        :response="{
+          dataName: 'records',
+          countName: 'total'
+        }"
+      >
+        <!-- 表头工具栏 -->
+        <template v-slot:toolbar>
+          <el-button
+            size="small"
+            type="primary"
+            icon="el-icon-plus"
+            class="ele-btn-icon"
+            @click="handleAddOrEdit('', 'add')"
+          >
+            新建
+          </el-button>
+        </template>
+        <!-- 操作 -->
+        <template v-slot:action="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            @click="handleAddOrEdit(row, 'edit')"
+          >
+            修改
+          </el-link>
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            @click="submit(row)"
+            v-if="row.reviewStatus == 0 || row.reviewStatus == 3"
+          >
+            提交
+          </el-link>
+          <el-popconfirm
+            class="ele-action"
+            title="确定要删除此信息吗?"
+            @confirm="remove([row.id])"
+          >
+            <template v-slot:reference>
+              <el-link type="danger" :underline="false" icon="el-icon-delete">
+                删除
+              </el-link>
+            </template>
+          </el-popconfirm>
+        </template>
+        <template v-slot:code="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            @click="handleAddOrEdit(row, 'view')"
+          >
+            {{ row.code }}
+          </el-link>
+        </template>
+      </ele-pro-table>
+    </el-card>
+
+    <add-or-edit-dialog
+      ref="addOrEditDialogRef"
+      @done="reload"
+    ></add-or-edit-dialog>
+
+    <process-submit-dialog
+      :processSubmitDialogFlag.sync="processSubmitDialogFlag"
+      v-if="processSubmitDialogFlag"
+      ref="processSubmitDialogRef"
+      @reload="reload"
+      :isNotNeedProcess="false"
+    ></process-submit-dialog>
+  </div>
+</template>
+
+<script>
+  import tabMixins from '@/mixins/tableColumnsMixin';
+  import addOrEditDialog from './components/addOrEditDialog.vue';
+  import searchTable from './components/searchTable.vue';
+  // import { getList, del } from '@/api/marketManagem/index';
+  import processSubmitDialog from '@/components/processSubmitDialog/processSubmitDialog.vue';
+  import {
+    getTableList,
+    deleteList
+  } from '@/api/productionScheduling/productionShift';
+  export default {
+    mixins: [tabMixins],
+    components: {
+      processSubmitDialog,
+      addOrEditDialog,
+      searchTable
+    },
+    data() {
+      return {
+        // 加载状态
+        loading: false,
+        delVisible: false,
+        selection: [],
+        processSubmitDialogFlag: false,
+        cacheKeyUrl: 'main-productionScheduling-productionShift-index',
+        columns: [
+          // {
+          //   width: 45,
+          //   type: 'selection',
+          //   columnKey: 'selection',
+          //   align: 'center',
+          //   selectable: (row, index) => {
+          //     return row.reviewStatus != 1 && row.reviewStatus != 2;
+          //   }
+          // },
+          {
+            width: 60,
+            label: '序号',
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          },
+          {
+            minWidth: 180,
+            prop: 'code',
+            label: '班次编码',
+            align: 'center',
+            slot: 'code',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 180,
+            prop: 'shiftName',
+            label: '班次名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'attendanceStartTime',
+            label: '出勤开始时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 130,
+            prop: 'attendanceEndTime',
+            label: '出勤结束时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'totalWorkingMinutes',
+            label: '工作总时长(分钟)',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 200,
+            prop: 'restDurationMinutes',
+            label: '休息时长(分钟)',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'status',
+            label: '状态',
+            align: 'center',
+            formatter: (row, column) => {
+              return row.status == 0 ? '启用' : '禁用';
+            },
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 100,
+            prop: 'updateUserName',
+            label: '设置人',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'updateTime',
+            label: '设置时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 200,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true,
+            fixed: 'right'
+          }
+        ]
+      };
+    },
+    computed: {
+      columns() {
+        return [];
+      }
+    },
+    created() {},
+    methods: {
+      //新增、修改
+      handleAddOrEdit(row = {}, type) {
+        this.$nextTick(() => {
+          this.$refs.addOrEditDialogRef.open(row, type);
+        });
+      },
+
+      /* 表格数据源 */
+      datasource({ page, limit, where, order }) {
+        return getTableList({
+          pageNum: page,
+          size: limit,
+          ...where
+        });
+      },
+      submit(res) {
+        this.processSubmitDialogFlag = true;
+        this.$nextTick(() => {
+          let params = {
+            businessId: res.id,
+            businessKey: 'market_management_approve',
+            formCreateUserId: res.createUserId,
+            pcHandle: '/bpm/handleTask/components/marketManagem/submit.vue',
+            pcView: '/bpm/handleTask/components/marketManagem/view.vue',
+            variables: {
+              businessCode: res.code,
+              businessName: res.name,
+              businessType: res.type
+            }
+          };
+          this.$refs.processSubmitDialogRef.init(params);
+        });
+      },
+      /* 刷新表格 */
+      reload(where = {}) {
+        this.$refs.table.reload({ page: 1, where });
+      },
+
+      remove(row) {
+        del(row).then((res) => {
+          this.$message.success('删除成功!');
+          this.reload();
+        });
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-link--inner) {
+    margin-left: 0px !important;
+  }
+
+  .sys-organization-list {
+    height: calc(100vh - 264px);
+    box-sizing: border-box;
+    border-width: 1px;
+    border-style: solid;
+    overflow: auto;
+  }
+
+  .sys-organization-list :deep(.el-tree-node__content) {
+    height: 40px;
+
+    & > .el-tree-node__expand-icon {
+      margin-left: 10px;
+    }
+  }
+</style>

+ 557 - 0
src/views/productionScheduling/stationBeat/components/addOrEditDialog.vue

@@ -0,0 +1,557 @@
+<template>
+  <ele-modal
+    custom-class="ele-dialog-form long-dialog-form"
+    :centered="true"
+    :visible.sync="addOrEditDialogFlag"
+    :title="title"
+    :append-to-body="false"
+    :close-on-click-modal="false"
+    width="60%"
+    :before-close="cancel"
+    :maxable="true"
+    :resizable="true"
+  >
+    <el-form
+      ref="form"
+      :model="form"
+      :rules="rules"
+      label-width="120px"
+      class="el-form-box"
+      :disabled="title == '详情'"
+    >
+      <headerTitle title="产品信息"> </headerTitle>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="编码" prop="code">
+            <el-input
+              clearable
+              readonly
+              v-model="form.code"
+              @click.native="$refs.produceRef.open()"
+              placeholder="请选择"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="名称" prop="name">
+            <el-input clearable disabled v-model="form.name" placeholder=" " />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="BOM类型" prop="bomCategory">
+            <el-select
+              v-model="form.bomCategory"
+              @change="changeProductType"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item of bomCategoryList"
+                :key="item.code"
+                :label="item.name"
+                :value="item.code"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="BOM版本" prop="bomId">
+            <el-select
+              v-model="form.bomId"
+              @change="changeBomId"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item of bomVersionList"
+                :key="item.id"
+                :label="item.name + '(V' + item.versions + '.0)'"
+                :value="item.id"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工艺路线" prop="produceRoutingId">
+            <el-select
+              v-model="form.produceRoutingId"
+              @change="changeRoutingList"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item of routingList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工厂名称:" prop="factoriesId">
+            <el-select
+              style="width: 100%"
+              clearable
+              v-model="form.factoriesId"
+              placeholder="请选择所属工厂"
+            >
+              <el-option
+                v-for="item in factoryList"
+                :label="item.name"
+                :value="item.id"
+                :key="item.id"
+                @click.native="change_factoryId(item)"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <headerTitle title="工位节拍"> </headerTitle>
+      <ele-pro-table
+        :columns="columns"
+        :datasource="form.list"
+        ref="table"
+        height="400px"
+        full-height="calc(100vh - 116px)"
+        :needPage="false"
+        :selection.sync="selection"
+      >
+        <template v-slot:toolbar>
+          <div style="display: flex">
+            <el-form-item label="工序名称" prop="operationId">
+              <el-select v-model="form.operationId" style="width: 230px">
+                <el-option
+                  v-for="item of taskList"
+                  :key="item.sourceTaskId"
+                  :label="item.name"
+                  :value="item.sourceTaskId"
+                  @click.native="changeOperationId(item)"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="工序节拍(分)" prop="cycleTime">
+              <el-input
+                clearable
+                style="width: 230px"
+                disabled
+                :value="cycleTime"
+                placeholder=" "
+              />
+            </el-form-item>
+          </div>
+        </template>
+        <template v-slot:stationCycleTime="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + $index + '.stationCycleTime'"
+            :rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
+          >
+            <el-input
+              clearable
+              v-model.number="row.stationCycleTime"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </template>
+        <template v-slot:priority="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + $index + '.priority'"
+            :rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
+          >
+            <el-input
+              clearable
+              v-model.number="row.priority"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </template>
+
+        <template v-slot:remark="{ row }">
+          <el-input clearable v-model="row.remark" placeholder="请输入" />
+        </template>
+        <template v-slot:columnRequired="{ column }">
+          <span class="is-required">{{ column.label }}</span>
+        </template>
+      </ele-pro-table>
+    </el-form>
+    <div slot="footer">
+      <el-button
+        type="primary"
+        @click="save()"
+        v-lodading="loading"
+        v-if="title != '详情'"
+        >保存</el-button
+      >
+
+      <el-button @click="cancel">返回</el-button>
+    </div>
+    <ProductModal
+      ref="produceRef"
+      @changeParent="produceConfirm"
+    ></ProductModal>
+  </ele-modal>
+</template>
+<script>
+  const defForm = {
+    code: '', //编码
+    name: '', //名称
+    productId: '', //物品id
+    factoriesId: '', //工厂id
+    factoriesName: '', //工厂名称
+    bomVersion: '', //bom版本
+    bomId: '', //bom版本id
+    bomCategory: '', //bom类型
+    brandNo: '', //品牌
+    specification: '', //规格
+    modelType: '', //型号
+    produceRoutingId: '', //	工艺路线
+    produceRoutingName: '', //	工艺路线名称
+    cycleTime: '', //	工序节拍
+    operationName: '', //	工序名称
+    operationId: '', //	工序id
+    list: [] //表格数据
+  };
+  import { getFactoryarea } from '@/api/factoryModel';
+  import ProductModal from '@/components/productList/product-list.vue';
+  import {
+    saveOrUpdate,
+    getById,
+    listBomType,
+    bomListByPlan,
+    bomRoutingList,
+    listByRoutingIds,
+    selectFactoryWorkstation
+  } from '@/api/productionScheduling/stationBeat';
+
+  export default {
+    components: { ProductModal },
+    computed: {},
+    data() {
+      return {
+        lodading: false,
+        title: '',
+        addOrEditDialogFlag: false,
+        form: { ...defForm },
+        rules: {
+          name: [
+            { required: true, message: '名称不能为空', trigger: 'change' }
+          ],
+          factoriesId: [
+            { required: true, message: '请选择工厂', trigger: 'change' }
+          ],
+          bomId: [
+            { required: true, message: '请选择BOM版本', trigger: 'change' }
+          ],
+          bomCategory: [
+            { required: true, message: '请选择BOM类型', trigger: 'change' }
+          ],
+          produceRoutingId: [
+            { required: true, message: '请选择工艺路线', trigger: 'change' }
+          ],
+          code: [{ required: true, message: '编码不能为空', trigger: 'change' }]
+        },
+        factoryList: [],
+        selection: [],
+        productionLineList: [],
+        columns: [
+          {
+            width: 60,
+            type: 'index',
+            columnKey: 'index',
+            label: '序号',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            prop: 'workstationName',
+            label: '工位名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'deviceCode',
+            label: '设备编码',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'deviceName',
+            label: '设备名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'model',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'deviceType',
+            label: '设备类型',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+
+          {
+            prop: 'stationCycleTime',
+            slot: 'stationCycleTime',
+            headerSlot: 'columnRequired',
+            label: '工位节拍(分钟)',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'priority',
+            slot: 'priority',
+            headerSlot: 'columnRequired',
+            label: '优先顺序',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 120
+          },
+          {
+            prop: 'remark',
+            slot: 'remark',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 200
+          }
+        ],
+        loading: false,
+        bomCategoryList: [],
+        bomVersionList: [],
+        routingList: [],
+        taskList: []
+      };
+    },
+    mounted() {},
+    created() {
+      this.getFactoryList();
+    },
+    computed: {
+      cycleTime() {
+        let cycleTime = this.form.list.reduce((pre, cur) => {
+          return pre + Number(cur.stationCycleTime);
+        }, 0);
+        this.form.cycleTime =
+          cycleTime / this.form.list.length / this.form.list.length;
+        return cycleTime / this.form.list.length / this.form.list.length || 0;
+      }
+    },
+
+    methods: {
+      //初始化
+      async open(row = {}, type) {
+        this.addOrEditDialogFlag = true;
+        this.title = type == 'add' ? '新增' : type == 'edit' ? '修改' : '详情';
+
+        if (type !== 'add') {
+          this.form = await getById(row.id);
+          this.form.list.forEach((item, index) => {
+            item.key = index;
+          });
+          this.getBomInfo(this.form.productId);
+          this.changeProductType('init');
+
+          if (this.form.factoriesId) {
+            this.change_factoryId({ id: this.form.factoriesId });
+          }
+        }
+      },
+      handParent() {
+        this.form.list.push({
+          productionLineName: '',
+          productionLineId: '',
+          stationCycleTime: '',
+          remark: '',
+          key: this.form.list?.length || 0
+        });
+      },
+      async getFactoryList() {
+        const { list } = await getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 1,
+          enable: 1
+        });
+        this.factoryList = list || [];
+      },
+      async change_factoryId(item) {
+        const { list } = await getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 4,
+          factoryId: item.id,
+          enable: 1
+        });
+        this.productionLineList = list || [];
+        this.form.factoriesName = item.name;
+      },
+      change_productionLineId(item, index) {
+        this.$set(this.form.list[index], 'productionLineName', item.name);
+      },
+      produceConfirm(data) {
+        this.form.name = data[0].name;
+        this.form.code = data[0].code;
+        this.form.productId = data[0].id;
+        this.form.brandNo = data[0].brandNum;
+        this.form.modelType = data[0].modelType;
+        this.form.specification = data[0].specification;
+        this.produceCancel();
+        this.getBomInfo(data[0].id);
+      },
+      produceCancel() {
+        this.form.bomCategory = '';
+        this.bomCategoryList = [];
+        this.form.bomVersion = '';
+        this.form.bomId = '';
+        this.bomVersionList = [];
+        this.routingList = [];
+        this.form.produceRoutingId = '';
+        this.form.produceRoutingName = '';
+        this.produceRoutingList = [];
+        this.form.operationId = '';
+        this.form.operationName = '';
+        this.taskList = [];
+        this.form.list = [];
+      },
+
+      // 获取bom信息
+      async getBomInfo(id) {
+        const res = await listBomType({ categoryId: id });
+        if (!res || res.length == 0) return;
+        let bomMap = {
+          2: { code: '2', name: 'MBOM' },
+          3: { code: '3', name: 'ABOM' }
+        };
+        let arr = [];
+        res.map((item) => {
+          if (bomMap[item.bomType]) {
+            arr.push(bomMap[item.bomType]);
+          }
+        });
+        this.bomCategoryList = arr;
+      },
+
+      // 根据bom类型和产品id获取bom版本
+      async changeProductType(type) {
+        let data = await bomListByPlan({
+          bomType: this.form.bomCategory,
+          categoryId: this.form.productId
+        });
+        this.bomVersionList = data;
+        if (data.length) {
+          if (type != 'init') {
+            this.form.bomId = data[0].id;
+            this.form.bomVersion = data[0].versions;
+          }
+          this.changeBomId(type);
+        }
+      },
+      async changeBomId(type) {
+        const res = await bomRoutingList(this.form.bomId);
+        let arr = res || [];
+        this.routingList = arr;
+        if (type != 'init') {
+          this.form.bomVersion = this.bomVersionList.find(
+            (item) => item.id == this.form.bomId
+          )?.versions;
+          this.taskList = [];
+          this.form.produceRoutingId = '';
+          this.form.produceRoutingName = '';
+          this.form.operationName = '';
+          this.form.operationId = '';
+          this.form.list = [];
+        }
+
+        if (arr.length) {
+          if (type != 'init') {
+            this.form.produceRoutingId = arr[0].id;
+            this.form.produceRoutingName = arr[0].name;
+          }
+          this.changeRoutingList(type);
+        }
+      },
+      async changeRoutingList(type) {
+        const res = await listByRoutingIds([this.form.produceRoutingId]);
+        let arr = res || [];
+        this.taskList = arr;
+        if (type != 'init') {
+          this.form.operationName = '';
+          this.form.operationId = '';
+          this.form.list = [];
+        }
+      },
+      async changeOperationId(item) {
+        this.form.operationName = item.name;
+        const res = await selectFactoryWorkstation({
+          taskId: this.form.operationId
+        });
+        let arr = res || [];
+        this.form.list = arr.map((item) => {
+          return {
+            deviceId: item.extInfo?.assetId,
+            deviceName: item.extInfo?.assetName,
+            deviceCode: item.extInfo?.assetCode,
+            workstationName: item.name,
+            workstationId: item.id,
+            model: item.modelType,
+            deviceType: item.categoryLevelName,
+            stationCycleTime: '',
+            remark: '',
+            priority: ''
+            // deviceType: item.extInfo?.assetType,
+          };
+        });
+      },
+
+      save() {
+        this.$refs.form.validate((valid) => {
+          if (!this.form.list?.length) {
+            this.$message.error('节拍信息不能为空');
+            return;
+          }
+          if (valid) {
+            let priorityList = this.form.list.map((item) => item.priority);
+
+            if (new Set(priorityList).size !== priorityList.length) {
+              this.$message.error('优先顺序不能重复');
+              return;
+            }
+            this.loading = true;
+            saveOrUpdate(this.form)
+              .then((res) => {
+                this.$message.success('操作成功');
+                this.cancel();
+                this.$emit('done');
+              })
+              .finally(() => {
+                this.loading = false;
+              });
+          }
+        });
+      },
+
+      //关闭弹窗
+      cancel() {
+        this.addOrEditDialogFlag = false;
+        this.form = {
+          ...defForm
+        };
+        this.form.list = [];
+      }
+    }
+  };
+</script>
+<style scoped lang="scss">
+  // :deep(.el-form-item--medium) {
+  //   display: none;
+  // }
+</style>

+ 59 - 0
src/views/productionScheduling/stationBeat/components/searchTable.vue

@@ -0,0 +1,59 @@
+<!-- 搜索表单 -->
+<template>
+  <seekPage :seekList="seekList" :formLength="3" @search="search"></seekPage>
+</template>
+<script>
+  import { getFactoryarea } from '@/api/factoryModel';
+
+  export default {
+    data() {
+      return {
+        productionLineList: []
+      };
+    },
+    computed: {
+      // 表格列配置
+      seekList() {
+        return [
+          {
+            label: '编号:',
+            value: 'code',
+            type: 'input',
+            placeholder: ' '
+          },
+          // {
+          //   label: '产线:',
+          //   value: 'productionLineName',
+          //   type: 'select',
+          //   placeholder: ' ',
+          //   planList: this.productionLineList
+          // }
+        ];
+      }
+    },
+    async created() {
+      const { list } = await getFactoryarea({
+        pageNum: 1,
+        size: 999,
+        type: 4,
+        enable: 1
+      });
+      this.productionLineList =
+        list.map((item) => {
+          return {
+            label: item.name,
+            value: item.name
+          };
+        }) || [];
+      console.log(this.productionLineList, 'this.productionLineList');
+    },
+    methods: {
+      /* 搜索 */
+      search(e) {
+        this.$emit('search', {
+          ...e
+        });
+      }
+    }
+  };
+</script>

+ 404 - 0
src/views/productionScheduling/stationBeat/index.vue

@@ -0,0 +1,404 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never" v-loading="loading">
+      <search-table @search="reload"></search-table>
+      <!-- 数据表格 -->
+      <ele-pro-table
+        ref="table"
+        :columns="columns"
+        :datasource="datasource"
+        height="calc(100vh - 325px)"
+        full-height="calc(100vh - 116px)"
+        tool-class="ele-toolbar-form"
+        :selection.sync="selection"
+        :page-size="20"
+        @columns-change="handleColumnChange"
+        :cache-key="cacheKeyUrl"
+        :response="{
+          dataName: 'records',
+          countName: 'total'
+        }"
+      >
+        <!-- 表头工具栏 -->
+        <template v-slot:toolbar>
+          <el-button
+            size="small"
+            type="primary"
+            icon="el-icon-plus"
+            class="ele-btn-icon"
+            @click="handleAddOrEdit('', 'add')"
+          >
+            新建
+          </el-button>
+        </template>
+        <!-- 操作 -->
+        <template v-slot:action="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            @click="handleAddOrEdit(row, 'edit')"
+          >
+            修改
+          </el-link>
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            @click="submit(row)"
+            v-if="row.reviewStatus == 0 || row.reviewStatus == 3"
+          >
+            提交
+          </el-link>
+          <el-popconfirm
+            class="ele-action"
+            title="确定要删除此信息吗?"
+            @confirm="remove([row.productionSchedulingConfigDetailId])"
+          >
+            <template v-slot:reference>
+              <el-link type="danger" :underline="false" icon="el-icon-delete">
+                删除
+              </el-link>
+            </template>
+          </el-popconfirm>
+        </template>
+        <template v-slot:code="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            @click="handleAddOrEdit(row, 'view')"
+          >
+            {{ row.code }}
+          </el-link>
+        </template>
+      </ele-pro-table>
+    </el-card>
+
+    <add-or-edit-dialog
+      ref="addOrEditDialogRef"
+      @done="reload"
+    ></add-or-edit-dialog>
+
+    <process-submit-dialog
+      :processSubmitDialogFlag.sync="processSubmitDialogFlag"
+      v-if="processSubmitDialogFlag"
+      ref="processSubmitDialogRef"
+      @reload="reload"
+      :isNotNeedProcess="false"
+    ></process-submit-dialog>
+  </div>
+</template>
+
+<script>
+  import tabMixins from '@/mixins/tableColumnsMixin';
+  import addOrEditDialog from './components/addOrEditDialog.vue';
+  import searchTable from './components/searchTable.vue';
+  import {
+    getTableList,
+    deleteList
+  } from '@/api/productionScheduling/stationBeat';
+  import processSubmitDialog from '@/components/processSubmitDialog/processSubmitDialog.vue';
+
+  export default {
+    mixins: [tabMixins],
+    components: {
+      processSubmitDialog,
+      addOrEditDialog,
+      searchTable
+    },
+    data() {
+      return {
+        // 加载状态
+        loading: false,
+        delVisible: false,
+        selection: [],
+        processSubmitDialogFlag: false,
+        cacheKeyUrl: 'main-productionScheduling-stationBeat-index',
+        columns: [
+          // {
+          //   width: 45,
+          //   type: 'selection',
+          //   columnKey: 'selection',
+          //   align: 'center',
+          //   selectable: (row, index) => {
+          //     return row.reviewStatus != 1 && row.reviewStatus != 2;
+          //   }
+          // },
+          {
+            width: 60,
+            label: '序号',
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          },
+          {
+            minWidth: 180,
+            prop: 'code',
+            label: '编码',
+            align: 'center',
+            slot: 'code',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 180,
+            prop: 'name',
+            label: '名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 130,
+            prop: 'brandNo',
+            label: '牌号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'modelType',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'bomCategory',
+            label: 'BOM分类',
+            align: 'center',
+            showOverflowTooltip: true,
+            formatter: (row, column) => {
+              return row.bomCategory == 2
+                ? 'MBOM'
+                : row.bomCategory == 3
+                ? 'ABOM'
+                : '';
+            }
+          },
+          {
+            minWidth: 150,
+            prop: 'bomVersion',
+            label: 'BOM版本',
+            align: 'center',
+            showOverflowTooltip: true,
+            formatter: (row, column) => {
+              return row.bomVersion?'v'+ row.bomVersion+'.0':''
+            }
+          },
+          {
+            minWidth: 200,
+            prop: 'produceRoutingName',
+            label: '工艺路线',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'factoriesName',
+            label: '工厂名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'operationName',
+            label: '工序名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'cycleTime',
+            label: '工序节拍(分)',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 150,
+            prop: 'workstationName',
+            label: '工位名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'stationCycleTime',
+            label: '工位节拍(分)',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'priority',
+            label: '优先顺序',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'gzzx',
+            label: '工作中心',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          // {
+          //   minWidth: 150,
+          //   prop: 'teamName',
+          //   label: '班组名称',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // },
+          {
+            minWidth: 150,
+            prop: 'deviceCode',
+            label: '设备编码',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'deviceName',
+            label: '设备名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'model',
+            label: '设备型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'deviceType',
+            label: '设备类型',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 200,
+            prop: 'remark',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'updateUserName',
+            label: '设置人',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 160,
+            prop: 'updateTime',
+            label: '设置时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 200,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true,
+            fixed: 'right'
+          }
+        ]
+      };
+    },
+    computed: {
+      columns() {
+        return [];
+      }
+    },
+    created() {},
+    methods: {
+      //新增、修改
+      handleAddOrEdit(row = {}, type) {
+        this.$nextTick(() => {
+          this.$refs.addOrEditDialogRef.open(row, type);
+        });
+      },
+
+      /* 表格数据源 */
+      datasource({ page, limit, where, order }) {
+        return getTableList({
+          pageNum: page,
+          size: limit,
+          ...where
+        });
+      },
+      submit(res) {
+        this.processSubmitDialogFlag = true;
+        this.$nextTick(() => {
+          let params = {
+            businessId: res.id,
+            businessKey: 'market_management_approve',
+            formCreateUserId: res.createUserId,
+            pcHandle: '/bpm/handleTask/components/marketManagem/submit.vue',
+            pcView: '/bpm/handleTask/components/marketManagem/view.vue',
+            variables: {
+              businessCode: res.code,
+              businessName: res.name,
+              businessType: res.type
+            }
+          };
+          this.$refs.processSubmitDialogRef.init(params);
+        });
+      },
+      /* 刷新表格 */
+      reload(where = {}) {
+        this.$refs.table.reload({ page: 1, where });
+      },
+
+      commitBtn() {
+        const dataId = this.selection.map((v) => v.id);
+        del(dataId).then((res) => {
+          this.$message.success('删除成功!');
+          this.reload();
+        });
+      },
+      remove(row) {
+        deleteList(row).then((res) => {
+          this.$message.success('删除成功!');
+          this.reload();
+        });
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-link--inner) {
+    margin-left: 0px !important;
+  }
+
+  .sys-organization-list {
+    height: calc(100vh - 264px);
+    box-sizing: border-box;
+    border-width: 1px;
+    border-style: solid;
+    overflow: auto;
+  }
+
+  .sys-organization-list :deep(.el-tree-node__content) {
+    height: 40px;
+
+    & > .el-tree-node__expand-icon {
+      margin-left: 10px;
+    }
+  }
+</style>

+ 530 - 0
src/views/productionScheduling/totalProcessTime/components/addOrEditDialog.vue

@@ -0,0 +1,530 @@
+<template>
+  <ele-modal
+    custom-class="ele-dialog-form long-dialog-form"
+    :centered="true"
+    :visible.sync="addOrEditDialogFlag"
+    :title="title"
+    :append-to-body="false"
+    :close-on-click-modal="false"
+    width="60%"
+    :before-close="cancel"
+    :maxable="true"
+    :resizable="true"
+  >
+    <el-form
+      ref="form"
+      :model="form"
+      :rules="rules"
+      label-width="120px"
+      class="el-form-box"
+      :disabled="title == '详情'"
+    >
+      <headerTitle title="产品信息"> </headerTitle>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="编码" prop="code">
+            <el-input
+              clearable
+              readonly
+              v-model="form.code"
+              @click.native="$refs.produceRef.open()"
+              placeholder="请选择"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="名称" prop="name">
+            <el-input clearable disabled v-model="form.name" placeholder=" " />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="BOM类型" prop="bomCategory">
+            <el-select
+              v-model="form.bomCategory"
+              @change="changeProductType"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item of bomCategoryList"
+                :key="item.code"
+                :label="item.name"
+                :value="item.code"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="BOM版本" prop="bomId">
+            <el-select
+              v-model="form.bomId"
+              @change="changeBomId"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item of bomVersionList"
+                :key="item.id"
+                :label="item.name + '(V' + item.versions + '.0)'"
+                :value="item.id"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工艺路线" prop="produceRoutingId">
+            <el-select
+              v-model="form.produceRoutingId"
+              @change="changeRoutingList"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item of routingList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工厂名称:" prop="factoriesId">
+            <el-select
+              style="width: 100%"
+              clearable
+              v-model="form.factoriesId"
+              placeholder="请选择所属工厂"
+            >
+              <el-option
+                v-for="item in factoryList"
+                :label="item.name"
+                :value="item.id"
+                :key="item.id"
+                @click.native="change_factoryId(item)"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <headerTitle title="工位节拍"> </headerTitle>
+      <ele-pro-table
+        :columns="columns"
+        :datasource="form.list"
+        ref="table"
+        height="400px"
+        full-height="calc(100vh - 116px)"
+        :needPage="false"
+        :selection.sync="selection"
+      >
+
+        <template v-slot:stationCycleTime="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + $index + '.stationCycleTime'"
+            :rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
+          >
+            <el-input
+              clearable
+              v-model.number="row.stationCycleTime"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </template>
+        <template v-slot:priority="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + $index + '.priority'"
+            :rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
+          >
+            <el-input
+              clearable
+              v-model.number="row.priority"
+              placeholder="请输入"
+            />
+          </el-form-item>
+        </template>
+
+        <template v-slot:remark="{ row }">
+          <el-input clearable v-model="row.remark" placeholder="请输入" />
+        </template>
+        <template v-slot:columnRequired="{ column }">
+          <span class="is-required">{{ column.label }}</span>
+        </template>
+      </ele-pro-table>
+    </el-form>
+    <div slot="footer">
+      <el-button
+        type="primary"
+        @click="save()"
+        v-lodading="loading"
+        v-if="title != '详情'"
+        >保存</el-button
+      >
+
+      <el-button @click="cancel">返回</el-button>
+    </div>
+    <ProductModal
+      ref="produceRef"
+      @changeParent="produceConfirm"
+    ></ProductModal>
+  </ele-modal>
+</template>
+<script>
+  const defForm = {
+    code: '', //编码
+    name: '', //名称
+    productId: '', //物品id
+    factoriesId: '', //工厂id
+    factoriesName: '', //工厂名称
+    bomVersion: '', //bom版本
+    bomId: '', //bom版本id
+    bomCategory: '', //bom类型
+    brandNo: '', //品牌
+    specification: '', //规格
+    modelType: '', //型号
+    produceRoutingId: '', //	工艺路线
+    produceRoutingName: '', //	工艺路线名称
+    cycleTime: '', //	工序节拍
+    operationName: '', //	工序名称
+    operationId: '', //	工序id
+    list: [] //表格数据
+  };
+  import { getFactoryarea } from '@/api/factoryModel';
+  import ProductModal from '@/components/productList/product-list.vue';
+  import {
+    saveOrUpdate,
+    getById,
+    listBomType,
+    bomListByPlan,
+    bomRoutingList,
+    listByRoutingIds,
+    selectFactoryWorkstation
+  } from '@/api/productionScheduling/stationBeat';
+
+  export default {
+    components: { ProductModal },
+    computed: {},
+    data() {
+      return {
+        lodading: false,
+        title: '',
+        addOrEditDialogFlag: false,
+        form: { ...defForm },
+        rules: {
+          name: [
+            { required: true, message: '名称不能为空', trigger: 'change' }
+          ],
+          factoriesId: [
+            { required: true, message: '请选择工厂', trigger: 'change' }
+          ],
+          bomId: [
+            { required: true, message: '请选择BOM版本', trigger: 'change' }
+          ],
+          bomCategory: [
+            { required: true, message: '请选择BOM类型', trigger: 'change' }
+          ],
+          produceRoutingId: [
+            { required: true, message: '请选择工艺路线', trigger: 'change' }
+          ],
+          code: [{ required: true, message: '编码不能为空', trigger: 'change' }]
+        },
+        factoryList: [],
+        selection: [],
+        productionLineList: [],
+        columns: [
+          {
+            width: 60,
+            type: 'index',
+            columnKey: 'index',
+            label: '序号',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            prop: 'workstationName',
+            label: '工位名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'deviceCode',
+            label: '设备编码',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'deviceName',
+            label: '设备名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'model',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'deviceType',
+            label: '设备类型',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+
+          {
+            prop: 'stationCycleTime',
+            slot: 'stationCycleTime',
+            headerSlot: 'columnRequired',
+            label: '工位节拍(分钟)',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            prop: 'priority',
+            slot: 'priority',
+            headerSlot: 'columnRequired',
+            label: '优先顺序',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 120
+          },
+          {
+            prop: 'remark',
+            slot: 'remark',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 200
+          }
+        ],
+        loading: false,
+        bomCategoryList: [],
+        bomVersionList: [],
+        routingList: [],
+        taskList: []
+      };
+    },
+    mounted() {},
+    created() {
+      this.getFactoryList();
+    },
+    computed: {
+      cycleTime() {
+        let cycleTime = this.form.list.reduce((pre, cur) => {
+          return pre + Number(cur.stationCycleTime);
+        }, 0);
+        this.form.cycleTime =
+          cycleTime / this.form.list.length / this.form.list.length;
+        return cycleTime / this.form.list.length / this.form.list.length || 0;
+      }
+    },
+
+    methods: {
+      //初始化
+      async open(row = {}) {
+        this.addOrEditDialogFlag = true;
+        this.title = '详情';
+
+        this.form = row;
+
+        this.getBomInfo(this.form.productId);
+        this.changeProductType('init');
+
+        if (this.form.factoriesId) {
+          this.change_factoryId({ id: this.form.factoriesId });
+        }
+      },
+      handParent() {
+        this.form.list.push({
+          productionLineName: '',
+          productionLineId: '',
+          stationCycleTime: '',
+          remark: '',
+          key: this.form.list?.length || 0
+        });
+      },
+      async getFactoryList() {
+        const { list } = await getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 1,
+          enable: 1
+        });
+        this.factoryList = list || [];
+      },
+      async change_factoryId(item) {
+        const { list } = await getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 4,
+          factoryId: item.id,
+          enable: 1
+        });
+        this.productionLineList = list || [];
+        this.form.factoriesName = item.name;
+      },
+      change_productionLineId(item, index) {
+        this.$set(this.form.list[index], 'productionLineName', item.name);
+      },
+      produceConfirm(data) {
+        this.form.name = data[0].name;
+        this.form.code = data[0].code;
+        this.form.productId = data[0].id;
+        this.form.brandNo = data[0].brandNum;
+        this.form.modelType = data[0].modelType;
+        this.form.specification = data[0].specification;
+        this.produceCancel();
+        this.getBomInfo(data[0].id);
+      },
+      produceCancel() {
+        this.form.bomCategory = '';
+        this.bomCategoryList = [];
+        this.form.bomVersion = '';
+        this.form.bomId = '';
+        this.bomVersionList = [];
+        this.routingList = [];
+        this.form.produceRoutingId = '';
+        this.form.produceRoutingName = '';
+        this.produceRoutingList = [];
+        this.form.operationId = '';
+        this.form.operationName = '';
+        this.taskList = [];
+        this.form.list = [];
+      },
+
+      // 获取bom信息
+      async getBomInfo(id) {
+        const res = await listBomType({ categoryId: id });
+        if (!res || res.length == 0) return;
+        let bomMap = {
+          2: { code: '2', name: 'MBOM' },
+          3: { code: '3', name: 'ABOM' }
+        };
+        let arr = [];
+        res.map((item) => {
+          if (bomMap[item.bomType]) {
+            arr.push(bomMap[item.bomType]);
+          }
+        });
+        this.bomCategoryList = arr;
+      },
+
+      // 根据bom类型和产品id获取bom版本
+      async changeProductType(type) {
+        let data = await bomListByPlan({
+          bomType: this.form.bomCategory,
+          categoryId: this.form.productId
+        });
+        this.bomVersionList = data;
+        if (data.length) {
+          if (type != 'init') {
+            this.form.bomId = data[0].id;
+            this.form.bomVersion = data[0].versions;
+          }
+          this.changeBomId(type);
+        }
+      },
+      async changeBomId(type) {
+        const res = await bomRoutingList(this.form.bomId);
+        let arr = res || [];
+        this.routingList = arr;
+        if (type != 'init') {
+          this.form.bomVersion = this.bomVersionList.find(
+            (item) => item.id == this.form.bomId
+          )?.versions;
+          this.taskList = [];
+          this.form.produceRoutingId = '';
+          this.form.produceRoutingName = '';
+          this.form.operationName = '';
+          this.form.operationId = '';
+          this.form.list = [];
+        }
+
+        if (arr.length) {
+          if (type != 'init') {
+            this.form.produceRoutingId = arr[0].id;
+            this.form.produceRoutingName = arr[0].name;
+          }
+          this.changeRoutingList(type);
+        }
+      },
+      async changeRoutingList(type) {
+        const res = await listByRoutingIds([this.form.produceRoutingId]);
+        let arr = res || [];
+        this.taskList = arr;
+        if (type != 'init') {
+          this.form.operationName = '';
+          this.form.operationId = '';
+          this.form.list = [];
+        }
+      },
+      async changeOperationId(item) {
+        this.form.operationName = item.name;
+        const res = await selectFactoryWorkstation({
+          taskId: this.form.operationId
+        });
+        let arr = res || [];
+        this.form.list = arr.map((item) => {
+          return {
+            deviceId: item.extInfo?.assetId,
+            deviceName: item.extInfo?.assetName,
+            deviceCode: item.extInfo?.assetCode,
+            workstationName: item.name,
+            workstationId: item.id,
+            model: item.modelType,
+            deviceType: item.categoryLevelName,
+            stationCycleTime: '',
+            remark: '',
+            priority: ''
+            // deviceType: item.extInfo?.assetType,
+          };
+        });
+      },
+
+      save() {
+        this.$refs.form.validate((valid) => {
+          if (!this.form.list?.length) {
+            this.$message.error('节拍信息不能为空');
+            return;
+          }
+          if (valid) {
+            let priorityList = this.form.list.map((item) => item.priority);
+
+            if (new Set(priorityList).size !== priorityList.length) {
+              this.$message.error('优先顺序不能重复');
+              return;
+            }
+            this.loading = true;
+            saveOrUpdate(this.form)
+              .then((res) => {
+                this.$message.success('操作成功');
+                this.cancel();
+                this.$emit('done');
+              })
+              .finally(() => {
+                this.loading = false;
+              });
+          }
+        });
+      },
+
+      //关闭弹窗
+      cancel() {
+        this.addOrEditDialogFlag = false;
+        this.form = {
+          ...defForm
+        };
+        this.form.list = [];
+      }
+    }
+  };
+</script>
+<style scoped lang="scss">
+  // :deep(.el-form-item--medium) {
+  //   display: none;
+  // }
+</style>

+ 44 - 0
src/views/productionScheduling/totalProcessTime/components/searchTable.vue

@@ -0,0 +1,44 @@
+<!-- 搜索表单 -->
+<template>
+  <seekPage :seekList="seekList" :formLength="3" @search="search"></seekPage>
+</template>
+<script>
+  export default {
+    data() {
+      return {};
+    },
+    computed: {
+      // 表格列配置
+      seekList() {
+        return [
+          {
+            label: '编号:',
+            value: 'code',
+            type: 'input',
+            placeholder: ' '
+          },
+          {
+            label: '工序名称:',
+            value: 'shiftName',
+            type: 'input',
+            placeholder: ' '
+          },
+          {
+            label: '工位名称:',
+            value: 'shiftName',
+            type: 'input',
+            placeholder: ' '
+          }
+        ];
+      }
+    },
+    methods: {
+      /* 搜索 */
+      search(e) {
+        this.$emit('search', {
+          ...e
+        });
+      }
+    }
+  };
+</script>

+ 258 - 0
src/views/productionScheduling/totalProcessTime/index.vue

@@ -0,0 +1,258 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never" v-loading="loading">
+      <search-table @search="reload"></search-table>
+      <!-- 数据表格 -->
+      <ele-pro-table
+        ref="table"
+        :columns="columns"
+        :datasource="datasource"
+        height="calc(100vh - 325px)"
+        full-height="calc(100vh - 116px)"
+        tool-class="ele-toolbar-form"
+        :selection.sync="selection"
+        :page-size="20"
+        @columns-change="handleColumnChange"
+        :cache-key="cacheKeyUrl"
+        :response="{
+          dataName: 'records',
+          countName: 'total'
+        }"
+      >
+        <template v-slot:code="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            @click="handleAddOrEdit(row, 'view')"
+          >
+            {{ row.code }}
+          </el-link>
+        </template>
+      </ele-pro-table>
+    </el-card>
+
+    <add-or-edit-dialog
+      ref="addOrEditDialogRef"
+      @done="reload"
+    ></add-or-edit-dialog>
+  </div>
+</template>
+
+<script>
+  import tabMixins from '@/mixins/tableColumnsMixin';
+  import addOrEditDialog from './components/addOrEditDialog.vue';
+  import searchTable from './components/searchTable.vue';
+  // import { getList, del } from '@/api/marketManagem/index';
+  import { selectProcessTotalDuration } from '@/api/productionScheduling/productionShift';
+  export default {
+    mixins: [tabMixins],
+    components: {
+      addOrEditDialog,
+      searchTable
+    },
+    data() {
+      return {
+        // 加载状态
+        loading: false,
+        delVisible: false,
+        selection: [],
+        processSubmitDialogFlag: false,
+        cacheKeyUrl: 'main-productionScheduling-totalProcessTime-index',
+        columns: [
+          // {
+          //   width: 45,
+          //   type: 'selection',
+          //   columnKey: 'selection',
+          //   align: 'center',
+          //   selectable: (row, index) => {
+          //     return row.reviewStatus != 1 && row.reviewStatus != 2;
+          //   }
+          // },
+          {
+            width: 60,
+            label: '序号',
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          },
+          {
+            minWidth: 180,
+            prop: 'code',
+            label: '编码',
+            align: 'center',
+            slot: 'code',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 180,
+            prop: 'name',
+            label: '名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 130,
+            prop: 'brandNo',
+            label: '牌号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'modelType',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 200,
+            prop: 'bomCategory',
+            label: 'BOM分类',
+            align: 'center',
+            showOverflowTooltip: true,
+            formatter: (row, column) => {
+              return row.bomCategory == 2
+                ? 'MBOM'
+                : row.bomCategory == 3
+                ? 'ABOM'
+                : '';
+            }
+          },
+          {
+            minWidth: 150,
+            prop: 'bomVersion',
+            label: 'BOM版本',
+            align: 'center',
+            formatter: (row, column) => {},
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'productionLineName',
+            label: '工艺路线',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'priority',
+            label: '优先顺序',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'factoriesName',
+            label: '工厂名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'totalTime',
+            label: '总时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 100,
+            prop: 'updateUserName',
+            label: '设置人',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'updateTime',
+            label: '设置时间',
+            align: 'center',
+            showOverflowTooltip: true
+          }
+        ]
+      };
+    },
+    computed: {
+      columns() {
+        return [];
+      }
+    },
+    created() {},
+    methods: {
+      //新增、修改
+      handleAddOrEdit(row = {}, type) {
+        this.$nextTick(() => {
+          this.$refs.addOrEditDialogRef.open(row);
+        });
+      },
+
+      /* 表格数据源 */
+      datasource({ page, limit, where, order }) {
+        return selectProcessTotalDuration({
+          pageNum: page,
+          size: limit,
+          ...where
+        });
+      },
+      submit(res) {
+        this.processSubmitDialogFlag = true;
+        this.$nextTick(() => {
+          let params = {
+            businessId: res.id,
+            businessKey: 'market_management_approve',
+            formCreateUserId: res.createUserId,
+            pcHandle: '/bpm/handleTask/components/marketManagem/submit.vue',
+            pcView: '/bpm/handleTask/components/marketManagem/view.vue',
+            variables: {
+              businessCode: res.code,
+              businessName: res.name,
+              businessType: res.type
+            }
+          };
+          this.$refs.processSubmitDialogRef.init(params);
+        });
+      },
+      /* 刷新表格 */
+      reload(where = {}) {
+        this.$refs.table.reload({ page: 1, where });
+      },
+
+      remove(row) {
+        del(row).then((res) => {
+          this.$message.success('删除成功!');
+          this.reload();
+        });
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-link--inner) {
+    margin-left: 0px !important;
+  }
+
+  .sys-organization-list {
+    height: calc(100vh - 264px);
+    box-sizing: border-box;
+    border-width: 1px;
+    border-style: solid;
+    overflow: auto;
+  }
+
+  .sys-organization-list :deep(.el-tree-node__content) {
+    height: 40px;
+
+    & > .el-tree-node__expand-icon {
+      margin-left: 10px;
+    }
+  }
+</style>

+ 88 - 0
src/views/productionScheduling/workCalendar/components/searchTable.vue

@@ -0,0 +1,88 @@
+<!-- 搜索表单 -->
+<template>
+  <seekPage :seekList="seekList" :formLength="3" @search="search"></seekPage>
+</template>
+<script>
+  import { getFactoryworkstation } from '@/api/factoryModel';
+  export default {
+    data() {
+      return { workStationList: [] };
+    },
+    props: {
+      productionLineList: {
+        type: Array,
+        default: () => []
+      },
+      // workStationList: {
+      //   type: Array,
+      //   default: () => []
+      // },
+      productionShiftList: {
+        type: Array,
+        default: () => []
+      }
+    },
+    computed: {
+      // 表格列配置
+      seekList() {
+        return [
+          {
+            label: '产线名称:',
+            value: 'productionLineId',
+            type: 'select',
+            planList: this.productionLineList.map((item) => {
+              return {
+                value: item.id,
+                label: item.name
+              };
+            }),
+            placeholder: '请选择 '
+          },
+          {
+            label: '工位名称:',
+            value: 'workStationId',
+            type: 'select',
+            planList: this.workStationList.map((item) => {
+              return {
+                value: item.id,
+                label: item.name
+              };
+            }),
+            placeholder: '请选择 '
+          },
+          {
+            label: '生产班次:',
+            value: 'productionShift',
+            type: 'select',
+            planList: this.productionShiftList.map((item) => {
+              return {
+                value: item.code,
+                label: item.shiftName
+              };
+            }),
+            placeholder: '请选择 '
+          }
+        ];
+      }
+    },
+    created() {
+      this.getFactoryworkstation();
+    },
+    methods: {
+      /* 获取工位 */
+      async getFactoryworkstation() {
+        const data = await getFactoryworkstation({
+          pageNum: 1,
+          size: 999
+        });
+        this.workStationList = data.list || [];
+      },
+      /* 搜索 */
+      search(e) {
+        this.$emit('search', {
+          ...e
+        });
+      }
+    }
+  };
+</script>

+ 473 - 0
src/views/productionScheduling/workCalendar/components/timeTable.vue

@@ -0,0 +1,473 @@
+<template>
+  <ele-modal
+    custom-class="ele-dialog-form long-dialog-form"
+    :centered="true"
+    :visible.sync="addOrEditDialogFlag"
+    :title="title"
+    :append-to-body="false"
+    :close-on-click-modal="false"
+    width="80%"
+    :before-close="cancel"
+    :maxable="true"
+    :resizable="true"
+  >
+    <el-form
+      ref="form"
+      :model="form"
+      :rules="rules"
+      label-width="140px"
+      class="el-form-box"
+    >
+      <div>
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-plus"
+          class="ele-btn-icon"
+          @click="handleAddOrEdit()"
+        >
+          新增行
+        </el-button>
+        <el-button
+          size="small"
+          type="primary"
+          class="ele-btn-icon"
+          @click="save"
+        >
+          保存
+        </el-button>
+      </div>
+      <ele-pro-table
+      style="margin-top: 5px;"
+        :columns="columns"
+        :datasource="item.list"
+        v-for="(item, index) in form.list"
+        :ref="'table' + index"
+        height="400px"
+        full-height="calc(100vh - 116px)"
+        @cell-click="cellClick"
+        :needPage="false"
+      >
+        <template v-slot:checked="{ row }">
+          <el-radio class="radio" v-model="radio" :label="row.key">
+            <i></i>
+          </el-radio>
+        </template>
+
+        <template v-slot:dutyName="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + index + '.list.' + $index + '.dutyName'"
+            :rules="[
+              {
+                required: true,
+                message: '出勤名称不能为空',
+                trigger: 'blur'
+              }
+            ]"
+          >
+            <DictSelection
+              dictName="班次出勤名称"
+              clearable
+              v-model="row.dutyName"
+              filterable
+              valueName="dictValue"
+              allow-create
+              default-first-option
+              :isOne="$index === 0"
+            >
+            </DictSelection>
+          </el-form-item>
+        </template>
+
+        <template v-slot:timeType="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + index + '.list.' + $index + '.timeType'"
+            :rules="[
+              {
+                required: true,
+                message: '请选择时间类型',
+                trigger: 'blur'
+              }
+            ]"
+          >
+            <el-select v-model="row.timeType" clearable style="width: 100%">
+              <el-option :value="'1'" label="工作时间"></el-option>
+              <el-option :value="'2'" label="休息时间"></el-option>
+            </el-select>
+          </el-form-item>
+        </template>
+        <template v-slot:startTime="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + index + '.list.' + $index + '.startTime'"
+            :rules="[
+              {
+                required: true,
+                message: '请选择开始时间',
+                trigger: 'blur'
+              }
+            ]"
+          >
+            <el-time-select
+              placeholder="开始时间"
+              v-model="row.startTime"
+              style="width: 100%"
+              :disabled="!!item.list[$index - 1]?.endTime"
+              @change="changeTime(row, $index, index, 'startTime')"
+              :picker-options="{
+                ...defTime,
+                ...(item.list[$index - 1]
+                  ? {
+                      minTime: item.list[$index - 1].endTime
+                    }
+                  : {})
+              }"
+            >
+            </el-time-select>
+          </el-form-item>
+        </template>
+        <template v-slot:endTime="{ row, $index }">
+          <el-form-item
+            label-width="0"
+            :prop="'list.' + index + '.list.' + $index + '.endTime'"
+            :rules="[
+              {
+                required: true,
+                message: '请选择结束时间',
+                trigger: 'blur'
+              }
+            ]"
+          >
+            <el-time-select
+              placeholder="结束时间"
+              v-model="row.endTime"
+              style="width: 100%"
+              @change="changeTime(row, $index, index, 'endTime')"
+              :picker-options="{
+                ...defTime,
+                ...{
+                  minTime: row.startTime
+                }
+              }"
+            >
+            </el-time-select>
+          </el-form-item>
+        </template>
+
+        <template v-slot:columnRequired="{ column }">
+          <span class="is-required">{{ column.label }}</span>
+        </template>
+        <template v-slot:action="{ row, $index }">
+          <el-popconfirm
+            class="ele-action"
+            title="确定要删除此信息吗?"
+            @confirm="remove($index, index)"
+          >
+            <template v-slot:reference>
+              <el-link type="danger" :underline="false" icon="el-icon-delete">
+                删除
+              </el-link>
+            </template>
+          </el-popconfirm>
+        </template>
+      </ele-pro-table>
+    </el-form>
+    <div slot="footer">
+      <el-button type="primary" @click="save()" v-lodading="loading"
+        >确定</el-button
+      >
+
+      <el-button @click="cancel">返回</el-button>
+    </div>
+  </ele-modal>
+</template>
+<script>
+  const defForm = {
+    list: [] //表格数据
+  };
+  import {
+    getTApsWorkdayCalendarMaster,
+    saveOrUpdate
+  } from '@/api/productionScheduling/workCalendar';
+  export default {
+    data() {
+      return {
+        addOrEditDialogFlag: false,
+        loading: false,
+        current: {},
+        radio: '',
+        defTime: {
+          step: '00:01',
+          start: '00:00',
+          end: '23:59'
+        },
+        columns: [
+          {
+            slot: 'checked',
+            align: 'center',
+            label: '选择',
+            reserveSelection: true
+          },
+          {
+            minWidth: 150,
+            prop: 'dayDate',
+            label: '日期',
+            align: 'center',
+            slot: 'code',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 180,
+            prop: 'productionLineName',
+            label: '产线名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'workCenterName',
+            label: '工作中心',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 130,
+            prop: 'workStationName',
+            label: '工位名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'productionShift',
+            label: '生产班次',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            width: 60,
+            type: 'index',
+            columnKey: 'index',
+            label: '序号',
+            align: 'center',
+            fixed: 'left'
+          },
+
+          {
+            prop: 'dutyName',
+            slot: 'dutyName',
+            headerSlot: 'columnRequired',
+            label: '出勤名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 180
+          },
+          {
+            slot: 'timeType',
+            prop: 'timeType',
+            headerSlot: 'columnRequired',
+            label: '时间类型',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'startTime',
+            label: '开始时间',
+            align: 'center',
+            headerSlot: 'columnRequired',
+            slot: 'startTime',
+            showOverflowTooltip: true,
+            minWidth: 170
+          },
+          {
+            prop: 'endTime',
+            minWidth: 170,
+            headerSlot: 'columnRequired',
+            slot: 'endTime',
+            label: '结束时间',
+            align: 'center'
+          },
+          {
+            width: 160,
+            prop: 'totalDuration',
+            label: '总时长(分钟)',
+            align: 'center'
+          },
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 150,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true,
+            fixed: 'right'
+          }
+        ],
+        selection: [],
+
+        form: { ...defForm },
+        rules: {},
+        title: '新增'
+      };
+    },
+    mounted() {},
+    created() {},
+
+    methods: {
+      //初始化
+      async init(ids = [], type) {
+        this.addOrEditDialogFlag = true;
+        this.title = type == 'add' ? '新增' : type == 'edit' ? '修改' : '详情';
+        let row = await getTApsWorkdayCalendarMaster(ids);
+        row.forEach((item, index) => {
+          item.list.forEach((item1, index1) => {
+            item1.key = item.dayDate + index + '' + index1;
+            item1.parentKey = index;
+          });
+        });
+        this.form.list = JSON.parse(JSON.stringify(row));
+      },
+      handleAddOrEdit() {
+        if (!this.current.endTime) {
+          return this.$message.error('请先选择结束时间');
+        }
+        let parentIndex = this.current.parentKey;
+        let index = this.form.list[parentIndex].list.findIndex(
+          (item) => item.key === this.current.key
+        );
+        let data = JSON.parse(JSON.stringify(this.current));
+        //先删除后面的数据在插入
+        this.removeFormtime(index, parentIndex, data);
+        this.form.list[parentIndex].list.splice(index + 1, 0, {
+          dayDate: data.dayDate,
+          dutyName: '',
+          endTime: '',
+          key: '',
+          parentKey: '',
+          productionLineId: data.productionLineId,
+          productionLineName: data.productionLineName,
+          productionShift: data.productionShift,
+          startTime: data.endTime,
+          timeType: '1',
+          totalDuration: ''
+        });
+        this.keyInit();
+      },
+      //key初始化
+      keyInit() {
+        this.form.list.forEach((item, index) => {
+          item.list.forEach((item1, index1) => {
+            this.$set(
+              this.form.list[index].list[index1],
+              'key',
+              item1.dayDate + index + '' + index1
+            );
+            this.$set(this.form.list[index].list[index1], 'parentKey', index);
+          });
+        });
+      },
+      changeTime(row, index, parentIndex, type) {
+        if (type == 'startTime') {
+          this.$set(this.form.list[parentIndex].list[index], 'endTime', '');
+        }
+        if (type == 'endTime') {
+          if (!row.startTime) {
+            this.$set(this.form.list[parentIndex].list[index], 'endTime', '');
+            this.$message.error('请先选择开始时间');
+            return;
+          }
+        }
+        this.removeFormtime(index, parentIndex, row);
+        if (row.startTime && row.endTime) {
+          let val =
+            (new Date('2025-01-01 ' + row.endTime).getTime() -
+              new Date('2025-01-01 ' + row.startTime).getTime()) /
+            60000;
+
+          this.$set(
+            this.form.list[parentIndex].list[index],
+            'totalDuration',
+            val
+          );
+        } else {
+          this.$set(
+            this.form.list[parentIndex].list[index],
+            'totalDuration',
+            0
+          );
+        }
+      },
+      removeFormtime(i, parentIndex, row) {
+        this.form.list[parentIndex].list.splice(
+          i + 1,
+          this.form.list[parentIndex].list.length - i
+        );
+      },
+      remove(index, parentIndex) {
+        if (index != 0) {
+          this.removeFormtime(index,parentIndex);
+        }
+        this.form.list[parentIndex].list.splice(index, 1);
+      },
+      setFormtime() {
+        this.form.totalWorkingMinutes = 0;
+        this.form.restDurationMinutes = 0;
+        this.form.list.forEach((v) => {
+          v.workingHours = 0;
+          v.restTime = 0;
+          v.list.forEach((v1) => {
+            if (v1.timeType == '1') {
+              v.workingHours += v1.totalDuration;
+            } else {
+              v.restTime += v1.totalDuration;
+            }
+          });
+        });
+      },
+
+      getValidate() {
+        let arr = [];
+        this.form.list.forEach((item, index) => {
+          arr.push(
+            new Promise((resolve, reject) => {
+              console.log(this.$refs['form' + index]);
+            })
+          );
+        });
+
+        return Promise.all(arr);
+      },
+      save() {
+        this.$refs.form.validate((valid) => {
+          if (valid) {
+            this.setFormtime();
+            saveOrUpdate(this.form.list).then((res) => {
+              this.$message.success('保存成功');
+              this.$emit('reload');
+              this.cancel();
+            });
+          }
+        });
+      },
+      // 单击获取id
+      cellClick(row) {
+        this.current = row;
+        this.radio = row.key;
+      },
+      //关闭弹窗
+      cancel() {
+        this.addOrEditDialogFlag = false;
+        this.form = {
+          ...defForm
+        };
+        this.form.list = [];
+      }
+    }
+  };
+</script>
+<style scoped lang="scss"></style>

+ 739 - 0
src/views/productionScheduling/workCalendar/index copy 2.vue

@@ -0,0 +1,739 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never" v-loading="loading">
+      <el-form
+        ref="form"
+        :model="form"
+        :rules="{}"
+        label-width="120px"
+        class="el-form-box"
+      >
+        <div style="display: flex">
+          <el-form-item label="工厂名称:" prop="factoriesId">
+            <el-select
+              v-model="form.factoriesId"
+              placeholder="请选择"
+              @change="change_factoryId"
+            >
+              <el-option
+                v-for="item in factoriesList"
+                :label="item.factoryName"
+                :value="item.factoryId"
+                :key="item.factoryId"
+                @click.native="form.factoriesName = item.factoryName"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="班组名称:" prop="teamId">
+            <el-select v-model="form.teamId" placeholder="请选择">
+              <el-option
+                v-for="item in teamList"
+                :label="item.name"
+                :value="item.id"
+                :key="item.id"
+                @click.native="teamChange(item)"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="产线名称:" prop="productionLineId">
+            <el-select v-model="form.productionLineId" placeholder="请选择">
+              <el-option
+                v-for="item in productionLineList"
+                :label="item.name"
+                :value="item.id"
+                @click.native="form.productionLineName = item.name"
+                :key="item.id"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button
+              type="primary"
+              icon="el-icon-search"
+              class="ele-btn-icon"
+              @click="search"
+            >
+              查询
+            </el-button>
+            <el-button @click="reset" icon="el-icon-refresh-left"
+              >重置</el-button
+            >
+          </el-form-item>
+        </div>
+      </el-form>
+      <div style="display: flex">
+        <div class="calendar-container">
+          <el-calendar v-model="timeValue">
+            <template #dateCell="{ date, data }">
+              <div
+                class="calendar-day"
+                @click="handleDateClick(date)"
+                :class="{ 'selected-date': isSelected(date) }"
+              >
+                <div class="datetext">{{
+                  data.day.split('-').slice(2).join('-')
+                }}</div>
+                <div class="lunar-date">{{ lunarDate(date) }}</div>
+              </div>
+            </template>
+          </el-calendar>
+        </div>
+        <el-form ref="form" :rules="{}" label-width="300px" class="el-form-box">
+          <el-form-item label="生产班次" prop="code">
+            <el-select
+              v-model="productionShift"
+              multiple
+              placeholder="请选择"
+              style="width: calc(100% - 75px)"
+            >
+              <el-option
+                v-for="item in productionShiftList"
+                :key="item.code"
+                :label="item.shiftName"
+                :value="item.code"
+              >
+              </el-option>
+            </el-select>
+            <el-button
+              size="small"
+              type="primary"
+              icon="el-icon-plus"
+              class="ele-btn-icon"
+              @click="selectProductionShiftList"
+              style="margin-left: 5px"
+            >
+              适用
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div>
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-plus"
+          class="ele-btn-icon"
+          @click="handleAddOrEdit()"
+        >
+          新增行
+        </el-button>
+        <el-button
+          size="small"
+          type="primary"
+          class="ele-btn-icon"
+          @click="save"
+        >
+          保存
+        </el-button>
+      </div>
+
+      <!-- 数据表格 -->
+      <ele-pro-table
+        v-for="(item, index) in list"
+        :ref="'table' + index"
+        :columns="columns"
+        :datasource="item.list"
+        height="300px"
+        full-height="calc(100vh - 116px)"
+        tool-class="ele-toolbar-form"
+        :selection.sync="selection"
+        @columns-change="handleColumnChange"
+        :cache-key="cacheKeyUrl"
+        :needPage="false"
+         
+        @cell-click="cellClick"
+        style="margin-top: 10px"
+        row-key="key"
+      >
+       <template v-slot:toolbar >
+         班次名称
+       </template>
+        <template v-slot:action="{ row }">
+          <el-radio class="radio" v-model="radio" :label="row.key">
+            <i></i>
+          </el-radio>
+        </template>
+        <template v-slot:dutyName="{ row, $index }">
+          <DictSelection
+            dictName="班次出勤名称"
+            clearable
+            v-model="row.dutyName"
+            filterable
+            valueName="dictValue"
+            allow-create
+            default-first-option
+            :isOne="$index === 0"
+          >
+          </DictSelection>
+        </template>
+        <template v-slot:timeType="{ row, $index }">
+          <el-select
+            v-model="row.timeType"
+            clearable
+            style="width: 100%"
+            @change="setFormtime"
+          >
+            <el-option :value="'1'" label="工作时间"></el-option>
+            <el-option :value="'2'" label="休息时间"></el-option>
+          </el-select>
+        </template>
+        <template v-slot:startTime="{ row, $index }">
+          <el-time-select
+            placeholder="开始时间"
+            v-model="row.startTime"
+            style="width: 100%"
+            :disabled="
+              item.list[$index - 1]?.endTime &&
+              item.list[$index - 1]?.dayDate == row.dayDate
+            "
+            @change="changeTime(row, $index, index, 'startTime')"
+            :picker-options="{
+              ...defTime,
+              ...(item.list[$index - 1]
+                ? {
+                    minTime:
+                      item.list[$index - 1].dayDate == row.dayDate
+                        ? item.list[$index - 1].endTime
+                        : ''
+                  }
+                : {})
+            }"
+          >
+          </el-time-select>
+        </template>
+        <template v-slot:endTime="{ row, $index }">
+          <el-time-select
+            placeholder="结束时间"
+            v-model="row.endTime"
+            style="width: 100%"
+            @change="changeTime(row, $index, index, 'endTime')"
+            :picker-options="{
+              ...defTime,
+              ...{
+                minTime: row.startTime
+              }
+            }"
+          >
+          </el-time-select>
+        </template>
+
+        <template v-slot:note="{ row }">
+          <el-input clearable v-model="row.note" placeholder="请输入" />
+        </template>
+      </ele-pro-table>
+    </el-card>
+
+    <!-- <add-or-edit-dialog
+      ref="addOrEditDialogRef"
+      @done="reload"
+    ></add-or-edit-dialog> -->
+
+    <!-- <process-submit-dialog
+      :processSubmitDialogFlag.sync="processSubmitDialogFlag"
+      v-if="processSubmitDialogFlag"
+      ref="processSubmitDialogRef"
+      @reload="reload"
+      :isNotNeedProcess="false"
+    ></process-submit-dialog> -->
+  </div>
+</template>
+
+<script>
+  import tabMixins from '@/mixins/tableColumnsMixin';
+  // import addOrEditDialog from './components/addOrEditDialog.vue';
+  import { getTableList } from '@/api/productionScheduling/workCalendar';
+  import {
+    getTableList as getProductionShift,
+    selectProductionShiftList
+  } from '@/api/productionScheduling/productionShift';
+  // import processSubmitDialog from '@/components/processSubmitDialog/processSubmitDialog.vue';
+  import { solarToLunar } from 'lunar-calendar';
+  import { getFactoryarea } from '@/api/factoryModel';
+  import { getteampage } from '@/api/workforceManagement/team.js';
+  import dayjs from 'dayjs';
+  import HeaderTools from '@/layout/components/header-tools.vue';
+  import r from 'highlight.js/lib/languages/r';
+
+  export default {
+    mixins: [tabMixins],
+    components: {
+      // processSubmitDialog,
+      // addOrEditDialog
+    },
+    data() {
+      return {
+        // 加载状态
+        loading: false,
+        delVisible: false,
+        current: {},
+        radio: '',
+        timeValue: new Date(),
+        selectedDates: [new Date()],
+        factoriesList: [],
+        productionShiftList: [],
+        productionShift: [],
+        teamList: [],
+        productionLineList: [],
+        defTime: {
+          step: '00:01',
+          start: '00:00',
+          end: '23:59'
+        },
+        form: {
+          factoriesId: '',
+          factoriesName: '',
+          teamId: '',
+          teamName: '',
+          productionLineId: '',
+          productionLineName: ''
+        },
+        list: [],
+        selection: [],
+        processSubmitDialogFlag: false,
+        cacheKeyUrl: 'main-productionScheduling-workCalendar-index',
+        columns: [
+          {
+            action: 'action',
+            slot: 'action',
+            align: 'center',
+            label: '选择',
+            reserveSelection: true
+          },
+          {
+            minWidth: 150,
+            prop: 'dayDate',
+            label: '日期',
+            align: 'center',
+            slot: 'code',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 180,
+            prop: 'productionLineName',
+            label: '产线名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'workCenterName',
+            label: '工作中心',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 130,
+            prop: 'workStationName',
+            label: '工位名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'productionShift',
+            label: '生产班次',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            width: 60,
+            label: '序号',
+            type: 'index',
+            columnKey: 'index',
+            align: 'center'
+          },
+          {
+            minWidth: 150,
+            prop: 'dutyName',
+            slot: 'dutyName',
+            label: '出勤名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 120,
+            prop: 'timeType',
+            slot: 'timeType',
+            label: '类型',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'startTime',
+            slot: 'startTime',
+            label: '开始时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            slot: 'endTime',
+            prop: 'endTime',
+            label: '结束时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'totalDuration',
+            label: '总时长(分钟)',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'updateUserName',
+            label: '设置人',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'updateTime',
+            label: '设置时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'note',
+            slot: 'note',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true
+          }
+        ]
+      };
+    },
+
+    created() {
+      this.getFactory();
+      this.getProductionShift();
+    },
+    methods: {
+      handleAddOrEdit() {
+        if (!this.current.endTime) {
+          return this.$message.error('请先选择结束时间');
+        }
+        let parentIndex = this.current.parentKey;
+        let index = this.list[parentIndex].list.findIndex(
+          (item) => item.key === this.current.key
+        );
+        let data = JSON.parse(JSON.stringify(this.current));
+        //先删除后面的数据在插入
+        this.removeFormtime(index, parentIndex, data);
+        this.list[parentIndex].list.splice(index + 1, 0, {
+          dayDate: data.dayDate,
+          dutyName: '',
+          endTime: '',
+          key: '',
+          productionLineId: data.productionLineId,
+          productionLineName: data.productionLineName,
+          productionShift: data.productionShift,
+          startTime: data.endTime,
+          timeType: '1',
+          totalDuration: ''
+        });
+        this.keyInit();
+      },
+      //key初始化
+      keyInit() {
+        this.list.forEach((item, index) => {
+          this.$set(this.list[index], 'key', index);
+          item.list.forEach((item1, index1) => {
+            this.$set(
+              this.list[index].list[index1],
+              'key',
+              item1.dayDate + index + '' + index1
+            );
+            this.$set(this.list[index].list[index1], 'parentKey', index);
+          });
+        });
+      },
+      async search() {
+        let data = await getTableList({ ...this.form });
+        this.list = data;
+      },
+      reset() {
+        this.form = {
+          factoriesId: '',
+          factoriesName: '',
+          teamId: '',
+          teamName: '',
+          productionLineId: '',
+          productionLineName: ''
+        };
+      },
+      //获取班次
+      getProductionShift() {
+        getProductionShift({
+          pageNum: 1,
+          size: 999
+        }).then((res) => {
+          this.productionShiftList = res.records;
+        });
+      },
+      handleDateClick(date) {
+        const dateStr = date.toISOString().split('T')[0];
+        const index = this.selectedDates.findIndex(
+          (d) => d.toISOString().split('T')[0] === dateStr
+        );
+        if (index === -1) {
+          this.selectedDates.push(date);
+        } else {
+          this.selectedDates.splice(index, 1);
+        }
+      },
+      isSelected(date) {
+        const dateStr = date.toISOString().split('T')[0];
+        return this.selectedDates.some(
+          (d) => d.toISOString().split('T')[0] === dateStr
+        );
+      },
+      formatDate(date) {
+        return date.toISOString().split('T')[0];
+      },
+      removeDate(index) {
+        this.selectedDates.splice(index, 1);
+      },
+      lunarDate(date) {
+        const lunar = solarToLunar(
+          date.getFullYear(),
+          date.getMonth() + 1,
+          date.getDate()
+        );
+        return lunar.lunarDay === 1
+          ? lunar.lunarMonthName + '月'
+          : lunar.lunarDayName; // 显示农历(初一显示月份)
+      },
+
+      /* 刷新表格 */
+      reload(where = {}) {
+        this.$refs.table.reload({ page: 1, where });
+      },
+
+      /* 获取工厂 */
+      async getFactory() {
+        const { list } = await getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 1,
+          enable: 1
+        });
+        this.factoriesList = list || [];
+      },
+      /* 获取班组 */
+      async getTeam() {
+        const { list } = await getteampage({
+          pageNum: 1,
+          size: 999,
+          enable: 1,
+          factoryId: this.form.factoriesId
+        });
+        this.teamList = list || [];
+      },
+      /* 获取产线 */
+      async getProductionLine() {
+        const { list } = await getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 4
+        });
+        this.productionLineList =
+          list.filter((item) => item.factoryId == this.form.factoriesId) || [];
+      },
+      change_factoryId() {
+        this.form.teamId = '';
+        this.form.productionLineId = '';
+        this.form.productionLineName = '';
+        this.form.teamName = '';
+        this.getTeam();
+        this.getProductionLine();
+      },
+      teamChange(item) {
+        this.form.teamName = item.name;
+        this.form.workCenterId = item.workCenterIds?.[0];
+        this.form.workCenterName = item.workCenterNames;
+      },
+      changeTime(row, index, parentIndex, type) {
+        if (type == 'startTime') {
+          this.$set(this.list[parentIndex].list[index], 'endTime', '');
+        }
+        if (type == 'endTime') {
+          if (!row.startTime) {
+            this.$set(this.list[parentIndex].list[index], 'endTime', '');
+            this.$message.error('请先选择开始时间');
+            return;
+          }
+        }
+        console.log(this.list[parentIndex], 'this.list[parentIndex][index]');
+        this.removeFormtime(index, parentIndex, row);
+        if (row.startTime && row.endTime) {
+          let val =
+            (new Date('2025-01-01 ' + row.endTime).getTime() -
+              new Date('2025-01-01 ' + row.startTime).getTime()) /
+            60000;
+
+          this.$set(this.list[parentIndex].list[index], 'totalDuration', val);
+        } else {
+          this.$set(this.list[parentIndex].list[index], 'totalDuration', 0);
+        }
+        // this.setFormtime();
+      },
+      removeFormtime(i, parentIndex, row) {
+        let indexs = [];
+        //找到日期相同的并且在这条数据之后的
+        this.list[parentIndex].list.forEach((item, index) => {
+          if (i < index && row.dayDate == item.dayDate) {
+            indexs.push(index);
+          }
+        });
+
+        if (indexs.length) {
+          this.list[parentIndex].list.splice(i + 1, indexs.length);
+        }
+      },
+      setFormtime() {
+        if (this.form.list?.length) {
+          this.form.attendanceStartTime = this.form.list[0].startTime;
+          this.form.attendanceEndTime =
+            this.form.list[this.form.list.length - 1].endTime;
+          this.form.totalWorkingMinutes = 0;
+          this.form.restDurationMinutes = 0;
+          this.form.list.forEach((v) => {
+            if (v.timeType == '1') {
+              this.form.totalWorkingMinutes += v.totalDuration;
+            } else {
+              this.form.restDurationMinutes += v.totalDuration;
+            }
+          });
+        }
+      },
+
+      async selectProductionShiftList() {
+        let data = await selectProductionShiftList(this.productionShift);
+        console.log(this.selectedDates, 'data');
+        this.list = data.map((item, index) => {
+          let dataValue = item.list.map((listItem, _index) => {
+            return {
+              key: index + '' + _index,
+              parentKey: index,
+              dayDate: '',
+              productionLineName: this.form.productionLineName,
+              productionLineId: this.form.productionLineId,
+              workCenterName: this.form.workCenterName,
+              workCenterId: this.form.workCenterId,
+              productionShift: item.shiftName,
+              dutyName: listItem.dutyName,
+              timeType: listItem.timeType,
+              startTime: listItem.startTime,
+              endTime: listItem.endTime,
+              totalDuration: listItem.totalDuration,
+              updateUserName: item.updateUserName,
+              updateTime: item.updateTime,
+              remark: listItem.remark
+            };
+          });
+          let list = [];
+          this.selectedDates.forEach((date) => {
+            list.push(
+              ...JSON.parse(
+                JSON.stringify(
+                  dataValue.map((dataValueItem) => {
+                    dataValueItem.dayDate = dayjs(date).format('YYYY-MM-DD');
+                    dataValueItem.key =
+                      dayjs(date).format('YYYY-MM-DD') + dataValueItem.key;
+                    return dataValueItem;
+                  })
+                )
+              )
+            );
+          });
+          return {
+            shiftName: item.shiftName,
+            key: index,
+            list
+          };
+        });
+
+        console.log(data, 'dasda');
+      },
+      // 单击获取id
+      cellClick(row) {
+        this.current = row;
+        this.radio = row.key;
+      },
+      save() {},
+      remove(row) {
+        del(row).then((res) => {
+          this.$message.success('删除成功!');
+          this.reload();
+        });
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-link--inner) {
+    margin-left: 0px !important;
+  }
+
+  .sys-organization-list {
+    height: calc(100vh - 264px);
+    box-sizing: border-box;
+    border-width: 1px;
+    border-style: solid;
+    overflow: auto;
+  }
+
+  .sys-organization-list :deep(.el-tree-node__content) {
+    height: 40px;
+
+    & > .el-tree-node__expand-icon {
+      margin-left: 10px;
+    }
+  }
+  :deep(.el-calendar__body) {
+    padding: 0;
+  }
+  .calendar-day {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+  }
+  .selected-date {
+    background: rgba($color: #409eff, $alpha: 0.2);
+  }
+  .selected-dates {
+    margin-top: 20px;
+  }
+  .calendar-container {
+    width: 480px;
+    height: 350px;
+  }
+  :deep(.el-calendar-day) {
+    padding: 0;
+    height: 40px;
+  }
+  :deep(.el-calendar-table)th:nth-last-child(-n + 2) {
+    color: #f56c6c; /* 深色文字 */
+  }
+  :deep(.el-calendar-table)td:nth-last-child(-n + 2) {
+    .datetext {
+      color: #f56c6c; /* 深色文字 */
+    }
+  }
+  :deep(.el-calendar-table)td.is-today {
+    color: #409eff !important; /* 深色文字 */
+  }
+  .lunar-date {
+    font-size: 10px;
+  }
+</style>

+ 681 - 0
src/views/productionScheduling/workCalendar/index.vue

@@ -0,0 +1,681 @@
+<template>
+  <div class="ele-body">
+    <el-card shadow="never" v-loading="loading">
+      <el-form
+        ref="form"
+        :model="form"
+        :rules="{}"
+        label-width="120px"
+        class="el-form-box"
+      >
+        <div style="display: flex">
+          <el-form-item label="工厂名称:" prop="factoriesId">
+            <el-select
+              v-model="form.factoriesId"
+              placeholder="请选择"
+              @change="change_factoryId"
+            >
+              <el-option
+                v-for="item in factoriesList"
+                :label="item.factoryName"
+                :value="item.factoryId"
+                :key="item.factoryId"
+                @click.native="form.factoriesName = item.factoryName"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="产线名称:" prop="productionLineId">
+            <el-select v-model="form.productionLineId" placeholder="请选择">
+              <el-option
+                v-for="item in productionLineList"
+                :label="item.name"
+                :value="item.id"
+                @click.native="change_productionLine(item)"
+                :key="item.id"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="工位名称:" prop="workStationId">
+            <el-select v-model="form.workStationId" placeholder="请选择">
+              <el-option
+                v-for="item in workStationList"
+                :label="item.name"
+                :value="item.id"
+                @click.native="form.workStationName = item.name"
+                :key="item.id"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <!-- <el-form-item>
+            <el-button
+              type="primary"
+              icon="el-icon-search"
+              class="ele-btn-icon"
+              @click="search"
+            >
+              查询
+            </el-button>
+            <el-button @click="reset" icon="el-icon-refresh-left"
+              >重置</el-button
+            >
+          </el-form-item> -->
+        </div>
+      </el-form>
+      <div style="display: flex">
+        <div class="calendar-container">
+          <el-calendar v-model="timeValue">
+            <template #dateCell="{ date, data }">
+              <div
+                class="calendar-day"
+                @click="handleDateClick(date)"
+                :class="{ 'selected-date': isSelected(date) }"
+              >
+                <div class="datetext">{{
+                  data.day.split('-').slice(2).join('-')
+                }}</div>
+                <div class="lunar-date">{{ lunarDate(date) }}</div>
+              </div>
+            </template>
+          </el-calendar>
+        </div>
+        <el-form ref="form" :rules="{}" label-width="300px" class="el-form-box">
+          <el-form-item label="生产班次" prop="code">
+            <el-select
+              v-model="productionShift"
+              multiple
+              placeholder="请选择"
+              style="width: calc(100% - 75px)"
+            >
+              <el-option
+                v-for="item in productionShiftList"
+                :key="item.code"
+                :label="item.shiftName"
+                :value="item.code"
+              >
+              </el-option>
+            </el-select>
+            <el-button
+              size="small"
+              type="primary"
+              icon="el-icon-plus"
+              class="ele-btn-icon"
+              @click="selectProductionShiftList"
+              style="margin-left: 5px"
+            >
+              适用
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div>
+        <!-- <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-plus"
+          class="ele-btn-icon"
+          @click="handleAddOrEdit()"
+        >
+          新增行
+        </el-button> -->
+        <el-button
+          size="small"
+          type="primary"
+          class="ele-btn-icon"
+          @click="edit()"
+        >
+          编辑
+        </el-button>
+      </div>
+      <searchTable
+        :productionLineList="productionLineList"
+        :productionShiftList="productionShiftList"
+        @search="search"
+      ></searchTable>
+
+      <!-- 数据表格 -->
+      <ele-pro-table
+        :ref="'table'"
+        :columns="columns"
+        :datasource="datasource"
+        height="600px"
+        full-height="calc(100vh - 116px)"
+        tool-class="ele-toolbar-form"
+        :selection.sync="selection"
+        @columns-change="handleColumnChange"
+        :cache-key="cacheKeyUrl"
+        style="margin-top: 10px"
+        row-key="id"
+      >
+        <template v-slot:action="{ row }">
+          <el-link
+            type="primary"
+            :underline="false"
+            icon="el-icon-edit"
+            @click="edit(row)"
+          >
+            修改
+          </el-link></template
+        >
+      </ele-pro-table>
+    </el-card>
+
+    <!-- <add-or-edit-dialog
+      ref="addOrEditDialogRef"
+      @done="reload"
+    ></add-or-edit-dialog> -->
+
+    <!-- <process-submit-dialog
+      :processSubmitDialogFlag.sync="processSubmitDialogFlag"
+      v-if="processSubmitDialogFlag"
+      ref="processSubmitDialogRef"
+      @reload="reload"
+      :isNotNeedProcess="false"
+    ></process-submit-dialog> -->
+    <timeTable ref="timeTableRef" @reload="reload"></timeTable>
+  </div>
+</template>
+
+<script>
+  import tabMixins from '@/mixins/tableColumnsMixin';
+  // import addOrEditDialog from './components/addOrEditDialog.vue';
+  import {
+    getTableList,
+    saveOrUpdate
+  } from '@/api/productionScheduling/workCalendar';
+  import {
+    getTableList as getProductionShift,
+    selectProductionShiftList
+  } from '@/api/productionScheduling/productionShift';
+  // import processSubmitDialog from '@/components/processSubmitDialog/processSubmitDialog.vue';
+  import timeTable from './components/timeTable.vue';
+  import { solarToLunar } from 'lunar-calendar';
+  import {
+    getFactoryarea,
+    getFactoryworkstation,
+    listFactoryLineByFactoryId,
+    listByProductionLineId
+  } from '@/api/factoryModel';
+  import searchTable from './components/searchTable.vue';
+  // import { getteampage } from '@/api/workforceManagement/team.js';
+  import dayjs from 'dayjs';
+
+  export default {
+    mixins: [tabMixins],
+    components: {
+      timeTable,
+      searchTable
+      // addOrEditDialog
+    },
+    data() {
+      return {
+        // 加载状态
+        loading: false,
+        delVisible: false,
+
+        timeValue: new Date(),
+        selectedDates: [new Date()],
+        factoriesList: [],
+        productionShiftList: [],
+        productionShift: [],
+        productionLineList: [],
+        workStationList: [],
+        selection: [],
+        defTime: {
+          step: '00:01',
+          start: '00:00',
+          end: '23:59'
+        },
+        form: {
+          factoriesId: '',
+          factoriesName: '',
+          productionLineId: '',
+          productionLineName: '',
+          workStationId: '',
+          workStationName: ''
+        },
+        params: {
+          workStationId: '',
+          productionLineId: '',
+          productionShift: ''
+        },
+        list: [],
+        processSubmitDialogFlag: false,
+        cacheKeyUrl: 'main-productionScheduling-workCalendar-index',
+        columns: [
+          {
+            label: '选择',
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            reserveSelection: true
+          },
+          {
+            minWidth: 150,
+            prop: 'dayDate',
+            label: '日期',
+            align: 'center',
+            slot: 'code',
+            showOverflowTooltip: true
+          },
+
+          {
+            minWidth: 180,
+            prop: 'productionLineName',
+            label: '产线名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'workCenterName',
+            label: '工作中心',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 130,
+            prop: 'workStationName',
+            label: '工位名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 180,
+            prop: 'productionShift',
+            label: '生产班次',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          // {
+          //   width: 60,
+          //   label: '序号',
+          //   type: 'index',
+          //   columnKey: 'index',
+          //   align: 'center'
+          // },
+          // {
+          //   minWidth: 150,
+          //   prop: 'dutyName',
+          //   slot: 'dutyName',
+          //   label: '出勤名称',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // },
+
+          // {
+          //   minWidth: 120,
+          //   prop: 'timeType',
+          //   slot: 'timeType',
+          //   label: '类型',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // },
+          // {
+          //   minWidth: 150,
+          //   prop: 'startTime',
+          //   slot: 'startTime',
+          //   label: '开始时间',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // },
+          // {
+          //   minWidth: 150,
+          //   slot: 'endTime',
+          //   prop: 'endTime',
+          //   label: '结束时间',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // },
+          {
+            minWidth: 150,
+            prop: 'workingHours',
+            label: '工作总时长(分钟)',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 150,
+            prop: 'restTime',
+            label: '休息时间',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 100,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true,
+            fixed: 'right'
+          }
+          // {
+          //   minWidth: 100,
+          //   prop: 'updateUserName',
+          //   label: '设置人',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // },
+          // {
+          //   minWidth: 100,
+          //   prop: 'updateTime',
+          //   label: '设置时间',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // },
+          // {
+          //   minWidth: 180,
+          //   prop: 'note',
+          //   slot: 'note',
+          //   label: '备注',
+          //   align: 'center',
+          //   showOverflowTooltip: true
+          // }
+        ]
+      };
+    },
+
+    created() {
+      this.getFactory();
+      this.getProductionShift();
+      this.form.factoriesId = this.$store.state.user.info.factoryId;
+      if (this.form.factoriesId) {
+        this.getProductionLine();
+      }
+    },
+    methods: {
+      edit(row) {
+        if (row?.id) {
+          this.$refs.timeTableRef.init([row?.id], 'edit');
+        } else {
+          this.$refs.timeTableRef.init(
+            this.selection.map((item) => item.id),
+            'edit'
+          );
+        }
+      },
+      /* 表格数据源 */
+      datasource({ page, limit, where, order }) {
+        if (this.selectedDates.length) {
+          where.dayDate = this.selectedDates.map(
+            (item) => item.toISOString().split('T')[0]
+          );
+        }
+        return getTableList({
+          pageNum: page,
+          size: limit,
+          ...where
+        });
+      },
+      async search(where) {
+        this.$refs.table.reload({ page: 1, where });
+      },
+
+      reset() {
+        this.form = {
+          factoriesId: '',
+          factoriesName: '',
+          productionLineId: '',
+          productionLineName: ''
+        };
+      },
+      //获取班次
+      getProductionShift() {
+        getProductionShift({
+          pageNum: 1,
+          size: 999
+        }).then((res) => {
+          this.productionShiftList = res.records;
+        });
+      },
+      handleDateClick(date) {
+        const dateStr = date.toISOString().split('T')[0];
+        const index = this.selectedDates.findIndex(
+          (d) => d.toISOString().split('T')[0] === dateStr
+        );
+        if (index === -1) {
+          this.selectedDates.push(date);
+        } else {
+          this.selectedDates.splice(index, 1);
+        }
+      },
+      isSelected(date) {
+        const dateStr = date.toISOString().split('T')[0];
+        return this.selectedDates.some(
+          (d) => d.toISOString().split('T')[0] === dateStr
+        );
+      },
+      formatDate(date) {
+        return date.toISOString().split('T')[0];
+      },
+      removeDate(index) {
+        this.selectedDates.splice(index, 1);
+      },
+      lunarDate(date) {
+        const lunar = solarToLunar(
+          date.getFullYear(),
+          date.getMonth() + 1,
+          date.getDate()
+        );
+        return lunar.lunarDay === 1
+          ? lunar.lunarMonthName + '月'
+          : lunar.lunarDayName; // 显示农历(初一显示月份)
+      },
+
+      /* 刷新表格 */
+      reload(where = {}) {
+        this.$refs.table.reload({ page: 1, where });
+      },
+
+      /* 获取工厂 */
+      async getFactory() {
+        const { list } = await getFactoryarea({
+          pageNum: 1,
+          size: 999,
+          type: 1,
+          enable: 1
+        });
+        this.factoriesList = list || [];
+      },
+
+      /* 获取产线 */
+      async getProductionLine() {
+        this.productionLineList = await listFactoryLineByFactoryId([
+          this.form.factoriesId
+        ]);
+      },
+      /* 获取工位 */
+      async getFactoryworkstation() {
+        this.workStationList = await listByProductionLineId(
+          this.form.productionLineId
+        );
+      },
+
+      change_factoryId() {
+        this.form.productionLineId = '';
+        this.form.productionLineName = '';
+        this.getProductionLine();
+      },
+      change_productionLine(item) {
+        this.form.workStationId = '';
+        this.form.workStationName = '';
+        this.form.productionLineName = item.name;
+        if (item.productionSchedulingDimension) {
+          this.getFactoryworkstation();
+        }
+      },
+      //根据排程维度获取初始化列表
+      async getInitList() {
+        // productionSchedulingDimension
+        let productionLineList = this.form.productionLineId
+          ? this.productionLineList.filter(
+              (item) => item.id == this.form.productionLineId
+            )
+          : this.productionLineList;
+        let promiseAll = [];
+
+        //获取工位
+        productionLineList
+          .filter((item) => item.productionSchedulingDimension == 1)
+          .forEach((item) => {
+            promiseAll.push(listByProductionLineId(item.id));
+          });
+        let workStationList = await Promise.all(promiseAll);
+        workStationList = workStationList.flat();
+
+        if (this.form.workStationId) {
+          workStationList = workStationList.filter(
+            (item) => item.id == this.form.workStationId
+          );
+        }
+
+        return [
+          ...workStationList.map((item) => {
+            item.productionLineName = productionLineList.find(
+              (item2) => item2.id == item.productionLineId
+            )?.name;
+            item.workStationName = item.name;
+            item.workStationId = item.id;
+            return item;
+          }),
+          ...productionLineList
+            .filter((item) => !item.productionSchedulingDimension)
+            .map((item) => {
+              item.productionLineName = item.name;
+              item.productionLineId = item.id;
+              return item;
+            })
+        ];
+      },
+      async selectProductionShiftList() {
+        if (!this.form.factoriesId) {
+          return this.$message.error('请选择工厂');
+        }
+        if (!this.productionLineList.length) {
+          return this.$message.error('产线不能为空');
+        }
+        if (!this.productionShift.length) {
+          return this.$message.error('请选择班次');
+        }
+        let data = await selectProductionShiftList(this.productionShift);
+        let list = [];
+        let initList = await this.getInitList();
+        initList.forEach((Lineitem) => {
+          data.forEach((dataItem) => {
+            list.push({
+              productionLineName: Lineitem.productionLineName,
+              productionLineId: Lineitem.productionLineId,
+              workStationName: Lineitem.workStationName,
+              workStationId: Lineitem.workStationId,
+              workCenterName: this.form.workCenterName,
+              workCenterId: this.form.workCenterId,
+              factoriesName: this.form.factoriesName,
+              factoriesId: this.form.factoriesId,
+              productionShift: dataItem.shiftName,
+              workingHours: dataItem.totalWorkingMinutes,
+              restTime: dataItem.restDurationMinutes,
+              list: dataItem.list.map((item) => {
+                item.id = '';
+                return item;
+              }),
+              dayDate: ''
+            });
+          });
+        });
+        this.list = this.selectedDates
+          .map((date) => {
+            list.forEach((listItem) => {
+              listItem.dayDate = dayjs(date).format('YYYY-MM-DD');
+              listItem.list.forEach((item1) => {
+                item1.dayDate = listItem.dayDate;
+                item1.productionLineId = listItem.productionLineId;
+                item1.productionLineName = listItem.productionLineName;
+                item1.productionShift = listItem.productionShift;
+                item1.workCenterId = listItem.workCenterId;
+                item1.workCenterName = listItem.workCenterName;
+                item1.workStationId = listItem.workStationId;
+                item1.workStationName = listItem.workStationName;
+                item1.factoriesName = this.form.factoriesName;
+                item1.factoriesId = this.form.factoriesId;
+              });
+            });
+            return JSON.parse(JSON.stringify(list));
+          })
+          .flat();
+        saveOrUpdate(this.list).then((res) => {
+          this.search({
+            productionLineId: this.form.productionLineId,
+            workStationId: this.form.workStationId
+          });
+        });
+      },
+
+      save() {}
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-link--inner) {
+    margin-left: 0px !important;
+  }
+
+  .sys-organization-list {
+    height: calc(100vh - 264px);
+    box-sizing: border-box;
+    border-width: 1px;
+    border-style: solid;
+    overflow: auto;
+  }
+
+  .sys-organization-list :deep(.el-tree-node__content) {
+    height: 40px;
+
+    & > .el-tree-node__expand-icon {
+      margin-left: 10px;
+    }
+  }
+  :deep(.el-calendar__body) {
+    padding: 0;
+  }
+  .calendar-day {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+  }
+  .selected-date {
+    background: rgba($color: #409eff, $alpha: 0.2);
+  }
+  .selected-dates {
+    margin-top: 20px;
+  }
+  .calendar-container {
+    width: 480px;
+    height: 350px;
+  }
+  :deep(.el-calendar-day) {
+    padding: 0;
+    height: 40px;
+  }
+  :deep(.el-calendar-table)th:nth-last-child(-n + 2) {
+    color: #f56c6c; /* 深色文字 */
+  }
+  :deep(.el-calendar-table)td:nth-last-child(-n + 2) {
+    .datetext {
+      color: #f56c6c; /* 深色文字 */
+    }
+  }
+  :deep(.el-calendar-table)td.is-today {
+    color: #409eff !important; /* 深色文字 */
+  }
+  .lunar-date {
+    font-size: 10px;
+  }
+</style>

+ 8 - 0
src/views/technology/production/index.vue

@@ -15,6 +15,7 @@
         :pageSize="this.$store.state.tablePageSize"
         @columns-change="handleColumnChange"
         :cache-key="cacheKeyUrl"
+        height="calc(100vh - 330px)"
       >
         <!-- 表头工具栏 -->
         <template v-slot:toolbar>
@@ -244,6 +245,13 @@
             showOverflowTooltip: true,
             minWidth: 110
           },
+          {
+            align: 'center',
+            prop: 'gui',
+            label: '固定节拍',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
           {
             prop: 'workCenterName',
             label: '所属工作中心',