页面
设计思路
- 设计数据结构
- 确定技术路线
- 阅读原型图,确定复用的业务组件
- 与后端沟通可行性
设计数据结构
设计数据结构之前,要考虑 1.后续迭代升级 2.权限字段 3.针对不同组件的配置
/**
* displayName: 中文名称
* name: 英文名称
* type: 表单组件类型
* value: 配置项的值
* options: 配置可选项
*/
// => 页面数据 Object
const JSON_SCHEMA = {
name: '模板内容', // 页面名称
thumb: '', // 页面缩略图
permission: [], // 权限
config: [
// 页面配置信息
{
displayName: '尺寸',
name: 'dimension',
value: [
{
displayName: '屏幕大小',
name: 'recommend',
type: 'select',
value: 0,
options: [
{
name: '375*667',
value: 0
},
{
name: '820*1180',
value: 1
}
]
},
{
displayName: '宽度',
name: 'width',
type: 'number',
value: 375
},
{
displayName: '高度',
name: 'height',
type: 'number',
value: 667
}
]
}
],
components: [
// 组件配置
{
// 组件配置数据
base: {
moduleName: 'Downline',
version: '1.0.0'
},
name: '图片魔方',
id: 1,
height: 200, // 估算最低高度,用户前台分批渲染组件
config: [
{
displayName: '内容配置',
name: 'componentConfig',
value: [
{
displayName: '链接',
name: 'url',
type: 'string',
value: 'https://……'
}
]
},
{
displayName: '样式配置',
name: 'style',
value: [
{
displayName: '背景颜色',
name: 'background-color',
type: 'string',
value: '#fff'
},
{
displayName: '页面边距',
name: 'page-padding',
type: 'number',
value: 16
}
]
}
],
componentData: {
// 组件配置取值
data: [
{
url: ''
}
],
fields: [
{
displayName: '选择图片',
name: 'url',
value: '',
desc: '图片地址'
}
],
staticData: {},
remoteData: {
displayName: '远程数据',
name: 'remoteApi',
value: [
{
displayName: '接口路径',
name: 'apiUrl',
value: ['', '']
}
]
}
}
}
]
}
确定技术路线
使用vuedraggable 可以实现互相拖拽,
分为 3个部分
拖拽业务组件开发:
预览区
配置区
数据控制 : 由于横跨多个组件,所以用vuex, 然后不同的模板由模板的uuid作为key来放在大json里面
实际实现
拖拽业务组件开发
不同的业务组件,实际上是不同的json,这里只需要配置好json,然后for循环json
显示的时候这里原型只要求显示正方形和组件名, 这里可以在for循环里面,添加div对应的样式以及文字即可
我这里新建了一个 COMPONENTS-DATA.js 里面是一个数组 , 数据里面是这些业务组件的配置
[
{
"base": {
"moduleName": "InstructionsText",
"version": "1.0.0"
},
"name": "说明文本",
"id": 1,
"height": 20,
type:"input",
"fontSize":"16",
placeholder:"请输入说明文本",
"config": [
{
"displayName": "对齐方式",
"name": "alignMode",
"type": "radio",
"value": "left",
"options": [
{
"value": "left",
"name": "左对齐"
},
{
"value": "center",
"name": "居中"
},
{
"value": "right",
"name": "右对齐"
}
]
},
{
"displayName": "字体大小",
"name": "fontSize",
"type": "radio",
"value": "16",
"options": [
{
"value": "16",
"name": "16像素"
},
{
"value": "14",
"name": "14像素"
},
{
"value": "12",
"name": "12像素"
}
]
},
{
"displayName": "文本内容",
"name": "text",
value:"",
type:"input",
}
]
},
]
然后将这个js文件引入 vuex中作为一个变量
import COMPONENTSDATA from "@/store/COMPONENTS-DATA";
const state = {
/* 预设模板 */
componentList:COMPONENTSDATA,
/* 模板配置 */
templateJson: {
newTemplateJSON:{
/* 模板名称 */
name: '',
/* 模板图标 */
thumb: '',
/* 被激活的组件索引 */
activeComponentIndex: 0,
/* 模板的页面配置信息,存在这里 */
config: [
{
displayName: '宽度',
name: 'width',
type: 'number',
value: 375
},
{
displayName: '高度',
name: 'height',
type: 'number',
value: 667
}
],
/* 模板中的各个组件 */
components: []
},
//剩下的以uuid作为key,区分不同的模板
},
}
后续维护新增组件的类型 , 就直接在 COMPONENTS-DATA.js 中新增即可
预览区
由于预览区要加载大量的的组件,所以这里使用 懒加载以及 动态组件渲染
预览区的代码实现分为
- 动态组件加载
- vue组件来根据配置json 来生成 实际的显示组件
<!--中间显示区-->
<!-- 因为这个根据配置显示页面的组件,要在多个地方用到,且用来仅显示的用途 , 所以这里单独抽成一个组件
放在了顶层的components文件夹中 -->
<div class="middle">
<H5ShowByJSONCommitment
:value.sync="templateJson.components"
:group="groupB"
></H5ShowByJSONCommitment>
</div>
代码实现 :
<draggable
v-model="templateJsonComponents"
animation="300"
:group="group"
>
<div
v-for="(item,index) in templateJsonComponents" :key="index" @click="handleClick(index)"
:class="{
active: activeComponentIndex === index && !preview,
'preview-no-border' : preview
}"
>
<component
:is="getComponentName(item.base.moduleName)"
:index="index"
:templateJsonKey="templateJsonKey"
></component>
</div>
</draggable>
/* 返回组件名称 */
getComponentName(moduleName) {
return components[moduleName];
}
const components = {
InstructionsText: () => import('@/views/enterpriseCredit/template-management/components/middleContentComponents/DescriptionText.vue'),
divider: () => import('@/views/enterpriseCredit/template-management/components/middleContentComponents/DividerComponents.vue'),
Instructions: () => import('@/views/enterpriseCredit/template-management/components/middleContentComponents/IndicatorComponents.vue'),
Input: () => import('@/views/enterpriseCredit/template-management/components/middleContentComponents/InputComponents.vue'),
};
components: {
...components,
draggable
},
其中 components 中各个vue组件就可以单独放到一个文件夹里面, 针对不同的业务json来开发不同的显示组件
配置区 :
不同的组件虽然有很多配置,且不相同, 但是可以把这些都抽象出来, 不单独针对组件一一对应 , 而是根据 input , radio 这些分类即可 , 具体要配置的东西 , 可以在 COMPONENTS-DATA.js 的config字段来控制,
"config": [
{
"displayName": "对齐方式",
"name": "alignMode",
"type": "radio",
"value": "left",
"options": [
{
"value": "left",
"name": "左对齐"
},
{
"value": "center",
"name": "居中"
},
{
"value": "right",
"name": "右对齐"
}
]
},
{
"displayName": "字体大小",
"name": "fontSize",
"type": "radio",
"value": "16",
"options": [
{
"value": "16",
"name": "16像素"
},
{
"value": "14",
"name": "14像素"
},
{
"value": "12",
"name": "12像素"
}
]
},
{
"displayName": "文本内容",
"name": "text",
value:"",
type:"input",
}
]
然后可以开发一个 配置的vue组件, 循环配置, 来显示不同的 元素即可, 这样可以大大减少配置区的开发
<el-form label-position="left">
<el-form-item v-for="(item,index) in configList" :label="item.displayName" :key="index">
<el-radio-group v-if="item.type === 'radio'" v-model="item.value">
<el-radio v-for="(optionItems,index) in item.options" :key="index" :label="optionItems.value">
{{ optionItems.name }}
</el-radio>
</el-radio-group>
<el-input v-if="item.type === 'input'"
:placeholder="item.placeholder"
v-model="item.value"></el-input>
<el-input v-if="item.type === 'inputPlaceholder'"
:placeholder="item.placeholderValue"
v-model="item.placeholderValue"></el-input>
<el-select v-if="item.type === 'select'"
v-model="item.value"
filterable
remote
:loading="loading"
clearable
:remote-method="(query)=>fetchOptionsForItem(item,index,query)"
placeholder="请选择">
<el-option
v-for="(optionItems,index) in item.options"
:key="index"
:label="optionItems.name"
:value="optionItems.value">
</el-option>
</el-select>
</el-form-item>
</el-form>
配置区要修改的配置,实际上是修改我们的vuex, 显示区仅仅接收vux相应的变量, 这边配置区,可以来使用 computed来 声明一个简洁的变量, 接受配置文件和修改配置文件
configList: {
get() {
let result = {}
if(this.$store.state.template.templateJson[this.templateJsonKey] && this.$store.state.template.templateJson[this.templateJsonKey].components[this.activeComponentIndex]) {
result = this.$store.state.template.templateJson[this.templateJsonKey].components[this.activeComponentIndex].config
}
return result
},
set(value) {
this.$store.commit('template/setComponentValue', {index: this.activeComponentIndex, value,uuid:this.templateJsonKey})
}
},
另外要注意的是配置区 有些下拉框的数据量很大, 这些需要在配置json配置好请求地址, 等配置区mounted之后, 再依次请求相应的地址, 然后将结果放入到 配置json里面(保存配置的时候,同时要注意要去掉这些数据), 这样就可以大大减少要传输的数量了
async fetchOptionsForItem(item, index, queryKey = '') {
const api = item.fetchConfig ? item.fetchConfig.api : null;
// 检查是否已经对这个api发送过请求
// && !this.apiRequestSent[api]
if (api ) {
try {
const response = await request.request({
url: api,
method: 'get',
params: {
...item.fetchConfig.params,
[item.fetchConfig.queryKey]: queryKey,
},
});
// 使用Vue.set来更新options以确保响应性
const options = []
response[item.fetchConfig.resListKey].forEach((optionsItem) => {
options.push({
value: optionsItem[item.fetchConfig.valueKey],
name: optionsItem[item.fetchConfig.nameKey],
...optionsItem,
})
})
this.$set(this.configList[index], 'options', options);
// 标记这个api已请求
this.apiRequestSent[api] = true;
} catch (error) {
console.error('Fetching options error:', error);
// 错误处理
this.$set(this.configList[index], 'options', [{value: 'error', name: '加载失败'}]);
}
}
},
fetchOptionsForConfigList() {
this.configList.forEach((item, index) => {
this.fetchOptionsForItem(item, index);
});
},