背景
因为公司有很多落地页,以及埋点数据上报等等需求(数据上报直接插入到对应组件里面啦),所以需要做一个工具使简化同事的工作,也减少了来找开发同学的频率,各自欢喜~
分析需求
因为是投放的落地页,所以需要考虑一定的并发量以及网站的稳定性,为了防止意外发生直接生成静态页面比较好。 最终是要生成一个完整的 html 页面,因为是vue写的,所以最简单的方式就是通过 json 去渲染组件啦
我这里使用的技术栈是 vite + ts + vue-router
技术实现
- 左侧基础组件,点击添加基础组件 基础组件通过对象的方式去存储,每点击一次都 push 当前组件 type 到一个数组里面(为了落地页的展示,落地页根据这个数组渲染组件), 每点击一次基础组件,都生成一个唯一的随机 ID(为了存储每一个组件的配置信息),这个随机 ID 对映到中间渲染页面组件,这样编辑组件的时候就可以对应到每一个组件。
配置信息
{
currentComponent: 'button',
listComponents: ['image-9592', 'button-317', 'video-2387']
}
- 中间渲染页面,点击组件弹出配置信息 中间页面各个组件点击的时候,实时记录当前点击的组件ID,弹出右侧相应的配置信息
{
currentSetting: 'setting'
}
- 右侧编辑组件,编辑组件配置 配置各个组件的基本信息,
vuex配置信息
state: {
value: '',
setting: { },
button: {
'button-317': {}
...
},
...
},
- 生成页面,保存所有配置信息生成页面 将页面的配置信息通过请求发给后端同学,后端生成页面,把配置信息添加到 html 页面就大功告成了啦~
最终 html 页面代码,放到服务器上就可以访问啦
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>测试</title>
<script>
let data = {"componentsSetting":{"currentComponent":"button","listComponents":["image-9592","button-317","video-2387"],"saveHtml":"","previewVal":""},"currentCompenentsSetting":{"currentSetting":"setting"},"editSetting":{"value":"setting","setting":{"landPageName":"测试","title":"灵芝孢子粉"},"text":{},"button":{"button-317":{"button":"点击进入京东店铺","color":"#F2B00A","bgcolor":"#B80314","size":"20","align":"center","layoutStyle":"","sizeStyle":"margin-top:0px;margin-right:0px;margin-bottom:0px;margin-left:0px;width:100%;height:39px;line-height:39px;border-radius:27px;color:#F2B00A;font-size:20px;background:#B80314;float:none","h5Url":"https://shop.m.jd.com/?shopId=119921&utm_user=plusmember&gx=RnEwyjVYbjKMy9RG_sYoAXcAFA&ad_od=share&utm_source=androidapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=Wxfriends","schemesUrl":"","transform":[{"translationId":100004,"translationName":"点击按钮","translationTarget":"buy"}],"transformValue":"点击按钮","dividerSize":[{"title":"宽度","label":"width","value":100,"unit":"%"},{"title":"高度","label":"height","value":39,"min":32},{"title":"圆角","label":"border-radius","value":27,"min":0,"max":50}]}}},"userSetting":{"userInfo":{},"advPageInfo":{}},"post":{"id":"23","advAccountId":"670"}}
window.localStorage.setItem('lp',JSON.stringify(data))
</script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
主要代码贴一下,vuex的就不贴了,相信大家都会用~
basic button code
<template>
<div style="display:inline-block;text-align:center" :id="value" :style="sizeStyle" @click="handleTranslate">{{ post.button || '这是按钮'}}</div>
</template>
<script lang="ts">
export default defineComponent({
props: {
value: {
type: String,
default: () => '',
},
},
setup(props) {
const store = useStore()
const route = useRoute()
const state = reactive({
style: '',
button: '',
post: {},
sizeStyle:'',
value: props.value
})
const getData = (d: any) => {
let { value } = props
let res = d && d[value]
if(!res) return
state.post = res
state.sizeStyle = res.sizeStyle
}
const handleTranslate = () => {
let{ h5Url, schemesUrl } = state.post
nextTick(() => {
if(Object.keys(state.post).includes('transform') && (state.post as any).transform){
let { translationId, translationTarget } = (state.post as any).transform[0]
transformEvent(translationTarget, translationId)
}
if(h5Url || schemesUrl){
openApp(state.value, h5Url,schemesUrl)
}
})
}
watch(store.state.editSetting, (val) => {
let { button } = val
getData(button)
});
onMounted(() => {
let { button } = store.state.editSetting
getData(button)
})
return {
...toRefs(state),
handleTranslate
}
},
})
</script>
setting button code
<template>
<div class="text">
<h3>基础设置</h3>
<el-row :gutter="20">
<el-col :span="5">点击跳转</el-col>
<el-col :span="19">
<el-radio v-model="radio" label="1">外链</el-radio>
</el-col>
<el-col :span="5"><div class="i-right-label">网页链接</div></el-col>
<el-col :span="19">
<el-input
placeholder="http://"
v-model="post.h5Url"
@change="postDate"
/>
</el-col>
<el-col :span="5"><div class="i-right-label">直达链接</div></el-col>
<el-col :span="19">
<el-input
placeholder="mttbrowser://url=https://www.qq.com"
v-model="post.schemesUrl"
@change="postDate"
/>
</el-col>
</el-row>
<el-divider></el-divider>
<h3>样式</h3>
<el-row :gutter="20">
<el-col :span="5">按钮文案</el-col>
<el-col :span="19">
<el-input
placeholder="这是按钮"
v-model="post.button"
@change="postDate"
/>
</el-col>
<el-col :span="5"><div class="i-right-label">按钮颜色</div></el-col>
<el-col :span="19">
<div class="i-right-label">文字</div>
<el-color-picker v-model="post.color" size="small" @change="postDate"> </el-color-picker>
<div class="i-right-label" style="margin-left: 30px">背景</div>
<el-color-picker v-model="post.bgcolor" size="small" @change="postDate" show-alpha> </el-color-picker>
</el-col>
<el-col :span="5"><div class="i-right-label">字号</div></el-col>
<el-col :span="19">
<el-select v-model="post.size" placeholder="请选择" style="width:70px" @change="postDate">
<el-option
v-for="item in options"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
</el-col>
</el-row>
<Divider @handleDivider="getDividerSize" :data="dividerSize"/>
<el-divider></el-divider>
<h3>布局</h3>
<el-row :gutter="20">
<el-col :span="5"><div class="i-right-label">对齐方式</div></el-col>
<el-col :span="19">
<el-radio-group v-model="post.align" class="text-align" @change="postDate">
<el-radio-button v-for="item in options2" :key="item" :label="item" value="item"></el-radio-button>
</el-radio-group>
</el-col>
</el-row>
<Divider @handleDivider="getDivider" :data="dividerData"/>
<Transform @handleTranslate="getTranslate" :data="transformData"/>
</div>
</template>
<script lang="ts">
export default defineComponent({
components: { Transform, Divider },
name: 'buttonSetting',
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute()
const clearData = (params) => {
return {
dividerSize: [
{
title: '宽度',
label: 'width',
value: 80,
unit: '%',
},
{
title: '高度',
label: 'height',
value: 32,
min: 32,
},
{
title: '圆角',
label: 'border-radius',
value: 3,
min: 0,
max: 50,
},
],
dividerData: [
{
title: '上边距',
label: 'margin-top',
value: 0
},
{
title: '右边距',
label: 'margin-right',
value: 0
},
{
title: '下边距',
label: 'margin-bottom',
value: 0
},
{
title: '左边距',
label: 'margin-left',
value: 0
},
],
post: {
button: '这是按钮',
color: '#fff',
bgcolor: 'rgba(64, 158, 255, 1)',
size: '16',
align: 'center',
layoutStyle: '',
sizeStyle: '',
h5Url: '',
schemesUrl: '',
},
transform: []
}[params]
}
const state = reactive({
radio: '1',
options: ['12', '14', '16', '18', '20', '24'],
options1: ['normal', 'bold'],
options2: ['left', 'center', 'right'],
post: clearData('post'),
dividerSize: clearData('dividerSize'),
dividerData: clearData('dividerData'),
transformData: clearData('transform'),
})
const value = computed(() => store.state.editSetting.value);
const getData = (d: any) => {
state.dividerData = clearData('dividerData')
state.dividerSize = clearData('dividerSize')
state.transformData = clearData('transform')
state.post = clearData('post')
if(!d || !value.value || !d[value.value]) return
let data = d[value.value]
state.dividerData = data.dividerData
state.dividerSize = data.dividerSize
state.transformData = data.transformValue
state.post = data
}
watch(value, (val) => {
if(val.indexOf('button') < 0) return
let { button } = store.state.editSetting
getData(button)
postDate()
});
const postDate = () => {
let { post } = state
let { color,bgcolor, size, align } = post
state.dividerData = post.dividerData || clearData('dividerData')
state.dividerSize = post.dividerSize || clearData('dividerSize')
let layoutStyle = styleTransform(state.dividerData)
let sizeStyle = styleTransform(state.dividerSize)
let style = `${layoutStyle}${sizeStyle}color:${color};font-size:${size}px;background:${bgcolor};float:${align=='center'?'none':align}`
post.sizeStyle = style
const params = {[value.value]: post}
store.dispatch('editSetting/setButton', params);
}
const getTranslate = (val: object) => {
Object.assign(state.post,val),postDate()
}
const getDivider = (val: object) => {
let { data } = val
Object.assign(state.post,{dividerData: data})
postDate()
}
const getDividerSize = (val: object) => {
let { data } = val
Object.assign(state.post,{dividerSize: data})
postDate()
}
const styleTransform = (data) => {
let str = ''
data.forEach(element => {
str += `${element.label}:${element.value}${element.unit || 'px'};`
if(element.label == 'height') str += `line-height:${element.value}${element.unit || 'px'};`
})
return str
}
onMounted(() => {
let { button } = store.state.editSetting
getData(button)
postDate()
})
return {
...toRefs(state),
postDate,
getTranslate,
getDivider,
getDividerSize
}
},
});
</script>
贴个完成图
有思路了,实现起来也就容易了,完成的时候,后端同学还以为我用了三方插件,哈哈哈,看着挺复杂的,其实仔细分析,就很容易完成了,写这个主要是记录一下,如果各位小伙伴有问题,欢迎来讨论啊~