|
|
@@ -0,0 +1,436 @@
|
|
|
+<template>
|
|
|
+ <ele-modal
|
|
|
+ v-if="visible"
|
|
|
+ custom-class="ele-dialog-form long-dialog-form"
|
|
|
+ :centered="true"
|
|
|
+ :append-to-body="true"
|
|
|
+ :visible.sync="dialogVisible"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ width="70%"
|
|
|
+ :maxable="true"
|
|
|
+ :resizable="true"
|
|
|
+ :title="title"
|
|
|
+ @close="onModalClose"
|
|
|
+ >
|
|
|
+ <div class="plan-dot-line">
|
|
|
+ <div class="top-route">
|
|
|
+ <div class="panel-title">工艺路线</div>
|
|
|
+ <el-empty
|
|
|
+ v-if="taskList.length === 0"
|
|
|
+ description="暂无工艺路线"
|
|
|
+ ></el-empty>
|
|
|
+ <div v-else class="route-line">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in taskList"
|
|
|
+ :key="`route-${item._taskKey}`"
|
|
|
+ class="route-item"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ class="route-node"
|
|
|
+ :class="{ 'route-node--done': isConfigured(item.dotLineConfig) }"
|
|
|
+ >
|
|
|
+ {{ item.name || item.taskName || `工艺${index + 1}` }}
|
|
|
+ </span>
|
|
|
+ <span v-if="index < taskList.length - 1" class="route-arrow"
|
|
|
+ >→</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="config-panel">
|
|
|
+ <div class="panel-title">工艺配置</div>
|
|
|
+ <el-empty
|
|
|
+ v-if="taskList.length === 0"
|
|
|
+ description="暂无工艺"
|
|
|
+ ></el-empty>
|
|
|
+ <div v-else class="task-config-table-wrap">
|
|
|
+ <el-table
|
|
|
+ :data="taskList"
|
|
|
+ border
|
|
|
+ size="small"
|
|
|
+ :row-key="getRowKey"
|
|
|
+ max-height="420"
|
|
|
+ class="config-table"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ type="index"
|
|
|
+ label="序号"
|
|
|
+ width="48"
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ label="工序"
|
|
|
+ min-width="90"
|
|
|
+ show-overflow-tooltip
|
|
|
+ class-name="task-name-cell"
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ >
|
|
|
+ <template slot-scope="{ row, $index }">
|
|
|
+ <span class="task-name-text">{{
|
|
|
+ row.name || row.taskName || `工艺${$index + 1}`
|
|
|
+ }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ label="班组"
|
|
|
+ min-width="130"
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ >
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-select
|
|
|
+ v-model="row.dotLineConfig.teamId"
|
|
|
+ placeholder="请选择班组"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ class="config-table-control"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="team in teamOptions"
|
|
|
+ :key="team.id"
|
|
|
+ :label="team.name"
|
|
|
+ :value="team.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ label="开始时间"
|
|
|
+ min-width="168"
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ >
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="row.dotLineConfig.startTime"
|
|
|
+ type="datetime"
|
|
|
+ value-format="yyyy-MM-dd HH:mm:ss"
|
|
|
+ placeholder="开始时间"
|
|
|
+ class="config-table-control"
|
|
|
+ @change="handleTimeChange(row, 'startTime')"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ label="结束时间"
|
|
|
+ min-width="168"
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ >
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="row.dotLineConfig.endTime"
|
|
|
+ type="datetime"
|
|
|
+ value-format="yyyy-MM-dd HH:mm:ss"
|
|
|
+ placeholder="结束时间"
|
|
|
+ class="config-table-control"
|
|
|
+ @change="handleTimeChange(row, 'endTime')"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ label="类型"
|
|
|
+ min-width="100"
|
|
|
+ align="center"
|
|
|
+ header-align="center"
|
|
|
+ >
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-select
|
|
|
+ v-model="row.dotLineConfig.type"
|
|
|
+ placeholder="请选择类型"
|
|
|
+ clearable
|
|
|
+ class="config-table-control"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="type in typeOptions"
|
|
|
+ :key="type.value"
|
|
|
+ :label="type.label"
|
|
|
+ :value="type.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button size="mini" @click="onModalClose">取消</el-button>
|
|
|
+ <el-button size="mini" type="primary" @click="handleSave">保存</el-button>
|
|
|
+ </div>
|
|
|
+ </ele-modal>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+ import { getTaskListById } from '@/api/materialPlan';
|
|
|
+ import { teamPage } from '@/api/mainData';
|
|
|
+ export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ visible: false,
|
|
|
+ dialogVisible: false,
|
|
|
+ title: '计划描点',
|
|
|
+ taskList: [],
|
|
|
+ teamOptions: [],
|
|
|
+ currentPlan: null,
|
|
|
+ typeOptions: [
|
|
|
+ { label: '自制', value: '1' },
|
|
|
+ { label: '请托', value: '2' },
|
|
|
+ { label: '委外', value: '3' }
|
|
|
+ ],
|
|
|
+ // 与 typeOptions 一致:自制 / 请托 / 委外
|
|
|
+ TYPE_HOMEMADE: '1',
|
|
|
+ TYPE_ENTRUST: '2',
|
|
|
+ TYPE_OUTSOURCE: '3'
|
|
|
+ };
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ getRowKey(row) {
|
|
|
+ return row._taskKey;
|
|
|
+ },
|
|
|
+ async open(data) {
|
|
|
+ this.currentPlan = data || null;
|
|
|
+ await Promise.all([this.loadTaskList(), this.loadTeamOptions()]);
|
|
|
+ this.fillSavedTaskConfig();
|
|
|
+ this.visible = true;
|
|
|
+ this.dialogVisible = true;
|
|
|
+ },
|
|
|
+ async loadTaskList() {
|
|
|
+ if (!this.currentPlan || !this.currentPlan.produceRoutingId) {
|
|
|
+ this.taskList = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const list = await getTaskListById(this.currentPlan.produceRoutingId);
|
|
|
+ this.taskList = (list || []).map((item, index) => ({
|
|
|
+ ...item,
|
|
|
+ _taskKey: item.id || item.taskId || item.sourceTaskId || index,
|
|
|
+ dotLineConfig: {
|
|
|
+ teamId: '',
|
|
|
+ startTime: '',
|
|
|
+ endTime: '',
|
|
|
+ type: this.TYPE_HOMEMADE
|
|
|
+ }
|
|
|
+ }));
|
|
|
+ },
|
|
|
+ async loadTeamOptions() {
|
|
|
+ try {
|
|
|
+ const res = await teamPage({ size: 500 });
|
|
|
+ const list = res?.list || [];
|
|
|
+ this.teamOptions = list.map((team) => ({
|
|
|
+ id: team.id,
|
|
|
+ name: team.name
|
|
|
+ }));
|
|
|
+ } catch (error) {
|
|
|
+ this.teamOptions = [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fillSavedTaskConfig() {
|
|
|
+ const savedList = this.currentPlan?.dotLineTaskList || [];
|
|
|
+ if (!savedList.length) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const map = {};
|
|
|
+ savedList.forEach((item) => {
|
|
|
+ map[item._taskKey] = item.dotLineConfig || {};
|
|
|
+ });
|
|
|
+ this.taskList = this.taskList.map((item) => {
|
|
|
+ const merged = {
|
|
|
+ ...item.dotLineConfig,
|
|
|
+ ...(map[item._taskKey] || {})
|
|
|
+ };
|
|
|
+ if (!merged.type) {
|
|
|
+ merged.type = this.TYPE_HOMEMADE;
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ dotLineConfig: merged
|
|
|
+ };
|
|
|
+ });
|
|
|
+ },
|
|
|
+ isConfigured(config) {
|
|
|
+ if (!config) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return !!(
|
|
|
+ config.teamId ||
|
|
|
+ config.startTime ||
|
|
|
+ config.endTime ||
|
|
|
+ config.type
|
|
|
+ );
|
|
|
+ },
|
|
|
+ handleTimeChange(item, changeKey) {
|
|
|
+ const cfg = item?.dotLineConfig || {};
|
|
|
+ if (!cfg.startTime || !cfg.endTime) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const start = new Date(cfg.startTime).getTime();
|
|
|
+ const end = new Date(cfg.endTime).getTime();
|
|
|
+ if (!Number.isNaN(start) && !Number.isNaN(end) && end < start) {
|
|
|
+ this.$message.warning('结束时间不能小于开始时间');
|
|
|
+ this.$set(cfg, changeKey, '');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleSave() {
|
|
|
+ const { TYPE_HOMEMADE, TYPE_ENTRUST, TYPE_OUTSOURCE } = this;
|
|
|
+ const taskLabel = (task) => task.name || task.taskName || '当前工艺';
|
|
|
+
|
|
|
+ for (const item of this.taskList) {
|
|
|
+ const cfg = item.dotLineConfig || {};
|
|
|
+ if (!cfg.type) {
|
|
|
+ this.$set(item.dotLineConfig, 'type', TYPE_HOMEMADE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const item of this.taskList) {
|
|
|
+ const cfg = item.dotLineConfig || {};
|
|
|
+ const type = cfg.type || TYPE_HOMEMADE;
|
|
|
+
|
|
|
+ if (type === TYPE_HOMEMADE) {
|
|
|
+ if (!cfg.teamId || !cfg.startTime || !cfg.endTime) {
|
|
|
+ this.$message.warning(
|
|
|
+ `${taskLabel(item)}类型为自制时,需填写班组、开始时间和结束时间`
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const start = new Date(cfg.startTime).getTime();
|
|
|
+ const end = new Date(cfg.endTime).getTime();
|
|
|
+ if (Number.isNaN(start) || Number.isNaN(end)) {
|
|
|
+ this.$message.warning(`${taskLabel(item)}开始时间或结束时间无效`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (end < start) {
|
|
|
+ this.$message.warning(
|
|
|
+ `${taskLabel(item)}结束时间不能小于开始时间`
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else if (type === TYPE_ENTRUST || type === TYPE_OUTSOURCE) {
|
|
|
+ if (cfg.startTime && cfg.endTime) {
|
|
|
+ const start = new Date(cfg.startTime).getTime();
|
|
|
+ const end = new Date(cfg.endTime).getTime();
|
|
|
+ if (!Number.isNaN(start) && !Number.isNaN(end) && end < start) {
|
|
|
+ this.$message.warning(
|
|
|
+ `${taskLabel(item)}结束时间不能小于开始时间`
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (this.currentPlan) {
|
|
|
+ this.$set(this.currentPlan, 'dotLineTaskList', this.taskList);
|
|
|
+ }
|
|
|
+ this.$emit('save', {
|
|
|
+ plan: this.currentPlan,
|
|
|
+ taskList: this.taskList
|
|
|
+ });
|
|
|
+ this.$message.success('保存成功');
|
|
|
+ this.onModalClose();
|
|
|
+ },
|
|
|
+
|
|
|
+ onModalClose() {
|
|
|
+ this.visible = false;
|
|
|
+ this.dialogVisible = false;
|
|
|
+ this.taskList = [];
|
|
|
+ this.currentPlan = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+ .plan-dot-line {
|
|
|
+ min-height: 360px;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-route,
|
|
|
+ .config-panel {
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 12px;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-panel {
|
|
|
+ margin-top: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .panel-title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-line {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ row-gap: 4px;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-item {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-node {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-width: 38px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 0 7px;
|
|
|
+ color: #ffffff;
|
|
|
+ background: #909399;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-node--done {
|
|
|
+ background: #56bf1d;
|
|
|
+ }
|
|
|
+
|
|
|
+ .route-arrow {
|
|
|
+ margin: 0 5px;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-config-table-wrap {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-table ::v-deep .el-table th > .cell,
|
|
|
+ .config-table ::v-deep .el-table td > .cell {
|
|
|
+ font-size: 14px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-table ::v-deep .el-table__body .cell {
|
|
|
+ padding-left: 6px;
|
|
|
+ padding-right: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-table ::v-deep td.task-name-cell .task-name-text {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 1.4;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-table ::v-deep .config-table-control {
|
|
|
+ width: 100% !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-table ::v-deep .config-table-control.el-date-editor {
|
|
|
+ max-width: 100%;
|
|
|
+ }
|
|
|
+</style>
|