feat(manager): 移动端装修编辑器 CRMEB 化迭代

- 轮播图:双 tab 面板(内容/样式)、轮播风格(通栏/卡片)、指示器样式/位置/色调、渐变背景、边距圆角
- 图片魔方:9 种风格收进样式 tab,拖入默认左一右二,内容 tab 固定槽位配置,新增颜色/边距/圆角设置
- 宫格导航:双 tab 面板,菜单管理 + 颜色/边距/圆角设置
- 编辑器整体:灰底卡片模块化、顶栏页面名称编辑、画布操作条/名称标签外置、组件列表三列通高卡片
- 预览模板同步应用各组件样式字段
This commit is contained in:
pikachu1995@126.com
2026-10-13 11:07:30 +08:00
parent b45910a35f
commit 0b644a1722
20 changed files with 1642 additions and 79 deletions

View File

@@ -0,0 +1,353 @@
<template>
<div class="panel-ad-cube">
<div class="panel-header">
<span class="panel-name">图片魔方</span>
<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>
</div>
<template v-if="activeTab === 'content'">
<div class="panel-card">
<div class="section-title">图片设置</div>
<div class="section-tip">当前风格:{{ res.name }},请按布局区域配置对应图片</div>
<div class="img-item" v-for="(item, index) in res.options.list" :key="index">
<div class="img-item-head">
<span>图片 {{ index + 1 }}</span>
<span class="img-size" v-if="item.size">建议尺寸 {{ item.size }}</span>
</div>
<div class="img-preview" v-if="item.img">
<img :src="item.img" alt="" />
</div>
<div class="img-placeholder" v-else>尺寸 {{ item.size || "不限" }}</div>
<div class="field-row" v-if="item.title !== undefined">
<div class="field-label">文字</div>
<el-input v-model="item.title" size="small" maxlength="10" />
</div>
<div class="btn-row">
<el-button plain type="primary" @click="pickImage(item)">选择照片</el-button>
<el-button plain type="primary" @click="pickLink(item)">选择链接</el-button>
</div>
<div class="link-view" v-if="item.url && item.url.___type">
已选链接:{{ linkText(item.url) }}
</div>
</div>
</div>
</template>
<template v-else>
<div class="panel-card">
<div class="section-title">风格选择</div>
<div class="section-tip">切换风格将重置当前已配置的图片内容</div>
<div class="style-grid">
<div
v-for="style in styleList"
:key="style.type"
class="style-item"
:class="{ active: res.type === style.type }"
@click="changeStyle(style)"
>
{{ style.name }}
</div>
</div>
</div>
<div class="panel-card">
<div class="section-title">颜色设置</div>
<color-field
v-model="res.options.bgColor1"
label="背景颜色"
:default-value="defaults.bgColor1"
/>
<color-field
v-model="res.options.bgColor2"
label="渐变结束色"
:default-value="defaults.bgColor2"
/>
<div class="section-tip">起始色与结束色一致时为纯色背景</div>
</div>
<div class="panel-card">
<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" />
<slider-field v-model="res.options.contentGap" label="内容间距" :min="0" :max="50" />
</div>
<div class="panel-card">
<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" />
</div>
</template>
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
<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 ossManage from "@/views/sys/oss-manage/ossManage";
import ways from "@/components/lili-dialog/wap.js";
import { modelData } from "../config";
import ColorField from "../controls/ColorField.vue";
import SliderField from "../controls/SliderField.vue";
const DEFAULTS = {
bgColor1: "#FFFFFF",
bgColor2: "#FFFFFF",
paddingTop: 10,
paddingBottom: 10,
paddingX: 12,
pageGap: 10,
contentGap: 0,
borderRadius: 0,
imgRadius: 0,
};
export default {
name: "PanelAdCube",
hideDecorateTitle: true,
components: { ossManage, ColorField, SliderField },
props: {
res: { type: Object, required: true },
},
data() {
return {
defaults: DEFAULTS,
activeTab: "content",
tabs: [
{ key: "content", title: "内容" },
{ key: "style", title: "样式" },
],
picModelFlag: false,
currentItem: null,
};
},
computed: {
// 图片魔方的可选风格(config 中 drawer 标记的条目)
styleList() {
return modelData.filter((m) => m.drawer);
},
},
created() {
this.res.options = Object.assign({}, DEFAULTS, this.res.options);
},
methods: {
linkText(url) {
const way = ways.find((e) => e.name === url.___type);
const typeTitle = way ? way.title : "发现";
const name =
url.name || url.goodsName || url.memberName || url.title || "";
return name ? `${typeTitle} - ${name}` : typeTitle;
},
changeStyle(style) {
if (style.type === this.res.type) return;
this.$emit("handleDrawer", style);
},
pickImage(item) {
this.currentItem = item;
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.currentItem) {
this.currentItem.img = val.url;
}
},
pickLink(item) {
this.currentItem = item;
const dialog = this.$refs.liliDialog;
dialog.clearGoodsSelected();
dialog.goodsFlag = false;
dialog.flag = true;
},
selectedLink(val) {
if (!this.currentItem) return;
delete val.selected;
delete val.intro;
delete val.mobileIntro;
this.currentItem.url = val;
},
},
};
</script>
<style scoped lang="scss">
.panel-ad-cube {
background: #f5f7fa;
margin: 0 -20px -120px;
padding: 0 12px 120px;
min-height: calc(100vh - 70px);
box-sizing: border-box;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
margin-bottom: 12px;
padding: 0 14px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
box-sizing: border-box;
.panel-name {
font-size: 14px;
font-weight: bold;
color: #303133;
}
}
.panel-tabs {
display: inline-flex;
background: #f4f4f5;
border-radius: 8px;
padding: 3px;
.panel-tab {
padding: 5px 14px;
font-size: 13px;
border-radius: 6px;
cursor: pointer;
color: #606266;
transition: all 0.2s ease;
&.active {
background: #ffffff;
color: #409eff;
font-weight: 600;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}
}
}
.panel-card {
background: #ffffff;
border-radius: 8px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.section-title {
font-size: 13px;
font-weight: 600;
color: #303133;
margin: 0 0 12px;
}
.section-tip {
font-size: 12px;
color: #909399;
margin-bottom: 10px;
}
.img-item {
border: 1px solid #ebeef5;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
.img-item-head {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 13px;
color: #303133;
margin-bottom: 8px;
.img-size {
font-size: 12px;
color: #909399;
}
}
}
.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: 80px;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #dcdfe6;
border-radius: 6px;
color: #909399;
font-size: 12px;
margin-bottom: 10px;
}
.field-row {
display: flex;
align-items: center;
margin-bottom: 10px;
.field-label {
font-size: 13px;
color: #606266;
margin-right: 10px;
flex-shrink: 0;
}
}
.btn-row {
display: flex;
gap: 10px;
}
.link-view {
margin-top: 8px;
font-size: 12px;
color: #909399;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.style-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 8px;
.style-item {
border: 1px solid #dcdfe6;
border-radius: 6px;
padding: 10px 4px;
text-align: center;
font-size: 12px;
color: #606266;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
border-color: #409eff;
color: #409eff;
}
&.active {
border-color: #409eff;
background: #ecf5ff;
color: #409eff;
font-weight: 600;
}
}
}
</style>

