- 移除不必要的 ISysDictTypeService 依赖,简化 SysDictDataController - 新增 selectDictDataByTypeRealtime 方法,支持实时查询字典数据,避免缓存问题 - 更新 SysDictDataController 中的字典数据查询逻辑,使用新方法 - 在 SysDictTypeController 中添加按字典类型编码精确查询的接口 - 更新前端组件以支持新的字典查询接口,优化字典选择器的加载逻辑
681 lines
24 KiB
Vue
681 lines
24 KiB
Vue
<template>
|
||
<div class="plan-spec-page" v-loading="pageLoading">
|
||
<!-- 头部 -->
|
||
<div class="page-header">
|
||
<el-button type="text" icon="el-icon-arrow-left" @click="goBack">返回</el-button>
|
||
<span class="page-title">方案详情</span>
|
||
<span v-if="versionCode" class="version-badge">版本 {{ versionCode }}</span>
|
||
</div>
|
||
|
||
<!-- 可配置 / 不可配置 切换 -->
|
||
<div class="config-tabs">
|
||
<span
|
||
:class="['config-tab', { active: configMode === 'configurable' }]"
|
||
@click="configMode = 'configurable'"
|
||
>可配置</span>
|
||
<span
|
||
:class="['config-tab', { active: configMode === 'readonly' }]"
|
||
@click="configMode = 'readonly'"
|
||
>不可配置</span>
|
||
</div>
|
||
|
||
<div class="main-layout">
|
||
<!-- 左侧段分组 -->
|
||
<div class="left-tree">
|
||
<div
|
||
:class="['tree-item', { active: activeSegmentType === '' }]"
|
||
@click="selectSegmentType('')"
|
||
>全部</div>
|
||
<div
|
||
v-for="seg in segmentTypeTabOptions"
|
||
:key="String(seg.value)"
|
||
:class="['tree-item', { active: String(activeSegmentType) === String(seg.value) }]"
|
||
@click="selectSegmentType(seg.value)"
|
||
>
|
||
<span class="tree-arrow">›</span> {{ seg.label }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧内容 -->
|
||
<div class="right-content">
|
||
<!-- 搜索栏 -->
|
||
<div class="search-bar">
|
||
<template v-if="activeSegmentType !== '' && activeSegmentType !== undefined && activeSegmentType !== null">
|
||
<span class="search-label">段名称:</span>
|
||
<el-select
|
||
v-model="activeSegmentName"
|
||
placeholder="请选择"
|
||
size="small"
|
||
clearable
|
||
style="width:180px"
|
||
@change="onSegmentNameFilterChange"
|
||
>
|
||
<el-option
|
||
v-for="opt in segmentNameFilterOptions"
|
||
:key="String(opt.value) + '-' + opt.label"
|
||
:label="opt.label"
|
||
:value="opt.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
<span class="search-label">点位名称:</span>
|
||
<el-input
|
||
v-model="filterName"
|
||
placeholder="请输入"
|
||
size="small"
|
||
style="width:200px"
|
||
clearable
|
||
@keyup.enter.native="applyFilter"
|
||
/>
|
||
<el-button size="mini" type="primary" @click="applyFilter">查询</el-button>
|
||
<el-button size="mini" @click="resetFilter">重置</el-button>
|
||
<el-button
|
||
size="mini"
|
||
type="primary"
|
||
icon="el-icon-plus"
|
||
style="margin-left:auto"
|
||
@click="openPlanDialog()"
|
||
>新建方案点位</el-button>
|
||
</div>
|
||
|
||
<!-- 方案点位表 -->
|
||
<el-table
|
||
v-loading="planLoading"
|
||
:data="filteredPlans"
|
||
size="small"
|
||
highlight-current-row
|
||
@current-change="onPlanSelect"
|
||
>
|
||
<el-table-column label="序号" type="index" align="center" />
|
||
<el-table-column label="父级名称" show-overflow-tooltip>
|
||
<template slot-scope="{ row }">{{ segLabel(row.segmentType) }} › {{ row.segmentName || '—' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="点位名称" prop="pointName" show-overflow-tooltip />
|
||
<el-table-column label="实际值ID" prop="actualValueId" show-overflow-tooltip />
|
||
<el-table-column label="L1设定值ID" prop="l1SetValueId" show-overflow-tooltip />
|
||
<el-table-column label="设定值" prop="targetValue" align="center" />
|
||
<el-table-column label="下限" prop="lowerLimit" align="center" />
|
||
<el-table-column label="上限" prop="upperLimit" align="center" />
|
||
<el-table-column label="操作" align="right">
|
||
<template slot-scope="{ row }">
|
||
<el-button type="text" size="mini" @click.stop="openPlanDialog(row)">编辑</el-button>
|
||
<el-button type="text" size="mini" @click.stop="openParamDialog(row)">参数</el-button>
|
||
<el-button type="text" size="mini" class="btn-danger" @click.stop="removePlan(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 方案参数面板 -->
|
||
<template v-if="selectedPlan">
|
||
<div class="param-header">
|
||
<span>{{ selectedPlan.pointName || selectedPlan.pointCode }} — 参数</span>
|
||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openParamDialog()">新建参数</el-button>
|
||
</div>
|
||
<el-table v-loading="paramLoading" :data="paramList" size="small">
|
||
<el-table-column label="参数编码" prop="paramCode" show-overflow-tooltip />
|
||
<el-table-column label="参数名称" prop="paramName" show-overflow-tooltip />
|
||
<el-table-column label="设定值" prop="targetValue" align="center" />
|
||
<el-table-column label="下限" prop="lowerLimit" align="center" />
|
||
<el-table-column label="上限" prop="upperLimit" align="center" />
|
||
<el-table-column label="单位" prop="unit" align="center" />
|
||
<el-table-column label="操作" align="right">
|
||
<template slot-scope="{ row }">
|
||
<el-button type="text" size="mini" @click="openParamDialog(null, row)">编辑</el-button>
|
||
<el-button type="text" size="mini" class="btn-danger" @click="removeParam(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 方案点位 dialog -->
|
||
<el-dialog :title="planTitle" :visible.sync="planOpen" width="520px" append-to-body @close="planForm = {}">
|
||
<el-form ref="planFormRef" :model="planForm" :rules="planRules" label-width="90px" size="small">
|
||
<el-form-item label="段类型" prop="segmentType">
|
||
<el-select v-model="planForm.segmentType" style="width:100%">
|
||
<el-option v-for="s in segmentFormOptionsForDialog" :key="String(s.value)" :label="s.label" :value="s.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="段名称" prop="segmentName">
|
||
<el-input v-model="planForm.segmentName" maxlength="100" />
|
||
</el-form-item>
|
||
<el-form-item label="点位名称" prop="pointName">
|
||
<el-input v-model="planForm.pointName" maxlength="200" />
|
||
</el-form-item>
|
||
<el-form-item label="点位编码" prop="pointCode">
|
||
<el-input v-model="planForm.pointCode" maxlength="64" />
|
||
</el-form-item>
|
||
<el-form-item label="实际值ID" prop="actualValueId">
|
||
<el-input v-model="planForm.actualValueId" maxlength="64" />
|
||
</el-form-item>
|
||
<el-form-item label="L1设定值ID" prop="l1SetValueId">
|
||
<el-input v-model="planForm.l1SetValueId" maxlength="64" />
|
||
</el-form-item>
|
||
<el-form-item label="设定值" prop="targetValue">
|
||
<el-input v-model="planForm.targetValue" />
|
||
</el-form-item>
|
||
<el-form-item label="下限" prop="lowerLimit">
|
||
<el-input v-model="planForm.lowerLimit" />
|
||
</el-form-item>
|
||
<el-form-item label="上限" prop="upperLimit">
|
||
<el-input v-model="planForm.upperLimit" />
|
||
</el-form-item>
|
||
<el-form-item label="排序">
|
||
<el-input-number v-model="planForm.sortOrder" :min="0" controls-position="right" style="width:100%" />
|
||
</el-form-item>
|
||
<el-form-item label="备注">
|
||
<el-input v-model="planForm.remark" type="textarea" rows="2" maxlength="500" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer">
|
||
<el-button size="small" @click="planOpen = false">取消</el-button>
|
||
<el-button size="small" type="primary" :loading="planSubmitLoading" @click="submitPlan">确定</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 方案参数 dialog -->
|
||
<el-dialog :title="paramTitle" :visible.sync="paramOpen" width="480px" append-to-body @close="paramForm = {}">
|
||
<el-form ref="paramFormRef" :model="paramForm" :rules="paramRules" label-width="90px" size="small">
|
||
<el-form-item label="参数编码" prop="paramCode">
|
||
<el-input v-model="paramForm.paramCode" maxlength="64" />
|
||
</el-form-item>
|
||
<el-form-item label="参数名称" prop="paramName">
|
||
<el-input v-model="paramForm.paramName" maxlength="200" />
|
||
</el-form-item>
|
||
<el-form-item label="设定值">
|
||
<el-input v-model="paramForm.targetValue" />
|
||
</el-form-item>
|
||
<el-form-item label="下限">
|
||
<el-input v-model="paramForm.lowerLimit" />
|
||
</el-form-item>
|
||
<el-form-item label="上限">
|
||
<el-input v-model="paramForm.upperLimit" />
|
||
</el-form-item>
|
||
<el-form-item label="单位">
|
||
<el-input v-model="paramForm.unit" maxlength="32" />
|
||
</el-form-item>
|
||
<el-form-item label="备注">
|
||
<el-input v-model="paramForm.remark" type="textarea" rows="2" maxlength="500" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer">
|
||
<el-button size="small" @click="paramOpen = false">取消</el-button>
|
||
<el-button size="small" type="primary" :loading="paramSubmitLoading" @click="submitParam">确定</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { listProcessPlan, addProcessPlan, updateProcessPlan, delProcessPlan } from '@/api/wms/processPlan'
|
||
import { listProcessPlanParam, addProcessPlanParam, updateProcessPlanParam, delProcessPlanParam } from '@/api/wms/processPlanParam'
|
||
|
||
/** 表单内可选段类型(新建/编辑仍支持全部枚举) */
|
||
const SEGMENT_FORM_OPTIONS = [
|
||
{ label: '入口段', value: 'INLET' },
|
||
{ label: '工艺段', value: 'PROCESS' },
|
||
{ label: '出口段', value: 'OUTLET' }
|
||
]
|
||
|
||
export default {
|
||
name: 'ProcessSpecPlanSpec',
|
||
data() {
|
||
return {
|
||
pageLoading: false,
|
||
versionId: undefined,
|
||
versionCode: '',
|
||
specId: undefined,
|
||
configMode: 'configurable',
|
||
/** 左侧:段类型;空=全部 */
|
||
activeSegmentType: '',
|
||
/** 工具栏下拉:当前段类型下的段名称;空字符串=该段类型下全部名称;__EMPTY__=未命名 */
|
||
activeSegmentName: '',
|
||
filterName: '',
|
||
appliedFilterName: '',
|
||
planList: [],
|
||
planLoading: false,
|
||
selectedPlan: null,
|
||
paramList: [],
|
||
paramLoading: false,
|
||
planOpen: false,
|
||
planTitle: '',
|
||
planSubmitLoading: false,
|
||
planForm: {},
|
||
planRules: {
|
||
segmentType: [{ required: true, message: '请选择段类型', trigger: 'change' }],
|
||
pointName: [{ required: true, message: '点位名称不能为空', trigger: 'blur' }],
|
||
pointCode: [{ required: true, message: '点位编码不能为空', trigger: 'blur' }]
|
||
},
|
||
paramOpen: false,
|
||
paramTitle: '',
|
||
paramSubmitLoading: false,
|
||
paramForm: {},
|
||
paramRules: {
|
||
paramCode: [{ required: true, message: '参数编码不能为空', trigger: 'blur' }],
|
||
paramName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }]
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
/**
|
||
* 左侧 Tab:仅从点位数据汇总「段类型」,展示用语义名称(入口段/工艺段/出口段)
|
||
*/
|
||
segmentTypeTabOptions() {
|
||
const orderIdx = {}
|
||
SEGMENT_FORM_OPTIONS.forEach((s, i) => { orderIdx[String(s.value)] = i })
|
||
const seen = new Set()
|
||
const list = []
|
||
for (const plan of this.planList) {
|
||
const t = plan.segmentType
|
||
if (t === undefined || t === null || String(t).trim() === '') continue
|
||
const key = String(t)
|
||
if (seen.has(key)) continue
|
||
seen.add(key)
|
||
list.push({
|
||
value: plan.segmentType,
|
||
label: this.segmentTypeDisplayLabel(t)
|
||
})
|
||
}
|
||
list.sort((a, b) => {
|
||
const ia = orderIdx[String(a.value)]
|
||
const ib = orderIdx[String(b.value)]
|
||
const aKnown = ia !== undefined
|
||
const bKnown = ib !== undefined
|
||
if (aKnown && bKnown) return ia - ib
|
||
if (aKnown) return -1
|
||
if (bKnown) return 1
|
||
return String(a.label).localeCompare(String(b.label), 'zh-CN')
|
||
})
|
||
return list
|
||
},
|
||
/** 当前选中段类型下,出现于数据中的段名称下拉项 */
|
||
segmentNameFilterOptions() {
|
||
const t = this.activeSegmentType
|
||
if (t === '' || t === undefined || t === null) return [{ value: '', label: '全部' }]
|
||
const names = new Set()
|
||
let hasEmpty = false
|
||
for (const p of this.planList) {
|
||
if (String(p.segmentType) !== String(t)) continue
|
||
const sn = p.segmentName != null ? String(p.segmentName).trim() : ''
|
||
if (!sn) hasEmpty = true
|
||
else names.add(sn)
|
||
}
|
||
const sorted = [...names].sort((a, b) => a.localeCompare(b, 'zh-CN'))
|
||
const opts = [{ value: '', label: '全部' }]
|
||
if (hasEmpty) opts.push({ value: '__EMPTY__', label: '(未命名)' })
|
||
sorted.forEach(n => opts.push({ value: n, label: n }))
|
||
return opts
|
||
},
|
||
/** 新建/编辑下拉:标准三项 + 当前方案中已出现的其它段类型 */
|
||
segmentFormOptionsForDialog() {
|
||
const seen = new Set(SEGMENT_FORM_OPTIONS.map(s => String(s.value)))
|
||
const extra = []
|
||
for (const plan of this.planList) {
|
||
const v = plan.segmentType
|
||
if (v === undefined || v === null || String(v).trim() === '') continue
|
||
const key = String(v)
|
||
if (seen.has(key)) continue
|
||
seen.add(key)
|
||
const name = plan.segmentName != null ? String(plan.segmentName).trim() : ''
|
||
extra.push({
|
||
value: plan.segmentType,
|
||
label: name || this.segmentTypeDisplayLabel(v)
|
||
})
|
||
}
|
||
extra.sort((a, b) => String(a.label).localeCompare(String(b.label), 'zh-CN'))
|
||
return [...SEGMENT_FORM_OPTIONS, ...extra]
|
||
},
|
||
filteredPlans() {
|
||
const type = this.activeSegmentType
|
||
const sub = this.activeSegmentName
|
||
return this.planList.filter(p => {
|
||
const typeOk =
|
||
type === '' || type === undefined || type === null
|
||
? true
|
||
: String(p.segmentType) === String(type)
|
||
let nameOkSeg = true
|
||
if (typeOk && type !== '' && type !== undefined && type !== null) {
|
||
if (sub === '' || sub === undefined || sub === null) {
|
||
nameOkSeg = true
|
||
} else if (sub === '__EMPTY__') {
|
||
const sn = p.segmentName != null ? String(p.segmentName).trim() : ''
|
||
nameOkSeg = !sn
|
||
} else {
|
||
const sn = p.segmentName != null ? String(p.segmentName).trim() : ''
|
||
nameOkSeg = sn === String(sub).trim()
|
||
}
|
||
}
|
||
const nameOk = !this.appliedFilterName || (p.pointName || '').includes(this.appliedFilterName)
|
||
return typeOk && nameOkSeg && nameOk
|
||
})
|
||
}
|
||
},
|
||
watch: {
|
||
$route: { immediate: true, handler() { this.syncFromRoute() } },
|
||
segmentTypeTabOptions: {
|
||
handler() {
|
||
const a = this.activeSegmentType
|
||
if (a === '' || a === undefined || a === null) return
|
||
const still = this.segmentTypeTabOptions.some(s => String(s.value) === String(a))
|
||
if (!still) {
|
||
this.activeSegmentType = ''
|
||
this.activeSegmentName = ''
|
||
}
|
||
}
|
||
},
|
||
planList: {
|
||
handler() {
|
||
this.$nextTick(() => {
|
||
if (this.activeSegmentType === '' || this.activeSegmentType === undefined || this.activeSegmentType === null) return
|
||
const allowed = this.segmentNameFilterOptions.map(o => o.value)
|
||
const cur = this.activeSegmentName
|
||
if (cur !== '' && cur !== undefined && cur !== null && !allowed.includes(cur)) {
|
||
this.activeSegmentName = ''
|
||
}
|
||
})
|
||
},
|
||
deep: true
|
||
}
|
||
},
|
||
methods: {
|
||
syncFromRoute() {
|
||
const q = this.$route.query
|
||
this.versionId = q.versionId || undefined
|
||
this.versionCode = q.versionCode || ''
|
||
this.specId = q.specId || undefined
|
||
if (this.versionId) this.loadPlans()
|
||
},
|
||
goBack() { this.$router.go(-1) },
|
||
selectSegmentType(val) {
|
||
this.activeSegmentType = val === undefined || val === null ? '' : val
|
||
this.activeSegmentName = ''
|
||
},
|
||
onSegmentNameFilterChange() {
|
||
const allowed = this.segmentNameFilterOptions.map(o => o.value)
|
||
if (!allowed.includes(this.activeSegmentName)) {
|
||
this.activeSegmentName = ''
|
||
}
|
||
},
|
||
segmentTypeDisplayLabel(segmentType) {
|
||
const hit = SEGMENT_FORM_OPTIONS.find(s => String(s.value) === String(segmentType))
|
||
return hit ? hit.label : (segmentType != null ? String(segmentType) : '')
|
||
},
|
||
segLabel(val) {
|
||
return this.segmentTypeDisplayLabel(val)
|
||
},
|
||
loadPlans() {
|
||
this.planLoading = true
|
||
this.selectedPlan = null
|
||
this.paramList = []
|
||
listProcessPlan({ versionId: this.versionId, pageNum: 1, pageSize: 500 }).then(res => {
|
||
this.planList = res.rows || []
|
||
}).catch(e => console.error(e)).finally(() => { this.planLoading = false })
|
||
},
|
||
loadParams(planId) {
|
||
this.paramLoading = true
|
||
listProcessPlanParam({ planId, pageNum: 1, pageSize: 500 }).then(res => {
|
||
this.paramList = res.rows || []
|
||
}).catch(e => console.error(e)).finally(() => { this.paramLoading = false })
|
||
},
|
||
onPlanSelect(row) {
|
||
if (!row) return
|
||
this.selectedPlan = row
|
||
this.loadParams(row.planId)
|
||
},
|
||
applyFilter() { this.appliedFilterName = this.filterName },
|
||
resetFilter() {
|
||
this.filterName = ''
|
||
this.appliedFilterName = ''
|
||
this.activeSegmentName = ''
|
||
},
|
||
openPlanDialog(row) {
|
||
this.planForm = row
|
||
? { ...row }
|
||
: {
|
||
versionId: this.versionId,
|
||
segmentType: 'PROCESS',
|
||
segmentName: undefined,
|
||
pointName: undefined,
|
||
pointCode: undefined,
|
||
actualValueId: undefined,
|
||
l1SetValueId: undefined,
|
||
targetValue: undefined,
|
||
lowerLimit: undefined,
|
||
upperLimit: undefined,
|
||
sortOrder: 0,
|
||
remark: undefined
|
||
}
|
||
this.planTitle = row ? '编辑方案点位' : '新建方案点位'
|
||
this.planOpen = true
|
||
this.$nextTick(() => this.$refs.planFormRef && this.$refs.planFormRef.clearValidate())
|
||
},
|
||
submitPlan() {
|
||
this.$refs.planFormRef.validate(ok => {
|
||
if (!ok) return
|
||
this.planSubmitLoading = true
|
||
const req = this.planForm.planId ? updateProcessPlan(this.planForm) : addProcessPlan(this.planForm)
|
||
req.then(() => {
|
||
this.$modal.msgSuccess('保存成功')
|
||
this.planOpen = false
|
||
this.loadPlans()
|
||
}).catch(e => console.error(e)).finally(() => { this.planSubmitLoading = false })
|
||
})
|
||
},
|
||
removePlan(row) {
|
||
this.$modal.confirm('确认删除该方案点位?').then(() => {
|
||
return delProcessPlan(row.planId)
|
||
}).then(() => {
|
||
this.$modal.msgSuccess('删除成功')
|
||
if (this.selectedPlan && this.selectedPlan.planId === row.planId) {
|
||
this.selectedPlan = null
|
||
this.paramList = []
|
||
}
|
||
this.loadPlans()
|
||
}).catch(() => {})
|
||
},
|
||
openParamDialog(planRow, paramRow) {
|
||
const targetPlan = planRow || this.selectedPlan
|
||
if (!targetPlan) { this.$message.warning('请先选择一个方案点位'); return }
|
||
if (!this.selectedPlan || this.selectedPlan.planId !== targetPlan.planId) {
|
||
this.selectedPlan = targetPlan
|
||
this.loadParams(targetPlan.planId)
|
||
}
|
||
this.paramForm = paramRow
|
||
? { ...paramRow }
|
||
: {
|
||
planId: targetPlan.planId,
|
||
paramCode: undefined,
|
||
paramName: undefined,
|
||
targetValue: undefined,
|
||
lowerLimit: undefined,
|
||
upperLimit: undefined,
|
||
unit: undefined,
|
||
remark: undefined
|
||
}
|
||
this.paramTitle = paramRow ? '编辑方案参数' : '新建方案参数'
|
||
this.paramOpen = true
|
||
this.$nextTick(() => this.$refs.paramFormRef && this.$refs.paramFormRef.clearValidate())
|
||
},
|
||
submitParam() {
|
||
this.$refs.paramFormRef.validate(ok => {
|
||
if (!ok) return
|
||
this.paramSubmitLoading = true
|
||
const req = this.paramForm.paramId ? updateProcessPlanParam(this.paramForm) : addProcessPlanParam(this.paramForm)
|
||
req.then(() => {
|
||
this.$modal.msgSuccess('保存成功')
|
||
this.paramOpen = false
|
||
this.loadParams(this.selectedPlan.planId)
|
||
}).catch(e => console.error(e)).finally(() => { this.paramSubmitLoading = false })
|
||
})
|
||
},
|
||
removeParam(row) {
|
||
this.$modal.confirm('确认删除该参数?').then(() => {
|
||
return delProcessPlanParam(row.paramId)
|
||
}).then(() => {
|
||
this.$modal.msgSuccess('删除成功')
|
||
this.loadParams(this.selectedPlan.planId)
|
||
}).catch(() => {})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.plan-spec-page {
|
||
padding: 16px 20px;
|
||
|
||
min-height: 100%;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.version-badge {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
background: #f0f2f5;
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.config-tabs {
|
||
display: flex;
|
||
gap: 0;
|
||
margin-bottom: 14px;
|
||
border: 1px solid #dcdfe6;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
width: fit-content;
|
||
}
|
||
|
||
.config-tab {
|
||
padding: 5px 20px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
color: #606266;
|
||
background: #fff;
|
||
border: none;
|
||
border-right: 1px solid #dcdfe6;
|
||
transition: color .15s, background .15s;
|
||
user-select: none;
|
||
line-height: 1;
|
||
}
|
||
|
||
.config-tab:last-child { border-right: none; }
|
||
.config-tab:hover { color: #5F7BA0; }
|
||
|
||
.config-tab.active {
|
||
color: #fff;
|
||
background: #5F7BA0;
|
||
}
|
||
|
||
.main-layout {
|
||
display: flex;
|
||
gap: 0;
|
||
background: #fff;
|
||
border-radius: 4px;
|
||
border: 1px solid #ebeef5;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.left-tree {
|
||
width: 120px;
|
||
flex-shrink: 0;
|
||
border-right: 1px solid #ebeef5;
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.tree-item {
|
||
padding: 9px 16px;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
color: #606266;
|
||
transition: all .15s;
|
||
user-select: none;
|
||
}
|
||
|
||
.tree-item:hover { background: #f5f7fa; }
|
||
|
||
.tree-item.active {
|
||
background: #5F7BA0;
|
||
color: #fff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tree-item.active .tree-arrow { color: rgba(255,255,255,.6); }
|
||
|
||
.tree-arrow {
|
||
margin-right: 4px;
|
||
color: #c0c4cc;
|
||
}
|
||
|
||
.right-content {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding: 12px;
|
||
}
|
||
|
||
.search-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.search-label {
|
||
font-size: 13px;
|
||
color: #606266;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.param-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 0 8px;
|
||
margin-top: 12px;
|
||
border-top: 1px solid #ebeef5;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #606266;
|
||
}
|
||
|
||
.el-table { border-radius: 0; }
|
||
|
||
::v-deep .el-button--primary {
|
||
color: #fff !important;
|
||
background: #5F7BA0 !important;
|
||
border-color: #5F7BA0 !important;
|
||
}
|
||
::v-deep .el-button--primary:hover,
|
||
::v-deep .el-button--primary:focus { background: #4d6a8e !important; border-color: #4d6a8e !important; }
|
||
::v-deep .el-button--primary:active { background: #4a6585 !important; border-color: #4a6585 !important; }
|
||
::v-deep .el-button--primary.is-disabled { opacity: .5; }
|
||
|
||
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger) {
|
||
color: #606266 !important;
|
||
background: #fff !important;
|
||
border-color: #dcdfe6 !important;
|
||
}
|
||
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger):hover {
|
||
color: #5F7BA0 !important;
|
||
border-color: #5F7BA0 !important;
|
||
}
|
||
|
||
::v-deep .el-button--text { background: transparent !important; border-color: transparent !important; }
|
||
::v-deep .el-button--text.btn-danger { color: #f56c6c !important; }
|
||
|
||
.btn-danger { color: #f56c6c; }
|
||
</style>
|