| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575 |
- <template>
- <ele-modal
- :key="dialogRenderKey"
- width="100%"
- :visible.sync="visible"
- :close-on-click-modal="false"
- custom-class="ele-dialog-form scheduling-dialog"
- :title="schedulingTitle"
- :maxable="true"
- >
- <projectGantt
- :style="{ height: ganttHeight }"
- :ganttTasks="tableData"
- :planList="planDataList"
- :calendarSourceData="calendarSourceData"
- :defaultTab="defaultGanttTab"
- :resource-tabs-config="resourceTabsConfig"
- :resource-label-mode="schedulingMode"
- :dateRange="dateRange"
- @tab-change="handleTabChange"
- >
- <template v-slot:toolbar>
- <el-date-picker
- v-model="dateRange"
- type="daterange"
- size="small"
- value-format="yyyy-MM-dd"
- range-separator="至"
- start-placeholder="计划开始时间"
- end-placeholder="计划结束时间"
- @input="handleDateInput"
- @change="handleDateChange"
- :append-to-body="false"
- ></el-date-picker>
- </template>
- </projectGantt>
- <ele-split-layout
- class="scheduling-content"
- space="0px"
- :width="splitPaneWidth"
- :resizable="true"
- :min-size="splitPaneMinSize"
- :max-size="splitPaneMaxSize"
- :left-style="{
- overflow: 'visible',
- width: '100%'
- }"
- :right-style="{
- overflow: 'hidden'
- }"
- :responsive="isCompactSchedulingLayout"
- :style="{ height: schedulingContentHeight }"
- >
- <div
- :class="[
- 'form-wrapper',
- {
- 'is-order-scheduling': isOrderScheduling,
- 'is-plan-scheduling': !isOrderScheduling
- }
- ]"
- >
- <div class="plan-table-status-tabs">
- <el-tabs
- v-model="planTableStatusTab"
- @tab-click="handlePlanTableStatusTabClick"
- >
- <el-tab-pane
- v-for="item in planTableStatusTabs"
- :key="item.name"
- :name="item.name"
- :label="item.label"
- />
- </el-tabs>
- </div>
- <ele-pro-table
- width="100%"
- ref="planTable"
- :columns="columns"
- cacheKeyUrl="paln_factory_production_scheduling"
- :datasource="planDataList"
- row-key="id"
- :selection.sync="selection"
- :height="planTableHeight"
- :page-size="20"
- :pagination-class="planPaginationClass"
- @reload="handlePlanTableReload"
- @update:selection="handlePlanSelectionChange"
- >
- <template v-slot:action="{ row }">
- <el-link
- type="primary"
- :underline="false"
- @click="handlePublish(row)"
- >
- {{ getPlanActionText(row) }}
- </el-link>
- </template>
- <template v-slot:dotLineStatus="{ row }">
- <el-tag type="success" size="mini" v-if="row.dotLineStatus == 1">
- 已布点
- </el-tag>
- <el-tag type="danger" size="mini" v-if="row.dotLineStatus == 0">
- 未布点
- </el-tag>
- </template>
- </ele-pro-table>
- </div>
- <template v-slot:content>
- <div
- v-loading="dotLineLoading"
- :class="[
- 'plan-dot-line-side',
- {
- 'is-fullscreen': dotLineFullscreen,
- 'is-plan-scheduling': !isOrderScheduling
- }
- ]"
- >
- <div class="plan-dot-line-detail">
- <plan-route-panel
- v-if="!isOrderScheduling"
- :plan-start-display="planStartDisplay"
- :plan-end-display="planEndDisplay"
- :plan-num-display="planNumDisplay"
- :task-list="taskList"
- :route-steps-active="routeStepsActive"
- :route-des-index="routeDesIndex"
- :route-step-title="routeStepTitle"
- :compact="isOrderScheduling"
- />
- <div
- :class="[
- 'plan-dot-line-scrollable',
- {
- 'is-order-dispatch-scroll': isOrderScheduling,
- 'is-plan-config-scroll': !isOrderScheduling
- }
- ]"
- >
- <task-config-panel
- :key="taskConfigPanelRenderKey"
- :class="{ 'factory-task-config-panel': !isOrderScheduling }"
- :dot-line-fullscreen="dotLineFullscreen"
- :is-current-plan-readonly="isCurrentPlanReadonly"
- :order-detail-readonly="isOrderDetailReadonly"
- :current-plan="currentPlan"
- :dot-line-loading="dotLineLoading"
- :task-list="taskList"
- :execution-type-options="executionTypeOptions"
- :team-options="teamOptions"
- :get-homemade-options="getHomemadeOptions"
- :homemade-object-label="homemadeObjectLabel"
- :show-quantity-column="isOrderScheduling"
- :order-dispatch-style="isOrderScheduling"
- :show-approve-button="!isOrderScheduling"
- :approve-button-disabled="!canApproveCurrentPlan"
- :approve-loading="approvalLoading"
- :table-max-height="taskConfigTableMaxHeight"
- :fill-table-height="!isOrderScheduling"
- :factory-list="factoryList"
- :exec-type="EXEC_TYPE"
- :task-name="taskName"
- :exec-type-num="execTypeNum"
- :is-execution-end-over-delivery="isExecutionEndOverDelivery"
- :is-execution-end-over-plan-end="isExecutionEndOverPlanEnd"
- @toggle-fullscreen="toggleDotLineFullscreen"
- @submit="submitDotLineConfig"
- @execution-type-change="onExecutionTypeChange"
- @execution-object-change="onExecutionObjectChange"
- @parallelism-change="handleParallelismChange"
- @time-change="handleTimeChange"
- @add-task-row="addTaskRow"
- @remove-task-row="removeTaskRow"
- @add-dispatch-stations="addDispatchStations"
- @remove-dispatch-station="removeDispatchStation"
- @dispatch-action-success="handleDispatchActionSuccess"
- @row-submit="submitDotLineRow"
- @approve="handleApproveCurrentPlan"
- />
- </div>
- </div>
- </div>
- </template>
- </ele-split-layout>
- <div slot="footer">
- <!-- <el-button plain @click="cancel">取消</el-button> -->
- <el-button @click="cancel">关闭</el-button>
- </div>
- <plan-picker-dialog
- ref="planPickerTable"
- :title="planPickerTitle"
- :visible.sync="planPickerVisible"
- :where.sync="planPickerWhere"
- :status-options="planPickerStatusOptions"
- :columns="columns"
- :loading="planPickerLoading"
- :datasource="planPickerDatasource"
- :selection.sync="planPickerSelection"
- @search="handlePlanPickerSearch"
- @reset-search="resetPlanPickerSearch"
- @confirm="confirmPlanPicker"
- @update:selection="handlePlanPickerSelectionChange"
- />
- </ele-modal>
- </template>
- <script>
- import projectGantt from './gantt/project-gantt.vue';
- import PlanPickerDialog from './newFactoryProductionScheduling/PlanPickerDialog.vue';
- import PlanRoutePanel from './newFactoryProductionScheduling/PlanRoutePanel.vue';
- import TaskConfigPanel from './newFactoryProductionScheduling/TaskConfigPanel.vue';
- import {
- getList,
- teamSchedulingGanttChart,
- workstationSchedulingGanttChart,
- factorySchedulingGanttChart,
- getPlansByIds
- } from '@/api/productionPlan/index.js';
- import { getTaskListById } from '@/api/materialPlan';
- import {
- listByFirstTaskId,
- parameterGetByCode,
- teamPage
- } from '@/api/mainData';
- import { getFactoryarea } from '@/api/saleOrder';
- import { getUserPage } from '@/api/system/organization';
- import { release, releaseByApproval } from '@/api/productionPlan/order.js';
- import {
- getPlanDotLine,
- savePlanDotLine
- } from '@/api/productionPlan/planDotLine';
- import { deepClone } from '@/utils';
- import {
- EXEC_TYPE,
- EXEC_TYPE_OPTIONS,
- PLAN_PICKER_STATUS_OPTIONS,
- READONLY_LAYOUT_STATUSES
- } from './newFactoryProductionScheduling.constants';
- import {
- createSchedulingColumns,
- createWorkOrderSchedulingColumns
- } from './newFactoryProductionScheduling.columns';
- import {
- createPlanPickerWhere,
- formatDotLineDateTime,
- isEndAfterPlanEnd,
- isEndBeforeStart,
- toSafeNumber
- } from './newFactoryProductionScheduling.utils';
- export default {
- components: {
- projectGantt,
- PlanPickerDialog,
- PlanRoutePanel,
- TaskConfigPanel
- },
- data() {
- const now = new Date();
- const startDate = `${now.getFullYear()}-${String(
- now.getMonth() + 1
- ).padStart(2, '0')}-01`;
- const lastDay = new Date(
- now.getFullYear(),
- now.getMonth() + 1,
- 0
- ).getDate();
- const endDate = `${now.getFullYear()}-${String(
- now.getMonth() + 1
- ).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
- return {
- visible: false,
- entryReadonly: false,
- form: {},
- currentPlan: null,
- planRoutingPayload: null,
- taskList: [],
- tableData: [],
- calendarSourceData: [],
- planDataList: [],
- allPlanDataList: [],
- selection: [],
- activeTab: 'factory',
- dateRange: [startDate, endDate],
- planPickerVisible: false,
- planPickerTitle: '添加计划',
- planPickerLoading: false,
- planPickerTimeDimensionPlanType: '',
- planPickerWhere: createPlanPickerWhere(),
- planPickerSelection: [],
- planPickerSelectionAll: [],
- planPickerSelectionSyncing: false,
- planTableSelectionSyncing: false,
- planTableStatusTab: 'unpublished',
- dialogRenderKey: 0,
- dotLineLoading: false,
- dotLineFullscreen: false,
- approvalLoading: false,
- isReview: false,
- orderDetailReadonly: false,
- orderDetailActionMode: '',
- teamOptions: [],
- workstationOptionsMap: {},
- workstationOptionsLoadingMap: {},
- factoryList: [],
- executionTypeOptions: EXEC_TYPE_OPTIONS,
- planPickerStatusOptions: PLAN_PICKER_STATUS_OPTIONS,
- EXEC_TYPE,
- schedulingMode: 'plan',
- selectedWorkOrders: [],
- orderListLoader: null,
- orderDateReloadTimer: null,
- columns: createSchedulingColumns(),
- viewportWidth: typeof window === 'undefined' ? 1920 : window.innerWidth,
- viewportHeight: typeof window === 'undefined' ? 900 : window.innerHeight
- };
- },
- computed: {
- routeStepsActive() {
- const list = this.taskList;
- if (!list.length) return 0;
- for (let i = 0; i < list.length; i++) {
- if (!this.isTaskDone(list[i])) return i;
- }
- return list.length;
- },
- routeDesIndex() {
- const list = this.taskList;
- if (!list.length) return -1;
- const a = this.routeStepsActive;
- return a >= list.length ? list.length - 1 : a;
- },
- planStartDisplay() {
- const t = this.currentPlan?.startTime;
- return t ? formatDotLineDateTime(t) || String(t) : '—';
- },
- planEndDisplay() {
- const t = this.currentPlan?.endTime;
- return t ? formatDotLineDateTime(t) || String(t) : '—';
- },
- planNumDisplay() {
- const num = this.currentPlan?.productNum;
- const unit = this.currentPlan?.measuringUnit || this.currentPlan?.unit;
- const text = [num, unit]
- .filter((item) => item !== null && item !== undefined && item !== '')
- .join('');
- return text || '—';
- },
- isCurrentPlanReadonly() {
- return (
- (!this.isOrderScheduling && this.entryReadonly) ||
- this.isOrderDetailReadonly ||
- (!this.isOrderScheduling && this.isReadonlyPlan(this.currentPlan))
- );
- },
- isOrderDetailReadonly() {
- return (
- this.isOrderScheduling &&
- !!this.currentPlan &&
- this.orderDetailActionMode === 'view'
- );
- },
- // 当前弹窗同时承载两条业务链路:
- // 1. 工厂排程:从生产计划页面进入,不传 mode,保持默认 plan 模式。
- // 2. 班组排程:从工单页面进入,显式传入 mode = order。
- // 后续表格列、资源页签、执行对象和保存字段都以该判断隔离。
- isOrderScheduling() {
- return this.schedulingMode === 'order';
- },
- resourceTabsConfig() {
- if (this.isOrderScheduling) {
- return [
- { key: 'team', label: '工位' },
- { key: 'line', label: '设备' },
- { key: 'person', label: '人员' }
- ];
- }
- return undefined;
- },
- homemadeObjectLabel() {
- return this.isOrderScheduling ? '执行对象(人员/工位)' : '执行对象';
- },
- defaultGanttTab() {
- return this.isOrderScheduling ? 'team' : 'factory';
- },
- schedulingTitle() {
- return this.isOrderScheduling ? '班组排程' : '工厂排产';
- },
- taskConfigPanelRenderKey() {
- const planKey =
- this.currentPlan?.workOrderId || this.currentPlan?.id || 'empty';
- return [
- this.schedulingMode,
- this.planTableStatusTab,
- planKey,
- this.orderDetailActionMode || 'none',
- this.isCurrentPlanReadonly ? 'readonly' : 'edit'
- ].join('-');
- },
- planTableStatusTabs() {
- if (this.isOrderScheduling) {
- return [
- {
- name: 'undispatched',
- label: '未派单'
- },
- {
- name: 'dispatched',
- label: '已派单'
- }
- ];
- }
- return [
- {
- name: 'unpublished',
- label: '未发布'
- },
- {
- name: 'published',
- label: '已发布'
- }
- ];
- },
- isCompactSchedulingLayout() {
- return this.viewportWidth <= 1180;
- },
- splitPaneWidth() {
- if (this.isOrderScheduling) {
- if (this.viewportWidth <= 1280) return '36%';
- if (this.viewportWidth <= 1440) return '39%';
- if (this.viewportWidth <= 1680) return '42%';
- return '44%';
- }
- if (this.viewportWidth <= 1280) return '44%';
- if (this.viewportWidth <= 1440) return '50%';
- return '58%';
- },
- splitPaneMaxSize() {
- if (this.isOrderScheduling) {
- if (this.viewportWidth <= 1280) return -620;
- if (this.viewportWidth <= 1440) return -680;
- return -720;
- }
- return this.viewportWidth <= 1440 ? -520 : -420;
- },
- splitPaneMinSize() {
- if (this.viewportWidth <= 1280) return 280;
- if (this.viewportWidth <= 1440) return 320;
- return 360;
- },
- schedulingLayoutAvailableHeightValue() {
- const headerHeight = this.viewportHeight <= 760 ? 38 : 42;
- const footerHeight = this.viewportHeight <= 760 ? 34 : 38;
- const bodyVerticalPadding = this.viewportHeight <= 760 ? 8 : 12;
- const contentGap = this.viewportHeight <= 760 ? 5 : 6;
- return (
- this.viewportHeight -
- 10 -
- headerHeight -
- footerHeight -
- bodyVerticalPadding -
- contentGap
- );
- },
- ganttPanelHeightValue() {
- const minGanttHeight =
- this.viewportHeight <= 720
- ? 230
- : this.viewportHeight <= 820
- ? 300
- : this.viewportHeight <= 960
- ? 340
- : 380;
- const maxGanttHeight =
- this.viewportHeight <= 820
- ? 390
- : this.viewportHeight <= 1080
- ? 500
- : 560;
- const balancedHeight = Math.floor(
- this.schedulingLayoutAvailableHeightValue / 2
- );
- return Math.max(
- 232,
- Math.min(maxGanttHeight, Math.max(minGanttHeight, balancedHeight))
- );
- },
- schedulingContentHeightValue() {
- const remainingHeight =
- this.schedulingLayoutAvailableHeightValue -
- this.ganttPanelHeightValue;
- const minHeight =
- this.viewportHeight <= 720
- ? 230
- : this.viewportHeight <= 820
- ? 300
- : this.viewportHeight <= 900
- ? 340
- : 380;
- return Math.max(minHeight, remainingHeight);
- },
- ganttHeight() {
- return `${this.ganttPanelHeightValue}px`;
- },
- schedulingContentHeight() {
- return `${this.schedulingContentHeightValue}px`;
- },
- planTableHeight() {
- const tabHeight = 42;
- const paginationHeight = 44;
- const tableHeight = Math.max(
- 220,
- this.schedulingContentHeightValue - tabHeight - paginationHeight
- );
- if (tableHeight >= 0) {
- return `${tableHeight}px`;
- }
- if (this.isOrderScheduling) {
- // ele-pro-table 的 height 只作用在表格主体,工具栏和分页器会继续占用额外高度。
- // 班组排产右侧是自定义 flex 分页区,左侧需要略收表格主体高度,保证两个分页器垂直位置一致。
- if (this.viewportHeight <= 760) return `${360 - tabHeight}px`;
- if (this.viewportHeight <= 900) return `${420 - tabHeight}px`;
- return `${470 - tabHeight}px`;
- }
- if (this.viewportHeight <= 760) return `${360 - tabHeight}px`;
- if (this.viewportHeight <= 900) return `${420 - tabHeight}px`;
- return `${455 - tabHeight}px`;
- },
- planPaginationClass() {
- return this.isOrderScheduling
- ? 'ele-pagination-circle order-plan-pagination'
- : 'ele-pagination-circle plan-scheduling-pagination';
- },
- taskConfigTableMaxHeight() {
- if (this.isOrderScheduling) {
- return Math.max(260, this.schedulingContentHeightValue - 132);
- }
- return Math.max(280, this.schedulingContentHeightValue - 132);
- },
- canApproveCurrentPlan() {
- const row = this.currentPlan;
- const hasPermission =
- typeof this.$hasPermission !== 'function' ||
- this.$hasPermission('aps:productionplan:release');
- return (
- !this.isOrderScheduling &&
- !!row?.id &&
- this.planTableStatusTab === 'unpublished' &&
- String(row.status) === '2' &&
- String(row.dotLineStatus) === '1' &&
- row.splitBatch != 1 &&
- row.approvalStatus != 1 &&
- row.approvalStatus != 2 &&
- hasPermission
- );
- }
- },
- created() {
- this.loadTeamOptions();
- this.loadFactoryOptions();
- this.loadApprovalReviewConfig();
- },
- mounted() {
- this.updateViewportSize();
- window.addEventListener('resize', this.updateViewportSize);
- },
- beforeDestroy() {
- window.removeEventListener('resize', this.updateViewportSize);
- },
- methods: {
- updateViewportSize() {
- this.viewportWidth = window.innerWidth;
- this.viewportHeight = window.innerHeight;
- },
- normalizeId(value) {
- return value === null || value === undefined ? '' : String(value);
- },
- normalizeBackendLongId(value) {
- if (value === null || value === undefined || value === '') {
- return '';
- }
- const text = String(value).trim();
- return /^\d+$/.test(text) ? text : '';
- },
- normalizeBackendTextValue(value) {
- if (value === null || value === undefined || value === '') {
- return '';
- }
- const text = String(value).trim();
- return ['undefined', 'null'].includes(text.toLowerCase()) ? '' : text;
- },
- pickBackendTextValue(...values) {
- for (const value of values) {
- const text = this.normalizeBackendTextValue(value);
- if (text) {
- return text;
- }
- }
- return '';
- },
- normalizeOrderQuantity(value) {
- if (value === null || value === undefined || value === '') {
- return '';
- }
- const text = String(value).trim();
- return /^0+(\.0+)?$/.test(text) ? '' : value;
- },
- optionMatchesId(optionId, targetId) {
- return this.normalizeId(optionId) === this.normalizeId(targetId);
- },
- // Plan list helpers
- getPlanRowKey(row) {
- return String(row?.id ?? row?.planId ?? '');
- },
- isReadonlyPlan(row) {
- return READONLY_LAYOUT_STATUSES.includes(String(row?.status ?? ''));
- },
- getOrderStatusCode(row) {
- const status = row?.status;
- if (status && typeof status === 'object') {
- return String(status.code ?? status.value ?? status.id ?? '');
- }
- return String(status ?? '');
- },
- isOrderSchedulingReadonlyRow(row) {
- if (!this.isOrderScheduling) {
- return false;
- }
- return (
- this.planTableStatusTab === 'dispatched' ||
- ['4', '5', '6', '7'].includes(this.getOrderStatusCode(row))
- );
- },
- getPlanActionText(row) {
- if (this.isOrderSchedulingReadonlyRow(row)) {
- return '查看';
- }
- return !this.isOrderScheduling && this.isReadonlyPlan(row)
- ? '查看'
- : '布点';
- },
- getDefaultPlanTableStatusTab(mode = this.schedulingMode) {
- return mode === 'order' ? 'undispatched' : 'unpublished';
- },
- getPlanTableStatusRequestWhere(tabName = this.planTableStatusTab) {
- if (this.isOrderScheduling) {
- return tabName === 'dispatched'
- ? { statusList: [4, 5, 6, 7] }
- : { statusList: [8] };
- }
- return tabName === 'published'
- ? { status: ['4'] }
- : { status: ['2', '3'] };
- },
- getPlanTableStatusRequestWhereList(tabName = this.planTableStatusTab) {
- return [this.getPlanTableStatusRequestWhere(tabName)];
- },
- async handlePlanTableStatusTabClick() {
- this.orderDetailReadonly = false;
- this.orderDetailActionMode = '';
- this.currentPlan = null;
- this.planRoutingPayload = null;
- this.taskList = [];
- this.selection = [];
- this.syncTableSelection('planTable', []);
- await this.reloadPlanTableByStatus();
- },
- async reloadPlanTableByStatus() {
- if (this.isOrderScheduling) {
- await this.reloadOrderSchedulingOrdersByDateRange(this.dateRange);
- } else {
- await this.reloadPlanSchedulingPlansByStatus();
- }
- },
- async reloadPlanSchedulingPlansByStatus(options = {}) {
- const preserveCurrent = !!options.preserveCurrent;
- const currentPlanKey = preserveCurrent
- ? this.getPlanRowKey(this.currentPlan)
- : '';
- this.orderDetailReadonly = false;
- this.orderDetailActionMode = '';
- const loading = this.$loading({
- lock: true,
- text: '加载计划中...'
- });
- let matchedCurrentPlan = null;
- try {
- const requestWhereList = this.getPlanTableStatusRequestWhereList();
- const resultList = await Promise.all(
- requestWhereList.map((where) =>
- getList({
- pageNum: 1,
- size: -1,
- ...this.getSchedulingDateRangeParams(),
- ...where
- })
- )
- );
- const rows = resultList.reduce(
- (list, data) => list.concat(data?.list || []),
- []
- );
- this.allPlanDataList = deepClone(rows);
- this.planDataList = this.buildPlanTableRows(rows, rows);
- if (preserveCurrent && currentPlanKey) {
- matchedCurrentPlan =
- this.planDataList.find(
- (item) => this.getPlanRowKey(item) === currentPlanKey
- ) || null;
- }
- } catch (e) {
- this.$message.error(e.message || '获取计划数据失败');
- } finally {
- loading.close();
- }
- if (preserveCurrent && matchedCurrentPlan) {
- this.currentPlan = {
- ...(this.currentPlan || {}),
- ...matchedCurrentPlan
- };
- this.selection = [matchedCurrentPlan];
- this.syncTableSelection('planTable', [matchedCurrentPlan]);
- return;
- }
- this.selection = [];
- this.currentPlan = null;
- this.planRoutingPayload = null;
- this.taskList = [];
- this.syncTableSelection('planTable', []);
- },
- async handlePlanTableReload() {
- if (this.isOrderScheduling) {
- await this.reloadOrderSchedulingOrdersByDateRange(this.dateRange);
- return;
- }
- await this.refreshSchedulingView();
- await this.reloadPlanSchedulingPlansByStatus({ preserveCurrent: true });
- if (this.currentPlan?.id) {
- await this.loadPlanDotLineDetail(this.currentPlan);
- }
- },
- getSchedulingDateRangeParams() {
- if (!Array.isArray(this.dateRange) || this.dateRange.length !== 2) {
- return {};
- }
- if (!this.dateRange[0] || !this.dateRange[1]) {
- return {};
- }
- return {
- startDate: this.dateRange[0],
- endDate: this.dateRange[1]
- };
- },
- 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
- }));
- },
- buildWorkOrderTableRows(rows = []) {
- return (rows || []).map((item, index) => ({
- ...deepClone(item),
- id: item.id ?? item.code ?? `work-order-${index}`,
- serialNumber: index + 1
- }));
- },
- syncTableSelection(refName, rows) {
- this.$nextTick(() => {
- const table = this.$refs[refName];
- if (!table) {
- return;
- }
- if (refName === 'planPickerTable') {
- this.planPickerSelectionSyncing = true;
- }
- if (refName === 'planTable') {
- this.planTableSelectionSyncing = 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;
- });
- }
- if (refName === 'planTable') {
- this.$nextTick(() => {
- this.planTableSelectionSyncing = false;
- });
- }
- }, 0);
- });
- },
- // Plan picker flow
- async planPickerDatasource({ page, limit, where }) {
- this.planPickerLoading = true;
- try {
- const requestWhere = { ...(where || {}) };
- requestWhere.status = Array.isArray(requestWhere.status)
- ? 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 = createPlanPickerWhere();
- 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 = createPlanPickerWhere();
- 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.orderDetailReadonly = false;
- this.orderDetailActionMode = '';
- this.currentPlan = null;
- this.planRoutingPayload = null;
- this.taskList = [];
- this.workstationOptionsMap = {};
- this.workstationOptionsLoadingMap = {};
- this.selection = [];
- this.dotLineLoading = false;
- await this.refreshSchedulingView();
- this.selection = matchedCurrentPlan ? [matchedCurrentPlan] : [];
- this.syncTableSelection(
- 'planTable',
- matchedCurrentPlan ? [matchedCurrentPlan] : []
- );
- },
- // Selected plan detail
- handlePlanSelectionChange(rows) {
- if (this.planTableSelectionSyncing) {
- return;
- }
- const nextRows = Array.isArray(rows) ? rows : [];
- const row = nextRows[nextRows.length - 1];
- if (!row) {
- this.selection = [];
- this.syncTableSelection('planTable', []);
- return;
- }
- this.handlePublish(row);
- },
- resolveCurrentPlan(row) {
- if (!this.isOrderScheduling) {
- return row;
- }
- return {
- ...row,
- workOrderId: row.id,
- id: row.productionPlanId || row.id,
- code: row.productionPlanCode || row.code,
- productionOrderNumber: row.code,
- startTime: row.planStartTime || row.startTime,
- endTime: row.planCompleteTime || row.endTime,
- productNum: row.formingNum ?? row.productNum,
- unit: row.measuringUnit || row.unit
- };
- },
- handlePublish(row) {
- if (!row.bomCategoryId) {
- this.$message.warning('该计划没有配置BOM请先配置BOM');
- return;
- }
- const readonly = this.isOrderSchedulingReadonlyRow(row);
- this.orderDetailReadonly = readonly;
- this.orderDetailActionMode = readonly ? 'view' : 'edit';
- const currentPlan = this.resolveCurrentPlan(row);
- currentPlan._orderDetailReadonly = readonly;
- currentPlan.orderDetailReadonly = readonly;
- this.currentPlan = currentPlan;
- this.selection = [row];
- this.syncTableSelection('planTable', [row]);
- this.loadPlanDotLineDetail(currentPlan);
- },
- async loadPlanDotLineDetail(currentPlan) {
- this.dotLineLoading = true;
- this.planRoutingPayload = null;
- this.taskList = [];
- try {
- const planData = await getPlanDotLine({ planId: currentPlan.id });
- this.planRoutingPayload = planData || null;
- const details = planData?.detailList;
- if (details?.length) {
- this.taskList = [...details]
- .sort((a, b) => (a.taskSort ?? 0) - (b.taskSort ?? 0))
- .map((item, index) => this.normalizeDetailRow(item, index));
- // 班组排程的“执行对象”支持人员/工位,工位必须按当前工序查询;
- // 工厂排程仍然使用原来的班组执行对象,preload 内部会直接跳过。
- await this.preloadTaskWorkstationOptions();
- this.syncWorkOrderExecutionSelections();
- return;
- }
- if (!currentPlan.produceRoutingId) {
- this.$message.warning('暂无布点明细且缺少工艺路线,无法加载工序');
- return;
- }
- const list = await getTaskListById(currentPlan.produceRoutingId);
- this.taskList = (list || []).map((item, index) =>
- this.normalizeRoutingTaskRow(item, index)
- );
- // 首次没有布点明细时会从工艺路线生成工序,班组排程仍需补齐
- // 每道工序可派工位;工厂排程不加载这部分数据。
- await this.preloadTaskWorkstationOptions();
- this.syncWorkOrderExecutionSelections();
- } catch (e) {
- this.planRoutingPayload = null;
- this.taskList = [];
- this.$message.error(e.message || '获取工序布点信息失败');
- } finally {
- this.dotLineLoading = false;
- }
- },
- normalizeDetailRow(item, index) {
- const execType = toSafeNumber(item.executionType) ?? EXEC_TYPE.HOMEMADE;
- const outsource = execType === EXEC_TYPE.OUTSOURCE;
- const entrust = execType === EXEC_TYPE.ENTRUST;
- const homemade = execType === EXEC_TYPE.HOMEMADE;
- const orderMode = this.isOrderScheduling;
- const savedObjectType =
- orderMode && homemade
- ? item.executionObjectType ||
- item.executionTargetType ||
- item.executionAssigneeType ||
- (item.executionWorkstationId ? 'workstation' : 'person')
- : '';
- const objectId = orderMode
- ? item.executionObjectId ||
- item.executionAssigneeId ||
- item.executionUserId ||
- item.executionCrewId ||
- item.executionWorkstationId ||
- item.executionTeamId ||
- ''
- : item.executionTeamId ?? '';
- const objectName = orderMode
- ? item.executionObjectName ||
- item.executionAssigneeName ||
- item.executionUserName ||
- item.executionCrewName ||
- item.executionWorkstationName ||
- item.executionTeamName ||
- ''
- : item.executionTeamName ?? '';
- const dispatchTeamId =
- item.dispatchTeamId ||
- item.teamId ||
- item.firstTaskTeamId ||
- item.firstTeamId ||
- item.executionTeamId ||
- '';
- const dispatchTeamName =
- item.dispatchTeamName ||
- item.teamName ||
- item.firstTaskTeamName ||
- item.firstTeamName ||
- item.executionTeamName ||
- '';
- const dispatchTeamLeaderId =
- item.dispatchTeamLeaderId ||
- item.teamLeaderUserId ||
- item.leaderUserId ||
- item.executionTeamLeaderId ||
- '';
- const executionStartTime = formatDotLineDateTime(
- item.executionStartTime
- );
- const executionEndTime = formatDotLineDateTime(item.executionEndTime);
- return {
- ...item,
- _taskKey: item.id ?? `detail-${item.taskId ?? index}`,
- isParallelism: toSafeNumber(item.isParallelism) === 1 ? 1 : 0,
- executionStartTime,
- executionEndTime,
- dispatchDefaultStartTime: executionStartTime,
- dispatchDefaultEndTime: executionEndTime,
- executionType: execType,
- executionFactoryId: entrust
- ? item.executionFactoryId ?? item.executionTeamId ?? ''
- : '',
- executionFactoryName: entrust
- ? item.executionFactoryName ?? item.executionTeamName ?? ''
- : '',
- executionTeamId:
- homemade && orderMode && objectId
- ? `${savedObjectType}:${objectId}`
- : homemade
- ? item.executionTeamId ?? ''
- : '',
- rawExecutionTeamId: homemade && orderMode ? objectId : '',
- executionObjectType: homemade && orderMode ? savedObjectType : '',
- executionObjectName: homemade && orderMode ? objectName : '',
- dispatchTeamId,
- dispatchTeamName,
- dispatchTeamLeaderId,
- quantity: orderMode
- ? this.normalizeOrderQuantity(
- item.quantity ??
- item.taskQuantity ??
- item.requiredFormingNum ??
- item.productNum ??
- ''
- )
- : item.quantity,
- executionTeamLeader:
- outsource || entrust ? '' : item.executionTeamLeader ?? '',
- executionTeamLeaderId:
- outsource || entrust ? '' : item.executionTeamLeaderId ?? '',
- executionTeamName: outsource || entrust ? '' : objectName
- };
- },
- normalizeRoutingTaskRow(item, index) {
- const plan = this.currentPlan || {};
- const taskId = item.id;
- const row = {
- ...item,
- _taskKey: item.id ?? `new-${taskId ?? index}-${index}`,
- taskId: taskId ?? undefined,
- taskName: item.name ?? item.taskName,
- taskCode: item.code ?? item.taskCode,
- taskSort: item.taskSort ?? index + 1,
- type: item.type ?? undefined,
- sourceTaskId: item.sourceTaskId,
- planId: plan.id,
- planCode: plan.code,
- routingId: plan.produceRoutingId,
- routingName: plan.produceRoutingName ?? plan.routingName,
- isParallelism: 0,
- executionType: EXEC_TYPE.HOMEMADE,
- executionTeamId: '',
- executionObjectType: '',
- executionObjectName: '',
- dispatchTeamId:
- item.dispatchTeamId ||
- item.teamId ||
- item.firstTaskTeamId ||
- item.firstTeamId ||
- item.executionTeamId ||
- '',
- dispatchTeamName:
- item.dispatchTeamName ||
- item.teamName ||
- item.firstTaskTeamName ||
- item.firstTeamName ||
- item.executionTeamName ||
- '',
- dispatchTeamLeaderId:
- item.dispatchTeamLeaderId ||
- item.teamLeaderUserId ||
- item.leaderUserId ||
- item.executionTeamLeaderId ||
- '',
- executionTeamLeader: '',
- executionTeamLeaderId: '',
- executionTeamName: '',
- executionStartTime: '',
- executionEndTime: '',
- dispatchDefaultStartTime: '',
- dispatchDefaultEndTime: '',
- executionFactoryId: '',
- executionFactoryName: ''
- };
- if (this.isOrderScheduling) {
- row.quantity = '';
- }
- return row;
- },
- taskName(item, index) {
- return item.taskName || item.name || `工艺${index + 1}`;
- },
- routeStepTitle(item, index) {
- return (
- item.taskTypeName || item.taskName || item.name || `工艺${index + 1}`
- );
- },
- isTaskDone(item) {
- return item.executionType != null && item.executionType !== '';
- },
- execTypeNum(row) {
- return toSafeNumber(row.executionType);
- },
- clearExecutionTeamFields(row) {
- this.$set(row, 'executionTeamId', '');
- this.$set(row, 'executionTeamName', '');
- this.$set(row, 'executionObjectType', '');
- this.$set(row, 'executionObjectName', '');
- this.$set(row, 'executionTeamLeader', '');
- this.$set(row, 'executionTeamLeaderId', '');
- this.$set(row, 'executionFactoryId', '');
- 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.findHomemadeOption(row);
- row.executionTeamName = team?.name ?? '';
- row.executionObjectType = team?.objectType ?? '';
- row.executionObjectName = team?.name ?? '';
- row.rawExecutionTeamId = team?.rawId ?? row.executionTeamId;
- row.executionTeamLeader = team?.leaderUserName ?? '';
- row.executionTeamLeaderId = team?.leaderUserId ?? '';
- row.executionFactoryId = '';
- row.executionFactoryName = '';
- } else if (t === EXEC_TYPE.ENTRUST) {
- const f = this.factoryList.find(
- (x) => x.value === row.executionFactoryId
- );
- row.executionFactoryName = f?.label ?? '';
- row.executionTeamId = '';
- row.executionTeamName = '';
- row.executionTeamLeader = '';
- row.executionTeamLeaderId = '';
- }
- },
- async loadTeamOptions() {
- try {
- const factoryId = this.$store.state.user.info.factoryId;
- const res = await teamPage({ pageNum: 1, size: -1, factoryId });
- this.teamOptions = (res?.list || []).map(
- ({ id, name, leaderUserName, leaderUserId }) => ({
- id,
- name,
- objectType: 'team',
- leaderUserName,
- leaderUserId
- })
- );
- } catch {
- this.teamOptions = [];
- }
- },
- async loadApprovalReviewConfig() {
- try {
- const res = await parameterGetByCode({
- code: 'planned_release_require'
- });
- this.isReview = res?.value == '1';
- } catch (e) {
- this.isReview = false;
- }
- },
- async loadWorkOrderExecutionOptions() {
- // 班组排程自制执行对象分两类:
- // - 人员:打开弹窗时一次性加载,所有工序共用。
- // - 工位:依赖具体工序,不能取全厂工位,需要在工序加载后按 taskId 查询。
- try {
- const res = await getUserPage({ pageNum: 1, size: -1 });
- const users = Array.isArray(res?.list) ? res.list : [];
- this.teamOptions = users.map((item) => ({
- id: `person:${item.id}`,
- rawId: item.id,
- objectType: 'person',
- name: `人员:${
- item.name ||
- item.userName ||
- item.loginName ||
- item.jobNumber ||
- item.id
- }`,
- leaderUserName: item.name || item.userName || '',
- leaderUserId: item.id
- }));
- } catch {
- this.teamOptions = [];
- }
- this.syncWorkOrderExecutionSelections();
- },
- getTaskWorkstationKey(row) {
- const rowTaskId = row?.taskId;
- const currentTaskId = this.currentPlan?.taskId;
- const taskId =
- row?.sourceTaskId ??
- row?.firstTaskId ??
- (rowTaskId && Number(rowTaskId) >= 0 ? rowTaskId : undefined) ??
- (currentTaskId && Number(currentTaskId) >= 0
- ? currentTaskId
- : undefined) ??
- this.currentPlan?.firstTaskId ??
- '';
- return taskId === null || taskId === undefined || taskId === ''
- ? ''
- : String(taskId);
- },
- formatWorkstationOptions(list = []) {
- return (Array.isArray(list) ? list : []).map((item) => {
- const substance =
- item.workstationSubstanceList?.[0]?.substance ||
- item.substance ||
- item.asset ||
- {};
- const category =
- substance.category?.category || substance.category || {};
- const name =
- item.name || item.workstationName || item.stationName || item.id;
- const code = item.code || item.workstationCode || item.stationCode;
- const displayName = code ? `${name}(${code})` : name;
- const rawId =
- item.id ??
- item.workstationId ??
- item.factoryWorkstationId ??
- item.stationId ??
- code ??
- name;
- return {
- id: `workstation:${rawId}`,
- rawId,
- objectType: 'workstation',
- name: `工位:${displayName}`,
- code: item.code || item.workstationCode || item.stationCode || '',
- workstationCode:
- item.workstationCode || item.code || item.stationCode || '',
- assetCode: this.pickBackendTextValue(
- item.assetCode,
- item.deviceCode,
- item.extInfo?.assetCode,
- item.extInfo?.deviceCode,
- substance.code
- ),
- assetId:
- this.normalizeBackendLongId(item.assetId) ||
- this.normalizeBackendLongId(item.deviceId) ||
- this.normalizeBackendLongId(item.extInfo?.assetId) ||
- this.normalizeBackendLongId(substance.id) ||
- '',
- assetName: this.pickBackendTextValue(
- item.assetName,
- item.deviceName,
- item.extInfo?.assetName,
- item.extInfo?.deviceName,
- substance.name
- ),
- assetModelType:
- item.assetModelType ||
- item.deviceModel ||
- category.modelType ||
- '',
- assetCategoryLevelPath:
- item.assetCategoryLevelPath ||
- item.deviceType ||
- category.categoryLevelPath?.split('-')?.[0] ||
- '',
- extInfo: item.extInfo || {},
- isNew: item.isNew || '',
- leaderUserName: item.leaderName || '',
- leaderUserId: item.leaderId || ''
- };
- });
- },
- async loadWorkstationOptionsByTaskId(taskId) {
- const key =
- taskId === null || taskId === undefined || taskId === ''
- ? ''
- : String(taskId);
- // 这里复用任务派单“工序派单”的工位来源:
- // /main/factoryworkstation/listByFirstTaskId/{taskId}
- // 这样每行工序只展示它能派发的工位。isOrderScheduling 用来保证
- // 该接口不会在工厂排程中触发,避免改变现有工厂排程数据链路。
- if (
- !this.isOrderScheduling ||
- !key ||
- this.workstationOptionsMap[key] ||
- this.workstationOptionsLoadingMap[key]
- ) {
- return;
- }
- this.$set(this.workstationOptionsLoadingMap, key, true);
- try {
- const res = await listByFirstTaskId(taskId);
- this.$set(
- this.workstationOptionsMap,
- key,
- this.formatWorkstationOptions(res)
- );
- this.syncWorkOrderExecutionSelections();
- } catch {
- this.$set(this.workstationOptionsMap, key, []);
- } finally {
- this.$delete(this.workstationOptionsLoadingMap, key);
- }
- },
- preloadTaskWorkstationOptions(rows = this.taskList) {
- if (!this.isOrderScheduling) {
- return Promise.resolve();
- }
- // 多个工序可能引用同一个 taskId,去重后再请求,减少重复接口调用。
- const taskIds = Array.from(
- new Set(
- (rows || [])
- .map((row) => this.getTaskWorkstationKey(row))
- .filter(Boolean)
- )
- );
- return Promise.all(
- taskIds.map((taskId) => this.loadWorkstationOptionsByTaskId(taskId))
- );
- },
- getHomemadeOptions(row) {
- if (!this.isOrderScheduling) {
- return this.teamOptions;
- }
- // 班组排程的自制执行对象 = 人员 + 当前工序可派工位。
- // 下拉框每一行调用一次该方法,因此这里按行拿 taskId,再合并该行的
- // 工位缓存;若缓存还没回来,先显示人员,接口返回后 Vue 会刷新下拉。
- const key = this.getTaskWorkstationKey(row);
- if (key && !this.workstationOptionsMap[key]) {
- this.loadWorkstationOptionsByTaskId(key);
- }
- return [
- ...this.teamOptions,
- ...(key ? this.workstationOptionsMap[key] || [] : [])
- ];
- },
- findHomemadeOption(row) {
- return this.getHomemadeOptions(row).find((option) =>
- this.optionMatchesId(option.id, row.executionTeamId)
- );
- },
- syncWorkOrderExecutionSelections() {
- if (!this.isOrderScheduling) {
- return;
- }
- // 前端下拉为避免人员和工位 id 冲突,会加 person:/workstation: 前缀,
- // 所以工位选项加载完成后需要把原始 id 回填成下拉可识别的值。
- this.taskList.forEach((row) => {
- if (this.execTypeNum(row) !== EXEC_TYPE.HOMEMADE) return;
- const objectType = row.executionObjectType || 'person';
- const matched = this.getHomemadeOptions(row).find(
- (option) =>
- option.objectType === objectType &&
- this.optionMatchesId(option.rawId, row.rawExecutionTeamId)
- );
- if (matched) {
- this.$set(row, 'executionTeamId', matched.id);
- this.$set(row, 'executionTeamName', matched.name);
- this.$set(row, 'executionObjectName', matched.name);
- this.$set(row, 'rawExecutionTeamId', matched.rawId);
- }
- });
- },
- async loadFactoryOptions() {
- try {
- const res = await getFactoryarea({ type: 1, size: 9999 });
- if (res.list?.length) {
- this.factoryList = res.list.map((el) => ({
- value: el.id,
- label: el.name
- }));
- } else {
- this.factoryList = [];
- }
- } catch {
- this.factoryList = [];
- }
- },
- // Task config editing
- handleTimeChange(row, changeKey) {
- if (this.isCurrentPlanReadonly) {
- return;
- }
- if (isEndBeforeStart(row.executionStartTime, row.executionEndTime)) {
- this.$message.warning('执行结束时间不能小于执行开始时间');
- this.$set(row, changeKey, '');
- return;
- }
- if (
- changeKey === 'executionStartTime' &&
- this.isExecutionStartBeforePlanStart(row.executionStartTime)
- ) {
- this.$message.warning('执行开始时间不能小于计划开始时间');
- this.$set(row, changeKey, '');
- return;
- }
- if (!this.validateTaskSequence(row, { resetKey: changeKey })) {
- return;
- }
- const deadline =
- this.currentPlan?.planDeliveryTime || this.currentPlan?.deliveryTime;
- if (
- changeKey === 'executionStartTime' &&
- deadline &&
- row.executionStartTime
- ) {
- const d = new Date(deadline).getTime();
- const v = new Date(row.executionStartTime).getTime();
- if (!Number.isNaN(d) && !Number.isNaN(v) && v > d) {
- this.$message.warning('时间不能大于计划交付时间');
- this.$set(row, changeKey, '');
- return;
- }
- }
- if (changeKey === 'executionEndTime' && row.executionEndTime) {
- const planEnd = this.currentPlan?.endTime;
- const overDelivery =
- !!deadline && isEndAfterPlanEnd(row.executionEndTime, deadline);
- const overPlanEnd =
- !!planEnd && isEndAfterPlanEnd(row.executionEndTime, planEnd);
- if (overDelivery && overPlanEnd) {
- this.$message.warning(
- '执行结束时间晚于计划交付时间及计划结束时间,已保留填写内容,请留意核对'
- );
- } else if (overDelivery) {
- this.$message.warning(
- '执行结束时间晚于计划交付时间,已保留填写内容,请留意核对'
- );
- } else if (overPlanEnd) {
- this.$message.warning(
- '执行结束时间晚于计划结束时间,已保留填写内容,请留意核对'
- );
- }
- }
- },
- handleParallelismChange(row) {
- if (this.isCurrentPlanReadonly || !row?.executionStartTime) {
- return;
- }
- if (this.isTaskParallel(row)) {
- if (this.isExecutionStartBeforePlanStart(row.executionStartTime)) {
- this.$message.warning('执行开始时间不能小于计划开始时间');
- this.$set(row, 'executionStartTime', '');
- }
- return;
- }
- this.validateTaskSequence(row, { resetKey: 'executionStartTime' });
- },
- isExecutionEndOverDelivery(row) {
- const deadline =
- this.currentPlan?.planDeliveryTime || this.currentPlan?.deliveryTime;
- return (
- !!deadline &&
- !!row?.executionEndTime &&
- isEndAfterPlanEnd(row.executionEndTime, deadline)
- );
- },
- isExecutionEndOverPlanEnd(row) {
- const planEnd = this.currentPlan?.endTime;
- return (
- !!planEnd &&
- !!row?.executionEndTime &&
- isEndAfterPlanEnd(row.executionEndTime, planEnd)
- );
- },
- toggleDotLineFullscreen() {
- this.dotLineFullscreen = !this.dotLineFullscreen;
- },
- getTaskLabel(item, index) {
- if (item) {
- return (
- item.taskName ||
- item.name ||
- (typeof index === 'number' ? `工艺${index + 1}` : '当前工艺')
- );
- }
- return index !== undefined ? `工艺${index + 1}` : '当前工艺';
- },
- getTaskIndex(row) {
- return this.taskList.findIndex((item) => item === row);
- },
- getTaskSequenceGroupKey(row) {
- const key =
- row?.sourceTaskId ??
- row?.firstTaskId ??
- row?.taskId ??
- row?.id ??
- row?._taskKey ??
- '';
- return key === null || key === undefined ? '' : String(key);
- },
- getTaskSequenceSortValue(row, index) {
- const values = [
- row?.taskSort,
- row?.sort,
- row?.sequence,
- row?.taskSequence,
- row?.orderNo,
- row?.sortNo
- ];
- for (const value of values) {
- if (value !== null && value !== undefined && value !== '') {
- const num = Number(value);
- if (!Number.isNaN(num)) {
- return num;
- }
- }
- }
- return index;
- },
- getTaskSequenceBoundary(group, timeKey, pickLatest) {
- if (!group?.items?.length) {
- return null;
- }
- return group.items.reduce((best, item, index) => {
- const value = item?.[timeKey];
- if (!value) {
- return best;
- }
- const time = new Date(value).getTime();
- if (Number.isNaN(time)) {
- return best;
- }
- if (!best || (pickLatest ? time > best.time : time < best.time)) {
- return {
- item,
- time,
- index
- };
- }
- return best;
- }, null);
- },
- getOrderTaskSequenceContext(row) {
- const currentKey = this.getTaskSequenceGroupKey(row);
- if (!currentKey) {
- return null;
- }
- const groupMap = new Map();
- (this.taskList || []).forEach((item, index) => {
- const key = this.getTaskSequenceGroupKey(item);
- if (!key) {
- return;
- }
- if (!groupMap.has(key)) {
- groupMap.set(key, {
- key,
- firstIndex: index,
- sortValue: this.getTaskSequenceSortValue(item, index),
- items: []
- });
- }
- const group = groupMap.get(key);
- group.sortValue = Math.min(
- group.sortValue,
- this.getTaskSequenceSortValue(item, index)
- );
- group.items.push(item);
- });
- const groups = Array.from(groupMap.values()).sort(
- (a, b) => a.sortValue - b.sortValue || a.firstIndex - b.firstIndex
- );
- const groupIndex = groups.findIndex((item) => item.key === currentKey);
- if (groupIndex < 0) {
- return null;
- }
- const prevGroup = groups[groupIndex - 1];
- const nextGroup = groups[groupIndex + 1];
- const prevBoundary = this.getTaskSequenceBoundary(
- prevGroup,
- 'executionEndTime',
- true
- );
- const nextBoundary = this.getTaskSequenceBoundary(
- nextGroup,
- 'executionStartTime',
- false
- );
- return {
- index: groupIndex,
- prev: prevBoundary?.item || null,
- prevIndex: groupIndex - 1,
- next: nextBoundary?.item || null,
- nextIndex: groupIndex + 1
- };
- },
- getTaskSequenceContext(row) {
- const index = this.getTaskIndex(row);
- if (index < 0) {
- return null;
- }
- if (!this.isOrderScheduling) {
- return {
- index,
- prev: this.taskList[index - 1],
- prevIndex: index - 1,
- next: this.taskList[index + 1],
- nextIndex: index + 1
- };
- }
- return (
- this.getOrderTaskSequenceContext(row) || {
- index,
- prev: this.taskList[index - 1],
- prevIndex: index - 1,
- next: this.taskList[index + 1],
- nextIndex: index + 1
- }
- );
- },
- compareTaskTime(start, end) {
- if (!start || !end) return false;
- const startTime = new Date(start).getTime();
- const endTime = new Date(end).getTime();
- return (
- !Number.isNaN(startTime) &&
- !Number.isNaN(endTime) &&
- startTime < endTime
- );
- },
- getCurrentPlanStartTime() {
- return (
- this.currentPlan?.startTime ||
- this.currentPlan?.planStartTime ||
- this.currentPlan?.executionStartTime ||
- ''
- );
- },
- isExecutionStartBeforePlanStart(executionStartTime) {
- if (this.isOrderScheduling || !executionStartTime) {
- return false;
- }
- const planStartTime = this.getCurrentPlanStartTime();
- if (!planStartTime) {
- return false;
- }
- const executionStart = new Date(executionStartTime).getTime();
- const planStart = new Date(planStartTime).getTime();
- return (
- !Number.isNaN(executionStart) &&
- !Number.isNaN(planStart) &&
- executionStart < planStart
- );
- },
- isTaskParallel(row) {
- return toSafeNumber(row?.isParallelism) === 1;
- },
- validateTaskSequence(row, options = {}) {
- const sequence = this.getTaskSequenceContext(row);
- if (!sequence) {
- return true;
- }
- const { index, prev, prevIndex, next, nextIndex } = sequence;
- const resetKey = options.resetKey;
- const checkPrev =
- (!resetKey || resetKey === 'executionStartTime') &&
- !this.isTaskParallel(row);
- const checkNext =
- (!resetKey || resetKey === 'executionEndTime') &&
- !this.isTaskParallel(next);
- if (
- checkPrev &&
- prev?.executionEndTime &&
- row.executionStartTime &&
- this.compareTaskTime(row.executionStartTime, prev.executionEndTime)
- ) {
- this.$message.warning(
- `${this.getTaskLabel(
- row,
- index
- )}执行开始时间不能小于上道工序${this.getTaskLabel(
- prev,
- prevIndex
- )}的执行结束时间`
- );
- if (resetKey) {
- this.$set(row, resetKey, '');
- }
- return false;
- }
- if (
- checkNext &&
- next?.executionStartTime &&
- row.executionEndTime &&
- this.compareTaskTime(next.executionStartTime, row.executionEndTime)
- ) {
- this.$message.warning(
- `下道工序${this.getTaskLabel(
- next,
- nextIndex
- )}的执行开始时间不能小于上道工序${this.getTaskLabel(
- row,
- index
- )}的执行结束时间`
- );
- if (resetKey) {
- this.$set(row, resetKey, '');
- }
- return false;
- }
- return true;
- },
- validateTaskRow(item, options = {}) {
- const label = (t) => this.getTaskLabel(t);
- const checkSequence = options.checkSequence !== false;
- const execType = toSafeNumber(item.executionType);
- if (execType === undefined) {
- this.$message.warning(`${label(item)}请选择执行模式`);
- return false;
- }
- if (execType === EXEC_TYPE.HOMEMADE) {
- if (
- !item.executionTeamId ||
- !item.executionStartTime ||
- !item.executionEndTime
- ) {
- this.$message.warning(
- `${label(
- item
- )}执行模式为自制时,需选择执行班组并填写执行开始和结束时间`
- );
- return false;
- }
- }
- if (execType === EXEC_TYPE.ENTRUST) {
- if (
- !item.executionFactoryId ||
- !item.executionStartTime ||
- !item.executionEndTime
- ) {
- this.$message.warning(
- `${label(
- item
- )}执行模式为请托时,需选择工厂并填写执行开始和结束时间`
- );
- return false;
- }
- }
- if (execType === EXEC_TYPE.OUTSOURCE) {
- if (!item.executionStartTime || !item.executionEndTime) {
- this.$message.warning(
- `${label(item)}执行模式为委外时,需填写执行开始和结束时间`
- );
- return false;
- }
- }
- if (isEndBeforeStart(item.executionStartTime, item.executionEndTime)) {
- this.$message.warning(
- `${label(item)}执行结束时间不能小于执行开始时间`
- );
- return false;
- }
- if (this.isExecutionStartBeforePlanStart(item.executionStartTime)) {
- this.$message.warning(
- `${label(item)}执行开始时间不能小于计划开始时间`
- );
- return false;
- }
- const deadline =
- this.currentPlan?.planDeliveryTime || this.currentPlan?.deliveryTime;
- if (deadline) {
- const d = new Date(deadline).getTime();
- const checkStart = (val, name) => {
- if (!val) return true;
- const t = new Date(val).getTime();
- return Number.isNaN(d) || Number.isNaN(t) || t <= d
- ? true
- : (this.$message.warning(
- `${label(item)}${name}不能大于计划交付时间`
- ),
- false);
- };
- if (!checkStart(item.executionStartTime, '执行开始时间'))
- return false;
- }
- return checkSequence ? this.validateTaskSequence(item) : true;
- },
- validateTaskList() {
- for (const item of this.taskList) {
- if (!this.validateTaskRow(item)) return false;
- }
- return true;
- },
- addTaskRow(row) {
- if (row) {
- this.taskList.push(row);
- }
- },
- removeTaskRow(row) {
- const index = this.taskList.findIndex((item) => item === row);
- if (index >= 0) {
- this.taskList.splice(index, 1);
- }
- },
- addDispatchStations({ task, list }) {
- if (!this.isOrderScheduling || !task) {
- return;
- }
- const key = this.getTaskWorkstationKey(task);
- if (!key) {
- this.$message.warning('当前工序缺少任务信息,无法添加工位');
- return;
- }
- const currentList = this.workstationOptionsMap[key] || [];
- const nextList = [...currentList];
- const existedIds = new Set(currentList.map((item) => String(item.id)));
- this.formatWorkstationOptions(list).forEach((item) => {
- if (!existedIds.has(String(item.id))) {
- existedIds.add(String(item.id));
- nextList.push(item);
- }
- });
- this.$set(this.workstationOptionsMap, key, nextList);
- },
- removeDispatchStation({ task, row }) {
- if (!this.isOrderScheduling || !task || !row) {
- return;
- }
- const key = this.getTaskWorkstationKey(task);
- if (!key) {
- return;
- }
- const rowIds = new Set(
- [row.id, row.rawId, row.workstationId, row.factoryWorkstationId]
- .filter(
- (item) => item !== null && item !== undefined && item !== ''
- )
- .map((item) => String(item))
- );
- const currentList = this.workstationOptionsMap[key] || [];
- const nextList = currentList.filter((item) => {
- const itemIds = [
- item.id,
- item.rawId,
- item.workstationId,
- item.factoryWorkstationId
- ]
- .filter(
- (value) => value !== null && value !== undefined && value !== ''
- )
- .map((value) => String(value));
- return !itemIds.some((id) => rowIds.has(id));
- });
- this.$set(this.workstationOptionsMap, key, nextList);
- },
- async handleDispatchActionSuccess(event = {}) {
- if (!this.isOrderScheduling) {
- return;
- }
- if (event.type === 'confirm') {
- await this.reloadOrderSchedulingOrdersByDateRange(this.dateRange);
- return;
- }
- await this.refreshSchedulingView();
- },
- buildSavePayload(isCache = 0) {
- const plan = this.currentPlan;
- const head = this.planRoutingPayload || {};
- const detailList = this.taskList.map((row) => {
- const execType = toSafeNumber(row.executionType) ?? 0;
- // 只有班组排程需要把带前缀的下拉值还原为人员/工位原始 id;
- // 工厂排程保持原 executionTeamId 保存方式。
- const homemadeOption = this.isOrderScheduling
- ? this.findHomemadeOption(row)
- : null;
- const detail = {
- executionEndTime: row.executionEndTime || undefined,
- executionStartTime: row.executionStartTime || undefined,
- executionTeamId:
- execType === EXEC_TYPE.HOMEMADE
- ? (this.isOrderScheduling
- ? homemadeOption?.rawId ||
- row.rawExecutionTeamId ||
- row.executionTeamId
- : row.executionTeamId) || undefined
- : undefined,
- executionTeamLeader: row.executionTeamLeader || '',
- executionTeamLeaderId: row.executionTeamLeaderId || '',
- executionTeamName: row.executionTeamName || '',
- executionType: execType,
- isParallelism: this.isTaskParallel(row) ? 1 : 0,
- planCode: row.planCode || plan?.code,
- planId: row.planId ?? plan?.id,
- routingId:
- row.routingId ?? plan?.produceRoutingId ?? head.routingId,
- routingName:
- row.routingName || plan?.produceRoutingName || head.routingName,
- sourceTaskId: row.sourceTaskId ?? row.taskId,
- taskCode: row.taskCode,
- taskId: row.taskId,
- taskName: row.taskName,
- taskSort: row.taskSort,
- type: toSafeNumber(row.type)
- };
- // 数量输入框是班组排程工序配置新增字段,工厂排程不写入,
- // 避免改变原工厂排程保存 payload。
- if (this.isOrderScheduling) {
- detail.quantity =
- row.quantity !== '' &&
- row.quantity !== null &&
- row.quantity !== undefined
- ? row.quantity
- : undefined;
- }
- // 班组排程自制模式下,执行对象可能是人员也可能是工位;
- // executionObject* 字段用于明确记录对象类型和值,委外/外协不写入。
- if (this.isOrderScheduling && execType === EXEC_TYPE.HOMEMADE) {
- detail.executionObjectId =
- homemadeOption?.rawId ||
- row.rawExecutionTeamId ||
- row.executionTeamId ||
- undefined;
- detail.executionObjectType = row.executionObjectType || undefined;
- detail.executionObjectName =
- row.executionObjectName || row.executionTeamName || '';
- }
- if (execType === EXEC_TYPE.ENTRUST) {
- detail.executionFactoryId = row.executionFactoryId || undefined;
- detail.executionFactoryName = row.executionFactoryName || '';
- }
- if (row.id != null) detail.id = row.id;
- return detail;
- });
- return {
- detailList,
- fileParam: head.fileParam ?? [],
- id: head.id,
- isCache,
- planCode: plan?.code ?? head.planCode,
- planId: plan?.id ?? head.planId,
- produceVersionId: head.produceVersionId,
- produceVersionName: head.produceVersionName,
- releaseTime: head.releaseTime,
- routingCode: head.routingCode,
- routingId: head.routingId ?? plan?.produceRoutingId,
- routingName: head.routingName ?? plan?.produceRoutingName,
- routingVersion: head.routingVersion
- };
- },
- // Scheduling data refresh
- async handleTabChange(tabKey) {
- this.activeTab = tabKey;
- await this.refreshSchedulingView();
- if (!this.isOrderScheduling) {
- await this.reloadPlanSchedulingPlansByStatus();
- }
- },
- async handleDateChange(val) {
- this.dateRange = val;
- if (this.isOrderScheduling) {
- this.scheduleOrderSchedulingDateReload(val);
- return;
- }
- await this.refreshSchedulingView();
- await this.reloadPlanSchedulingPlansByStatus();
- },
- handleDateInput(val) {
- if (!this.isOrderScheduling) {
- return;
- }
- this.dateRange = val;
- this.scheduleOrderSchedulingDateReload(val);
- },
- scheduleOrderSchedulingDateReload(dateRange) {
- if (!Array.isArray(dateRange) || dateRange.length !== 2) {
- return;
- }
- if (!dateRange[0] || !dateRange[1]) {
- return;
- }
- if (this.orderDateReloadTimer) {
- clearTimeout(this.orderDateReloadTimer);
- }
- this.orderDateReloadTimer = setTimeout(() => {
- this.orderDateReloadTimer = null;
- this.reloadOrderSchedulingOrdersByDateRange(dateRange);
- }, 0);
- },
- async reloadOrderSchedulingOrdersByDateRange(dateRange) {
- this.orderDetailReadonly = false;
- this.orderDetailActionMode = '';
- if (typeof this.orderListLoader !== 'function') {
- await this.refreshSchedulingView();
- return;
- }
- const loading = this.$loading({
- lock: true,
- text: '加载订单中...'
- });
- try {
- const list = await this.orderListLoader(
- dateRange,
- this.getPlanTableStatusRequestWhere()
- );
- this.selectedWorkOrders = this.buildWorkOrderTableRows(list);
- this.planDataList = this.selectedWorkOrders;
- const currentWorkOrderId = this.currentPlan?.workOrderId;
- const matchedCurrentOrder = currentWorkOrderId
- ? this.selectedWorkOrders.find(
- (item) => String(item.id) === String(currentWorkOrderId)
- )
- : null;
- if (matchedCurrentOrder) {
- const currentPlan = this.resolveCurrentPlan(matchedCurrentOrder);
- this.orderDetailReadonly =
- this.isOrderSchedulingReadonlyRow(matchedCurrentOrder);
- this.orderDetailActionMode = this.orderDetailReadonly
- ? 'view'
- : 'edit';
- currentPlan._orderDetailReadonly = this.orderDetailReadonly;
- currentPlan.orderDetailReadonly = this.orderDetailReadonly;
- this.currentPlan = currentPlan;
- this.selection = [matchedCurrentOrder];
- this.syncTableSelection('planTable', [matchedCurrentOrder]);
- await this.loadPlanDotLineDetail(currentPlan);
- } else {
- this.orderDetailReadonly = false;
- this.orderDetailActionMode = '';
- this.currentPlan = null;
- this.planRoutingPayload = null;
- this.taskList = [];
- this.selection = [];
- this.syncTableSelection('planTable', []);
- }
- await this.refreshSchedulingView();
- } catch (e) {
- this.$message.error(e.message || '获取订单数据失败');
- } finally {
- loading.close();
- }
- },
- extractPlanIdsFromGanttData(data = []) {
- const planIds = new Set();
- (Array.isArray(data) ? data : []).forEach((item) => {
- (item.resourcePeriods || []).forEach((period) => {
- if (period.planId) planIds.add(period.planId);
- });
- (item.resourceList || []).forEach((resource) => {
- if (resource.planId) planIds.add(resource.planId);
- });
- });
- return Array.from(planIds);
- },
- async syncPlanTableRowsByGanttData(data = []) {
- if (this.isOrderScheduling) {
- return;
- }
- const planIds = this.extractPlanIdsFromGanttData(data);
- if (!planIds.length) {
- return;
- }
- const plans = await this.loadPlansByIds(planIds);
- this.planDataList = this.buildPlanTableRows(plans);
- },
- async refreshSchedulingView() {
- let data;
- const params = {};
- if (this.dateRange && this.dateRange.length === 2) {
- params.startDate = this.dateRange[0];
- params.endDate = this.dateRange[1];
- }
- // 班组排程从工单页面进入,用户选择的是工单数据;
- // 甘特接口仍按计划维度查询,所以这里提取工单关联的 productionPlanId
- // 作为 planIds 过滤条件。工厂排程本身就是计划入口,不需要该过滤。
- if (this.isOrderScheduling) {
- const planIds = Array.from(
- new Set(
- (this.selectedWorkOrders || [])
- .map((item) => item.productionPlanId)
- .filter((id) => id !== null && id !== undefined && id !== '')
- )
- );
- if (planIds.length) {
- params.planIds = planIds;
- } else {
- this.calendarSourceData = [];
- this.tableData = [];
- return;
- }
- }
- if (this.activeTab === 'factory') {
- // 工厂维度甘特:
- // - 工厂排程默认进入该页签,继续使用 factorySchedulingGanttChart。
- data = await factorySchedulingGanttChart(params);
- await this.syncPlanTableRowsByGanttData(data);
- } else if (this.isOrderScheduling) {
- // 班组排程甘特按工单关联计划过滤,再使用工位/设备/人员专用接口。
- // 展示结构仍交给现有甘特组件处理,保持和工厂排产一致的树形时间轴形式。
- data = await workstationSchedulingGanttChart(params);
- data = this.filterOrderSchedulingGanttDataByTab(data || []);
- } else {
- // 班组/工位维度甘特:
- // 工厂排程切到该页签时保持原班组甘特逻辑;
- data = await teamSchedulingGanttChart(params);
- await this.syncPlanTableRowsByGanttData(data);
- }
- this.calendarSourceData = this.normalizeOrderSchedulingGanttData(
- data || []
- );
- this.tableData = this.initGanttFlatRows(this.calendarSourceData);
- },
- getOrderSchedulingGanttWorkstationType(tabKey = this.activeTab) {
- const workstationTypeMap = {
- line: 1,
- person: 3
- };
- return workstationTypeMap[tabKey];
- },
- hasOrderSchedulingDeviceResource(item = {}) {
- return !!(item.deviceId || item.deviceCode || item.deviceName);
- },
- getOrderSchedulingAssigneeUsers(item = {}) {
- if (Array.isArray(item.assigneeUser)) {
- return item.assigneeUser;
- }
- if (item.assigneeUser && typeof item.assigneeUser === 'object') {
- return [item.assigneeUser];
- }
- return [];
- },
- buildOrderSchedulingDeviceResource(item = {}) {
- const deviceName =
- item.deviceName || item.equipmentName || item.assetName || '';
- const deviceCode = item.deviceCode || item.assetCode || '';
- const deviceId =
- this.normalizeBackendLongId(item.deviceId) ||
- this.normalizeBackendLongId(item.assetId) ||
- this.normalizeBackendLongId(item.extInfo?.assetId);
- const resourceId =
- deviceId || deviceCode || deviceName || item.resourceId;
- const workstationName = item.name || item.resourceName || '';
- const workstationCode = item.workstationCode || item.code || '';
- return {
- ...item,
- resourceId: `device:${resourceId || ''}`,
- resourceName: deviceName || deviceCode || workstationName,
- resourceCode: deviceCode,
- deviceId,
- deviceCode,
- deviceName,
- equipmentName: deviceName || deviceCode,
- workstationName,
- workstationCode,
- originResourceName: item.resourceName || item.name || '',
- originResourceCode: item.resourceCode || item.code || '',
- orderResourceTabType: 'device'
- };
- },
- buildOrderSchedulingPersonResources(item = {}) {
- const workstationName = item.name || item.resourceName || '';
- const workstationCode = item.workstationCode || item.code || '';
- return this.getOrderSchedulingAssigneeUsers(item)
- .map((user, index) => {
- const userName =
- user?.userName || user?.name || user?.nickName || '';
- const userId = user?.userId || user?.id || userName;
- const jobNumber = user?.jobNumber || user?.code || '';
- if (!userName && !userId && !jobNumber) {
- return null;
- }
- return {
- ...item,
- resourceId: `person:${userId || index}-${
- item.resourceId || item.taskId || item.sourceTaskId || index
- }`,
- resourceName: userName || jobNumber || String(userId),
- resourceCode: jobNumber || String(userId || ''),
- assigneeUser: [user],
- assigneeUserId: userId,
- assigneeUserName: userName,
- assigneeJobNumber: jobNumber,
- executionObjectType: 'person',
- executionObjectName: userName || jobNumber || String(userId),
- workstationName,
- workstationCode,
- originResourceName: item.resourceName || item.name || '',
- originResourceCode: item.resourceCode || item.code || '',
- orderResourceTabType: 'person'
- };
- })
- .filter(Boolean);
- },
- filterOrderSchedulingGanttDataByTab(data = []) {
- const list = Array.isArray(data) ? data : [];
- if (this.activeTab === 'line') {
- return list
- .filter((item) => this.hasOrderSchedulingDeviceResource(item))
- .map((item) => this.buildOrderSchedulingDeviceResource(item));
- }
- if (this.activeTab === 'person') {
- return list.flatMap((item) =>
- this.buildOrderSchedulingPersonResources(item)
- );
- }
- const targetWorkstationType =
- this.getOrderSchedulingGanttWorkstationType();
- if (!targetWorkstationType) {
- return list;
- }
- return list.filter(
- (item) => Number(item?.workstationType) === targetWorkstationType
- );
- },
- getOrderSchedulingResourceInfo(item = {}) {
- const resourceName = item.resourceName || item.name || '';
- const resourceCode = item.resourceCode || item.code || '';
- const resourceType = Number(item.type);
- const workstationType = Number(item.workstationType);
- const deviceName =
- item.deviceName || item.equipmentName || item.assetName || '';
- const deviceCode = item.deviceCode || item.assetCode || '';
- const personName =
- item.executionObjectName || item.assigneeUserName || '';
- return {
- resourceName,
- resourceCode,
- resourceType,
- workstationType,
- orderResourceTabType: item.orderResourceTabType || '',
- deviceId:
- this.normalizeBackendLongId(item.deviceId) ||
- this.normalizeBackendLongId(item.assetId) ||
- this.normalizeBackendLongId(item.extInfo?.assetId) ||
- '',
- deviceCode,
- deviceName,
- assigneeUser: item.assigneeUser || [],
- assigneeUserId: item.assigneeUserId || '',
- assigneeUserName: item.assigneeUserName || '',
- assigneeJobNumber: item.assigneeJobNumber || '',
- workstationName:
- item.workstationName ||
- item.stationName ||
- (resourceType === 1 ? resourceName : ''),
- equipmentName:
- item.equipmentName ||
- deviceName ||
- item.assetName ||
- (item.orderResourceTabType === 'device' ? resourceName : ''),
- executionObjectType:
- item.executionObjectType || (workstationType === 3 ? 'person' : ''),
- executionObjectName:
- item.executionObjectName ||
- personName ||
- (workstationType === 3 ? resourceName : '')
- };
- },
- normalizeOrderSchedulingPlanRef(source = {}) {
- const productionOrderNumber =
- source.productionOrderNumber ||
- source.apsWorkOrderCode ||
- source.workOrderCode ||
- source.orderCode ||
- '';
- return {
- planId: source.planId ?? source.apsWorkOrderId ?? source.workOrderId,
- planCode:
- source.planCode ||
- source.apsWorkOrderCode ||
- source.workOrderCode ||
- '',
- productionOrderNumber
- };
- },
- getOrderSchedulingDisplayOrderCode(...sources) {
- for (const source of sources) {
- if (!source) continue;
- const code =
- source.productionOrderNumber ||
- source.apsWorkOrderCode ||
- source.workOrderCode ||
- source.orderCode ||
- source.planCode ||
- '';
- if (code) return code;
- }
- return '';
- },
- buildGanttPlanLabel(planInfo = {}, fallbackInfo = {}) {
- const displayCode = this.isOrderScheduling
- ? this.getOrderSchedulingDisplayOrderCode(planInfo, fallbackInfo)
- : planInfo.planCode || '';
- return [displayCode, planInfo.productName || fallbackInfo.productName]
- .filter(Boolean)
- .join(' ');
- },
- normalizeOrderSchedulingGanttData(data = []) {
- if (!this.isOrderScheduling) {
- return data;
- }
- return (Array.isArray(data) ? data : []).map((item) => {
- const resourceInfo = this.getOrderSchedulingResourceInfo(item);
- const resourceList = Array.isArray(item.resourceList)
- ? item.resourceList.map((resource) => {
- const planRef = this.normalizeOrderSchedulingPlanRef(resource);
- return {
- ...resource,
- ...planRef,
- productName: resource.productName || '',
- workstationName:
- resource.workstationName ||
- resource.stationName ||
- (resourceInfo.resourceType === 1
- ? resourceInfo.resourceName
- : ''),
- equipmentName:
- resource.equipmentName ||
- resource.deviceName ||
- resource.assetName ||
- (resourceInfo.orderResourceTabType === 'device'
- ? resourceInfo.resourceName
- : ''),
- deviceId:
- this.normalizeBackendLongId(resource.deviceId) ||
- this.normalizeBackendLongId(resource.assetId) ||
- this.normalizeBackendLongId(resource.extInfo?.assetId) ||
- resourceInfo.deviceId ||
- '',
- deviceCode:
- resource.deviceCode || resourceInfo.deviceCode || '',
- deviceName:
- resource.deviceName || resourceInfo.deviceName || '',
- assigneeUser:
- resource.assigneeUser || resourceInfo.assigneeUser || [],
- assigneeUserId:
- resource.assigneeUserId ||
- resourceInfo.assigneeUserId ||
- '',
- assigneeUserName:
- resource.assigneeUserName ||
- resourceInfo.assigneeUserName ||
- '',
- assigneeJobNumber:
- resource.assigneeJobNumber ||
- resourceInfo.assigneeJobNumber ||
- '',
- executionObjectName:
- resource.executionObjectName ||
- (resourceInfo.orderResourceTabType === 'person'
- ? resourceInfo.resourceName
- : '')
- };
- })
- : [];
- const resourcePeriods = Array.isArray(item.resourcePeriods)
- ? item.resourcePeriods.map((period) => ({
- ...period,
- ...this.normalizeOrderSchedulingPlanRef(period)
- }))
- : [];
- return {
- ...item,
- resourceCode: resourceInfo.resourceCode,
- resourceName: resourceInfo.resourceName,
- resourceList,
- resourcePeriods,
- orderResourceTabType: resourceInfo.orderResourceTabType,
- deviceId: resourceInfo.deviceId,
- deviceCode: resourceInfo.deviceCode,
- deviceName: resourceInfo.deviceName,
- assigneeUser: resourceInfo.assigneeUser,
- assigneeUserId: resourceInfo.assigneeUserId,
- assigneeUserName: resourceInfo.assigneeUserName,
- assigneeJobNumber: resourceInfo.assigneeJobNumber,
- workstationName: resourceInfo.workstationName,
- equipmentName: resourceInfo.equipmentName,
- executionObjectType: resourceInfo.executionObjectType,
- executionObjectName: resourceInfo.executionObjectName
- };
- });
- },
- buildGanttPeriodTaskId(parentId, matchedPlanInfo, period, periodIndex) {
- const periodTaskId = [
- matchedPlanInfo.planId || period.planId || matchedPlanInfo.planCode,
- period.apsWorkOrderId || period.workOrderId || period.planCode,
- period.startTime,
- period.endTime,
- periodIndex
- ]
- .filter(
- (value) => value !== '' && value !== null && value !== undefined
- )
- .join('-');
- return `${parentId}-${periodTaskId || periodIndex}`;
- },
- async open(row) {
- const ids = Array.isArray(row?.ids) ? row.ids : [];
- const readonly = !!row?.readonly;
- const mode = row?.mode === 'order' ? 'order' : 'plan';
- const selectedWorkOrders = Array.isArray(row?.selectedWorkOrders)
- ? row.selectedWorkOrders
- : [];
- const orderListLoader =
- typeof row?.orderListLoader === 'function'
- ? row.orderListLoader
- : null;
- const loading = ids.length
- ? this.$loading({
- lock: true,
- text: mode === 'order' ? '加载订单中...' : '加载计划中...'
- })
- : 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.schedulingMode = mode;
- this.planTableStatusTab = this.getDefaultPlanTableStatusTab(mode);
- this.orderListLoader = orderListLoader;
- // 弹窗复用同一个组件,但入口数据不同:
- // - plan:生产计划行,使用工厂排程列 createSchedulingColumns。
- // - order:工单行,使用班组排程列 createWorkOrderSchedulingColumns。
- this.columns =
- mode === 'order'
- ? createWorkOrderSchedulingColumns()
- : createSchedulingColumns();
- this.selectedWorkOrders =
- this.buildWorkOrderTableRows(selectedWorkOrders);
- if (mode === 'order' && this.selectedWorkOrders.length) {
- // 班组排程表格展示工单,但工序布点和甘特仍依赖生产计划 id;
- // 因此把工单里的 productionPlan* 字段映射成 currentPlan 可识别的形态。
- selectedPlans = this.selectedWorkOrders
- .filter((item) => item.productionPlanId)
- .map((item) => ({
- ...item,
- id: item.productionPlanId,
- code: item.productionPlanCode,
- startTime: item.planStartTime,
- endTime: item.planCompleteTime,
- productNum: item.formingNum,
- unit: item.measuringUnit
- }));
- allPlans = selectedPlans;
- }
- this.entryReadonly = readonly;
- this.orderDetailReadonly = false;
- this.orderDetailActionMode = '';
- this.allPlanDataList = deepClone(allPlans || []);
- this.planDataList =
- mode === 'order'
- ? this.selectedWorkOrders
- : this.buildPlanTableRows(selectedPlans, this.allPlanDataList);
- this.currentPlan = null;
- this.planRoutingPayload = null;
- this.taskList = [];
- // 每次打开弹窗都清理按工序缓存,避免上一次班组排程的工位
- // 泄露到新的工单/计划中。
- this.workstationOptionsMap = {};
- this.workstationOptionsLoadingMap = {};
- this.selection = [];
- this.planPickerVisible = false;
- this.planPickerSelection = [];
- this.dotLineLoading = false;
- this.dotLineFullscreen = false;
- this.calendarSourceData = [];
- this.tableData = [];
- this.activeTab = mode === 'order' ? 'team' : 'factory';
- const now = new Date();
- this.dateRange = [
- `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(
- 2,
- '0'
- )}-01`,
- `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(
- 2,
- '0'
- )}-${String(
- new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate()
- ).padStart(2, '0')}`
- ];
- if (mode === 'order') {
- // 班组排程只预加载人员选项;工位必须等工序列表出来后,
- // 按每道工序 taskId 调任务派单同款接口补齐。
- await this.loadWorkOrderExecutionOptions();
- } else {
- // 工厂排程仍使用班组主数据作为自制执行对象,不走人员/工位混合逻辑。
- await this.loadTeamOptions();
- }
- if (mode === 'order') {
- await this.reloadPlanTableByStatus();
- } else {
- await this.refreshSchedulingView();
- await this.reloadPlanSchedulingPlansByStatus();
- }
- this.visible = true;
- },
- // Legacy operations kept for compatibility
- swap(index1, index2) {
- let temp1 = deepClone(this.form.scheduledPlans[index1]);
- let temp2 = deepClone(this.form.scheduledPlans[index2]);
- this.$set(this.form.scheduledPlans, index1, temp2);
- this.$set(this.form.scheduledPlans, index2, temp1);
- this.indexSort();
- },
- indexSort() {
- this.form.scheduledPlans.forEach((item, index) => {
- this.$set(this.form.scheduledPlans[index], 'serialNumber', index + 1);
- });
- },
- remove() {
- let codeS = this.selection.map((item) => item.code);
- this.form.scheduledPlans = this.form.scheduledPlans.filter(
- (item) => !codeS.includes(item.code)
- );
- this.indexSort();
- },
- moveUp() {
- let index = this.selection[0].serialNumber - 1;
- if (index > 0) {
- this.swap(index, index - 1);
- }
- },
- moveDown() {
- let index = this.selection[0].serialNumber - 1;
- if (index < this.form.scheduledPlans.length - 1) {
- this.swap(index, index + 1);
- }
- },
- exportList() {
- const plans = this.form.scheduledPlans || [];
- if (!plans.length) {
- this.$message.warning('暂无可导出数据');
- return;
- }
- const headers = [
- '序号',
- '计划状态',
- '计划编号',
- '生产订单号',
- '产线名称',
- '计划开始时间',
- '计划结束时间',
- '交期',
- '计划数量',
- '单位',
- '优先级'
- ];
- const escapeCsv = (value) => {
- const text =
- value === null || value === undefined ? '' : String(value);
- return `"${text.replace(/"/g, '""')}"`;
- };
- const rows = plans.map((item, index) =>
- [
- item.serialNumber || index + 1,
- item.mesStatusName,
- item.code,
- item.productionOrderNumber,
- item.productionLineName,
- item.startTime,
- item.endTime,
- item.deliveryTime,
- item.productNum,
- item.unit,
- item.priority
- ]
- .map((cell) => escapeCsv(cell))
- .join(',')
- );
- const csvContent = [headers.join(','), ...rows].join('\n');
- const blob = new Blob([`\uFEFF${csvContent}`], {
- type: 'text/csv;charset=utf-8;'
- });
- const url = window.URL.createObjectURL(blob);
- const link = document.createElement('a');
- link.href = url;
- link.download = `factory_production_scheduling_${Date.now()}.csv`;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- window.URL.revokeObjectURL(url);
- },
- initGanttFlatRows(data) {
- if (!Array.isArray(data) || data.length === 0) {
- return [];
- }
- const rows = [];
- data.forEach((item, index) => {
- const planList = Array.isArray(item.resourceList)
- ? item.resourceList
- : [];
- const planInfo = planList[0] || {};
- const taskLabel = item.taskName || item.name || '工序';
- const planLabel = this.buildGanttPlanLabel(planInfo);
- const resourceLabel =
- planInfo.teamName ||
- planInfo.productionLineName ||
- item.name ||
- item.taskName ||
- item.resourceId ||
- `资源${index + 1}`;
- const teamName = planInfo.teamName || resourceLabel;
- const productionLineName =
- planInfo.productionLineName || resourceLabel;
- const resourceName = this.isOrderScheduling
- ? item.resourceName || item.name || ''
- : '';
- const resourceCode = this.isOrderScheduling
- ? item.resourceCode || item.code || ''
- : '';
- const periods = Array.isArray(item.resourcePeriods)
- ? item.resourcePeriods
- : [];
- const parentId = `gantt-${item.resourceId || 'resource'}-${
- item.taskId || item.sourceTaskId || index
- }`;
- rows.push({
- id: parentId,
- code:
- item.taskId || item.sourceTaskId || planInfo.planCode || parentId,
- type: 2,
- name: taskLabel,
- text: taskLabel,
- executor: planLabel || taskLabel,
- laneLabel: resourceLabel,
- resourceName,
- resourceCode,
- teamName,
- productionOrderNumber: planInfo.productionOrderNumber || '',
- productionLineName,
- workstationName:
- item.workstationName ||
- item.stationName ||
- planInfo.workstationName ||
- planInfo.stationName ||
- resourceLabel,
- equipmentName:
- item.equipmentName ||
- item.deviceName ||
- item.assetName ||
- planInfo.equipmentName ||
- planInfo.deviceName ||
- planInfo.assetName ||
- '',
- deviceId:
- this.normalizeBackendLongId(item.deviceId) ||
- this.normalizeBackendLongId(item.assetId) ||
- this.normalizeBackendLongId(planInfo.deviceId) ||
- this.normalizeBackendLongId(planInfo.assetId) ||
- '',
- deviceCode: item.deviceCode || planInfo.deviceCode || '',
- deviceName: item.deviceName || planInfo.deviceName || '',
- assetName: item.assetName || planInfo.assetName || '',
- assigneeUser: item.assigneeUser || planInfo.assigneeUser || [],
- assigneeUserId:
- item.assigneeUserId || planInfo.assigneeUserId || '',
- assigneeUserName:
- item.assigneeUserName || planInfo.assigneeUserName || '',
- assigneeJobNumber:
- item.assigneeJobNumber || planInfo.assigneeJobNumber || '',
- executionObjectType: item.executionObjectType || '',
- executionObjectName: item.executionObjectName || '',
- render: periods.length ? 'split' : '',
- open: true
- });
- periods.forEach((period, periodIndex) => {
- const matchedPlanInfo =
- planList.find(
- (planItem) =>
- String(planItem.planId || '') === String(period.planId || '')
- ) ||
- planList[periodIndex] ||
- planInfo;
- const childPlanLabel = this.buildGanttPlanLabel(
- matchedPlanInfo,
- period
- );
- const childDisplayOrderCode = this.isOrderScheduling
- ? this.getOrderSchedulingDisplayOrderCode(matchedPlanInfo, period)
- : matchedPlanInfo.planCode;
- rows.push({
- id: this.buildGanttPeriodTaskId(
- parentId,
- matchedPlanInfo,
- period,
- periodIndex
- ),
- parent: parentId,
- code:
- matchedPlanInfo.planCode ||
- item.taskId ||
- item.sourceTaskId ||
- `${parentId}-${periodIndex}`,
- planId: matchedPlanInfo.planId || period.planId || '',
- planCode: matchedPlanInfo.planCode || '',
- productionOrderNumber:
- matchedPlanInfo.productionOrderNumber ||
- period.productionOrderNumber ||
- period.apsWorkOrderCode ||
- period.workOrderCode ||
- '',
- productName: matchedPlanInfo.productName || '',
- productCode: matchedPlanInfo.productCode || '',
- routingName:
- matchedPlanInfo.routingName ||
- matchedPlanInfo.produceRoutingName ||
- '',
- batchNo: matchedPlanInfo.batchNo || '',
- specification: matchedPlanInfo.specification || '',
- modeType: matchedPlanInfo.modeType || '',
- workstationName:
- matchedPlanInfo.workstationName ||
- matchedPlanInfo.stationName ||
- item.workstationName ||
- item.stationName ||
- '',
- equipmentName:
- matchedPlanInfo.equipmentName ||
- matchedPlanInfo.deviceName ||
- matchedPlanInfo.assetName ||
- item.equipmentName ||
- item.deviceName ||
- item.assetName ||
- '',
- deviceId:
- this.normalizeBackendLongId(matchedPlanInfo.deviceId) ||
- this.normalizeBackendLongId(matchedPlanInfo.assetId) ||
- this.normalizeBackendLongId(item.deviceId) ||
- this.normalizeBackendLongId(item.assetId) ||
- '',
- deviceCode: matchedPlanInfo.deviceCode || item.deviceCode || '',
- deviceName: matchedPlanInfo.deviceName || item.deviceName || '',
- assetName: matchedPlanInfo.assetName || item.assetName || '',
- assigneeUser:
- matchedPlanInfo.assigneeUser || item.assigneeUser || [],
- assigneeUserId:
- matchedPlanInfo.assigneeUserId || item.assigneeUserId || '',
- assigneeUserName:
- matchedPlanInfo.assigneeUserName || item.assigneeUserName || '',
- assigneeJobNumber:
- matchedPlanInfo.assigneeJobNumber ||
- item.assigneeJobNumber ||
- '',
- executionObjectType: item.executionObjectType || '',
- executionObjectName: item.executionObjectName || '',
- type: 5,
- name:
- [taskLabel, childPlanLabel].filter(Boolean).join(' / ') ||
- taskLabel,
- text: [
- taskLabel,
- childDisplayOrderCode,
- matchedPlanInfo.productName,
- matchedPlanInfo.batchNo
- ]
- .filter(Boolean)
- .join(' / '),
- executor: childPlanLabel || taskLabel,
- laneLabel: resourceLabel,
- resourceName,
- resourceCode,
- teamName,
- productionLineName,
- start_date: formatDotLineDateTime(period.startTime),
- end_date: formatDotLineDateTime(period.endTime)
- });
- });
- });
- return rows;
- },
- // 添加班组行合并
- // addDuplicateCount(arr) {
- // const elementCount = {}; // 用于存储元素及其出现次数
- // arr.forEach((element, index) => {
- // if (elementCount[element.shiftGroupCode] === undefined) {
- // // 第一次出现,记录索引
- // elementCount[element.shiftGroupCode] = { index, count: 1 };
- // } else {
- // // 不是第一次出现,增加计数
- // elementCount[element.shiftGroupCode].count++;
- // }
- // });
- // arr.forEach((item, index) => {
- // if (elementCount[item.shiftGroupCode]?.index == index) {
- // item.groupRowSpan = elementCount[item.shiftGroupCode].count;
- // }
- // });
- // return arr;
- // },
- async handleApproveCurrentPlan() {
- const row = this.currentPlan;
- if (!row?.id) {
- this.$message.warning('请先勾选一条计划后再审批');
- return;
- }
- if (!row.produceRoutingName && !row.routingName) {
- this.$message.error('请先选择工艺路线!');
- return;
- }
- if (String(row.dotLineStatus) !== '1') {
- this.$message.error('请先完成布点!');
- return;
- }
- if (!this.canApproveCurrentPlan) {
- this.$message.warning('当前计划不满足审批条件');
- return;
- }
- const titleText = this.isReview
- ? '是否发起流程'
- : '审批工单后不可撤回,确定审批吗?';
- const title = this.isReview ? '流程确认' : '审批确认';
- const text = this.isReview ? '流程审批中...' : '工单审批中...';
- try {
- await this.$confirm(titleText, title);
- } catch (e) {
- return;
- }
- const loading = this.$loading({
- lock: true,
- fullscreen: true,
- text
- });
- this.approvalLoading = true;
- try {
- const api = this.isReview ? releaseByApproval : release;
- const data = await api([row.id]);
- if (data || data === 0) {
- this.$message.success('提交审核成功!');
- } else {
- this.$message.error('提交审核失败,请重新提交!');
- }
- await this.refreshSchedulingView();
- await this.reloadPlanSchedulingPlansByStatus();
- } catch (e) {
- this.$message.error(e.message || '提交审核失败,请重新提交!');
- } finally {
- this.approvalLoading = false;
- loading.close();
- }
- },
- async submitDotLineConfig() {
- if (!this.currentPlan?.id) {
- this.$message.warning('请先勾选一条计划后再确定');
- return;
- }
- if (!this.isOrderScheduling && this.isCurrentPlanReadonly) {
- this.$message.warning('当前计划状态下布局数据仅支持查看');
- return;
- }
- if (!this.taskList.length) {
- this.$message.warning('当前没有可提交的工艺配置');
- return;
- }
- if (!this.validateTaskList()) return;
- const payload = this.buildSavePayload(0);
- const loading = this.$loading({ lock: true, text: '保存中...' });
- try {
- await savePlanDotLine(payload);
- await this.refreshSchedulingView();
- if (!this.isOrderScheduling) {
- await this.reloadPlanSchedulingPlansByStatus({
- preserveCurrent: true
- });
- }
- if (this.currentPlan?.id) {
- await this.loadPlanDotLineDetail(this.currentPlan);
- }
- this.dotLineFullscreen = false;
- this.$message.success('保存成功');
- } catch (e) {
- this.$message.error(e.message || '保存失败');
- } finally {
- loading.close();
- }
- },
- async submitDotLineRow(row) {
- if (!this.currentPlan?.id) {
- this.$message.warning('请先勾选一条计划后再确定');
- return;
- }
- if (!this.isOrderScheduling && this.isCurrentPlanReadonly) {
- this.$message.warning('当前计划状态下布局数据仅支持查看');
- return;
- }
- if (!row) {
- this.$message.warning('请选择需要保存的工艺配置');
- return;
- }
- if (!this.validateTaskRow(row, { checkSequence: false })) return;
- const payload = this.buildSavePayload(this.isOrderScheduling ? 0 : 1);
- const loadingText = this.isOrderScheduling ? '保存中...' : '缓存中...';
- const successText = this.isOrderScheduling ? '保存成功' : '缓存成功';
- const errorText = this.isOrderScheduling ? '保存失败' : '缓存失败';
- const loading = this.$loading({ lock: true, text: loadingText });
- try {
- await savePlanDotLine(payload);
- await this.refreshSchedulingView();
- if (!this.isOrderScheduling) {
- await this.reloadPlanSchedulingPlansByStatus({
- preserveCurrent: true
- });
- }
- if (this.currentPlan?.id) {
- await this.loadPlanDotLineDetail(this.currentPlan);
- }
- this.$message.success(successText);
- } catch (e) {
- this.$message.error(e.message || errorText);
- } finally {
- loading.close();
- }
- },
- // Dialog lifecycle
- cancel() {
- if (this.orderDateReloadTimer) {
- clearTimeout(this.orderDateReloadTimer);
- this.orderDateReloadTimer = null;
- }
- this.form = {};
- this.entryReadonly = false;
- this.orderDetailReadonly = false;
- this.orderDetailActionMode = '';
- this.currentPlan = null;
- this.planRoutingPayload = null;
- this.taskList = [];
- this.selection = [];
- this.planPickerSelection = [];
- this.planPickerSelectionAll = [];
- this.planPickerTimeDimensionPlanType = '';
- this.planPickerWhere = createPlanPickerWhere();
- this.schedulingMode = 'plan';
- this.planTableStatusTab = this.getDefaultPlanTableStatusTab('plan');
- this.selectedWorkOrders = [];
- this.orderListLoader = null;
- this.columns = createSchedulingColumns();
- this.dotLineLoading = false;
- this.dotLineFullscreen = false;
- this.visible = false;
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .pane-box {
- padding: 20px 0;
- }
- .btnList {
- display: flex;
- align-items: center;
- gap: 8px;
- margin: 10px 0 12px;
- button {
- margin: 0;
- }
- }
- .scheduling-content {
- position: relative;
- margin-top: 6px;
- width: 100%;
- min-height: 0;
- padding: 0;
- box-sizing: border-box;
- }
- .form-wrapper {
- position: relative;
- display: flex;
- flex-direction: column;
- width: 100%;
- height: 100%;
- min-width: 0;
- overflow: hidden;
- border: 1px solid #d8e3ef;
- border-radius: 10px;
- background: linear-gradient(180deg, #ffffff 0%, #fafdff 100%);
- box-shadow: 0 10px 26px rgba(26, 39, 55, 0.08);
- }
- .form-wrapper::before,
- .plan-dot-line-detail::before {
- position: absolute;
- top: 0;
- right: 0;
- left: 0;
- height: 2px;
- background: linear-gradient(90deg, #2489e8 0%, #14b8a6 60%, #7aa7ff 100%);
- content: '';
- pointer-events: none;
- }
- .plan-table-status-tabs {
- flex: 0 0 42px;
- height: 42px;
- padding: 3px 12px 0;
- box-sizing: border-box;
- border-bottom: 1px solid #e5edf6;
- background: linear-gradient(180deg, #f9fcff 0%, #f3f7fb 100%);
- }
- .plan-table-status-tabs ::v-deep {
- .el-tabs__header {
- margin: 0;
- }
- .el-tabs__nav-wrap::after {
- display: none;
- }
- .el-tabs__item {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- height: 36px;
- line-height: 1;
- margin-right: 8px;
- padding: 0 16px;
- border-radius: 7px 7px 0 0;
- color: #667085;
- font-size: 14px;
- font-weight: 600;
- transition: all 0.18s ease;
- }
- .el-tabs__item:not(.is-active):hover {
- color: #1683d8;
- background: rgba(31, 140, 235, 0.06);
- }
- .el-tabs__item.is-active {
- color: #0f75bd;
- background: #fff;
- box-shadow: 0 -1px 0 #dce8f4, 1px 0 0 #e5edf6, -1px 0 0 #e5edf6;
- }
- .el-tabs__active-bar {
- height: 2px;
- border-radius: 2px 2px 0 0;
- background: linear-gradient(90deg, #1f8ceb 0%, #16b8a6 100%);
- }
- }
- .form-wrapper ::v-deep .ele-pro-table {
- flex: 1 1 auto;
- height: calc(100% - 42px);
- min-height: 0;
- overflow: hidden;
- background: transparent;
- }
- .form-wrapper.is-order-scheduling ::v-deep .ele-pro-table,
- .form-wrapper.is-plan-scheduling ::v-deep .ele-pro-table {
- display: flex;
- flex-direction: column;
- }
- .form-wrapper.is-order-scheduling ::v-deep .ele-pro-table > .el-table {
- flex: 1 1 auto;
- height: calc(100% - 44px) !important;
- min-height: 0;
- }
- .form-wrapper.is-order-scheduling ::v-deep .ele-pro-table > .el-pagination {
- margin-top: auto !important;
- }
- .form-wrapper ::v-deep .el-table {
- color: #364152;
- border-color: #e5edf6;
- font-size: 14px;
- }
- .form-wrapper ::v-deep .el-table__body-wrapper {
- overflow-y: auto !important;
- overflow-x: auto;
- padding-bottom: 1px;
- box-sizing: border-box;
- }
- .form-wrapper ::v-deep .el-table th.el-table__cell {
- background: linear-gradient(180deg, #f8fbff 0%, #eef5fb 100%);
- color: #1f2d3d;
- font-weight: 600;
- position: sticky;
- top: 0;
- z-index: 2;
- }
- .form-wrapper ::v-deep .el-table td.el-table__cell,
- .form-wrapper ::v-deep .el-table th.el-table__cell {
- border-bottom-color: #e9eff6;
- }
- .form-wrapper ::v-deep .el-table td.el-table__cell {
- padding-top: 5px;
- padding-bottom: 5px;
- }
- .form-wrapper ::v-deep .el-table__row:hover > td.el-table__cell {
- background: #f5faff;
- }
- .form-wrapper ::v-deep .el-table__row.current-row > td.el-table__cell,
- .form-wrapper
- ::v-deep
- .el-table__row.el-table__row--striped.current-row
- > td.el-table__cell {
- background: #eaf5ff;
- }
- .form-wrapper ::v-deep .el-link {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 42px;
- height: 26px;
- padding: 0 10px;
- border: 1px solid #c8e0f4;
- border-radius: 6px;
- background: linear-gradient(180deg, #f6fbff 0%, #eaf5ff 100%);
- color: #0f75bd;
- font-weight: 600;
- line-height: 24px;
- transition: all 0.18s ease;
- }
- .form-wrapper ::v-deep .el-link:hover {
- border-color: #8ec6f4;
- background: linear-gradient(180deg, #eef8ff 0%, #dff0ff 100%);
- color: #0964a3;
- }
- .form-wrapper ::v-deep .el-checkbox__inner {
- border-color: #d8e1eb;
- }
- .form-wrapper ::v-deep .el-table__body-wrapper::-webkit-scrollbar,
- .form-wrapper ::v-deep .el-table__fixed-body-wrapper::-webkit-scrollbar {
- width: 8px;
- height: 8px;
- }
- .form-wrapper ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb,
- .form-wrapper
- ::v-deep
- .el-table__fixed-body-wrapper::-webkit-scrollbar-thumb {
- border: 2px solid #fff;
- border-radius: 999px;
- background: #c9d4e2;
- }
- .form-wrapper ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover,
- .form-wrapper
- ::v-deep
- .el-table__fixed-body-wrapper::-webkit-scrollbar-thumb:hover {
- background: #aeb9c8;
- }
- .form-wrapper.is-plan-scheduling ::v-deep .ele-pro-table {
- display: flex;
- flex-direction: column;
- min-height: 0;
- }
- .form-wrapper.is-plan-scheduling ::v-deep .ele-pro-table > .el-table {
- flex: 1 1 auto;
- height: calc(100% - 44px) !important;
- min-height: 0;
- }
- .form-wrapper.is-plan-scheduling ::v-deep .ele-pro-table > .el-pagination {
- margin-top: auto !important;
- }
- .form-wrapper.is-plan-scheduling ::v-deep .plan-scheduling-pagination {
- display: flex;
- align-items: center;
- justify-content: center;
- flex: 0 0 44px;
- height: 44px;
- min-height: 44px;
- padding: 0 10px;
- box-sizing: border-box;
- border-top: 1px solid #e5edf6;
- background: linear-gradient(180deg, #ffffff 0%, #f1f6fb 100%);
- box-shadow: 0 -3px 10px rgba(31, 45, 61, 0.04);
- }
- .form-wrapper.is-plan-scheduling
- ::v-deep
- .ele-pro-table
- > .el-pagination
- > * {
- margin-top: 0 !important;
- }
- .form-wrapper.is-order-scheduling ::v-deep .order-plan-pagination {
- display: flex;
- align-items: center;
- justify-content: center;
- flex: 0 0 44px;
- height: 44px;
- min-height: 44px;
- margin-top: 0 !important;
- padding: 0 10px;
- box-sizing: border-box;
- border-top: 1px solid #e5edf6;
- background: linear-gradient(180deg, #ffffff 0%, #f1f6fb 100%);
- box-shadow: 0 -3px 10px rgba(31, 45, 61, 0.04);
- }
- .form-wrapper.is-order-scheduling ::v-deep .order-plan-pagination > * {
- margin-top: 0 !important;
- }
- .plan-dot-line-side {
- width: 100%;
- height: 100%;
- overflow: hidden;
- padding-left: 10px;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- }
- .plan-dot-line-side.is-fullscreen {
- position: fixed;
- top: 24px;
- right: 24px;
- bottom: 24px;
- left: 24px;
- z-index: 3000;
- width: auto !important;
- height: auto !important;
- background: #fff;
- border-radius: 8px;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
- padding: 20px;
- }
- .plan-dot-line-detail {
- position: relative;
- display: flex;
- flex-direction: column;
- height: 100%;
- min-width: 0;
- overflow: hidden;
- border: 1px solid #d8e3ef;
- border-radius: 10px;
- background: linear-gradient(180deg, #ffffff 0%, #fafdff 100%);
- box-shadow: 0 10px 26px rgba(26, 39, 55, 0.08);
- }
- .plan-dot-line-scrollable {
- flex: 1;
- overflow: hidden;
- padding-bottom: 14px;
- box-sizing: border-box;
- min-height: 0;
- }
- .plan-dot-line-scrollable.is-order-dispatch-scroll {
- padding-bottom: 0;
- }
- .plan-dot-line-scrollable.is-plan-config-scroll {
- display: flex;
- flex-direction: column;
- padding-bottom: 0;
- }
- .plan-dot-line-scrollable.is-plan-config-scroll
- ::v-deep
- .factory-task-config-panel {
- flex: 1 1 auto;
- min-height: 0;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
- .plan-dot-line-scrollable.is-plan-config-scroll
- ::v-deep
- .factory-task-config-panel
- .task-config-table-wrap {
- flex: 1 1 auto;
- height: 100%;
- min-height: 0;
- }
- @media (max-width: 1440px) {
- .scheduling-content {
- margin-top: 8px;
- }
- .plan-dot-line-side {
- padding-left: 8px;
- }
- .plan-dot-line-scrollable {
- padding-bottom: 12px;
- }
- }
- @media (max-width: 1180px) {
- .scheduling-content {
- height: auto !important;
- }
- .plan-dot-line-side {
- padding-left: 0;
- padding-top: 10px;
- min-height: 460px;
- }
- .form-wrapper {
- min-height: 260px;
- }
- .plan-dot-line-scrollable {
- overflow: auto;
- }
- }
- </style>
- <style lang="scss">
- .scheduling-dialog.el-dialog {
- width: calc(100vw - 10px) !important;
- max-width: none;
- height: calc(100vh - 10px);
- max-height: calc(100vh - 10px);
- margin-top: 5px !important;
- margin-bottom: 5px !important;
- border: 1px solid rgba(190, 205, 222, 0.85);
- border-radius: 12px;
- background: #eef4fa;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- box-shadow: 0 22px 64px rgba(15, 23, 42, 0.22);
- .el-dialog__header {
- flex: 0 0 auto;
- padding: 8px 16px;
- border-bottom: 1px solid #dce7f2;
- background: linear-gradient(180deg, #ffffff 0%, #f7fafc 100%);
- }
- .el-dialog__title {
- color: #1d2939;
- font-size: 16px;
- font-weight: 700;
- letter-spacing: 0;
- }
- .el-dialog__body {
- flex: 1 1 auto;
- display: flex;
- flex-direction: column;
- min-height: 0;
- padding: 6px 8px 6px;
- background: radial-gradient(
- circle at 12% 0,
- rgba(31, 140, 235, 0.08) 0,
- transparent 28%
- ),
- linear-gradient(90deg, rgba(31, 140, 235, 0.035) 1px, transparent 1px),
- linear-gradient(180deg, rgba(22, 184, 166, 0.025) 1px, transparent 1px),
- #eef4fa;
- background-size: auto, 24px 24px, 24px 24px;
- overflow: hidden;
- }
- .project-gantt {
- flex: 0 0 auto;
- }
- .scheduling-content {
- flex: 1 1 auto;
- height: auto !important;
- min-height: 0;
- }
- .el-dialog__footer {
- flex: 0 0 auto;
- padding: 5px 16px 6px;
- border-top: 1px solid #dce7f2;
- background: linear-gradient(180deg, #f9fbfd 0%, #f3f7fb 100%);
- }
- }
- @media (max-height: 760px) {
- .scheduling-dialog.el-dialog {
- .el-dialog__header {
- padding: 8px 14px;
- }
- .el-dialog__body {
- padding: 6px 8px;
- }
- .el-dialog__footer {
- padding: 4px 14px 5px;
- }
- }
- }
- @media (max-width: 1180px) {
- .scheduling-dialog.el-dialog {
- .el-dialog__body {
- overflow: auto;
- }
- .scheduling-content {
- flex: 0 0 auto;
- min-height: 720px;
- }
- }
- }
- </style>
|