| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>MES综合大屏</title>
- <link rel="stylesheet" href="${ctxStatic}/bootstrap/css/bootstrap.min.css">
- <link rel="stylesheet" href="${ctxStatic}/screen/style.css">
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- overflow: hidden;
- }
- .mes3-wrapper {
- position: relative;
- width: 1920px;
- height: 1080px;
- background-color: #00091A;
- background-image: url("${ctxStatic}/screen/imgs/ljrSreen.png");
- background-size: 100% 100%;
- background-position: center;
- overflow: hidden;
- margin: 0 auto;
- }
- /* ===== 顶部标题栏 ===== */
- .mes3-header {
- position: absolute;
- top: -13px;
- left: 0;
- width: 100%;
- height: 87px;
- z-index: 10;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 20px;
- }
- .mes3-logo {
- height: 50px;
- width: auto;
- vertical-align: middle;
- }
- .mes3-header-title {
- position: absolute;
- left: 50%;
- top: -13px;
- transform: translateX(-50%);
- width: 1833px;
- height: 162px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 32px;
- font-weight: bold;
- color: #FFFFFF;
- font-family: "Microsoft YaHei", sans-serif;
- letter-spacing: 2px;
- text-shadow: 0 0 10px rgba(77, 201, 245, 0.5);
- background-image: url("${ctxStatic}/screen/imgs/ljrBt.png");
- background-size: contain;
- background-position: center;
- background-repeat: no-repeat;
- }
- .mes3-bt {
- position: absolute;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- height: 45px;
- width: auto;
- vertical-align: middle;
- }
- .mes3-header-right {
- text-align: right;
- font-family: "Microsoft YaHei", sans-serif;
- margin-right: 88px;
- }
- .mes3-header-right .mes3-date {
- font-size: 16px;
- line-height: 24px;
- color: #4DC9F5;
- }
- .mes3-header-right .mes3-time {
- font-size: 24px;
- line-height: 32px;
- font-weight: bold;
- color: #FFFFFF;
- }
- .mes-fullscreen-btn {
- position: absolute;
- top: 22px;
- right: 18px;
- z-index: 30;
- padding: 6px 14px;
- font-size: 14px;
- line-height: 1.2;
- color: #4DC9F5;
- background: rgba(0, 20, 50, 0.65);
- border: 1px solid #4DC9F5;
- border-radius: 4px;
- cursor: pointer;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .mes-fullscreen-btn:hover {
- color: #FFFFFF;
- background: rgba(77, 201, 245, 0.28);
- }
- html:fullscreen .mes-fullscreen-btn,
- html:-webkit-full-screen .mes-fullscreen-btn {
- display: none !important;
- }
- /* ===== 中间主体区域 ===== */
- .mes3-body {
- position: absolute;
- top: 87px;
- left: 0;
- right: 0;
- bottom: 0;
- }
- /* ===== 左侧三列面板(容器铺满主体,便于按名称重排到与 screen3/4/5 对齐) ===== */
- .mes3-left {
- position: absolute;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- width: auto;
- pointer-events: none;
- }
- .mes3-left .mes3-panel {
- pointer-events: auto;
- }
- /* 通用面板样式 */
- .mes3-panel {
- position: absolute;
- left: 0;
- right: auto;
- background-image: url("${ctxStatic}/screen/imgs/ljrZ1.png");
- background-size: 100% 100%;
- background-repeat: no-repeat;
- }
- .mes3-panel .panel-header {
- height: 45px;
- line-height: 45px;
- text-align: center;
- font-size: 18px;
- font-weight: bold;
- color: #4DC9F5;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .mes3-panel .panel-body {
- position: absolute;
- top: 45px;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 10px;
- overflow: hidden;
- }
- /* 近七日产量折线图 -> 右上(对齐前屏「总览」高度区) */
- .mes3-panel-left1 {
- left: 1450px;
- top: 58px;
- width: 440px;
- height: 290px;
- background-image: url("${ctxStatic}/screen/imgs/ljrY1.png");
- }
- .mes3-panel-left1 .panel-body {
- display: block;
- padding: 8px 10px;
- }
- .mes3-panel-left1 .panel-body img {
- width: 70px;
- height: auto;
- }
- /* 近七日产品抽检/质检 -> 左下(对齐前屏质检区) */
- .mes3-panel-left2 {
- left: 30px;
- top: 508px;
- width: 440px;
- height: 460px;
- }
- .mes3-panel-left2 .panel-body {
- display: block;
- padding: 8px 10px;
- }
- .mes3-panel-left2 .panel-body img {
- width: 70px;
- height: auto;
- }
- /* 不良类型柱状图:隐藏(左边第三个) */
- .mes3-panel-left3 {
- display: none;
- }
- .mes3-panel-left3 .panel-body {
- padding: 5px;
- }
- /* ===== 中间区域(中下表隐藏后加高) ===== */
- .mes3-center {
- position: absolute;
- left: 490px;
- top: 58px;
- width: 940px;
- bottom: 15px;
- height: auto;
- }
- /* 中间Logo区域 */
- .mes3-center-logo {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 600px;
- height: 400px;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1;
- }
- .mes3-center-logo-img {
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
- }
- /* 中间数据卡片区域 - 环绕Logo布局 */
- .mes3-center-cards {
- position: relative;
- width: 100%;
- height: 100%;
- z-index: 2;
- }
- .mes3-data-card {
- position: absolute;
- width: 120px;
- height: 90px;
- background-image: url("${ctxStatic}/screen/imgs/ljrZHOxiao.png");
- background-size: 100% 100%;
- background-repeat: no-repeat;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 8px;
- }
- .mes3-data-card .card-label {
- font-size: 14px;
- color: #4DC9F5;
- font-family: "Microsoft YaHei", sans-serif;
- margin-bottom: 5px;
- }
- .mes3-data-card .card-value {
- font-size: 28px;
- font-weight: bold;
- color: #00F7FF;
- font-family: "Microsoft YaHei", sans-serif;
- }
- /* 数据卡片位置 - 左侧 */
- .mes3-data-card:nth-child(1) { left: 50px; top: 180px; } /* 生产人数 */
- .mes3-data-card:nth-child(2) { left: 180px; top: 80px; } /* 设备数量 */
- /* 数据卡片位置 - 顶部 */
- .mes3-data-card:nth-child(3) { left: 320px; top: 20px; } /* 计划产量 */
- .mes3-data-card:nth-child(4) { left: 500px; top: 20px; } /* 实际产量 */
- /* 数据卡片位置 - 右侧 */
- .mes3-data-card:nth-child(5) { left: 640px; top: 80px; } /* 近修数量 */
- .mes3-data-card:nth-child(6) { left: 770px; top: 180px; } /* 报废数量 */
- /* 移除中间流程图 */
- .mes3-center-flow {
- display: none;
- }
- /* ===== 中间下方大面板 - 设备故障统计分析:隐藏(中间第二个) ===== */
- .mes3-panel-center-bottom {
- display: none;
- }
- .mes3-panel-center-bottom .panel-header {
- height: 45px;
- line-height: 45px;
- text-align: center;
- font-size: 18px;
- font-weight: bold;
- color: #4DC9F5;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .mes3-panel-center-bottom .panel-body {
- position: absolute;
- top: 45px;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 10px;
- }
- /* ===== 右侧面板区域 ===== */
- /* 达成率数据分析:隐藏(右边第一个) */
- .mes3-panel-right1 {
- display: none;
- }
- .mes3-panel-right1 .panel-header {
- height: 45px;
- line-height: 45px;
- text-align: center;
- font-size: 18px;
- font-weight: bold;
- color: #4DC9F5;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .mes3-panel-right1 .panel-body {
- position: absolute;
- top: 45px;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 10px;
- }
- /* 产量统计内容 */
- .mes3-stat-grid {
- display: flex;
- flex-direction: column;
- height: 100%;
- gap: 10px;
- }
- .mes3-stat-row {
- flex: 1;
- display: flex;
- gap: 10px;
- }
- .mes3-stat-cell {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- .mes3-stat-label {
- font-size: 16px;
- font-weight: bold;
- color: #fff;
- font-family: "Microsoft YaHei", sans-serif;
- margin-bottom: 8px;
- }
- .mes3-stat-value {
- font-size: 36px;
- font-weight: bold;
- color: #00F7FF;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .mes3-stat-value.green { color: #00FF88; }
- .mes3-stat-value.yellow { color: #FFDD00; }
- .mes3-stat-unit {
- font-size: 16px;
- color: #fff;
- font-family: "Microsoft YaHei", sans-serif;
- margin-left: 5px;
- }
- /* 每小时上下线 -> 右下(对齐前屏同名表) */
- .mes3-panel-right2 {
- position: absolute;
- left: 1450px;
- top: 508px;
- width: 440px;
- height: 460px;
- background-image: url("${ctxStatic}/screen/imgs/ljrY2.png");
- background-size: 100% 100%;
- background-repeat: no-repeat;
- }
- .mes3-panel-right2 .panel-header {
- height: 45px;
- line-height: 45px;
- text-align: center;
- font-size: 18px;
- font-weight: bold;
- color: #4DC9F5;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .mes3-panel-right2 .panel-body {
- position: absolute;
- top: 45px;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 10px;
- }
- /* 设备报警 -> 左上(对齐前屏报警信息区) */
- .mes3-panel-right3 {
- position: absolute;
- left: 30px;
- top: 58px;
- width: 440px;
- height: 430px;
- background-image: url("${ctxStatic}/screen/imgs/ljrZ1.png");
- background-size: 100% 100%;
- background-repeat: no-repeat;
- }
- .mes3-panel-right3 .panel-header {
- height: 45px;
- line-height: 45px;
- text-align: center;
- font-size: 18px;
- font-weight: bold;
- color: #4DC9F5;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .mes3-panel-right3 .panel-body {
- position: absolute;
- top: 45px;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 10px;
- }
- /* 图表 */
- .mes3-chart {
- width: 100%;
- height: 100%;
- }
- .mes3-chart-grid-line {
- position: absolute;
- width: 100%;
- height: 1px;
- background-color: rgba(0, 143, 253, 0.15);
- }
- /* ===== 达成率数据分析 ===== */
- .mes3-rate-cards {
- display: flex;
- gap: 12px;
- padding: 5px 0 10px;
- }
- .mes3-rate-card {
- flex: 1;
- height: 70px;
- border: 1px solid rgba(0, 200, 255, 0.4);
- background: linear-gradient(to right, rgba(0, 100, 180, 0.3), rgba(0, 50, 120, 0.2));
- display: flex;
- align-items: center;
- padding: 0 10px;
- gap: 8px;
- }
- .mes3-rate-card .rate-icon {
- flex-shrink: 0;
- }
- .mes3-rate-card .rate-label {
- font-size: 13px;
- color: #ffffff;
- font-family: "Microsoft YaHei", sans-serif;
- margin-bottom: 2px;
- }
- .mes3-rate-card .rate-value {
- font-size: 26px;
- font-weight: bold;
- color: #00F7FF;
- font-family: "Microsoft YaHei", sans-serif;
- line-height: 1;
- }
- .mes3-rate-card .rate-unit {
- font-size: 14px;
- color: #00F7FF;
- margin-left: 2px;
- }
- .mes3-rate-chart-title {
- display: flex;
- justify-content: space-between;
- align-items: center;
- color: #4DC9F5;
- font-size: 13px;
- font-family: "Microsoft YaHei", sans-serif;
- padding: 6px 0 4px;
- }
- .mes3-rate-chart-title .legend {
- color: #ffffff;
- font-size: 12px;
- }
- .mes3-rate-chart {
- position: relative;
- height: 180px;
- }
- .legend-dot {
- display: inline-block;
- width: 10px;
- height: 10px;
- border-radius: 50%;
- margin-right: 4px;
- vertical-align: middle;
- }
- /* ===== 设备故障统计分析 ===== */
- .mes3-fault-rank {
- width: 42%;
- padding-right: 15px;
- }
- .mes3-fault-rank .fault-rank-title {
- color: #fff;
- font-size: 14px;
- font-weight: bold;
- padding-bottom: 10px;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .mes3-fault-item {
- display: flex;
- align-items: center;
- height: 32px;
- margin-bottom: 4px;
- font-family: "Microsoft YaHei", sans-serif;
- font-size: 13px;
- }
- .mes3-fault-item .fault-rank-no {
- width: 50px;
- color: #4DC9F5;
- }
- .mes3-fault-item.top1 .fault-rank-no { color: #FF4444; }
- .mes3-fault-item.top2 .fault-rank-no { color: #FF9900; }
- .mes3-fault-item .fault-name {
- width: 100px;
- color: #ffffff;
- }
- .mes3-fault-item .fault-bar-wrap {
- flex: 1;
- height: 8px;
- background: rgba(0, 100, 180, 0.3);
- margin: 0 10px;
- position: relative;
- }
- .mes3-fault-item .fault-bar {
- height: 100%;
- background: linear-gradient(to right, #00C8FF, #00FF88);
- }
- .mes3-fault-item .fault-count {
- width: 30px;
- text-align: right;
- color: #00F7FF;
- font-weight: bold;
- }
- .mes3-fault-chart {
- flex: 1;
- position: relative;
- }
- .mes3-fault-chart .fault-chart-title {
- display: flex;
- justify-content: space-between;
- align-items: center;
- color: #fff;
- font-size: 14px;
- font-weight: bold;
- padding-bottom: 6px;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .mes3-fault-chart .legend {
- color: #ffffff;
- font-size: 12px;
- font-weight: normal;
- }
- /* ===== 报警信息 ===== */
- .mes3-alert-list {
- padding-top: 5px;
- padding-right: 4px;
- height: 100%;
- overflow-y: auto;
- scrollbar-width: thin;
- scrollbar-color: rgba(0, 200, 255, 0.45) rgba(0, 40, 80, 0.25);
- }
- .mes3-alert-list::-webkit-scrollbar {
- width: 6px;
- }
- .mes3-alert-list::-webkit-scrollbar-track {
- background: rgba(0, 40, 80, 0.25);
- border-radius: 3px;
- }
- .mes3-alert-list::-webkit-scrollbar-thumb {
- background: linear-gradient(180deg, rgba(0, 200, 255, 0.55), rgba(77, 201, 245, 0.35));
- border-radius: 3px;
- }
- .mes3-alert-list::-webkit-scrollbar-thumb:hover {
- background: rgba(0, 230, 255, 0.7);
- }
- .mes3-alert-list::-webkit-scrollbar-button {
- display: none;
- width: 0;
- height: 0;
- }
- .mes3-alert-list::-webkit-scrollbar-corner {
- background: transparent;
- }
- .mes3-alert-item {
- display: flex;
- align-items: center;
- height: 32px;
- padding: 0 8px;
- margin-bottom: 4px;
- font-family: "Microsoft YaHei", sans-serif;
- font-size: 14px;
- border-bottom: 1px dashed rgba(77, 201, 245, 0.2);
- }
- .mes3-alert-item .alert-no {
- width: 30px;
- color: #4DC9F5;
- font-weight: bold;
- }
- .mes3-alert-item .alert-text {
- flex: 1;
- color: #ffffff;
- }
- /* ===== 告警 ===== */
- .mes3-alert {
- position: absolute;
- top: 80px;
- left: 15px;
- right: 15px;
- height: 0;
- overflow: hidden;
- background-color: rgba(255, 0, 0, 0.08);
- border: 2px solid #FF0000;
- border-left-width: 6px;
- line-height: 48px;
- text-indent: 20px;
- font-size: 20px;
- color: #FF4444;
- font-weight: bold;
- font-family: "Microsoft YaHei", sans-serif;
- z-index: 100;
- transition: height 0.3s, opacity 0.3s;
- opacity: 0;
- }
- .mes3-alert.show {
- height: 48px;
- opacity: 1;
- }
- </style>
- </head>
- <body>
- <div class="mes3-wrapper" id="app">
- <!-- 顶部标题栏 -->
- <div class="mes3-header">
- <img class="mes3-logo" src="${ctxStatic}/screen/imgs/ljrLogo.png" alt="logo">
- <div class="mes3-header-title">MES系统综合数据大屏</div>
- <div class="mes3-header-right">
- <div class="mes3-date" id="mes3Date"></div>
- <div class="mes3-time" id="mes3Time"></div>
- </div>
- <button type="button" class="mes-fullscreen-btn" id="btnFullscreen" title="全屏">全屏</button>
- </div>
- <!-- 中间主体 -->
- <div class="mes3-body">
- <!-- 左侧三列面板 -->
- <div class="mes3-left">
- <!-- 面板1: 近七日产量折线图 -->
- <div class="mes3-panel mes3-panel-left1">
- <div class="panel-header">近七日产量分析</div>
- <div class="panel-body">
- <canvas id="chartLeft1" class="mes3-chart"></canvas>
- </div>
- </div>
- <!-- 面板2: 近七日产品质检(对齐前屏同名逻辑) -->
- <div class="mes3-panel mes3-panel-left2">
- <div class="panel-header">近七日产品质检数据分析</div>
- <div class="panel-body">
- <canvas id="chartLeft2" class="mes3-chart"></canvas>
- </div>
- </div>
- <!-- 面板3: 不良类型柱状图 -->
- <div class="mes3-panel mes3-panel-left3">
- <div class="panel-header">不良类型柱状图</div>
- <div class="panel-body">
- <canvas id="chartLeft3" class="mes3-chart"></canvas>
- </div>
- </div>
- </div>
- <!-- 中间区域 -->
- <div class="mes3-center">
- <!-- 中间数据卡片 - 环绕Logo -->
- <div class="mes3-center-cards">
- <div class="mes3-data-card">
- <div class="card-label">生产人数</div>
- <div class="card-value" id="productionPeople">48</div>
- </div>
- <div class="mes3-data-card">
- <div class="card-label">设备数量</div>
- <div class="card-value" id="deviceCount">56</div>
- </div>
- <div class="mes3-data-card">
- <div class="card-label">计划产量</div>
- <div class="card-value" id="planProduction">221</div>
- </div>
- <div class="mes3-data-card">
- <div class="card-label">实际产量</div>
- <div class="card-value" id="actualProduction">225</div>
- </div>
- <div class="mes3-data-card">
- <div class="card-label">返修数量</div>
- <div class="card-value" id="repairCount">11</div>
- </div>
- <div class="mes3-data-card">
- <div class="card-label">报废数量</div>
- <div class="card-value" id="scrapCount">10</div>
- </div>
- </div>
- <!-- 中间Logo -->
- <div class="mes3-center-logo">
- <img class="mes3-center-logo-img" src="${ctxStatic}/screen/imgs/ljrZho.png" alt="LJR">
- </div>
- </div>
- <!-- 右侧面板1: 达成率数据分析(右上角独立小面板)-->
- <div class="mes3-panel-right1">
- <div class="panel-header">达成率数据分析</div>
- <div class="panel-body">
- <!-- 上方两个达成率指标卡 -->
- <div class="mes3-rate-cards">
- <div class="mes3-rate-card">
- <div class="rate-icon">
- <svg width="40" height="40" viewBox="0 0 40 40"><circle cx="20" cy="20" r="18" fill="none" stroke="#00C8FF" stroke-width="2"/><path d="M 13 20 L 18 25 L 27 14" stroke="#00C8FF" stroke-width="2.5" fill="none" stroke-linecap="round"/></svg>
- </div>
- <div class="rate-text">
- <div class="rate-label">今日达成率</div>
- <div class="rate-value"><span id="todayRate">80</span><span class="rate-unit">%</span></div>
- </div>
- </div>
- <div class="mes3-rate-card">
- <div class="rate-icon">
- <svg width="40" height="40" viewBox="0 0 40 40"><rect x="8" y="10" width="24" height="22" fill="none" stroke="#00C8FF" stroke-width="2" rx="2"/><line x1="14" y1="6" x2="14" y2="14" stroke="#00C8FF" stroke-width="2"/><line x1="26" y1="6" x2="26" y2="14" stroke="#00C8FF" stroke-width="2"/><line x1="8" y1="18" x2="32" y2="18" stroke="#00C8FF" stroke-width="1.5"/></svg>
- </div>
- <div class="rate-text">
- <div class="rate-label">当月达成率</div>
- <div class="rate-value"><span id="monthRate">70</span><span class="rate-unit">%</span></div>
- </div>
- </div>
- </div>
- <!-- 下方折线图 -->
- <div class="mes3-rate-chart-title">
- <span>近七日达成率数据分析</span>
- <span class="legend"><i class="legend-dot" style="background:#FFDD00"></i>目标 <i class="legend-dot" style="background:#00C8FF;margin-left:10px"></i>实际</span>
- </div>
- <div class="mes3-rate-chart">
- <canvas id="chartRight1" class="mes3-chart"></canvas>
- </div>
- </div>
- </div>
- <!-- 中间下方大面板: 设备故障统计分析 -->
- <div class="mes3-panel-center-bottom">
- <div class="panel-header">设备故障统计分析</div>
- <div class="panel-body" style="display:flex;padding: 15px;">
- <!-- 左侧故障类型排名 -->
- <div class="mes3-fault-rank">
- <div class="fault-rank-title">故障类型排名</div>
- <div id="faultRankList"></div>
- </div>
- <!-- 右侧近七日设备故障分析图 -->
- <div class="mes3-fault-chart">
- <div class="fault-chart-title">
- <span>近七日设备故障分析</span>
- <span class="legend"><i class="legend-dot" style="background:#00C8FF"></i>故障时间 <i class="legend-dot" style="background:#00FF88;margin-left:10px"></i>故障率</span>
- </div>
- <div style="position:absolute;top:40px;left:10px;right:10px;bottom:10px;">
- <canvas id="chartCenterBottom" class="mes3-chart"></canvas>
- </div>
- </div>
- </div>
- </div>
- <!-- 右侧面板2: 每小时上下线数据分析 -->
- <div class="mes3-panel-right2">
- <div class="panel-header">每小时上下线数据分析</div>
- <div class="panel-body">
- <canvas id="chartRight2" class="mes3-chart"></canvas>
- </div>
- </div>
- <!-- 右侧面板3: 报警信息 -->
- <div class="mes3-panel-right3">
- <div class="panel-header">产线阶段报警信息</div>
- <div class="panel-body" style="padding: 0 15px;">
- <div id="alertList" class="mes3-alert-list"></div>
- </div>
- </div>
- </div>
- <!-- 告警 -->
- <div class="mes3-alert" id="mes3Alert"></div>
- </div>
- <script src="${ctxStatic}/jquery-1.11.3.min.js"></script>
- <script>
- (function() {
- var __screenFail = 0, __screenReloadAt = 0;
- $(document).ajaxError(function(e, xhr) {
- if (!xhr) return;
- var text = xhr.responseText || '';
- if (xhr.status === 0 || xhr.status === 401 || xhr.status === 403 || xhr.status === 502 || xhr.status === 503
- || text.indexOf('login') >= 0 || text.indexOf('登录') >= 0) {
- __screenFail++;
- if (__screenFail >= 3 && Date.now() - __screenReloadAt > 60000) {
- __screenReloadAt = Date.now();
- location.reload();
- }
- }
- });
- $(document).ajaxSuccess(function() { __screenFail = 0; });
- function pad(n) { return n < 10 ? '0' + n : '' + n; }
- function updateClock() {
- var now = new Date();
- var y = now.getFullYear();
- var m = now.getMonth() + 1;
- var d = now.getDate();
- var w = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'][now.getDay()];
- document.getElementById('mes3Date').textContent = y + '年' + pad(m) + '月' + pad(d) + '日 ' + w;
- var h = now.getHours(), mi = now.getMinutes(), s = now.getSeconds();
- document.getElementById('mes3Time').textContent = pad(h) + ':' + pad(mi) + ':' + pad(s);
- }
- // ====== 各窗口测试数据 ======
- var mockData = {};
- /**
- * 总览页:全产线汇总,上线 OP060 / 下线 OP400(lineSn 空=不过滤产线)
- */
- var HOURLY_API_CFG = { lineSn: '', onlineOprno: 'OP060', offlineOprno: 'OP400' };
- var OP_ONLINE = 'OP060';
- var OP_OFFLINE = 'OP400';
- var QUALITY_LINE_LIST = ['MP', 'XT', 'ZP'];
- function buildHourLabels24() {
- var hourLabels = [];
- for (var i = 1; i <= 24; i++) {
- hourLabels.push(pad(i));
- }
- return hourLabels;
- }
- function updateHourlyUpDownByApi() {
- $.ajax({
- url: '${ctx}/mes/mesProduct/hourlyUpDownOkData',
- type: 'POST',
- dataType: 'json',
- data: {
- lineSn: HOURLY_API_CFG.lineSn,
- onlineOprno: HOURLY_API_CFG.onlineOprno,
- offlineOprno: HOURLY_API_CFG.offlineOprno
- },
- success: function(resp) {
- if (resp && resp.result === 'true' && resp.data) {
- var d = resp.data;
- if (!d.up || !d.down || d.up.length < 24 || d.down.length < 24) {
- return;
- }
- mockData.hourlyData = {
- labels: buildHourLabels24(),
- up: d.up,
- down: d.down,
- max: typeof d.max === 'number' ? d.max : 100
- };
- drawAllCharts();
- }
- }
- });
- }
- function generateMockData() {
- // 近7日日期标签
- var now = new Date();
- var weekLabels = [];
- for (var i = 6; i >= 0; i--) {
- var d = new Date(now);
- d.setDate(d.getDate() - i);
- weekLabels.push((d.getMonth() + 1) + '月' + d.getDate() + '日');
- }
- // 1. 近七日产量折线图 (左1)
- mockData.productTrend = {
- labels: weekLabels,
- data: [1100, 1100, 1200, 1125, 1125, 1180, 1150],
- max: 1300, min: 1000
- };
- // 2. 近七日产品检验盘点 (左2) - 产出/不良 双柱状图
- mockData.qualityInspect = {
- labels: weekLabels,
- produced: [400, 321, 354, 164, 241, 342, 231],
- defect: [345, 256, 312, 123, 214, 286, 189],
- max: 500
- };
- // 3. 不良类型柱状图 (左3)
- mockData.defectType = {
- labels: ['故障A', '故障B', '电阻设备', '焊接', '铸锭', '故障C', '故障D'],
- data: [480, 380, 220, 180, 60, 200, 80],
- max: 500
- };
- // 4. 达成率数据分析 - 近七日达成率 (右1)
- mockData.achieveRate = {
- labels: weekLabels,
- target: [1150, 1160, 1180, 1165, 1180, 1180, 1170],
- actual: [1150, 1160, 1180, 1165, 1180, 1200, 1170],
- max: 1300, min: 1000
- };
- mockData.todayRate = 80;
- mockData.monthRate = 70;
- // 5. 每小时上下线数据分析 (右2) - 24小时
- var hourLabels = [];
- var upLine = [], downLine = [];
- for (var i = 1; i <= 24; i++) {
- hourLabels.push(pad(i));
- upLine.push(Math.round(60 + Math.random() * 40));
- downLine.push(Math.round(50 + Math.random() * 40));
- }
- mockData.hourlyData = {
- labels: hourLabels,
- up: upLine,
- down: downLine,
- max: 120
- };
- // 6. 报警信息由 updateDeviceAlarmAlertsByApi 拉取 mesDeviceAlarm/listData,展示 type
- mockData.alerts = [];
- // 7. 设备故障统计分析 - 故障类型排名
- mockData.faultRank = [
- { rank: 'No.1', name: '机构故障', count: 10, percent: 100, top: 1 },
- { rank: 'No.2', name: '电控故障', count: 8, percent: 80, top: 2 },
- { rank: 'No.3', name: '辅助设备故障', count: 6, percent: 60 },
- { rank: 'No.4', name: '突发故障', count: 4, percent: 40 },
- { rank: 'No.5', name: '故障类型1', count: 2, percent: 20 }
- ];
- // 近七日设备故障分析
- mockData.faultAnalysis = {
- labels: weekLabels,
- faultTime: [25, 35, 45, 35, 30, 40, 43],
- faultRate: [40, 60, 100, 55, 55, 60, 65],
- maxTime: 60, maxRate: 120
- };
- // 6个数据卡片
- mockData.cards = {
- productionPeople: 48,
- deviceCount: 56,
- planProduction: 221,
- actualProduction: 225,
- repairCount: 11,
- scrapCount: 10
- };
- }
- // 通用:准备 canvas 上下文
- function prepareCanvas(canvasId) {
- var canvas = document.getElementById(canvasId);
- if (!canvas) return null;
- var parent = canvas.parentElement;
- var pw = parent.offsetWidth;
- var ph = parent.offsetHeight;
- if (canvas.width !== pw || canvas.height !== ph) {
- canvas.width = pw;
- canvas.height = ph;
- }
- return { ctx: canvas.getContext('2d'), w: pw, h: ph };
- }
- // 通用:Y 轴刻度和网格
- function drawYAxis(ctx, padL, padT, cw, ch, maxVal, minVal) {
- minVal = minVal || 0;
- ctx.fillStyle = 'rgba(77, 201, 245, 0.8)';
- ctx.font = '11px Microsoft YaHei';
- ctx.textAlign = 'right';
- ctx.strokeStyle = 'rgba(0, 143, 253, 0.15)';
- ctx.lineWidth = 1;
- for (var i = 0; i <= 4; i++) {
- var val = Math.round(minVal + (maxVal - minVal) * (4 - i) / 4);
- var y = padT + ch * i / 4;
- ctx.fillText(val, padL - 5, y + 4);
- ctx.beginPath();
- ctx.moveTo(padL, y);
- ctx.lineTo(padL + cw, y);
- ctx.stroke();
- }
- }
- // 折线图(单线,平滑,带面积)
- function drawLineChart(canvasId, data, labels, maxVal, minVal, color) {
- var p = prepareCanvas(canvasId);
- if (!p) return;
- var ctx = p.ctx, w = p.w, h = p.h;
- var padL = 42, padR = 12, padT = 15, padB = 25;
- var cw = w - padL - padR, ch = h - padT - padB;
- color = color || '#00C8FF';
- minVal = minVal || 0;
- ctx.clearRect(0, 0, w, h);
- drawYAxis(ctx, padL, padT, cw, ch, maxVal, minVal);
- if (!data || data.length < 2) return;
- var stepX = cw / (data.length - 1);
- var range = maxVal - minVal;
- // 渐变面积
- var grad = ctx.createLinearGradient(0, padT, 0, padT + ch);
- grad.addColorStop(0, hexToRgba(color, 0.4));
- grad.addColorStop(1, hexToRgba(color, 0.02));
- ctx.beginPath();
- ctx.moveTo(padL, padT + ch);
- for (var i = 0; i < data.length; i++) {
- var x = padL + i * stepX;
- var y = padT + ch - ((data[i] - minVal) / range) * ch;
- if (i === 0) ctx.lineTo(x, y);
- else {
- var px = padL + (i - 1) * stepX;
- var py = padT + ch - ((data[i - 1] - minVal) / range) * ch;
- var cpx = (px + x) / 2;
- ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
- }
- }
- ctx.lineTo(padL + (data.length - 1) * stepX, padT + ch);
- ctx.closePath();
- ctx.fillStyle = grad;
- ctx.fill();
- // 折线
- ctx.beginPath();
- for (var i = 0; i < data.length; i++) {
- var x = padL + i * stepX;
- var y = padT + ch - ((data[i] - minVal) / range) * ch;
- if (i === 0) ctx.moveTo(x, y);
- else {
- var px = padL + (i - 1) * stepX;
- var py = padT + ch - ((data[i - 1] - minVal) / range) * ch;
- var cpx = (px + x) / 2;
- ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
- }
- }
- ctx.strokeStyle = color;
- ctx.lineWidth = 2;
- ctx.stroke();
- // 数据点和标签
- ctx.fillStyle = '#ffffff';
- ctx.font = 'bold 11px Microsoft YaHei';
- ctx.textAlign = 'center';
- for (var i = 0; i < data.length; i++) {
- var x = padL + i * stepX;
- var y = padT + ch - ((data[i] - minVal) / range) * ch;
- ctx.beginPath();
- ctx.arc(x, y, 3, 0, Math.PI * 2);
- ctx.fillStyle = color;
- ctx.fill();
- ctx.strokeStyle = '#fff';
- ctx.lineWidth = 1.5;
- ctx.stroke();
- ctx.fillStyle = '#ffffff';
- ctx.fillText(data[i], x, y - 8);
- }
- // X 轴标签
- ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
- ctx.font = '11px Microsoft YaHei';
- ctx.textAlign = 'center';
- for (var i = 0; i < labels.length; i++) {
- var x = padL + i * stepX;
- ctx.fillText(labels[i], x, h - 6);
- }
- }
- // 双折线图(目标+实际)
- function drawDualLineChart(canvasId, target, actual, labels, maxVal, minVal) {
- var p = prepareCanvas(canvasId);
- if (!p) return;
- var ctx = p.ctx, w = p.w, h = p.h;
- var padL = 42, padR = 12, padT = 12, padB = 25;
- var cw = w - padL - padR, ch = h - padT - padB;
- minVal = minVal || 0;
- var range = maxVal - minVal;
- ctx.clearRect(0, 0, w, h);
- drawYAxis(ctx, padL, padT, cw, ch, maxVal, minVal);
- if (!target || target.length < 2) return;
- var stepX = cw / (target.length - 1);
- function drawLine(arr, color) {
- // 面积
- var grad = ctx.createLinearGradient(0, padT, 0, padT + ch);
- grad.addColorStop(0, hexToRgba(color, 0.3));
- grad.addColorStop(1, hexToRgba(color, 0.02));
- ctx.beginPath();
- ctx.moveTo(padL, padT + ch);
- for (var i = 0; i < arr.length; i++) {
- var x = padL + i * stepX;
- var y = padT + ch - ((arr[i] - minVal) / range) * ch;
- if (i === 0) ctx.lineTo(x, y);
- else {
- var px = padL + (i - 1) * stepX;
- var py = padT + ch - ((arr[i - 1] - minVal) / range) * ch;
- var cpx = (px + x) / 2;
- ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
- }
- }
- ctx.lineTo(padL + (arr.length - 1) * stepX, padT + ch);
- ctx.closePath();
- ctx.fillStyle = grad;
- ctx.fill();
- // 线
- ctx.beginPath();
- for (var i = 0; i < arr.length; i++) {
- var x = padL + i * stepX;
- var y = padT + ch - ((arr[i] - minVal) / range) * ch;
- if (i === 0) ctx.moveTo(x, y);
- else {
- var px = padL + (i - 1) * stepX;
- var py = padT + ch - ((arr[i - 1] - minVal) / range) * ch;
- var cpx = (px + x) / 2;
- ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
- }
- }
- ctx.strokeStyle = color;
- ctx.lineWidth = 2;
- ctx.stroke();
- // 点
- for (var i = 0; i < arr.length; i++) {
- var x = padL + i * stepX;
- var y = padT + ch - ((arr[i] - minVal) / range) * ch;
- ctx.beginPath();
- ctx.arc(x, y, 3, 0, Math.PI * 2);
- ctx.fillStyle = color;
- ctx.fill();
- }
- }
- drawLine(target, '#FFDD00');
- drawLine(actual, '#00C8FF');
- // 数值
- ctx.fillStyle = '#ffffff';
- ctx.font = '10px Microsoft YaHei';
- ctx.textAlign = 'center';
- for (var i = 0; i < actual.length; i++) {
- var x = padL + i * stepX;
- var y = padT + ch - ((actual[i] - minVal) / range) * ch;
- ctx.fillText(actual[i], x, y - 6);
- }
- // X 轴
- ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
- ctx.font = '10px Microsoft YaHei';
- for (var i = 0; i < labels.length; i++) {
- var x = padL + i * stepX;
- ctx.fillText(labels[i], x, h - 6);
- }
- }
- // 双柱状图 (产出/不良)
- function drawDoubleBarChart(canvasId, produced, defect, labels, maxVal) {
- var p = prepareCanvas(canvasId);
- if (!p) return;
- var ctx = p.ctx, w = p.w, h = p.h;
- var padL = 42, padR = 12, padT = 30, padB = 25;
- var cw = w - padL - padR, ch = h - padT - padB;
- ctx.clearRect(0, 0, w, h);
- drawYAxis(ctx, padL, padT, cw, ch, maxVal);
- // 图例
- ctx.fillStyle = '#00FF88';
- ctx.fillRect(padL + 40, 8, 12, 12);
- ctx.fillStyle = '#ffffff';
- ctx.font = '12px Microsoft YaHei';
- ctx.textAlign = 'left';
- ctx.fillText('合格', padL + 58, 18);
- ctx.fillStyle = '#00C8FF';
- ctx.fillRect(padL + 110, 8, 12, 12);
- ctx.fillStyle = '#ffffff';
- ctx.fillText('不良', padL + 128, 18);
- if (!produced || produced.length < 1) return;
- var groupW = cw / produced.length;
- var barW = groupW * 0.3;
- for (var i = 0; i < produced.length; i++) {
- var cx = padL + i * groupW + groupW / 2;
- // 产出
- var h1 = (produced[i] / maxVal) * ch;
- var g1 = ctx.createLinearGradient(0, padT + ch - h1, 0, padT + ch);
- g1.addColorStop(0, '#00FF88');
- g1.addColorStop(1, 'rgba(0, 255, 136, 0.2)');
- ctx.fillStyle = g1;
- ctx.fillRect(cx - barW - 2, padT + ch - h1, barW, h1);
- // 不良
- var h2 = (defect[i] / maxVal) * ch;
- var g2 = ctx.createLinearGradient(0, padT + ch - h2, 0, padT + ch);
- g2.addColorStop(0, '#00C8FF');
- g2.addColorStop(1, 'rgba(0, 200, 255, 0.2)');
- ctx.fillStyle = g2;
- ctx.fillRect(cx + 2, padT + ch - h2, barW, h2);
- // 数值
- ctx.fillStyle = '#00FF88';
- ctx.font = 'bold 10px Microsoft YaHei';
- ctx.textAlign = 'center';
- ctx.fillText(produced[i], cx - barW / 2 - 2, padT + ch - h1 - 4);
- ctx.fillStyle = '#00C8FF';
- ctx.fillText(defect[i], cx + barW / 2 + 2, padT + ch - h2 - 4);
- // X 轴
- ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
- ctx.font = '11px Microsoft YaHei';
- ctx.fillText(labels[i], cx, h - 6);
- }
- }
- // 单柱状图 (不良类型)
- function drawBarChart(canvasId, data, labels, maxVal) {
- var p = prepareCanvas(canvasId);
- if (!p) return;
- var ctx = p.ctx, w = p.w, h = p.h;
- var padL = 42, padR = 12, padT = 15, padB = 25;
- var cw = w - padL - padR, ch = h - padT - padB;
- ctx.clearRect(0, 0, w, h);
- drawYAxis(ctx, padL, padT, cw, ch, maxVal);
- if (!data || data.length < 1) return;
- var groupW = cw / data.length;
- var barW = groupW * 0.5;
- for (var i = 0; i < data.length; i++) {
- var cx = padL + i * groupW + groupW / 2;
- var barH = (data[i] / maxVal) * ch;
- var grad = ctx.createLinearGradient(0, padT + ch - barH, 0, padT + ch);
- grad.addColorStop(0, '#00C8FF');
- grad.addColorStop(1, 'rgba(0, 200, 255, 0.1)');
- ctx.fillStyle = grad;
- ctx.fillRect(cx - barW / 2, padT + ch - barH, barW, barH);
- // X 轴
- ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
- ctx.font = '11px Microsoft YaHei';
- ctx.textAlign = 'center';
- ctx.fillText(labels[i], cx, h - 6);
- }
- }
- // 混合柱+折线 (设备故障分析)
- function drawBarLineChart(canvasId, barData, lineData, labels, barMax, lineMax) {
- var p = prepareCanvas(canvasId);
- if (!p) return;
- var ctx = p.ctx, w = p.w, h = p.h;
- var padL = 42, padR = 42, padT = 15, padB = 25;
- var cw = w - padL - padR, ch = h - padT - padB;
- ctx.clearRect(0, 0, w, h);
- // 左 Y 轴:柱状图刻度
- drawYAxis(ctx, padL, padT, cw, ch, barMax);
- // 右 Y 轴:折线图刻度 (%)
- ctx.fillStyle = 'rgba(0, 255, 136, 0.9)';
- ctx.font = '11px Microsoft YaHei';
- ctx.textAlign = 'left';
- for (var i = 0; i <= 4; i++) {
- var val = Math.round(lineMax * (4 - i) / 4);
- var y = padT + ch * i / 4;
- ctx.fillText(val + '%', padL + cw + 5, y + 4);
- }
- if (!barData || barData.length < 1) return;
- var groupW = cw / barData.length;
- var barW = groupW * 0.4;
- // 柱
- for (var i = 0; i < barData.length; i++) {
- var cx = padL + i * groupW + groupW / 2;
- var barH = (barData[i] / barMax) * ch;
- var grad = ctx.createLinearGradient(0, padT + ch - barH, 0, padT + ch);
- grad.addColorStop(0, '#00C8FF');
- grad.addColorStop(1, 'rgba(0, 200, 255, 0.2)');
- ctx.fillStyle = grad;
- ctx.fillRect(cx - barW / 2, padT + ch - barH, barW, barH);
- // 数值
- ctx.fillStyle = '#00C8FF';
- ctx.font = 'bold 11px Microsoft YaHei';
- ctx.textAlign = 'center';
- ctx.fillText(barData[i], cx, padT + ch - barH - 4);
- // X 轴
- ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
- ctx.font = '11px Microsoft YaHei';
- ctx.fillText(labels[i], cx, h - 6);
- }
- // 折线
- var stepX = groupW;
- ctx.beginPath();
- for (var i = 0; i < lineData.length; i++) {
- var cx = padL + i * groupW + groupW / 2;
- var cy = padT + ch - (lineData[i] / lineMax) * ch;
- if (i === 0) ctx.moveTo(cx, cy);
- else ctx.lineTo(cx, cy);
- }
- ctx.strokeStyle = '#00FF88';
- ctx.lineWidth = 2;
- ctx.stroke();
- // 点
- for (var i = 0; i < lineData.length; i++) {
- var cx = padL + i * groupW + groupW / 2;
- var cy = padT + ch - (lineData[i] / lineMax) * ch;
- ctx.beginPath();
- ctx.arc(cx, cy, 3, 0, Math.PI * 2);
- ctx.fillStyle = '#00FF88';
- ctx.fill();
- }
- }
- // 双面积折线 (每小时上下线)
- function drawAreaLineChart(canvasId, upData, downData, labels, maxVal) {
- var p = prepareCanvas(canvasId);
- if (!p) return;
- var ctx = p.ctx, w = p.w, h = p.h;
- var padL = 35, padR = 12, padT = 30, padB = 25;
- var cw = w - padL - padR, ch = h - padT - padB;
- ctx.clearRect(0, 0, w, h);
- drawYAxis(ctx, padL, padT, cw, ch, maxVal);
- // 图例
- ctx.fillStyle = '#00FF88';
- ctx.fillRect(padL + 60, 8, 12, 12);
- ctx.fillStyle = '#ffffff';
- ctx.font = '12px Microsoft YaHei';
- ctx.textAlign = 'left';
- ctx.fillText('上线', padL + 78, 18);
- ctx.fillStyle = '#00C8FF';
- ctx.fillRect(padL + 130, 8, 12, 12);
- ctx.fillStyle = '#ffffff';
- ctx.fillText('下线', padL + 148, 18);
- if (!upData || upData.length < 2) return;
- var stepX = cw / (upData.length - 1);
- function drawOne(arr, color) {
- var grad = ctx.createLinearGradient(0, padT, 0, padT + ch);
- grad.addColorStop(0, hexToRgba(color, 0.5));
- grad.addColorStop(1, hexToRgba(color, 0.05));
- ctx.beginPath();
- ctx.moveTo(padL, padT + ch);
- for (var i = 0; i < arr.length; i++) {
- var x = padL + i * stepX;
- var y = padT + ch - (arr[i] / maxVal) * ch;
- if (i === 0) ctx.lineTo(x, y);
- else {
- var px = padL + (i - 1) * stepX;
- var py = padT + ch - (arr[i - 1] / maxVal) * ch;
- var cpx = (px + x) / 2;
- ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
- }
- }
- ctx.lineTo(padL + (arr.length - 1) * stepX, padT + ch);
- ctx.closePath();
- ctx.fillStyle = grad;
- ctx.fill();
- ctx.beginPath();
- for (var i = 0; i < arr.length; i++) {
- var x = padL + i * stepX;
- var y = padT + ch - (arr[i] / maxVal) * ch;
- if (i === 0) ctx.moveTo(x, y);
- else {
- var px = padL + (i - 1) * stepX;
- var py = padT + ch - (arr[i - 1] / maxVal) * ch;
- var cpx = (px + x) / 2;
- ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
- }
- }
- ctx.strokeStyle = color;
- ctx.lineWidth = 1.5;
- ctx.stroke();
- }
- drawOne(upData, '#00FF88');
- drawOne(downData, '#00C8FF');
- // X 轴
- ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
- ctx.font = '9px Microsoft YaHei';
- ctx.textAlign = 'center';
- for (var i = 0; i < labels.length; i++) {
- if (i % 2 === 0) {
- var x = padL + i * stepX;
- ctx.fillText(labels[i], x, h - 6);
- }
- }
- }
- // 工具:hex 转 rgba
- function hexToRgba(hex, alpha) {
- var r = parseInt(hex.slice(1, 3), 16);
- var g = parseInt(hex.slice(3, 5), 16);
- var b = parseInt(hex.slice(5, 7), 16);
- return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
- }
- // 渲染故障类型排名列表
- function renderFaultRank() {
- var list = mockData.faultRank || [];
- var html = '';
- for (var i = 0; i < list.length; i++) {
- var it = list[i];
- var topCls = it.top === 1 ? 'top1' : (it.top === 2 ? 'top2' : '');
- html += '<div class="mes3-fault-item ' + topCls + '">';
- html += '<div class="fault-rank-no">' + it.rank + '</div>';
- html += '<div class="fault-name">' + it.name + '</div>';
- html += '<div class="fault-bar-wrap"><div class="fault-bar" style="width:' + it.percent + '%"></div></div>';
- html += '<div class="fault-count">' + it.count + '</div>';
- html += '</div>';
- }
- var el = document.getElementById('faultRankList');
- if (el) el.innerHTML = html;
- }
- function escapeHtml(s) {
- if (s == null || s === '') return '';
- return String(s)
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"');
- }
- // 渲染报警信息列表(text 为设备报警记录的 type 字段)
- function renderAlerts() {
- var list = mockData.alerts || [];
- var html = '';
- for (var i = 0; i < list.length; i++) {
- html += '<div class="mes3-alert-item">';
- html += '<div class="alert-no">' + list[i].no + '</div>';
- html += '<div class="alert-text">' + escapeHtml(list[i].text) + '</div>';
- html += '</div>';
- }
- var el = document.getElementById('alertList');
- if (el) el.innerHTML = html;
- }
- // 渲染数据卡片和达成率数字
- function renderTextData() {
- var c = mockData.cards || {};
- var map = {
- productionPeople: c.productionPeople,
- deviceCount: c.deviceCount,
- planProduction: c.planProduction,
- actualProduction: c.actualProduction,
- repairCount: c.repairCount,
- scrapCount: c.scrapCount,
- todayRate: mockData.todayRate,
- monthRate: mockData.monthRate
- };
- for (var k in map) {
- var el = document.getElementById(k);
- if (el && map[k] != null) el.textContent = map[k];
- }
- }
- function extractList(resp) {
- if (!resp) return [];
- if (Object.prototype.toString.call(resp.list) === '[object Array]') return resp.list;
- if (Object.prototype.toString.call(resp.rows) === '[object Array]') return resp.rows;
- if (resp.data) {
- if (Object.prototype.toString.call(resp.data.list) === '[object Array]') return resp.data.list;
- if (Object.prototype.toString.call(resp.data.rows) === '[object Array]') return resp.data.rows;
- }
- return [];
- }
- function parseDeviceSnParts(deviceSn) {
- var s = String(deviceSn || '').trim().toUpperCase();
- var parts = s.split('_').filter(function(p) { return !!p; });
- var line = parts.length > 0 ? parts[0] : '';
- var oprno = '';
- for (var i = 0; i < parts.length; i++) {
- if (/^OP\d+$/.test(parts[i])) {
- oprno = parts[i];
- break;
- }
- }
- if (!oprno) {
- var m = s.match(/OP\d+/);
- if (m) oprno = m[0];
- }
- return { line: line, oprno: oprno };
- }
- function updateDeviceAlarmAlertsByApi() {
- $.ajax({
- url: '${ctx}/mes/mesProductScreen/alertList',
- type: 'POST',
- dataType: 'json',
- data: {
- pageNo: 1,
- pageSize: 200,
- state: '0'
- },
- success: function(resp) {
- var rows = extractList(resp);
- var alerts = [];
- for (var i = 0; i < rows.length; i++) {
- var r = rows[i] || {};
- if (String(r.state) !== '0') continue;
- var sn = r.deviceSn || r.device_sn || r.deviceCode || r.device_code || '';
- var parts = parseDeviceSnParts(sn);
- var oprno = String(r.oprno || parts.oprno || '').toUpperCase();
- var typ = (r.type != null && r.type !== undefined) ? String(r.type) : '';
- var text = oprno
- ? (oprno + (typ ? (' | ' + typ) : ''))
- : typ;
- if (!text) continue;
- alerts.push({ no: alerts.length + 1, text: text, oprno: oprno || '' });
- }
- mockData.alerts = alerts;
- renderAlerts();
- },
- error: function() {
- mockData.alerts = [];
- renderAlerts();
- }
- });
- }
- /** 总览三卡:生产人数 / 设备数量 / 计划产量 <- mesProductScreen 最新配置 */
- function updateScreenConfigByApi() {
- $.ajax({
- url: '${ctx}/mes/mesProductScreen/latestConfig',
- type: 'POST',
- dataType: 'json',
- success: function(resp) {
- if (!resp || resp.result !== 'true' || !resp.data) return;
- var d = resp.data;
- mockData.cards = mockData.cards || {};
- mockData.cards.productionPeople = Number(d.peopleNums) || 0;
- mockData.cards.deviceCount = Number(d.deviceNums) || 0;
- mockData.cards.planProduction = Number(d.planNums) || 0;
- var elPeople = document.getElementById('productionPeople');
- var elDevice = document.getElementById('deviceCount');
- var elPlan = document.getElementById('planProduction');
- if (elPeople) elPeople.textContent = mockData.cards.productionPeople;
- if (elDevice) elDevice.textContent = mockData.cards.deviceCount;
- if (elPlan) elPlan.textContent = mockData.cards.planProduction;
- syncAchieveRateWithPlan();
- }
- });
- }
- function calcRatePercent(actual, plan) {
- actual = Number(actual) || 0;
- plan = Number(plan) || 0;
- if (plan <= 0) {
- return 0;
- }
- return Math.round(actual * 100 / plan);
- }
- function syncAchieveRateWithPlan() {
- mockData.cards = mockData.cards || {};
- mockData.achieveRate = mockData.achieveRate || { labels: [], actual: [] };
- var planNums = Number(mockData.cards.planProduction) || 0;
- var actualProduction = Number(mockData.cards.actualProduction) || 0;
- var labels = mockData.achieveRate.labels || [];
- var actualSeries = mockData.achieveRate.actual || [];
- var targetSeries = [];
- for (var i = 0; i < labels.length; i++) {
- targetSeries.push(planNums);
- }
- var maxVal = Math.max.apply(null, targetSeries.concat(actualSeries).concat([10]));
- mockData.achieveRate.target = targetSeries;
- mockData.achieveRate.max = maxVal <= 0 ? 10 : Math.ceil(maxVal * 1.1);
- mockData.achieveRate.min = 0;
- mockData.todayRate = calcRatePercent(actualProduction, planNums);
- mockData.monthRate = calcRatePercent(actualProduction, planNums);
- renderTextData();
- drawAllCharts();
- }
- function isOprOfflineFamily(oprno) {
- var op = (oprno || '').replace(/\s/g, '').toUpperCase();
- var prefix = OP_OFFLINE;
- if (op.length < prefix.length || op.indexOf(prefix) !== 0) return false;
- if (op.length === prefix.length) return op === prefix;
- return new RegExp('^' + prefix + '[A-Z0-9_-]+$').test(op);
- }
- function isRecordContentOk(content) {
- var c = (content || '').replace(/\s/g, '').toUpperCase();
- return c === 'OK';
- }
- function getCalendarDayUpdateDateRange() {
- var now = new Date();
- var p = function(n) { return n < 10 ? '0' + n : '' + n; };
- var dateStr = now.getFullYear() + '-' + p(now.getMonth() + 1) + '-' + p(now.getDate());
- return {
- gte: dateStr + ' 00:00:00',
- lte: dateStr + ' 23:59:59',
- startMs: new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime(),
- endMs: new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999).getTime()
- };
- }
- function parseRecordDateMs(val) {
- if (val == null || val === '') return NaN;
- if (typeof val === 'number' && !isNaN(val)) return val;
- var s = String(val).trim().replace(/-/g, '/');
- var t = Date.parse(s);
- return isNaN(t) ? NaN : t;
- }
- function isRecordWithinToday(r, day) {
- var t = parseRecordDateMs(r.updateDate || r.update_date);
- if (isNaN(t)) return false;
- return t >= day.startMs && t <= day.endMs;
- }
- function extractPageCount(resp) {
- if (!resp) return 0;
- if (typeof resp.count === 'number') return resp.count;
- if (resp.data && typeof resp.data.count === 'number') return resp.data.count;
- var list = extractList(resp);
- return list.length;
- }
- // 中间卡:返修/报废数量 <- mesProductScreen/repairScrapCount
- function updateRepairScrapCountByApi() {
- $.ajax({
- url: '${ctx}/mes/mesProductScreen/repairScrapCount',
- type: 'POST',
- dataType: 'json',
- success: function(resp) {
- if (!resp || resp.result !== 'true' || !resp.data) return;
- mockData.cards = mockData.cards || {};
- mockData.cards.repairCount = Number(resp.data.repairCount) || 0;
- mockData.cards.scrapCount = Number(resp.data.scrapCount) || 0;
- renderTextData();
- }
- });
- }
- // 不良类型柱状图:按返工白名单(item_id->title)分组统计当天返工次数
- function updateDefectTypeByApi() {
- $.ajax({
- url: '${ctx}/mes/mesProduct/defectTypeData',
- type: 'POST',
- dataType: 'json',
- success: function(resp) {
- if (!resp || resp.result !== 'true' || !resp.data) return;
- var d = resp.data;
- mockData.defectType = {
- labels: d.labels || [],
- data: d.data || [],
- max: Number(d.max) || 10
- };
- drawAllCharts();
- }
- });
- }
- // 近七日产品质检:产出=OP400 OK,不良=返工 OP070-OP400,按产线汇总
- function updateQualityInspectByApi() {
- var labels = null;
- var producedSum = null;
- var defectSum = null;
- var total = QUALITY_LINE_LIST.length;
- var finished = 0;
- function finishOne() {
- finished++;
- if (finished < total) return;
- if (!producedSum || !defectSum) return;
- var maxV = 10;
- var i;
- for (i = 0; i < producedSum.length; i++) {
- if (Number(producedSum[i]) > maxV) maxV = Number(producedSum[i]);
- }
- for (i = 0; i < defectSum.length; i++) {
- if (Number(defectSum[i]) > maxV) maxV = Number(defectSum[i]);
- }
- mockData.qualityInspect = {
- labels: labels || [],
- produced: producedSum,
- defect: defectSum,
- max: Math.ceil(maxV * 1.15) || 10
- };
- drawAllCharts();
- }
- for (var li = 0; li < QUALITY_LINE_LIST.length; li++) {
- (function(lineSn) {
- $.ajax({
- url: '${ctx}/mes/mesProductRecord/weekQualityData',
- type: 'POST',
- dataType: 'json',
- data: {
- lineSn: lineSn,
- producedOprno: OP_OFFLINE,
- defectOprnoFrom: 'OP070',
- defectOprnoTo: OP_OFFLINE
- },
- success: function(resp) {
- if (resp && resp.result === 'true' && resp.data) {
- var d = resp.data;
- if (!labels) labels = d.labels || [];
- var p = d.produced || [];
- var df = d.defect || [];
- if (!producedSum) {
- producedSum = [];
- defectSum = [];
- for (var i = 0; i < 7; i++) {
- producedSum[i] = 0;
- defectSum[i] = 0;
- }
- }
- for (var j = 0; j < 7; j++) {
- producedSum[j] += Number(p[j]) || 0;
- defectSum[j] += Number(df[j]) || 0;
- }
- }
- finishOne();
- },
- error: function() {
- finishOne();
- }
- });
- })(QUALITY_LINE_LIST[li]);
- }
- }
- /** 实际产量:OP400 当天完成(OK)数 */
- function updateActualProductionByApi() {
- var dayRange = getCalendarDayUpdateDateRange();
- var pageSize = 3000;
- var maxPages = 200;
- var pageNo = 1;
- var totalOk = 0;
- function applyActualProductionCount(n) {
- mockData.cards = mockData.cards || {};
- mockData.cards.actualProduction = Number(n) || 0;
- syncAchieveRateWithPlan();
- renderTextData();
- }
- function fetchPage() {
- $.ajax({
- url: '${ctx}/mes/mesProductScreen/recordList',
- type: 'POST',
- dataType: 'json',
- data: {
- pageNo: pageNo,
- pageSize: pageSize,
- oprno: OP_OFFLINE,
- content: 'OK',
- updateDate_gte: dayRange.gte,
- updateDate_lte: dayRange.lte
- },
- success: function(resp) {
- var list = extractList(resp);
- for (var i = 0; i < list.length; i++) {
- var r = list[i] || {};
- if (isOprOfflineFamily(r.oprno) && isRecordContentOk(r.content) && isRecordWithinToday(r, dayRange)) {
- totalOk++;
- }
- }
- if (list.length === pageSize && pageNo < maxPages) {
- pageNo++;
- fetchPage();
- } else {
- applyActualProductionCount(totalOk);
- }
- },
- error: function() {
- applyActualProductionCount(totalOk);
- }
- });
- }
- fetchPage();
- }
- function matchesOfflineOkRecord(r) {
- var t = parseRecordDateMs(r.updateDate || r.update_date);
- if (isNaN(t)) return false;
- if (!isOprOfflineFamily(r.oprno) || !isRecordContentOk(r.content)) return false;
- return true;
- }
- function dateKeyFromMs(ms, padFn) {
- var d = new Date(ms);
- return d.getFullYear() + '-' + padFn(d.getMonth() + 1) + '-' + padFn(d.getDate());
- }
- function updateProductTrend7DaysByApi() {
- var now = new Date();
- var padFn = function(n) { return n < 10 ? '0' + n : '' + n; };
- var labels = [];
- var dayKeys = [];
- var i;
- for (i = 6; i >= 0; i--) {
- var d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i);
- labels.push((d.getMonth() + 1) + '月' + d.getDate() + '日');
- dayKeys.push(d.getFullYear() + '-' + padFn(d.getMonth() + 1) + '-' + padFn(d.getDate()));
- }
- var oldest = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
- var gte = oldest.getFullYear() + '-' + padFn(oldest.getMonth() + 1) + '-' + padFn(oldest.getDate()) + ' 00:00:00';
- var lte = now.getFullYear() + '-' + padFn(now.getMonth() + 1) + '-' + padFn(now.getDate()) + ' 23:59:59';
- var counts = {};
- for (i = 0; i < dayKeys.length; i++) {
- counts[dayKeys[i]] = 0;
- }
- var pageSize = 3000;
- var maxPages = 500;
- var pageNo = 1;
- function fetchPage() {
- $.ajax({
- url: '${ctx}/mes/mesProductScreen/recordList',
- type: 'POST',
- dataType: 'json',
- data: {
- pageNo: pageNo,
- pageSize: pageSize,
- oprno: OP_OFFLINE,
- content: 'OK',
- updateDate_gte: gte,
- updateDate_lte: lte
- },
- success: function(resp) {
- var list = extractList(resp);
- var j;
- for (j = 0; j < list.length; j++) {
- var r = list[j] || {};
- if (!matchesOfflineOkRecord(r)) continue;
- var t = parseRecordDateMs(r.updateDate || r.update_date);
- var key = dateKeyFromMs(t, padFn);
- if (Object.prototype.hasOwnProperty.call(counts, key)) {
- counts[key]++;
- }
- }
- if (list.length === pageSize && pageNo < maxPages) {
- pageNo++;
- fetchPage();
- } else {
- var data = [];
- for (j = 0; j < dayKeys.length; j++) {
- data.push(counts[dayKeys[j]] || 0);
- }
- var mx = Math.max.apply(null, data.concat([0]));
- var maxV = mx <= 0 ? 10 : Math.ceil(mx * 1.15);
- mockData.productTrend = {
- labels: labels,
- data: data,
- max: maxV,
- min: 0
- };
- drawAllCharts();
- }
- },
- error: function() {
- drawAllCharts();
- }
- });
- }
- fetchPage();
- }
- function refreshData() {
- generateMockData();
- renderTextData();
- updateScreenConfigByApi();
- updateActualProductionByApi();
- updateProductTrend7DaysByApi();
- updateDeviceAlarmAlertsByApi();
- updateRepairScrapCountByApi();
- updateDefectTypeByApi();
- updateQualityInspectByApi();
- renderFaultRank();
- renderAlerts();
- updateHourlyUpDownByApi();
- drawAllCharts();
- }
- function changeZoom() {
- var wb = $(window).width() / 1920;
- var hb = $(window).height() / 1080;
- var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement);
- var $app = $('#app');
- if (isFs) {
- $('html, body').css({ 'zoom': 1, 'width': '100%', 'height': '100%', 'overflow': 'hidden', 'margin': 0, 'background': '#00091A' });
- $app.css({
- 'transform': 'scale(' + wb + ', ' + hb + ')',
- 'transform-origin': 'left top',
- 'margin': 0,
- 'position': 'absolute',
- 'left': 0,
- 'top': 0
- });
- } else {
- $('html').css({ 'width': '', 'height': '', 'overflow': '', 'margin': '', 'background': '', 'zoom': '' });
- $app.css({ 'transform': 'none', 'transform-origin': '', 'margin': '0 auto', 'position': 'relative', 'left': '', 'top': '' });
- $('body').css({ 'zoom': wb >= hb ? hb : wb, 'width': '', 'height': '', 'overflow': 'hidden', 'margin': '', 'background': '' });
- }
- }
- function drawAllCharts() {
- // 近七日产量(右上)
- var d1 = mockData.productTrend;
- if (d1) drawLineChart('chartLeft1', d1.data, d1.labels, d1.max, d1.min, '#00C8FF');
- // 近七日产品质检(左下)
- var d2 = mockData.qualityInspect;
- if (d2) drawDoubleBarChart('chartLeft2', d2.produced, d2.defect, d2.labels, d2.max);
- // 每小时上下线(右下)
- var d6 = mockData.hourlyData;
- if (d6) drawAreaLineChart('chartRight2', d6.up, d6.down, d6.labels, d6.max);
- }
- $(window).resize(function() {
- changeZoom();
- drawAllCharts();
- });
- function initFullscreenBtn() {
- var btn = document.getElementById('btnFullscreen');
- if (!btn) return;
- btn.addEventListener('click', function () {
- var el = document.documentElement;
- if (el.requestFullscreen) {
- el.requestFullscreen();
- } else if (el.webkitRequestFullscreen) {
- el.webkitRequestFullscreen();
- }
- });
- function syncFullscreenBtn() {
- var fs = document.fullscreenElement || document.webkitFullscreenElement;
- btn.style.display = fs ? 'none' : '';
- changeZoom();
- drawAllCharts();
- }
- document.addEventListener('fullscreenchange', syncFullscreenBtn);
- document.addEventListener('webkitfullscreenchange', syncFullscreenBtn);
- }
- $(function() {
- changeZoom();
- updateClock();
- setInterval(updateClock, 1000);
- refreshData();
- initFullscreenBtn();
- // 每2分钟刷新一次数据
- setInterval(refreshData, 1000 * 60 * 2);
- // 每秒重绘一次(应对 resize 或 zoom 变化)
- setInterval(drawAllCharts, 1000);
- });
- })();
- </script>
- </body>
- </html>
|