一次偶然的机会,让我遇见了amis之排错总结(一)

1,487 阅读17分钟

接着上一回了解到amis低代码框架后,这半个月都在去看官网,看源码去学习。

说实话amis的官网对新手不是很友好啊。(抱歉,对比起百度baba)

下面是一些开发中遇到的问题,然后去不断地试错,最终找到解决办法。还在持续学习,望大佬们也能提供一些自己在开发中遇到的问题和解决办法,欢迎在评论区留言。

在js jdk开发中使用amis-ui组件

    let amisLib = amisRequire('amis');  

如果我们在交互过程中不想要它内部提供的内容提示,我们可以自己在全局环境中结合提供的组件配置自己想要的内容提示,我们可以这样。

下面以复制内容后提示内容为例。

copy(content) {
    console.log("content", content)
    amisLib.toast.success('内容已复制到粘贴板');
},

哪些属性可以书写表达式

  • className
className: {
  "text-danger": "this.orderstatus == 0", 
  "text-success":"this.orderstatus == 1"
},
  • 后缀是on的属性 只有存在数据域的组件才可以被传递参数。

页面中一些操作按钮的权限控制(重点

这个需求想了好久,最开始是想着通过定义一个js文件引入到项目中,然后通过visibleOn, hiddenOn等控制显隐的属性做一下判断,但是不管是模板语法${},还是ejs模板语法<%%>,还有直接取值this.showPermit()都是不可以获取,并加以判断的。

{
  type: "button",
  actionType: "ajax",
  // 取值
  label: "<%= data.permits[0].activityName %>",
}

image.png

  // 这个都是可以的
  hiddenOn: "${ls:permits[0][activityValue] == 'sum'}",
  disabledOn: "${ls:permits[0][activityValue] == 'add'}",

上面比较的是sum,返回false。所以会显示 image.png

  hiddenOn: "${data.showPermit('add')}"

image.png 使用ejs语法判断

    hiddenOn: "<%= data.showPermit('add') %>"
    hiddenOn: "<%= data.permits.some(function(item) { return item.activityValue == 'add'}) %>"

image.png image.png

最后发现了onClick事件,他可以直接写js语句来加一判断啊。

onClick: "if(props.showPermit('add')) {return true } else { props.env.notify('info', '暂未权限'); return false}",

上面这种方式就可以在用户点击某个按钮的时候,进行权限校验,然后通过返回值做出提示。

将函数定义在全局的props中即可。

{
  location: history.location,
  showPermit(activityValue) {
    console.log("===activityValue", activityValue)
    const permits = JSON.parse(localStorage.getItem("permits"))
    return permits?.some(item => item.activityValue == activityValue)
  }
},

今天(2022-9-14)又一次发现了新大陆。找到了可以统一处理页面按钮权限的方法了。 我们只需要定义一个js文件,引入页面中。然后在visibleOn属性中使用即可。

真的需要吐槽一下官方文档了。一直都是通过data.函数去获取的。谁知道原来可以直接获取,而且自己还在每个页面的data数据域中写了该权限函数,通过data.函数的方式去获取,还是不行。 image.png 所以就需要多去尝试。

快速创建一个漂亮的crud组件

// 展示和筛选都是依靠这个接口实现
api: '/api/overseatopup',
// 是否将过滤条件的参数同步到地址栏
syncLocation: false,
// 开启查询区域 列表中需要制定searchable属性
autoGenerateFilter: true,
// 设置分页页码属性名
pageField: 'pageNum',
// 设置每页显示多少条数据属性名
perPageField: 'pageSize',
// 分页相关。其实他的官方文档写的也不是很全,所以需要去测试
"footerToolbar": [
    "statistics",
    "switch-per-page",
    "pagination",
],
// 列属性过多,可以点击展开详情来展示其他的列信息。需要在列表中定义breakpoint: "*"这个属性
"footable": true,

添加

 // 显示列和列排序
 headerToolbar: [
     {
         type: 'columns-toggler',
         align: 'left',
         draggable: true,
         icon: 'fas fa-cog',
         overlay: true,
         footerBtnSize: 'sm'
     },
     {
         align: 'right',
         label: '添加',
         size: "md",
         type: 'button',
         actionType: 'dialog',
         level: 'primary',
         dialog: {
             title: '添加记录',
             className: 'dialog-title',
             size: 'lg',
             body: {
                 debug: true,
                 type: 'form',
                 labelWidth: 200,
                 horizontal: {
                     left: 4,
                     right: 6,
                 },
                 // className: 'form-wrapper',
                 api: {
                     url: '/api/add',
                     method: 'get',
                     // method: 'post',
                     data: {
                         ordernum: "${ordernum}", 
                     }
                 },
                 body: [
                     {
                         label: '订单号',
                         type: 'input-text',
                         name: 'ordernum',
                     }
                 ]
             }
         }
     },
 ],

定制个性化的列(最好使用map组件)

因为他的映射可以写html标签。

就那下面这个例子来说status控制,我们在labelMap中就不能使用html标签来定制我们的内容。

    {
        name: 'orderstatus',
        "labelClassName": "text-current text-black",
        label: '充值状态',
        type: 'map',
        map: {
            "0": "<span class='text-danger'><span class='fa fa-check-circle m-1'></span>失败</span>",
            "1": "<span class='text-success'><span class='fa fa-times-circle m-1'></span>成功</span>"
        },

        // "type": "status",
        // "map": {
        //   "0": "fa fa-check-circle text-danger",
        //   "1": "fa fa-times-circle text-success"
        // },
        // 数据映射的时候不能使用过滤器
        // "labelMap": {
        //   "0": "${'<span class='text-danger'>失败</span>' | raw}",
        //   "1": "${'<span class='text-success'>成功</span>' | raw}"
        // },// 下面这部分代替上面
        // "labelMap": {
        //   "0": "失败",
        //   "1": "成功"
        // },
        // // 可以动态配置className属性
        // className: {
        //   "text-danger": "this.orderstatus == 0", 
        //   "text-success":"this.orderstatus == 1"
        // },
        searchable: {
            type: 'select',
            name: 'orderstatus',
            label: '充值状态:',
            placeholder: '请选择充值状态',
            options: [
                {
                    label: '成功',
                    value: '1'
                },
                {
                    label: '失败',
                    value: '0'
                }
            ]
        }
    },

如果想让该列可以复制,那么我们需要在列中定义capyable: true属性。

{
    name: 'ordernum',
    label: '订单号',
    searchable: {
      type: 'input-text',
      name: 'ordernum',
      label: '订单号:',
      placeholder: '请输入订单号'
    },
    copyable: true
},

其他的自定义table每列的样式

"align": "center", // 内容居中对齐
"labelClassName": "font-bold text-primary text-lg", // 设置表头文字样式
// 设置每一个单元格的样式 ,这个表示过滤一筛选确定样式
"classNameExpr": "<%= data.unusetotal > 1000 ? 'text-danger' : '' %>",

编辑和删除(operation组件)

写在列最后的操作项中。

    {
        type: "operation",
        "label": "操作",
        "buttons": [
          {
            "label": "编辑",
            "type": "button",
            "level": "link",
            "actionType": "dialog",
            "dialog": {
              "title": "查看详情",
              size: "lg",
              "body": {
                "type": "form",
                mode: "horizontal",
                horizontal: {
                  left: 4,
                  right: 6
                },
                api: {
                  url: '/api/update',
                  method: 'get',
                  // method: 'post',
                  data: {
                    ordernum: "${ordernum}",
                    id: "${id}"
                  }
                },
                body: [
                  {
                    type: 'hidden',
                    name: 'id',
                  },
                  {
                    label: '订单号',
                    type: 'input-text',
                    name: 'ordernum',
                  },
                ]
              }
            }
          },
          {
            "label": "删除",
            "type": "button",
            level: "link",
            actionType: "ajax",
            "className": "text-danger",
            api: {
              url: "/api/delete",
              method: "get",
              data: {
                id: "${id}",
              }
            }
          }
        ]
    }

actionType中可以设置哪些值

image.png

如何自定义页面的整体样式

做这个后台管理系统的demo,给领导看后,他直接pass了,原因就是颜色太暗。

所以我们可以改变这个框架的css变量。

/* 统一修改内置的css变量 */
:root {
      --Layout-aside-width: 220px;
      /* 全局背景颜色 */
      --body-bg: #f4f6f8;
      --fontFamilyBase: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
      --body-color: #595959;
      /* table背景颜色 */
      --background: #fff;
      --Table-borderColor: #f0f0f0;
      --Table-color: #595959;
      --Table-onHover-bg: #fafafa;
      --Table-onHover-borderColor: #f0f0f0;
      --Table-onHover-color: #595959;
      /* table表头 */
      --Table-thead-color:  #262626;

      /*筛选表单组件背景颜色 */
      --Table-searchableForm-backgroundColor: #fff;
      /* 表单提示文字颜色 */
      --Form-select-placeholderColor: #bfbfbf;
      --Form-input-placeholderColor: #bfbfbf;
      --Form-select-borderColor: #e0e0e0;
      --Form-input-borderColor: #e0e0e0;
      /* --Form-input-color: #000; */

      /* --Form-item-gap: 40px; */
      /* 定义表格标题栏操作项偏移的高度 */
      --table-title-operator-top: 14px;

      /* 加载时的背景图片 */
      --Spinner-bg: url("data:image/svg+xml;base64,Cjxzdmcgd2lkdGg9IjY0cHgiIGhlaWdodD0iNjRweCIgdmlld0JveD0iMCAwIDY0IDY0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPGcgaWQ9Iumhtemdoi0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0ibG9hZGluZyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC41MDAwMDAsIDAuNTAwMDAwKSI+CiAgICAgICAgICAgIDxyZWN0IGlkPSLnn6nlvaIiIHN0cm9rZT0iIzk3OTc5NyIgZmlsbD0iI0Q4RDhEOCIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIwIiB4PSIwIiB5PSIwIiB3aWR0aD0iNjMiIGhlaWdodD0iNjMiPjwvcmVjdD4KICAgICAgICAgICAgPGcgaWQ9Iue8lue7hOWkh+S7vS02IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzLjk4MDAwMCwgMC43ODAwMDApIj4KICAgICAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJmcmFtZSIgc3Ryb2tlPSIjMjQ2OEYyIiBzdHJva2Utd2lkdGg9IjMuMiIgcG9pbnRzPSIyNy41MiA1LjEyIDQ5LjY5MDI1MDMgMTcuOTIgNDkuNjkwMjUwMyA0My41MiAyNy41MiA1Ni4zMiA1LjM0OTc0OTY2IDQzLjUyIDUuMzQ5NzQ5NjYgMTcuOTIiIHN0cm9rZS1kYXNoYXJyYXk9IjE2MCAxNjAiIHN0cm9rZS1kYXNob2Zmc2V0PSIxNjAiPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlIGlkPSJmcmFtZTEiIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNob2Zmc2V0IiBiZWdpbj0iLjQ1cztmcmFtZTIuZW5kIiBkdXI9Ii45cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIxNjAiIHRvPSItMTYwIiBmaWxsPSJmcmVlemUiICBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlIGlkPSJmcmFtZTIiIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNob2Zmc2V0IiBiZWdpbj0iZnJhbWUxLmVuZCIgZHVyPSIuOXMiIHR5cGU9InRyYW5zbGF0ZSIgZnJvbT0iMTYwIiB0bz0iMTYwIiBmaWxsPSJmcmVlemUiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOyAxIiAga2V5U3BsaW5lcz0iLjUgMCAuNSAxIi8+CiAgICAgICAgICAgICAgICA8L3BvbHlnb24+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJjaXJjbGUxIiBmaWxsPSIjMjQ2OEYyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGN4PSIyNy41MiIgY3k9IjQuOCIgcj0iNC44Ij4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlMW9uZSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iMHM7Y2lyY2xlMXR3by5lbmQiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDI2IiB0bz0iMCAwIiBmaWxsPSJmcmVlemUiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOyAxIiAga2V5U3BsaW5lcz0iLjUgMCAuNSAxIi8+CiAgICAgICAgICAgICAgICAgICAgPGFuaW1hdGVUcmFuc2Zvcm0gaWQ9ImNpcmNsZTF0d28iIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgYmVnaW49ImNpcmNsZTFvbmUuZW5kICsgLjcycyIgZHVyPSIuNTRzIiB0eXBlPSJ0cmFuc2xhdGUiIGZyb209IjAgMCIgdG89IjAgMjYiIGZpbGw9ImZyZWV6ZSIgY2FsY01vZGU9InNwbGluZSIga2V5VGltZXM9IjA7IDEiICBrZXlTcGxpbmVzPSIuNSAwIC41IDEiLz4KICAgICAgICAgICAgICAgIDwvY2lyY2xlPgogICAgICAgICAgICAgICAgPGNpcmNsZSBpZD0iY2lyY2xlMiIgZmlsbD0iIzI0NjhGMiIgZmlsbC1ydWxlPSJub256ZXJvIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1MC4yNDAwMDAsIDE3LjI4MDAwMCkgcm90YXRlKC0xODAuMDAwMDAwKSB0cmFuc2xhdGUoLTUwLjI0MDAwMCwgLTE3LjI4MDAwMCkgIiBjeD0iNTAuMjQiIGN5PSIxNy4yOCIgcj0iNC44Ij4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlMm9uZSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iMHM7Y2lyY2xlMnR3by5lbmQiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSItMjIuNSAxMyIgdG89IjAgMCIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGUydHdvIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSJjaXJjbGUyb25lLmVuZCArIC43MnMiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDAiIHRvPSItMjIuNSAxMyIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJjaXJjbGUzIiBmaWxsPSIjMjQ2OEYyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDUwLjI0MDAwMCwgNDMuMjAwMDAwKSByb3RhdGUoOTAuMDAwMDAwKSB0cmFuc2xhdGUoLTUwLjI0MDAwMCwgLTQzLjIwMDAwMCkgIiBjeD0iNTAuMjQiIGN5PSI0My4yIiByPSI0LjgiPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGUzb25lIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSIwcztjaXJjbGUzdHdvLmVuZCIgZHVyPSIuNTRzIiB0eXBlPSJ0cmFuc2xhdGUiIGZyb209Ii0yMi41IC0xMyIgdG89IjAgMCIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGUzdHdvIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSJjaXJjbGUzb25lLmVuZCArIC43MnMiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDAiIHRvPSItMjIuNSAtMTMiIGZpbGw9ImZyZWV6ZSIgY2FsY01vZGU9InNwbGluZSIga2V5VGltZXM9IjA7IDEiICBrZXlTcGxpbmVzPSIuNSAwIC41IDEiLz4KICAgICAgICAgICAgICAgIDwvY2lyY2xlPgogICAgICAgICAgICAgICAgPGNpcmNsZSBpZD0iY2lyY2xlNCIgZmlsbD0iIzI0NjhGMiIgZmlsbC1ydWxlPSJub256ZXJvIiBjeD0iMjcuNTIiIGN5PSI1Ni42NCIgcj0iNC44Ij4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlMW9uZSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iMHM7Y2lyY2xlMXR3by5lbmQiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIC0yNiIgdG89IjAgMCIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGUxdHdvIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSJjaXJjbGUxb25lLmVuZCArIC43MnMiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDAiIHRvPSIwIC0yNiIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJjaXJjbGU1IiBmaWxsPSIjMjQ2OEYyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQuODAwMDAwLCA0My4yMDAwMDApIHJvdGF0ZSgtMTgwLjAwMDAwMCkgdHJhbnNsYXRlKC00LjgwMDAwMCwgLTQzLjIwMDAwMCkgIiBjeD0iNC44IiBjeT0iNDMuMiIgcj0iNC44Ij4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlNW9uZSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iMHM7Y2lyY2xlNXR3by5lbmQiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIyMi41IC0xMyIgdG89IjAgMCIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGU1dHdvIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSJjaXJjbGU1b25lLmVuZCArIC43MnMiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDAiIHRvPSIyMi41IC0xMyIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJjaXJjbGU2IiBmaWxsPSIjMjQ2OEYyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQuODAwMDAwLCAxNy4yODAwMDApIHJvdGF0ZSg5MC4wMDAwMDApIHRyYW5zbGF0ZSgtNC44MDAwMDAsIC0xNy4yODAwMDApICIgY3g9IjQuOCIgY3k9IjE3LjI4IiByPSI0LjgiPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGU2b25lIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSIwcztjaXJjbGU2dHdvLmVuZCIgZHVyPSIuNTRzIiB0eXBlPSJ0cmFuc2xhdGUiIGZyb209IjIyLjUgMTMiIHRvPSIwIDAiIGZpbGw9ImZyZWV6ZSIgY2FsY01vZGU9InNwbGluZSIga2V5VGltZXM9IjA7IDEiICBrZXlTcGxpbmVzPSIuNSAwIC41IDEiLz4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlNnR3byIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iY2lyY2xlNm9uZS5lbmQgKyAuNzJzIiBkdXI9Ii41NHMiIHR5cGU9InRyYW5zbGF0ZSIgZnJvbT0iMCAwIiB0bz0iMjIuNSAxMyIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==")
}

区分事件和动作

看官方文档中很多组件都可以设置事件,看的是一头雾水。不知道如何去操作。

比如下面的dialog组件中的事件和动作。 image.png 看完之后不知道怎么去操作。

其实事件就是写在每个组件的onEvent属性中的。

 onEvent: {
 // 当鼠标移入时触发
  mouseenter: {
    // 配置动作表
    actions: [
      {
        actionType: "toast",
        args: {
          msg: "toast变量"
        }
      }
    ]
  },
  // 当鼠标移除时触发
  mouseleave: {
    // 配置动作表
    actions: [
      {
        actionType: "toast",
        args: {
          msg: "离开"
        }
      }
    ]
  }
}

而动作就是写在每个事件中的actions属性中的actionType。见上。

下面我们来看一个例子。其实他就是通过设置mouseenter, mouseleave, click事件然后配合toast, setValue来提示用户做了哪些操作,并且修改表单中的值。

{
  "type": "page",
  "body": {
    "type": "form",
    id: "form",
    data: {
      username: "zh"
    },
    "body": [
      {
        "type": "input-text",
        "name": "name",
        "label": "姓名:",
        value: "${username}"
      },
      {
        "label": "修改姓名",
        "name": "name",
        "type": "button",
        // "onClick": "props.formStore.setValues({name: 'amis', email: 'amis@baidu.com'});console.log('props', props)",
        onEvent: {
          mouseenter: {
            // 配置动作表
            actions: [
              {
                actionType: "toast",
                args: {
                  msg: "移入",
                }
              },
            ]
          },
          mouseleave: {
            // 配置动作表
            actions: [
              {
                actionType: "toast",
                args: {
                  msg: "离开"
                }
              }
            ]
          },
          click: {
            actions: [
              {
                actionType: "toast",
                args: {
                  msg: "点击了按钮,并且修改了username"
                }
              }, 
              {
                actionType: "setValue",
                // 这个一定需要写上哪个组件的id
                componentId: "form",
                args: {
                  value: {
                    username: "llm"
                  }
                }
              }
            ]
          }
        }
      }
    ]
  }
},

动作和事件测试.gif

并不是所有的组件都可以监听click

目的:想要通过icon组件作为一个复制文本的图标,点击后可以复制文本。但是失败了。

{
    type: "page",
    data: {
      a: "复制的内容"
    },
    body: {
      type: "icon",
      icon: "fa fa-files-o",
      onClick: "console.log('copy.......')",
      "onEvent": {
        "click": {
          "actions": [
            {
              "actionType": "copy",
              "args": {
                "copyFormat": "text/html",
                "content": "${a}"
              }
            }
          ]
        }
      }
    }
},

点击页面的icon没有任何反应。

所以这时只能通过button(action)组件来解决。

 {
    type: "page",
    data: {
      a: "复制的内容"
    },
    body: {
      // type: "icon",
      type: "action",
      label: "",
      icon: "fa fa-files-o",
      onClick: "console.log('copy.......')",
      "onEvent": {
        "click": {
          "actions": [
            {
              "actionType": "copy",
              "args": {
                "copyFormat": "text/html",
                "content": "${a}"
              }
            }
          ]
        }
      }
    }
  },

image.png

关于setValue动作的解释

它主要是更新数据域中数据的值的。其中通过componentId来绑定更新那个组件中的数据域。

如果不写componentId,那么更新的就是自己数据域中的值,如果写了,就是更新其他数据域中的值。

    {
      "type": "page",
      data: {
        a: "llm"
      },
      id: "pageId",
      "body": [
        {
          type: "tpl",
          tpl: "${a}",
        },
        {
          "type": "form",
          onEvent: {
            change: {
              actions: [
                {
                  actionType: "toast",
                  args: {
                    msg: "提示toast"
                  }
                },
                // 直接清空选中的选项
                // {
                //   actionType: "clear",
                // },
  
                {
                  actionType: "setValue",
                  // 这个写的是那个组件id,他就更新那个组件的数据域
                  componentId: "pageId",
                  args: {
                    value: {
                      a: "${select}"
                    }
                  }

                  // 如果没有写,那么它将绑定的是该组件的id。
                  // args: {
                  //   value: {
                  //     select: "a"
                  //   }
                  // }
                }
              ]
            }
          },
          "body": [
            {
              "label": "选项",
              "type": "select",
              "name": "select",
              multiple: true,
              "menuTpl": "<div>${label} 值:${value}, 当前是否选中: ${checked}</div>",
              "options": [
                {
                  "label": "A",
                  "value": "a"
                },
                {
                  "label": "B",
                  "value": "b"
                },
                {
                  "label": "C",
                  "value": "c"
                }
              ]
            }
          ]
        }
      ]
    },

测试setValue中componentId的作用.gif

事件动作中获取的event.data到底是啥

这个我也很迷,结合官网的一个案例,我只知道是当前事件传递的args。

    {
      "type": "page",
      "body": [
        {
          "type": "button",
          "id": "b_001",
          "label": "发一个广播,携带动作参数",
          "className": "mb-2",
          "level": "primary",
          "onEvent": {
            "click": {
              "actions": [
                {
                  "actionType": "broadcast",
                  "eventName": "broadcast_1",
                  "args": {
                    "username": "lvxj",
                    "age": 30
                  },
                  "description": "一个按钮的点击事件"
                }
              ]
            }
          }
        },
        {
          "type": "form",
          "name": "form1",
          "id": "form_001",
          "title": "接收广播事件的参数",
          "debug": true,
          "body": [
            {
              "type": "input-text",
              "id": "form_001_text_01",
              "label": "年龄",
              "name": "age",
              "disabled": false,
              "mode": "horizontal"
            },
            {
              "type": "input-text",
              "id": "form_001_text_02",
              "label": "usernae",
              "name": "username",
              "disabled": false,
              "mode": "horizontal"
            }
          ],
          "data": {
            "username": "amis"
          },
          "onEvent": {
            "broadcast_1": {
              "actions": [
                {
                  "actionType": "reload",
                  "args": {
                    "age": "${event.data.age}",
                    "username": "${event.data.username}"
                  }
                },
                {
                  actionType: "toast",
                  args: {
                    msg: "${event.data | json}"
                  }
                }
              ]
            }
          }
        }
      ]
    },

image.png 所以当我们用到了event这个对象的时候,可以通过这种方式打印出来看一下。

{
  "actionType": "toast",
  "args": {
    "msgType": "info",
    "msg": "${event.data | json}"
  }
}

具体案例请看这里

在事件中发送网络请求数据保存在哪

这里有介绍

这里有一个案例

其实他就是保存在event.data.responseResult中,如果配置了"outputVar"这个属性,那么将保存在这个属性中的变量里。

    {
      "type": "form",
      "id": "form_data_001",
      "title": "用户信息",
      "body": [
        {
          "type": "input-text",
          "label": "名称",
          "name": "age",
          "disabled": false,
          "mode": "horizontal"
        },
        {
          "type": "input-text",
          "label": "作者",
          "name": "author",
          "disabled": false,
          "mode": "horizontal"
        }
      ],
      "actions": [
        {
          "type": "button",
          "label": "去获取表单数据",
          "primary": true,
          "wrapWithPanel": false,
          "onEvent": {
            "click": {
              "actions": [
                {
                  "actionType": "ajax",
                  "args": {
                    "api": "/api/users/del"
                  },
                  // 如果配置了这个属性,那么它将表示返回的信息放在myResult中
                  // 如果没有配置这个属性,那么么人放置在responseResult属性中
                  // "outputVar": "myResult"
                },
                {
                  "actionType": "setValue",
                  "componentId": "form_data_001",
                  "args": {
                    // "value": "${event.data.myResult.other}"
                    "value": "${event.data.responseResult.other}"
                  }
                },
                {
                  "actionType": "toast",
                  "args": {
                    // "msg": "${event.data.myResult | json}"
                    "msg": "${event.data.responseResult | json}"
                  }
                }
              ]
            }
          }
        }
      ]
    },

image.png

阻止默认行为

在动作(actions)中配置这个属性为true即可。

    "preventDefault": true

具体案例请看这里

还有这个案例

表单验证

表单验证的需求很简单,当用户在没有填写必填项时给出提示。需要我们手动定制提示内容。

当提交表单时,如果验证不通过toast中提示的内容。

// 验证的信息
"messages": {
"validateFailed": "请仔细检查表单规则,部分表单项没通过验证"
},

表单项的验证规则和自定义错误提示

{
  "type": "input-text",
  "name": "test",
  "label": "必填",
  required: true, 
  // 表单验证规则
  validations: {
    isRequired: true
  },
  // 验证错误是提示的内容
  "validationErrors": {
    "isRequired": "请填写test"
  }
},

具体请查看这里

这里有对应验证规则的默认的提示信息

在筛选数据时,输入(选中)表单值时,将会触发筛选请求,而无需点击筛选按钮(重点)

因为看我们公司的后台管理系统都有这样一个需求,然后就想着也做一下。但是对于这种高度封装的库,不一定可以实现。于是就试试了。

💢💢💢 星期五晚上测试了很久没有实现,星期六 2022-9-17 来再次尝试。

因为select,input-text这个组件也暴露了一些事件和动作,想着实现这种也很简单,就是监听change事件,然后发送ajax动作。并在此刷新页面,做筛选请求。但是这又是那种问题。在ajax动作中使用reload是不生效的。但是官网明确说了是可以生效的。看这里官方案例 这个是自己理解错了。"reload": "ComponentName"是action组件的属性,而不是任何动作都具有的属性。

然后就想着使用setValue动作吧,把值更新过去,但是也是无济于事的。根本没有变化。

onEvent: {
    change: {
      actions: [
        // 直接在这里调用submit呢 啥也不行
        // {
        //   actionType: "submit",
        // },

        // {
        //   actionType: "ajax",
        //   reload: "loginclosecrud?closestyle=${closestyle}&productid=${productid}",
        //   args: {
        //     api: {
        //       url: "/api/logindata",
        //       method: "get",
        //       data: {
        //         // 在这里可以这样获取值
        //         "closestyle": "${event.data.value}",
        //         productid: "${productid}"
        //       }
        //     }, 
        //   }
        // },
        {
          // 这里刷新的时候会携带很多无用的信息
          // 这里刷新的时候会携带很多无用的信息
          actionType: "reload",
          componentId: "crudId",
          // reload也可以写args参数
          args: {
            closestyle: "${event.data.value}",
            productid: "${productid}"
          }
        },
        // {
        //   actionType: "setValue",
        //   componentId: "crudId",
        //   args: {
        //     value: {
        //       count: "${event.data.responseResult.count}",
        //       rows: "${event.data.responseResult.rows}"
        //     }
        //   }
        // },
        // {
        //   actionType: "toast",
        //   args: {
        //     msg: "${event.data.value | json}"
        //   }
        // }
      ]
    }
  }

讲一下自己试错的过程吧

  • 通过ajax动作,获取数据,并设置reload属性绑定组件的name属性
    // {
    //   actionType: "ajax",
    //   reload: "loginclosecrud?closestyle=${closestyle}&productid=${productid}",
    //   args: {
    //     api: {
    //       url: "/api/logindata",
    //       method: "get",
    //       data: {
    //         // 在这里可以这样获取值
    //         "closestyle": "${event.data.value}",
    //         productid: "${productid}"
    //       }
    //     }, 
    //   }
    // },

上面的做法发现没有刷新指定的组件。

  • 通过setValue动作,发送请求回来的数据。
    // {
    //   actionType: "setValue",
    //   componentId: "crudId",
    //   args: {
    //     value: {
    //       count: "${event.data.responseResult.count}",
    //       rows: "${event.data.responseResult.rows}"
    //     }
    //   }
    // },

上面这种做法,也是没有更新界面数据的。

  • 然后直接使用reload动作,来去刷新界面。
    {
      // 这里刷新的时候会携带很多无用的信息
      actionType: "reload",
      componentId: "crudId",
    },

但是查看控制台,发现,目标组件刷新时会携带当前数据域 (event.data) 中的所有数据。 image.png

  • 然后尝试了一下在刷新目标组件的时候发送需要的数据
    {
      // 这里刷新的时候会携带很多无用的信息
      // 这里刷新的时候会携带很多无用的信息
      actionType: "reload",
      componentId: "crudId",
      // reload也可以写args参数
      args: {
        closestyle: "${event.data.value}",
        productid: "${productid}"
      }
    },

这样就实现了这个需求。

之所以之前没有尝试这种写法,是因为官方文档中,没有指出说reload动作可以携带args。 image.png

这里还需要注意一下,在args中获取数据的时候,如果是在当前表单项获取数据需要通过event.data.value,如果获取其他表单项的值时,直接通过模板语法获取表单的name属性即可。

      args: {
        // 获取当前表单项值
        closestyle: "${event.data.value}",
        // 获取其他表单项值
        productid: "${productid}"
      }

删除按钮的二次确认模态框的修改

原生的太丑了。想要一个轻提示。 image.png 为啥官方不提供一个可以定制的入口呢?我是没有找到,今天看源码也没有找到。

今天(2022-9-15)发现了解决办法,不过不是tooltip组件,而是通过一个定制的弹窗来做二次确认,而不是使用它的confirmText属性。

 body: {
    type: "button",
    level: "link",
    className: "text-danger",
    label: "删除",
    "onEvent": {
      "click": {
        "actions": [
          {
            "actionType": "dialog",
            "dialog": {
              "type": "dialog",
              "title": "",
              "body": [
                {
                  "type": "tpl",
                  "tpl": "<p>确定要删除吗?</p>",
                  "inline": false
                }
              ],
              "onEvent": {
                "confirm": {
                  "actions": [
                    {
                      "actionType": "ajax",
                      args: {
                        api: {
                          method: "get",
                          url: "/api/users/del"
                        }
                      }
                    }
                  ]
                },
                "cancel": {
                  "actions": [
                    {
                      "actionType": "toast",
                      "args": {
                        "msg": "删除失败"
                      }
                    }
                  ]
                }
              }
            }
          }
        ]
      }
    }
  }

image.png

2022-9-15测试发现,删除后不能联动crud组件重新获取数据,这就很无语好吧。然后想着通过reload属性去关联crud组件,来刷新crud组件,让其重新获取数据,凡是无济于事。

"confirm": {
  // 配置监听的动作 event.data.xxx
  "actions": [
    {
      "actionType": "ajax",
      // 在这里增加一个reload去关联crud组件。
      "reload": "loginclosecrud",
      args: {
        api: {
          method: "get",
          url: "/api/logindata/delete",
          data: {
            id: "${id}",
          }
        }
      }
    }
  ]
},

这里可以参考这个。请求成功后,刷新目标组件官方网站可是说了可以,但是就是不行。

解决方案一

但是在一个交流群里,看见了一位老哥的操作,就试了试。结果还真的可以。

  {
    "label": "删除",
    "type": "button",
    loading: false,
    "level": "link",
    "className": "text-danger",
    actionType: "dialog",
    dialog: {
      type: "dialog",
      "title": "",
      actions: [
        {
          type: "button",
          actionType: "cancel",
          label: "取消"
        },
        {
          type: "button",
          actionType: "confirm",
          label: "确认",
          level: "danger"
        }
      ],
      body: [
        {
          type: "tpl",
          tpl: "<p> 确定要删除吗?</p>"
        },
        {
          type: "form",
          api: {
            url: "/api/logindata/delete",
            method: "get",
            data: {
              id: "${id}",
            }
          }
        }
      ]
    },

解决方案二

今天2022-9-16今天看官网发现了一个可以更新页面的动作,并且联动其他组件。

// 这里加上一个reload动作,并绑定crud组件的id
{
  actionType: "reload",
  // 写刷新页面的id
  componentId: "crudId"
}

就是通过制定目的组件的id属性。

"onEvent": {
    // 事件名称
    "confirm": {
        // 配置监听的动作 event.data.xxx
        "actions": [
            {
              "actionType": "ajax",
              args: {
                  api: {
                      method: "get",
                      url: "/api/logindata/delete",
                      data: {
                          id: "${id}",
                      }
                  },
                  // 自定义请求成功或者失败的提示信息。toast。这个只是网络请求中的成功或者失败,
                  // 而不是我们自己点击取消或者确认按钮的成功或者失败。
                  "messages": {
                      "success": "成功了!欧耶",
                      "failed": "失败了呢。。"
                  }
              }
            },
            // 这里加上一个reload动作,并绑定crud组件的id
            {
              actionType: "reload",
              // 写刷新页面的id
              componentId: "crudId"
            }
        ]
    },
    // 点击取消按钮的时候触发
    "cancel": {
        "actions": [
            {
                "actionType": "toast",
                "args": {
                    "msg": "删除失败"
                }
            }
        ]
    }
}

页面加载的loading图标修改。

image.png 加载按钮太暗。

这个可以通过设置css变量来改变

/* 统一修改内置的css变量 */
:root {
  /* 加载时的背景图片 */
  --Spinner-bg: url("data:image/svg+xml;base64,Cjxzdmcgd2lkdGg9IjY0cHgiIGhlaWdodD0iNjRweCIgdmlld0JveD0iMCAwIDY0IDY0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPGcgaWQ9Iumhtemdoi0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0ibG9hZGluZyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC41MDAwMDAsIDAuNTAwMDAwKSI+CiAgICAgICAgICAgIDxyZWN0IGlkPSLnn6nlvaIiIHN0cm9rZT0iIzk3OTc5NyIgZmlsbD0iI0Q4RDhEOCIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIwIiB4PSIwIiB5PSIwIiB3aWR0aD0iNjMiIGhlaWdodD0iNjMiPjwvcmVjdD4KICAgICAgICAgICAgPGcgaWQ9Iue8lue7hOWkh+S7vS02IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzLjk4MDAwMCwgMC43ODAwMDApIj4KICAgICAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJmcmFtZSIgc3Ryb2tlPSIjMjQ2OEYyIiBzdHJva2Utd2lkdGg9IjMuMiIgcG9pbnRzPSIyNy41MiA1LjEyIDQ5LjY5MDI1MDMgMTcuOTIgNDkuNjkwMjUwMyA0My41MiAyNy41MiA1Ni4zMiA1LjM0OTc0OTY2IDQzLjUyIDUuMzQ5NzQ5NjYgMTcuOTIiIHN0cm9rZS1kYXNoYXJyYXk9IjE2MCAxNjAiIHN0cm9rZS1kYXNob2Zmc2V0PSIxNjAiPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlIGlkPSJmcmFtZTEiIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNob2Zmc2V0IiBiZWdpbj0iLjQ1cztmcmFtZTIuZW5kIiBkdXI9Ii45cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIxNjAiIHRvPSItMTYwIiBmaWxsPSJmcmVlemUiICBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlIGlkPSJmcmFtZTIiIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNob2Zmc2V0IiBiZWdpbj0iZnJhbWUxLmVuZCIgZHVyPSIuOXMiIHR5cGU9InRyYW5zbGF0ZSIgZnJvbT0iMTYwIiB0bz0iMTYwIiBmaWxsPSJmcmVlemUiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOyAxIiAga2V5U3BsaW5lcz0iLjUgMCAuNSAxIi8+CiAgICAgICAgICAgICAgICA8L3BvbHlnb24+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJjaXJjbGUxIiBmaWxsPSIjMjQ2OEYyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGN4PSIyNy41MiIgY3k9IjQuOCIgcj0iNC44Ij4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlMW9uZSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iMHM7Y2lyY2xlMXR3by5lbmQiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDI2IiB0bz0iMCAwIiBmaWxsPSJmcmVlemUiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOyAxIiAga2V5U3BsaW5lcz0iLjUgMCAuNSAxIi8+CiAgICAgICAgICAgICAgICAgICAgPGFuaW1hdGVUcmFuc2Zvcm0gaWQ9ImNpcmNsZTF0d28iIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgYmVnaW49ImNpcmNsZTFvbmUuZW5kICsgLjcycyIgZHVyPSIuNTRzIiB0eXBlPSJ0cmFuc2xhdGUiIGZyb209IjAgMCIgdG89IjAgMjYiIGZpbGw9ImZyZWV6ZSIgY2FsY01vZGU9InNwbGluZSIga2V5VGltZXM9IjA7IDEiICBrZXlTcGxpbmVzPSIuNSAwIC41IDEiLz4KICAgICAgICAgICAgICAgIDwvY2lyY2xlPgogICAgICAgICAgICAgICAgPGNpcmNsZSBpZD0iY2lyY2xlMiIgZmlsbD0iIzI0NjhGMiIgZmlsbC1ydWxlPSJub256ZXJvIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1MC4yNDAwMDAsIDE3LjI4MDAwMCkgcm90YXRlKC0xODAuMDAwMDAwKSB0cmFuc2xhdGUoLTUwLjI0MDAwMCwgLTE3LjI4MDAwMCkgIiBjeD0iNTAuMjQiIGN5PSIxNy4yOCIgcj0iNC44Ij4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlMm9uZSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iMHM7Y2lyY2xlMnR3by5lbmQiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSItMjIuNSAxMyIgdG89IjAgMCIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGUydHdvIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSJjaXJjbGUyb25lLmVuZCArIC43MnMiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDAiIHRvPSItMjIuNSAxMyIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJjaXJjbGUzIiBmaWxsPSIjMjQ2OEYyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDUwLjI0MDAwMCwgNDMuMjAwMDAwKSByb3RhdGUoOTAuMDAwMDAwKSB0cmFuc2xhdGUoLTUwLjI0MDAwMCwgLTQzLjIwMDAwMCkgIiBjeD0iNTAuMjQiIGN5PSI0My4yIiByPSI0LjgiPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGUzb25lIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSIwcztjaXJjbGUzdHdvLmVuZCIgZHVyPSIuNTRzIiB0eXBlPSJ0cmFuc2xhdGUiIGZyb209Ii0yMi41IC0xMyIgdG89IjAgMCIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGUzdHdvIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSJjaXJjbGUzb25lLmVuZCArIC43MnMiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDAiIHRvPSItMjIuNSAtMTMiIGZpbGw9ImZyZWV6ZSIgY2FsY01vZGU9InNwbGluZSIga2V5VGltZXM9IjA7IDEiICBrZXlTcGxpbmVzPSIuNSAwIC41IDEiLz4KICAgICAgICAgICAgICAgIDwvY2lyY2xlPgogICAgICAgICAgICAgICAgPGNpcmNsZSBpZD0iY2lyY2xlNCIgZmlsbD0iIzI0NjhGMiIgZmlsbC1ydWxlPSJub256ZXJvIiBjeD0iMjcuNTIiIGN5PSI1Ni42NCIgcj0iNC44Ij4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlMW9uZSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iMHM7Y2lyY2xlMXR3by5lbmQiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIC0yNiIgdG89IjAgMCIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGUxdHdvIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSJjaXJjbGUxb25lLmVuZCArIC43MnMiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDAiIHRvPSIwIC0yNiIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJjaXJjbGU1IiBmaWxsPSIjMjQ2OEYyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQuODAwMDAwLCA0My4yMDAwMDApIHJvdGF0ZSgtMTgwLjAwMDAwMCkgdHJhbnNsYXRlKC00LjgwMDAwMCwgLTQzLjIwMDAwMCkgIiBjeD0iNC44IiBjeT0iNDMuMiIgcj0iNC44Ij4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlNW9uZSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iMHM7Y2lyY2xlNXR3by5lbmQiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIyMi41IC0xMyIgdG89IjAgMCIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGU1dHdvIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSJjaXJjbGU1b25lLmVuZCArIC43MnMiIGR1cj0iLjU0cyIgdHlwZT0idHJhbnNsYXRlIiBmcm9tPSIwIDAiIHRvPSIyMi41IC0xMyIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJjaXJjbGU2IiBmaWxsPSIjMjQ2OEYyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQuODAwMDAwLCAxNy4yODAwMDApIHJvdGF0ZSg5MC4wMDAwMDApIHRyYW5zbGF0ZSgtNC44MDAwMDAsIC0xNy4yODAwMDApICIgY3g9IjQuOCIgY3k9IjE3LjI4IiByPSI0LjgiPgogICAgICAgICAgICAgICAgICAgIDxhbmltYXRlVHJhbnNmb3JtIGlkPSJjaXJjbGU2b25lIiBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGJlZ2luPSIwcztjaXJjbGU2dHdvLmVuZCIgZHVyPSIuNTRzIiB0eXBlPSJ0cmFuc2xhdGUiIGZyb209IjIyLjUgMTMiIHRvPSIwIDAiIGZpbGw9ImZyZWV6ZSIgY2FsY01vZGU9InNwbGluZSIga2V5VGltZXM9IjA7IDEiICBrZXlTcGxpbmVzPSIuNSAwIC41IDEiLz4KICAgICAgICAgICAgICAgICAgICA8YW5pbWF0ZVRyYW5zZm9ybSBpZD0iY2lyY2xlNnR3byIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBiZWdpbj0iY2lyY2xlNm9uZS5lbmQgKyAuNzJzIiBkdXI9Ii41NHMiIHR5cGU9InRyYW5zbGF0ZSIgZnJvbT0iMCAwIiB0bz0iMjIuNSAxMyIgZmlsbD0iZnJlZXplIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsgMSIgIGtleVNwbGluZXM9Ii41IDAgLjUgMSIvPgogICAgICAgICAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==")
}

进入页面时,不希望立刻请求

由于数据量过大,我们希望进入页面的时候,不去请求数据,只有当用户点击了查询按钮时,采取请求数据。

对于crud组件来说,这个很方便。只需要配置这个属性即可。

  // 只针对有filter时,当点击了查询时,才会去请求数据
  // 是否初始化的时候拉取数据, 只针对有 filter 的情况, 没有 filter 初始都会拉取数据
  initFetch: false,

注意:这个属性只在设置了filter属性时有效。

表单中表单项的值需要发送请求获取 (重点)

现在又有这么一个需求,当我们点击列表中的某一项编辑时,编辑表单中的表单项需要发送请求获取数据,我们就可以通过input-text(以及各种表单项)组件的addOn属性来发送ajax请求,然后通过事件和动作来更新当前表单项的值,但是经过我的测试发现他写入的值都是上一次请求回来的数据,第一次请求直接会将表单项置空,因为第一次请求没有上一次。

"addOn": {
  "type": "button",
  "label": "生成Key",
  actionType: "ajax",
  "api": {
    "method": "post",
    "url": ""
  },
  onEvent: {
    click: {
      actions: [
        {
          actionType: "setValue",
          // 这个写的是那个组件id,他就更新那个组件的数据域
          componentId: "keyId",
          // 这里如果跟新的不是一个对象,直接把跟新的数据赋值给value即可。
          args: {
            value: "${event.data.memo}"
          }
        },
        {
          actionType: "toast",
          args: {
            msg: "${event.data | json}"
          }
        }
      ]
    }
  }
}

为了实现这个功能,我尝试包裹一层form表单,但是不能很好地更新,而且表单域中的数据不知道会不会在保存编辑的时候获取到,并且如果表单项被设置为必填项,这更是一个大坑,都不可能将请求发出去。所以尝试使用service组件包裹。

{
    type: "service",
    className: "edit-form-service",
    "api": {
      "method": "post",
      "url": "",
      data: {}
    },
    // 不初始化
    initFetch: false,
    body: [
      {
        type: "input-text",
        name: "keyid",
        // 我们可以通过将回写的数据定位默认值,重新生成的数据定为name属性值
        "value": "${keyid}",
        // "disabled": true,
        required: true, 
        // 表单验证规则
        validations: {
          isRequired: true
        },
        // 验证错误是提示的内容
        "validationErrors": {
          "isRequired": "请点击生成keyId"
        },
        // "labelRemark": "请点击生成keyId,禁止用户输入",
        "labelRemark": {
          "type": "remark",
          "content": "请点击生成keyId,禁止自行输入",
          "placement": "bottom"
        },
        "label": "keyId",
        "addOn": {
          "type": "button",
          "label": "生成keyId",
          actionType: "ajax",
          "api": {
            "method": "post",
            "url": "",
            data: {}
          }
        }
      }
    ]
 },

还需要调整一下样式

    /* 编辑表单内部的子请求样式 */
    .edit-form-service {
      margin-bottom: 24px;
    }
    /* 不需要service的alert提示 */
    .edit-form-service .antd-Alert{
      display: none;
    }

上面这个请求方式是不需要携带别的表单项参数去请求的。如果需要携带其他表单项参数请求,我们需要用到《表单项的addOn属性的局限性,及如何替代》 这个解决办法。

表单回写数据

如果更新的数据和表单回写的属性名不一致,我们可以通过value属性绑定回写数据属性,name属性绑定请求数据属性。

    // value属性绑定回写数据(即默认值)
    // name属性绑定请求的表单项数据