|
|
@@ -14,8 +14,26 @@
|
|
|
:planList="planDataList"
|
|
|
:calendarSourceData="calendarSourceData"
|
|
|
></projectGantt>
|
|
|
- <div style="display: flex">
|
|
|
- <div class="form-wrapper" style="width: calc(100% - 600px)">
|
|
|
+ <div class="scheduling-content">
|
|
|
+ <div class="form-wrapper">
|
|
|
+ <div class="btnList">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="mini"
|
|
|
+ :disabled="entryReadonly"
|
|
|
+ @click="openPlanPicker(1, '添加生产计划')"
|
|
|
+ >
|
|
|
+ 添加生产计划
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="mini"
|
|
|
+ :disabled="entryReadonly"
|
|
|
+ @click="openPlanPicker(3, '添加临时计划')"
|
|
|
+ >
|
|
|
+ 添加临时计划
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
<!-- <div class="btnList">
|
|
|
<el-button type="primary" @click="confirm">保存</el-button>
|
|
|
<el-popconfirm
|
|
|
@@ -53,7 +71,7 @@
|
|
|
ref="planTable"
|
|
|
:columns="columns"
|
|
|
:datasource="planDataList"
|
|
|
- row-key="code"
|
|
|
+ row-key="id"
|
|
|
:selection.sync="selection"
|
|
|
@update:selection="handlePlanSelectionChange"
|
|
|
height="400px"
|
|
|
@@ -62,11 +80,10 @@
|
|
|
></ele-pro-table>
|
|
|
</div>
|
|
|
<div
|
|
|
- style="width: 600px"
|
|
|
v-loading="dotLineLoading"
|
|
|
:class="['plan-dot-line-side', { 'is-fullscreen': dotLineFullscreen }]"
|
|
|
>
|
|
|
- <div class="pane-box plan-dot-line-detail">
|
|
|
+ <div class="plan-dot-line-detail">
|
|
|
<div class="plan-time-bar">
|
|
|
<span class="plan-time-item">
|
|
|
<span class="plan-time-label">计划开始时间:</span>
|
|
|
@@ -81,7 +98,7 @@
|
|
|
<span class="plan-time-value">{{ planNumDisplay }}</span>
|
|
|
</span>
|
|
|
</div>
|
|
|
- <div class="plan-dot-line">
|
|
|
+ <div class="plan-dot-line-scrollable">
|
|
|
<div class="top-route">
|
|
|
<div class="panel-title">工艺路线</div>
|
|
|
<el-empty
|
|
|
@@ -108,6 +125,9 @@
|
|
|
<div class="panel-header">
|
|
|
<div class="panel-title">工艺配置</div>
|
|
|
<div class="panel-actions">
|
|
|
+ <span v-if="isCurrentPlanReadonly" class="readonly-tip">
|
|
|
+ 当前状态仅支持查看
|
|
|
+ </span>
|
|
|
<el-tooltip
|
|
|
:content="dotLineFullscreen ? '退出全屏' : '全屏展示'"
|
|
|
placement="top"
|
|
|
@@ -129,7 +149,9 @@
|
|
|
<el-button
|
|
|
type="primary"
|
|
|
size="mini"
|
|
|
- :disabled="!currentPlan || dotLineLoading"
|
|
|
+ :disabled="
|
|
|
+ !currentPlan || dotLineLoading || isCurrentPlanReadonly
|
|
|
+ "
|
|
|
@click="submitDotLineConfig"
|
|
|
>
|
|
|
确定
|
|
|
@@ -179,6 +201,7 @@
|
|
|
v-model="row.executionType"
|
|
|
placeholder="执行模式"
|
|
|
clearable
|
|
|
+ :disabled="isCurrentPlanReadonly"
|
|
|
class="config-table-control"
|
|
|
@change="onExecutionTypeChange(row)"
|
|
|
>
|
|
|
@@ -203,6 +226,7 @@
|
|
|
placeholder="请选择"
|
|
|
clearable
|
|
|
filterable
|
|
|
+ :disabled="isCurrentPlanReadonly"
|
|
|
class="config-table-control"
|
|
|
@change="onExecutionObjectChange(row)"
|
|
|
>
|
|
|
@@ -219,6 +243,7 @@
|
|
|
placeholder="请选择"
|
|
|
clearable
|
|
|
filterable
|
|
|
+ :disabled="isCurrentPlanReadonly"
|
|
|
class="config-table-control"
|
|
|
@change="onExecutionObjectChange(row)"
|
|
|
>
|
|
|
@@ -257,6 +282,7 @@
|
|
|
value-format="yyyy-MM-dd HH:mm:ss"
|
|
|
placeholder="执行开始时间"
|
|
|
clearable
|
|
|
+ :disabled="isCurrentPlanReadonly"
|
|
|
class="config-table-control"
|
|
|
@change="handleTimeChange(row, 'executionStartTime')"
|
|
|
/>
|
|
|
@@ -282,6 +308,7 @@
|
|
|
value-format="yyyy-MM-dd HH:mm:ss"
|
|
|
placeholder="执行结束时间"
|
|
|
clearable
|
|
|
+ :disabled="isCurrentPlanReadonly"
|
|
|
class="config-table-control"
|
|
|
@change="handleTimeChange(row, 'executionEndTime')"
|
|
|
/>
|
|
|
@@ -297,16 +324,98 @@
|
|
|
</div>
|
|
|
|
|
|
<div slot="footer">
|
|
|
- <el-button plain @click="cancel">取消</el-button>
|
|
|
+ <!-- <el-button plain @click="cancel">取消</el-button> -->
|
|
|
<el-button @click="cancel">关闭</el-button>
|
|
|
</div>
|
|
|
+
|
|
|
+ <ele-modal
|
|
|
+ :title="planPickerTitle"
|
|
|
+ :visible.sync="planPickerVisible"
|
|
|
+ append-to-body
|
|
|
+ width="88%"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :maxable="true"
|
|
|
+ >
|
|
|
+ <div class="plan-picker-search">
|
|
|
+ <div class="plan-picker-search__item">
|
|
|
+ <span class="plan-picker-search__label">计划编号:</span>
|
|
|
+ <el-input
|
|
|
+ v-model.trim="planPickerWhere.code"
|
|
|
+ size="mini"
|
|
|
+ clearable
|
|
|
+ placeholder="请输入"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="plan-picker-search__item">
|
|
|
+ <span class="plan-picker-search__label">产品编码:</span>
|
|
|
+ <el-input
|
|
|
+ v-model.trim="planPickerWhere.productCode"
|
|
|
+ size="mini"
|
|
|
+ clearable
|
|
|
+ placeholder="请输入"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="plan-picker-search__item">
|
|
|
+ <span class="plan-picker-search__label">状态:</span>
|
|
|
+ <el-select
|
|
|
+ v-model="planPickerWhere.status"
|
|
|
+ size="mini"
|
|
|
+ clearable
|
|
|
+ placeholder="请选择"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in planPickerStatusOptions"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ <div class="plan-picker-search__item">
|
|
|
+ <span class="plan-picker-search__label">批次号:</span>
|
|
|
+ <el-input
|
|
|
+ v-model.trim="planPickerWhere.batchNo"
|
|
|
+ size="mini"
|
|
|
+ clearable
|
|
|
+ placeholder="请输入"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="plan-picker-search__actions">
|
|
|
+ <el-button size="mini" type="primary" @click="handlePlanPickerSearch">
|
|
|
+ 搜索
|
|
|
+ </el-button>
|
|
|
+ <el-button size="mini" @click="resetPlanPickerSearch">重置</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-loading="planPickerLoading">
|
|
|
+ <ele-pro-table
|
|
|
+ ref="planPickerTable"
|
|
|
+ width="100%"
|
|
|
+ :columns="columns"
|
|
|
+ :datasource="planPickerDatasource"
|
|
|
+ row-key="id"
|
|
|
+ :selection.sync="planPickerSelection"
|
|
|
+ @update:selection="handlePlanPickerSelectionChange"
|
|
|
+ height="520px"
|
|
|
+ full-height="calc(100vh - 220px)"
|
|
|
+ :page-size="20"
|
|
|
+ ></ele-pro-table>
|
|
|
+ </div>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="planPickerVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmPlanPicker">确定</el-button>
|
|
|
+ </div>
|
|
|
+ </ele-modal>
|
|
|
</ele-modal>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import projectGantt from './gantt/project-gantt.vue';
|
|
|
// import dayjs from 'dayjs';
|
|
|
- import { teamSchedulingGanttChart } from '@/api/productionPlan/index.js';
|
|
|
+ import {
|
|
|
+ getList,
|
|
|
+ teamSchedulingGanttChart
|
|
|
+ } from '@/api/productionPlan/index.js';
|
|
|
import { getTaskListById } from '@/api/materialPlan';
|
|
|
import { teamPage } from '@/api/mainData';
|
|
|
import { getFactoryarea } from '@/api/saleOrder';
|
|
|
@@ -318,6 +427,13 @@
|
|
|
|
|
|
const EXEC_TYPE_MAP = { 0: '自制', 1: '请托', 2: '委外' };
|
|
|
const EXEC_TYPE = Object.freeze({ HOMEMADE: 0, ENTRUST: 1, OUTSOURCE: 2 });
|
|
|
+ const READONLY_LAYOUT_STATUSES = Object.freeze(['7', '4', '5', '6']);
|
|
|
+ const PLAN_PICKER_STATUS_OPTIONS = Object.freeze([
|
|
|
+ { label: '待生产', value: '4' },
|
|
|
+ { label: '生产中', value: '5' },
|
|
|
+ { label: '已完成', value: '6' },
|
|
|
+ { label: '已延期', value: '7' }
|
|
|
+ ]);
|
|
|
const EXEC_TYPE_OPTIONS = Object.freeze([
|
|
|
{ label: '自制', value: EXEC_TYPE.HOMEMADE },
|
|
|
{ label: '请托', value: EXEC_TYPE.ENTRUST },
|
|
|
@@ -360,6 +476,7 @@
|
|
|
data() {
|
|
|
return {
|
|
|
visible: false,
|
|
|
+ entryReadonly: false,
|
|
|
form: {},
|
|
|
currentPlan: null,
|
|
|
planRoutingPayload: null,
|
|
|
@@ -367,13 +484,28 @@
|
|
|
tableData: [],
|
|
|
calendarSourceData: [],
|
|
|
planDataList: [],
|
|
|
+ allPlanDataList: [],
|
|
|
selection: [],
|
|
|
+ planPickerVisible: false,
|
|
|
+ planPickerTitle: '添加计划',
|
|
|
+ planPickerLoading: false,
|
|
|
+ planPickerTimeDimensionPlanType: '',
|
|
|
+ planPickerWhere: {
|
|
|
+ code: '',
|
|
|
+ productCode: '',
|
|
|
+ status: '',
|
|
|
+ batchNo: ''
|
|
|
+ },
|
|
|
+ planPickerSelection: [],
|
|
|
+ planPickerSelectionAll: [],
|
|
|
+ planPickerSelectionSyncing: false,
|
|
|
dialogRenderKey: 0,
|
|
|
dotLineLoading: false,
|
|
|
dotLineFullscreen: false,
|
|
|
teamOptions: [],
|
|
|
factoryList: [],
|
|
|
executionTypeOptions: EXEC_TYPE_OPTIONS,
|
|
|
+ planPickerStatusOptions: PLAN_PICKER_STATUS_OPTIONS,
|
|
|
EXEC_TYPE,
|
|
|
columns: [
|
|
|
{
|
|
|
@@ -607,6 +739,9 @@
|
|
|
.filter((item) => item !== null && item !== undefined && item !== '')
|
|
|
.join('');
|
|
|
return text || '—';
|
|
|
+ },
|
|
|
+ isCurrentPlanReadonly() {
|
|
|
+ return this.entryReadonly || this.isReadonlyPlan(this.currentPlan);
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
@@ -614,10 +749,203 @@
|
|
|
this.loadFactoryOptions();
|
|
|
},
|
|
|
methods: {
|
|
|
+ getPlanRowKey(row) {
|
|
|
+ return String(row?.id ?? row?.planId ?? '');
|
|
|
+ },
|
|
|
+ isReadonlyPlan(row) {
|
|
|
+ return READONLY_LAYOUT_STATUSES.includes(String(row?.status ?? ''));
|
|
|
+ },
|
|
|
+ async loadPlansByIds(ids = []) {
|
|
|
+ const uniqueIds = Array.from(
|
|
|
+ new Set(
|
|
|
+ (ids || []).filter(
|
|
|
+ (id) => id !== null && id !== undefined && id !== ''
|
|
|
+ )
|
|
|
+ )
|
|
|
+ );
|
|
|
+ if (!uniqueIds.length) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ const data = await getList({
|
|
|
+ pageNum: 1,
|
|
|
+ size: uniqueIds.length,
|
|
|
+ ids: uniqueIds
|
|
|
+ });
|
|
|
+ const orderMap = new Map(
|
|
|
+ uniqueIds.map((id, index) => [String(id), index])
|
|
|
+ );
|
|
|
+ const list = Array.isArray(data?.list) ? data.list : [];
|
|
|
+ return [...list].sort((a, b) => {
|
|
|
+ const aIndex = orderMap.get(String(a?.id ?? ''));
|
|
|
+ const bIndex = orderMap.get(String(b?.id ?? ''));
|
|
|
+ return (
|
|
|
+ (aIndex ?? Number.MAX_SAFE_INTEGER) -
|
|
|
+ (bIndex ?? Number.MAX_SAFE_INTEGER)
|
|
|
+ );
|
|
|
+ });
|
|
|
+ },
|
|
|
+ buildPlanTableRows(selectedRows = [], sourceRows = []) {
|
|
|
+ const sourceList =
|
|
|
+ Array.isArray(sourceRows) && sourceRows.length
|
|
|
+ ? sourceRows
|
|
|
+ : selectedRows;
|
|
|
+ const selectedKeySet = new Set(
|
|
|
+ (selectedRows || [])
|
|
|
+ .map((item) => this.getPlanRowKey(item))
|
|
|
+ .filter((item) => item !== '')
|
|
|
+ );
|
|
|
+ return sourceList
|
|
|
+ .filter((item) => selectedKeySet.has(this.getPlanRowKey(item)))
|
|
|
+ .map((item, index) => ({
|
|
|
+ ...deepClone(item),
|
|
|
+ serialNumber: index + 1
|
|
|
+ }));
|
|
|
+ },
|
|
|
+ syncTableSelection(refName, rows) {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const table = this.$refs[refName];
|
|
|
+ if (!table) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (refName === 'planPickerTable') {
|
|
|
+ this.planPickerSelectionSyncing = true;
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ if (typeof table?.setSelectedRows === 'function') {
|
|
|
+ table.setSelectedRows(rows || []);
|
|
|
+ } else {
|
|
|
+ table?.clearSelection?.();
|
|
|
+ (rows || []).forEach((row) => {
|
|
|
+ table?.toggleRowSelection?.(row, true);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (refName === 'planPickerTable') {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.planPickerSelectionSyncing = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, 0);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async planPickerDatasource({ page, limit, where }) {
|
|
|
+ this.planPickerLoading = true;
|
|
|
+ try {
|
|
|
+ const requestWhere = { ...(where || {}) };
|
|
|
+ if (requestWhere.status) {
|
|
|
+ requestWhere.status = [requestWhere.status];
|
|
|
+ }
|
|
|
+ const data = await getList({
|
|
|
+ ...requestWhere,
|
|
|
+ pageNum: page,
|
|
|
+ size: limit,
|
|
|
+ timeDimensionPlanType: this.planPickerTimeDimensionPlanType
|
|
|
+ });
|
|
|
+ this.allPlanDataList = Array.isArray(data?.list) ? data.list : [];
|
|
|
+ const selectedMap = new Map(
|
|
|
+ (this.planPickerSelectionAll || []).map((item) => [
|
|
|
+ this.getPlanRowKey(item),
|
|
|
+ item
|
|
|
+ ])
|
|
|
+ );
|
|
|
+ this.planPickerSelection = this.allPlanDataList.filter((item) =>
|
|
|
+ selectedMap.has(this.getPlanRowKey(item))
|
|
|
+ );
|
|
|
+ this.syncTableSelection('planPickerTable', this.planPickerSelection);
|
|
|
+ return data;
|
|
|
+ } finally {
|
|
|
+ this.planPickerLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async openPlanPicker(timeDimensionPlanType, title) {
|
|
|
+ if (this.entryReadonly) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.planPickerTitle = title || '添加计划';
|
|
|
+ this.planPickerTimeDimensionPlanType = timeDimensionPlanType;
|
|
|
+ this.planPickerWhere = {
|
|
|
+ code: '',
|
|
|
+ productCode: '',
|
|
|
+ status: '',
|
|
|
+ batchNo: ''
|
|
|
+ };
|
|
|
+ this.planPickerSelectionAll = this.buildPlanTableRows(
|
|
|
+ this.planDataList
|
|
|
+ );
|
|
|
+ this.planPickerSelection = [];
|
|
|
+ this.allPlanDataList = [];
|
|
|
+ this.planPickerVisible = true;
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.$refs.planPickerTable?.reload({ page: 1, where: {} });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ handlePlanPickerSearch() {
|
|
|
+ this.$refs.planPickerTable?.reload({
|
|
|
+ page: 1,
|
|
|
+ where: { ...this.planPickerWhere }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ resetPlanPickerSearch() {
|
|
|
+ this.planPickerWhere = {
|
|
|
+ code: '',
|
|
|
+ productCode: '',
|
|
|
+ status: '',
|
|
|
+ batchNo: ''
|
|
|
+ };
|
|
|
+ this.$refs.planPickerTable?.reload({ page: 1, where: {} });
|
|
|
+ },
|
|
|
+ handlePlanPickerSelectionChange(rows) {
|
|
|
+ if (this.planPickerSelectionSyncing) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const currentPageSelection = Array.isArray(rows) ? rows : [];
|
|
|
+ const currentPageKeySet = new Set(
|
|
|
+ (this.allPlanDataList || [])
|
|
|
+ .map((item) => this.getPlanRowKey(item))
|
|
|
+ .filter((item) => item !== '')
|
|
|
+ );
|
|
|
+ const selectedMap = new Map(
|
|
|
+ (this.planPickerSelectionAll || [])
|
|
|
+ .filter((item) => !currentPageKeySet.has(this.getPlanRowKey(item)))
|
|
|
+ .map((item) => [this.getPlanRowKey(item), item])
|
|
|
+ );
|
|
|
+ currentPageSelection.forEach((item) => {
|
|
|
+ selectedMap.set(this.getPlanRowKey(item), item);
|
|
|
+ });
|
|
|
+ this.planPickerSelection = currentPageSelection;
|
|
|
+ this.planPickerSelectionAll = Array.from(selectedMap.values());
|
|
|
+ },
|
|
|
+ async confirmPlanPicker() {
|
|
|
+ const nextPlanDataList = this.buildPlanTableRows(
|
|
|
+ this.planPickerSelectionAll
|
|
|
+ );
|
|
|
+ if (!nextPlanDataList.length) {
|
|
|
+ this.$message.warning('请至少选择一条计划');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const currentPlanKey = this.getPlanRowKey(this.currentPlan);
|
|
|
+ const matchedCurrentPlan = nextPlanDataList.find(
|
|
|
+ (item) => this.getPlanRowKey(item) === currentPlanKey
|
|
|
+ );
|
|
|
+ this.planPickerVisible = false;
|
|
|
+ this.planDataList = nextPlanDataList;
|
|
|
+ this.currentPlan = null;
|
|
|
+ this.planRoutingPayload = null;
|
|
|
+ this.taskList = [];
|
|
|
+ this.selection = [];
|
|
|
+ this.dotLineLoading = false;
|
|
|
+ await this.refreshSchedulingView();
|
|
|
+ this.selection = matchedCurrentPlan ? [matchedCurrentPlan] : [];
|
|
|
+ this.syncTableSelection(
|
|
|
+ 'planTable',
|
|
|
+ matchedCurrentPlan ? [matchedCurrentPlan] : []
|
|
|
+ );
|
|
|
+ },
|
|
|
handlePlanSelectionChange(rows) {
|
|
|
const selectedRows = Array.isArray(rows) ? rows : [];
|
|
|
const currentPlan =
|
|
|
- selectedRows.length > 0 ? selectedRows[selectedRows.length - 1] : null;
|
|
|
+ selectedRows.length > 0
|
|
|
+ ? selectedRows[selectedRows.length - 1]
|
|
|
+ : null;
|
|
|
if (selectedRows.length > 1 && currentPlan) {
|
|
|
this.selection = [currentPlan];
|
|
|
this.$nextTick(() => {
|
|
|
@@ -770,9 +1098,15 @@
|
|
|
this.$set(row, 'executionFactoryName', '');
|
|
|
},
|
|
|
onExecutionTypeChange(row) {
|
|
|
+ if (this.isCurrentPlanReadonly) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
this.clearExecutionTeamFields(row);
|
|
|
},
|
|
|
onExecutionObjectChange(row) {
|
|
|
+ if (this.isCurrentPlanReadonly) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
const t = this.execTypeNum(row);
|
|
|
if (t === EXEC_TYPE.HOMEMADE) {
|
|
|
const team = this.teamOptions.find(
|
|
|
@@ -826,6 +1160,9 @@
|
|
|
}
|
|
|
},
|
|
|
handleTimeChange(row, changeKey) {
|
|
|
+ if (this.isCurrentPlanReadonly) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
if (isEndBeforeStart(row.executionStartTime, row.executionEndTime)) {
|
|
|
this.$message.warning('执行结束时间不能小于执行开始时间');
|
|
|
this.$set(row, changeKey, '');
|
|
|
@@ -946,7 +1283,8 @@
|
|
|
}
|
|
|
|
|
|
const deadline =
|
|
|
- this.currentPlan?.planDeliveryTime || this.currentPlan?.deliveryTime;
|
|
|
+ this.currentPlan?.planDeliveryTime ||
|
|
|
+ this.currentPlan?.deliveryTime;
|
|
|
if (deadline) {
|
|
|
const d = new Date(deadline).getTime();
|
|
|
const checkStart = (val, name) => {
|
|
|
@@ -984,7 +1322,8 @@
|
|
|
executionType: execType,
|
|
|
planCode: row.planCode || plan?.code,
|
|
|
planId: row.planId ?? plan?.id,
|
|
|
- routingId: row.routingId ?? plan?.produceRoutingId ?? head.routingId,
|
|
|
+ routingId:
|
|
|
+ row.routingId ?? plan?.produceRoutingId ?? head.routingId,
|
|
|
routingName:
|
|
|
row.routingName || plan?.produceRoutingName || head.routingName,
|
|
|
sourceTaskId: row.sourceTaskId ?? row.taskId,
|
|
|
@@ -1034,12 +1373,41 @@
|
|
|
this.tableData = this.initGanttFlatRows(data || []);
|
|
|
},
|
|
|
async open(row) {
|
|
|
+ const ids = Array.isArray(row?.ids) ? row.ids : [];
|
|
|
+ const readonly = !!row?.readonly;
|
|
|
+ const loading = ids.length
|
|
|
+ ? this.$loading({ lock: true, text: '加载计划中...' })
|
|
|
+ : null;
|
|
|
+ let selectedPlans = Array.isArray(row)
|
|
|
+ ? row
|
|
|
+ : Array.isArray(row?.selectedPlans)
|
|
|
+ ? row.selectedPlans
|
|
|
+ : [];
|
|
|
+ let allPlans = Array.isArray(row?.allPlans)
|
|
|
+ ? row.allPlans
|
|
|
+ : selectedPlans;
|
|
|
+ try {
|
|
|
+ if (ids.length) {
|
|
|
+ const planList = await this.loadPlansByIds(ids);
|
|
|
+ selectedPlans = planList;
|
|
|
+ allPlans = planList;
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ loading?.close();
|
|
|
+ }
|
|
|
this.dialogRenderKey += 1;
|
|
|
- this.planDataList = row;
|
|
|
+ this.entryReadonly = readonly;
|
|
|
+ this.allPlanDataList = deepClone(allPlans || []);
|
|
|
+ this.planDataList = this.buildPlanTableRows(
|
|
|
+ selectedPlans,
|
|
|
+ this.allPlanDataList
|
|
|
+ );
|
|
|
this.currentPlan = null;
|
|
|
this.planRoutingPayload = null;
|
|
|
this.taskList = [];
|
|
|
this.selection = [];
|
|
|
+ this.planPickerVisible = false;
|
|
|
+ this.planPickerSelection = [];
|
|
|
this.dotLineLoading = false;
|
|
|
this.dotLineFullscreen = false;
|
|
|
this.calendarSourceData = [];
|
|
|
@@ -1264,6 +1632,10 @@
|
|
|
this.$message.warning('请先勾选一条计划后再确定');
|
|
|
return;
|
|
|
}
|
|
|
+ if (this.isCurrentPlanReadonly) {
|
|
|
+ this.$message.warning('当前计划状态下布局数据仅支持查看');
|
|
|
+ return;
|
|
|
+ }
|
|
|
if (!this.taskList.length) {
|
|
|
this.$message.warning('当前没有可提交的工艺配置');
|
|
|
return;
|
|
|
@@ -1286,10 +1658,14 @@
|
|
|
},
|
|
|
cancel() {
|
|
|
this.form = {};
|
|
|
+ this.entryReadonly = false;
|
|
|
this.currentPlan = null;
|
|
|
this.planRoutingPayload = null;
|
|
|
this.taskList = [];
|
|
|
this.selection = [];
|
|
|
+ this.planPickerSelection = [];
|
|
|
+ this.planPickerSelectionAll = [];
|
|
|
+ this.planPickerTimeDimensionPlanType = '';
|
|
|
this.dotLineLoading = false;
|
|
|
this.dotLineFullscreen = false;
|
|
|
this.visible = false;
|
|
|
@@ -1303,9 +1679,63 @@
|
|
|
padding: 20px 0;
|
|
|
}
|
|
|
|
|
|
+ .btnList {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .plan-picker-search {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .plan-picker-search__item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ :deep(.el-input),
|
|
|
+ :deep(.el-select) {
|
|
|
+ width: 180px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .plan-picker-search__label {
|
|
|
+ color: #606266;
|
|
|
+ font-size: 12px;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .plan-picker-search__actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .scheduling-content {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-end;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-wrapper {
|
|
|
+ width: calc(100% - 700px);
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
.plan-dot-line-side {
|
|
|
- height: 400px;
|
|
|
+ width: 700px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ height: 525px;
|
|
|
overflow: hidden;
|
|
|
+ padding-left: 12px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
|
|
|
.plan-dot-line-side.is-fullscreen {
|
|
|
@@ -1320,17 +1750,28 @@
|
|
|
background: #fff;
|
|
|
border-radius: 8px;
|
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
|
|
|
+ padding: 20px;
|
|
|
}
|
|
|
|
|
|
- .plan-dot-line-side .pane-box {
|
|
|
+ .plan-dot-line-detail {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
height: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .plan-dot-line-detail .plan-time-bar {
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .plan-dot-line-detail .plan-dot-line-scrollable {
|
|
|
+ flex: 1;
|
|
|
overflow: auto;
|
|
|
- padding-left: 12px;
|
|
|
- padding-right: 0;
|
|
|
+ padding-bottom: 20px;
|
|
|
box-sizing: border-box;
|
|
|
+ min-height: 0;
|
|
|
}
|
|
|
|
|
|
- .plan-dot-line-detail .plan-dot-line,
|
|
|
+ .plan-dot-line-detail .plan-dot-line-scrollable,
|
|
|
.plan-dot-line-detail .top-route,
|
|
|
.plan-dot-line-detail .config-panel {
|
|
|
width: 100%;
|
|
|
@@ -1339,10 +1780,6 @@
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
- .plan-dot-line-detail .plan-dot-line {
|
|
|
- min-height: 360px;
|
|
|
- }
|
|
|
-
|
|
|
.plan-dot-line-detail .top-route,
|
|
|
.plan-dot-line-detail .config-panel {
|
|
|
border: 1px solid #ebeef5;
|
|
|
@@ -1383,6 +1820,11 @@
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
+ .readonly-tip {
|
|
|
+ color: #e6a23c;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
.plan-dot-line-detail .route-steps {
|
|
|
width: 100%;
|
|
|
overflow-x: auto;
|
|
|
@@ -1534,8 +1976,12 @@
|
|
|
}
|
|
|
|
|
|
.btnList {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin: 10px 0 12px;
|
|
|
button {
|
|
|
- margin: 10px;
|
|
|
+ margin: 0;
|
|
|
}
|
|
|
}
|
|
|
</style>
|