2025-12-31 18:02:35 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="app-container">
|
|
|
|
|
|
<!-- Toolbar / 工具栏 -->
|
|
|
|
|
|
<div class="toolbar">
|
|
|
|
|
|
<el-button @click="reload" icon="el-icon-refresh" size="small" :loading="loading">
|
|
|
|
|
|
Refresh
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="lastSuccess && lastSuccess.lastSendTime"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
plain
|
|
|
|
|
|
icon="el-icon-magic-stick"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="applyLastSuccessValues"
|
|
|
|
|
|
>
|
|
|
|
|
|
Apply Last Success Values
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Cards / 卡片 -->
|
|
|
|
|
|
<div v-loading="loading" class="card-grid-container">
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
|
<el-col
|
|
|
|
|
|
v-for="plan in plans"
|
|
|
|
|
|
:key="plan.id"
|
|
|
|
|
|
:xs="24"
|
|
|
|
|
|
:sm="12"
|
|
|
|
|
|
:md="8"
|
|
|
|
|
|
class="card-col"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-card class="parameter-card" shadow="hover">
|
|
|
|
|
|
<div slot="header" class="card-header">
|
|
|
|
|
|
<!-- Steel Grade only / 只展示钢种,不展示计划 -->
|
|
|
|
|
|
<span class="card-title">Steel Grade: {{ plan.steelGrade || '-' }}</span>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="header-right">
|
|
|
|
|
|
<span v-if="lastSuccess && lastSuccess.lastSendTime" class="last-send-time">
|
|
|
|
|
|
<i class="el-icon-time"></i>
|
|
|
|
|
|
Last Sent: {{ formatTime(lastSuccess.lastSendTime) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="mini"
|
|
|
|
|
|
icon="el-icon-s-promotion"
|
|
|
|
|
|
@click="handleSend(plan)"
|
|
|
|
|
|
:loading="plan.sending"
|
|
|
|
|
|
>
|
|
|
|
|
|
Send
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card-body">
|
2026-01-02 16:00:10 +08:00
|
|
|
|
<!-- 可编辑表单:值 + OPC点位(点位可先为空,后续协商配置) -->
|
2025-12-31 18:02:35 +08:00
|
|
|
|
<el-form :model="plan.params" label-position="top" size="mini">
|
|
|
|
|
|
<el-row :gutter="10">
|
|
|
|
|
|
<el-col
|
2025-12-31 20:05:29 +08:00
|
|
|
|
v-for="item in driveFields"
|
|
|
|
|
|
:key="item.key"
|
2025-12-31 18:02:35 +08:00
|
|
|
|
:span="12"
|
|
|
|
|
|
>
|
2025-12-31 20:05:29 +08:00
|
|
|
|
<el-form-item :label="item.label">
|
2025-12-31 18:02:35 +08:00
|
|
|
|
<el-input
|
2025-12-31 20:05:29 +08:00
|
|
|
|
v-model="plan.params[item.key]"
|
|
|
|
|
|
:placeholder="getPlaceholder(item.key)"
|
2025-12-31 18:02:35 +08:00
|
|
|
|
/>
|
2026-01-02 16:00:10 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- OPC address input / OPC点位输入框(可先不填) -->
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="driveAddress[item.key]"
|
|
|
|
|
|
size="mini"
|
|
|
|
|
|
class="addr-input"
|
|
|
|
|
|
placeholder="OPC address (optional)"
|
|
|
|
|
|
/>
|
2025-12-31 18:02:35 +08:00
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="plans.length === 0 && !loading" class="empty-data">
|
|
|
|
|
|
<el-empty description="No Data"></el-empty>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// Import APIs / 引入接口
|
|
|
|
|
|
import { listPlan } from '@/api/l2/plan'
|
|
|
|
|
|
import { listSetup } from '@/api/business/setup'
|
|
|
|
|
|
import { createSendJob, executeSendJob } from '@/api/l2/sendJob'
|
2025-12-31 20:05:29 +08:00
|
|
|
|
import { getLastSuccess } from '@/api/l2/sendTemplate'
|
|
|
|
|
|
|
|
|
|
|
|
// Drive fields definition (English UI, Chinese comments) / 传动字段定义(英文界面,中文注释)
|
|
|
|
|
|
// 说明:key 必须与 setupForm 字段一致(来自 plan/components/setupForm.vue)
|
2026-01-02 16:00:10 +08:00
|
|
|
|
// Drive + Plan fields definition (show effect first; OPC address can be edited later)
|
|
|
|
|
|
// 传动 + 计划字段定义(先把效果做出来;OPC点位后续可协商配置)
|
2025-12-31 20:05:29 +08:00
|
|
|
|
const DRIVE_FIELDS = [
|
2026-01-02 16:00:10 +08:00
|
|
|
|
// ---- Drive tension / 传动张力 ----
|
|
|
|
|
|
{ key: 'porTension', label: 'Pay-off Reel Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'celTension', label: 'Entry Loop Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'cleanTension', label: 'Cleaning Section Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'furTension', label: 'Furnace Zone Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'towerTension', label: 'Cooling Tower Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'tmNoneTension', label: 'TM No Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'tmEntryTension', label: 'TM Entry Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'tmExitTension', label: 'TM Exit Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'tlNoneTension', label: 'TL No Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'tlExitTension', label: 'TL Exit Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'coatTension', label: 'Post-treatment Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'cxlTension', label: 'Exit Loop Tension', source: 'setup' },
|
|
|
|
|
|
{ key: 'trTension', label: 'Take-up Reel Tension', source: 'setup' },
|
|
|
|
|
|
|
|
|
|
|
|
// ---- TL / TM setup ----
|
|
|
|
|
|
{ key: 'tlElong', label: 'TL Elongation', source: 'setup' },
|
|
|
|
|
|
{ key: 'tlLvlMesh1', label: 'TL Leveling Roll Mesh 1', source: 'setup' },
|
|
|
|
|
|
{ key: 'tlLvlMesh2', label: 'TL Leveling Roll Mesh 2', source: 'setup' },
|
|
|
|
|
|
{ key: 'tlAcbMesh', label: 'TL Anti-crossbow Mesh', source: 'setup' },
|
|
|
|
|
|
|
|
|
|
|
|
{ key: 'tmBendforce', label: 'TM Bending Force', source: 'setup' },
|
|
|
|
|
|
{ key: 'tmAcrMesh', label: 'TM Anti-crimping Roll Mesh', source: 'setup' },
|
|
|
|
|
|
{ key: 'tmBrMesh', label: 'TM Anti-tremor Roll Mesh', source: 'setup' },
|
|
|
|
|
|
{ key: 'tmRollforce', label: 'TM Roll Force', source: 'setup' },
|
2025-12-31 20:05:29 +08:00
|
|
|
|
|
2026-01-02 16:00:10 +08:00
|
|
|
|
// ---- Plan (from listPlan response) / 计划参数(来自 listPlan 返回)----
|
|
|
|
|
|
{ key: 'entryWidth', label: 'Entry Width', source: 'plan' },
|
|
|
|
|
|
{ key: 'entryThick', label: 'Entry Thick', source: 'plan' },
|
|
|
|
|
|
{ key: 'entryWeight', label: 'Entry Weight', source: 'plan' },
|
|
|
|
|
|
{ key: 'entryLength', label: 'Entry Length', source: 'plan' },
|
2025-12-31 20:05:29 +08:00
|
|
|
|
|
2026-01-02 16:00:10 +08:00
|
|
|
|
{ key: 'steelGrade', label: 'Steel Grade', source: 'plan' },
|
|
|
|
|
|
|
|
|
|
|
|
{ key: 'spmElongation', label: 'SPM Elongation', source: 'plan' },
|
|
|
|
|
|
{ key: 'spmRollforce', label: 'SPM Roll Force', source: 'plan' },
|
|
|
|
|
|
{ key: 'spmBendingForce', label: 'SPM Bending Force', source: 'plan' },
|
|
|
|
|
|
|
|
|
|
|
|
{ key: 'yieldPoint', label: 'Yield Point', source: 'plan' }
|
2025-12-31 20:05:29 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
2026-01-02 16:00:10 +08:00
|
|
|
|
// OPC address mapping / OPC点位映射
|
|
|
|
|
|
// 说明:此处后续可协商配置;当前允许在页面上编辑(默认可为空)
|
2025-12-31 20:05:29 +08:00
|
|
|
|
const DRIVE_ADDRESS = {
|
|
|
|
|
|
porTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionPorBR1',
|
|
|
|
|
|
celTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR3',
|
|
|
|
|
|
cleanTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR1BR2',
|
|
|
|
|
|
furTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionFur1',
|
|
|
|
|
|
towerTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionFur2',
|
|
|
|
|
|
tmNoneTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR5BR6',
|
|
|
|
|
|
tmEntryTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR5TM',
|
|
|
|
|
|
tmExitTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionTMBR6',
|
|
|
|
|
|
tlNoneTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR6BR7',
|
|
|
|
|
|
tlExitTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionTLBR7',
|
|
|
|
|
|
coatTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR7BR8',
|
|
|
|
|
|
cxlTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR8BR9',
|
|
|
|
|
|
trTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR9TR',
|
|
|
|
|
|
|
|
|
|
|
|
tlElong: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.TLElongation',
|
|
|
|
|
|
tlLvlMesh1: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.LevelingMesh1',
|
|
|
|
|
|
tlLvlMesh2: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.LevelingMesh2',
|
|
|
|
|
|
tlAcbMesh: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.AntiCrossBowUnitMesh',
|
|
|
|
|
|
|
|
|
|
|
|
tmBendforce: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.TMBendforce',
|
|
|
|
|
|
tmAcrMesh: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.ACRMesh',
|
|
|
|
|
|
tmBrMesh: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.BRMesh',
|
|
|
|
|
|
tmRollforce: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.TMRollforce'
|
|
|
|
|
|
}
|
2025-12-31 18:02:35 +08:00
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'DriveSend',
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
2025-12-31 20:05:29 +08:00
|
|
|
|
loading: false,
|
|
|
|
|
|
lastSuccess: null,
|
|
|
|
|
|
plans: [],
|
2026-01-02 16:00:10 +08:00
|
|
|
|
driveFields: DRIVE_FIELDS,
|
|
|
|
|
|
// 可编辑的 OPC 点位(默认从常量拷贝;你也可以后续改成从后端/本地存储加载)
|
|
|
|
|
|
driveAddress: { ...DRIVE_ADDRESS }
|
2025-12-31 18:02:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
created() {
|
|
|
|
|
|
this.reload()
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async reload() {
|
|
|
|
|
|
this.loading = true
|
|
|
|
|
|
try {
|
2025-12-31 20:05:29 +08:00
|
|
|
|
// last success for DRIVE / 获取传动上次成功
|
|
|
|
|
|
const lastRes = await getLastSuccess('DRIVE')
|
|
|
|
|
|
this.lastSuccess = lastRes && lastRes.code === 200 ? lastRes.data : null
|
2025-12-31 18:02:35 +08:00
|
|
|
|
|
2025-12-31 20:05:29 +08:00
|
|
|
|
// plans / 获取计划
|
2026-01-02 14:30:52 +08:00
|
|
|
|
const planRes = await listPlan({ status: 'NEW,READY,ONLINE,PRODUCING' })
|
2026-01-02 16:00:10 +08:00
|
|
|
|
// 兼容后端返回结构:既可能是 {rows: []} 也可能是 {data: []}
|
|
|
|
|
|
const planList = (planRes && (planRes.rows || planRes.data)) || []
|
2025-12-31 18:02:35 +08:00
|
|
|
|
|
|
|
|
|
|
const tasks = planList.map(async (p) => {
|
|
|
|
|
|
let setup = {}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const setupRes = await listSetup({ coilid: p.coilid, planid: p.planid })
|
|
|
|
|
|
setup = (setupRes.rows && setupRes.rows.length > 0) ? setupRes.rows[0] : {}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
setup = {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const params = {}
|
2025-12-31 20:05:29 +08:00
|
|
|
|
this.driveFields.forEach(f => {
|
2026-01-02 16:00:10 +08:00
|
|
|
|
const fromPlan = p ? p[f.key] : undefined
|
|
|
|
|
|
const fromSetup = setup ? setup[f.key] : undefined
|
2025-12-31 20:05:29 +08:00
|
|
|
|
const fromLast = this.lastSuccess?.values?.[f.key]
|
2026-01-02 16:00:10 +08:00
|
|
|
|
|
|
|
|
|
|
// 优先级:setup(如果字段来自setup) / plan(如果字段来自plan) -> lastSuccess -> ''
|
|
|
|
|
|
if (f.source === 'plan') {
|
|
|
|
|
|
if (fromPlan !== undefined && fromPlan !== null && String(fromPlan) !== '') {
|
|
|
|
|
|
params[f.key] = String(fromPlan)
|
|
|
|
|
|
} else if (fromLast !== undefined && fromLast !== null) {
|
|
|
|
|
|
params[f.key] = String(fromLast)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
params[f.key] = ''
|
|
|
|
|
|
}
|
2025-12-31 18:02:35 +08:00
|
|
|
|
} else {
|
2026-01-02 16:00:10 +08:00
|
|
|
|
if (fromSetup !== undefined && fromSetup !== null && String(fromSetup) !== '') {
|
|
|
|
|
|
params[f.key] = String(fromSetup)
|
|
|
|
|
|
} else if (fromLast !== undefined && fromLast !== null) {
|
|
|
|
|
|
params[f.key] = String(fromLast)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
params[f.key] = ''
|
|
|
|
|
|
}
|
2025-12-31 18:02:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
...p,
|
|
|
|
|
|
setup,
|
|
|
|
|
|
params,
|
|
|
|
|
|
sending: false
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
this.plans = await Promise.all(tasks)
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error(e)
|
|
|
|
|
|
this.$message.error('Load failed')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
applyLastSuccessValues() {
|
|
|
|
|
|
if (!this.lastSuccess || !this.lastSuccess.values) {
|
|
|
|
|
|
this.$message.info('No last success data')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
this.plans.forEach(plan => {
|
2025-12-31 20:05:29 +08:00
|
|
|
|
this.driveFields.forEach(f => {
|
|
|
|
|
|
const v = this.lastSuccess.values[f.key]
|
2025-12-31 18:02:35 +08:00
|
|
|
|
if (v !== undefined) {
|
2025-12-31 20:05:29 +08:00
|
|
|
|
this.$set(plan.params, f.key, String(v))
|
2025-12-31 18:02:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
this.$message.success('Last success values applied')
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-12-31 20:05:29 +08:00
|
|
|
|
getPlaceholder(key) {
|
|
|
|
|
|
const v = this.lastSuccess?.values?.[key]
|
|
|
|
|
|
if (v !== undefined) return `Last: ${v}`
|
2025-12-31 18:02:35 +08:00
|
|
|
|
return 'Please enter'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
formatTime(t) {
|
|
|
|
|
|
if (!t) return ''
|
2025-12-31 20:05:29 +08:00
|
|
|
|
return new Date(t).toLocaleString()
|
2025-12-31 18:02:35 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
handleSend(plan) {
|
|
|
|
|
|
this.$confirm(
|
|
|
|
|
|
`Confirm to send parameters for Steel Grade [${plan.steelGrade || '-'}]?`,
|
|
|
|
|
|
'Warning',
|
|
|
|
|
|
{
|
|
|
|
|
|
confirmButtonText: 'Confirm',
|
|
|
|
|
|
cancelButtonText: 'Cancel',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
}
|
|
|
|
|
|
).then(() => this.doSend(plan)).catch(() => {})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async doSend(plan) {
|
|
|
|
|
|
plan.sending = true
|
|
|
|
|
|
try {
|
2025-12-31 20:05:29 +08:00
|
|
|
|
const items = this.driveFields.map(f => ({
|
|
|
|
|
|
paramCode: f.key,
|
2026-01-02 16:00:10 +08:00
|
|
|
|
// OPC点位允许为空:为空则本次不发送该字段(先做效果,后续再配置)
|
|
|
|
|
|
address: this.driveAddress[f.key],
|
2025-12-31 20:05:29 +08:00
|
|
|
|
valueRaw: String(plan.params[f.key] || ''),
|
2025-12-31 18:02:35 +08:00
|
|
|
|
setTime: new Date()
|
|
|
|
|
|
})).filter(it => !!it.address)
|
|
|
|
|
|
|
2026-01-02 16:00:10 +08:00
|
|
|
|
if (!items.length) {
|
|
|
|
|
|
this.$message.warning('OPC点位为空:当前没有可发送的字段(请先在输入框里填写点位)')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 18:02:35 +08:00
|
|
|
|
const dto = {
|
2025-12-31 20:05:29 +08:00
|
|
|
|
deviceName: 'CGL_LINE_1',
|
2025-12-31 18:02:35 +08:00
|
|
|
|
groups: [
|
|
|
|
|
|
{
|
|
|
|
|
|
groupNo: 1,
|
|
|
|
|
|
groupType: 'DRIVE',
|
2026-01-02 16:00:10 +08:00
|
|
|
|
groupName: `Drive/Plan Params for ${plan.steelGrade || ''}`,
|
2025-12-31 18:02:35 +08:00
|
|
|
|
items
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const createRes = await createSendJob(dto)
|
|
|
|
|
|
const jobId = createRes.data
|
|
|
|
|
|
if (!jobId) throw new Error('Create send job failed')
|
|
|
|
|
|
|
|
|
|
|
|
await executeSendJob(jobId)
|
|
|
|
|
|
this.$message.success('Send success')
|
|
|
|
|
|
|
|
|
|
|
|
await this.reload()
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error(e)
|
|
|
|
|
|
this.$message.error(e.message || 'Send failed')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
plan.sending = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2025-12-31 20:05:29 +08:00
|
|
|
|
.page-title { margin-bottom: 20px; }
|
|
|
|
|
|
.toolbar { margin-bottom: 20px; display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
|
|
|
|
|
|
.card-grid-container { min-height: 300px; }
|
|
|
|
|
|
.card-col { margin-bottom: 20px; }
|
|
|
|
|
|
.parameter-card .card-header { display:flex; justify-content:space-between; align-items:center; }
|
|
|
|
|
|
.card-title { font-weight: 600; }
|
|
|
|
|
|
.header-right { display:flex; align-items:center; }
|
|
|
|
|
|
.last-send-time { font-size: 12px; color:#909399; margin-right:16px; }
|
|
|
|
|
|
.empty-data { margin-top: 20px; }
|
2025-12-31 18:02:35 +08:00
|
|
|
|
</style>
|