| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031 |
- <template>
- <vue-fullscreen
- class="fp-container"
- v-cloak
- v-model="isFullscreen"
- fullscreenClass="fp-container"
- :exit-on-click-wrapper="false"
- >
- <div class="fp-container" v-cloak>
- <div class="fp-header">
- <div class="fp-header-left">
- <el-select
- v-model="factoryId"
- size="mini"
- class="fp-select"
- placeholder="工厂名称"
- :popper-append-to-body="false"
- >
- <el-option
- v-for="f in factoryList"
- :key="f.id"
- :label="f.name"
- :value="f.id"
- />
- </el-select>
- <el-date-picker
- v-model="dateRange"
- class="fp-date"
- size="mini"
- type="daterange"
- range-separator="-"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- value-format="yyyy-MM-dd"
- :append-to-body="false"
- />
- </div>
- <div class="fp-title">工厂生产综合看板</div>
- <div class="fp-header-right">
- <div class="fp-time">
- <span class="fp-date-text">{{ date }}</span>
- <span class="fp-time-text">{{ time }}</span>
- <span class="fp-week-text">{{ week }}</span>
- </div>
- <span class="fp-fullscreen-toggle" @click.passive="onFullscreen">
- <i
- v-if="isFullscreen"
- title="取消全屏"
- class="el-icon-_screen-restore"
- ></i>
- <i v-else title="全屏" class="el-icon-_screen-full"></i>
- </span>
- </div>
- </div>
- <div class="fp-body">
- <!-- Left -->
- <div class="fp-col fp-col-left">
- <chart-card title="生产统计" size="small">
- <div class="fp-number-card">
- <div class="fp-number-display">
- <div class="fp-unit-box">
- <span class="fp-unit">十万</span>
- <div class="fp-digit-box">
- <span class="fp-digit">{{
- productionStats.transformQuantity[0]
- }}</span>
- </div>
- </div>
- <div class="fp-unit-box">
- <span class="fp-unit">万</span>
- <div class="fp-digit-box">
- <span class="fp-digit">{{
- productionStats.transformQuantity[1]
- }}</span>
- </div>
- </div>
- <div class="fp-unit-box">
- <span class="fp-unit">千</span>
- <div class="fp-digit-box">
- <span class="fp-digit">{{
- productionStats.transformQuantity[2]
- }}</span>
- </div>
- </div>
- <div class="fp-separator">,</div>
- <div class="fp-unit-box">
- <span class="fp-unit"></span>
- <div class="fp-digit-box">
- <span class="fp-digit">{{
- productionStats.transformQuantity[3]
- }}</span>
- </div>
- </div>
- <div class="fp-unit-box">
- <span class="fp-unit"></span>
- <div class="fp-digit-box">
- <span class="fp-digit">{{
- productionStats.transformQuantity[4]
- }}</span>
- </div>
- </div>
- <div class="fp-unit-box">
- <span class="fp-unit"></span>
- <div class="fp-digit-box">
- <span class="fp-digit">{{
- productionStats.transformQuantity[5]
- }}</span>
- </div>
- </div>
- </div>
- <div class="fp-mini-stats">
- <div class="fp-mini-stat">
- <div class="fp-mini-icon">
- <img src="@/assets/png/Group.png" alt="" />
- </div>
- <div class="fp-mini-info">
- <div class="fp-mini-label">要求生产总数量:</div>
- <div class="fp-mini-value">{{
- productionStats.requiredTotal
- }}</div>
- </div>
- </div>
- <div class="fp-mini-stat">
- <div class="fp-mini-icon">
- <img src="@/assets/png/Group.png" alt="" />
- </div>
- <div class="fp-mini-info">
- <div class="fp-mini-label">生产达成率:</div>
- <div class="fp-mini-value"
- >{{ productionStats.achievementRate }}%</div
- >
- </div>
- </div>
- </div>
- </div>
- </chart-card>
- <chart-card title="五品" size="small">
- <div class="fp-chart-box">
- <device-status-chart
- v-if="isFlag"
- :data="fiveCategoryData"
- class="fp-chart-fill"
- />
- </div>
- </chart-card>
- <chart-card title="四数" size="small">
- <div class="fp-chart-box">
- <Bar3DChart
- v-if="isFlag"
- :data="fourNumberBarData"
- :colors="['#1163fb', '#1163fb', '#1163fb', '#1163fb']"
- :show-label="true"
- class="fp-chart-fill"
- />
- </div>
- </chart-card>
- </div>
- <!-- Middle -->
- <div class="fp-col fp-col-middle">
- <div class="fp-middle-top">
- <div class="fp-middle-title">
- <img class="fp-title-icon" src="@/assets/png/icon.png" alt="" />
- <span>生产情况</span>
- </div>
- <div class="fp-kpis">
- <div class="fp-kpi">
- <div class="fp-kpi-screen">
- <div class="fp-kpi-value fp-kpi-glow">{{
- productionKpi.delayedCount
- }}</div>
- </div>
- <div class="fp-kpi-base"></div>
- <div class="fp-kpi-label">已延期生产计划数</div>
- </div>
- <div class="fp-kpi">
- <div class="fp-kpi-screen">
- <div class="fp-kpi-value fp-kpi-glow"
- >{{ productionKpi.planAchievementRate }}%</div
- >
- </div>
- <div class="fp-kpi-base"></div>
- <div class="fp-kpi-label">计划达成率</div>
- </div>
- <div class="fp-kpi">
- <div class="fp-kpi-screen">
- <div class="fp-kpi-value fp-kpi-glow">{{
- productionKpi.unpublishedCount
- }}</div>
- </div>
- <div class="fp-kpi-base"></div>
- <div class="fp-kpi-label">未发布计划数</div>
- </div>
- </div>
- <div class="fp-middle-note"
- >注:生产计划完成率=已完成生产计划数/所有生产计划数</div
- >
- </div>
- <chart-card title="生产计划进度" size="large">
- <div class="fp-table-wrap">
- <div class="fp-table-tabs">
- <div
- v-for="t in planTabs"
- :key="t.key"
- class="fp-tab"
- :class="{ active: activePlanTab === t.key }"
- @click="activePlanTab = t.key"
- >
- {{ t.label }}
- </div>
- </div>
- <div class="fp-table">
- <table class="fp-main-table">
- <colgroup>
- <col style="width: 11%" />
- <col style="width: 15%" />
- <col style="width: 10%" />
- <col style="width: 9%" />
- <col style="width: 11%" />
- <col style="width: 15%" />
- <col style="width: 15%" />
- <col style="width: 14%" />
- </colgroup>
- <thead>
- <tr>
- <th>计划编号</th>
- <th>产品名称</th>
- <th>批次号</th>
- <th>计划数量</th>
- <th>工艺路线</th>
- <th>计划开始时间</th>
- <th>计划结束时间</th>
- <th>计划状态</th>
- </tr>
- </thead>
- </table>
- <div
- ref="planScrollBody"
- class="fp-scroll-body"
- @mouseenter="stopPlanAutoScroll"
- @mouseleave="startPlanAutoScroll"
- >
- <table class="fp-main-table">
- <colgroup>
- <col style="width: 11%" />
- <col style="width: 15%" />
- <col style="width: 10%" />
- <col style="width: 9%" />
- <col style="width: 11%" />
- <col style="width: 15%" />
- <col style="width: 15%" />
- <col style="width: 14%" />
- </colgroup>
- <tbody>
- <tr
- v-for="row in filteredPlanRows"
- :key="row.planNo"
- class="fp-row"
- :style="{
- backgroundColor: row.isDelayed ? '#ff4d4f' : '#031d42'
- }"
- >
- <td class="fp-cell" @mouseenter="syncOverflowTitle">{{
- row.planNo
- }}</td>
- <td class="fp-cell" @mouseenter="syncOverflowTitle">{{
- row.productName
- }}</td>
- <td class="fp-cell" @mouseenter="syncOverflowTitle">{{
- row.batchNo
- }}</td>
- <td class="fp-cell" @mouseenter="syncOverflowTitle">{{
- row.planQty
- }}</td>
- <td class="fp-cell" @mouseenter="syncOverflowTitle">{{
- row.currentProcess
- }}</td>
- <td class="fp-cell" @mouseenter="syncOverflowTitle">{{
- row.planStart
- }}</td>
- <td class="fp-cell" @mouseenter="syncOverflowTitle">{{
- row.planEnd
- }}</td>
- <td class="fp-cell" @mouseenter="syncOverflowTitle">
- <span
- :class="
- row.isDelayed ? 'fp-status-red' : 'fp-status-blue'
- "
- >
- {{ row.statusText }}
- </span>
- </td>
- </tr>
- <tr v-if="filteredPlanRows.length === 0">
- <td class="fp-empty" colspan="8">暂无生产计划数据</td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- </div>
- </chart-card>
- </div>
- <!-- Right -->
- <div class="fp-col fp-col-right">
- <div class="fp-inventory-card chart-card">
- <div class="card-header fp-inventory-header">
- <div class="card-logo">
- <img :src="inventoryIcon" alt="" />
- </div>
- <div class="card-title">库存台账</div>
- <div class="fp-inventory-filters">
- <span
- class="fp-filter-btn"
- :class="{ active: activeInventoryFilter === 'category' }"
- @click="setInventoryFilter('category')"
- >
- 分类维度
- </span>
- <span
- class="fp-filter-btn"
- :class="{ active: activeInventoryFilter === 'keeper' }"
- @click="setInventoryFilter('keeper')"
- >
- 仓管
- </span>
- <i
- class="el-icon-setting fp-filter-icon"
- @click.stop="showInventoryDropdown = !showInventoryDropdown"
- ></i>
- <div
- v-if="showInventoryDropdown"
- class="fp-inventory-dropdown"
- @click.stop
- >
- <div class="fp-dropdown-header">
- <el-checkbox
- :value="isAllInventoryChecked"
- :indeterminate="
- checkedInventoryCount > 0 &&
- checkedInventoryCount < inventoryXAxisOptions.length
- "
- @change="toggleAllInventoryXAxis"
- >
- 展示设置
- </el-checkbox>
- <span class="fp-dropdown-count"
- >({{ checkedInventoryCount }}/{{
- inventoryXAxisOptions.length
- }})</span
- >
- </div>
- <div class="fp-dropdown-content">
- <div
- v-for="item in inventoryXAxisOptions"
- :key="item.name"
- class="fp-dropdown-item"
- >
- <el-checkbox
- v-model="item.checked"
- @change="handleInventoryXAxisChange"
- >
- {{ item.name }}
- </el-checkbox>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="fp-chart-box card-content">
- <Bar3DChart
- v-if="isFlag"
- :data="inventoryLedgerDisplayData"
- :colors="inventoryBarColors"
- :show-label="true"
- :bar-size="[10, 6]"
- class="fp-chart-fill"
- />
- </div>
- </div>
- <chart-card title="各类型设备台账" size="small">
- <div class="fp-chart-box">
- <bar-line-combo-chart
- v-if="isFlag"
- :categories="deviceLedger.categories"
- :bar-data="deviceLedger.barData"
- :top-bar-data="deviceLedger.topBarData"
- :line-data="deviceLedger.lineData"
- bar-name="占用数"
- top-bar-name="总数量"
- line-name="占比"
- :show-bar-label="false"
- :show-line-label="false"
- :show-legend="false"
- :bar-colors="['#1163fb', '#1163fb', '#1163fb', '#1163fb']"
- top-bar-color="#f9bb19"
- class="fp-chart-fill"
- />
- </div>
- </chart-card>
- <chart-card title="质量统计" size="small">
- <div class="fp-quality">
- <div class="fp-quality-top">
- <div class="fp-quality-item">
- <div class="fp-quality-label">合格率</div>
- <div class="fp-quality-value good"
- >{{ quality.passRate }}%</div
- >
- </div>
- <div class="fp-quality-item">
- <div class="fp-quality-label">不合格率</div>
- <div class="fp-quality-value bad"
- >{{ quality.failRate }}%</div
- >
- </div>
- <div class="fp-quality-item">
- <div class="fp-quality-label">损耗率</div>
- <div class="fp-quality-value warn"
- >{{ quality.lossRate }}%</div
- >
- </div>
- </div>
- <div class="fp-quality-bars">
- <div class="fp-qbar">
- <div class="fp-qbar-name">合格率</div>
- <div class="fp-qbar-track">
- <div
- class="fp-qbar-fill good"
- :style="{ width: quality.passRate + '%' }"
- ></div>
- </div>
- </div>
- <div class="fp-qbar">
- <div class="fp-qbar-name">不合格率</div>
- <div class="fp-qbar-track">
- <div
- class="fp-qbar-fill bad"
- :style="{ width: quality.failRate + '%' }"
- ></div>
- </div>
- </div>
- <div class="fp-qbar">
- <div class="fp-qbar-name">损耗率</div>
- <div class="fp-qbar-track">
- <div
- class="fp-qbar-fill warn"
- :style="{ width: quality.lossRate + '%' }"
- ></div>
- </div>
- </div>
- </div>
- </div>
- </chart-card>
- </div>
- </div>
- </div>
- </vue-fullscreen>
- </template>
- <script>
- import { component as VueFullscreen } from 'vue-fullscreen';
- import ChartCard from './components/ChartCard';
- import BarChart from './components/charts/BarChart';
- import Bar3DChart from './components/charts/Bar3DChart';
- import DeviceStatusChart from './components/charts/DeviceStatusChart';
- import BarLineComboChart from './components/charts/BarLineComboChart';
- import {
- getFactoryProductionDashboardData,
- getFactoryProductionDataByPlan,
- getFactoryProductionDataByStock,
- getFactoryProductionDataByDevice,
- getFactoryListByUser
- } from '@/api/vis/factoryProductionDashboard';
- const inventoryIcon = require('@/assets/png/icon.png');
- export default {
- name: 'FactoryProductionDashboard',
- components: {
- VueFullscreen,
- ChartCard,
- BarChart,
- Bar3DChart,
- DeviceStatusChart,
- BarLineComboChart
- },
- computed: {
- contentWidth() {
- return this.$store.state.theme.contentWidth || '100%';
- },
- inventoryLedgerDisplayData() {
- const sourceData =
- this.activeInventoryFilter === 'keeper'
- ? this.inventoryLedgerKeeperData
- : this.inventoryLedgerData;
- const checkedNames = this.inventoryXAxisOptions
- .filter((opt) => opt.checked)
- .map((opt) => opt.name);
- if (checkedNames.length === 0) return [];
- return sourceData.filter((item) => checkedNames.includes(item.name));
- },
- inventoryBarColors() {
- return (this.inventoryLedgerDisplayData || []).map(() => '#1163fb');
- },
- checkedInventoryCount() {
- return this.inventoryXAxisOptions.filter((opt) => opt.checked).length;
- },
- isAllInventoryChecked() {
- return (
- this.inventoryXAxisOptions.length > 0 &&
- this.checkedInventoryCount === this.inventoryXAxisOptions.length
- );
- },
- filteredPlanRows() {
- if (this.activePlanTab === 'all') return this.planRows;
- return this.planRows.filter((r) => r.type === this.activePlanTab);
- }
- },
- watch: {
- isFullscreen: {
- handler(val) {
- this.isFlag = false;
- this.$nextTick(() => {
- const delay = this._initLayout ? 160 : 0;
- this._initLayout = true;
- const run = () => {
- this.applyScreenSize();
- this.$nextTick(() => {
- this.isFlag = true;
- this.triggerChartResize();
- });
- };
- delay ? setTimeout(run, delay) : run();
- });
- },
- immediate: true
- },
- contentWidth: {
- handler() {
- clearTimeout(this.resizeTimer);
- this.isFlag = false;
- this.resizeTimer = setTimeout(() => {
- this.$nextTick(() => {
- this.applyScreenSize();
- this.isFlag = true;
- });
- }, 300);
- },
- immediate: true
- },
- factoryId() {
- this.loadDashboardData();
- },
- dateRange: {
- handler() {
- this.loadDashboardData();
- },
- deep: true
- },
- activePlanTab() {
- this.fetchPlanData();
- },
- activeInventoryFilter() {
- this.fetchInventoryData();
- },
- filteredPlanRows() {
- this.$nextTick(() => {
- this.restartPlanAutoScroll();
- });
- }
- },
- data() {
- return {
- isFullscreen: false,
- isFlag: false,
- _initLayout: false,
- resizeTimer: null,
- updateTimer: null,
- planAutoScrollTimer: null,
- date: '',
- time: '',
- week: '',
- inventoryIcon,
- factoryId: '',
- factoryList: [],
- dateRange: [],
- productionStats: {
- quantity: 0,
- transformQuantity: [0, 0, 0, 0, 0, 0],
- requiredTotal: 0,
- achievementRate: 0
- },
- productionKpi: {
- delayedCount: 0,
- planAchievementRate: 0,
- unpublishedCount: 0
- },
- fiveCategoryData: [
- { name: '成品', value: 0 },
- { name: '半成品', value: 0 },
- { name: '在制品', value: 0 },
- { name: '废品', value: 0 },
- { name: '返修品', value: 0 }
- ],
- fourNumberBarData: [
- { name: '投入数', value: 0 },
- { name: '产出数', value: 0 },
- { name: '废品数', value: 0 },
- { name: '周转数', value: 0 }
- ],
- inventoryLedgerData: [],
- inventoryLedgerKeeperData: [],
- activeInventoryFilter: 'category',
- showInventoryDropdown: false,
- inventoryXAxisOptions: [],
- deviceLedger: {
- categories: [],
- barData: [],
- topBarData: [],
- lineData: []
- },
- quality: {
- passRate: 0,
- failRate: 0,
- lossRate: 0
- },
- planTabs: [
- { key: 'all', label: '全部' },
- { key: 'monthly', label: '月度滚动计划' },
- { key: 'temporary', label: '临时生产计划' },
- { key: 'research', label: '分厂临时计划' }
- ],
- activePlanTab: 'all',
- planRows: []
- };
- },
- created() {
- this.updateTime();
- this.updateTimer = setInterval(this.updateTime, 1000);
- const now = new Date();
- const today = this.formatDate(now);
- this.dateRange = [today, today];
- this.getFactoryListByData();
- document.addEventListener('click', this.handleDocumentClick);
- },
- mounted() {
- this.applyScreenSize();
- window.addEventListener('resize', this.handleWindowResize);
- document.addEventListener(
- 'fullscreenchange',
- this.handleFullscreenChange
- );
- document.addEventListener(
- 'webkitfullscreenchange',
- this.handleFullscreenChange
- );
- this.$nextTick(() => {
- this.isFlag = true;
- this.startPlanAutoScroll();
- });
- },
- beforeDestroy() {
- clearInterval(this.updateTimer);
- clearTimeout(this.resizeTimer);
- this.stopPlanAutoScroll();
- window.removeEventListener('resize', this.handleWindowResize);
- document.removeEventListener(
- 'fullscreenchange',
- this.handleFullscreenChange
- );
- document.removeEventListener(
- 'webkitfullscreenchange',
- this.handleFullscreenChange
- );
- document.removeEventListener('click', this.handleDocumentClick);
- },
- methods: {
- handleDocumentClick() {
- this.showInventoryDropdown = false;
- },
- toggleAllInventoryXAxis(val) {
- this.inventoryXAxisOptions.forEach((item) => {
- item.checked = val;
- });
- this.handleInventoryXAxisChange();
- },
- handleInventoryXAxisChange() {
- // Force reactivity update by replacing array
- this.inventoryXAxisOptions = [...this.inventoryXAxisOptions];
- },
- updateInventoryXAxisOptions(data) {
- const newNames = data.map((item) => item.name);
- const updatedOptions = newNames.map((name) => {
- const existing = this.inventoryXAxisOptions.find(
- (opt) => opt.name === name
- );
- return { name, checked: existing ? existing.checked : true };
- });
- this.inventoryXAxisOptions = updatedOptions;
- },
- handleWindowResize() {
- clearTimeout(this.resizeTimer);
- this.resizeTimer = setTimeout(() => {
- this.applyScreenSize();
- }, 120);
- },
- syncOverflowTitle(event) {
- const el = event?.currentTarget;
- if (!el) return;
- const text = (el.innerText || '').trim();
- const isOverflowing = el.scrollWidth > el.clientWidth;
- el.title = isOverflowing ? text : '';
- },
- restartPlanAutoScroll() {
- this.stopPlanAutoScroll();
- this.startPlanAutoScroll();
- },
- startPlanAutoScroll() {
- this.stopPlanAutoScroll();
- const scrollBody = this.$refs.planScrollBody;
- if (!scrollBody) return;
- if (scrollBody.scrollHeight <= scrollBody.clientHeight + 2) {
- scrollBody.scrollTop = 0;
- return;
- }
- this.planAutoScrollTimer = setInterval(() => {
- const maxScrollTop =
- scrollBody.scrollHeight - scrollBody.clientHeight;
- if (maxScrollTop <= 0) return;
- if (scrollBody.scrollTop >= maxScrollTop - 1) {
- scrollBody.scrollTop = 0;
- return;
- }
- scrollBody.scrollTop += 1;
- }, 40);
- },
- stopPlanAutoScroll() {
- if (this.planAutoScrollTimer) {
- clearInterval(this.planAutoScrollTimer);
- this.planAutoScrollTimer = null;
- }
- },
- handleFullscreenChange() {
- this.isFlag = false;
- this.$nextTick(() => {
- setTimeout(() => {
- this.applyScreenSize();
- this.$nextTick(() => {
- this.isFlag = true;
- this.triggerChartResize();
- });
- }, 150);
- });
- },
- async getFactoryListByData() {
- const par = {
- type: 1,
- size: 9999
- };
- await getFactoryListByUser(par).then((res) => {
- if (res.list && res.list.length > 0) {
- this.factoryList = res.list.map((el) => {
- return {
- id: el.id,
- name: el.name
- };
- });
- }
- });
- this.factoryId = this.$store.state.user.info.factoryId
- ? this.$store.state.user.info.factoryId
- : this.factoryList.length
- ? this.factoryList[0].id
- : '';
- },
- triggerChartResize() {
- this.$nextTick(() => {
- window.dispatchEvent(new Event('resize'));
- });
- },
- applyScreenSize() {
- const isFs = !!(
- document.fullscreenElement || document.webkitFullscreenElement
- );
- let deviceWidth;
- let deviceHeight;
- if (isFs) {
- deviceWidth =
- window.innerWidth || document.documentElement.clientWidth;
- deviceHeight =
- window.innerHeight || document.documentElement.clientHeight;
- } else {
- deviceWidth = document.documentElement.clientWidth;
- deviceHeight = document.documentElement.clientHeight;
- }
- const eleAdminHeaderHeight =
- (!isFs &&
- document.getElementsByClassName('ele-admin-header')[0]
- ?.offsetHeight) ||
- 0;
- const eleAdminSidebarWidth =
- (!isFs &&
- document.getElementsByClassName('ele-admin-sidebar')[0]
- ?.offsetWidth) ||
- 0;
- const eleAdminTabsHeight =
- (!isFs &&
- document.getElementsByClassName('ele-admin-tabs')[0]
- ?.offsetHeight) ||
- 0;
- const h = isFs
- ? deviceHeight
- : deviceHeight - eleAdminHeaderHeight - eleAdminTabsHeight;
- const w = isFs ? deviceWidth : deviceWidth - eleAdminSidebarWidth;
- const setContainerSize = (item) => {
- item.style.height = h + 'px';
- item.style.width = w + 'px';
- };
- const containers = [...document.getElementsByClassName('fp-container')];
- containers.forEach(setContainerSize);
- document.documentElement.style.fontSize = isFs ? '24px' : '16px';
- },
- onFullscreen() {
- this.isFullscreen = !this.isFullscreen;
- },
- setInventoryFilter(type) {
- if (this.activeInventoryFilter === type) return;
- this.activeInventoryFilter = type;
- },
- updateTime() {
- const now = new Date();
- const pad = (n) => n.toString().padStart(2, '0');
- this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(
- now.getSeconds()
- )}`;
- this.date = `${now.getFullYear()}年${
- now.getMonth() + 1
- }月${now.getDate()}日`;
- const weekMap = {
- 0: '日',
- 1: '一',
- 2: '二',
- 3: '三',
- 4: '四',
- 5: '五',
- 6: '六'
- };
- this.week = `星期${weekMap[now.getDay()]}`;
- },
- formatDate(d) {
- const pad = (n) => n.toString().padStart(2, '0');
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(
- d.getDate()
- )}`;
- },
- formatNumber6(num) {
- const paddedStr = String(num ?? 0).padStart(6, '0');
- return paddedStr.split('').map((c) => Number(c));
- },
- normalizeNumber(value) {
- const num = Number(value);
- return Number.isFinite(num) ? num : 0;
- },
- normalizePercent(value) {
- if (value === null || value === undefined || value === '') return 0;
- const text = String(value).replace('%', '').trim();
- const num = Number(text);
- return Number.isFinite(num) ? Number(num.toFixed(2)) : 0;
- },
- normalizeNameValueList(list) {
- if (!Array.isArray(list)) return [];
- return list.map((item) => ({
- name: item?.name || '--',
- value: this.normalizeNumber(item?.value)
- }));
- },
- getBaseParams(extra = {}) {
- if (!this.factoryId) return null;
- const [rangeStart, rangeEnd] = this.dateRange || [];
- const today = this.formatDate(new Date());
- const startTime = rangeStart || today;
- const endTime = rangeEnd || startTime;
- return {
- factoryId: this.factoryId,
- startTime,
- endTime,
- ...extra
- };
- },
- getInventoryStockType(filterType = this.activeInventoryFilter) {
- return filterType === 'keeper' ? 2 : 1;
- },
- mapPlanTabType(tab = this.activePlanTab) {
- const tabTypeMap = {
- all: 0,
- monthly: 1,
- temporary: 2,
- research: 3
- };
- return tabTypeMap[tab] ?? 0;
- },
- mapPlanStatus(status) {
- const statusMap = {
- 1: '待排产',
- 2: '待发布',
- 3: '发布失败',
- 4: '待生产',
- 5: '进行中',
- 6: '已完成',
- 7: '延期',
- 8: '待下达'
- };
- return statusMap[Number(status)] || '--';
- },
- formatPlanDate(value) {
- if (!value) return '--';
- const text = String(value).replace('T', ' ');
- return text.slice(0, 10);
- },
- mapPlanRow(row, planTab = this.activePlanTab) {
- const status = Number(row?.status || 0);
- return {
- planNo: row?.code || '--',
- productName: row?.productName || '--',
- batchNo: row?.batchNo || '--',
- planQty: this.normalizeNumber(row?.planQuantity),
- currentProcess: row?.produceRoutingName || '--',
- planStart: this.formatPlanDate(row?.plannedStartTime),
- planEnd: this.formatPlanDate(row?.plannedEndTime),
- isDelayed: status === 7,
- statusText: this.mapPlanStatus(status),
- type: planTab
- };
- },
- buildDeviceLedger(list) {
- const normalized = Array.isArray(list)
- ? list.map((item) => {
- const total = this.normalizeNumber(item?.value);
- const occupied = this.normalizeNumber(
- item?.value2 !== undefined ? item?.value2 : item?.value
- );
- return {
- name: item?.name || '--',
- total,
- occupied
- };
- })
- : [];
- return {
- categories: normalized.map((item) => item.name),
- barData: normalized.map((item) => item.occupied),
- topBarData: normalized.map((item) => item.total),
- lineData: normalized.map((item) =>
- item.total
- ? Number(((item.occupied / item.total) * 100).toFixed(2))
- : 0
- )
- };
- },
- applyDashboardSummary(data = {}) {
- const quantity = this.normalizeNumber(data?.quantity);
- this.productionStats.quantity = quantity;
- this.productionStats.transformQuantity = this.formatNumber6(quantity);
- this.productionStats.requiredTotal = this.normalizeNumber(
- data?.totalQuantity
- );
- this.productionStats.achievementRate = this.normalizePercent(
- data?.quantityAchievementRate
- );
- this.productionKpi.delayedCount = this.normalizeNumber(
- data?.delayNumber
- );
- this.productionKpi.planAchievementRate = this.normalizePercent(
- data?.achievementRate
- );
- this.productionKpi.unpublishedCount = this.normalizeNumber(
- data?.numberToBeProduced
- );
- this.fiveCategoryData = [
- {
- name: '成品',
- value: this.normalizeNumber(data?.finishedProductQuantity)
- },
- {
- name: '半成品',
- value: this.normalizeNumber(data?.semiFinishedProductQuantity)
- },
- {
- name: '在制品',
- value: this.normalizeNumber(data?.workInProgressQuantity)
- },
- {
- name: '废品',
- value: this.normalizeNumber(data?.scrapProductQuantity)
- },
- {
- name: '返修品',
- value: this.normalizeNumber(data?.reworkProductQuantity)
- }
- ];
- this.fourNumberBarData = [
- { name: '投入数', value: this.normalizeNumber(data?.inputQuantity) },
- { name: '产出数', value: this.normalizeNumber(data?.outputQuantity) },
- { name: '废品数', value: this.normalizeNumber(data?.scrapQuantity) },
- {
- name: '周转数',
- value: this.normalizeNumber(data?.turnoverQuantity)
- }
- ];
- this.quality.passRate = this.normalizePercent(data?.passRate);
- this.quality.failRate = this.normalizePercent(data?.defectiveRate);
- this.quality.lossRate = this.normalizePercent(data?.attritionRate);
- },
- async loadDashboardData() {
- const baseParams = this.getBaseParams();
- if (!baseParams) return;
- await Promise.all([
- this.fetchSummaryData(baseParams),
- this.fetchInventoryData(baseParams),
- this.fetchDeviceData(baseParams),
- this.fetchPlanData(baseParams)
- ]);
- },
- async fetchSummaryData(baseParams = this.getBaseParams()) {
- if (!baseParams) return;
- try {
- const data = await getFactoryProductionDashboardData(baseParams);
- this.applyDashboardSummary(data || {});
- } catch (error) {
- console.error('获取工厂生产综合看板汇总数据失败:', error);
- this.applyDashboardSummary({});
- }
- },
- async fetchInventoryData(
- baseParams = this.getBaseParams(),
- filterType = this.activeInventoryFilter
- ) {
- if (!baseParams) return;
- try {
- const list = await getFactoryProductionDataByStock({
- ...baseParams,
- stockType: this.getInventoryStockType(filterType)
- });
- if (filterType !== this.activeInventoryFilter) return;
- const normalized = this.normalizeNameValueList(list);
- if (filterType === 'keeper') {
- this.inventoryLedgerKeeperData = normalized;
- } else {
- this.inventoryLedgerData = normalized;
- }
- this.updateInventoryXAxisOptions(normalized);
- } catch (error) {
- console.error('获取库存台账数据失败:', error);
- if (filterType === 'keeper') {
- this.inventoryLedgerKeeperData = [];
- } else {
- this.inventoryLedgerData = [];
- }
- }
- },
- async fetchDeviceData(baseParams = this.getBaseParams()) {
- if (!baseParams) return;
- try {
- const list = await getFactoryProductionDataByDevice(baseParams);
- this.deviceLedger = this.buildDeviceLedger(list);
- } catch (error) {
- console.error('获取各类型设备台账数据失败:', error);
- this.deviceLedger = this.buildDeviceLedger([]);
- }
- },
- async fetchPlanData(
- baseParams = this.getBaseParams(),
- planTab = this.activePlanTab
- ) {
- if (!baseParams) return;
- try {
- const list = await getFactoryProductionDataByPlan({
- ...baseParams,
- type: this.mapPlanTabType(planTab)
- });
- if (planTab !== this.activePlanTab) return;
- this.planRows = Array.isArray(list)
- ? list.map((item) => this.mapPlanRow(item, planTab))
- : [];
- } catch (error) {
- console.error('获取生产计划进度数据失败:', error);
- if (planTab === this.activePlanTab) {
- this.planRows = [];
- }
- }
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- [v-cloak] {
- display: none;
- }
- .fp-container {
- font-size: 16px;
- font-family: 'AlibabaPuHuiTi';
- background-image: url('@/assets/png/bj.png');
- background-repeat: no-repeat;
- background-size: 100% 100%;
- background-color: rgb(8, 29, 65);
- height: 100%;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- min-height: 0;
- }
- .fp-header {
- background-image: url('@/assets/border2.png');
- background-repeat: no-repeat;
- background-size: 100% 100%;
- height: 8.5%;
- min-height: 64px;
- max-height: 72px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 14px;
- box-sizing: border-box;
- }
- .fp-header-left {
- width: 24%;
- display: flex;
- gap: 8px;
- align-items: center;
- justify-content: flex-start;
- padding: 0;
- margin-top: 1%;
- }
- .fp-select {
- width: 36%;
- }
- .fp-date {
- width: 64%;
- }
- /* 顶部筛选控件皮肤,贴近原始大屏样式 */
- ::v-deep .fp-header-left .el-input__inner,
- ::v-deep .fp-header-left .el-range-editor.el-input__inner {
- background-color: #011944;
- border: 1px solid #0b6fd5;
- color: #e9f3ff;
- height: 28px;
- line-height: 28px;
- border-radius: 2px;
- box-shadow: 0 0 4px rgba(11, 109, 213, 0.45);
- }
- ::v-deep .fp-header-left .el-input__inner::placeholder,
- ::v-deep .fp-header-left .el-range-input::placeholder {
- color: #6f8fb8;
- }
- ::v-deep .fp-header-left .el-input__suffix,
- ::v-deep .fp-header-left .el-range__icon,
- ::v-deep .fp-header-left .el-range-separator {
- color: #9fc5ff;
- }
- ::v-deep .fp-header-left .el-range-input {
- background-color: transparent;
- color: #e9f3ff;
- }
- .fp-title {
- flex: 1;
- min-width: 0;
- text-align: center;
- font-family: '优设标题黑';
- color: #fff;
- font-size: clamp(2rem, 2.45vw, 2.7rem);
- letter-spacing: clamp(0.16rem, 0.28vw, 0.4rem);
- line-height: 1.08;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: clip;
- transform: none;
- }
- .fp-header-right {
- width: 24%;
- display: flex;
- justify-content: flex-end;
- align-items: center;
- gap: 12px;
- margin-top: 1%;
- color: #7fa7ce;
- font-size: 0.8rem;
- }
- .fp-time {
- color: rgb(210 215 221);
- font-weight: 600;
- }
- .fp-time-text {
- padding: 0 6px;
- }
- .fp-fullscreen-toggle {
- cursor: pointer;
- }
- .fp-body {
- flex: 1;
- display: flex;
- gap: 0.6rem;
- padding: 0.55rem 0.75rem 0.75rem;
- box-sizing: border-box;
- min-height: 0;
- overflow: hidden;
- }
- .fp-col {
- display: flex;
- flex-direction: column;
- gap: 0.8rem;
- min-height: 0;
- }
- .fp-col-left,
- .fp-col-right {
- width: 24.5%;
- min-width: 0;
- }
- .fp-col-middle {
- flex: 1;
- min-width: 0;
- }
- .fp-chart-box {
- flex: 1;
- min-height: 0;
- padding: 6px 10px 10px;
- box-sizing: border-box;
- }
- .fp-chart-fill {
- width: 100%;
- height: 100%;
- }
- /* 库存台账:自定义卡片,标题右侧带筛选 */
- .fp-inventory-card {
- flex: 0.8 !important;
- background-color: rgba(8, 29, 65, 0.8);
- border: 1px solid #124c77;
- overflow: visible;
- display: flex;
- flex-direction: column;
- min-height: 0;
- }
- .fp-inventory-header {
- display: flex;
- align-items: center;
- padding: 0.2rem 0.5rem;
- gap: 0.3rem;
- background-color: rgb(0, 64, 133);
- border-bottom: 1px solid #124c77;
- font-family: '优设标题黑';
- font-size: 1.2rem;
- color: #fff;
- flex-shrink: 0;
- }
- .fp-inventory-header .card-title {
- flex: 1;
- font-size: clamp(1rem, 1.8vw, 1.4rem);
- }
- .fp-inventory-filters {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 0.75rem;
- color: #9fc5ff;
- }
- .fp-filter-btn {
- cursor: pointer;
- padding: 2px 6px;
- border: 1px solid rgba(18, 76, 119, 0.8);
- border-radius: 2px;
- white-space: nowrap;
- }
- .fp-filter-btn:hover {
- color: #3bfff2;
- border-color: rgba(20, 205, 201, 0.6);
- }
- .fp-filter-btn.active {
- color: #3bfff2;
- background: linear-gradient(
- 180deg,
- rgba(20, 205, 201, 0.26),
- rgba(17, 99, 251, 0.2)
- );
- border-color: rgba(20, 205, 201, 0.85);
- box-shadow: 0 0 8px rgba(20, 205, 201, 0.35);
- }
- .fp-filter-icon {
- font-size: 1rem;
- cursor: pointer;
- }
- .fp-inventory-card .card-content {
- overflow: hidden;
- flex: 1;
- min-height: 0;
- }
- .fp-inventory-header {
- position: relative;
- z-index: 10;
- }
- .fp-inventory-dropdown {
- position: absolute;
- top: calc(100% + 6px);
- right: 0;
- min-width: 160px;
- background: rgba(1, 25, 68, 0.96);
- border: 1px solid rgba(11, 111, 213, 0.8);
- border-radius: 2px;
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.6), 0 0 6px rgba(11, 111, 213, 0.3);
- z-index: 1000;
- overflow: hidden;
- }
- .fp-dropdown-header {
- padding: 8px 12px;
- background: rgba(11, 111, 213, 0.25);
- border-bottom: 1px solid rgba(11, 111, 213, 0.4);
- color: #e9f3ff;
- font-size: 0.8rem;
- display: flex;
- align-items: center;
- gap: 4px;
- }
- .fp-dropdown-count {
- color: #9fc5ff;
- font-size: 0.75rem;
- margin-left: auto;
- }
- .fp-dropdown-content {
- max-height: 280px;
- overflow-y: auto;
- padding: 2px 0;
- }
- .fp-dropdown-content::-webkit-scrollbar {
- width: 6px;
- }
- .fp-dropdown-content::-webkit-scrollbar-track {
- background: rgba(3, 29, 66, 0.5);
- }
- .fp-dropdown-content::-webkit-scrollbar-thumb {
- background: rgba(11, 111, 213, 0.6);
- border-radius: 3px;
- }
- .fp-dropdown-item {
- padding: 6px 12px;
- cursor: pointer;
- transition: background-color 0.15s;
- border-bottom: 1px solid rgba(11, 111, 213, 0.1);
- }
- .fp-dropdown-item:last-child {
- border-bottom: none;
- }
- .fp-dropdown-item:hover {
- background-color: rgba(11, 111, 213, 0.15);
- }
- ::v-deep .fp-dropdown-item .el-checkbox,
- ::v-deep .fp-dropdown-header .el-checkbox {
- display: flex;
- align-items: center;
- width: 100%;
- }
- ::v-deep .fp-dropdown-item .el-checkbox__label,
- ::v-deep .fp-dropdown-header .el-checkbox__label {
- color: #d4e4ff;
- font-size: 0.78rem;
- padding-left: 6px;
- }
- ::v-deep .fp-dropdown-item .el-checkbox__input.is-checked .el-checkbox__inner,
- ::v-deep
- .fp-dropdown-header
- .el-checkbox__input.is-checked
- .el-checkbox__inner {
- background-color: #0b6fd5;
- border-color: #0b6fd5;
- }
- ::v-deep
- .fp-dropdown-item
- .el-checkbox__input.is-checked
- + .el-checkbox__label,
- ::v-deep
- .fp-dropdown-header
- .el-checkbox__input.is-checked
- + .el-checkbox__label {
- color: #fff;
- }
- ::v-deep .fp-dropdown-item .el-checkbox__inner,
- ::v-deep .fp-dropdown-header .el-checkbox__inner {
- width: 14px;
- height: 14px;
- background-color: rgba(1, 25, 68, 0.6);
- border-color: rgba(11, 111, 213, 0.6);
- }
- ::v-deep .fp-dropdown-item .el-checkbox__inner:hover,
- ::v-deep .fp-dropdown-header .el-checkbox__inner:hover {
- border-color: #3bfff2;
- }
- /* left number card */
- .fp-number-card {
- height: 100%;
- min-width: 0;
- padding: 0 10px 10px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- box-sizing: border-box;
- }
- .fp-number-display {
- flex: 1;
- min-width: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 0.3rem;
- }
- .fp-unit-box {
- display: flex;
- flex-direction: column;
- align-items: center;
- flex: 1 1 0;
- min-width: 1.2rem;
- max-width: 3rem;
- height: 5rem;
- position: relative;
- }
- .fp-unit {
- font-size: 14px;
- color: #a4b9d7;
- position: absolute;
- top: -22px;
- left: 50%;
- transform: translateX(-50%);
- width: 35px;
- white-space: nowrap;
- }
- .fp-digit-box {
- background-image: url('@/assets/png/num_bj.png');
- background-repeat: no-repeat;
- background-size: 100% 100%;
- color: #fff;
- padding: 0 0.4rem;
- height: 5rem;
- font-size: clamp(1rem, 3.5vw, 2.2rem);
- display: flex;
- align-items: center;
- justify-content: center;
- font-weight: bold;
- border-radius: 2px;
- width: 100%;
- box-sizing: border-box;
- }
- .fp-separator {
- flex-shrink: 0;
- color: #fff;
- font-size: clamp(1.2rem, 3vw, 2.5rem);
- font-weight: 700;
- margin: 0 2px;
- display: flex;
- align-items: center;
- }
- .fp-mini-stats {
- display: flex;
- gap: 10px;
- }
- .fp-mini-stat {
- flex: 1;
- display: flex;
- gap: 8px;
- padding: 6px 8px;
- border: 1px solid rgba(88, 137, 196, 0.9);
- background-color: rgba(0, 123, 255, 0.1);
- border-radius: 4px;
- align-items: center;
- min-width: 0;
- }
- .fp-mini-icon {
- width: 1.6rem;
- height: 1.6rem;
- padding: 0.35rem;
- background-color: rgba(0, 64, 133, 0.3);
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .fp-mini-icon img {
- width: 100%;
- height: 100%;
- object-fit: contain;
- }
- .fp-mini-info {
- min-width: 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- flex: 1;
- }
- .fp-mini-label {
- color: #fff;
- font-size: 0.75rem;
- letter-spacing: 1px;
- white-space: nowrap;
- }
- .fp-mini-value {
- color: #3bfff2;
- font-weight: bold;
- font-size: 0.75rem;
- white-space: nowrap;
- }
- /* middle top */
- .fp-middle-top {
- border: 1px solid #031a64;
- background: radial-gradient(
- circle at top,
- rgba(50, 197, 225, 0.22),
- transparent 55%
- )
- rgba(5, 18, 86, 0.95);
- height: 21%;
- min-height: 118px;
- display: flex;
- flex-direction: column;
- padding: 8px 12px 10px;
- box-sizing: border-box;
- }
- .fp-middle-title {
- display: flex;
- align-items: center;
- gap: 8px;
- color: #fff;
- font-family: '优设标题黑';
- font-size: 1.2rem;
- }
- .fp-title-icon {
- width: 18px;
- height: 18px;
- }
- .fp-kpis {
- flex: 1;
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 14px;
- align-items: center;
- padding-top: 6px;
- }
- .fp-kpi {
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: flex-start;
- color: #fff;
- gap: 4px;
- position: relative;
- overflow: hidden;
- padding-top: 6px;
- }
- .fp-kpi-screen {
- width: 66%;
- min-height: 3.5rem;
- border: 1px solid rgba(43, 150, 255, 0.6);
- border-radius: 6px;
- background: linear-gradient(
- 180deg,
- rgba(118, 255, 255, 0.22),
- rgba(40, 176, 255, 0.26) 54%,
- rgba(10, 36, 121, 0.36)
- );
- box-shadow: 0 0 20px rgba(43, 202, 255, 0.4),
- 0 0 12px rgba(17, 99, 251, 0.28) inset;
- display: flex;
- align-items: center;
- justify-content: center;
- position: relative;
- transform: perspective(280px) rotateX(8deg) skewX(-3deg);
- transform-origin: center bottom;
- }
- .fp-kpi-screen::before,
- .fp-kpi-screen::after {
- content: '';
- position: absolute;
- bottom: -8px;
- width: 24%;
- height: 10px;
- border-bottom: 2px solid rgba(43, 202, 255, 0.62);
- }
- .fp-kpi-screen::before {
- left: 3%;
- transform: skewX(-26deg);
- }
- .fp-kpi-screen::after {
- right: 3%;
- transform: skewX(26deg);
- }
- .fp-kpi-base {
- width: 44%;
- height: 0.32rem;
- border-radius: 999px;
- background: linear-gradient(
- 180deg,
- rgba(142, 255, 255, 0.98),
- rgba(22, 238, 255, 0.95) 48%,
- rgba(17, 99, 251, 0.82)
- );
- box-shadow: 0 0 16px rgba(22, 238, 255, 0.72),
- 0 0 8px rgba(17, 99, 251, 0.45);
- }
- .fp-kpi-value {
- font-family: 'LCD2B';
- font-size: clamp(1.45rem, 3vw, 2.5rem);
- font-weight: 800;
- letter-spacing: 0.06em;
- }
- .fp-kpi-label {
- font-size: 0.92rem;
- color: #cfe4ff;
- letter-spacing: 0.5px;
- margin-top: 4px;
- }
- .fp-kpi-glow {
- text-shadow: 0 0 8px rgba(185, 255, 255, 0.8),
- 0 0 16px rgba(20, 205, 201, 0.65), 0 0 24px rgba(17, 99, 251, 0.42);
- }
- .fp-middle-note {
- font-size: 0.75rem;
- color: rgba(255, 255, 255, 0.85);
- letter-spacing: 0.12rem;
- text-align: right;
- }
- /* table */
- .fp-table-wrap {
- height: 100%;
- display: flex;
- flex-direction: column;
- padding: 4px 8px 8px;
- box-sizing: border-box;
- min-height: 0;
- }
- .fp-table-tabs {
- display: flex;
- gap: 6px;
- padding: 4px 0 8px;
- flex-wrap: wrap;
- }
- .fp-tab {
- padding: 6px 10px;
- border: 1px solid rgba(18, 76, 119, 0.9);
- background-color: rgba(3, 29, 66, 0.6);
- color: #e9ecef;
- border-radius: 4px;
- cursor: pointer;
- font-size: 0.85rem;
- user-select: none;
- }
- .fp-tab.active {
- border-color: rgba(20, 205, 201, 0.95);
- color: #3bfff2;
- box-shadow: 0 0 10px rgba(20, 205, 201, 0.2);
- }
- .fp-table {
- flex: 1;
- min-height: 0;
- display: flex;
- flex-direction: column;
- }
- .fp-main-table {
- width: 100%;
- border-collapse: collapse;
- table-layout: fixed;
- }
- .fp-main-table thead th {
- background-color: rgba(3, 29, 66, 0.95);
- padding: 0.55rem 0.2rem;
- text-align: center;
- font-weight: bold;
- color: #0b6fd5;
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
- font-size: clamp(0.74rem, 0.9vw, 0.82rem);
- line-height: 1.15;
- white-space: normal;
- overflow: hidden;
- text-overflow: clip;
- word-break: keep-all;
- }
- .fp-scroll-body {
- flex: 1;
- min-height: 0;
- overflow: hidden;
- }
- .fp-cell {
- padding: 0.35rem 0.2rem;
- text-align: center;
- color: #e9ecef;
- font-size: 0.82rem;
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .fp-row:hover .fp-cell {
- background-color: rgba(3, 29, 66, 0.8) !important;
- }
- .fp-empty {
- text-align: center;
- padding: 1rem;
- color: #7fa7ce;
- }
- .fp-status-red {
- color: #fff;
- font-weight: 700;
- }
- .fp-status-blue {
- color: #ffd16c;
- font-weight: 700;
- }
- /* quality */
- .fp-quality {
- height: 100%;
- padding: 8px 10px 10px;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- gap: 12px;
- perspective: 700px;
- }
- .fp-quality-top {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 10px;
- }
- .fp-quality-item {
- border: 1px solid rgba(18, 76, 119, 0.9);
- border-radius: 6px;
- background: linear-gradient(
- 180deg,
- rgba(22, 120, 201, 0.18),
- rgba(3, 29, 66, 0.72) 62%,
- rgba(2, 21, 54, 0.9)
- );
- padding: 8px 10px;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 6px;
- position: relative;
- transform: perspective(260px) rotateX(8deg);
- transform-origin: center bottom;
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.22),
- 0 0 12px rgba(18, 129, 214, 0.22) inset;
- overflow: hidden;
- }
- .fp-quality-item::before {
- content: '';
- position: absolute;
- top: 0;
- left: 8%;
- right: 8%;
- height: 1px;
- background: linear-gradient(
- 90deg,
- rgba(255, 255, 255, 0),
- rgba(153, 245, 255, 0.95),
- rgba(255, 255, 255, 0)
- );
- }
- .fp-quality-label {
- color: #cfe4ff;
- font-size: 0.85rem;
- letter-spacing: 1px;
- }
- .fp-quality-value {
- font-family: 'LCD2B';
- font-size: clamp(1.25rem, 2.1vw, 1.6rem);
- font-weight: 800;
- text-shadow: 0 0 8px rgba(111, 233, 255, 0.35),
- 0 0 14px rgba(17, 99, 251, 0.28);
- }
- .fp-quality-value.good {
- color: #14cdc9;
- }
- .fp-quality-value.bad {
- color: #ff4d4f;
- }
- .fp-quality-value.warn {
- color: #f9bb19;
- }
- .fp-quality-bars {
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 10px;
- }
- .fp-qbar {
- display: flex;
- gap: 10px;
- align-items: center;
- }
- .fp-qbar-name {
- width: 72px;
- color: #e9ecef;
- font-size: 0.85rem;
- }
- .fp-qbar-track {
- flex: 1;
- height: 10px;
- border-radius: 999px;
- background: linear-gradient(
- 180deg,
- rgba(6, 34, 82, 0.92),
- rgba(2, 25, 61, 0.88)
- );
- border: 1px solid rgba(18, 76, 119, 0.9);
- overflow: hidden;
- position: relative;
- box-shadow: 0 2px 7px rgba(0, 0, 0, 0.3) inset;
- }
- .fp-qbar-track::before {
- content: '';
- position: absolute;
- left: 4px;
- right: 4px;
- top: 1px;
- height: 2px;
- border-radius: 999px;
- background: linear-gradient(
- 90deg,
- rgba(255, 255, 255, 0),
- rgba(168, 233, 255, 0.5),
- rgba(255, 255, 255, 0)
- );
- pointer-events: none;
- }
- .fp-qbar-fill {
- height: 100%;
- border-radius: 999px;
- box-shadow: 0 0 10px currentColor;
- position: relative;
- }
- .fp-qbar-fill::after {
- content: '';
- position: absolute;
- right: 0;
- top: -1px;
- width: 16px;
- height: calc(100% + 2px);
- border-radius: 999px;
- background: radial-gradient(
- circle at 25% 50%,
- rgba(255, 255, 255, 0.62),
- rgba(255, 255, 255, 0) 70%
- );
- }
- .fp-qbar-fill.good {
- background: linear-gradient(
- 90deg,
- rgba(20, 205, 201, 1),
- rgba(20, 205, 201, 0.25)
- );
- }
- .fp-qbar-fill.bad {
- background: linear-gradient(
- 90deg,
- rgba(255, 77, 79, 1),
- rgba(255, 77, 79, 0.2)
- );
- }
- .fp-qbar-fill.warn {
- background: linear-gradient(
- 90deg,
- rgba(249, 187, 25, 1),
- rgba(249, 187, 25, 0.25)
- );
- }
- /* 覆盖通用 ChartCard 的最小高度,避免低分辨率下被挤出可视区 */
- ::v-deep .chart-container,
- ::v-deep .process-chart-container,
- ::v-deep .product-top10-chart-container,
- ::v-deep .qualification-rate-chart-container {
- min-height: 0 !important;
- }
- ::v-deep .chart-card .card-content {
- min-height: 0;
- }
- </style>
- <style>
- .el-icon-_screen-full,
- .el-icon-_screen-restore {
- font-size: 1.2rem;
- cursor: pointer;
- transition: all 0.3s;
- color: #7fa7ce;
- }
- .el-icon-_screen-full:hover,
- .el-icon-_screen-restore:hover {
- color: #fff;
- transform: scale(1.1);
- }
- </style>
|