feat(manager): 热区组件配置面板重做为内容/样式双 tab

- 新增 PanelFlexOne:内容 tab(选图+设置热区,复用 ossManage 与 hotzone 编辑器),样式 tab(背景颜色/上下左右边距/页面间距/背景圆角/内容圆角)
- 注册进 panelMap 替换 flexOne 的通用老面板
- 画布预览 tpl_flex_one 应用相同样式字段,缺字段兜底保持原样
This commit is contained in:
pikachu1995@126.com
2026-10-12 15:38:25 +08:00
parent 0ad79157d3
commit ce03b13718
3 changed files with 243 additions and 3 deletions

View File

@@ -0,0 +1,229 @@
<template>
<div class="panel-flex-one">
<div class="panel-tabs">
<div
v-for="tab in tabs"
:key="tab.key"
class="panel-tab"
:class="{ active: activeTab === tab.key }"
@click="activeTab = tab.key"
>
{{ tab.title }}
</div>
</div>
<template v-if="activeTab === 'content'">
<div class="section-title">内容设置</div>
<div class="section-tip">选定布局区域,在下方添加图片,建议添加比例一致的图片</div>
<div class="img-preview" v-if="item.img">
<img :src="item.img" alt="" />
</div>
<div class="img-placeholder" v-else>尺寸不限</div>
<div class="btn-row">
<el-button plain size="small" type="primary" @click="pickImage">选择照片</el-button>
<el-button plain size="small" type="primary" @click="openHotzone">设置热区</el-button>
</div>
</template>
<template v-else>
<div class="section-title">颜色设置</div>
<color-field
v-model="res.options.bgColor"
label="背景颜色"
:default-value="defaults.bgColor"
/>
<div class="section-title">边距设置</div>
<slider-field
v-model="res.options.paddingTop"
label="上边距"
:min="0"
:max="100"
/>
<slider-field
v-model="res.options.paddingBottom"
label="下边距"
:min="0"
:max="100"
/>
<slider-field
v-model="res.options.paddingX"
label="左右边距"
:min="0"
:max="30"
/>
<slider-field
v-model="res.options.pageGap"
label="页面间距"
:min="0"
:max="100"
/>
<div class="section-title">圆角设置</div>
<slider-field
v-model="res.options.borderRadius"
label="背景圆角"
:min="0"
:max="50"
/>
<slider-field
v-model="res.options.imgRadius"
label="内容圆角"
:min="0"
:max="50"
/>
</template>
<hotzone ref="hotzone" @changeZone="changeZone"></hotzone>
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage
@callback="callbackSelected"
:is-component="true"
:initialize="picModelFlag"
:max-select="1"
ref="ossManage"
/>
</el-dialog>
</div>
</template>
<script>
import ColorField from "../controls/ColorField.vue";
import SliderField from "../controls/SliderField.vue";
import hotzone from "@/components/hotzone";
import ossManage from "@/views/sys/oss-manage/ossManage";
const DEFAULTS = {
bgColor: "#FFFFFF",
paddingTop: 10,
paddingBottom: 10,
paddingX: 12,
pageGap: 10,
borderRadius: 0,
imgRadius: 0,
};
export default {
name: "PanelFlexOne",
components: { ColorField, SliderField, hotzone, ossManage },
props: {
res: { type: Object, required: true },
},
data() {
return {
defaults: DEFAULTS,
activeTab: "content",
tabs: [
{ key: "content", title: "内容" },
{ key: "style", title: "样式" },
],
picModelFlag: false,
};
},
computed: {
item() {
return this.res.options.list[0];
},
},
created() {
this.res.options = Object.assign({}, DEFAULTS, this.res.options);
if (!Array.isArray(this.res.options.list) || !this.res.options.list.length) {
this.res.options.list = [
{ img: "", url: "", link: "", size: "750*280", model: "hotzone" },
];
}
},
methods: {
pickImage() {
this.picModelFlag = true;
this.$nextTick(() => {
const oss = this.$refs.ossManage;
if (!oss) return;
oss.selectImage = true;
oss.fileType = "pic";
oss.changeFileType();
});
},
callbackSelected(val) {
this.picModelFlag = false;
if (val && val.url) {
this.item.img = val.url;
}
},
openHotzone() {
if (!this.item.img) {
this.$Message.warning("请先选择照片");
return;
}
this.$refs.hotzone.open(this.item);
},
changeZone(zoneInfo) {
this.item.zoneInfo = zoneInfo;
},
},
};
</script>
<style scoped lang="scss">
.panel-tabs {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 14px;
.panel-tab {
padding: 5px 18px;
font-size: 13px;
border-radius: 999px;
cursor: pointer;
color: #606266;
background: #f4f4f5;
&.active {
background: #409eff;
color: #fff;
}
}
}
.section-title {
font-size: 13px;
font-weight: 600;
color: #303133;
margin: 14px 0 10px;
&:first-of-type {
margin-top: 0;
}
}
.section-tip {
font-size: 12px;
color: #909399;
margin-bottom: 10px;
}
.img-preview {
border: 1px dashed #dcdfe6;
border-radius: 6px;
padding: 6px;
margin-bottom: 10px;
img {
width: 100%;
display: block;
border-radius: 4px;
}
}
.img-placeholder {
height: 120px;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #dcdfe6;
border-radius: 6px;
color: #909399;
font-size: 12px;
margin-bottom: 10px;
}
.btn-row {
display: flex;
gap: 10px;
.el-button {
flex: 1;
margin: 0;
}
}
</style>

View File

@@ -7,6 +7,7 @@ import PanelIntegral from "./PanelIntegral.vue";
import PanelCoupon from "./PanelCoupon.vue";
import PanelArticle from "./PanelArticle.vue";
import PanelShop from "./PanelShop.vue";
import PanelFlexOne from "./PanelFlexOne.vue";
export default {
blank: PanelBlank,
@@ -16,4 +17,5 @@ export default {
coupon: PanelCoupon,
article: PanelArticle,
shop: PanelShop,
flexOne: PanelFlexOne,
};

View File

@@ -1,6 +1,15 @@
<template>
<div class="layout">
<div class="flex-one">
<div
class="layout"
:style="{
background: res.bgColor ?? 'transparent',
padding: `${res.paddingTop ?? 0}px ${res.paddingX ?? 0}px ${res.paddingBottom ?? 0}px`,
marginBottom: (res.pageGap ?? 0) + 'px',
borderRadius: (res.borderRadius ?? 0) + 'px',
overflow: (res.borderRadius ?? 0) > 0 ? 'hidden' : 'visible',
}"
>
<div class="flex-one" :style="{ borderRadius: (res.imgRadius ?? 0) + 'px' }">
<img :src="res.list[0].img" alt="">
</div>
</div>
@@ -21,4 +30,4 @@ export default {
height: 100%;
}
}
</style>
</style>