View File

@@ -0,0 +1,503 @@
<template>
<div class="panel-carousel">
<div class="panel-header">
<span class="panel-name">{{ res.name }}</span>
<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>
</div>
<template v-if="activeTab === 'content'">
<div class="panel-card">
<div class="section-title">图片设置</div>
<div class="section-tip">最多添加 10 张,建议添加比例一致的图片</div>
<div class="img-item" v-for="(item, index) in res.options.list" :key="index">
<div class="img-item-head">
<span>图片 {{ index + 1 }}</span>
<div class="img-item-ops">
<el-icon
:class="{ disabled: index === 0 }"
@click="moveItem(index, -1)"
><Top
/></el-icon>
<el-icon
:class="{ disabled: index === res.options.list.length - 1 }"
@click="moveItem(index, 1)"
><Bottom
/></el-icon>
<el-icon class="del" @click="removeItem(index)"><Delete /></el-icon>
</div>
</div>
<div class="img-preview" v-if="item.img">
<img :src="item.img" alt="" />
</div>
<div class="img-placeholder" v-else>建议尺寸 750*350</div>
<div class="btn-row">
<el-button plain type="primary" @click="pickImage(item)">选择照片</el-button>
<el-button plain type="primary" @click="pickLink(item)">选择链接</el-button>
</div>
<div class="link-view" v-if="item.url && item.url.___type">
已选链接:{{ linkText(item.url) }}
</div>
</div>
<el-button
v-if="res.options.list.length < 10"
class="add-btn"
plain
type="primary"
@click="addItem"
>添加图片</el-button
>
</div>
</template>
<template v-else>
<div class="panel-card">
<div class="section-title">轮播风格</div>
<div class="setting-row">
<div class="setting-label">
风格
<span class="setting-value">{{ swiperStyleText }}</span>
</div>
<div class="seg">
<div
v-for="opt in swiperStyleOptions"
:key="opt.value"
class="seg-item"
:class="{ active: res.options.swiperStyle === opt.value }"
@click="res.options.swiperStyle = opt.value"
>
{{ opt.title }}
</div>
</div>
</div>
<div class="section-tip" v-if="res.options.swiperStyle === 'card'">
卡片风格下,两侧会露出前后图片的一部分
</div>
</div>
<div class="panel-card">
<div class="section-title">指示器设置</div>
<div class="setting-row">
<div class="setting-label">
指示器样式
<span class="setting-value">{{ indicatorStyleText }}</span>
</div>
<div class="seg">
<div
v-for="opt in indicatorStyleOptions"
:key="opt.value"
class="seg-item"
:class="{ active: res.options.indicatorStyle === opt.value }"
@click="res.options.indicatorStyle = opt.value"
>
{{ opt.title }}
</div>
</div>
</div>
<div class="setting-row" v-if="res.options.indicatorStyle !== 'none'">
<div class="setting-label">
指示器位置
<span class="setting-value">{{ indicatorPositionText }}</span>
</div>
<div class="seg">
<div
v-for="opt in indicatorPositionOptions"
:key="opt.value"
class="seg-item"
:class="{ active: res.options.indicatorPosition === opt.value }"
@click="res.options.indicatorPosition = opt.value"
>
{{ opt.title }}
</div>
</div>
</div>
<div class="setting-row" v-if="res.options.indicatorStyle !== 'none'">
<div class="setting-label">色调</div>
<el-radio-group v-model="res.options.indicatorColorMode">
<el-radio value="theme">跟随主题风格</el-radio>
<el-radio value="custom">自定义</el-radio>
</el-radio-group>
</div>
<template
v-if="
res.options.indicatorStyle !== 'none' &&
res.options.indicatorColorMode === 'custom'
"
>
<color-field
v-model="res.options.indicatorColor"
label="指示器颜色"
:default-value="defaults.indicatorColor"
/>
</template>
</div>
<div class="panel-card">
<div class="section-title">颜色设置</div>
<color-field
v-model="res.options.bgColor1"
label="背景颜色"
:default-value="defaults.bgColor1"
/>
<color-field
v-model="res.options.bgColor2"
label="渐变结束色"
:default-value="defaults.bgColor2"
/>
<div class="section-tip">起始色与结束色一致时为纯色背景</div>
</div>
<div class="panel-card">
<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>
<div class="panel-card">
<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" />
</div>
</template>
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
<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 { Top, Bottom, Delete } from "@element-plus/icons-vue";
import ColorField from "../controls/ColorField.vue";
import SliderField from "../controls/SliderField.vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
import ways from "@/components/lili-dialog/wap.js";
const DEFAULTS = {
swiperStyle: "full",
indicatorStyle: "round",
indicatorPosition: "center",
indicatorColorMode: "theme",
indicatorColor: "#409eff",
bgColor1: "#FFFFFF",
bgColor2: "#FFFFFF",
paddingTop: 10,
paddingBottom: 10,
paddingX: 12,
pageGap: 0,
borderRadius: 0,
imgRadius: 7,
};
export default {
name: "PanelCarousel",
hideDecorateTitle: true,
components: { ColorField, SliderField, ossManage, Top, Bottom, Delete },
props: {
res: { type: Object, required: true },
},
data() {
return {
defaults: DEFAULTS,
activeTab: "content",
tabs: [
{ key: "content", title: "内容" },
{ key: "style", title: "样式" },
],
swiperStyleOptions: [
{ value: "full", title: "通栏" },
{ value: "card", title: "卡片" },
],
indicatorStyleOptions: [
{ value: "round", title: "圆形" },
{ value: "line", title: "长条" },
{ value: "none", title: "无" },
],
indicatorPositionOptions: [
{ value: "left", title: "居左" },
{ value: "center", title: "居中" },
{ value: "right", title: "居右" },
],
picModelFlag: false,
currentItem: null,
};
},
computed: {
swiperStyleText() {
const hit = this.swiperStyleOptions.find(
(o) => o.value === this.res.options.swiperStyle
);
return hit ? hit.title : "";
},
indicatorStyleText() {
const hit = this.indicatorStyleOptions.find(
(o) => o.value === this.res.options.indicatorStyle
);
return hit ? hit.title : "";
},
indicatorPositionText() {
const hit = this.indicatorPositionOptions.find(
(o) => o.value === this.res.options.indicatorPosition
);
return hit ? hit.title : "";
},
},
created() {
this.res.options = Object.assign({}, DEFAULTS, this.res.options);
if (!Array.isArray(this.res.options.list)) {
this.res.options.list = [];
}
},
methods: {
linkText(url) {
const way = ways.find((e) => e.name === url.___type);
const typeTitle = way ? way.title : "发现";
const name =
url.name || url.goodsName || url.memberName || url.title || "";
return name ? `${typeTitle} - ${name}` : typeTitle;
},
pickImage(item) {
this.currentItem = item;
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.currentItem) {
this.currentItem.img = val.url;
}
},
pickLink(item) {
this.currentItem = item;
const dialog = this.$refs.liliDialog;
dialog.clearGoodsSelected();
dialog.goodsFlag = false;
dialog.flag = true;
},
selectedLink(val) {
if (!this.currentItem) return;
delete val.selected;
delete val.intro;
delete val.mobileIntro;
this.currentItem.url = val;
},
moveItem(index, dir) {
const list = this.res.options.list;
const target = index + dir;
if (target < 0 || target >= list.length) return;
list.splice(target, 1, ...list.splice(index, 1, list[target]));
},
removeItem(index) {
this.res.options.list.splice(index, 1);
},
addItem() {
this.res.options.list.push({
img: "",
url: "",
link: "",
size: "750*350",
model: "link",
});
},
},
};
</script>
<style scoped lang="scss">
.panel-carousel {
background: #f5f7fa;
margin: 0 -20px -120px;
padding: 0 12px 120px;
min-height: calc(100vh - 70px);
box-sizing: border-box;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
margin-bottom: 12px;
padding: 0 14px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
box-sizing: border-box;
.panel-name {
font-size: 14px;
font-weight: bold;
color: #303133;
}
}
.panel-tabs {
display: inline-flex;
background: #f4f4f5;
border-radius: 8px;
padding: 3px;
.panel-tab {
padding: 5px 14px;
font-size: 13px;
border-radius: 6px;
cursor: pointer;
color: #606266;
transition: all 0.2s ease;
&.active {
background: #ffffff;
color: #409eff;
font-weight: 600;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}
}
}
.panel-card {
background: #ffffff;
border-radius: 8px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.section-title {
font-size: 13px;
font-weight: 600;
color: #303133;
margin: 0 0 12px;
}
.section-tip {
font-size: 12px;
color: #909399;
margin-bottom: 10px;
}
.img-item {
border: 1px solid #ebeef5;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
.img-item-head {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 13px;
color: #303133;
margin-bottom: 8px;
.img-item-ops {
display: flex;
gap: 4px;
.el-icon {
padding: 4px;
cursor: pointer;
color: #606266;
&:hover {
color: #409eff;
}
&.del:hover {
color: #e1251b;
}
&.disabled {
color: #dcdfe6;
cursor: not-allowed;
}
}
}
}
}
.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: 80px;
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;
}
.link-view {
margin-top: 8px;
font-size: 12px;
color: #909399;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.add-btn {
width: 100%;
}
.setting-row {
display: flex;
align-items: center;
justify-content: space-between;
margin: 14px 0;
&:last-child {
margin-bottom: 0;
}
.setting-label {
font-size: 13px;
color: #606266;
.setting-value {
margin-left: 8px;
color: #303133;
}
}
}
.seg {
display: inline-flex;
border: 1px solid #dcdfe6;
border-radius: 6px;
overflow: hidden;
.seg-item {
padding: 6px 14px;
font-size: 12px;
color: #606266;
cursor: pointer;
border-left: 1px solid #dcdfe6;
transition: all 0.2s ease;
&:first-child {
border-left: none;
}
&.active {
background: #ecf5ff;
color: #409eff;
font-weight: 600;
}
}
}
</style>

