refactor(frontend): 统一图标使用EP PascalCase命名,修复分栏风格逻辑及ElMessage防刷屏

1. 统一全局图标使用Element Plus官方PascalCase命名,替换所有el-icon-*字符串
2. 修复分栏高亮风格逻辑,修正默认值及路由切换时的样式匹配
3. 新增ElMessage防重复提示逻辑,合并相同内容的消息
4. 优化分栏菜单选中滑块样式,适配新的风格逻辑
5. 重构系统分组表单布局,优化字段排列及响应式显示
This commit is contained in:
sugar1569 2026-09-23 18:35:57 +08:00
parent dfb8c5132b
commit d34cbc59aa
22 changed files with 137 additions and 109 deletions

View File

@ -40,8 +40,8 @@
accept="image/png, image/jpeg, image/gif, image/jpg"
@change="selectImg($event)"
/>
<el-button type="danger" plain icon="el-icon-zoom-in" v-db-click @click="changeScale(1)">放大</el-button>
<el-button type="danger" plain icon="el-icon-zoom-out" v-db-click @click="changeScale(-1)">缩小</el-button>
<el-button type="danger" plain icon="ZoomIn" v-db-click @click="changeScale(1)">放大</el-button>
<el-button type="danger" plain icon="ZoomOut" v-db-click @click="changeScale(-1)">缩小</el-button>
<el-button type="danger" plain v-db-click @click="rotateLeft">↺ 左旋转</el-button>
<el-button type="danger" plain v-db-click @click="rotateRight">↻ 右旋转</el-button>
</div>

View File

@ -46,14 +46,14 @@
>
<el-button
type="success"
icon="el-icon-edit"
icon="Edit"
v-show="item.new[i].isDel && isShow"
v-db-click
@click="clkk(item)"
></el-button>
<el-button
type="error"
icon="el-icon-delete"
icon="Delete"
v-show="item.new[i].isDel && isShow"
v-db-click
@click="del(item, '删除图文', i)"
@ -61,7 +61,7 @@
></el-button>
<el-button
type="primary"
icon="el-icon-s-promotion"
icon="Promotion"
v-show="item.new[i].isDel && isShowSend"
v-db-click
@click="send(item, '发送', i)"

View File

@ -2,7 +2,7 @@
<el-dialog v-model="visible" width="800px" title="选择图标" append-to-body>
<div class="icon-selector">
<div class="search-box">
<el-input v-model="searchText" placeholder="搜索图标名称" prefix-icon="el-icon-search" clearable></el-input>
<el-input v-model="searchText" placeholder="搜索图标名称" prefix-icon="Search" clearable></el-input>
</div>
<div class="icon-list">
<div class="icon-item" v-for="(item, index) in filteredIcons" :key="index" @click="selectIcon(item)">

View File

@ -43,7 +43,7 @@
<el-input
v-model="searchKeyword"
placeholder="请输入主题名称"
suffix-icon="el-icon-search"
suffix-icon="Search"
@change="searchTheme"
></el-input>
</div>

View File

@ -8,7 +8,7 @@
</div>
<div class="header-right">
<el-button icon="el-icon-view" @click="$emit('preview')">预览</el-button>
<el-button icon="View" @click="$emit('preview')">预览</el-button>
<el-button v-if="!isMicroPage" @click="$emit('save-template')">另存主题</el-button>
<el-button @click="$emit('save')">保存</el-button>
<el-button type="primary" @click="$emit('save-close')">保存并关闭</el-button>

View File

@ -106,11 +106,7 @@
<div class="main-content">
<div class="filters-header">
<div class="search-box">
<el-input
v-model="searchKeyword"
placeholder="请输入主题名称"
prefix-icon="el-icon-search"
></el-input>
<el-input v-model="searchKeyword" placeholder="请输入主题名称" prefix-icon="Search"></el-input>
</div>
</div>

View File

