|
|
|
|
@ -1,124 +1,172 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="app-container">
|
|
|
|
|
<el-form :model="queryParams" ref="queryFormRef" size="small" :inline="true" v-show="showSearch" label-width="100px">
|
|
|
|
|
<el-form-item label="规则名称" prop="ruleName">
|
|
|
|
|
<el-input v-model="queryParams.ruleName" placeholder="请输入规则名称" clearable @keyup.enter="handleQuery" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="触发规则" prop="triggerRule">
|
|
|
|
|
<el-select v-model="queryParams.triggerRule" placeholder="请选择触发规则" clearable>
|
|
|
|
|
<el-option v-for="dict in dict.type.trigger_rule" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="启用状态" prop="isEnable">
|
|
|
|
|
<el-select v-model="queryParams.isEnable" placeholder="请选择启用状态" clearable>
|
|
|
|
|
<el-option v-for="dict in dict.type.is_flag" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<!-- <el-form-item label="通知用户" prop="notifyUser">-->
|
|
|
|
|
<!-- <el-input-->
|
|
|
|
|
<!-- v-model="queryParams.notifyUser"-->
|
|
|
|
|
<!-- placeholder="请输入通知用户"-->
|
|
|
|
|
<!-- clearable-->
|
|
|
|
|
<!-- @keyup.enter="handleQuery"-->
|
|
|
|
|
<!-- />-->
|
|
|
|
|
<!-- </el-form-item>-->
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
|
|
|
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<div class="app-container page-shell">
|
|
|
|
|
<section class="page-hero">
|
|
|
|
|
<div class="hero-copy">
|
|
|
|
|
<span class="hero-eyebrow">ALARM RULE STUDIO</span>
|
|
|
|
|
<h2 class="hero-title">异常告警规则与处置措施工作台</h2>
|
|
|
|
|
<p class="hero-desc">把触发阈值、恢复区间和处置步骤聚合在一个界面中,帮助运维团队更快定位规则、编排措施并完成闭环维护。</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="hero-stats">
|
|
|
|
|
<article v-for="item in overviewStats" :key="item.label" class="stat-card">
|
|
|
|
|
<span class="stat-label">{{ item.label }}</span>
|
|
|
|
|
<strong class="stat-value">{{ item.value }}</strong>
|
|
|
|
|
<span class="stat-hint">{{ item.hint }}</span>
|
|
|
|
|
</article>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="10" class="mb8">
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['ems/record:recordAlarmRule:add']"
|
|
|
|
|
>新增
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
<el-button
|
|
|
|
|
type="success"
|
|
|
|
|
plain
|
|
|
|
|
icon="el-icon-edit"
|
|
|
|
|
size="mini"
|
|
|
|
|
:disabled="single"
|
|
|
|
|
@click="handleUpdate"
|
|
|
|
|
v-hasPermi="['ems/record:recordAlarmRule:edit']"
|
|
|
|
|
>修改
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
<el-button
|
|
|
|
|
type="danger"
|
|
|
|
|
plain
|
|
|
|
|
icon="el-icon-delete"
|
|
|
|
|
size="mini"
|
|
|
|
|
:disabled="multiple"
|
|
|
|
|
@click="handleDelete"
|
|
|
|
|
v-hasPermi="['ems/record:recordAlarmRule:remove']"
|
|
|
|
|
>删除
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['ems/record:recordAlarmRule:export']"
|
|
|
|
|
>导出
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
|
<right-toolbar v-model:show-search="showSearch" @query-table="getList" :columns="columns"></right-toolbar>
|
|
|
|
|
</el-row>
|
|
|
|
|
<section v-show="showSearch" class="panel-shell search-shell">
|
|
|
|
|
<div class="panel-head">
|
|
|
|
|
<div>
|
|
|
|
|
<h3>规则筛选</h3>
|
|
|
|
|
<p>按规则名称、触发逻辑和启用状态快速定位目标告警规则。</p>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="panel-badge">规则检索</span>
|
|
|
|
|
</div>
|
|
|
|
|
<el-form :model="queryParams" ref="queryFormRef" size="small" :inline="true" label-width="100px" class="query-form">
|
|
|
|
|
<el-form-item label="规则名称" prop="ruleName">
|
|
|
|
|
<el-input v-model="queryParams.ruleName" placeholder="请输入规则名称" clearable @keyup.enter="handleQuery" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="触发规则" prop="triggerRule">
|
|
|
|
|
<el-select v-model="queryParams.triggerRule" placeholder="请选择触发规则" clearable>
|
|
|
|
|
<el-option v-for="dict in dict.type.trigger_rule" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="启用状态" prop="isEnable">
|
|
|
|
|
<el-select v-model="queryParams.isEnable" placeholder="请选择启用状态" clearable>
|
|
|
|
|
<el-option v-for="dict in dict.type.is_flag" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="通知组" prop="notifyGroupId">
|
|
|
|
|
<el-select v-model="queryParams.notifyGroupId" placeholder="请选择通知组" clearable filterable style="width: 220px">
|
|
|
|
|
<el-option v-for="group in groupOptions" :key="group.id" :label="group.groupName" :value="group.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item class="query-actions">
|
|
|
|
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
|
|
|
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<el-table v-loading="loading" :data="recordAlarmRuleList" @selection-change="handleSelectionChange">
|
|
|
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
|
|
|
<el-table-column label="序号" type="index" width="60" align="center" v-if="columns[0].visible" />
|
|
|
|
|
<el-table-column label="规则名称" align="center" prop="ruleName" v-if="columns[1].visible" />
|
|
|
|
|
<el-table-column label="计量设备" align="center" prop="monitorName" v-if="columns[2].visible" />
|
|
|
|
|
<el-table-column label="触发规则" align="center" prop="triggerRule" v-if="columns[3].visible">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<dict-tag :options="dict.type.trigger_rule" :value="scope.row.triggerRule" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="监测字段" align="center" prop="monitorField" v-if="columns[4].visible">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<dict-tag :options="dict.type.monitor_field" :value="scope.row.monitorField" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="告警上限" align="center" prop="alarmUpper" v-if="columns[5].visible" />
|
|
|
|
|
<el-table-column label="告警下限" align="center" prop="alarmLower" v-if="columns[6].visible" />
|
|
|
|
|
<el-table-column label="恢复上限" align="center" prop="recoverUpper" v-if="columns[7].visible" />
|
|
|
|
|
<el-table-column label="恢复下限" align="center" prop="recoverLower" v-if="columns[8].visible" />
|
|
|
|
|
<el-table-column label="回差" align="center" prop="hysteresis" v-if="columns[9].visible" />
|
|
|
|
|
<el-table-column label="持续秒数" align="center" prop="durationSec" v-if="columns[10].visible" />
|
|
|
|
|
<el-table-column label="告警级别" align="center" prop="alarmLevel" v-if="columns[11].visible" />
|
|
|
|
|
<el-table-column label="启用状态" align="center" prop="isEnable" v-if="columns[12].visible">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<dict-tag :options="dict.type.is_flag" :value="scope.row.isEnable" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="备注" align="center" prop="cause" v-if="columns[13].visible" />
|
|
|
|
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-button
|
|
|
|
|
size="mini"
|
|
|
|
|
type="text"
|
|
|
|
|
icon="el-icon-setting"
|
|
|
|
|
@click="handleActionSteps(scope.row)"
|
|
|
|
|
v-hasPermi="['ems/record:recordAlarmRule:edit']"
|
|
|
|
|
>措施
|
|
|
|
|
<section class="panel-shell table-shell">
|
|
|
|
|
<div class="panel-head">
|
|
|
|
|
<div>
|
|
|
|
|
<h3>规则列表</h3>
|
|
|
|
|
<p>保持原有批量操作与导出能力,同时通过更清晰的层次强化巡检效率。</p>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="panel-badge warm">措施联动</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="notify-entry-bar">
|
|
|
|
|
<div class="notify-entry-text">通知组与通知成员的配置入口已经接回规则页,保存时会同步回填通知对象摘要,避免规则挂空通知链。</div>
|
|
|
|
|
<div class="notify-entry-actions">
|
|
|
|
|
<el-button link type="primary" @click="openNotifyGroupPage">配置通知组</el-button>
|
|
|
|
|
<el-button link type="primary" @click="openNotifyUserPage">配置通知成员</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="10" class="toolbar-row">
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['ems/record:recordAlarmRule:add']"
|
|
|
|
|
>新增
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems/record:recordAlarmRule:edit']"
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
<el-button
|
|
|
|
|
type="success"
|
|
|
|
|
plain
|
|
|
|
|
icon="el-icon-edit"
|
|
|
|
|
size="mini"
|
|
|
|
|
:disabled="single"
|
|
|
|
|
@click="handleUpdate"
|
|
|
|
|
v-hasPermi="['ems/record:recordAlarmRule:edit']"
|
|
|
|
|
>修改
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['ems/record:recordAlarmRule:remove']"
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
<el-button
|
|
|
|
|
type="danger"
|
|
|
|
|
plain
|
|
|
|
|
icon="el-icon-delete"
|
|
|
|
|
size="mini"
|
|
|
|
|
:disabled="multiple"
|
|
|
|
|
@click="handleDelete"
|
|
|
|
|
v-hasPermi="['ems/record:recordAlarmRule:remove']"
|
|
|
|
|
>删除
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['ems/record:recordAlarmRule:export']"
|
|
|
|
|
>导出
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
|
<right-toolbar v-model:show-search="showSearch" @query-table="getList" :columns="columns"></right-toolbar>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
|
|
|
<el-table v-loading="loading" :data="recordAlarmRuleList" @selection-change="handleSelectionChange" class="data-table">
|
|
|
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
|
|
|
<el-table-column label="序号" type="index" width="60" align="center" v-if="columns[0].visible" />
|
|
|
|
|
<el-table-column label="规则名称" align="center" prop="ruleName" v-if="columns[1].visible" />
|
|
|
|
|
<el-table-column label="计量设备" align="center" prop="monitorName" v-if="columns[2].visible" />
|
|
|
|
|
<el-table-column label="触发规则" align="center" prop="triggerRule" v-if="columns[3].visible">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<dict-tag :options="dict.type.trigger_rule" :value="scope.row.triggerRule" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="监测字段" align="center" prop="monitorField" v-if="columns[4].visible">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<dict-tag :options="dict.type.monitor_field" :value="scope.row.monitorField" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="告警上限" align="center" prop="alarmUpper" v-if="columns[5].visible" />
|
|
|
|
|
<el-table-column label="告警下限" align="center" prop="alarmLower" v-if="columns[6].visible" />
|
|
|
|
|
<el-table-column label="恢复上限" align="center" prop="recoverUpper" v-if="columns[7].visible" />
|
|
|
|
|
<el-table-column label="恢复下限" align="center" prop="recoverLower" v-if="columns[8].visible" />
|
|
|
|
|
<el-table-column label="回差" align="center" prop="hysteresis" v-if="columns[9].visible" />
|
|
|
|
|
<el-table-column label="持续秒数" align="center" prop="durationSec" v-if="columns[10].visible" />
|
|
|
|
|
<el-table-column label="告警级别" align="center" prop="alarmLevel" v-if="columns[11].visible" />
|
|
|
|
|
<el-table-column label="启用状态" align="center" prop="isEnable" v-if="columns[12].visible">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<dict-tag :options="dict.type.is_flag" :value="scope.row.isEnable" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="备注" align="center" prop="cause" v-if="columns[13].visible" />
|
|
|
|
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-button
|
|
|
|
|
size="mini"
|
|
|
|
|
type="text"
|
|
|
|
|
icon="el-icon-setting"
|
|
|
|
|
@click="handleActionSteps(scope.row)"
|
|
|
|
|
v-hasPermi="['ems/record:recordAlarmRule:edit']"
|
|
|
|
|
>措施
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems/record:recordAlarmRule:edit']"
|
|
|
|
|
>修改
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
size="mini"
|
|
|
|
|
type="text"
|
|
|
|
|
icon="el-icon-delete"
|
|
|
|
|
@click="handleDelete(scope.row)"
|
|
|
|
|
v-hasPermi="['ems/record:recordAlarmRule:remove']"
|
|
|
|
|
>删除
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- 添加或修改异常告警规则对话框 -->
|
|
|
|
|
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
|
|
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="150px">
|
|
|
|
|
<el-dialog :title="title" v-model="open" width="500px" append-to-body class="themed-dialog">
|
|
|
|
|
<div class="dialog-tip">
|
|
|
|
|
<span class="tip-dot"></span>
|
|
|
|
|
规则页继续沿用现有字段提交逻辑,只在视觉上突出阈值、恢复区间和设备绑定关系,避免误改业务能力。
|
|
|
|
|
</div>
|
|
|
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="150px" class="dialog-form">
|
|
|
|
|
<!-- <el-form-item label="规则编号" prop="ruleId">
|
|
|
|
|
<el-input v-model="form.ruleId" placeholder="请输入规则编号" disabled/>
|
|
|
|
|
</el-form-item> -->
|
|
|
|
|
@ -177,14 +225,20 @@
|
|
|
|
|
<el-form-item label="告警级别" prop="alarmLevel">
|
|
|
|
|
<el-input v-model="form.alarmLevel" placeholder="请输入告警级别" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="通知组" prop="notifyGroupId">
|
|
|
|
|
<el-select v-model="form.notifyGroupId" placeholder="请选择通知组" clearable filterable style="width: 100%" @change="handleNotifyGroupChange">
|
|
|
|
|
<el-option v-for="group in groupOptions" :key="group.id" :label="group.groupName" :value="group.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="通知对象摘要" prop="notifyUser">
|
|
|
|
|
<el-input v-model="form.notifyUser" type="textarea" :rows="2" placeholder="可手工补充通知对象;选择通知组后会自动回填成员摘要" />
|
|
|
|
|
<div class="field-tip">这里保留 notifyUser 摘要字段,是为了兼容旧台账展示;后端会以 notifyGroupId 作为通知配置的主校验入口。</div>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="启用状态" prop="isEnable">
|
|
|
|
|
<el-radio-group v-model="form.isEnable">
|
|
|
|
|
<el-radio v-for="dict in dict.type.is_flag" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<!-- <el-form-item label="通知用户" prop="notifyUser">-->
|
|
|
|
|
<!-- <el-input v-model="form.notifyUser" placeholder="请输入通知用户"/>-->
|
|
|
|
|
<!-- </el-form-item>-->
|
|
|
|
|
<el-form-item label="备注" prop="cause">
|
|
|
|
|
<el-input v-model="form.cause" placeholder="请输入备注" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
@ -198,8 +252,12 @@
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 措施管理对话框 -->
|
|
|
|
|
<el-dialog :title="actionStepsTitle" v-model="actionStepsOpen" width="1200px" append-to-body>
|
|
|
|
|
<el-dialog :title="actionStepsTitle" v-model="actionStepsOpen" width="1200px" append-to-body class="themed-dialog action-dialog">
|
|
|
|
|
<div class="action-steps-container">
|
|
|
|
|
<div class="dialog-tip soft">
|
|
|
|
|
<span class="tip-dot"></span>
|
|
|
|
|
处置步骤支持顺序编排、图片补充和批量保存,适合沉淀标准化应急 SOP。
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 步骤列表 -->
|
|
|
|
|
<div class="steps-section">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
@ -289,7 +347,7 @@
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 图片预览对话框 -->
|
|
|
|
|
<el-dialog title="图片预览" v-model="imagePreviewVisible" width="80%" append-to-body center>
|
|
|
|
|
<el-dialog title="图片预览" v-model="imagePreviewVisible" width="80%" append-to-body center class="themed-dialog preview-dialog">
|
|
|
|
|
<div class="image-preview-container">
|
|
|
|
|
<img :src="previewImageUrl" style="max-width: 100%; max-height: 70vh" />
|
|
|
|
|
</div>
|
|
|
|
|
@ -300,6 +358,8 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
|
|
import { useDict } from '@/utils/dict';
|
|
|
|
|
import { listAlarmNotifyGroupAll } from '@/api/ems/base/alarmNotifyGroup';
|
|
|
|
|
import { listAlarmNotifyGroupUser } from '@/api/ems/base/alarmNotifyGroupUser';
|
|
|
|
|
import {
|
|
|
|
|
listRecordAlarmRule,
|
|
|
|
|
getRecordAlarmRule,
|
|
|
|
|
@ -309,6 +369,8 @@ import {
|
|
|
|
|
} from '@/api/ems/record/recordAlarmRule';
|
|
|
|
|
import { listBaseMonitorInfo } from '@/api/ems/base/baseMonitorInfo';
|
|
|
|
|
import { getEmsAlarmActionStepsByRuleId, batchSaveActionSteps } from '@/api/ems/base/emsAlarmActionStep';
|
|
|
|
|
import type { AlarmNotifyGroupVO } from '@/api/ems/base/alarmNotifyGroup/types';
|
|
|
|
|
import type { AlarmNotifyGroupUserVO } from '@/api/ems/base/alarmNotifyGroupUser/types';
|
|
|
|
|
import { getToken } from '@/utils/auth';
|
|
|
|
|
|
|
|
|
|
defineOptions({
|
|
|
|
|
@ -410,6 +472,8 @@ const state = reactive({
|
|
|
|
|
open: false,
|
|
|
|
|
// 设备列表
|
|
|
|
|
monitorList: [],
|
|
|
|
|
groupOptions: [] as AlarmNotifyGroupVO[],
|
|
|
|
|
groupUserMap: {} as Record<string, AlarmNotifyGroupUserVO[]>,
|
|
|
|
|
selectedMonitorType: null,
|
|
|
|
|
// 新增:用于存储选中设备的 monitorType
|
|
|
|
|
// 查询参数
|
|
|
|
|
@ -419,13 +483,10 @@ const state = reactive({
|
|
|
|
|
form: createFormData(),
|
|
|
|
|
// 表单校验
|
|
|
|
|
rules: {
|
|
|
|
|
objId: [
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: '自增标识不能为空',
|
|
|
|
|
trigger: 'blur'
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
ruleName: [{ required: true, message: '规则名称不能为空', trigger: 'blur' }],
|
|
|
|
|
monitorId: [{ required: true, message: '计量设备不能为空', trigger: 'change' }],
|
|
|
|
|
triggerRule: [{ required: true, message: '触发规则不能为空', trigger: 'change' }],
|
|
|
|
|
monitorField: [{ required: true, message: '监测字段不能为空', trigger: 'change' }]
|
|
|
|
|
},
|
|
|
|
|
columns: [
|
|
|
|
|
{
|
|
|
|
|
@ -524,6 +585,7 @@ const {
|
|
|
|
|
columns,
|
|
|
|
|
currentRuleObjId,
|
|
|
|
|
form,
|
|
|
|
|
groupOptions,
|
|
|
|
|
ids,
|
|
|
|
|
imagePreviewVisible,
|
|
|
|
|
loading,
|
|
|
|
|
@ -543,6 +605,48 @@ const {
|
|
|
|
|
uploadHeaders
|
|
|
|
|
} = toRefs(state);
|
|
|
|
|
|
|
|
|
|
const resolveNotifyUsers = (groupId?: string | number | null) => {
|
|
|
|
|
if (groupId === null || groupId === undefined || groupId === '') {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
const users = state.groupUserMap[String(groupId)] || [];
|
|
|
|
|
return users
|
|
|
|
|
.map((item) => item.nickName || item.userName || item.phone || item.email)
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.join('、');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 这里补充展示型统计卡,帮助运维先看到规则覆盖面,不改变任何列表查询和保存逻辑。
|
|
|
|
|
const overviewStats = computed(() => {
|
|
|
|
|
const rules = recordAlarmRuleList.value;
|
|
|
|
|
const relatedDevices = new Set(rules.filter((item) => item.monitorId).map((item) => item.monitorId)).size;
|
|
|
|
|
const dualThresholdRules = rules.filter((item) => item.alarmUpper != null && item.alarmLower != null).length;
|
|
|
|
|
const recoveryRules = rules.filter((item) => item.recoverUpper != null || item.recoverLower != null).length;
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
label: '规则总数',
|
|
|
|
|
value: total.value || rules.length,
|
|
|
|
|
hint: '当前列表分页总量'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '关联设备',
|
|
|
|
|
value: relatedDevices,
|
|
|
|
|
hint: '已绑定计量设备'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '双阈值规则',
|
|
|
|
|
value: dualThresholdRules,
|
|
|
|
|
hint: '同时配置上下限'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '含恢复区间',
|
|
|
|
|
value: recoveryRules,
|
|
|
|
|
hint: '带恢复阈值约束'
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const getList = () => {
|
|
|
|
|
loading.value = true;
|
|
|
|
|
listRecordAlarmRule(queryParams.value).then((response) => {
|
|
|
|
|
@ -600,6 +704,9 @@ const handleUpdate = (row) => {
|
|
|
|
|
if (selectedMonitor) {
|
|
|
|
|
selectedMonitorType.value = selectedMonitor.monitorType;
|
|
|
|
|
}
|
|
|
|
|
if (form.value.notifyGroupId) {
|
|
|
|
|
handleNotifyGroupChange(form.value.notifyGroupId);
|
|
|
|
|
}
|
|
|
|
|
open.value = true;
|
|
|
|
|
title.value = '修改异常告警规则';
|
|
|
|
|
});
|
|
|
|
|
@ -623,6 +730,14 @@ const submitForm = () => {
|
|
|
|
|
form.value.triggerValue = form.value.alarmLower;
|
|
|
|
|
}
|
|
|
|
|
form.value.isEnable = form.value.isEnable ?? '0';
|
|
|
|
|
if (form.value.notifyGroupId) {
|
|
|
|
|
const notifySummary = resolveNotifyUsers(form.value.notifyGroupId);
|
|
|
|
|
if (!notifySummary && !form.value.notifyUser) {
|
|
|
|
|
ElMessage.warning('当前通知组没有可用成员,请先补充通知成员后再保存规则');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
form.value.notifyUser = form.value.notifyUser || notifySummary;
|
|
|
|
|
}
|
|
|
|
|
if (form.value.objId != null) {
|
|
|
|
|
updateRecordAlarmRule(form.value).then((response) => {
|
|
|
|
|
proxy?.$modal.msgSuccess('修改成功');
|
|
|
|
|
@ -670,6 +785,49 @@ const getMonitorList = () => {
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const loadNotifyGroupOptions = async () => {
|
|
|
|
|
const response = await listAlarmNotifyGroupAll();
|
|
|
|
|
groupOptions.value = (((response as any).data ?? (response as any).rows ?? []) || []) as AlarmNotifyGroupVO[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const loadNotifyGroupUsers = async () => {
|
|
|
|
|
const response = await listAlarmNotifyGroupUser({
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
pageSize: 1000
|
|
|
|
|
} as any);
|
|
|
|
|
const users = (((response as any).rows ?? (response as any).data ?? []) || []) as AlarmNotifyGroupUserVO[];
|
|
|
|
|
const nextMap: Record<string, AlarmNotifyGroupUserVO[]> = {};
|
|
|
|
|
users.forEach((item) => {
|
|
|
|
|
const groupId = item.groupId;
|
|
|
|
|
if (groupId === null || groupId === undefined || groupId === '') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const key = String(groupId);
|
|
|
|
|
if (!nextMap[key]) {
|
|
|
|
|
nextMap[key] = [];
|
|
|
|
|
}
|
|
|
|
|
nextMap[key].push(item);
|
|
|
|
|
});
|
|
|
|
|
state.groupUserMap = nextMap;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleNotifyGroupChange = (groupId?: string | number | null) => {
|
|
|
|
|
if (!groupId) {
|
|
|
|
|
form.value.notifyUser = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const notifySummary = resolveNotifyUsers(groupId);
|
|
|
|
|
form.value.notifyUser = notifySummary || form.value.notifyUser;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const openNotifyGroupPage = () => {
|
|
|
|
|
proxy?.$tab.openPage('/ems/base/alarmNotifyGroup', '通知组配置');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const openNotifyUserPage = () => {
|
|
|
|
|
proxy?.$tab.openPage('/ems/base/alarmNotifyGroupUser', '通知成员配置');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleMonitorChange = (monitorId) => {
|
|
|
|
|
// monitorId这里实际传入的是monitorCode值
|
|
|
|
|
const selectedMonitor = monitorList.value.find((m) => m.monitorCode === monitorId);
|
|
|
|
|
@ -902,12 +1060,252 @@ const getFullImageUrl = (relativePath) => {
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
getList();
|
|
|
|
|
getMonitorList();
|
|
|
|
|
loadNotifyGroupOptions();
|
|
|
|
|
loadNotifyGroupUsers();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.action-steps-container {
|
|
|
|
|
.page-shell {
|
|
|
|
|
min-height: 100%;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
background:
|
|
|
|
|
radial-gradient(circle at top left, rgba(59, 130, 246, 0.16), transparent 30%),
|
|
|
|
|
radial-gradient(circle at right center, rgba(244, 114, 182, 0.12), transparent 24%),
|
|
|
|
|
linear-gradient(180deg, #f6f8ff 0%, #eef3fb 45%, #f8fafc 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-hero {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: minmax(0, 1.35fr) minmax(320px, 1fr);
|
|
|
|
|
gap: 18px;
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
padding: 30px;
|
|
|
|
|
border-radius: 28px;
|
|
|
|
|
background: linear-gradient(135deg, rgba(43, 52, 126, 0.96) 0%, rgba(30, 64, 175, 0.92) 45%, rgba(219, 39, 119, 0.82) 100%);
|
|
|
|
|
box-shadow: 0 28px 54px rgba(46, 63, 140, 0.2);
|
|
|
|
|
color: #fff;
|
|
|
|
|
position: relative;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-hero::before {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: -40px auto auto -70px;
|
|
|
|
|
width: 220px;
|
|
|
|
|
height: 220px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: rgba(255, 255, 255, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hero-copy,
|
|
|
|
|
.hero-stats {
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hero-eyebrow {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: rgba(255, 255, 255, 0.14);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
letter-spacing: 0.16em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hero-title {
|
|
|
|
|
margin: 18px 0 12px;
|
|
|
|
|
font-size: 30px;
|
|
|
|
|
line-height: 1.2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hero-desc {
|
|
|
|
|
margin: 0;
|
|
|
|
|
max-width: 680px;
|
|
|
|
|
line-height: 1.75;
|
|
|
|
|
color: rgba(255, 255, 255, 0.82);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hero-stats {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
gap: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card {
|
|
|
|
|
padding: 18px;
|
|
|
|
|
border-radius: 22px;
|
|
|
|
|
background: rgba(255, 255, 255, 0.12);
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.16);
|
|
|
|
|
backdrop-filter: blur(10px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-label,
|
|
|
|
|
.stat-hint {
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-label {
|
|
|
|
|
color: rgba(255, 255, 255, 0.72);
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
display: block;
|
|
|
|
|
margin: 10px 0 8px;
|
|
|
|
|
font-size: 30px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
line-height: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-hint {
|
|
|
|
|
color: rgba(255, 255, 255, 0.72);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-shell {
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
padding: 20px 22px;
|
|
|
|
|
border-radius: 24px;
|
|
|
|
|
border: 1px solid rgba(208, 216, 237, 0.75);
|
|
|
|
|
background: rgba(255, 255, 255, 0.88);
|
|
|
|
|
box-shadow: 0 18px 38px rgba(49, 62, 124, 0.08);
|
|
|
|
|
backdrop-filter: blur(14px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-head {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-head h3 {
|
|
|
|
|
margin: 0 0 6px;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
color: #1f2a5f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-head p {
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: #6c7598;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-badge {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 8px 14px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: linear-gradient(135deg, #dbeafe, #ede9fe);
|
|
|
|
|
color: #3344a1;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-badge.warm {
|
|
|
|
|
background: linear-gradient(135deg, #fee2e2, #fce7f3);
|
|
|
|
|
color: #b42367;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.query-form {
|
|
|
|
|
padding: 18px;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
background: linear-gradient(180deg, rgba(248, 250, 255, 0.96) 0%, rgba(255, 255, 255, 0.92) 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.query-actions {
|
|
|
|
|
:deep(.el-form-item__content) {
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toolbar-row {
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
background: linear-gradient(90deg, rgba(59, 130, 246, 0.08), rgba(236, 72, 153, 0.08));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notify-entry-bar {
|
|
|
|
|
align-items: center;
|
|
|
|
|
background: rgba(249, 239, 225, 0.7);
|
|
|
|
|
border: 1px solid rgba(190, 141, 103, 0.18);
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notify-entry-text {
|
|
|
|
|
color: #6f5b51;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notify-entry-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.data-table {
|
|
|
|
|
:deep(.el-table__header-wrapper th) {
|
|
|
|
|
background: #f5f7ff;
|
|
|
|
|
color: #24336f;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-table__body tr:hover > td) {
|
|
|
|
|
background: rgba(79, 70, 229, 0.06) !important;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dialog-tip {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
padding: 12px 14px;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
background: linear-gradient(90deg, rgba(79, 70, 229, 0.08), rgba(236, 72, 153, 0.08));
|
|
|
|
|
color: #42507f;
|
|
|
|
|
line-height: 1.65;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dialog-tip.soft {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
background: linear-gradient(90deg, rgba(59, 130, 246, 0.08), rgba(16, 185, 129, 0.08));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tip-dot {
|
|
|
|
|
width: 10px;
|
|
|
|
|
height: 10px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: linear-gradient(135deg, #6366f1, #ec4899);
|
|
|
|
|
box-shadow: 0 0 0 5px rgba(99, 102, 241, 0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dialog-form {
|
|
|
|
|
padding: 18px;
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
background: #fafbff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.field-tip {
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-steps-container {
|
|
|
|
|
padding: 6px 0 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-header {
|
|
|
|
|
@ -915,14 +1313,14 @@ onMounted(() => {
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
border-bottom: 2px solid #409eff;
|
|
|
|
|
padding: 0 0 14px;
|
|
|
|
|
border-bottom: 1px solid rgba(189, 199, 226, 0.72);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: #409eff;
|
|
|
|
|
color: #31439f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty-steps {
|
|
|
|
|
@ -933,23 +1331,24 @@ onMounted(() => {
|
|
|
|
|
.steps-list {
|
|
|
|
|
.step-item {
|
|
|
|
|
margin-bottom: 30px;
|
|
|
|
|
border: 1px solid #e4e7ed;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
border: 1px solid rgba(208, 216, 237, 0.85);
|
|
|
|
|
border-radius: 22px;
|
|
|
|
|
background: linear-gradient(180deg, #ffffff 0%, #f9fbff 100%);
|
|
|
|
|
box-shadow: 0 16px 30px rgba(49, 62, 124, 0.08);
|
|
|
|
|
|
|
|
|
|
.step-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 15px 20px;
|
|
|
|
|
background: #f0f9ff;
|
|
|
|
|
border-bottom: 1px solid #e4e7ed;
|
|
|
|
|
border-radius: 8px 8px 0 0;
|
|
|
|
|
background: linear-gradient(90deg, rgba(99, 102, 241, 0.1), rgba(236, 72, 153, 0.08));
|
|
|
|
|
border-bottom: 1px solid rgba(208, 216, 237, 0.7);
|
|
|
|
|
border-radius: 22px 22px 0 0;
|
|
|
|
|
|
|
|
|
|
.step-number {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: #409eff;
|
|
|
|
|
color: #31439f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.step-controls {
|
|
|
|
|
@ -962,7 +1361,7 @@ onMounted(() => {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
color: #409eff;
|
|
|
|
|
color: #31439f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -979,6 +1378,8 @@ onMounted(() => {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 120px;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
background: linear-gradient(180deg, #f9fbff 0%, #ffffff 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.image-list {
|
|
|
|
|
@ -989,10 +1390,11 @@ onMounted(() => {
|
|
|
|
|
|
|
|
|
|
.image-item {
|
|
|
|
|
width: 200px;
|
|
|
|
|
border: 1px solid #e4e7ed;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
border: 1px solid rgba(208, 216, 237, 0.85);
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
background: white;
|
|
|
|
|
box-shadow: 0 12px 24px rgba(49, 62, 124, 0.08);
|
|
|
|
|
|
|
|
|
|
.image-preview {
|
|
|
|
|
height: 150px;
|
|
|
|
|
@ -1030,12 +1432,70 @@ onMounted(() => {
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
|
|
img {
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
box-shadow: 0 20px 40px rgba(15, 23, 42, 0.18);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dialog-footer {
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.themed-dialog .el-dialog) {
|
|
|
|
|
border-radius: 26px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
box-shadow: 0 28px 60px rgba(28, 37, 94, 0.22);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.themed-dialog .el-dialog__header) {
|
|
|
|
|
padding: 22px 24px 14px;
|
|
|
|
|
background: linear-gradient(135deg, rgba(49, 65, 163, 0.96), rgba(190, 24, 93, 0.88));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.themed-dialog .el-dialog__title),
|
|
|
|
|
:deep(.themed-dialog .el-dialog__headerbtn .el-dialog__close) {
|
|
|
|
|
color: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.themed-dialog .el-dialog__body) {
|
|
|
|
|
padding: 22px 24px;
|
|
|
|
|
background: linear-gradient(180deg, #f8faff 0%, #ffffff 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.themed-dialog .el-dialog__footer) {
|
|
|
|
|
padding: 0 24px 24px;
|
|
|
|
|
background: linear-gradient(180deg, #ffffff 0%, #f8faff 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 992px) {
|
|
|
|
|
.page-hero {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.page-shell {
|
|
|
|
|
padding: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-hero,
|
|
|
|
|
.panel-shell {
|
|
|
|
|
padding: 18px;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hero-stats {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-head {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notify-entry-bar {
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|