View File

@@ -0,0 +1,367 @@
<template>
<div class="panel-menu">
<div class="panel-header">
<span class="panel-name">{{ res.name }}</span>
<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>
</div>
<template v-if="activeTab === 'content'">
<div class="panel-card">
<div class="section-title">菜单设置</div>
<div class="section-tip">每行展示 5 个,建议图标比例一致</div>
<div class="menu-item-card" v-for="(item, index) in res.options.list" :key="index">
<div class="img-item-head">
<span>菜单 {{ index + 1 }}</span>
<div class="img-item-ops">
<el-icon
:class="{ disabled: index === 0 }"
@click="moveItem(index, -1)"
><Top
/></el-icon>
<el-icon
:class="{ disabled: index === res.options.list.length - 1 }"
@click="moveItem(index, 1)"
><Bottom
/></el-icon>
<el-icon class="del" @click="removeItem(index)"><Delete /></el-icon>
</div>
</div>
<div class="menu-item-body">
<div class="icon-preview" v-if="item.img">
<img :src="item.img" alt="" />
</div>
<div class="icon-placeholder" v-else>图标</div>
<div class="menu-item-fields">
<el-input v-model="item.title" size="small" maxlength="6" placeholder="菜单名称" />
<div class="btn-row">
<el-button plain type="primary" size="small" @click="pickImage(item)">选择图标</el-button>
<el-button plain type="primary" size="small" @click="pickLink(item)">选择链接</el-button>
</div>
</div>
</div>
<div class="link-view" v-if="item.url && item.url.___type">
已选链接:{{ linkText(item.url) }}
</div>
</div>
<el-button class="add-btn" plain type="primary" @click="addItem">添加菜单</el-button>
</div>
</template>
<template v-else>
<div class="panel-card">
<div class="section-title">颜色设置</div>
<color-field
v-model="res.options.bgColor1"
label="背景颜色"
:default-value="defaults.bgColor1"
/>
<color-field
v-model="res.options.bgColor2"
label="渐变结束色"
:default-value="defaults.bgColor2"
/>
<div class="section-tip">起始色与结束色一致时为纯色背景</div>
<color-field
v-model="res.options.textColor"
label="文字颜色"
:default-value="defaults.textColor"
/>
</div>
<div class="panel-card">
<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.contentGap" label="内容间距" :min="0" :max="50" />
<slider-field v-model="res.options.pageGap" label="页面间距" :min="0" :max="100" />
</div>
<div class="panel-card">
<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" />
</div>
</template>
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
<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 { Top, Bottom, Delete } from "@element-plus/icons-vue";
import ColorField from "../controls/ColorField.vue";
import SliderField from "../controls/SliderField.vue";
import ossManage from "@/views/sys/oss-manage/ossManage";
import ways from "@/components/lili-dialog/wap.js";
const DEFAULTS = {
bgColor1: "#FFFFFF",
bgColor2: "#FFFFFF",
textColor: "#282828",
paddingTop: 10,
paddingBottom: 10,
paddingX: 12,
contentGap: 10,
pageGap: 10,
borderRadius: 0,
imgRadius: 30,
};
export default {
name: "PanelMenu",
hideDecorateTitle: true,
components: { ColorField, SliderField, ossManage, Top, Bottom, Delete },
props: {
res: { type: Object, required: true },
},
data() {
return {
defaults: DEFAULTS,
activeTab: "content",
tabs: [
{ key: "content", title: "内容" },
{ key: "style", title: "样式" },
],
picModelFlag: false,
currentItem: null,
};
},
created() {
this.res.options = Object.assign({}, DEFAULTS, this.res.options);
if (!Array.isArray(this.res.options.list)) {
this.res.options.list = [];
}
},
methods: {
linkText(url) {
const way = ways.find((e) => e.name === url.___type);
const typeTitle = way ? way.title : "发现";
const name =
url.name || url.goodsName || url.memberName || url.title || "";
return name ? `${typeTitle} - ${name}` : typeTitle;
},
pickImage(item) {
this.currentItem = item;
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.currentItem) {
this.currentItem.img = val.url;
}
},
pickLink(item) {
this.currentItem = item;
const dialog = this.$refs.liliDialog;
dialog.clearGoodsSelected();
dialog.goodsFlag = false;
dialog.flag = true;
},
selectedLink(val) {
if (!this.currentItem) return;
delete val.selected;
delete val.intro;
delete val.mobileIntro;
this.currentItem.url = val;
},
moveItem(index, dir) {
const list = this.res.options.list;
const target = index + dir;
if (target < 0 || target >= list.length) return;
list.splice(target, 1, ...list.splice(index, 1, list[target]));
},
removeItem(index) {
this.res.options.list.splice(index, 1);
},
addItem() {
this.res.options.list.push({
img: "",
url: "",
link: "",
title: "标题",
size: "88*88 (1:1)",
model: "link",
});
},
},
};
</script>
<style scoped lang="scss">
.panel-menu {
background: #f5f7fa;
margin: 0 -20px -120px;
padding: 0 12px 120px;
min-height: calc(100vh - 70px);
box-sizing: border-box;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
margin-bottom: 12px;
padding: 0 14px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
box-sizing: border-box;
.panel-name {
font-size: 14px;
font-weight: bold;
color: #303133;
}
}
.panel-tabs {
display: inline-flex;
background: #f4f4f5;
border-radius: 8px;
padding: 3px;
.panel-tab {
padding: 5px 14px;
font-size: 13px;
border-radius: 6px;
cursor: pointer;
color: #606266;
transition: all 0.2s ease;
&.active {
background: #ffffff;
color: #409eff;
font-weight: 600;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}
}
}
.panel-card {
background: #ffffff;
border-radius: 8px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.section-title {
font-size: 13px;
font-weight: 600;
color: #303133;
margin: 0 0 12px;
}
.section-tip {
font-size: 12px;
color: #909399;
margin-bottom: 10px;
}
.menu-item-card {
border: 1px solid #ebeef5;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
.img-item-head {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 13px;
color: #303133;
margin-bottom: 8px;
.img-item-ops {
display: flex;
gap: 4px;
.el-icon {
padding: 4px;
cursor: pointer;
color: #606266;
&:hover {
color: #409eff;
}
&.del:hover {
color: #e1251b;
}
&.disabled {
color: #dcdfe6;
cursor: not-allowed;
}
}
}
}
.menu-item-body {
display: flex;
gap: 10px;
}
.icon-preview {
width: 44px;
height: 44px;
border: 1px dashed #dcdfe6;
border-radius: 8px;
padding: 3px;
box-sizing: border-box;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
display: block;
border-radius: 6px;
}
}
.icon-placeholder {
width: 44px;
height: 44px;
border: 1px dashed #dcdfe6;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 12px;
flex-shrink: 0;
}
.menu-item-fields {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.btn-row {
display: flex;
gap: 10px;
}
.link-view {
margin-top: 8px;
font-size: 12px;
color: #909399;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.add-btn {
width: 100%;
}
</style>

View File

@@ -8,6 +8,23 @@ import PanelCoupon from "./PanelCoupon.vue";
import PanelArticle from "./PanelArticle.vue";
import PanelShop from "./PanelShop.vue";
import PanelFlexOne from "./PanelFlexOne.vue";
import PanelCarousel from "./PanelCarousel.vue";
import PanelAdCube from "./PanelAdCube.vue";
import PanelMenu from "./PanelMenu.vue";
// 图片魔方及其全部可选风格共用 PanelAdCube
const adCubeTypes = [
"tpl_ad_list",
"leftOneRightTwo",
"leftTwoRightOne",
"flexThree",
"flexTwo",
"topOneBottomTwo",
"topTwoBottomOne",
"flexFive",
"flexFour",
"textPicture",
];
export default {
blank: PanelBlank,
@@ -18,4 +35,7 @@ export default {
article: PanelArticle,
shop: PanelShop,
flexOne: PanelFlexOne,
carousel: PanelCarousel,
menu: PanelMenu,
...Object.fromEntries(adCubeTypes.map((t) => [t, PanelAdCube])),
};

View File

@@ -419,7 +419,7 @@ export const modelData = [
{
type: "tpl_ad_list",
name: "广告魔方",
name: "图片魔方",
icon: "Grid",
group: "base",
img: "md-image",

View File

@@ -32,6 +32,7 @@
v-if="panelComponent"
:is="panelComponent"
:res="res"
@handleDrawer="$emit('handleDrawer', $event)"
/>
<!-- 老组件遗留配置分支 -->
<template v-else>

View File

@@ -196,7 +196,8 @@ export default {
];
return groups.map((g) => ({
...g,
list: this.modelData.filter((m) => m.group === g.key),
// drawer 标记的是图片魔方的可选风格,只在「选择风格」抽屉中出现,不在左侧组件列表展示
list: this.modelData.filter((m) => m.group === g.key && !m.drawer),
}));
},
},
@@ -213,7 +214,12 @@ export default {
},
cloneModel(model) {
const key = Date.now() + "_" + Math.ceil(Math.random() * 99999);
const cloned = JSON.parse(JSON.stringify(model));
// 图片魔方拖入画布时默认使用「左一右二」风格
const source =
model.type === "tpl_ad_list"
? this.modelData.find((m) => m.type === "leftOneRightTwo") || model
: model;
const cloned = JSON.parse(JSON.stringify(source));
return {
...cloned,
key,

View File

@@ -109,7 +109,7 @@
margin: 20px 0;
width: 375px;
background: #ebebeb;
min-height: 700px;
min-height: calc(100vh - 200px);
border-radius: 8px;
overflow: visible;
flex-shrink: 0;

View File

@@ -0,0 +1,28 @@
// 图片魔方各布局预览共用的样式字段(面板配置;老数据无字段时返回空对象,保持原样)
// 画布 375px ≈ 750rpx,预览按数值一半像素呈现
const px = (v) => `${(Number(v) || 0) / 2}px`;
export function cubeWrapperStyle(res) {
if (!res || res.paddingTop === undefined) return {};
const bg =
!res.bgColor2 || res.bgColor1 === res.bgColor2
? res.bgColor1
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
return {
padding: `${px(res.paddingTop)} ${px(res.paddingX)} ${px(res.paddingBottom)}`,
background: bg,
borderRadius: px(res.borderRadius),
marginBottom: px(res.pageGap),
overflow: Number(res.borderRadius) > 0 ? "hidden" : "visible",
};
}
export function cubeImgStyle(res) {
if (!res || res.imgRadius === undefined) return {};
return { borderRadius: px(res.imgRadius), overflow: "hidden" };
}
export function cubeGapStyle(res) {
if (!res || res.contentGap === undefined) return {};
return { gap: px(res.contentGap) };
}

View File

@@ -1,18 +1,86 @@
<template>
<div class="layout">
<el-carousel class="carousel" v-if="res" autoplay :interval="5000" height="150px">
<el-carousel-item v-for="(item, index) in res.list" :key="index">
<div>
<img class="image-mode" :src="item.img" />
</div>
</el-carousel-item>
</el-carousel>
<div class="layout" :style="wrapperStyle">
<div class="banner-box" :style="cardMode ? {} : boxStyle">
<el-carousel
class="carousel"
:class="{ 'card-mode': cardMode }"
v-if="res"
autoplay
:interval="5000"
height="150px"
:type="cardMode ? 'card' : ''"
:indicator-position="customIndicator ? 'none' : ''"
@change="onChange"
>
<el-carousel-item v-for="(item, index) in res.list" :key="index">
<div class="slide-wrap">
<img class="image-mode" :style="boxStyle" :src="item.img" />
</div>
</el-carousel-item>
</el-carousel>
<div
v-if="customIndicator && res.indicatorStyle !== 'none' && res.list.length > 1"
class="indicator"
:class="'pos-' + (res.indicatorPosition || 'center')"
>
<div
v-for="(item, i) in res.list"
:key="i"
class="dot"
:class="[res.indicatorStyle || 'round', { active: i === current }]"
:style="i === current ? { background: indicatorColor } : {}"
></div>
</div>
</div>
</div>
</template>
<script>
export default {
title: "导航栏",
props: ["res"],
data() {
return { current: 0 };
},
computed: {
// 配置过样式字段才启用自定义样式/指示器,老数据保持原样
customIndicator() {
return this.res && this.res.indicatorStyle !== undefined;
},
cardMode() {
return this.res && this.res.swiperStyle === "card";
},
indicatorColor() {
return this.res.indicatorColorMode === "custom" && this.res.indicatorColor
? this.res.indicatorColor
: "#409eff";
},
wrapperStyle() {
const res = this.res || {};
if (res.paddingTop === undefined) return {};
const bg =
!res.bgColor2 || res.bgColor1 === res.bgColor2
? res.bgColor1
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
// 画布 375px ≈ 750rpx,预览按一半像素呈现
const px = (v) => `${(Number(v) || 0) / 2}px`;
return {
padding: `${px(res.paddingTop)} ${px(res.paddingX)} ${px(res.paddingBottom)}`,
background: bg,
borderRadius: px(res.borderRadius),
marginBottom: px(res.pageGap),
};
},
boxStyle() {
const res = this.res || {};
if (res.imgRadius === undefined) return {};
return { borderRadius: `${(Number(res.imgRadius) || 0) / 2}px` };
},
},
methods: {
onChange(index) {
this.current = index;
},
},
};
</script>
<style lang="scss" scoped>
@@ -21,4 +89,61 @@ export default {
.image-mode {
height: 150px;
}
</style>
.banner-box {
position: relative;
overflow: hidden;
}
.slide-wrap {
height: 100%;
padding: 0 3px;
box-sizing: border-box;
}
.image-mode {
width: 100%;
display: block;
overflow: hidden;
}
// 卡片模式:卡片铺满轮播项,去掉 el-carousel 默认卡片白底阴影
.card-mode {
:deep(.el-carousel__item--card) {
background: transparent;
box-shadow: none;
}
:deep(.el-carousel__mask) {
background: transparent;
}
}
.indicator {
position: absolute;
bottom: 8px;
display: flex;
align-items: center;
z-index: 2;
&.pos-left {
left: 12px;
}
&.pos-center {
left: 50%;
transform: translateX(-50%);
}
&.pos-right {
right: 12px;
}
.dot {
background: rgba(255, 255, 255, 0.7);
transition: background 0.3s ease;
&.round {
width: 6px;
height: 6px;
border-radius: 50%;
margin: 0 3px;
}
&.line {
width: 12px;
height: 3px;
border-radius: 2px;
margin: 0 2px;
}
}
}
</style>

View File

@@ -1,18 +1,31 @@
<template>
<div class="layout">
<img class="image-mode" :src="res.list[0].img" alt="">
<img class="image-mode" :src="res.list[1].img" alt="">
<img class="image-mode" :src="res.list[2].img" alt="">
<img class="image-mode" :src="res.list[3].img" alt="">
<img class="image-mode" :src="res.list[4].img" alt="">
<div class="layout" :style="[wrapperStyle, gapStyle]">
<img class="image-mode" :style="imgStyle" :src="res.list[0].img" alt="">
<img class="image-mode" :style="imgStyle" :src="res.list[1].img" alt="">
<img class="image-mode" :style="imgStyle" :src="res.list[2].img" alt="">
<img class="image-mode" :style="imgStyle" :src="res.list[3].img" alt="">
<img class="image-mode" :style="imgStyle" :src="res.list[4].img" alt="">
</div>
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
export default {
title: "五列单行图片模块",
props: ["res"],
computed: {
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
gapStyle() {
return cubeGapStyle(this.res);
},
},
};
</script>
<style lang="scss" scoped>
@@ -25,6 +38,7 @@ export default {
background-size: cover;
}
img {
width: 67px !important;
flex: 1;
min-width: 0;
}
</style>

View File

@@ -1,18 +1,30 @@
<template>
<div class="layout">
<img class="image-mode" :src="res.list[0].img" alt="">
<img class="image-mode" :src="res.list[1].img" alt="">
<img class="image-mode" :src="res.list[2].img" alt="">
<img class="image-mode" :src="res.list[3].img" alt="">
<div class="layout" :style="[wrapperStyle, gapStyle]">
<img class="image-mode" :style="imgStyle" :src="res.list[0].img" alt="">
<img class="image-mode" :style="imgStyle" :src="res.list[1].img" alt="">
<img class="image-mode" :style="imgStyle" :src="res.list[2].img" alt="">
<img class="image-mode" :style="imgStyle" :src="res.list[3].img" alt="">
</div>
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
export default {
title: "四列单行图片模块",
props: ["res"],
computed: {
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
gapStyle() {
return cubeGapStyle(this.res);
},
},
};
</script>
<style lang="scss" scoped>
@@ -28,6 +40,7 @@ export default {
}
img{
width: 84px !important;
flex: 1;
min-width: 0;
}
</style>

View File

@@ -1,16 +1,29 @@
<template>
<div class="layout">
<img class="image-mode" :src="res.list[0].img" alt="">
<img class="image-mode" :src="res.list[1].img" alt="">
<img class="image-mode" :src="res.list[2].img" alt="">
<div class="layout" :style="[wrapperStyle, gapStyle]">
<img class="image-mode" :style="imgStyle" :src="res.list[0].img" alt="">
<img class="image-mode" :style="imgStyle" :src="res.list[1].img" alt="">
<img class="image-mode" :style="imgStyle" :src="res.list[2].img" alt="">
</div>
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
export default {
title: "三列单行图片模块",
props: ["res"],
computed: {
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
gapStyle() {
return cubeGapStyle(this.res);
},
},
};
</script>
<style lang="scss" scoped>
@@ -24,6 +37,7 @@ export default {
background-size: cover;
}
img{
width: 111px !important;
flex: 1;
min-width: 0;
}
</style>

View File

@@ -1,19 +1,32 @@
<template>
<div class="layout">
<div class="flex-two">
<div class="flex-item">
<div class="layout" :style="wrapperStyle">
<div class="flex-two" :style="gapStyle">
<div class="flex-item" :style="imgStyle">
<img :src="res.list[0].img" alt />
</div>
<div class="flex-item">
<div class="flex-item" :style="imgStyle">
<img :src="res.list[1].img" alt />
</div>
</div>
</div>
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
export default {
title: "两张横图",
props: ["res"],
computed: {
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
gapStyle() {
return cubeGapStyle(this.res);
},
},
};
</script>
<style lang="scss" scoped>
@@ -25,10 +38,11 @@ export default {
overflow: hidden;
}
.flex-item {
width: 50%;
flex: 1;
min-width: 0;
> img {
display: block;
max-width: 100%;
width: 100%;
height: 100%;
}
}

View File

@@ -1,14 +1,14 @@
<template>
<div class="layout">
<div class="view-height-150">
<div class="layout" :style="[wrapperStyle, gapStyle]">
<div class="view-height-150" :style="imgStyle">
<img class="image-mode" :src="res.list[0].img" />
</div>
<div class="view-height-150">
<div class="view-height-75">
<div class="view-height-75" :style="imgStyle">
<img class="image-mode" :src="res.list[1].img" alt />
</div>
<div class="view-height-75">
<div class="view-height-75" :style="imgStyle">
<img class="image-mode" :src="res.list[2].img" alt />
</div>
</div>
@@ -16,9 +16,22 @@
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
export default {
title: "左一右二",
props: ["res"]
props: ["res"],
computed: {
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
gapStyle() {
return cubeGapStyle(this.res);
},
},
};
</script>
<style lang="scss" scoped>
@@ -30,5 +43,8 @@ export default {
background-size: cover;
}
.view-height-150 {
min-width: 0;
}
</style>

View File

@@ -1,24 +1,37 @@
<template>
<div class="layout ">
<div class="layout " :style="[wrapperStyle, gapStyle]">
<div class="view-height-150">
<div class="view-height-75">
<div class="view-height-75" :style="imgStyle">
<img class="image-mode" :src="res.list[0].img" alt />
</div>
<div class="view-height-75">
<div class="view-height-75" :style="imgStyle">
<img class="image-mode" :src="res.list[1].img" alt />
</div>
</div>
<div class="view-height-150">
<div class="view-height-150" :style="imgStyle">
<img class="image-mode" style="height:150px;" :src="res.list[2].img" />
</div>
</div>
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
export default {
title: "左二右一",
props: ["res"]
props: ["res"],
computed: {
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
gapStyle() {
return cubeGapStyle(this.res);
},
},
};
</script>
<style lang="scss" scoped>
@@ -31,6 +44,9 @@ export default {
background-size: cover;
}
.view-height-150 {
min-width: 0;
}
.view-height-75 {
.image-mode {
height: 75px;

View File

@@ -1,26 +1,49 @@
<template>
<div class="layout">
<div class="menu-list">
<div class="layout" :style="wrapperStyle">
<div class="menu-list" :class="{ styled: styled }" :style="listStyle">
<div class="menu-item" v-for="(item, index) in res.list" :key="index">
<div>
<div class="menu-img-wrap" :style="imgStyle">
<img class="menu-img" :src="item.img" alt="" />
</div>
<div class="menu-title">{{ item.title }}</div>
<div class="menu-title" :style="titleStyle">{{ item.title }}</div>
</div>
</div>
</div>
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle } from "./cubeStyle";
export default {
props: ["res"],
computed: {
// 面板保存过样式字段才启用自定义样式,老数据保持原样
styled() {
return this.res && this.res.paddingTop !== undefined;
},
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
listStyle() {
return this.styled
? { rowGap: `${(Number(this.res.contentGap) || 0) / 2}px` }
: {};
},
titleStyle() {
return this.styled && this.res.textColor
? { color: this.res.textColor }
: {};
},
},
};
</script>
<style lang="scss" scoped>
@import "./tpl.scss";
.menu-list {
display: flex;
align-items: center;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
> .menu-item {
@@ -28,6 +51,16 @@ export default {
width: 20%;
margin: 10px 0;
}
&.styled > .menu-item {
margin: 0;
}
}
.menu-img-wrap {
width: 44px;
height: 44px;
margin: 0 auto;
overflow: hidden;
}
.menu-img {
width: 44px;

View File

@@ -1,30 +1,30 @@
<template>
<div class="layout">
<div class="view-list">
<div class="layout" :style="[wrapperStyle, gapStyle]">
<div class="view-list" :style="gapStyle">
<div class="view-item">
<div class="-item-tilte">{{res.list[0].title}}</div>
<div class="-item-image">
<div class="-item-image" :style="imgStyle">
<img :src="res.list[0].img" alt />
</div>
</div>
<div class="view-item">
<div class="-item-tilte">{{res.list[1].title}}</div>
<div class="-item-image">
<div class="-item-image" :style="imgStyle">
<img :src="res.list[1].img" alt />
</div>
</div>
</div>
<div class="view-list">
<div class="view-list" :style="gapStyle">
<div class="view-item">
<div class="-item-tilte">{{res.list[2].title}}</div>
<div class="-item-image">
<div class="-item-image" :style="imgStyle">
<img :src="res.list[2].img" alt />
</div>
</div>
<div class="view-item">
<div class="-item-tilte">{{res.list[3].title}}</div>
<div class="-item-image">
<div class="-item-image" :style="imgStyle">
<img :src="res.list[3].img" alt />
</div>
</div>
@@ -33,9 +33,22 @@
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
export default {
title: "文字图片模板",
props: ["res"]
props: ["res"],
computed: {
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
gapStyle() {
return cubeGapStyle(this.res);
},
},
};
</script>
<style lang="scss" scoped>
@@ -75,7 +88,8 @@ export default {
border: 1px solid #ededed;
> .view-item {
width: 50%;
flex: 1;
min-width: 0;
}
> .view-item:nth-of-type(1) {
> .-item-tilte {

View File

@@ -1,14 +1,14 @@
<template>
<div class="layout">
<div class="view-width-100">
<div class="layout" :style="[wrapperStyle, gapStyle]">
<div class="view-width-100" :style="imgStyle">
<img class="image-mode" :src="res.list[0].img" />
</div>
<div class="view-width-100">
<div class="view-height-85">
<div class="view-width-100" :style="gapStyle">
<div class="view-height-85" :style="imgStyle">
<img class="image-mode" :src="res.list[1].img" alt />
</div>
<div class="view-height-85">
<div class="view-height-85" :style="imgStyle">
<img class="image-mode" :src="res.list[2].img" alt />
</div>
</div>
@@ -16,9 +16,22 @@
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
export default {
title: "上一下二",
props: ["res"]
props: ["res"],
computed: {
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
gapStyle() {
return cubeGapStyle(this.res);
},
},
};
</script>
<style lang="scss" scoped>
@@ -28,7 +41,6 @@ export default {
align-items: center;
justify-content: center;
background-size: cover;
height: 170px;
flex-direction: column;
}
img {
@@ -42,6 +54,7 @@ img {
}
.view-height-85 {
padding: 0 1px;
width: 50%;
flex: 1;
min-width: 0;
}
</style>
</style>

View File

@@ -1,23 +1,36 @@
<template>
<div class="layout">
<div class="view-width-100">
<div class="view-height-85">
<div class="layout" :style="[wrapperStyle, gapStyle]">
<div class="view-width-100" :style="gapStyle">
<div class="view-height-85" :style="imgStyle">
<img class="image-mode" :src="res.list[0].img" alt />
</div>
<div class="view-height-85">
<div class="view-height-85" :style="imgStyle">
<img class="image-mode" :src="res.list[1].img" alt />
</div>
</div>
<div class="view-width-100">
<div class="view-width-100" :style="imgStyle">
<img class="image-mode" :src="res.list[2].img" />
</div>
</div>
</template>
<script>
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
export default {
title: "上二下一",
props: ["res"],
computed: {
wrapperStyle() {
return cubeWrapperStyle(this.res);
},
imgStyle() {
return cubeImgStyle(this.res);
},
gapStyle() {
return cubeGapStyle(this.res);
},
},
};
</script>
<style lang="scss" scoped>
@@ -27,7 +40,6 @@ export default {
align-items: center;
justify-content: center;
background-size: cover;
height: 170px;
flex-direction: column;
}
img {
@@ -42,6 +54,7 @@ img {
}
.view-height-85 {
padding: 0 1px;
width: 50%;
flex: 1;
min-width: 0;
}
</style>