@ -185,8 +185,11 @@ function setColumnsMenuHighlight(path) {
watch(
() => themeConfigStore.themeConfig.columnsAsideStyle,
(val) => {
val === 'columnsRound' ? (difference.value = 3) : (difference.value = 0);
// 圆角滑块高 44px(项高 50px),top 下移 3px 使滑块在项内垂直居中;卡片滑块顶满 50px 项高,无需偏移。
// 设置面板 radio 下发 kebab-case(columns-round/columns-card),原驼峰比较 columnsRound 永不匹配,一并修正
val === 'columns-round' ? (difference.value = 3) : (difference.value = 0);
},
{ immediate: true },
);
// 监听路由的变化
@ -222,6 +225,8 @@ watch(
color: var(--prev-bg-columnsMenuBarColor);
width: 100%;
height: 50px;
/* 一级菜单图标统一 16px:iconfont 菜单图标组件默认 16px,EP 图标无 .el-menu 上下文会回落 14px */
font-size: 16px;
text-align: center;
display: flex;
cursor: pointer;
@ -260,11 +265,11 @@ watch(
text-decoration: none;
color: var(--prev-bg-columnsMenuBarColor);
}
/* 悬停反馈:以菜单文字色衍生 8% 半透明底,深/浅主题自动适配;不占用选中态主题蓝 */
&:hover {
background: color-mix(in srgb, var(--prev-bg-columnsMenuBarColor) 8%, transparent);
}
}
// li:hover {
// background: var(--prev-color-primary);
// color: var(--prev-bg-columnsMenuBarColor);
// }
.layout-columns {
transition: 0.3s ease-in-out;
}
@ -274,18 +279,19 @@ watch(
transition: 0.3s ease-in-out;
}
/* 圆角选中滑块:项高 50px 内上下各留 3px、左右各留 5px,圆角 6px 与全站控件圆角一致;卡片风格由 .columns-card 覆盖回全宽直角 */
.columns-round {
background: var(--prev-color-primary);
// color: var(--prev-color-text-white);
position: absolute;
left: 50%;
top: 2px;
height: 50px;
width: 65px;
top: 3px;
height: 44px;
width: calc(100% - 10px);
transform: translateX(-50%);
z-index: 0;
transition: 0.3s ease-in-out;
border-radius: 5px;
border-radius: 6px;
}
.columns-card {
@extend .columns-round;

View File

@ -12,7 +12,7 @@
v-model="menuQuery"
:fetch-suggestions="menuSearch"
:placeholder="$t('message.user.searchPlaceholder')"
prefix-icon="el-icon-search"
prefix-icon="Search"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
@blur="onSearchBlur"

View File

@ -526,7 +526,7 @@ function onSetLayout(layout) {
}
themeConfigStore.themeConfig.layout = layout;
themeConfigStore.themeConfig.isDrawer = false;
themeConfigStore.themeConfig.columnsAsideStyle = 'columns-card';
// 高亮风格(columnsAsideStyle)不随布局切换重置,保留用户选择;默认值唯一真源在 store/module/themeConfig.js
setLocalTheme(themeConfigStore.themeConfig.themeStyle);
}

View File

@ -24,7 +24,7 @@ import * as tools from '@/libs/tools';
import timeOptions from '@/libs/timeOptions';
// UI 框架
import ElementPlus from 'element-plus';
import ElementPlus, { ElMessage } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import 'element-plus/dist/index.css';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
@ -75,6 +75,20 @@ const app = createApp(App);
window.Vue = Vue;
window.ElementPlus = ElementPlus;
// ---- ElMessage 防刷屏:登录过期等场景下多个并发请求同时失败,各页面 catch 各弹一条相同提示。
// 为类型方法统一注入 grouping,由 Element Plus 把相同内容合并为一条并显示重复计数;
// 内容为 VNode 时不支持合并,原样透传。 ----
['success', 'warning', 'info', 'error'].forEach((type) => {
const original = ElMessage[type].bind(ElMessage);
ElMessage[type] = (options) => {
const message = typeof options === 'string' ? options : options?.message;
if (typeof message === 'string' && message) {
return original(typeof options === 'string' ? { message, grouping: true } : { ...options, grouping: true });
}
return original(options);
};
});
// ---- 过渡兼容层:Vue2 时代的 this.bus($on/$off/$emit 转发到 mitt 总线) ----
app.config.globalProperties.bus = {
$on: (type, fn) => bus.on(type, fn),

View File

@ -5,7 +5,7 @@
<el-card :bordered="false" shadow="never">
<div class="card-title">
<el-avatar
icon="el-icon-user-solid"
icon="UserFilled"
style="color: var(--prev-color-primary); background-color: #e6f7ff"
></el-avatar>
<h4 class="ivu-pl-8">用户</h4>
@ -23,7 +23,7 @@
<el-card :bordered="false" shadow="never" class="dashboard-console-visit">
<div class="card-title">
<el-avatar
icon="el-icon-s-marketing"
icon="DataAnalysis"
style="color: var(--prev-color-primary); background-color: #e6f7ff"
></el-avatar>
<h4 class="ivu-pl-8">购买用户统计</h4>

View File

@ -7,7 +7,7 @@
<el-row justify="center" align="middle">
<el-col :span="8" class="card-title">
<el-avatar
icon="el-icon-s-marketing"
icon="DataAnalysis"
style="color: var(--prev-color-primary); background-color: #e6f7ff"
></el-avatar>
<h4 class="ivu-pl-8">订单</h4>

View File

@ -7,11 +7,12 @@
<div class="mcp-hero-title">MCP 服务管理</div>
<div class="mcp-hero-sub">安装 MCP 服务,为 AI Agent 扩展更多工具能力</div>
</div>
<el-button type="primary" icon="el-icon-setting" v-db-click @click="openEditor()">配置 MCP</el-button>
<!-- icon prop 需传全局注册的 EP 图标名(PascalCase);el-icon-* 字符串不会解析成组件 -->
<el-button type="primary" icon="Setting" v-db-click @click="openEditor()">配置 MCP</el-button>
</div>
<div class="mcp-toolbar">
<el-input v-model="keyword" placeholder="搜索 MCP" prefix-icon="el-icon-search" clearable class="mcp-search" />
<el-input v-model="keyword" placeholder="搜索 MCP" prefix-icon="Search" clearable class="mcp-search" />
</div>
<div class="mcp-section">
@ -75,7 +76,13 @@
尚未同步工具,点击 <i class="el-icon-refresh" /> 拉取远端工具清单。
</div>
<div v-else class="mcp-tools">
<el-tooltip v-for="tool in row.tools" :key="tool.name" :content="toolTip(tool)" placement="top">
<el-tooltip
v-for="tool in row.tools"
:key="tool.name"
:content="toolTip(tool)"
placement="top"
popper-class="ai-mcp-tool-tip"
>
<span class="tool-chip mono">{{ tool.label || tool.name }}</span>
</el-tooltip>
</div>
@ -609,3 +616,11 @@ function removeServer(row) {
}
}
</style>
<style lang="scss">
/* 工具提示 popper 挂载在 body 下,scoped 样式无法命中,用专用类全局限定宽度 */
.ai-mcp-tool-tip {
max-width: 420px;
line-height: 1.6;
}
</style>

View File

@ -130,7 +130,7 @@
附属资料不随对话注入,Agent 判断需要更完整资料时按需加载;文件为 .md 格式(SKILL.md 与 EXAMPLES.md
为保留名)。
</div>
<el-button type="primary" size="small" icon="el-icon-plus" v-db-click @click="openAttachmentEditor()"
<el-button type="primary" size="small" icon="Plus" v-db-click @click="openAttachmentEditor()"
>新建附件</el-button
>
</div>

View File

@ -9,7 +9,7 @@
<div class="task-hero-title">自动化任务</div>
<div class="task-hero-sub">让 AI 按计划无人值守执行指令并推送结果(如每日经营日报)</div>
</div>
<el-button type="primary" icon="el-icon-plus" v-db-click @click="openEditor()">新建任务</el-button>
<el-button type="primary" icon="Plus" v-db-click @click="openEditor()">新建任务</el-button>
</div>
<div class="task-section">

View File

@ -2,9 +2,7 @@
<div>
<div class="i-layout-page-header header-title">
<div class="fl_header">
<el-button class="btn-back" icon="el-icon-arrow-left" type="text" v-db-click @click="$router.go(-1)"
>返回</el-button
>
<el-button class="btn-back" icon="ArrowLeft" type="text" v-db-click @click="$router.go(-1)">返回</el-button>
<el-divider direction="vertical"></el-divider>
<span class="ivu-page-header-title">{{ $route.query.lottery_id ? '编辑' : '新增' }}抽奖活动</span>
</div>

View File

@ -2,9 +2,7 @@
<div>
<div class="i-layout-page-header header-title">
<div class="fl_header">
<el-button class="btn-back" icon="el-icon-arrow-left" type="text" v-db-click @click="$router.go(-1)"
>返回</el-button
>
<el-button class="btn-back" icon="ArrowLeft" type="text" v-db-click @click="$router.go(-1)">返回</el-button>
<el-divider direction="vertical"></el-divider>
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
</div>

View File

@ -25,7 +25,7 @@
backgroundSize: '100% 100%',
}"
>
<el-button type="error" icon="el-icon-delete" v-db-click @click="del(i)" v-show="isDel"></el-button>
<el-button type="error" icon="Delete" v-db-click @click="del(i)" v-show="isDel"></el-button>
</div>
<span class="news_sp">{{ item.title }}</span>
</div>
@ -34,7 +34,7 @@
<div class="news_cent_img ivu-mr-8">
<img :src="item.image_input ? item.image_input : baseImg" />
</div>
<el-button type="error" icon="el-icon-delete" v-db-click @click="del(i)"></el-button>
<el-button type="error" icon="Delete" v-db-click @click="del(i)"></el-button>
</div>
</div>
<!-- <div class="acea-row row-center-wrapper">

