开发指南

概述

第三方开发者可以通过开发 DIY 组件来扩展系统的页面装修功能。DIY 组件是指可以添加到自定义页面上的独立功能模块,这些组件既可以供开发者自己使用,也可以开放给其他用户(包括其他开发者和最终用户)使用,极大地增强了系统的扩展性和灵活性

功能介绍

框架支持自定义页面装修,用户可以通过「自由可视化拖拽+组件配置+实时预览」的方式,无需代码开发即可自定义生成页面(如商城首页、活动页、个人中心页等),同时满足开发者扩展自定义组件的需求,兼顾易用性于灵活性,适配多终端(H5/微信小程序)展示场景

装修界面采用三栏式布局:左侧展示组件库供用户选择,中间区域提供实时预览功能,右侧面板用于编辑和配置所选组件的各项属性。

框架开发的自定义页面装修

自定义页面装修核心代码

如需了解自定义页面装修的核心原理,开发者可以自行阅读

文件位置:admin\src\app\views\diy\edit.vue

该文件实现了整个装修界面的交互逻辑、组件管理和实时预览功能

页面装修 store 状态管理代码

装修过程中的状态管理通过 Store 模式实现

文件位置:admin\src\stores\modules\diy.ts

装修数据最终存储到数据表中的结构主要包含两大部分:

global:global 对象为整体页面的数据结构,后续还会持续完善,实现更加灵活的自定义装修

value:value 为组件集合,在装修时添加自定义组件会变化

uni-app 手机端渲染自定义组件 diy-group 组件

在移动端(uni-app)中,自定义页面通过 diy-group 组件进行渲染。该组件负责解析和展示装修数据中的所有组件。

文件位置:uni-app/src/addon/components/diy/group/

自定义组件渲染 store 状态管理代码

该文件负责处理移动端组件的数据获取、解析和渲染逻辑

文件位置:uni-app/src/app/stores/diy.ts

配置本地开发环境

概述

由于自定义页面装修的效果是实时预览的,在本地开发环境时,需要同时运行 admin 后台管理端和 uni-app 手机端

niucloud-web-app/src/main/resources/application.yml 文件中默认设置了手机端的访问地址

配置本地开发环境

如果出现了下面的界面,就要检测 uni-app 手机算有无运行,以及网址是否正确

解决办法

打开 uni-app 下的 .env.development 配置文件,填写配置文件中的 api 请求地址和图片服务器地址

填写好配置文件后,开始执行编译命令。先在指定目录下执行命令,编译h5

编译完成后,将地址填写到页面装修域名配置中,不需要写后面的 wap(例如:http://localhost:5174

刷新后,装修页面就可以正常显示了

自定义组件

概述

自定义组件是 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';

完成上面的操作后,点击添加组件,即可看到渲染后的组件效果

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