- 🏠 简介
- 🔌 api接口
-
🔧 插件开发
- 介绍
- 插件优势
- 新建插件
- 技术栈
- 插件开发规范
- 插件目录结构
- model 层开发
- service 层开发
- api 接口开发
- menu 菜单开发
-
event 事件钩子开发
- 开发指南
- 常用事件
- SiteInit 站点初始化事件
- AddSiteAfter 站点创建后事件
- MemberRegister 会员注册后事件
- MemberLogin 会员登录后事件
- PayCreate 支付创建事件
- PaySuccess 支付成功事件
- RefundSuccess 退款成功事件
- TransferSuccess 转账成功事件
- BottomNavigation 底部导航事件
- NoticeData 消息模板数据内容事件
- GetQrcodeOfChannel 创建二维码事件
- ExportDataType 导出数据类型事件
- ExportData 导出数据源事件
- GetPosterType 海报类型事件
- GetPosterData 海报数据事件
- ShowCustomer 应用管理加载事件
- GetWechatTransferTradeScene 微信支付转账场景事件
- ThemeColor 主题风格事件
- initWap 手机端初始化加载事件
- dict 数据字典开发
- layout 自定义布局开发
- job 消息队列和计划任务开发
- notice 消息发送开发
- printer 小票打印模板开发
- export 数据导出开发
- diy 自定义组件/页面装修开发
- diy 自定义表单组件开发
- poster 自定义海报开发
- icon 引入图标
- 支付方式开发
- upgrade 插件版本升级
- 打包插件
- 授权信息变更回调通知
- 官网上架
- 📝 二次开发须知
- 👨💻 二次开发指导
-
🎬 二次开发应用插件视频教程
- 二次开发安装视频教程
- 准备工作与创建插件
- 插件目录整体说明
- 插件admin目录
- 插件app目录说明(adminapi、api、验证器)
- 插件app目录(dict、job)
- 插件app目录说明(lang、listener)
- 插件app目录说明(model、service)
- 插件uniapp目录说明
- 插件开发之后台功能开发(代码生成器)
- 插件开发之uniapp功能开发(api)第一节
- 插件开发之uniapp功能开发(api)第二节
- 插件开发之uniapp功能开发(api)第三节
- 插件安装与打包原理
- 消息队列开发
- 计划任务开发
- DIY组件和自定义页面装修开发
- 支付接口开发
- 插件升级包打包流程以及云编译功能
diy 自定义表单组件
功能介绍
在装修自定义表单时,可以使用表单组件,也可以使用自定义组件,两者都可以搭配使用

框架定义的组件
系统框架已经定义好了一些基础的通用组件(不包含业务), 若无业务需求,开发者可以正常使用,无需重复开发
文件位置:niucloud/app/dict/diy_form/ComponentDict.php

自定义表单组件核心原理
如需了解自定义表单组件的核心原理,开发者可以自行阅读
文件路径:niucloud/core/dict/DiyFormComponent.php
根据当前站点下的应用/插件,找到 diy_form/components.php 文件,合并自定义表单组件列表

前端定义表单组件的编辑属性组件
框架定义的自定义表单组件目录为:admin/src/app/views/diy_form/components

uni-app手机端渲染自定义表单组件
框架定义的自定义组件目录为:uni-app/src/app/components/diy(与自定义组件都在一个目录下)

更新diy-group组件代码核心原理
在使用云编译或者微信小程序云端发布时,会根据当前站点所安装的应用/插件,更新diy-group组件代码
如需了解核心原理,开发者可以自行阅读
文件位置:niucloud/app/service/core/addon/WapTrait.php

开发自定义表单组件步骤
若框架定义的表单组件无法满足需求,可以自行开发表单组件
新建components.php文件
在niucloud/addon/shop/app/dict/diy_form/目录下,新建components.php文件
文件名称必须对应,框架会寻找所有插件下的这个文件,进行加载

