feat(micro/acid): 酸轧新增「模型」tab,对接六机架轧制模型服务
- 新增/mill-api代理(dev+nginx),指向klp-ml-mill FastAPI服务 - Model.vue:当前卷/下一卷,L2实测 vs 我们模型预测,逐机架对照 - 鉴权Key通过VUE_APP_MILL_API_KEY环境变量注入,不写死在源码里
This commit is contained in:
26
klp-ui/src/api/l2/mill.js
Normal file
26
klp-ui/src/api/l2/mill.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import request from '@/utils/millRequest'
|
||||||
|
|
||||||
|
// 当前卷 + 下一卷:L2真实数据 与 我们模型预测数据,四组一起返回
|
||||||
|
export function getLatestMillData() {
|
||||||
|
return request({
|
||||||
|
url: '/latest',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单次预测(静态/序贯两种模式,见klp-ml-mill/api.py PredictRequest)
|
||||||
|
export function predictMill(data) {
|
||||||
|
return request({
|
||||||
|
url: '/predict',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务健康检查 + 数据库摘要
|
||||||
|
export function getMillHealth() {
|
||||||
|
return request({
|
||||||
|
url: '/health',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
41
klp-ui/src/utils/millRequest.js
Normal file
41
klp-ui/src/utils/millRequest.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { Message } from 'element-ui'
|
||||||
|
|
||||||
|
// 酸轧六机架轧制模型服务(klp-ml-mill FastAPI)专用请求实例。
|
||||||
|
// 这是一套独立的 Python 服务,不走若依后端的 {code,msg,data} 包装协议,
|
||||||
|
// 直接返回业务 JSON,所以单独起一个 axios 实例,不复用 utils/request.js 的响应拦截逻辑。
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: '/mill-api',
|
||||||
|
timeout: 60000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json;charset=utf-8',
|
||||||
|
// 临时鉴权Key,服务端在请求头 X-API-Key 校验(见 klp-ml-mill/api.py)。
|
||||||
|
// 值不写死在代码里,从 VUE_APP_MILL_API_KEY 读(见 klp-ui/.env.local,未入库)。
|
||||||
|
'X-API-Key': process.env.VUE_APP_MILL_API_KEY || ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!process.env.VUE_APP_MILL_API_KEY) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('[millRequest] 未配置 VUE_APP_MILL_API_KEY,模型服务接口会被拒绝(401)。'
|
||||||
|
+ '请在 klp-ui/.env.local 里设置该变量。')
|
||||||
|
}
|
||||||
|
|
||||||
|
service.interceptors.response.use(
|
||||||
|
res => res.data,
|
||||||
|
error => {
|
||||||
|
let message = error.message || '请求失败'
|
||||||
|
if (error.response) {
|
||||||
|
const detail = error.response.data && error.response.data.detail
|
||||||
|
message = detail || `模型服务接口异常(${error.response.status})`
|
||||||
|
} else if (message === 'Network Error') {
|
||||||
|
message = '无法连接模型服务(klp-ml-mill),请确认服务是否已启动'
|
||||||
|
} else if (message.includes('timeout')) {
|
||||||
|
message = '模型服务请求超时'
|
||||||
|
}
|
||||||
|
Message({ message, type: 'error', duration: 5000 })
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default service
|
||||||
360
klp-ui/src/views/micro/pages/acid/components/Model.vue
Normal file
360
klp-ui/src/views/micro/pages/acid/components/Model.vue
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
<template>
|
||||||
|
<div class="model-page">
|
||||||
|
<el-card shadow="never" class="page-card">
|
||||||
|
<div slot="header" class="card-header">
|
||||||
|
<span>
|
||||||
|
<i class="el-icon-cpu"></i>
|
||||||
|
六机架轧制模型 · 当前卷 / 下一卷预测对照
|
||||||
|
</span>
|
||||||
|
<div class="header-actions">
|
||||||
|
<span v-if="updatedAt" class="updated-at">更新于 {{ updatedAt }}</span>
|
||||||
|
<el-switch
|
||||||
|
v-model="autoRefresh"
|
||||||
|
active-text="自动刷新"
|
||||||
|
inactive-color="#c0c4cc"
|
||||||
|
style="margin: 0 12px;"
|
||||||
|
/>
|
||||||
|
<el-button size="mini" icon="el-icon-refresh" :loading="loading" @click="fetchData">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-if="errorMsg"
|
||||||
|
:title="errorMsg"
|
||||||
|
type="error"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
style="margin-bottom: 16px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-loading="loading && !data" element-loading-text="正在从模型服务拉取数据...">
|
||||||
|
<el-empty v-if="!data && !loading && !errorMsg" description="暂无数据,点击右上角「刷新」拉取" />
|
||||||
|
|
||||||
|
<template v-if="data && !data.error">
|
||||||
|
<coil-panel title="当前卷" tag-type="success" :coil="data.current" />
|
||||||
|
<coil-panel title="下一卷" tag-type="warning" :coil="data.next" style="margin-top: 20px;" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-else-if="data && data.error"
|
||||||
|
:title="data.error"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getLatestMillData } from '@/api/l2/mill'
|
||||||
|
|
||||||
|
const STATUS_MAP = {
|
||||||
|
PRODUCING: { text: '正在轧', type: 'success' },
|
||||||
|
READY: { text: 'L2已生成设定值', type: 'primary' },
|
||||||
|
ONLINE: { text: '已排队待轧', type: 'warning' },
|
||||||
|
PRODUCT: { text: '已轧完', type: 'info' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与 klp-ml-mill/predict.py 的 TABLE_COLUMNS 保持一致,跟HMI「模型设定」页面列一致
|
||||||
|
const COLUMNS = [
|
||||||
|
{ label: '入口厚度', unit: 'mm', field: 'setup_enthick', fmt: v => fmtNum(v, 3) },
|
||||||
|
{ label: '压下率', unit: '%', field: 'setup_reduct', fmt: v => fmtNum(v, 0) },
|
||||||
|
{ label: '轧辊速度', unit: 'm/min', field: 'calc_stand_spd', fmt: v => fmtNum(v, 0) },
|
||||||
|
{ label: '前滑', unit: '%', field: 'meas_slip', fmt: v => fmtNum(v, 2) },
|
||||||
|
{ label: '轧制力', unit: 'kN', field: 'calc_force', fmt: v => fmtNum(v, 0) },
|
||||||
|
{ label: '辊缝', unit: 'mm', field: 'calc_gap_run', fmt: v => fmtNum(v, 2) },
|
||||||
|
{ label: '工作辊弯辊', unit: 'kN', field: 'mea_wr_bend', fmt: v => fmtNum(v, 0) },
|
||||||
|
{ label: '中间辊弯辊', unit: 'kN', field: 'mea_ir_bend', fmt: v => fmtNum(v, 0) },
|
||||||
|
{ label: '张应力', unit: 'MPa', field: 'setup_extension_stress', fmt: v => fmtNum(v, 0) },
|
||||||
|
{ label: '出口张力', unit: 'kN', field: 'setup_extension_force', fmt: v => fmtNum(v, 0) },
|
||||||
|
{ label: '负荷', unit: '%', field: 'calc_power_ratio', fmt: v => fmtNum(v, 0) },
|
||||||
|
]
|
||||||
|
|
||||||
|
function fmtNum(v, digits) {
|
||||||
|
if (v === null || v === undefined || v === '') return '-'
|
||||||
|
const n = Number(v)
|
||||||
|
if (Number.isNaN(n)) return '-'
|
||||||
|
return n.toFixed(digits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CoilPanel:一个卷(当前/下一)的展示,L2实测 vs 我们的预测,逐机架对照
|
||||||
|
const CoilPanel = {
|
||||||
|
name: 'CoilPanel',
|
||||||
|
props: {
|
||||||
|
title: { type: String, required: true },
|
||||||
|
tagType: { type: String, default: 'info' },
|
||||||
|
coil: { type: Object, default: null },
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
statusInfo() {
|
||||||
|
const s = this.coil && this.coil.status
|
||||||
|
return STATUS_MAP[s] || { text: s || '未知', type: 'info' }
|
||||||
|
},
|
||||||
|
specText() {
|
||||||
|
const s = this.coil && this.coil.spec
|
||||||
|
if (!s) return '-'
|
||||||
|
return `${s.grade} ${s.entry_thick} → ${s.exit_thick} mm 宽 ${s.width}`
|
||||||
|
},
|
||||||
|
// 6机架 x 2行(产线/预测),空过机架单独一行
|
||||||
|
rows() {
|
||||||
|
if (!this.coil) return []
|
||||||
|
const l2 = this.coil.l2 || {}
|
||||||
|
const ours = (this.coil.ours && this.coil.ours.predictions) || {}
|
||||||
|
const out = []
|
||||||
|
for (let sid = 0; sid < 6; sid++) {
|
||||||
|
const oursStand = ours['stand' + sid]
|
||||||
|
const l2Stand = l2[String(sid)] || l2[sid]
|
||||||
|
const active = oursStand ? oursStand.active !== false : !!l2Stand
|
||||||
|
if (!active) {
|
||||||
|
out.push({ standLabel: '机架' + (sid + 1), source: '空过', idle: true, rowspan: 1 })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out.push({
|
||||||
|
standLabel: '机架' + (sid + 1), source: '产线(L2)', rowspan: 2, isFirst: true,
|
||||||
|
values: COLUMNS.map(c => (l2Stand ? l2Stand[c.field] : null)),
|
||||||
|
})
|
||||||
|
out.push({
|
||||||
|
standLabel: '机架' + (sid + 1), source: '预测(我们)', rowspan: 0, isFirst: false,
|
||||||
|
values: COLUMNS.map(c => {
|
||||||
|
const e = oursStand && oursStand[c.field]
|
||||||
|
return e && typeof e === 'object' ? e.value : null
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
objectSpanMethod({ row, columnIndex }) {
|
||||||
|
if (columnIndex === 0) {
|
||||||
|
if (row.rowspan === 0) return { rowspan: 0, colspan: 0 }
|
||||||
|
return { rowspan: row.rowspan, colspan: 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fmt(row, colIndex) {
|
||||||
|
if (row.idle) return '-'
|
||||||
|
const raw = row.values[colIndex]
|
||||||
|
return COLUMNS[colIndex].fmt(raw)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render(h) {
|
||||||
|
const columns = COLUMNS
|
||||||
|
const header = h('div', { class: 'coil-panel-header' }, [
|
||||||
|
h('span', { class: 'coil-panel-title' }, [
|
||||||
|
h('el-tag', { props: { type: this.tagType, effect: 'dark', size: 'medium' } }, this.title),
|
||||||
|
]),
|
||||||
|
this.coil
|
||||||
|
? h('span', { class: 'coil-meta' }, [
|
||||||
|
h('b', this.coil.coilid || '-'),
|
||||||
|
h('el-tag', { props: { type: this.statusInfo.type, size: 'mini' }, style: 'margin: 0 8px;' }, this.statusInfo.text),
|
||||||
|
h('span', { class: 'spec-text' }, this.specText),
|
||||||
|
])
|
||||||
|
: null,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!this.coil) return h('div', { class: 'coil-panel' }, [header, h('el-empty', { props: { description: '暂无数据' } })])
|
||||||
|
|
||||||
|
const notes = []
|
||||||
|
if (this.coil.l2_note) notes.push(h('el-alert', {
|
||||||
|
props: { title: 'L2实测:' + this.coil.l2_note, type: 'info', showIcon: true, closable: false },
|
||||||
|
style: 'margin: 8px 0;',
|
||||||
|
}))
|
||||||
|
if (this.coil.ours_note) notes.push(h('el-alert', {
|
||||||
|
props: { title: '我们的预测:' + this.coil.ours_note, type: 'info', showIcon: true, closable: false },
|
||||||
|
style: 'margin: 8px 0;',
|
||||||
|
}))
|
||||||
|
|
||||||
|
const tableCols = [
|
||||||
|
h('el-table-column', {
|
||||||
|
props: { label: '机架', width: '150', align: 'center' },
|
||||||
|
scopedSlots: {
|
||||||
|
default: ({ row }) => [
|
||||||
|
h('div', { class: 'stand-cell' }, [
|
||||||
|
h('span', row.standLabel),
|
||||||
|
!row.idle ? h('el-tag', {
|
||||||
|
props: { size: 'mini', type: row.isFirst ? 'success' : 'primary', effect: row.isFirst ? 'plain' : 'dark' },
|
||||||
|
style: 'margin-left: 6px;',
|
||||||
|
}, row.source) : h('el-tag', { props: { size: 'mini', type: 'info' } }, '空过'),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...columns.map((c, idx) => h('el-table-column', {
|
||||||
|
props: { label: c.label, align: 'right', minWidth: '92' },
|
||||||
|
scopedSlots: {
|
||||||
|
header: () => [h('div', [h('div', c.label), h('div', { class: 'col-unit' }, c.unit)])],
|
||||||
|
default: ({ row }) => [h('span', { class: { 'l2-value': row.isFirst, 'pred-value': row.isFirst === false } }, this.fmt(row, idx))],
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
|
||||||
|
return h('div', { class: 'coil-panel' }, [
|
||||||
|
header,
|
||||||
|
...notes,
|
||||||
|
h('el-table', {
|
||||||
|
props: {
|
||||||
|
data: this.rows, border: true, size: 'mini', stripe: false,
|
||||||
|
spanMethod: this.objectSpanMethod,
|
||||||
|
rowClassName: ({ row }) => (row.idle ? 'idle-row' : (row.isFirst ? 'l2-row' : 'pred-row')),
|
||||||
|
},
|
||||||
|
}, tableCols),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AcidModel',
|
||||||
|
components: { CoilPanel },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
data: null,
|
||||||
|
errorMsg: '',
|
||||||
|
updatedAt: '',
|
||||||
|
autoRefresh: false,
|
||||||
|
timer: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
autoRefresh(val) {
|
||||||
|
if (val) {
|
||||||
|
this.timer = setInterval(this.fetchData, 30000)
|
||||||
|
} else if (this.timer) {
|
||||||
|
clearInterval(this.timer)
|
||||||
|
this.timer = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.timer) clearInterval(this.timer)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
this.loading = true
|
||||||
|
this.errorMsg = ''
|
||||||
|
try {
|
||||||
|
this.data = await getLatestMillData()
|
||||||
|
this.updatedAt = new Date().toLocaleTimeString('zh-CN', { hour12: false })
|
||||||
|
} catch (e) {
|
||||||
|
this.errorMsg = (e && e.response && e.response.data && e.response.data.detail) || '获取模型数据失败'
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.model-page {
|
||||||
|
padding: 16px;
|
||||||
|
height: calc(100% - 32px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #1f2d3d;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: #409eff;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.updated-at {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .coil-panel {
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fafbfc;
|
||||||
|
|
||||||
|
.coil-panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coil-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
|
||||||
|
b {
|
||||||
|
color: #1f2d3d;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-text {
|
||||||
|
color: #606266;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stand-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-unit {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #909399;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l2-value {
|
||||||
|
color: #1f2d3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pred-value {
|
||||||
|
color: #409eff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table .l2-row {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table .pred-row {
|
||||||
|
background-color: #ecf5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table .idle-row {
|
||||||
|
background-color: #f4f4f5;
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -26,6 +26,10 @@
|
|||||||
<i class="el-icon-s-tools"></i>
|
<i class="el-icon-s-tools"></i>
|
||||||
<span slot="title">规程</span>
|
<span slot="title">规程</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
<el-menu-item index="model">
|
||||||
|
<i class="el-icon-cpu"></i>
|
||||||
|
<span slot="title">模型</span>
|
||||||
|
</el-menu-item>
|
||||||
<el-menu-item index="performance">
|
<el-menu-item index="performance">
|
||||||
<i class="el-icon-date"></i>
|
<i class="el-icon-date"></i>
|
||||||
<span slot="title">计划</span>
|
<span slot="title">计划</span>
|
||||||
@@ -39,7 +43,7 @@
|
|||||||
<span slot="title">实绩</span>
|
<span slot="title">实绩</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="rollConfig">
|
<el-menu-item index="rollConfig">
|
||||||
<i class="el-icon-s-tools"></i>
|
<i class="el-icon-setting"></i>
|
||||||
<span slot="title">配辊</span>
|
<span slot="title">配辊</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="rollHistory">
|
<el-menu-item index="rollHistory">
|
||||||
@@ -76,6 +80,7 @@ import Stoppage from '@/views/timing/stoppage/index.vue';
|
|||||||
import AcidTiming from '@/views/lines/acid/index.vue';
|
import AcidTiming from '@/views/lines/acid/index.vue';
|
||||||
import TrackingView from './components/TrackingView.vue';
|
import TrackingView from './components/TrackingView.vue';
|
||||||
import ProcessSpec from './components/ProcessSpec.vue';
|
import ProcessSpec from './components/ProcessSpec.vue';
|
||||||
|
import Model from './components/Model.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AcidSystem',
|
name: 'AcidSystem',
|
||||||
@@ -92,7 +97,8 @@ export default {
|
|||||||
Stoppage,
|
Stoppage,
|
||||||
AcidTiming,
|
AcidTiming,
|
||||||
TrackingView,
|
TrackingView,
|
||||||
ProcessSpec
|
ProcessSpec,
|
||||||
|
Model
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -115,6 +121,7 @@ export default {
|
|||||||
acidTiming: 'AcidTiming',
|
acidTiming: 'AcidTiming',
|
||||||
tracking: 'TrackingView',
|
tracking: 'TrackingView',
|
||||||
processSpec: 'ProcessSpec',
|
processSpec: 'ProcessSpec',
|
||||||
|
model: 'Model',
|
||||||
};
|
};
|
||||||
return componentMap[this.activeMenu];
|
return componentMap[this.activeMenu];
|
||||||
},
|
},
|
||||||
@@ -159,6 +166,7 @@ export default {
|
|||||||
color: #606266;
|
color: #606266;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #ecf5ff;
|
background-color: #ecf5ff;
|
||||||
@@ -169,11 +177,13 @@ export default {
|
|||||||
background-color: #ecf5ff;
|
background-color: #ecf5ff;
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
border-right: 3px solid #409eff;
|
border-right: 3px solid #409eff;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
transition: color 0.2s ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ module.exports = {
|
|||||||
['^' + '/zinc-api']: '/api'
|
['^' + '/zinc-api']: '/api'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 酸轧六机架轧制模型服务(klp-ml-mill FastAPI),/predict /latest /sync /retrain /health
|
||||||
|
'/mill-api': {
|
||||||
|
target: `http://117.72.159.31:10001`,
|
||||||
|
changeOrigin: true,
|
||||||
|
pathRewrite: {
|
||||||
|
['^' + '/mill-api']: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
// 直接代理WebSocket相关路径
|
// 直接代理WebSocket相关路径
|
||||||
'/websocket': {
|
'/websocket': {
|
||||||
target: `http://localhost:8080`,
|
target: `http://localhost:8080`,
|
||||||
|
|||||||
@@ -81,6 +81,15 @@ http {
|
|||||||
proxy_pass http://server/;
|
proxy_pass http://server/;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 酸轧六机架轧制模型服务(klp-ml-mill FastAPI)
|
||||||
|
location /mill-api/ {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header REMOTE-HOST $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_pass http://117.72.159.31:10001/;
|
||||||
|
}
|
||||||
|
|
||||||
# https 会拦截内链所有的 http 请求 造成功能无法使用
|
# https 会拦截内链所有的 http 请求 造成功能无法使用
|
||||||
# 解决方案1 将 admin 服务 也配置成 https
|
# 解决方案1 将 admin 服务 也配置成 https
|
||||||
# 解决方案2 将菜单配置为外链访问 走独立页面 http 访问
|
# 解决方案2 将菜单配置为外链访问 走独立页面 http 访问
|
||||||
|
|||||||
Reference in New Issue
Block a user