mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-10 02:49:03 +08:00
feat(manager): 移动端装修编辑器 CRMEB 化迭代
- 轮播图:双 tab 面板(内容/样式)、轮播风格(通栏/卡片)、指示器样式/位置/色调、渐变背景、边距圆角 - 图片魔方:9 种风格收进样式 tab,拖入默认左一右二,内容 tab 固定槽位配置,新增颜色/边距/圆角设置 - 宫格导航:双 tab 面板,菜单管理 + 颜色/边距/圆角设置 - 编辑器整体:灰底卡片模块化、顶栏页面名称编辑、画布操作条/名称标签外置、组件列表三列通高卡片 - 预览模板同步应用各组件样式字段
This commit is contained in:
@@ -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>
|
||||||
@@ -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>
|
||||||
367
manager/src/views/page-decoration/wap/config-panel/PanelMenu.vue
Normal file
367
manager/src/views/page-decoration/wap/config-panel/PanelMenu.vue
Normal 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>
|
||||||
@@ -8,6 +8,23 @@ import PanelCoupon from "./PanelCoupon.vue";
|
|||||||
import PanelArticle from "./PanelArticle.vue";
|
import PanelArticle from "./PanelArticle.vue";
|
||||||
import PanelShop from "./PanelShop.vue";
|
import PanelShop from "./PanelShop.vue";
|
||||||
import PanelFlexOne from "./PanelFlexOne.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 {
|
export default {
|
||||||
blank: PanelBlank,
|
blank: PanelBlank,
|
||||||
@@ -18,4 +35,7 @@ export default {
|
|||||||
article: PanelArticle,
|
article: PanelArticle,
|
||||||
shop: PanelShop,
|
shop: PanelShop,
|
||||||
flexOne: PanelFlexOne,
|
flexOne: PanelFlexOne,
|
||||||
|
carousel: PanelCarousel,
|
||||||
|
menu: PanelMenu,
|
||||||
|
...Object.fromEntries(adCubeTypes.map((t) => [t, PanelAdCube])),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ export const modelData = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
type: "tpl_ad_list",
|
type: "tpl_ad_list",
|
||||||
name: "广告魔方",
|
name: "图片魔方",
|
||||||
icon: "Grid",
|
icon: "Grid",
|
||||||
group: "base",
|
group: "base",
|
||||||
img: "md-image",
|
img: "md-image",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
v-if="panelComponent"
|
v-if="panelComponent"
|
||||||
:is="panelComponent"
|
:is="panelComponent"
|
||||||
:res="res"
|
:res="res"
|
||||||
|
@handleDrawer="$emit('handleDrawer', $event)"
|
||||||
/>
|
/>
|
||||||
<!-- 老组件遗留配置分支 -->
|
<!-- 老组件遗留配置分支 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|||||||
@@ -196,7 +196,8 @@ export default {
|
|||||||
];
|
];
|
||||||
return groups.map((g) => ({
|
return groups.map((g) => ({
|
||||||
...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) {
|
cloneModel(model) {
|
||||||
const key = Date.now() + "_" + Math.ceil(Math.random() * 99999);
|
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 {
|
return {
|
||||||
...cloned,
|
...cloned,
|
||||||
key,
|
key,
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
width: 375px;
|
width: 375px;
|
||||||
background: #ebebeb;
|
background: #ebebeb;
|
||||||
min-height: 700px;
|
min-height: calc(100vh - 200px);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
28
manager/src/views/page-decoration/wap/template/cubeStyle.js
Normal file
28
manager/src/views/page-decoration/wap/template/cubeStyle.js
Normal 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) };
|
||||||
|
}
|
||||||
@@ -1,18 +1,86 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="wrapperStyle">
|
||||||
<el-carousel class="carousel" v-if="res" autoplay :interval="5000" height="150px">
|
<div class="banner-box" :style="cardMode ? {} : boxStyle">
|
||||||
<el-carousel-item v-for="(item, index) in res.list" :key="index">
|
<el-carousel
|
||||||
<div>
|
class="carousel"
|
||||||
<img class="image-mode" :src="item.img" />
|
:class="{ 'card-mode': cardMode }"
|
||||||
</div>
|
v-if="res"
|
||||||
</el-carousel-item>
|
autoplay
|
||||||
</el-carousel>
|
: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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
title: "导航栏",
|
title: "导航栏",
|
||||||
props: ["res"],
|
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>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -21,4 +89,61 @@ export default {
|
|||||||
.image-mode {
|
.image-mode {
|
||||||
height: 150px;
|
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>
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||||
<img class="image-mode" :src="res.list[0].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[0].img" alt="">
|
||||||
<img class="image-mode" :src="res.list[1].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[1].img" alt="">
|
||||||
<img class="image-mode" :src="res.list[2].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[2].img" alt="">
|
||||||
<img class="image-mode" :src="res.list[3].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[3].img" alt="">
|
||||||
<img class="image-mode" :src="res.list[4].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[4].img" alt="">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "五列单行图片模块",
|
title: "五列单行图片模块",
|
||||||
props: ["res"],
|
props: ["res"],
|
||||||
|
computed: {
|
||||||
|
wrapperStyle() {
|
||||||
|
return cubeWrapperStyle(this.res);
|
||||||
|
},
|
||||||
|
imgStyle() {
|
||||||
|
return cubeImgStyle(this.res);
|
||||||
|
},
|
||||||
|
gapStyle() {
|
||||||
|
return cubeGapStyle(this.res);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -25,6 +38,7 @@ export default {
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
width: 67px !important;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||||
<img class="image-mode" :src="res.list[0].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[0].img" alt="">
|
||||||
<img class="image-mode" :src="res.list[1].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[1].img" alt="">
|
||||||
<img class="image-mode" :src="res.list[2].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[2].img" alt="">
|
||||||
<img class="image-mode" :src="res.list[3].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[3].img" alt="">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "四列单行图片模块",
|
title: "四列单行图片模块",
|
||||||
props: ["res"],
|
props: ["res"],
|
||||||
|
computed: {
|
||||||
|
wrapperStyle() {
|
||||||
|
return cubeWrapperStyle(this.res);
|
||||||
|
},
|
||||||
|
imgStyle() {
|
||||||
|
return cubeImgStyle(this.res);
|
||||||
|
},
|
||||||
|
gapStyle() {
|
||||||
|
return cubeGapStyle(this.res);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -28,6 +40,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img{
|
img{
|
||||||
width: 84px !important;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||||
<img class="image-mode" :src="res.list[0].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[0].img" alt="">
|
||||||
<img class="image-mode" :src="res.list[1].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[1].img" alt="">
|
||||||
<img class="image-mode" :src="res.list[2].img" alt="">
|
<img class="image-mode" :style="imgStyle" :src="res.list[2].img" alt="">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "三列单行图片模块",
|
title: "三列单行图片模块",
|
||||||
props: ["res"],
|
props: ["res"],
|
||||||
|
computed: {
|
||||||
|
wrapperStyle() {
|
||||||
|
return cubeWrapperStyle(this.res);
|
||||||
|
},
|
||||||
|
imgStyle() {
|
||||||
|
return cubeImgStyle(this.res);
|
||||||
|
},
|
||||||
|
gapStyle() {
|
||||||
|
return cubeGapStyle(this.res);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -24,6 +37,7 @@ export default {
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
img{
|
img{
|
||||||
width: 111px !important;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="wrapperStyle">
|
||||||
<div class="flex-two">
|
<div class="flex-two" :style="gapStyle">
|
||||||
<div class="flex-item">
|
<div class="flex-item" :style="imgStyle">
|
||||||
<img :src="res.list[0].img" alt />
|
<img :src="res.list[0].img" alt />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item">
|
<div class="flex-item" :style="imgStyle">
|
||||||
<img :src="res.list[1].img" alt />
|
<img :src="res.list[1].img" alt />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "两张横图",
|
title: "两张横图",
|
||||||
props: ["res"],
|
props: ["res"],
|
||||||
|
computed: {
|
||||||
|
wrapperStyle() {
|
||||||
|
return cubeWrapperStyle(this.res);
|
||||||
|
},
|
||||||
|
imgStyle() {
|
||||||
|
return cubeImgStyle(this.res);
|
||||||
|
},
|
||||||
|
gapStyle() {
|
||||||
|
return cubeGapStyle(this.res);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -25,10 +38,11 @@ export default {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.flex-item {
|
.flex-item {
|
||||||
width: 50%;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
> img {
|
> img {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||||
<div class="view-height-150">
|
<div class="view-height-150" :style="imgStyle">
|
||||||
<img class="image-mode" :src="res.list[0].img" />
|
<img class="image-mode" :src="res.list[0].img" />
|
||||||
</div>
|
</div>
|
||||||
<div class="view-height-150">
|
<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 />
|
<img class="image-mode" :src="res.list[1].img" alt />
|
||||||
</div>
|
</div>
|
||||||
<div class="view-height-75">
|
<div class="view-height-75" :style="imgStyle">
|
||||||
<img class="image-mode" :src="res.list[2].img" alt />
|
<img class="image-mode" :src="res.list[2].img" alt />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,9 +16,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "左一右二",
|
title: "左一右二",
|
||||||
props: ["res"]
|
props: ["res"],
|
||||||
|
computed: {
|
||||||
|
wrapperStyle() {
|
||||||
|
return cubeWrapperStyle(this.res);
|
||||||
|
},
|
||||||
|
imgStyle() {
|
||||||
|
return cubeImgStyle(this.res);
|
||||||
|
},
|
||||||
|
gapStyle() {
|
||||||
|
return cubeGapStyle(this.res);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -30,5 +43,8 @@ export default {
|
|||||||
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
.view-height-150 {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,24 +1,37 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout ">
|
<div class="layout " :style="[wrapperStyle, gapStyle]">
|
||||||
<div class="view-height-150">
|
<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 />
|
<img class="image-mode" :src="res.list[0].img" alt />
|
||||||
</div>
|
</div>
|
||||||
<div class="view-height-75">
|
<div class="view-height-75" :style="imgStyle">
|
||||||
<img class="image-mode" :src="res.list[1].img" alt />
|
<img class="image-mode" :src="res.list[1].img" alt />
|
||||||
</div>
|
</div>
|
||||||
</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" />
|
<img class="image-mode" style="height:150px;" :src="res.list[2].img" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "左二右一",
|
title: "左二右一",
|
||||||
props: ["res"]
|
props: ["res"],
|
||||||
|
computed: {
|
||||||
|
wrapperStyle() {
|
||||||
|
return cubeWrapperStyle(this.res);
|
||||||
|
},
|
||||||
|
imgStyle() {
|
||||||
|
return cubeImgStyle(this.res);
|
||||||
|
},
|
||||||
|
gapStyle() {
|
||||||
|
return cubeGapStyle(this.res);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -31,6 +44,9 @@ export default {
|
|||||||
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
.view-height-150 {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
.view-height-75 {
|
.view-height-75 {
|
||||||
.image-mode {
|
.image-mode {
|
||||||
height: 75px;
|
height: 75px;
|
||||||
|
|||||||
@@ -1,26 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="wrapperStyle">
|
||||||
<div class="menu-list">
|
<div class="menu-list" :class="{ styled: styled }" :style="listStyle">
|
||||||
<div class="menu-item" v-for="(item, index) in res.list" :key="index">
|
<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="" />
|
<img class="menu-img" :src="item.img" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">{{ item.title }}</div>
|
<div class="menu-title" :style="titleStyle">{{ item.title }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ["res"],
|
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>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "./tpl.scss";
|
@import "./tpl.scss";
|
||||||
.menu-list {
|
.menu-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
> .menu-item {
|
> .menu-item {
|
||||||
@@ -28,6 +51,16 @@ export default {
|
|||||||
width: 20%;
|
width: 20%;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.styled > .menu-item {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menu-img-wrap {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
margin: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.menu-img {
|
.menu-img {
|
||||||
width: 44px;
|
width: 44px;
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||||
<div class="view-list">
|
<div class="view-list" :style="gapStyle">
|
||||||
<div class="view-item">
|
<div class="view-item">
|
||||||
<div class="-item-tilte">{{res.list[0].title}}</div>
|
<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 />
|
<img :src="res.list[0].img" alt />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-item">
|
<div class="view-item">
|
||||||
<div class="-item-tilte">{{res.list[1].title}}</div>
|
<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 />
|
<img :src="res.list[1].img" alt />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-list">
|
<div class="view-list" :style="gapStyle">
|
||||||
<div class="view-item">
|
<div class="view-item">
|
||||||
<div class="-item-tilte">{{res.list[2].title}}</div>
|
<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 />
|
<img :src="res.list[2].img" alt />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-item">
|
<div class="view-item">
|
||||||
<div class="-item-tilte">{{res.list[3].title}}</div>
|
<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 />
|
<img :src="res.list[3].img" alt />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,9 +33,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "文字图片模板",
|
title: "文字图片模板",
|
||||||
props: ["res"]
|
props: ["res"],
|
||||||
|
computed: {
|
||||||
|
wrapperStyle() {
|
||||||
|
return cubeWrapperStyle(this.res);
|
||||||
|
},
|
||||||
|
imgStyle() {
|
||||||
|
return cubeImgStyle(this.res);
|
||||||
|
},
|
||||||
|
gapStyle() {
|
||||||
|
return cubeGapStyle(this.res);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -75,7 +88,8 @@ export default {
|
|||||||
border: 1px solid #ededed;
|
border: 1px solid #ededed;
|
||||||
|
|
||||||
> .view-item {
|
> .view-item {
|
||||||
width: 50%;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
> .view-item:nth-of-type(1) {
|
> .view-item:nth-of-type(1) {
|
||||||
> .-item-tilte {
|
> .-item-tilte {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||||
<div class="view-width-100">
|
<div class="view-width-100" :style="imgStyle">
|
||||||
<img class="image-mode" :src="res.list[0].img" />
|
<img class="image-mode" :src="res.list[0].img" />
|
||||||
</div>
|
</div>
|
||||||
<div class="view-width-100">
|
<div class="view-width-100" :style="gapStyle">
|
||||||
<div class="view-height-85">
|
<div class="view-height-85" :style="imgStyle">
|
||||||
<img class="image-mode" :src="res.list[1].img" alt />
|
<img class="image-mode" :src="res.list[1].img" alt />
|
||||||
</div>
|
</div>
|
||||||
<div class="view-height-85">
|
<div class="view-height-85" :style="imgStyle">
|
||||||
<img class="image-mode" :src="res.list[2].img" alt />
|
<img class="image-mode" :src="res.list[2].img" alt />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,9 +16,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "上一下二",
|
title: "上一下二",
|
||||||
props: ["res"]
|
props: ["res"],
|
||||||
|
computed: {
|
||||||
|
wrapperStyle() {
|
||||||
|
return cubeWrapperStyle(this.res);
|
||||||
|
},
|
||||||
|
imgStyle() {
|
||||||
|
return cubeImgStyle(this.res);
|
||||||
|
},
|
||||||
|
gapStyle() {
|
||||||
|
return cubeGapStyle(this.res);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -28,7 +41,6 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
height: 170px;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
@@ -42,6 +54,7 @@ img {
|
|||||||
}
|
}
|
||||||
.view-height-85 {
|
.view-height-85 {
|
||||||
padding: 0 1px;
|
padding: 0 1px;
|
||||||
width: 50%;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,23 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||||
<div class="view-width-100">
|
<div class="view-width-100" :style="gapStyle">
|
||||||
<div class="view-height-85">
|
<div class="view-height-85" :style="imgStyle">
|
||||||
<img class="image-mode" :src="res.list[0].img" alt />
|
<img class="image-mode" :src="res.list[0].img" alt />
|
||||||
</div>
|
</div>
|
||||||
<div class="view-height-85">
|
<div class="view-height-85" :style="imgStyle">
|
||||||
<img class="image-mode" :src="res.list[1].img" alt />
|
<img class="image-mode" :src="res.list[1].img" alt />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-width-100">
|
<div class="view-width-100" :style="imgStyle">
|
||||||
<img class="image-mode" :src="res.list[2].img" />
|
<img class="image-mode" :src="res.list[2].img" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./cubeStyle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "上二下一",
|
title: "上二下一",
|
||||||
props: ["res"],
|
props: ["res"],
|
||||||
|
computed: {
|
||||||
|
wrapperStyle() {
|
||||||
|
return cubeWrapperStyle(this.res);
|
||||||
|
},
|
||||||
|
imgStyle() {
|
||||||
|
return cubeImgStyle(this.res);
|
||||||
|
},
|
||||||
|
gapStyle() {
|
||||||
|
return cubeGapStyle(this.res);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -27,7 +40,6 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
height: 170px;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
@@ -42,6 +54,7 @@ img {
|
|||||||
}
|
}
|
||||||
.view-height-85 {
|
.view-height-85 {
|
||||||
padding: 0 1px;
|
padding: 0 1px;
|
||||||
width: 50%;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user