组件关键字建议增加自己插件的前缀,防止冲突。例如:shopFormInput、o2oFromTextarea等
关键代码
<?php
return [
'SHOP_FORM_COMPONENT' => [ // 组件分类关键字key,建议大写
'title' => get_lang('dict_diy_form.shop_component_type_basic'), // 组件分类名称
'list' => [ // 组件列表
'FormInput' => [ // 组件关键字key
'title' => '单行文本', // 组件名称
'icon' => 'iconfont icona-danhangwenben-1pc30', // 组件iconfont图标
'path' => 'edit-form-input', // 编辑组件属性名称
'uses' => 0, // 最大添加数量,0为不限制
'sort' => 10002, // 排序号,从小到大
'position' => '', // 组件置顶标识,不能拖拽,可选值:fixed、top_fixed、right_fixed、bottom_fixed、left_fixed
// 组件属性
'template' => [
"textColor" => "#303133", // 文字颜色
'pageStartBgColor' => '#FFFFFF', // 底部背景颜色(开始)
'pageEndBgColor' => '', // 底部背景颜色(结束)
'pageGradientAngle' => 'to bottom', // 渐变角度,从上到下(to bottom)、从左到右(to right)
'componentBgUrl' => '', // 组件背景图片
'componentBgAlpha' => 2, // 组件背景图片的透明度,0~10
"componentStartBgColor" => '', // 组件背景颜色(开始)
"componentEndBgColor" => '', // 组件背景颜色(结束)
"componentGradientAngle" => 'to bottom', // 渐变角度,上下(to bottom)、左右(to right)
"topRounded" => 0, // 组件上圆角
"bottomRounded" => 0, // 组件下圆角
"elementBgColor" => '', // 元素背景颜色
"topElementRounded" => 0,// 元素上圆角
"bottomElementRounded" => 0, // 元素下圆角
"margin" => [
"top" => 8, // 上边距
"bottom" => 8, // 下边距
"both" => 10 // 左右边距
],
],
'value' => [
// 表单的公共属性
'field' => [
'name' => '单行文本', // 字段名称
// 字段说明,支持修改颜色、大小
'remark' => [
'text' => '',
'color' => '#999999',
"fontSize" => 14,
],
'required' => false, // 是否必填 true:是,false:否
'unique' => false, // 内容不可重复提交 true:是,false:否
'autofill' => false, // 自动填充上次填写的内容 true:开启,false:关闭
'privacyProtection' => false, // 隐私保护 true:开启,false:关闭,隐藏逻辑各组件自行处理
'detailComponent' => '', // 用于详情展示,后台会返回默认内容,特殊组件可以重写内容渲染
'cache' => true, // 开启本地数据缓存 true:开启,false:关闭
'default' => '', // 默认值 存储数据类型不同,各组件自行处理
'value' => '', // 字段值 存储数据类型不同,各组件自行处理
],
// todo 表单组件的数据结构,根据业务需求,自行定义,命名规范建议采用小驼峰,字段要尽可能简单,避免太长
],
// 渲染值
'render' => function ($data) {
// todo 渲染内容
return $data;
},
// 转换类型
'convert' => function ($data) {
// todo 处理数据
return $data;
}
]
]
]
];
定义组件的语言包

前端定义编辑属性组件
代码位置:admin/src/addon/shop/views/diy_form/components
根据components.php定义的path路径,需要在前端定义编辑属性组件
例如:path为edit-fom-input,编辑属性组件名称就是:edit-form-input.vue

edit编辑属性组件
框架封装好了表单组件公共设置和样式
关键代码
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<!-- 表单组件 字段内容设置 -->
<slot name="field"></slot>
<!-- 表单组件 其他设置 -->
<slot name="other"></slot>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<!-- 表单组件 字段样式 -->
<slot name="style-field"></slot>
<!-- 组件样式 -->
<slot name="style"></slot>
</div>
</template>
关键代码截图
效果图

组件忽略属性
每个组件可以根据自身业务情况,设置忽略组件样式,防止出现不可控效果
关键代码,为空时表示不忽略