View File

@ -185,13 +185,7 @@
</div>
</div>
</div>
<el-button
v-else
class="upload-select"
type="dashed"
icon="el-icon-plus"
@click="modalPicTap(1)"
></el-button>
<el-button v-else class="upload-select" type="dashed" icon="Plus" @click="modalPicTap(1)"></el-button>
<p class="tip">建议尺寸:80px*30px,若未上传则为空白</p>
</el-form-item>
<el-form-item label="排序:">

View File

@ -2,9 +2,7 @@
<div>
<div class="i-layout-page-header header-title">
<div class="fl_header">
<el-button class="btn-back" icon="el-icon-arrow-left" type="text" v-db-click @click="$router.go(-1)"
>返回</el-button
>
<el-button class="btn-back" icon="ArrowLeft" type="text" v-db-click @click="$router.go(-1)">返回</el-button>
<el-divider direction="vertical"></el-divider>
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
</div>

View File

@ -27,61 +27,66 @@
</el-form-item>
</el-col>
<el-col :span="24" v-for="(item, index) in formValidate.typelist" :key="index">
<el-col v-bind="grid">
<el-form-item
:label="'字段' + (index + 1) + ':'"
label-width="90px"
:prop="'typelist.' + index + '.name.value'"
:rules="{ required: true, message: '请输入字段名称:姓名', trigger: 'blur' }"
<el-row :gutter="20">
<el-col v-bind="grid">
<el-form-item
:label="'字段' + (index + 1) + ':'"
label-width="90px"
:prop="'typelist.' + index + '.name.value'"
:rules="{ required: true, message: '请输入字段名称:姓名', trigger: 'blur' }"
>
<el-input v-model="item.name.value" placeholder="字段名称:姓名"></el-input>
</el-form-item>
</el-col>
<el-col v-bind="grid">
<el-form-item
label-width="0"
:prop="'typelist.' + index + '.title.value'"
:rules="{ required: true, message: '请输入字段配置名', trigger: 'blur' }"
>
<el-input v-model="item.title.value" placeholder="字段配置名:name"></el-input>
</el-form-item>
</el-col>
<el-col v-bind="grid">
<el-form-item
label-width="0"
:prop="'typelist.' + index + '.type.value'"
:rules="{ required: true, message: '请选择字段类型', trigger: 'change' }"
>
<el-select placeholder="字段类型" v-model="item.type.value">
<el-option value="input">文本框</el-option>
<el-option value="textarea">多行文本框</el-option>
<el-option value="radio">单选框</el-option>
<el-option value="checkbox">多选框</el-option>
<el-option value="select">下拉选择</el-option>
<el-option value="upload">单图</el-option>
<el-option value="uploads">多图</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col v-bind="gridBtn">
<el-form-item label-width="0">
<el-button icon="Delete" v-db-click @click="delGroup(index)"></el-button>
</el-form-item>
</el-col>
<el-col
v-if="item.type.value === 'radio' || item.type.value === 'checkbox' || item.type.value === 'select'"
:span="24"
>
<el-input v-model="item.name.value" placeholder="字段名称:姓名"></el-input>
</el-form-item>
</el-col>
<el-col v-bind="grid" class="goupBox">
<el-form-item
label-width="0"
:prop="'typelist.' + index + '.title.value'"
:rules="{ required: true, message: '请输入字段配置名', trigger: 'blur' }"
>
<el-input v-model="item.title.value" placeholder="字段配置名:name"></el-input>
</el-form-item>
</el-col>
<el-col v-bind="grid" prop="type" class="goupBox">
<el-form-item
:prop="'typelist.' + index + '.type.value'"
:rules="{ required: true, message: '请选择字段类型', trigger: 'change' }"
label-width="0"
>
<el-select placeholder="字段类型" v-model="item.type.value">
<el-option value="input">文本框</el-option>
<el-option value="textarea">多行文本框</el-option>
<el-option value="radio">单选框</el-option>
<el-option value="checkbox">多选框</el-option>
<el-option value="select">下拉选择</el-option>
<el-option value="upload">单图</el-option>
<el-option value="uploads">多图</el-option>
</el-select>
</el-form-item>
</el-col>
<el-button icon="el-icon-delete" v-db-click @click="delGroup(index)"></el-button>
<!-- <el-col span="1"> </el-col> -->
<el-col
:span="24"
v-if="item.type.value === 'radio' || item.type.value === 'checkbox' || item.type.value === 'select'"
>
<el-form-item
:prop="'typelist.' + index + '.param.value'"
:rules="{ required: true, message: '请输入参数方式', trigger: 'blur' }"
>
<el-input
type="textarea"
:rows="4"
:placeholder="item.param.placeholder"
v-model="item.param.value"
style="width: 90%"
></el-input>
</el-form-item>
</el-col>
<el-form-item
:prop="'typelist.' + index + '.param.value'"
:rules="{ required: true, message: '请输入参数方式', trigger: 'blur' }"
>
<el-input
type="textarea"
:rows="4"
:placeholder="item.param.placeholder"
v-model="item.param.value"
style="width: 90%"
></el-input>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col>
<el-form-item>
@ -134,6 +139,13 @@ const grid = {
sm: 24,
xs: 24,
};
const gridBtn = {
xl: 3,
lg: 3,
md: 12,
sm: 24,
xs: 24,
};
const modals = ref(false);
const modal12 = ref(false);
const ruleValidate = {
@ -235,7 +247,4 @@ defineExpose({ modals, fromData });
.cur {
cursor: pointer;
}
.goupBox :deep(.ivu-form-item-content) {
margin-left: 43px !important;
}
</style>

View File

@ -80,7 +80,7 @@ export const useThemeConfigStore = defineStore(
// 主页面切换动画:可选值"<slide-right|slide-left|opacitys>",默认 slide-right
animation: 'opacitys',
// 分栏高亮风格:可选值"<columns-round|columns-card>",默认 columns-round
columnsAsideStyle: 'columns-card',
columnsAsideStyle: 'columns-round',
// 分栏布局风格:可ƒ选值"<columns-horizontal|columns-vertical>",默认 columns-horizontal
columnsAsideLayout: 'columns-vertical',