自定义组件
概述
自定义组件是 NIUCLOUD 框架提供的页面装修功能模块,分为两类:
框架开发的自定义组件:系统内置的组件,如标题、图片广告、图文导航、魔方、文章、热区、公告等基础组件
插件开发的自定义组件:第三方开发者通过插件机制开发的组件,如商城组件、会员卡组件等业务组件
所有组件均支持拖拽操作和属性配置,实现可视化页面装修 
框架定义的组件
框架开发的自定义组件配置
框架开发的自定义组件配置在 niucloud-core/src/main/resources/core/loader/diy/components.json,如图所示 
配置文件是一个 json,其中第一级对应组件分组(如 BASIC),然后对应分组名称(title),然后 list 列表,其中每一项就是一个组件 
图文导航组件
针对图文导航组件进行说明,具体一个组件的字段如下 
前端定义组件的编辑属性组件
path 是 edit-graphic-nav, 这里指后台 diy 管理端配置的前端代码位置 
这里的图文导航有很多配置选项,比如横排,竖排,每行显示几个等等都有一个配置,这些配置成功之后会组装成 json 传到后台,同时系统配置也有初始化,这些都在后台的配置文件,具体配置字段多少与开发的组件有关 
uni-app 手机端渲染自定义组件
uniapp 作用有两方面,一方面后台配置实时展示,另一方面针对配置后的结果展示在用户端
比如上述图文导航组件 
比如上面图文导航组件名称 GraphicNav,对应 uniapp 的自定义组件名称就是 graphinc-nav,下面就是针对图文导航的文件定义以及页面,文件位置:uni-app/src/app/components/diy/graphic-nav 
开发自定义组件步骤
新建 components.json 文件
在 niucloud-addon/shop/src/main/resources/shop/loader/diy/ 目录下,新建 components.json 文件
文件名称必须对应,框架会寻找所有插件下的这个文件,进行加载 
前端定义编辑属性组件
代码位置:admin/src/addon/shop/views/diy/components
根据 components.json 定义的 path 路径,需要在前端定义编辑属性组件
例如:path 为 edit-goods-list,编辑属性组件文件名称就是:edit-goods-list.vue 
edit编辑属性组件

增加这段代码,即可调用框架封装好的组件公共样式
<!-- 组件样式 -->
<slot name="style"></slot>组件忽略属性
每个组件可以根据自身业务情况,设置忽略组件样式,防止出现不可控效果
关键代码,为空时表示不忽略 
const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['pageBgColor', 'marginTop', 'marginBottom', 'marginBoth', 'componentBgUrl'] // 忽略公共属性目前可忽略的属性如下:
| 属性 | 说明 |
|---|---|
| pageBgColor | 底部背景颜色 |
| componentBgUrl | 组件背景图 |
| componentBgColor | 组件背景颜色 |
| marginTop | 上边距 |
| marginBottom | 下边距 |
| marginBoth | 左右边距 |
| topRounded | 上圆角 |
| bottomRounded | 下圆角 |
组件验证
每个组件可以自定义验证规则,点击保存时会触发
关键代码截图 
{
code: true, // 验证状态,true:通过,false:未通过
message: '' // 提示信息
}// 组件验证
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
if (diyStore.value[index].source == 'category') {
if (diyStore.value[index].goods_category == '') {
res.code = false
res.message = t('goodsCategoryPlaceholder')
}
} else if (diyStore.value[index].source == 'custom') {
if (diyStore.value[index].goods_ids.length == 0) {
res.code = false
res.message = t('goodsPlaceholder')
}
}
return res
}uni-app 手机端定义渲染组件
目录位置:uni-app/src/addon/shop/components/diy
根据 components.json 定义的组件关键字 key,需要在 uni-app 手机端定义渲染组件
例如:组件关键字 key 为 GoodsList 。渲染组件名称就是:goods-list,将驼峰命名改成横杠 - 分割即可。注意:组件文件名称是小写
关键代码
<template>
<view :style="warpCss">
<!-- todo 背景图加遮罩层【可选,若不需要,可以移除】 -->
<view :style="maskLayer"></view>
<!-- todo 根据业务自行编写渲染组件代码 -->
<!-- diyComponent 可以获取当前组件的数据结构,可以打印查看 -->
<view class="diy-comp">{{ diyComponent }}</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick, getCurrentInstance } from 'vue';
import useDiyStore from '@/app/stores/diy';
import { img } from '@/utils/common';
const props = defineProps(['component', 'index']);
const diyStore = useDiyStore();
// 获取当前组件的数据结构
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
// 组件样式
const warpCss = computed(() => {
let style = '';
style += 'position:relative;';
if (diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${ diyComponent.value.componentGradientAngle },${ diyComponent.value.componentStartBgColor },${ diyComponent.value.componentEndBgColor });`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
// 背景图加遮罩层【可选】
const maskLayer = computed(() => {
let style = '';
if (diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${ diyComponent.value.componentBgAlpha / 10 });`;
style += `height:${ height.value }px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
onMounted(() => {
refresh();
// 装修模式下刷新
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
// todo 注意,这里要替换成组件关键字key
if (newValue && newValue.componentName == 'GoodsList') {
refresh();
}
}
)
}
});
const instance = getCurrentInstance();
const height = ref(0)
// 页面onShow调用时,会触发该方法
const refresh = () => {
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-comp').boundingClientRect((data: any) => {
}).exec();
})
}
</script>
<style lang="scss" scoped>
</style>开发环境下,建议手动修改 diy-group 组件代码,引入自己开发的自定义组件
文件位置:uni-app/src/addon/components/diy/group/index.vue 
关键代码
<template v-if="component.componentName == 'GoodsList'">
<diy-goods-list :component="component" :global="data.global" :index="index" />
</template>
import diyGoodsList from '@/addon/shop/components/diy/goods-list/index.vue';完成上面的操作后,点击添加组件,即可看到渲染后的组件效果 