const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['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].field.default) {
if (diyStore.value[index].field.default < 0) {
res.code = false
res.message = t('defaultMustZeroTips')
}
}
return res
}
uni-app手机端定义渲染组件
目录位置:uni-app/src/addon/shop/components/diy
根据components.php定义的组件关键字key,需要在uni-app手机端定义渲染组件
例如:组件关键字key为FormInput。渲染组件名称就是:form-input,将驼峰命名改成横杠 - 分割即可。注意:组件文件名称是小写
关键代码
<template>
<view v-if="diyComponent.viewFormDetail" class="form-item-frame">
<!-- todo 根据业务自行编写渲染表单组件详情的代码 -->
<view>{{ diyComponent.field }}</view>
</view>
<view v-else :style="warpCss" class="form-item-frame">
<!-- todo 根据业务自行编写渲染组件代码 -->
<view>{{ diyComponent }}</view>
<view class="base-layout-one" v-if="diyGlobal.completeLayout == 'style-1'">
<view class="layout-one-label">
<text class="text-overflow-ellipsis" :style="{'color': diyComponent.textColor,'font-size': (diyComponent.fontSize * 2) + 'rpx' ,'font-weight': diyComponent.fontWeight}">{{ diyComponent.field.name }}</text>
<text class="required">{{ diyComponent.field.required ? '*' : '' }}</text>
<text v-if="diyStore.mode == 'decorate' && diyComponent.isHidden" class="is-hidden">{{ t('diyForm.hidden') }}</text>
</view>
<view v-if="diyComponent.field.remark.text" class="layout-one-remark" :style="{ color: diyComponent.field.remark.color, fontSize: (diyComponent.field.remark.fontSize * 2 ) + 'rpx' }">{{ diyComponent.field.remark.text }}</view>
<view class="layout-one-error-message" v-if="errorInfo && !errorInfo.code">{{ errorInfo.message }}</view>
<input type="text" class="layout-one-content" :placeholder="inputPlaceholder"
placeholderClass="layout-one-input-placeholder"
:placeholder-style="{'font-size': (diyComponent.fontSize * 2) + 'rpx' }"
:style="{'color': diyComponent.textColor,'font-size': (diyComponent.fontSize * 2) + 'rpx'}"
v-model="diyComponent.field.value" :disabled="isDisabled" />
<view class="layout-one-attribute-wrap" v-if="inputAttribute().length">
<view v-for="(item,index) in inputAttribute()" :key="index" @click="eventFn(item.type)" class="layout-one-attribute-item">{{ item.title }}</view>
</view>
</view>
<view v-if="diyStore.mode == 'decorate'" class="form-item-mask"></view>
<form-privacy-pop ref="formPrivacyRef" :data="formPrivacyData" />
</view>
</template>
<script setup lang="ts">
// 表单 单行文本组件
import { ref, computed, watch, onMounted } from 'vue';
import { img } from '@/utils/common';
import { t } from '@/locale'
import formPrivacyPop from './../form-privacy-pop/index.vue';
import useDiyStore from '@/app/stores/diy';
const props = defineProps(['component', 'index', 'global']);
const diyStore = useDiyStore();
const errorInfo: any = ref(null);
const formPrivacyRef: any = ref(null);
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const diyGlobal = computed(() => {
return props.global;
})
const inputPlaceholder = computed(() => {
let str = '';
str += diyComponent.value.placeholder
return str;
})
const formPrivacyData = computed(() => {
let str = `${ diyComponent.value.field.name }已开启隐私保护,提交后会部分打码,只有你自己和管理员才能查看完整信息`;
return str;
})
// input属性
const inputAttribute = () => {
let arr = [];
if (diyComponent.value.autofill) {
let obj = {
title: '已自动填充'
};
arr.push(obj);
}
if (diyComponent.value.field.privacyProtection) {
let obj = {
title: '已开启隐私保护',
type: 'privacy'
};
arr.push(obj);
}
arr.forEach((item, index, arr) => {
if (index != (arr.length - 1)) {
let obj = {
title: '|'
};
arr.push(obj);
}
})
return arr;
}
const eventFn = (type: any) => {
if (type == 'privacy') {
// 查看隐私
formPrivacyRef.value.open();
}
}
const warpCss = computed(() => {
let style = '';
style += 'position:relative;';
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${ diyComponent.value.componentGradientAngle },${ diyComponent.value.componentStartBgColor },${ diyComponent.value.componentEndBgColor });`;
else if (diyComponent.value.componentStartBgColor) style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
else if (diyComponent.value.componentEndBgColor) style += 'background-color:' + diyComponent.value.componentEndBgColor + ';';
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;
})
onMounted(() => {
refresh();
// 装修模式下刷新
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'FormInput') {
refresh();
}
}
)
} else {
}
});
const refresh = () => {
// 装修模式下,展示默认值
if (diyStore.mode == 'decorate') {
diyComponent.value.field.value = diyComponent.value.field.default;
} else {
// 实际展示,优先缓存,其次默认值
if (diyComponent.value.field.value == '' && diyComponent.value.field.default) {
diyComponent.value.field.value = diyComponent.value.field.default;
}
}
}
// 表单组件验证
const verify = () => {
const res = { code: true, message: '' }
if (diyComponent.value.field.required && diyComponent.value.field.value == '' && diyStore.mode != 'decorate') {
res.code = false
res.message = `${ inputPlaceholder.value }`;
}
errorInfo.value = res;
return res;
}
// 重置表单组件数据
const reset = () => {
diyComponent.value.field.value = '';
}
const isDisabled = computed(() => {
return diyStore.mode == 'decorate';
});
defineExpose({
verify,
reset
})
</script>
<style lang="scss" scoped>
@import '@/styles/diy_form.scss';
</style>
开发环境下,建议手动修改diy-group组件代码,引入自己开发的自定义组件
文件位置:uni-app/src/addon/components/diy/group/index.vue
关键代码
<template v-if="component.componentName == 'FormInput'">
<diy-form-input ref="diyFormInputRef" :component="component" :global="data.global" :index="index" />
</template>
import diyFormInput from '@/addon/shop/components/diy/form-input/index.vue';
完成上面的操作后,点击添加组件,即可看到渲染后的组件效果
