feat(system-menu): 优化菜单样式配置功能

1. 修复收货计划跳转路径错误,将路径从/receive/plan调整为/wip/receive/plan
2. 重构菜单样式编辑区域:将单行输入框改为多行文本编辑器,支持快捷样式预设、格式校验和实时预览
3. 新增菜单样式JSON校验逻辑,支持验证格式合法性和无效CSS属性
This commit is contained in:
2026-07-03 17:15:13 +08:00
parent 388d5a10c4
commit a0b283a21e
2 changed files with 222 additions and 3 deletions

View File

@@ -231,15 +231,55 @@
</span>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType != 'F'">
<el-col :span="24" v-if="form.menuType != 'F'">
<el-form-item prop="style">
<el-input v-model="form.style" placeholder="请输入菜单样式" maxlength="255" />
<span slot="label">
<el-tooltip content='菜单样式JSON格式如`{"backgroundColor":"#ff0000","color":"#fff","fontWeight":"bold"}`' placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
菜单样式
</span>
<div class="style-editor">
<el-input
type="textarea"
v-model="form.style"
placeholder='请输入菜单样式JSON格式'
:rows="4"
maxlength="500"
show-word-limit
class="style-textarea"
/>
<div class="style-toolbar">
<div class="style-presets">
<span class="preset-label">快捷样式</span>
<el-button size="mini" @click="applyStylePreset('danger')">浅红</el-button>
<el-button size="mini" @click="applyStylePreset('success')">浅绿</el-button>
<el-button size="mini" @click="applyStylePreset('warning')">浅橙</el-button>
<el-button size="mini" @click="applyStylePreset('primary')">浅蓝</el-button>
<el-button size="mini" @click="applyStylePreset('accent')">浅紫</el-button>
<el-button size="mini" @click="applyStylePreset('warm')">暖色</el-button>
<el-button size="mini" @click="applyStylePreset('bold')">加粗</el-button>
<el-button size="mini" @click="applyStylePreset('italic')">斜体</el-button>
<el-button size="mini" @click="applyStylePreset('clear')">清除</el-button>
</div>
<div class="style-actions">
<el-button size="mini" type="success" icon="el-icon-check" @click="validateStyle">校验</el-button>
<el-button size="mini" type="info" icon="el-icon-view" @click="showPreview = !showPreview">{{ showPreview ? '隐藏预览' : '预览样式' }}</el-button>
</div>
</div>
<div v-if="styleError" class="style-error">
<i class="el-icon-warning"></i> {{ styleError }}
</div>
<div v-if="showPreview && form.style" class="style-preview">
<div class="preview-title">菜单预览效果</div>
<div class="preview-container">
<div class="preview-menu-item" :style="parsedStyle">
<span class="preview-icon">📁</span>
<span class="preview-text">{{ form.menuName || '菜单名称' }}</span>
</div>
</div>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType == 'C'">
@@ -311,6 +351,16 @@ export default {
name: "Menu",
dicts: ['sys_show_hide', 'sys_normal_disable'],
components: { Treeselect, IconSelect },
computed: {
parsedStyle() {
if (!this.form.style) return {};
try {
return JSON.parse(this.form.style);
} catch (e) {
return {};
}
}
},
data() {
return {
// 遮罩层
@@ -334,6 +384,10 @@ export default {
menuName: undefined,
visible: undefined
},
// 菜单样式预览
showPreview: false,
// 菜单样式校验错误
styleError: '',
// 表单参数
form: {},
// 表单校验
@@ -358,6 +412,67 @@ export default {
selected(name) {
this.form.icon = name;
},
/** 应用样式预设 */
applyStylePreset(type) {
const presets = {
danger: { background: 'linear-gradient(135deg, #fff5f5 0%, #ffe0e0 100%)', color: '#c45c5c', borderRadius: '4px', border: '1px solid #ffd4d4' },
success: { background: 'linear-gradient(135deg, #f0fff4 0%, #dcffe4 100%)', color: '#5c8a6b', borderRadius: '4px', border: '1px solid #c3e6cb' },
warning: { background: 'linear-gradient(135deg, #fffdf0 0%, #fff3cd 100%)', color: '#b8860b', borderRadius: '4px', border: '1px solid #ffeeba' },
primary: { background: 'linear-gradient(135deg, #f0f7ff 0%, #d6eaff 100%)', color: '#4a7fb5', borderRadius: '4px', border: '1px solid #b8daff' },
accent: { background: 'linear-gradient(135deg, #f8f0ff 0%, #e8d5ff 100%)', color: '#8b5cf6', borderRadius: '4px', border: '1px solid #d8b4fe' },
warm: { background: 'linear-gradient(135deg, #fff8f0 0%, #ffedd5 100%)', color: '#c27832', borderRadius: '4px', border: '1px solid #fed7aa' },
bold: { fontWeight: '600', color: '#303133' },
italic: { fontStyle: 'italic', color: '#606266' },
clear: {}
};
if (type === 'clear') {
this.form.style = '';
this.styleError = '';
} else {
// 合并已有样式
let currentStyle = {};
if (this.form.style) {
try {
currentStyle = JSON.parse(this.form.style);
} catch (e) {
currentStyle = {};
}
}
const mergedStyle = { ...currentStyle, ...presets[type] };
this.form.style = JSON.stringify(mergedStyle);
this.validateStyle();
}
},
/** 校验菜单样式JSON格式 */
validateStyle() {
this.styleError = '';
if (!this.form.style) {
this.$modal.msgSuccess('样式为空,无需校验');
return;
}
try {
const styleObj = JSON.parse(this.form.style);
if (typeof styleObj !== 'object' || styleObj === null) {
this.styleError = '样式必须是JSON对象格式';
this.$modal.msgError('校验失败样式必须是JSON对象格式');
return;
}
// 检查是否包含有效的CSS属性
const validCssProps = [
'backgroundColor', 'background', 'color', 'fontWeight', 'fontSize', 'fontStyle',
'borderRadius', 'padding', 'margin', 'border', 'textAlign',
'textDecoration', 'lineHeight', 'letterSpacing', 'opacity', 'boxShadow'
];
const invalidProps = Object.keys(styleObj).filter(prop => !validCssProps.includes(prop));
if (invalidProps.length > 0) {
this.styleError = `可能无效的属性:${invalidProps.join(', ')}`;
}
this.$modal.msgSuccess('校验通过JSON格式正确');
} catch (e) {
this.styleError = `JSON格式错误${e.message}`;
this.$modal.msgError('校验失败:' + e.message);
}
},
/** 查询菜单列表 */
getList() {
this.loading = true;
@@ -554,3 +669,107 @@ export default {
}
};
</script>
<style lang="scss" scoped>
.style-editor {
width: 100%;
max-width: 600px;
.style-textarea {
margin-bottom: 8px;
:deep(.el-textarea__inner) {
font-family: 'Consolas', 'Monaco', monospace;
font-size: 12px;
line-height: 1.5;
padding: 8px;
}
}
.style-toolbar {
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 8px;
.style-presets {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
.preset-label {
font-size: 12px;
color: #909399;
margin-right: 4px;
}
.el-button {
margin-left: 0;
}
}
.style-actions {
display: flex;
gap: 4px;
}
}
.style-error {
color: #F56C6C;
font-size: 12px;
margin-bottom: 8px;
padding: 4px 8px;
background-color: #FEF0F0;
border-radius: 4px;
border: 1px solid #FBC4C4;
i {
margin-right: 4px;
}
}
.style-preview {
border: 1px solid #EBEEF5;
border-radius: 4px;
padding: 12px;
background-color: #FAFAFA;
.preview-title {
font-size: 12px;
color: #909399;
margin-bottom: 8px;
}
.preview-container {
display: flex;
justify-content: center;
padding: 16px;
background-color: #F5F7FA;
border-radius: 4px;
.preview-menu-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: #fff;
border-radius: 4px;
min-width: 120px;
justify-content: center;
transition: all 0.3s ease;
.preview-icon {
font-size: 16px;
}
.preview-text {
font-size: 14px;
}
}
}
}
}
</style>

View File

@@ -20,7 +20,7 @@
</el-form-item>
<!-- 查找并选择选择收货计划, 使用远程搜索 -->
<el-alert v-if="noPlan" type="warning" title="今天还没有收货计划,点击创建" show-icon
@click.native="$router.push('/receive/plan')"></el-alert>
@click.native="$router.push('/wip/receive/plan')"></el-alert>
</el-col>
</el-row>
<el-row>