| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052 |
- <template>
- <div class="project-gantt" :class="{ 'is-fullscreen': isFullscreen }">
- <div class="view-switch">
- <div class="view-switch-left">
- <el-button
- size="mini"
- :type="viewMode === 'gantt' ? 'primary' : 'default'"
- @click="handleViewSwitch('gantt')"
- >
- 甘特图
- </el-button>
- <el-button
- size="mini"
- :type="viewMode === 'calendar' ? 'primary' : 'default'"
- @click="handleViewSwitch('calendar')"
- >
- 工作日历
- </el-button>
- <slot name="toolbar"></slot>
- <gantt-filter-bar
- v-show="viewMode === 'gantt'"
- class="gantt-filter-inline"
- :query="ganttQuery"
- :plan-code-label="
- resourceLabelMode === 'order'
- ? '\u751f\u4ea7\u8ba2\u5355\u53f7'
- : '\u8ba1\u5212\u7f16\u53f7'
- "
- @update:query="ganttQuery = $event"
- @apply="applyGanttFilters"
- @reset="resetGanttFilters"
- />
- <gantt-filter-bar
- v-show="viewMode === 'calendar'"
- class="gantt-filter-inline"
- :query="calendarQuery"
- :plan-code-label="
- resourceLabelMode === 'order'
- ? '\u751f\u4ea7\u8ba2\u5355\u53f7'
- : '\u8ba1\u5212\u7f16\u53f7'
- "
- @update:query="calendarQuery = $event"
- @apply="applyCalendarFilters"
- @reset="resetCalendarFilters"
- />
- </div>
- <el-button
- size="mini"
- :icon="isFullscreen ? 'el-icon-close' : 'el-icon-full-screen'"
- @click="toggleFullscreen"
- >
- {{ isFullscreen ? '退出全屏' : '全屏显示' }}
- </el-button>
- </div>
- <div class="project-gantt__body">
- <resource-panel
- :is-fullscreen="isFullscreen"
- :resource-tabs="resourceTabs"
- :active-tab="activeTab"
- :keyword="keyword"
- :filtered-resource-list="filteredResourceList"
- :selected-resource="selectedResource"
- @tab-change="handleResourceTabChange"
- @update:keyword="keyword = $event"
- @reset="resetResourceFilter"
- @select="handleResourceSelect"
- />
- <div v-show="viewMode === 'gantt'" class="gantt-view">
- <gantt-filter-bar
- v-show="!isFullscreen"
- :query="ganttQuery"
- :plan-code-label="
- resourceLabelMode === 'order'
- ? '\u751f\u4ea7\u8ba2\u5355\u53f7'
- : '\u8ba1\u5212\u7f16\u53f7'
- "
- @update:query="ganttQuery = $event"
- @apply="applyGanttFilters"
- @reset="resetGanttFilters"
- />
- <div ref="gantt_scrollbar" class="gantt-box">
- <div ref="gantt" class="gantt-container"></div>
- </div>
- </div>
- <div v-show="viewMode === 'calendar'" class="calendar-view">
- <gantt-filter-bar
- v-show="!isFullscreen"
- :query="calendarQuery"
- :plan-code-label="
- resourceLabelMode === 'order'
- ? '\u751f\u4ea7\u8ba2\u5355\u53f7'
- : '\u8ba1\u5212\u7f16\u53f7'
- "
- @update:query="calendarQuery = $event"
- @apply="applyCalendarFilters"
- @reset="resetCalendarFilters"
- />
- <calendar-table
- :calendar-month-groups="calendarMonthGroups"
- :calendar-days="calendarDays"
- :calendar-display-rows="calendarDisplayRows"
- :group-label="calendarGroupLabel"
- @show-tooltip="showCalendarTooltip"
- @move-tooltip="moveHoverTooltip"
- @hide-tooltip="hideHoverTooltip"
- />
- </div>
- </div>
- <div
- ref="hoverTooltip"
- v-show="hoverTooltip.visible"
- class="gantt-hover-tooltip"
- :style="hoverTooltip.style"
- v-html="hoverTooltip.content"
- ></div>
- </div>
- </template>
- <script>
- import { Gantt } from './gantt';
- import CalendarTable from './components/CalendarTable.vue';
- import GanttFilterBar from './components/GanttFilterBar.vue';
- import ResourcePanel from './components/ResourcePanel.vue';
- import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
- import {
- DAY_MS,
- PLAN_COLOR_PALETTE,
- RESOURCE_TABS,
- WEEK_MAP
- } from './project-gantt.constants';
- import {
- buildCalendarTooltipHtml,
- buildTooltipHtml,
- createHoverTooltipState,
- createSearchQuery,
- endOfMonth,
- escapeHtml,
- formatDateKey,
- parseDate,
- resolvePlanColor,
- resolveTaskColor,
- startOfDay,
- startOfMonth
- } from './project-gantt.utils';
- let gantt = new Gantt();
- export default {
- name: 'gantt',
- components: {
- CalendarTable,
- GanttFilterBar,
- ResourcePanel
- },
- props: {
- ganttTasks: {
- type: Array,
- default: () => {
- return [];
- }
- },
- planList: {
- type: Array,
- default: () => {
- return [];
- }
- },
- calendarSourceData: {
- type: Array,
- default: () => {
- return [];
- }
- },
- defaultTab: {
- type: String,
- default: 'team'
- },
- resourceTabsConfig: {
- type: Array,
- default: () => RESOURCE_TABS
- },
- resourceLabelMode: {
- type: String,
- default: 'plan'
- },
- dateRange: {
- type: Array,
- default: () => {
- return null;
- }
- }
- },
- watch: {
- defaultTab(val) {
- if (val && val !== this.activeTab) {
- this.activeTab = val;
- }
- },
- ganttTasks: {
- handler(val) {
- this.initData();
- },
- deep: true
- },
- activeTab() {
- this.selectedResource = '';
- this.initData();
- },
- selectedResource() {
- this.initData();
- },
- viewMode() {
- this.syncGanttVisibility();
- },
- dateRange: {
- handler(val) {
- if (val && val.length === 2) {
- this.initData();
- }
- },
- deep: true
- }
- },
- data() {
- return {
- ganttData: [],
- activeTab: this.defaultTab,
- selectedResource: '',
- keyword: '',
- viewMode: 'gantt',
- ganttQuery: createSearchQuery(),
- ganttFilters: createSearchQuery(),
- calendarQuery: createSearchQuery(),
- calendarFilters: createSearchQuery(),
- calendarExpand: true,
- hoverTooltip: createHoverTooltipState(),
- isFullscreen: false
- };
- },
- computed: {
- resourceTabs() {
- return this.resourceTabsConfig && this.resourceTabsConfig.length
- ? this.resourceTabsConfig
- : RESOURCE_TABS;
- },
- resourceList() {
- const source = this.ganttTasks
- .map((item) => this.getActiveResourceName(item))
- .filter(Boolean);
- const result = [];
- const cache = new Set();
- source.forEach((name, index) => {
- const label = (name || '').trim();
- if (!label || cache.has(label)) {
- return;
- }
- cache.add(label);
- result.push({
- key: `${this.activeTab}-${index}-${label}`,
- label
- });
- });
- return result;
- },
- filteredResourceList() {
- const keyword = this.keyword.trim().toLowerCase();
- if (!keyword) {
- return this.resourceList;
- }
- return this.resourceList.filter((item) =>
- item.label.toLowerCase().includes(keyword)
- );
- },
- filteredGanttTasks() {
- return this.buildFilteredGanttTasks();
- },
- planMetaByCode() {
- return this.planList.reduce((result, item) => {
- const key = item.code || item.planCode || item.id;
- if (!key || result[key]) {
- return result;
- }
- result[key] = item;
- return result;
- }, {});
- },
- orderPlanColorMap() {
- if (this.resourceLabelMode !== 'order') {
- return {};
- }
- const result = {};
- const planKeys = [];
- const assignColor = (primaryKey, aliases = []) => {
- if (!primaryKey) {
- return;
- }
- const existingKey = [primaryKey, ...aliases].find(
- (key) => key && result[key]
- );
- const color =
- (existingKey && result[existingKey]) ||
- PLAN_COLOR_PALETTE[planKeys.length % PLAN_COLOR_PALETTE.length];
- if (!existingKey) {
- planKeys.push(primaryKey);
- }
- [primaryKey, ...aliases].forEach((key) => {
- if (key) {
- result[key] = color;
- }
- });
- };
- this.ganttTasks.forEach((item) => {
- assignColor(item.planId || item.planCode, [
- item.planId,
- item.planCode
- ]);
- });
- return result;
- },
- calendarTaskSource() {
- if (
- Array.isArray(this.calendarSourceData) &&
- this.calendarSourceData.length
- ) {
- return this.calendarSourceData
- .map((item, index) => {
- const resourceList = Array.isArray(item.resourceList)
- ? item.resourceList
- : [];
- const resourceName = item.resourceName || item.name || '';
- const resourceCode = item.resourceCode || item.code || '';
- const dayEntryMap = {};
- const periodDates = [];
- const searchEntries = [];
- resourceList.forEach((resourceItem, resourceIndex) => {
- searchEntries.push({
- planCode: resourceItem.planCode || '',
- productionOrderNumber:
- resourceItem.productionOrderNumber ||
- resourceItem.apsWorkOrderCode ||
- resourceItem.workOrderCode ||
- '',
- productName: resourceItem.productName || '',
- routingName:
- resourceItem.routingName ||
- resourceItem.produceRoutingName ||
- '',
- productCode: resourceItem.productCode || '',
- specification: resourceItem.specification || '',
- modeType: resourceItem.modeType || ''
- });
- const periods = Array.isArray(resourceItem.planTimePeriod)
- ? resourceItem.planTimePeriod
- : [];
- periods.forEach((period, periodIndex) => {
- const dayKey = String(period.today || '').trim();
- if (!dayKey) {
- return;
- }
- const parsedDate = parseDate(dayKey);
- if (parsedDate) {
- periodDates.push(parsedDate);
- }
- if (!dayEntryMap[dayKey]) {
- dayEntryMap[dayKey] = [];
- }
- dayEntryMap[dayKey].push({
- id: `${item.resourceId || 'resource'}-${
- item.taskId || index
- }-${resourceItem.planId || resourceIndex}-${periodIndex}`,
- planId: resourceItem.planId || '',
- planCode:
- resourceItem.planCode || resourceItem.productCode || '',
- productionOrderNumber:
- resourceItem.productionOrderNumber ||
- resourceItem.apsWorkOrderCode ||
- resourceItem.workOrderCode ||
- '',
- displayCode:
- resourceItem.productionOrderNumber ||
- resourceItem.apsWorkOrderCode ||
- resourceItem.workOrderCode ||
- resourceItem.planCode ||
- resourceItem.productCode ||
- '',
- qty: this.getCalendarEntryQuantity(
- item,
- resourceItem,
- period
- ),
- productName: resourceItem.productName || '',
- routingName:
- resourceItem.routingName ||
- resourceItem.produceRoutingName ||
- '',
- productCode: resourceItem.productCode || '',
- specification: resourceItem.specification || '',
- batchNo: resourceItem.batchNo || '',
- modeType: resourceItem.modeType || '',
- resourceName,
- resourceCode
- });
- });
- });
- if (!periodDates.length) {
- return null;
- }
- periodDates.sort((a, b) => a.getTime() - b.getTime());
- return {
- id: `${item.resourceId || 'resource'}-${item.taskId || index}`,
- code: item.taskId || item.sourceTaskId || '',
- groupLabel: this.getActiveCalendarGroupName(item),
- productionLineName:
- this.getCalendarProductionLineName(item, resourceList) ||
- this.getPlanProductionLineName(resourceList[0]?.planCode) ||
- '',
- factoryName: '',
- routingName:
- resourceList[0]?.routingName ||
- resourceList[0]?.produceRoutingName ||
- '',
- taskLabel: this.getCalendarTaskLabel(item),
- startDate: periodDates[0],
- endDate: periodDates[periodDates.length - 1],
- searchEntries,
- dayEntryMap
- };
- })
- .filter(Boolean)
- .sort((a, b) => {
- if (a.groupLabel !== b.groupLabel) {
- return a.groupLabel.localeCompare(b.groupLabel, 'zh-CN');
- }
- return a.startDate.getTime() - b.startDate.getTime();
- });
- }
- return this.ganttTasks
- .filter((item) => item.start_date && item.end_date)
- .map((item, index) => {
- const planMeta = this.planMetaByCode[item.code] || {};
- const startDate = parseDate(item.start_date);
- const endDate = parseDate(item.end_date);
- if (!startDate || !endDate) {
- return null;
- }
- return {
- id: item.id || `${item.code || 'task'}-${index}`,
- code: item.code || '',
- groupLabel:
- this.getActiveResourceName(item) ||
- item.teamName ||
- planMeta.teamName ||
- '\u672a\u5206\u7ec4',
- productionLineName:
- item.productionLineName || planMeta.productionLineName || '',
- factoryName: planMeta.factoryName || '',
- routingName:
- planMeta.produceRoutingName ||
- planMeta.routingName ||
- planMeta.routeName ||
- '',
- taskLabel:
- item.name || item.text || item.code || `任务${index + 1}`,
- barLabel:
- [item.title, item.name || item.text || item.code]
- .filter(Boolean)
- .join(' / ') || `\u4efb\u52a1${index + 1}`,
- color: item.color || resolveTaskColor(item.type),
- startDate,
- endDate
- };
- })
- .filter(Boolean)
- .sort((a, b) => {
- if (a.groupLabel !== b.groupLabel) {
- return a.groupLabel.localeCompare(b.groupLabel, 'zh-CN');
- }
- return a.startDate.getTime() - b.startDate.getTime();
- });
- },
- calendarFilteredTasks() {
- const hasFilters =
- this.calendarFilters.routingName ||
- this.calendarFilters.productName ||
- this.calendarFilters.planCode;
- const resourceFilteredTasks = this.calendarTaskSource.filter((item) =>
- this.matchesCalendarResourceFilter(item)
- );
- if (!hasFilters) {
- return resourceFilteredTasks;
- }
- return resourceFilteredTasks
- .map((item) => {
- const sourceMap = item.dayEntryMap || {};
- const filteredDayEntryMap = Object.keys(sourceMap).reduce(
- (result, dayKey) => {
- const matchedEntries = (sourceMap[dayKey] || []).filter(
- (entry) => this.matchesCalendarFilters(entry)
- );
- if (matchedEntries.length) {
- result[dayKey] = matchedEntries;
- }
- return result;
- },
- {}
- );
- const matchedDayKeys = Object.keys(filteredDayEntryMap);
- if (!matchedDayKeys.length) {
- return null;
- }
- const matchedDates = matchedDayKeys
- .map((dayKey) => parseDate(dayKey))
- .filter(Boolean)
- .sort((a, b) => a.getTime() - b.getTime());
- return {
- ...item,
- dayEntryMap: filteredDayEntryMap,
- startDate: matchedDates[0] || item.startDate,
- endDate: matchedDates[matchedDates.length - 1] || item.endDate,
- searchEntries: Object.values(filteredDayEntryMap).flat()
- };
- })
- .filter(Boolean);
- },
- calendarDays() {
- if (!this.calendarFilteredTasks.length) {
- return [];
- }
- let minDate, maxDate;
- if (this.dateRange && this.dateRange.length === 2) {
- minDate = startOfDay(parseDate(this.dateRange[0]));
- maxDate = startOfDay(parseDate(this.dateRange[1]));
- } else {
- const firstTask = this.calendarFilteredTasks[0];
- minDate = startOfMonth(firstTask.startDate);
- maxDate = endOfMonth(firstTask.endDate);
- this.calendarFilteredTasks.forEach((item) => {
- const start = startOfMonth(item.startDate);
- const end = endOfMonth(item.endDate);
- if (start.getTime() < minDate.getTime()) {
- minDate = start;
- }
- if (end.getTime() > maxDate.getTime()) {
- maxDate = end;
- }
- });
- }
- const days = [];
- let current = new Date(minDate);
- while (current.getTime() <= maxDate.getTime()) {
- days.push({
- key: formatDateKey(current),
- label: current.getDate(),
- date: new Date(current),
- isWeekend: current.getDay() === 0 || current.getDay() === 6,
- isToday:
- startOfDay(current).getTime() === startOfDay(new Date()).getTime()
- });
- current = new Date(current.getTime() + DAY_MS);
- }
- return days;
- },
- calendarMonthGroups() {
- const result = [];
- this.calendarDays.forEach((day) => {
- const key = `${day.date.getFullYear()}-${day.date.getMonth() + 1}`;
- const current = result[result.length - 1];
- if (current && current.key === key) {
- current.span += 1;
- } else {
- result.push({
- key,
- label: `${day.date.getFullYear()}\u5e74${
- day.date.getMonth() + 1
- }\u6708`,
- span: 1
- });
- }
- });
- return result;
- },
- calendarDisplayRows() {
- if (!this.calendarDays.length) {
- return [];
- }
- const rowMap = new Map();
- this.calendarFilteredTasks.forEach((item) => {
- const groupKey = item.groupLabel || '\u672a\u5206\u7ec4';
- if (!rowMap.has(groupKey)) {
- rowMap.set(groupKey, []);
- }
- rowMap.get(groupKey).push({
- id: item.id,
- taskLabel: item.taskLabel,
- dayCells: this.calendarDays.map((day, index) => {
- const entries = item.dayEntryMap
- ? item.dayEntryMap[day.key] || []
- : [];
- return {
- key: `${item.id}-${day.key}`,
- entries,
- isActive: entries.length > 0,
- isWeekend: day.isWeekend,
- isToday: day.isToday
- };
- })
- });
- });
- const result = [];
- rowMap.forEach((rows, groupLabel) => {
- const visibleRows = this.calendarExpand ? rows : rows.slice(0, 1);
- visibleRows.forEach((row, index) => {
- result.push({
- ...row,
- groupLabel,
- showGroup: index === 0,
- rowspan: visibleRows.length
- });
- });
- });
- return result;
- },
- calendarGroupLabel() {
- const tab = this.resourceTabs.find(
- (item) => item.key === this.activeTab
- );
- return tab?.label ? `${tab.label}名称` : '资源名称';
- }
- },
- mounted() {
- this.initGantt(); // gantt初始化配置
- this.initData(); // 数据format以及gantt reload
- this.syncGanttVisibility();
- this.bindGanttHoverTooltip();
- // 监听全屏状态变化
- document.addEventListener(
- 'fullscreenchange',
- this.handleFullscreenChange
- );
- document.addEventListener(
- 'webkitfullscreenchange',
- this.handleFullscreenChange
- );
- document.addEventListener(
- 'mozfullscreenchange',
- this.handleFullscreenChange
- );
- document.addEventListener(
- 'MSFullscreenChange',
- this.handleFullscreenChange
- );
- },
- methods: {
- handleResourceTabChange(tabKey) {
- if (this.activeTab === tabKey) {
- return;
- }
- this.activeTab = tabKey;
- this.$emit('tab-change', tabKey);
- },
- handleResourceSelect(item) {
- this.selectedResource =
- this.selectedResource === item.label ? '' : item.label;
- },
- getCalendarTaskLabel(item = {}) {
- return String(item.taskName || '').trim();
- },
- getCalendarEntryQuantity(item = {}, resourceItem = {}, period = {}) {
- const planMeta =
- this.planMetaByCode[resourceItem.planCode] ||
- this.planMetaByCode[resourceItem.planId] ||
- this.planMetaByCode[period.planCode] ||
- this.planMetaByCode[period.planId] ||
- {};
- const quantity =
- period.todayRequiredFormingNum ??
- period.todayQuantity ??
- period.dayQuantity ??
- resourceItem.productNum ??
- resourceItem.formingNum ??
- resourceItem.requiredFormingNum ??
- resourceItem.requiredNum ??
- resourceItem.planNum ??
- resourceItem.quantity ??
- period.productNum ??
- period.formingNum ??
- period.requiredFormingNum ??
- period.requiredNum ??
- period.planNum ??
- period.quantity ??
- '';
- if (quantity === '' || quantity === null || quantity === undefined) {
- return '';
- }
- const unit =
- item.measuringUnit ||
- item.unit ||
- item.measuringUnitName ||
- item.unitName ||
- item.unitMeasurement ||
- item.productUnit ||
- resourceItem.measuringUnit ||
- resourceItem.unit ||
- resourceItem.measuringUnitName ||
- resourceItem.unitName ||
- resourceItem.unitMeasurement ||
- resourceItem.productUnit ||
- planMeta.measuringUnit ||
- planMeta.unit ||
- planMeta.measuringUnitName ||
- planMeta.unitName ||
- planMeta.unitMeasurement ||
- planMeta.productUnit ||
- period.measuringUnit ||
- period.unit ||
- period.measuringUnitName ||
- period.unitName ||
- period.unitMeasurement ||
- period.productUnit ||
- '';
- const text = String(quantity);
- if (!unit || text.includes(String(unit))) {
- return text;
- }
- return `${text}${unit}`;
- },
- resetResourceFilter() {
- this.keyword = '';
- this.selectedResource = '';
- },
- matchesResourceFilter(item) {
- if (!this.selectedResource) {
- return true;
- }
- const currentLabel = this.getActiveResourceName(item);
- return String(currentLabel || '').trim() === this.selectedResource;
- },
- resolveGanttPlanColor(planKey) {
- if (
- this.resourceLabelMode === 'order' &&
- this.orderPlanColorMap[planKey]
- ) {
- return this.orderPlanColorMap[planKey];
- }
- return resolvePlanColor(planKey);
- },
- matchesCalendarResourceFilter(item) {
- if (!this.selectedResource) {
- return true;
- }
- const currentLabel =
- this.activeTab === 'line'
- ? item.productionLineName
- : item.groupLabel || item.teamName || '';
- return String(currentLabel || '').trim() === this.selectedResource;
- },
- getActiveCalendarGroupName(item) {
- if (this.activeTab === 'person') {
- return this.getCalendarPersonGroupName(item);
- }
- return this.getCalendarTeamGroupName(item);
- },
- getCalendarTeamGroupName(item) {
- if (this.resourceLabelMode === 'order') {
- return (
- item.workstationName ||
- item.workstation ||
- item.stationName ||
- item.workStationName ||
- item.factoryWorkstationName ||
- item.resourceName ||
- item.name ||
- item.teamName ||
- '\u672a\u5206\u7ec4'
- );
- }
- return item.name || item.teamName || '\u672a\u5206\u7ec4';
- },
- getCalendarPersonGroupName(item) {
- if (this.resourceLabelMode === 'order') {
- return this.getTaskPersonName(item) || '\u672a\u5206\u7ec4';
- }
- return this.getTaskPersonName(item) || '\u672a\u5206\u7ec4';
- },
- getCalendarProductionLineName(item, resourceList = []) {
- if (this.resourceLabelMode === 'order') {
- return (
- item.equipmentName ||
- item.deviceName ||
- item.assetName ||
- item.machineName ||
- resourceList[0]?.equipmentName ||
- resourceList[0]?.deviceName ||
- resourceList[0]?.assetName ||
- resourceList[0]?.machineName ||
- item.productionLineName ||
- ''
- );
- }
- return item.productionLineName || '';
- },
- getPlanProductionLineName(planCode) {
- if (!planCode) {
- return '';
- }
- const planMeta = this.planMetaByCode[planCode] || {};
- return String(planMeta.productionLineName || '').trim();
- },
- getTaskTeamName(item) {
- return String(
- item.teamName || item.laneLabel || item.name || ''
- ).trim();
- },
- getTaskWorkstationName(item) {
- return String(
- item.workstationName ||
- item.workstation ||
- item.stationName ||
- item.workStationName ||
- item.factoryWorkstationName ||
- item.resourceName ||
- item.laneLabel ||
- item.name ||
- ''
- ).trim();
- },
- getTaskEquipmentName(item) {
- return String(
- item.equipmentName ||
- item.deviceName ||
- item.deviceCode ||
- item.assetName ||
- item.machineName ||
- item.productionLineName ||
- ''
- ).trim();
- },
- getTaskPersonName(item) {
- const directValue = String(
- item.executionObjectType === 'person'
- ? item.executionObjectName || ''
- : ''
- ).trim();
- const name =
- directValue ||
- item.assigneeUserName ||
- item.assigneeUser?.[0]?.userName ||
- item.assigneeUser?.[0]?.name ||
- item.executionUserName ||
- item.executionAssigneeName ||
- item.assigneeName ||
- item.operatorName ||
- item.userName ||
- item.executionTeamLeader ||
- item.leaderUserName ||
- '';
- return String(name || '')
- .replace(/^\u4eba\u5458[:\uff1a]/, '')
- .trim();
- },
- getActiveResourceName(item) {
- if (this.activeTab === 'line') {
- return this.getLineResourceName(item);
- }
- if (this.activeTab === 'person') {
- return this.getPersonResourceName(item);
- }
- return this.getTeamResourceName(item);
- },
- getTeamResourceName(item) {
- if (this.resourceLabelMode === 'order') {
- return (
- this.getTaskWorkstationName(item) || this.getTaskTeamName(item)
- );
- }
- return this.getTaskTeamName(item);
- },
- getPersonResourceName(item) {
- return this.getTaskPersonName(item);
- },
- getLineResourceName(item) {
- if (this.resourceLabelMode === 'order') {
- return (
- this.getTaskEquipmentName(item) ||
- this.getTaskProductionLineName(item)
- );
- }
- return this.getTaskProductionLineName(item);
- },
- getTaskProductionLineName(item) {
- const directValue = String(item.productionLineName || '').trim();
- const teamValue = this.getTaskTeamName(item);
- const planLineName =
- this.getPlanProductionLineName(item.planCode || item.code) || '';
- if (planLineName) {
- return planLineName;
- }
- if (directValue && directValue !== teamValue) {
- return directValue;
- }
- return '';
- },
- bindGanttHoverTooltip() {
- if (!this.$refs.gantt || this._ganttHoverBound) {
- return;
- }
- this._ganttHoverBound = true;
- this.$refs.gantt.addEventListener(
- 'mousemove',
- this.handleGanttMouseMove
- );
- this.$refs.gantt.addEventListener('mouseleave', this.hideHoverTooltip);
- },
- handleGanttMouseMove(event) {
- if (this.viewMode !== 'gantt') {
- this.hideHoverTooltip();
- return;
- }
- const taskNode =
- event.target &&
- event.target.closest &&
- event.target.closest('.gantt_task_line[data-task-id]');
- if (!taskNode) {
- this.hideHoverTooltip();
- return;
- }
- const taskId = taskNode.getAttribute('data-task-id');
- const task = this.ganttData.find(
- (item) => String(item.id) === String(taskId)
- );
- if (!task) {
- this.hideHoverTooltip();
- return;
- }
- this.setHoverTooltip(
- buildTooltipHtml(
- task,
- this.planMetaByCode,
- this.activeTab,
- this.resourceLabelMode
- ),
- event
- );
- },
- hideHoverTooltip() {
- this.removeNativeGanttTooltips();
- if (!this.hoverTooltip.visible) {
- return;
- }
- this.hoverTooltip = {
- ...this.hoverTooltip,
- visible: false
- };
- },
- removeNativeGanttTooltips() {
- document.querySelectorAll('.gantt_tooltip').forEach((item) => {
- item.parentNode && item.parentNode.removeChild(item);
- });
- },
- getHoverTooltipBaseStyle(event) {
- const viewportWidth =
- window.innerWidth || document.documentElement.clientWidth || 1200;
- const viewportHeight =
- window.innerHeight || document.documentElement.clientHeight || 800;
- const margin = 12;
- const maxWidth = Math.max(260, Math.min(420, viewportWidth - 24));
- const maxHeight = Math.max(180, viewportHeight - 24);
- return {
- left: `${Math.min(
- event.clientX + 14,
- viewportWidth - maxWidth - margin
- )}px`,
- top: `${Math.min(
- event.clientY + 14,
- viewportHeight - 180 - margin
- )}px`,
- maxWidth: `${maxWidth}px`,
- maxHeight: `${maxHeight}px`
- };
- },
- setHoverTooltip(content, event) {
- this.removeNativeGanttTooltips();
- this.hoverTooltip = {
- visible: true,
- content,
- style: this.getHoverTooltipBaseStyle(event)
- };
- this.$nextTick(() => {
- this.adjustHoverTooltipPosition(event);
- });
- },
- adjustHoverTooltipPosition(event) {
- const tooltip = this.$refs.hoverTooltip;
- if (!tooltip || !event) {
- return;
- }
- const viewportWidth =
- window.innerWidth || document.documentElement.clientWidth || 1200;
- const viewportHeight =
- window.innerHeight || document.documentElement.clientHeight || 800;
- const margin = 12;
- const offset = 14;
- const rect = tooltip.getBoundingClientRect();
- const width = rect.width || 360;
- const height = rect.height || 220;
- let left = event.clientX + offset;
- let top = event.clientY + offset;
- if (left + width + margin > viewportWidth) {
- left = event.clientX - width - offset;
- }
- if (top + height + margin > viewportHeight) {
- top = event.clientY - height - offset;
- }
- left = Math.max(margin, Math.min(left, viewportWidth - width - margin));
- top = Math.max(margin, Math.min(top, viewportHeight - height - margin));
- this.hoverTooltip = {
- ...this.hoverTooltip,
- style: {
- ...this.hoverTooltip.style,
- left: `${left}px`,
- top: `${top}px`
- }
- };
- },
- syncGanttVisibility() {
- this.$nextTick(() => {
- const ganttRoot =
- (this.$refs.gantt && this.$refs.gantt.firstElementChild) ||
- gantt.$root ||
- null;
- const shouldShow = this.viewMode === 'gantt';
- if (ganttRoot && ganttRoot.style) {
- ganttRoot.style.display = shouldShow ? '' : 'none';
- }
- if (this.$refs.gantt_scrollbar && this.$refs.gantt_scrollbar.style) {
- this.$refs.gantt_scrollbar.style.display = shouldShow ? '' : 'none';
- }
- if (!shouldShow) {
- this.hideHoverTooltip();
- }
- if (shouldShow) {
- gantt.render();
- }
- });
- },
- collectUniqueOptions(list, keys) {
- const values = list
- .map((item) => {
- const matchedKey = keys.find((key) => item[key]);
- return matchedKey ? item[matchedKey] : '';
- })
- .filter(Boolean);
- return [...new Set(values)];
- },
- applyCalendarFilters() {
- this.calendarFilters = {
- ...this.calendarQuery
- };
- },
- applyGanttFilters() {
- this.ganttFilters = {
- ...this.ganttQuery
- };
- this.hideHoverTooltip();
- this.initData();
- },
- resetGanttFilters() {
- this.ganttQuery = createSearchQuery();
- this.ganttFilters = createSearchQuery();
- this.hideHoverTooltip();
- this.initData();
- },
- resetCalendarFilters() {
- this.calendarQuery = createSearchQuery();
- this.calendarFilters = createSearchQuery();
- this.calendarExpand = true;
- },
- joinSearchValues(values = []) {
- return values
- .filter((item) => item !== '' && item !== null && item !== undefined)
- .map((item) => String(item))
- .join(' ');
- },
- normalizePlanSearchEntry(entry) {
- const planKey = entry.planCode || entry.code || entry.id;
- const planMeta = this.planMetaByCode[planKey] || {};
- const isOrderScheduling = this.resourceLabelMode === 'order';
- const visibleTaskText = [
- entry.taskName,
- entry.title,
- entry.name,
- entry.text,
- entry.executor
- ];
- return {
- routingName: this.joinSearchValues([
- entry.routingName ||
- entry.produceRoutingName ||
- planMeta.routingName ||
- planMeta.produceRoutingName ||
- planMeta.routeName,
- ...visibleTaskText
- ]),
- productName: this.joinSearchValues([
- entry.productName,
- planMeta.productName,
- isOrderScheduling ? entry.name : '',
- isOrderScheduling ? entry.text : ''
- ]),
- planCode: this.joinSearchValues([
- entry.productionOrderNumber ||
- entry.apsWorkOrderCode ||
- entry.workOrderCode ||
- entry.productionOrderCode,
- entry.displayCode,
- entry.planCode,
- entry.orderCode,
- entry.code,
- planMeta.productionOrderNumber,
- planMeta.apsWorkOrderCode,
- planMeta.workOrderCode,
- planMeta.code,
- planMeta.planCode,
- ...visibleTaskText
- ])
- };
- },
- matchesPlanSearchFilters(entry, filters) {
- const normalizedEntry = this.normalizePlanSearchEntry(entry || {});
- if (
- filters.routingName &&
- !String(normalizedEntry.routingName || '')
- .toLowerCase()
- .includes(filters.routingName.toLowerCase())
- ) {
- return false;
- }
- if (
- filters.productName &&
- !String(normalizedEntry.productName || '')
- .toLowerCase()
- .includes(filters.productName.toLowerCase())
- ) {
- return false;
- }
- if (
- filters.planCode &&
- !String(normalizedEntry.planCode || '')
- .toLowerCase()
- .includes(filters.planCode.toLowerCase())
- ) {
- return false;
- }
- return true;
- },
- matchesGanttFilters(item) {
- return this.matchesPlanSearchFilters(item, this.ganttFilters);
- },
- matchesCalendarFilters(entry) {
- return this.matchesPlanSearchFilters(entry, this.calendarFilters);
- },
- buildFilteredGanttTasks() {
- const resourceMatchedTasks = this.ganttTasks.filter((item) =>
- this.matchesResourceFilter(item)
- );
- const directMatchedTasks = resourceMatchedTasks.filter((item) =>
- this.matchesGanttFilters(item)
- );
- const matchedIds = new Set(directMatchedTasks.map((item) => item.id));
- const shouldExpandMatchedParent =
- (!this.ganttFilters.planCode &&
- (this.ganttFilters.routingName || this.ganttFilters.productName)) ||
- (this.resourceLabelMode !== 'order' && this.ganttFilters.planCode);
- const matchedSplitParentIds = shouldExpandMatchedParent
- ? new Set(
- directMatchedTasks
- .filter((item) => item.render === 'split' && !item.parent)
- .map((item) => item.id)
- )
- : new Set();
- directMatchedTasks.forEach((item) => {
- if (item.parent) {
- matchedIds.add(item.parent);
- }
- });
- resourceMatchedTasks.forEach((item) => {
- if (matchedSplitParentIds.has(item.parent)) {
- matchedIds.add(item.id);
- }
- });
- return resourceMatchedTasks.filter((item) => matchedIds.has(item.id));
- },
- showCalendarTooltip(event, entries) {
- this.removeNativeGanttTooltips();
- this.setHoverTooltip(buildCalendarTooltipHtml(entries), event);
- },
- moveHoverTooltip(event) {
- if (!this.hoverTooltip.visible) {
- return;
- }
- this.adjustHoverTooltipPosition(event);
- },
- getChildRangeMap() {
- return this.ganttTasks.reduce((result, item) => {
- if (!item.parent || !item.start_date || !item.end_date) {
- return result;
- }
- const start = parseDate(item.start_date);
- const end = parseDate(item.end_date);
- if (!start || !end) {
- return result;
- }
- if (!result[item.parent]) {
- result[item.parent] = {
- start,
- end
- };
- return result;
- }
- if (start.getTime() < result[item.parent].start.getTime()) {
- result[item.parent].start = start;
- }
- if (end.getTime() > result[item.parent].end.getTime()) {
- result[item.parent].end = end;
- }
- return result;
- }, {});
- },
- handleViewSwitch(mode) {
- if (this.viewMode === mode) {
- return;
- }
- this.hideHoverTooltip();
- this.removeNativeGanttTooltips();
- this.viewMode = mode;
- },
- async toggleFullscreen() {
- const element = this.$el;
- if (!this.isFullscreen) {
- // 进入全屏
- try {
- if (element.requestFullscreen) {
- await element.requestFullscreen();
- } else if (element.webkitRequestFullscreen) {
- await element.webkitRequestFullscreen();
- } else if (element.mozRequestFullScreen) {
- await element.mozRequestFullScreen();
- } else if (element.msRequestFullscreen) {
- await element.msRequestFullscreen();
- }
- this.isFullscreen = true;
- } catch (err) {
- console.error('全屏请求失败:', err);
- this.$message.warning('浏览器不支持全屏功能');
- }
- } else {
- // 退出全屏
- try {
- if (document.exitFullscreen) {
- await document.exitFullscreen();
- } else if (document.webkitExitFullscreen) {
- await document.webkitExitFullscreen();
- } else if (document.mozCancelFullScreen) {
- await document.mozCancelFullScreen();
- } else if (document.msExitFullscreen) {
- await document.msExitFullscreen();
- }
- this.isFullscreen = false;
- } catch (err) {
- console.error('退出全屏失败:', err);
- }
- }
- this.$nextTick(() => {
- if (this.viewMode === 'gantt') {
- gantt.render();
- }
- });
- },
- handleFullscreenChange() {
- // 监听全屏状态变化(用户按ESC键退出全屏)
- const isCurrentlyFullscreen = !!(
- document.fullscreenElement ||
- document.webkitFullscreenElement ||
- document.mozFullScreenElement ||
- document.msFullscreenElement
- );
- if (this.isFullscreen && !isCurrentlyFullscreen) {
- this.isFullscreen = false;
- this.$nextTick(() => {
- if (this.viewMode === 'gantt') {
- gantt.render();
- }
- });
- }
- },
- handleEscKey(event) {
- if (event.key === 'Escape' && this.isFullscreen) {
- this.toggleFullscreen();
- }
- },
- initGantt() {
- let _this = this;
- gantt.config.readonly = true;
- gantt.config.select_task = false;
- gantt.config.autofit = false;
- gantt.config.xml_date = '%Y-%m-%d %H:%i:%s';
- gantt.config.show_grid = false;
- gantt.config.open_split_tasks = true;
- let daysStyle = function (date) {
- let todayDate = new Date().setHours(0, 0, 0, 0);
- let paramsDate = new Date(date).setHours(0, 0, 0, 0);
- if (todayDate === paramsDate) {
- return 'today';
- }
- if (date.getDay() === 0 || date.getDay() === 6) {
- return 'weekend';
- }
- return '';
- };
- gantt.config.scales = [
- { unit: 'month', step: 1, format: '%Y/%M', css: () => 'month' },
- {
- unit: 'day',
- step: 1,
- format(date) {
- return `
- <div class="gantt-day-scale">
- <div class="gantt-day-scale__date">${date.getDate()}日</div>
- <div class="gantt-day-scale__week">星期${
- WEEK_MAP[date.getDay()]
- }</div>
- </div>
- `;
- },
- css: daysStyle
- }
- ];
- // 这里在时间轴表格内的cell上添加class
- gantt.templates.timeline_cell_class = function (item, date) {
- if (date.getDay() == 0 || date.getDay() == 6) {
- return 'weekend';
- }
- };
- // 条形图里的文字
- gantt.templates.task_text = function (start, end, task) {
- const label = task.name || task.text || task.code || '';
- return `<span gantt_task_content-id="${
- task.id
- }" class="gantt-task-text">${escapeHtml(label)}</span>`;
- };
- // 条形图自定义class
- gantt.templates.task_class = function (start, end, task) {
- return ['task-222', task.customClass].filter(Boolean).join(' ');
- };
- gantt.config.scale_height = 50;
- gantt.config.task_height = 22;
- gantt.config.row_height = 27;
- gantt.config.min_column_width = 40;
- gantt.config.show_task_cells = false;
- gantt.plugins({
- marker: true
- });
- let today = new Date();
- gantt.addMarker({
- start_date: today,
- css: 'today',
- text: ''
- });
- gantt.plugins({
- tooltip: true
- });
- // 自定义tooltip内容
- gantt.templates.tooltip_text = function (start, end, task) {
- return buildTooltipHtml(
- task,
- _this.planMetaByCode,
- _this.activeTab,
- _this.resourceLabelMode
- );
- };
- gantt.attachEvent('onTaskClick', function (id, e) {
- let box = document.querySelector('.gantt_tooltip');
- if (box) {
- box.parentNode.removeChild(box);
- }
- let classList = String(e?.target?.className || '').split(' ');
- // 这里自定义了展开/收起树形的点击事件
- if (classList.includes('gantt_open')) {
- gantt.open(id); // 灞曞紑
- return true;
- }
- if (classList.includes('gantt_close')) {
- gantt.close(id); // 鏀惰捣
- return true;
- }
- // 这里是其他的点击事件
- let task = _this.ganttData.find((item) => {
- return item.id == id;
- });
- if (!task) {
- return true;
- }
- if (task.taskType === 'stage') {
- // toDo()
- return true;
- }
- return true;
- });
- gantt.i18n.setLocale('cn');
- // 初始化gantt
- gantt.init(this.$refs.gantt);
- // 绑定数据
- gantt.parse({
- data: [], // 时间轴上横条的数据
- link: [] // 连接线的数据
- });
- },
- reload() {
- gantt.clearAll();
- if (this.dateRange && this.dateRange.length === 2) {
- gantt.config.start_date = new Date(this.dateRange[0]);
- gantt.config.end_date = new Date(this.dateRange[1]);
- gantt.config.fit_tasks = false;
- } else {
- gantt.config.start_date = null;
- gantt.config.end_date = null;
- gantt.config.fit_tasks = true;
- }
- this.ganttData = JSON.parse(JSON.stringify(this.ganttData));
- gantt.parse({
- data: this.ganttData,
- link: []
- });
- gantt.render();
- },
- getGanttData() {
- // toDo
- // formatGanttData()
- this.ganttData = this.ganttTasks.map((item) =>
- this.applyGanttFullDayRange(item)
- );
- this.reload();
- },
- formatGanttRenderDate(date) {
- const pad = (value) => String(value).padStart(2, '0');
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(
- date.getDate()
- )} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(
- date.getSeconds()
- )}`;
- },
- applyGanttFullDayRange(task) {
- const start = parseDate(task.start_date);
- const end = parseDate(task.end_date);
- if (!start || !end) {
- return task;
- }
- const renderStart = startOfDay(start);
- let renderEnd = new Date(startOfDay(end).getTime() + DAY_MS);
- if (renderEnd.getTime() <= renderStart.getTime()) {
- renderEnd = new Date(renderStart.getTime() + DAY_MS);
- }
- return {
- ...task,
- rawStartDate: task.rawStartDate || task.start_date,
- rawEndDate: task.rawEndDate || task.end_date,
- start_date: this.formatGanttRenderDate(renderStart),
- end_date: this.formatGanttRenderDate(renderEnd)
- };
- },
- initData() {
- const sourceTasks = this.filteredGanttTasks;
- const parentTaskMap = sourceTasks.reduce((result, item) => {
- if (item.render === 'split' && !item.parent) {
- result[item.id] = item;
- }
- return result;
- }, {});
- this.ganttData = sourceTasks
- .filter((item) => !(item.render === 'split' && !item.parent))
- .map((current) => {
- const hasHiddenParent =
- current.parent && parentTaskMap[current.parent];
- const mergedCurrent = hasHiddenParent
- ? Object.assign({}, parentTaskMap[current.parent], current, {
- parent: undefined
- })
- : current;
- let newObj = {};
- if (current.parent || hasHiddenParent) {
- const planKey =
- mergedCurrent.planId ||
- mergedCurrent.planCode ||
- mergedCurrent.code ||
- mergedCurrent.id;
- newObj = Object.assign({}, mergedCurrent, {
- color: this.resolveGanttPlanColor(planKey),
- title: mergedCurrent.title || '任务',
- customClass: 'task-child-plan'
- });
- return this.applyGanttFullDayRange(newObj);
- }
- if (mergedCurrent.type == 1) {
- newObj = Object.assign({}, mergedCurrent, {
- color: '#169fe6',
- title: '计划',
- customClass: 'task-parent-blue'
- });
- } else if (mergedCurrent.type == 2) {
- newObj = Object.assign({}, mergedCurrent, {
- color: '#ffa21a',
- title: '工序',
- customClass: 'task-child-plan'
- });
- } else if (mergedCurrent.type == 3) {
- newObj = Object.assign({}, mergedCurrent, {
- color: '#62ddd4',
- title: '工位',
- customClass: 'task-station-green'
- });
- } else if (mergedCurrent.type == 4) {
- //计划节点
- newObj = Object.assign({}, mergedCurrent, {
- color: '#d1a6ff',
- title: '计划节点',
- customClass: 'task-node-purple'
- });
- } else if (mergedCurrent.type == 5) {
- //任务
- newObj = Object.assign({}, mergedCurrent, {
- color: '#ffa21a',
- title: '任务',
- customClass: 'task-child-plan'
- });
- } else {
- //默认色
- newObj = Object.assign({}, mergedCurrent, {
- color: '#169fe6',
- title: '计划',
- customClass: 'task-parent-blue'
- });
- }
- return this.applyGanttFullDayRange(newObj);
- });
- this.reload();
- }
- },
- activated() {
- this.syncGanttVisibility();
- },
- beforeDestroy() {
- if (this.$refs.gantt) {
- this.$refs.gantt.removeEventListener(
- 'mousemove',
- this.handleGanttMouseMove
- );
- this.$refs.gantt.removeEventListener(
- 'mouseleave',
- this.hideHoverTooltip
- );
- }
- document.removeEventListener('keydown', this.handleEscKey);
- // 移除全屏状态监听
- document.removeEventListener(
- 'fullscreenchange',
- this.handleFullscreenChange
- );
- document.removeEventListener(
- 'webkitfullscreenchange',
- this.handleFullscreenChange
- );
- document.removeEventListener(
- 'mozfullscreenchange',
- this.handleFullscreenChange
- );
- document.removeEventListener(
- 'MSFullscreenChange',
- this.handleFullscreenChange
- );
- this.removeNativeGanttTooltips();
- }
- };
- </script>
- <style lang="scss" scoped>
- .project-gantt {
- position: relative;
- display: flex;
- flex-direction: column;
- width: 100%;
- height: 100%;
- min-height: 260px;
- gap: 6px;
- padding: 6px;
- box-sizing: border-box;
- border: 1px solid #d8e3ef;
- border-radius: 10px;
- background: linear-gradient(
- 135deg,
- rgba(31, 140, 235, 0.06) 0%,
- transparent 32%
- ),
- linear-gradient(180deg, #ffffff 0%, #f7fafc 100%);
- box-shadow: 0 10px 26px rgba(26, 39, 55, 0.08);
- overflow: hidden;
- &.is-fullscreen {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- width: 100vw;
- height: 100vh;
- z-index: 9999;
- background: #eef4fa;
- padding: 10px;
- box-sizing: border-box;
- }
- }
- .project-gantt::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;
- }
- .view-switch {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 6px;
- min-height: 34px;
- padding: 3px 6px;
- box-sizing: border-box;
- border: 1px solid #dfe9f4;
- border-radius: 8px;
- background: rgba(255, 255, 255, 0.94);
- box-shadow: 0 3px 12px rgba(31, 45, 61, 0.05);
- }
- .view-switch-left {
- display: flex;
- align-items: center;
- gap: 6px;
- flex: 1 1 auto;
- min-width: 0;
- overflow-x: auto;
- flex-wrap: nowrap;
- padding-bottom: 1px;
- }
- .view-switch-left::-webkit-scrollbar {
- height: 4px;
- }
- .view-switch-left::-webkit-scrollbar-thumb {
- border-radius: 999px;
- background: #c9d4e2;
- }
- .view-switch-left ::v-deep .el-date-editor--daterange.el-input__inner {
- width: 330px;
- flex: 0 0 auto;
- }
- .gantt-view ::v-deep > .calendar-toolbar,
- .calendar-view ::v-deep > .calendar-toolbar {
- display: none;
- }
- .view-switch ::v-deep .el-button {
- flex: 0 0 auto;
- border-radius: 7px;
- font-weight: 600;
- transition: all 0.18s ease;
- }
- .view-switch ::v-deep .el-button:not(.el-button--primary):hover {
- color: #1683d8;
- border-color: #bfdcf5;
- background: #f4f9ff;
- }
- .view-switch ::v-deep .el-button--primary {
- border-color: #1f8ceb;
- background: linear-gradient(180deg, #2a9af0 0%, #1684df 100%);
- box-shadow: 0 3px 8px rgba(31, 140, 235, 0.2);
- }
- .calendar-view {
- flex: 1;
- min-width: 0;
- min-height: 0;
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .gantt-view {
- flex: 1;
- min-width: 0;
- min-height: 0;
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .project-gantt__body {
- flex: 1;
- min-height: 0;
- display: flex;
- align-items: stretch;
- gap: 6px;
- }
- .gantt-box {
- flex: 1;
- min-width: 0;
- width: calc(100%);
- height: calc(100%);
- background: #fff;
- border: 1px solid #d8e3ef;
- border-radius: 9px;
- overflow: hidden;
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.74);
- }
- .gantt-hover-tooltip {
- position: fixed;
- z-index: 9999;
- pointer-events: none;
- max-width: calc(100vw - 24px);
- max-height: calc(100vh - 24px);
- }
- .gantt-container {
- width: calc(100%);
- height: calc(100%);
- }
- /* 周末高亮 */
- :deep(.weekend) {
- background: #f7f9fb;
- color: #7a8794 !important;
- }
- /* 今日高亮 */
- :deep(.gantt_layout_cell),
- :deep(.gantt_task),
- :deep(.gantt_data_area),
- :deep(.gantt_task_bg),
- :deep(.gantt_task_scale) {
- background: #ffffff;
- }
- :deep(.gantt_task) {
- border-left: none;
- }
- :deep(.gantt_ver_scroll),
- :deep(.gantt_hor_scroll) {
- background: #eef4fa;
- }
- :deep(.gantt_ver_scroll::-webkit-scrollbar),
- :deep(.gantt_hor_scroll::-webkit-scrollbar),
- :deep(.gantt_data_area::-webkit-scrollbar) {
- width: 10px;
- height: 10px;
- }
- :deep(.gantt_ver_scroll::-webkit-scrollbar-thumb),
- :deep(.gantt_hor_scroll::-webkit-scrollbar-thumb),
- :deep(.gantt_data_area::-webkit-scrollbar-thumb) {
- border: 2px solid #f8fafc;
- border-radius: 999px;
- background: #bcc8d6;
- }
- :deep(.gantt_ver_scroll::-webkit-scrollbar-thumb:hover),
- :deep(.gantt_hor_scroll::-webkit-scrollbar-thumb:hover),
- :deep(.gantt_data_area::-webkit-scrollbar-thumb:hover) {
- background: #aeb7c4;
- }
- :deep(.gantt_task_scale) {
- border-bottom: 1px solid #d9e4ef;
- .gantt_scale_line > .gantt_scale_cell {
- color: #344054;
- font-size: 11px;
- border-right: 1px solid #e7edf5;
- background: #fbfdff;
- &.today {
- background: #e8f4ff;
- color: #0f75bd !important;
- box-shadow: inset 0 -2px 0 #14b8a6;
- }
- }
- }
- :deep(.gantt_scale_line) {
- border-bottom: 1px solid #d9e4ef;
- }
- :deep(.gantt_scale_line:first-child .gantt_scale_cell) {
- height: 24px;
- line-height: 24px;
- font-size: 14px;
- font-weight: 600;
- color: #253244;
- background: linear-gradient(180deg, #f9fcff 0%, #eef5fb 100%);
- }
- :deep(.gantt_scale_line:last-child .gantt_scale_cell) {
- height: 26px;
- padding: 0;
- box-sizing: border-box;
- }
- :deep(.gantt-day-scale) {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- text-align: center;
- }
- :deep(.gantt-day-scale__date) {
- font-size: 11px;
- line-height: 13px;
- color: #606266;
- }
- :deep(.gantt-day-scale__week) {
- margin-top: 0;
- font-size: 12px;
- line-height: 14px;
- font-weight: 500;
- color: #303133;
- }
- /* 去掉文件夹图标 */
- :deep(.gantt_tree_icon) {
- &.gantt_folder_closed,
- &.gantt_file,
- &.gantt_folder_open {
- display: none;
- }
- }
- /* 把展开收起图标替换成element-ui的icon */
- /* 这里没有找到在js中配置图标的方法,为省事,使用css */
- /* 扒了element-ui的icon样式表 */
- :deep(.gantt_tree_icon.gantt_open) {
- background-image: none;
- font-family: element-icons !important;
- speak: none;
- font-style: normal;
- font-weight: 400;
- font-variant: normal;
- text-transform: none;
- vertical-align: baseline;
- display: inline-block;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- &::before {
- content: '\e6df';
- }
- }
- :deep(.gantt_tree_icon.gantt_close) {
- background-image: none;
- font-family: element-icons !important;
- speak: none;
- font-style: normal;
- font-weight: 400;
- font-variant: normal;
- text-transform: none;
- vertical-align: baseline;
- display: inline-block;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- &::before {
- content: '\e6e1';
- }
- }
- :deep(.gantt_cell) {
- border-right: 1px solid #edf2f7;
- }
- :deep(.gantt_task_bg) {
- border-right: 1px solid #edf2f7;
- }
- :deep(.gantt_task_bg .gantt_task_row),
- :deep(.gantt_task_row) {
- border-bottom: 1px solid #edf2f7;
- }
- :deep(.gantt_task_bg .gantt_task_row:nth-child(odd)) {
- background: #fbfdff;
- }
- :deep(.gantt_task_bg .gantt_task_row:nth-child(even)) {
- background: #ffffff;
- }
- :deep(.gantt_task_bg .gantt_task_row:hover) {
- background: #f5fbff;
- }
- :deep(.task-222) {
- height: 22px !important;
- line-height: 22px !important;
- border: none !important;
- border-radius: 7px !important;
- box-shadow: 0 3px 8px rgba(15, 23, 42, 0.16) !important;
- filter: saturate(0.92) brightness(1);
- overflow: hidden;
- transition: box-shadow 0.18s ease, filter 0.18s ease;
- }
- :deep(.task-222:hover) {
- filter: saturate(1) brightness(1.02);
- box-shadow: 0 5px 14px rgba(15, 23, 42, 0.2) !important;
- }
- :deep(.gantt_task_line.task-parent-red),
- :deep(.gantt_task_line.task-parent-red .gantt_task_progress) {
- background: #e60012 !important;
- }
- :deep(.gantt_task_line.task-parent-blue),
- :deep(.gantt_task_line.task-parent-blue .gantt_task_progress) {
- background: linear-gradient(90deg, #1688dc 0%, #26a9ee 100%) !important;
- }
- :deep(.gantt_task_line.task-child-plan) {
- border: none !important;
- }
- :deep(.gantt_task_line.task-station-green),
- :deep(.gantt_task_line.task-station-green .gantt_task_progress) {
- background: linear-gradient(90deg, #24a865 0%, #53c988 100%) !important;
- }
- :deep(.gantt_task_line.task-node-purple),
- :deep(.gantt_task_line.task-node-purple .gantt_task_progress) {
- background: linear-gradient(90deg, #8d62e5 0%, #b985ff 100%) !important;
- }
- :deep(.gantt_split_parent) {
- opacity: 1;
- }
- :deep(.gantt_task_content) {
- width: 100%;
- color: #fff;
- }
- :deep(.gantt-task-text) {
- display: block;
- width: 100%;
- overflow: hidden;
- padding: 0 8px;
- box-sizing: border-box;
- color: #fff;
- font-size: 12px;
- font-weight: 600;
- line-height: 22px;
- white-space: nowrap;
- text-overflow: ellipsis;
- text-align: center;
- text-shadow: 0 1px 1px rgba(0, 0, 0, 0.16);
- }
- :deep(.month) {
- font-size: 18px;
- }
- </style>
- <style lang="scss">
- .gantt_tooltip {
- display: none !important;
- padding: 0 !important;
- border: none !important;
- background: transparent !important;
- box-shadow: none !important;
- }
- .gantt-tooltip-card {
- position: relative;
- width: min(420px, calc(100vw - 24px));
- min-width: min(300px, calc(100vw - 24px));
- max-height: inherit;
- padding: 12px 14px 14px;
- box-sizing: border-box;
- overflow-y: auto;
- border: 1px solid rgba(198, 210, 224, 0.92);
- border-radius: 8px;
- background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
- color: #1f2937;
- box-shadow: 0 16px 38px rgba(15, 23, 42, 0.18),
- 0 0 0 1px rgba(255, 255, 255, 0.72) inset;
- backdrop-filter: blur(10px);
- }
- .gantt-tooltip-card::before {
- content: '';
- position: absolute;
- top: 0;
- right: 0;
- left: 0;
- height: 3px;
- background: linear-gradient(90deg, #15b8a6 0%, #2587d8 100%);
- }
- .gantt-tooltip-card::-webkit-scrollbar {
- width: 8px;
- }
- .gantt-tooltip-card::-webkit-scrollbar-thumb {
- border: 2px solid #f8fbff;
- border-radius: 999px;
- background: #b8c7d8;
- }
- .gantt-tooltip-card__title {
- margin-bottom: 10px;
- padding-right: 4px;
- padding-bottom: 8px;
- border-bottom: 1px solid #e8eef6;
- font-size: 13px;
- font-weight: 700;
- line-height: 20px;
- color: #0f172a;
- word-break: break-all;
- }
- .gantt-tooltip-card__row {
- display: flex;
- align-items: flex-start;
- gap: 8px;
- font-size: 12px;
- line-height: 19px;
- }
- .gantt-tooltip-card__section + .gantt-tooltip-card__section {
- margin-top: 10px;
- padding-top: 10px;
- border-top: 1px dashed #d9e4ef;
- }
- .gantt-tooltip-card__section-title {
- display: inline-flex;
- align-items: center;
- height: 20px;
- margin-bottom: 6px;
- padding: 0 8px;
- border-radius: 999px;
- background: #edf8f6;
- font-size: 12px;
- font-weight: 600;
- color: #147b72;
- }
- .gantt-tooltip-card__row + .gantt-tooltip-card__row {
- margin-top: 5px;
- }
- .gantt-tooltip-card__label {
- flex: 0 0 78px;
- color: #64748b;
- text-align: right;
- white-space: nowrap;
- }
- .gantt-tooltip-card__value {
- flex: 1;
- min-width: 0;
- color: #111827;
- font-weight: 500;
- white-space: normal;
- word-break: break-all;
- }
- </style>
|