为了便于阅读代码, 已经把测试数据分离出来, 放在了 mock 目录下:
阅读代码的话, 稍微留意一下就好. 本次介绍 RXEditor 界面最重要的部分, 属性输入组件, 该组件可以显示是否有数据被修改, 还可以批量重置到缺省值, 效果如下:
这个界面是动态构建的, 根据 JSON 数据, 动态构建输入界面. 我之前做过一个 PHP larvel+vuetify 的框架, 也是用这个原理, PHP 段代码构造 JSON 数据, VUE 代码根据数据动态构造输入界面. 我用那个框架做了好几个网站和公司自己的订单管理系统, 但是代码需要优化的地方不少, 我自己用起来很方便, 离开源发布还有些距离, 框架的名字叫 Vular,GitHub 地址: https://github.com/vularsoft/vular, 感兴趣的可以看看, 等完成 RXEditor, 就继续这个框架, 给自己加的工作太多了, 时间不够.
本次实现的要比这个框架简单好多, 原理相同. 要理解代码, 需要明白页面元素的结构及命名:
整个输入组件叫 OptionBox, 这个控件可以输入各种属性. OptionBox 下包含若干 OptionGroup, 每个 OptionGroup 就是一个手风琴折叠的子项, OptionBox 是一个手风琴式折叠控件 (该控件的实现方式, 请参看以前的文章),OptionGroup 下包含多个 RxInputRow, 每个 RxInputRow 包含一个输入控件跟一个标签, OptionBox 根据 JSON 数据动态构建, JSON 数据结构如下:
- [
- {
- label:'Row 选项',
- rows:[
- {
- label:'Gutters',
- value:'no-gutters',
- defaultValue:'no-gutters',
- inputName:'RxSwitch',
- props:{
- onValue:'no-gutters',
- offValue:'',
- },
- },
- {
- label:'测试',
- value:'off',
- defaultValue:'off',
- inputName:'RxSwitch',
- props:{
- onValue:'on',
- offValue:'off',
- },
- },
- ],
- },
- {
- label:'文本选项',
- rows:[
- {
- label:'测试 3',
- value:'no-gutters',
- defaultValue:'no-gutters',
- inputName:'RxSwitch',
- props:{
- onValue:'no-gutters',
- offValue:'',
- },
- },
- {
- label:'测试 4',
- value:'off',
- defaultValue:'off',
- inputName:'RxSwitch',
- props:{
- onValue:'on',
- offValue:'off',
- },
- },
- ],
- },
- {
- label:'Bootstrap 工具',
- },
- ]
到目前为止, 我们只实现了一个输入控件: RxSwitch, 所以测试数据中只有这一个控件, 后面会逐步添加下拉列表等其它控件, 到时也会更新测试数据.
代码目录结构:
Inputs 目录下的 RXSwitch.vue 是以前实现的, RXSelect.vue 只是个备忘文件, 下次实现. accordion 目录下是以前实现的手风琴式折叠组件, 这次要用到. 剩下的文件就是本次要实现的.
老习惯, 先看 OptionBox 的调用代码, 看看如何使用这个组件:
<OptionBox v-model="options"></OptionBox>
非常简单, 用 v-model 的方式传入一个 JSON 数据. 它的实现代码:
- <template>
- <SimpleAccordion>
- <OptionGroup v-for="(optionGroup, i) in inputValue" :key="i" v-model="inputValue[i]">
- </OptionGroup>
- </SimpleAccordion>
- </template>
- <script>
- import SimpleAccordion from '../accordion/SimpleAccordion.vue'
- import OptionGroup from './OptionGroup.vue'
- export default {
- name: 'OptionBox',
- components:{
- SimpleAccordion,
- OptionGroup
- },
- props:{
- value:{ default:[] },
- },
- computed:{
- inputValue: {
- get:function() {
- return this.value;
- },
- set:function(val) {
- this.$emit('input', val);
- },
- },
- },
- methods: {
- },
- }
- </script>
使用一个 SimpleAccordion(手风琴式折叠组件) 实现, 内部循环调用 OptionGroup. 给 OptionGroup 传入的 v-model, 是一个 row 数据列表. 它的实现代码:
- <template>
- <CollapsibleItem class="option-item" @itemClick = "itemClick">
- <template #heading>
- {{inputValue.label}}
- <div v-if="changed" class="reset-button" @click="resetAll">{{$t('widgets.reset')}}</div>
- </template>
- <template #body>
- <RxInputRow
- v-for="(row, i) in inputValue.rows"
- :key="i"
- :label = "row.label"
- :inputName = "row.inputName"
- :inputProps = "row.props"
- :defaultValue = "row.defaultValue"
- v-model = "row.value"
- >
- </RxInputRow>
- </template>
- </CollapsibleItem>
- </template>
- <script>
- import CollapsibleItem from '../accordion/CollapsibleItem.vue'
- import RxInputRow from '../inputs/RxInputRow.vue'
- export default {
- name: 'OptionGroup',
- components:{
- CollapsibleItem,
- RxInputRow
- },
- props:{
- value:{ default:{} },
- },
- computed:{
- inputValue: {
- get:function() {
- return this.value;
- },
- set:function(val) {
- this.$emit('input', val);
- },
- },
- changed(){
- for(var i in this.inputValue.rows){
- let row = this.inputValue.rows[i]
- if(row.value !== row.defaultValue){
- return true
- }
- }
- return false
- }
- },
- methods: {
- itemClick(item){
- this.$emit('itemClick', item)
- },
- resetAll(event){
- for(var i in this.inputValue.rows){
- this.inputValue.rows[i].value = this.inputValue.rows[i].defaultValue
- }
- event.stopPropagation()
- }
- },
- }
- </script>
- <style>
- .option-item.collapsible-item .item-heading{
- display: flex;
- flex-flow: row;
- justify-content: space-between;
- }
- .reset-button{
- margin-right:20px;
- color: #bbb;
- }
- </style>
它使用 CollapsibleItem 实现, 所以需要把 CollapsibleItem 的事件通过 $emit 方法转发给父组件, 要不然手风琴折叠就不灵了.
根据计算属性 changed, 确定是否显示重置按钮, 如果有数据跟缺省值不一样, chenged 就是 true, 反之为 false.
重置按钮的处理事件 resetAll, 它把所有的值都设置成默认值. 不得不感叹 VUE 双向数据绑定的强大, RXEditor 第一个版本没有使用 VUE, 用的是原生 JS, 类似功能实现起来非常复杂.
样式随状态改变, 具体参看 CSS 就好.
该组件循环调用 RxInputRow , 注意他的传入参数.
RxInputRow 代码:
- <template>
- <div class="rx-input-row" :class = "changed ?'changed':''">
- <div class="label">{{label}}</div>
- <component
- :is = "inputName"
- v-bind = "inputProps"
- v-model = "inputValue"
- ></component>
- </div>
- </template>
- <script>
- import RxSwitch from './RxSwitch.vue'
- export default {
- name: 'RxInputRow',
- props:{
- label:{ default:'' },
- defaultValue:{ default:'' },
- value:{ default:'' },
- inputProps:{ default:'' },
- inputName:{defalut:'input'},
- },
- components:{
- RxSwitch,
- },
- data () {
- return {
- }
- },
- computed:{
- changed(){
- return this.inputValue !== this.defaultValue
- },
- inputValue: {
- get:function() {
- return this.value;
- },
- set:function(val) {
- this.$emit('input', val);
- },
- },
- },
- methods: {
- },
- }
- </script>
- <style>
- .rx-input-row{
- display: flex;
- flex-flow: row;
- align-items: center;
- }
- .rx-input-row .label{
- display: flex;
- height: 30px;
- align-items: center;
- padding-left: 6px;
- color:#c2c2c2;
- font-size: 12px;
- width: 100px;
- }
- .rx-input-row.changed .label{
- color:#7c9161;
- }
- </style>
如果输入值有变化, 也是通过 changed 计算属性通知界面, CSS 用绿色字体显示标签.
通过 VUE 的 component 动态绑定组件, 可以省很多代码量, 一定要记住的是, 它使用的组件要注册.
这段代码:
- <component
- :is = "inputName"
- v-bind = "inputProps"
- v-model = "inputValue"
- ></component>
就相当于
- <RxSwitch
- v-if = "inputName ==='RxSwitch'" onValue="inputProps.onValue" offValue="inputProps.offValue" v-model ="inputValue"
- ></RxSwitch>
- <RxSelect
- v-if = "inputName ==='RxSelect'" list="inputProps.list" v-model ="inputValue"
- ></RxSelect>
- ......
这样的方式, 需要大量的 if 判断, 代码不好看. 我在 Vular 框架里, 因为要通过 PHP 代码控制每一个 Vuetify 细节, component 动态组件, 也有些不方便, 我直接做了一个自定义组件, 通过重写 render 方法实现, 太深入底层了. 在这里, 能用这样的方式完美实现, 已经很幸福了.
今天作文就写到这里, 感谢耐心阅读, 详细代码, 请参考 GitHub:https://github.com/vularsoft/studio-ui
若有有问题, 请留言交流.
来源: https://www.cnblogs.com/idlewater/p/12442835.html