vue遍历中存在el-form之踩坑记录

Vue
237
0
0
2023-06-04
目录
  • vue遍历存在el-form之踩坑
  • 初版
  • 完善版
  • 抽组件版
  • vue中el-form循环绑定

vue遍历存在el-form之踩坑

初版

<template>
  <div class="message-templete-style">
    <div class="title">短信通知模板
      <el-tooltip popper-class="tooltip-style" content="请到阿里云短信平台申请短信权限" placement="bottom">
        <i class="h-icon-help" :offset="85"></i>
      </el-tooltip></div>
    <el-form :model="numberForm" ref="numberForm" :rules="numberFormRule" label-width="100px" class="number-form">
      <el-form-item label="测试手机号" prop="phoneNumber">
        <el-input v-model="numberForm.phoneNumber"></el-input>
      </el-form-item>
      <el-form-item class="btn">
        <el-button type="primary" @click="submitForm">保存</el-button>
      </el-form-item>
    </el-form>
    <el-tabs v-model="activeName" @tab-click="handleClick">
      <el-tab-pane v-for="item in tabList" :key="item.id" :label="item.name|templateTypeFilter " :name="item.name">
        
        <!-- <el-scrollbar wrap-class="scrollbar-wrap"> -->
        <el-row :gutter="12">
          <el-col v-for="(itemCard, index) in item.cardList" :key="itemCard.id" :xs="8" :sm="8" :md="8" :lg="8" :xl="6">
            <div class="card-wrap">
              <div class="top-style">
                <div class="card-title">{{ itemCard.templateName }}</div>
                <el-button type="text" @click="sendMessage(itemCard.templateCode)" :disabled="itemCard.templateCode ? false: true">发送</el-button>
              </div>
              <div class="content-style">
                <div><h4>通知对象</h4> {{ itemCard.notifyParty | notifyPartyFilter }}</div>
                <div><h4>远程会议</h4> {{ itemCard.remoteMeeting | remoteMeetingFilter }}</div>
                <div><h4>申请模板</h4> 模板名称/内容
                  <el-popover width="300" trigger="hover" popper-class="detail-pop-style">
                    <p>模板名称 <i class="h-icon-copy" @click="copyText(itemCard.templateName, '名称')"></i></p>
                    <div class="title">{{ itemCard.templateName }}</div>
                    <p>模板内容<i class="h-icon-copy" @click="copyText(itemCard.templateContent, '内容')"></i></p>
                    <!-- \n -->
                    <div style="white-space: pre-wrap;">{{ itemCard.templateContent }}</div>
                    <!-- 
 -->
                    <!-- <div v-html="itemCard.templateContent"></div> -->
                    <i slot="reference" class="h-icon-details"></i>
                  </el-popover>
                </div>
                <div class="code-style"><h4>模板code</h4><span class="code-width">{{ itemCard.templateCode || '' }}</span>
                  <el-popover v-model="itemCard.visible" popper-class="code-pop-style">
                    <el-form :model="item" ref="codeForm" label-position="top" class="code-form">
                      <el-form-item label="模板code" :prop="'cardList.' + index + '.templateCodeValue'">
                        <el-input v-model="itemCard.templateCodeValue"></el-input>
                      </el-form-item>
                      <div class="btn-style">
                        <el-button type="primary" @click.native="confirm(index, itemCard)">确定</el-button>
                        <el-button @click="itemCard.visible = false">取消</el-button>
                      </div>
                    </el-form>
                    <i slot="reference" class="h-icon-edit" @click="openInit(itemCard)"></i>
                  </el-popover>
                </div>
              </div>
            </div>
          </el-col>
        </el-row>
        <!-- </el-scrollbar> -->
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script>
import rules from '@/lib/rules';

export default {
  filters: {
    templateTypeFilter: value => {
      switch (value) {
        case 'MEETING_BEFORE':
          return '会前通知';
        case 'MEETING_DURING':
          return '会中通知';
        case 'MEETING_AFTER':
          return '会后通知';
        case 'NOTICE_TIMING':
          return '定时提醒';
        case 'USER_MANAGEMENT':
          return '用户管理';
        default:
          return;
      }
    },
    remoteMeetingFilter: value => {
      switch (value) {
        case 'YES':
          return '有';
        case 'NO':
          return '无';
        default:
          return;
      }
    },
    notifyPartyFilter: value => {
      switch (value) {
        case 'CC':
          return '抄送人';
        case 'MEMBER':
          return '与会人';
        case 'MEMBER_CC':
          return '与会人和抄送人';
        case 'REGISTRANT':
          return '注册人';
        case 'CREATOR':
          return '发起人';
        default:
          return;
      }
    }
  },
  data() {
    return {
      activeName: 'NOTICE_TIMING', // 当前激活的tab
      successPhoneNumber: '', // 保存成功的测试手机号
      tabList: [
        // {
        //   id: 1,
        //   name: 'hqtz',
        //   cardList: [
        //     {
        //       id: 1,
        //       templateName: '预定会议通知1', // ,模板名称
        //       isSend: true, // 是否发送
        //       notifyParty: '发起人', // 通知对象
        //       remoteMeeting: true, // 是否支持远程会议
        //       templateContent: 'xx', // 模板内容
        //       templateCode: 'cc' // 模板code,
        //       visible: false, // 是否开启弹框
        //     }
        //   ]
        // }, {
        //   id: 2,
        //   name: 'hztz',
        //   cardList: []
        // }, {
        //   id: 3,
        //   name: 'hhtz',
        //   cardList: []
        // }, {
        //   id: 4,
        //   name: 'dstz',
        //   cardList: []
        // }, {
        //   id: 5,
        //   name: 'yhgl',
        //   cardList: []
        // }
      ],
      numberForm: { // 测试手机号
        phoneNumber: ''
      },
      numberFormRule: {
        phoneNumber: [rules.required('phoneNumber'), rules.phoneNumber()]
      }
      // codeFormRule: {
      //   templateCodeValue: [
      //     {
      //       required: true,
      //       message: '请输入模板code'
      //       // trigger: 'blur'
      //     }
      //   ]
      // }
    };
  },
  created() {
    this.getPhoneNumber();
    this.getData();
  },
  methods: {
    // 获取测试手机号
    async getPhoneNumber() {
      const res = await this.$get('meeting/aLiYunSmsSdk/v1/getALiYunSmsTestPhone');
      this.numberForm.phoneNumber = res || '';
      this.successPhoneNumber = res || '';
    },
    // 获取短信模板
    async getData() {
      const res = await this.$get('meeting/template/v1/getSmsTemplateInfo');
      let dataList = res || [];
      const newTabList = dataList.map((item, index) => {
        return {
          id: index + 1,
          name: item.noticePeriodName,
          cardList: item.noticePeriodData ? item.noticePeriodData.map(item_ => {
            return {
              id: item_.id,
              templateName: item_.templateName, // 模板名称
              isSend: true, // 是否发送
              notifyParty: item_.notifyObject, // 通知对象
              remoteMeeting: item_.videoConference, // 是否支持远程会议
              templateContent: item_.templateContent, // 模板内容
              templateCode: item_.templateCode, // 模板code
              templateCodeValue: item_.templateCode, // 输入的code
              visible: false
            };
          }) : []
        };
      });
      console.log(newTabList);
      this.tabList = JSON.parse(JSON.stringify(newTabList));
    },
    // 保存测试手机号
    submitForm() {
      this.$refs.numberForm.validate(async valid => {
        if (valid) {
          const res = await this.$get(`meeting/aLiYunSmsSdk/v1/saveALiYunSmsTestPhone/${this.numberForm.phoneNumber}`);
          this.successPhoneNumber = res || '';
          this.$message({
            message: '手机号保存成功,可用于有code码的模板进行短信测试!',
            type: 'success'
          });
        }
      });
    },
    // 切换tab
    handleClick(tab, event) {
      console.log(tab, event);
    },
    // 发送
    async sendMessage(code) {
      if (!this.successPhoneNumber) {
        return this.$message({
          message: '测试手机号未保存!',
          type: 'warning'
        });
      }
      await this.$get(`meeting/sms/v1/sendSmsTest/${this.successPhoneNumber}/$[code]`);
    },
    // 复制
    copyText(text) {
      console.log(text);//获取input对象
      //创建input标签
      var input = document.createElement('textarea');
      //将input的值设置为需要复制的内容
      input.value = text;
      //添加input标签
      document.body.appendChild(input);
      //选中input标签
      input.select();
      //执行复制
      document.execCommand('copy');
      //成功提示信息
      this.$message({
        message: '已复制成功!',
        type: 'success'
      });
      //移除input标签
      document.body.removeChild(input);
    },
    // 开启code弹框
    openInit(item) {
      item.templateCodeValue = item.templateCode;
    },
    // 确认code
    async confirm(index, item) {
      console.log(index, Boolean(item.templateCodeValue));
      if (!item.templateCodeValue) {
        return this.$message({
          message: '模板code不能为空!',
          type: 'warning'
        });
      }
      if (item.templateCodeValue) {
        await this.$get(`meeting/template/v1/bindingSmsTemplateCode/${item.id}/${item.templateCodeValue}`);
        item.templateCode = item.templateCodeValue;
        item.visible = false;
      }

      this.$refs.codeForm[index].focusFirstField();
    }
    // this.$refs.codeForm[index].validate(async valid => {
    //   if (valid) {
    //     console.log(valid);
    //     console.log(item);
    //     await this.$get(`meeting/template/v1/bindingSmsTemplateCode/${item.id}/${item.templateCodeValue}`);
    //     item.templateCode = item.templateCodeValue;
    //     item.visible = false;
    //   }
    // });
    // }
    
  }
};
</script>
<style lang="less">
.message-templete-style {
  height: 100%;
  // background: rgb(253, 243, 243);
  overflow: hidden;

  .title{
    font-size: 16px;
    color:#000000;
    font-weight: 900;
    padding-bottom: 10px;

    .h-icon-help{
      position: absolute;
      font-size: 26px;
      cursor: pointer;
      padding-left:2px;
      margin-top:-3px;
    }
  }

  .number-form{
    height:64px;
    padding-top:12px;
    background: #F7F8F9;
    border-radius: 2px;
    display: flex;
    .el-form-item{
      margin-bottom: 20px;
    }
    .btn .el-form-item__content{
      margin-left: 10px !important;
    }
  }

  .el-tabs{
    height: calc(100% - 94px);
    // background: #2080F7;
    padding-top:12px;


    // .scrollbar-wrap{
    //   // max-height: 700px;
    // }

    .el-tabs__item{
      padding:0px 32px;
      span{
        opacity: 0.7;
        font-size: 14px;
      }
    }
    .el-tabs__item.is-active{
      color: #000000;
      font-weight: 900;
    }
    .el-tabs__item:hover{
      color: #000000;
      font-weight: 900;
    }
    .el-tabs__active-bar{
      background-color: #2080F7;
    }

    .el-tabs__header{
      padding-bottom: 10px;
    }
    .el-tabs__content{
      height:calc(100% - 51px);
      // background: #e8eef5;
      overflow: auto;

      // 重置滚动条位置
      &::-webkit-scrollbar {
        width: 4px;
        height: 3px;
        background: transparent;
      }
      &::-webkit-scrollbar-thumb {
        background: transparent;
        border-radius: 4px;
      }
      &:hover::-webkit-scrollbar-thumb {
        background: hsla(0, 0%, 53%, 0.5);
      }
      &:hover::-webkit-scrollbar-track {
        background: hsla(0, 0%, 53%, 0);
      }

      .el-col{
        margin-bottom: 12px;
      }

      .card-wrap{
        padding: 16px 20px;
        background: #F7F8F9;
        border-radius: 2px;
        .top-style{
          display: flex;
          justify-content: space-between;
          align-items: center;
          font-size: 14px;
          font-weight: 900;
          line-height: 14px;
          padding-bottom: 20px;
          .card-title{
            color: #000000;
          }
          .el-button {
            border: 0px;
            height:16px;
            margin-top:-4px;
            span{
              line-height: 14px;
              font-size: 14px;
              color:#2080F7;
            }
          }
        }
        .content-style{
          div{
            position: relative;
            opacity: 0.9;
            font-size: 14px;
            color: #000000;
            line-height: 24px;
            h4{
              display: inline-block;
              opacity: 0.5;
              font-size: 14px;
              color: #000000;
              line-height: 24px;
              padding-right:16px;
              font-weight: normal;
            }

            .h-icon-details, .h-icon-edit{
              position: absolute;
              font-size: 22px;
              cursor: pointer;
              padding-left:8px;
            }

            .h-icon-edit{
              margin-top:-12px;
            }
          }
          .code-style{
            display: flex;
            align-items: center;
            .code-width{
              max-width:calc(100% - 104px);
              text-overflow: ellipsis;
              overflow: hidden;
              white-space: nowrap;
              
              line-height: 14px;
              font-size: 14px;
              font-weight: normal;
            }
          }
        }
      }
    }
  }
}
.detail-pop-style{
  background: #FFFFFF;
  box-shadow: 0 0 16px 0 rgba(0,0,0,0.20), 0 16px 32px 0 rgba(0,0,0,0.12);
  p{
    position: relative;
    font-weight: 900;
    font-size: 14px;
    line-height: 14px;
    color: #000000;
    padding-bottom: 8px;

    .h-icon-copy{
      color:#1C7FFF;
      position: absolute;
      font-size: 20px;
      cursor: pointer;
      padding-left:4px;
      margin-top:-3px;
    }
  }
  .title{
    padding-bottom:16px;
  }
  div{
    font-size: 14px;
    color: rgba(0,0,0,0.50);
    line-height: 24px;
  }
}

.code-pop-style{
  background: #FFFFFF;
  box-shadow: 0 0 16px 0 rgba(0,0,0,0.20), 0 16px 32px 0 rgba(0,0,0,0.12);
  .el-form-item__content{
    margin-left:0px;
  }

  .btn-style{
    float: right;
  }
}
.tooltip-style{
  background: #000000;
}
</style>

完善版

<template>
  <div class="message-templete-style">
    <div class="title">短信通知模板
      <el-tooltip popper-class="tooltip-style" content="请到阿里云短信平台申请短信权限" placement="bottom">
        <i class="h-icon-help" :offset="85"></i>
      </el-tooltip></div>
    <el-form :model="numberForm" ref="numberForm" :rules="numberFormRule" label-width="100px" class="number-form">
      <el-form-item label="测试手机号" prop="phoneNumber">
        <el-input v-model="numberForm.phoneNumber"></el-input>
      </el-form-item>
      <el-form-item class="btn">
        <el-button type="primary" @click="submitForm">保存</el-button>
      </el-form-item>
    </el-form>
    <el-tabs v-model="activeName" @tab-click="handleClick">
      <!-- tab头部 -->
      <el-tab-pane v-for="item in tabList" :key="item.key" :label="item.label|templateTypeFilter " :name="item.label"></el-tab-pane>
      <!-- tab内容 -->
      <el-row :gutter="12">
        <el-col v-for="(itemCard) in templateList" :key="itemCard.id" :xs="8" :sm="8" :md="8" :lg="8" :xl="6">
          <div class="card-wrap">
            <div class="top-style">
              <div class="card-title">{{ itemCard.templateName }}</div>
              <el-button type="text" @click="sendMessage(itemCard.templateCode)" :disabled="itemCard.templateCode ? false: true">发送</el-button>
            </div>
            <div class="content-style">
              <div><h4>通知对象</h4> {{ itemCard.notifyParty | notifyPartyFilter }}</div>
              <div><h4>远程会议</h4> {{ itemCard.remoteMeeting | remoteMeetingFilter }}</div>
              <div><h4>申请模板</h4> 模板名称/内容
                <el-popover width="300" trigger="hover" popper-class="detail-pop-style">
                  <p>模板名称 <i class="h-icon-copy" @click="copyText(itemCard.templateName, '名称')"></i></p>
                  <div class="title">{{ itemCard.templateName }}</div>
                  <p>模板内容<i class="h-icon-copy" @click="copyText(itemCard.templateContent, '内容')"></i></p>
                  <div style="white-space: pre-wrap;">{{ itemCard.templateContent }}</div>
                  <i slot="reference" class="h-icon-details"></i>
                </el-popover>
              </div>
              <div class="code-style"><h4>模板code</h4><span class="code-width">{{ itemCard.templateCode || '' }}</span>
                <el-popover v-model="itemCard.visible" popper-class="code-pop-style">
                  <el-form :model="itemCard" :ref="itemCard.id" label-position="top" class="code-form">
                    <el-form-item label="模板code" prop="templateCodeValue" :rules="codeFormRule.templateCodeValue">
                      <el-input v-model="itemCard.templateCodeValue"></el-input>
                    </el-form-item>
                    <div class="btn-style">
                      <el-button type="primary" @click="confirm(itemCard)">确定</el-button>
                      <el-button @click="itemCard.visible = false">取消</el-button>
                    </div>
                  </el-form>
                  <i slot="reference" class="h-icon-edit" @click="openInit(itemCard)"></i>
                </el-popover>
              </div>
            </div>
          </div>
        </el-col>
      </el-row>
    </el-tabs>
  </div>
</template>
<script>
import rules from '@/lib/rules';

export default {
  filters: {
    templateTypeFilter: value => {
      switch (value) {
        case 'MEETING_BEFORE':
          return '会前通知';
        case 'MEETING_DURING':
          return '会中通知';
        case 'MEETING_AFTER':
          return '会后通知';
        case 'NOTICE_TIMING':
          return '定时提醒';
        case 'USER_MANAGEMENT':
          return '用户管理';
        default:
          return;
      }
    },
    remoteMeetingFilter: value => {
      switch (value) {
        case 'YES':
          return '有';
        case 'NO':
          return '无';
        default:
          return;
      }
    },
    notifyPartyFilter: value => {
      switch (value) {
        case 'CC':
          return '抄送人';
        case 'MEMBER':
          return '与会人';
        case 'MEMBER_CC':
          return '与会人和抄送人';
        case 'REGISTRANT':
          return '注册人';
        case 'CREATOR':
          return '发起人';
        default:
          return;
      }
    }
  },
  data() {
    return {
      activeName: 'NOTICE_TIMING', // 当前激活的tab
      successPhoneNumber: '', // 保存成功的测试手机号
      tabList: [  // 获取的所有数据 重组数据结构如下:
        // {
        //   key: 1,
        //   label: 'MEETING_AFTER',
        //   contentList: []
        // },
        // {
        //   key: 2,
        //   label: 'MEETING_BEFORE',
        //   contentList: []
        // },
        // {
        //   key: 3,
        //   label: 'MEETING_DURING',
        //   contentList: []
        // },
        // {
        //   key: 4,
        //   label: 'NOTICE_TIMING',
        //   contentList: []
        // },
        // {
        //   key: 5,
        //   label: 'USER_MANAGEMENT',
        //   contentList: []
        // }
      ],
      templateList: [], // 要渲染的模板列表

      numberForm: {  // 测试手机号
        phoneNumber: ''
      },
      numberFormRule: { // 测试手机号校验
        phoneNumber: [rules.required('phoneNumber'), rules.phoneNumber()]
      },
      codeFormRule: { // 模板code校验
        templateCodeValue: [
          // rules.required('templateCodeValue')
          {
            required: true,
            message: '请输入模板code'
            // trigger: 'blur'
          }
        ]
      }
    };
  },
  created() {
    this.getPhoneNumber();
    this.getData();
  },
  methods: {
    // 获取测试手机号
    async getPhoneNumber() {
      const res = await this.$get('meeting/aLiYunSmsSdk/v1/getALiYunSmsTestPhone');
      this.numberForm.phoneNumber = res || '';
      this.successPhoneNumber = res || '';
    },
    // 获取短信模板
    async getData() {
      const res = await this.$get('meeting/template/v1/getSmsTemplateInfo');
      let dataList = res || [];
      const newTabList = dataList.map((item, index) => {
        return {
          key: index + 1,
          label: item.noticePeriodName,
          contentList: item.noticePeriodData ? item.noticePeriodData.map(item_ => {
            return {
              id: item_.id,
              templateName: item_.templateName, // 模板名称
              isSend: true, // 是否发送
              notifyParty: item_.notifyObject, // 通知对象
              remoteMeeting: item_.videoConference, // 是否支持远程会议
              templateContent: item_.templateContent, // 模板内容
              templateCode: item_.templateCode, // 模板code
              templateCodeValue: item_.templateCode, // 双向绑定的code
              visible: false
            };
          }) : []
        };
      });

      this.tabList = JSON.parse(JSON.stringify(newTabList));
      this.templateList = newTabList[0] ? JSON.parse(JSON.stringify(newTabList))[0].contentList : [];
    },
    // 保存测试手机号
    submitForm() {
      this.$refs.numberForm.validate(async valid => {
        if (valid) {
          const res = await this.$get(`meeting/aLiYunSmsSdk/v1/saveALiYunSmsTestPhone/${this.numberForm.phoneNumber}`);
          this.successPhoneNumber = res || '';
          this.$message({
            message: '手机号保存成功,可用于有code码的模板进行短信测试!',
            type: 'success'
          });
        }
      });
    },
    // 切换tab (按理此处可以通过接口获取,因为数据少,所以后端一次性返回,前端自行处理)
    handleClick(tab) {
      const newArr = JSON.parse(JSON.stringify(this.tabList));
      let newTemplateList = JSON.parse(JSON.stringify(this.templateList));

      for (let i = 0; i < newArr.length; i++) {
        if (newArr[i].label === tab.name) {
          newTemplateList = newArr[i].contentList;
        }
      }

      this.templateList = newTemplateList;
    },
    // 发送
    async sendMessage(code) {
      if (!this.successPhoneNumber) {
        return this.$message({
          message: '测试手机号未保存!',
          type: 'warning'
        });
      }
      await this.$get(`meeting/sms/v1/sendSmsTest/${this.successPhoneNumber}/$[code]`);
    },
    // 复制
    copyText(text) {
      console.log(text);//获取input对象
      //创建input标签
      var input = document.createElement('textarea');
      //将input的值设置为需要复制的内容
      input.value = text;
      //添加input标签
      document.body.appendChild(input);
      //选中input标签
      input.select();
      //执行复制
      document.execCommand('copy');
      //成功提示信息
      this.$message({
        message: '已复制成功!',
        type: 'success'
      });
      //移除input标签
      document.body.removeChild(input);
    },
    // 开启code弹框
    openInit(item) {
      item.templateCodeValue = item.templateCode;
    },
    // 确认code
    async confirm(item) {
      this.$refs[item.id][0].validate(async valid => {
        if (valid) {
          await this.$get(`meeting/template/v1/bindingSmsTemplateCode/${item.id}/${item.templateCodeValue}`);
          item.templateCode = item.templateCodeValue;
          item.visible = false;
        }
      });
    }
  }
};
</script>
<style lang="less">
.message-templete-style {
  height: 100%;
  // background: rgb(253, 243, 243);
  overflow: hidden;

  .title{
    font-size: 16px;
    color:#000000;
    font-weight: 900;
    padding-bottom: 10px;

    .h-icon-help{
      position: absolute;
      font-size: 26px;
      cursor: pointer;
      padding-left:2px;
      margin-top:-3px;
    }
  }

  .number-form{
    height:64px;
    padding-top:12px;
    background: #F7F8F9;
    border-radius: 2px;
    display: flex;
    .el-form-item{
      margin-bottom: 20px;
    }
    .btn .el-form-item__content{
      margin-left: 10px !important;
    }
  }

  .el-tabs{
    height: calc(100% - 94px);
    // background: #2080F7;
    padding-top:12px;

    .el-tabs__item{
      padding:0px 32px;
      span{
        opacity: 0.7;
        font-size: 14px;
      }
    }
    .el-tabs__item.is-active{
      color: #000000;
      font-weight: 900;
    }
    .el-tabs__item:hover{
      color: #000000;
      font-weight: 900;
    }
    .el-tabs__active-bar{
      background-color: #2080F7;
    }

    .el-tabs__header{
      padding-bottom: 10px;
    }
    .el-tabs__content{
      height:calc(100% - 51px);
      // background: #e8eef5;
      overflow: auto;

      // 重置滚动条位置
      &::-webkit-scrollbar {
        width: 4px;
        height: 3px;
        background: transparent;
      }
      &::-webkit-scrollbar-thumb {
        background: transparent;
        border-radius: 4px;
      }
      &:hover::-webkit-scrollbar-thumb {
        background: hsla(0, 0%, 53%, 0.5);
      }
      &:hover::-webkit-scrollbar-track {
        background: hsla(0, 0%, 53%, 0);
      }

      .el-col{
        margin-bottom: 12px;
      }

      .card-wrap{
        padding: 16px 20px;
        background: #F7F8F9;
        border-radius: 2px;
        .top-style{
          display: flex;
          justify-content: space-between;
          align-items: center;
          font-size: 14px;
          font-weight: 900;
          line-height: 14px;
          padding-bottom: 20px;
          .card-title{
            color: #000000;
          }
          .el-button {
            border: 0px;
            height:16px;
            margin-top:-4px;
            span{
              line-height: 14px;
              font-size: 14px;
              color:#2080F7;
            }
          }
        }
        .content-style{
          div{
            position: relative;
            opacity: 0.9;
            font-size: 14px;
            color: #000000;
            line-height: 24px;
            h4{
              display: inline-block;
              opacity: 0.5;
              font-size: 14px;
              color: #000000;
              line-height: 24px;
              padding-right:16px;
              font-weight: normal;
            }

            .h-icon-details, .h-icon-edit{
              position: absolute;
              font-size: 22px;
              cursor: pointer;
              padding-left:8px;
            }

            .h-icon-edit{
              margin-top:-12px;
            }
          }
          .code-style{
            display: flex;
            align-items: center;
            .code-width{
              max-width:calc(100% - 104px);
              text-overflow: ellipsis;
              overflow: hidden;
              white-space: nowrap;
              
              line-height: 14px;
              font-size: 14px;
              font-weight: normal;
            }
          }
        }
      }
    }
  }
}
.detail-pop-style{
  background: #FFFFFF;
  box-shadow: 0 0 16px 0 rgba(0,0,0,0.20), 0 16px 32px 0 rgba(0,0,0,0.12);
  p{
    position: relative;
    font-weight: 900;
    font-size: 14px;
    line-height: 14px;
    color: #000000;
    padding-bottom: 8px;

    .h-icon-copy{
      color:#1C7FFF;
      position: absolute;
      font-size: 20px;
      cursor: pointer;
      padding-left:4px;
      margin-top:-3px;
    }
  }
  .title{
    padding-bottom:16px;
  }
  div{
    font-size: 14px;
    color: rgba(0,0,0,0.50);
    line-height: 24px;
  }
}

.code-pop-style{
  background: #FFFFFF;
  box-shadow: 0 0 16px 0 rgba(0,0,0,0.20), 0 16px 32px 0 rgba(0,0,0,0.12);
  .el-form-item__content{
    margin-left:0px;
  }

  .btn-style{
    float: right;
  }
}
.tooltip-style{
  background: #000000;
}
</style>

抽组件版

父组件:
<template>
  <div class="message-templete-style">
    <div class="title">短信通知模板
      <el-tooltip popper-class="tooltip-style" content="请到阿里云短信平台申请短信权限" placement="bottom">
        <i class="h-icon-help" :offset="85"></i>
      </el-tooltip></div>
    <el-form :model="numberForm" ref="numberForm" :rules="numberFormRule" label-width="100px" class="number-form">
      <el-form-item label="测试手机号" prop="phoneNumber">
        <el-input v-model="numberForm.phoneNumber"></el-input>
      </el-form-item>
      <el-form-item class="btn">
        <el-button type="primary" @click="submitForm">保存</el-button>
      </el-form-item>
    </el-form>
    <el-tabs v-model="activeName" @tab-click="handleClick">
      <!-- tab头部 -->
      <el-tab-pane v-for="item in tabList" :key="item.key" :label="item.label|templateTypeFilter " :name="item.label"></el-tab-pane>
      <!-- tab内容 -->
      <el-row :gutter="12">
        <el-col v-for="(itemCard) in templateList" :key="itemCard.id" :xs="8" :sm="8" :md="8" :lg="8" :xl="6">
          <template-card :success-phone-number="successPhoneNumber" :item-card="itemCard"></template-card>
          <!-- <div class="card-wrap">
            <div class="top-style">
              <div class="card-title">{{ itemCard.templateName }}</div>
              <el-button type="text" @click="sendMessage(itemCard.templateCode)" :disabled="itemCard.templateCode ? false: true">发送</el-button>
            </div>
            <div class="content-style">
              <div><h4>通知对象</h4> {{ itemCard.notifyParty | notifyPartyFilter }}</div>
              <div><h4>远程会议</h4> {{ itemCard.remoteMeeting | remoteMeetingFilter }}</div>
              <div><h4>申请模板</h4> 模板名称/内容
                <el-popover width="300" trigger="hover" popper-class="detail-pop-style">
                  <p>模板名称 <i class="h-icon-copy" @click="copyText(itemCard.templateName, '名称')"></i></p>
                  <div class="title">{{ itemCard.templateName }}</div>
                  <p>模板内容<i class="h-icon-copy" @click="copyText(itemCard.templateContent, '内容')"></i></p>
                  <div style="white-space: pre-wrap;">{{ itemCard.templateContent }}</div>
                  <i slot="reference" class="h-icon-details"></i>
                </el-popover>
              </div>
              <div class="code-style"><h4>模板code</h4><span class="code-width">{{ itemCard.templateCode || '' }}</span>
                <el-popover v-model="itemCard.visible" popper-class="code-pop-style">
                  <el-form :model="itemCard" :ref="itemCard.id" label-position="top" class="code-form">
                    <el-form-item label="模板code" prop="templateCodeValue" :rules="codeFormRule.templateCodeValue">
                      <el-input v-model="itemCard.templateCodeValue"></el-input>
                    </el-form-item>
                    <div class="btn-style">
                      <el-button type="primary" @click="confirm(itemCard)">确定</el-button>
                      <el-button @click="itemCard.visible = false">取消</el-button>
                    </div>
                  </el-form>
                  <i slot="reference" class="h-icon-edit" @click="openInit(itemCard)"></i>
                </el-popover>
              </div>
            </div>
          </div> -->
        </el-col>
      </el-row>
    </el-tabs>
  </div>
</template>
<script>
import rules from '@/lib/rules';
import templateCard from '@/components/view/SettingManage/templateCard.vue';
export default {
  components: {
    templateCard
  },
  filters: {
    templateTypeFilter: value => {
      switch (value) {
        case 'MEETING_BEFORE':
          return '会前通知';
        case 'MEETING_DURING':
          return '会中通知';
        case 'MEETING_AFTER':
          return '会后通知';
        case 'NOTICE_TIMING':
          return '定时提醒';
        case 'USER_MANAGEMENT':
          return '用户管理';
        default:
          return;
      }
    }
  },
  data() {
    return {
      activeName: 'NOTICE_TIMING', // 当前激活的tab
      successPhoneNumber: '', // 保存成功的测试手机号
      tabList: [  // 获取的所有数据 重组数据结构如下:
        // {
        //   key: 1,
        //   label: 'MEETING_AFTER',
        //   contentList: []
        // },
        // {
        //   key: 2,
        //   label: 'MEETING_BEFORE',
        //   contentList: []
        // },
        // {
        //   key: 3,
        //   label: 'MEETING_DURING',
        //   contentList: []
        // },
        // {
        //   key: 4,
        //   label: 'NOTICE_TIMING',
        //   contentList: []
        // },
        // {
        //   key: 5,
        //   label: 'USER_MANAGEMENT',
        //   contentList: []
        // }
      ],
      templateList: [], // 要渲染的模板列表

      numberForm: {  // 测试手机号
        phoneNumber: ''
      },
      numberFormRule: { // 测试手机号校验
        phoneNumber: [rules.required('phoneNumber'), rules.phoneNumber()]
      },
      codeFormRule: { // 模板code校验
        templateCodeValue: [
          // rules.required('templateCodeValue')
          {
            required: true,
            message: '请输入模板code'
            // trigger: 'blur'
          }
        ]
      }
    };
  },
  created() {
    this.getPhoneNumber();
    this.getData();
  },
  methods: {
    // 获取测试手机号
    async getPhoneNumber() {
      const res = await this.$get('meeting/aLiYunSmsSdk/v1/getALiYunSmsTestPhone');
      this.numberForm.phoneNumber = res || '';
      this.successPhoneNumber = res || '';
    },
    // 获取短信模板
    async getData() {
      const res = await this.$get('meeting/template/v1/getSmsTemplateInfo');
      let dataList = res || [];
      const newTabList = dataList.map((item, index) => {
        return {
          key: index + 1,
          label: item.noticePeriodName,
          contentList: item.noticePeriodData ? item.noticePeriodData.map(item_ => {
            return {
              id: item_.id,
              templateName: item_.templateName, // 模板名称
              isSend: true, // 是否发送
              notifyParty: item_.notifyObject, // 通知对象
              remoteMeeting: item_.videoConference, // 是否支持远程会议
              templateContent: item_.templateContent, // 模板内容
              templateCode: item_.templateCode, // 模板code
              templateCodeValue: item_.templateCode, // 双向绑定的code
              visible: false
            };
          }) : []
        };
      });

      this.tabList = JSON.parse(JSON.stringify(newTabList));
      this.templateList = newTabList[0] ? JSON.parse(JSON.stringify(newTabList))[0].contentList : [];
    },
    // 保存测试手机号
    submitForm() {
      this.$refs.numberForm.validate(async valid => {
        if (valid) {
          const res = await this.$get(`meeting/aLiYunSmsSdk/v1/saveALiYunSmsTestPhone/${this.numberForm.phoneNumber}`);
          this.successPhoneNumber = res || '';
          this.$message({
            message: '手机号保存成功,可用于有code码的模板进行短信测试!',
            type: 'success'
          });
        }
      });
    },
    // 切换tab (按理此处可以通过接口获取,因为数据少,所以后端一次性返回,前端自行处理)
    handleClick(tab) {
      const newArr = JSON.parse(JSON.stringify(this.tabList));
      let newTemplateList = JSON.parse(JSON.stringify(this.templateList));

      for (let i = 0; i < newArr.length; i++) {
        if (newArr[i].label === tab.name) {
          newTemplateList = newArr[i].contentList;
        }
      }

      this.templateList = newTemplateList;
    }
  }
};
</script>
<style lang="less" scoped>
.message-templete-style {
  height: 100%;
  // background: rgb(253, 243, 243);
  overflow: hidden;

  .title{
    font-size: 16px;
    color:#000000;
    font-weight: 900;
    padding-bottom: 10px;

    .h-icon-help{
      position: absolute;
      font-size: 26px;
      cursor: pointer;
      padding-left:2px;
      margin-top:-3px;
    }
  }

  .number-form{
    height:64px;
    padding-top:12px;
    background: #F7F8F9;
    border-radius: 2px;
    display: flex;
    .el-form-item{
      margin-bottom: 20px;
    }
    .btn .el-form-item__content{
      margin-left: 10px !important;
    }
  }

  .el-tabs{
    height: calc(100% - 94px);
    // background: #2080F7;
    padding-top:12px;

    .el-tabs__item{
      padding:0px 32px;
      span{
        opacity: 0.7;
        font-size: 14px;
      }
    }
    .el-tabs__item.is-active{
      color: #000000;
      font-weight: 900;
    }
    .el-tabs__item:hover{
      color: #000000;
      font-weight: 900;
    }
    .el-tabs__active-bar{
      background-color: #2080F7;
    }

    .el-tabs__header{
      padding-bottom: 10px;
    }
    .el-tabs__content{
      height:calc(100% - 51px);
      // background: #e8eef5;
      overflow: auto;

      // 重置滚动条位置
      &::-webkit-scrollbar {
        width: 4px;
        height: 3px;
        background: transparent;
      }
      &::-webkit-scrollbar-thumb {
        background: transparent;
        border-radius: 4px;
      }
      &:hover::-webkit-scrollbar-thumb {
        background: hsla(0, 0%, 53%, 0.5);
      }
      &:hover::-webkit-scrollbar-track {
        background: hsla(0, 0%, 53%, 0);
      }

      .el-col{
        margin-bottom: 12px;
      }
    }
  }
}
.tooltip-style{
  background: #000000;
}
</style>
```javascript
子组件:
<template>
  <div class="card-wrap">
    <div class="top-style">
      <div class="card-title">{{ itemCard.templateName }}</div>
      <el-button type="text" @click="sendMessage(itemCard.templateCode)" :disabled="itemCard.templateCode ? false: true">发送</el-button>
    </div>
    <div class="content-style">
      <div><h4>通知对象</h4> {{ itemCard.notifyParty | notifyPartyFilter }}</div>
      <div><h4>远程会议</h4> {{ itemCard.remoteMeeting | remoteMeetingFilter }}</div>
      <div><h4>申请模板</h4> 模板名称/内容
        <el-popover width="300" trigger="hover" popper-class="detail-pop-style">
          <p>模板名称 <i class="h-icon-copy" @click="copyText(itemCard.templateName, '名称')"></i></p>
          <div class="title">{{ itemCard.templateName }}</div>
          <p>模板内容<i class="h-icon-copy" @click="copyText(itemCard.templateContent, '内容')"></i></p>
          <div style="white-space: pre-wrap;">{{ itemCard.templateContent }}</div>
          <i slot="reference" class="h-icon-details"></i>
        </el-popover>
      </div>
      <div class="code-style"><h4>模板code</h4><span class="code-width">{{ itemCard.templateCode || '' }}</span>
        <el-popover v-model="itemCard.visible" popper-class="code-pop-style">
          <el-form :model="itemCard" :ref="itemCard.id" label-position="top" class="code-form">
            <el-form-item label="模板code" prop="templateCodeValue" :rules="codeFormRule.templateCodeValue">
              <el-input v-model="itemCard.templateCodeValue"></el-input>
            </el-form-item>
            <div class="btn-style">
              <el-button type="primary" @click="confirm(itemCard)">确定</el-button>
              <el-button @click="itemCard.visible = false">取消</el-button>
            </div>
          </el-form>
          <i slot="reference" class="h-icon-edit" @click="openInit(itemCard)"></i>
        </el-popover>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  filters: {
    remoteMeetingFilter: value => {
      switch (value) {
        case 'YES':
          return '有';
        case 'NO':
          return '无';
        default:
          return;
      }
    },
    notifyPartyFilter: value => {
      switch (value) {
        case 'CC':
          return '抄送人';
        case 'MEMBER':
          return '与会人';
        case 'MEMBER_CC':
          return '与会人和抄送人';
        case 'REGISTRANT':
          return '注册人';
        case 'CREATOR':
          return '发起人';
        default:
          return;
      }
    }
  },
  props: {
    successPhoneNumber: {
      type: String,
      default: ''
    },
    itemCard: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      codeFormRule: { // 模板code校验
        templateCodeValue: [
          // rules.required('templateCodeValue')
          {
            required: true,
            message: '请输入模板code'
            // trigger: 'blur'
          }
        ]
      }
    };
  },
  methods: {
  // 发送
    async sendMessage(code) {
      if (!this.successPhoneNumber) {
        return this.$message({
          message: '测试手机号未保存!',
          type: 'warning'
        });
      }
      await this.$get(`meeting/sms/v1/sendSmsTest/${this.successPhoneNumber}/$[code]`);
    },
    // 复制
    copyText(text) {
      console.log(text);//获取input对象
      //创建input标签
      var input = document.createElement('textarea');
      //将input的值设置为需要复制的内容
      input.value = text;
      //添加input标签
      document.body.appendChild(input);
      //选中input标签
      input.select();
      //执行复制
      document.execCommand('copy');
      //成功提示信息
      this.$message({
        message: '已复制成功!',
        type: 'success'
      });
      //移除input标签
      document.body.removeChild(input);
    },
    // 开启code弹框
    openInit(item) {
      item.templateCodeValue = item.templateCode;
    },
    // 确认code
    confirm(item) {
      this.$refs[item.id].validate(async valid => {
        if (valid) {
          await this.$get(`meeting/template/v1/bindingSmsTemplateCode/${item.id}/${item.templateCodeValue}`);
          item.templateCode = item.templateCodeValue;
          item.visible = false;
        }
      });
    }
  }
};
</script>
<style lang='less' scoped>
.card-wrap{
  padding: 16px 20px;
  background: #F7F8F9;
  border-radius: 2px;
  .top-style{
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 14px;
    font-weight: 900;
    line-height: 14px;
    padding-bottom: 20px;
    .card-title{
      color: #000000;
    }
    /deep/.el-button {
      border: 0px;
      height:16px;
      margin-top:-4px;
      span{
        line-height: 14px;
        font-size: 14px;
        color:#2080F7;
      }
    }
  }
  .content-style{
    div{
      position: relative;
      opacity: 0.9;
      font-size: 14px;
      color: #000000;
      line-height: 24px;
      h4{
        display: inline-block;
        opacity: 0.5;
        font-size: 14px;
        color: #000000;
        line-height: 24px;
        padding-right:16px;
        font-weight: normal;
      }

      .h-icon-details, .h-icon-edit{
        position: absolute;
        font-size: 22px;
        cursor: pointer;
        padding-left:8px;
      }

      .h-icon-edit{
        margin-top:-12px;
      }
    }
    .code-style{
      display: flex;
      align-items: center;
      .code-width{
        max-width:calc(100% - 104px);
        text-overflow: ellipsis;
        overflow: hidden;
        white-space: nowrap;
        
        line-height: 14px;
        font-size: 14px;
        font-weight: normal;
      }
    }
  }
}

.detail-pop-style{
  background: #FFFFFF;
  box-shadow: 0 0 16px 0 rgba(0,0,0,0.20), 0 16px 32px 0 rgba(0,0,0,0.12);
  p{
    position: relative;
    font-weight: 900;
    font-size: 14px;
    line-height: 14px;
    color: #000000;
    padding-bottom: 8px;

    .h-icon-copy{
      color:#1C7FFF;
      position: absolute;
      font-size: 20px;
      cursor: pointer;
      padding-left:4px;
      margin-top:-3px;
    }
  }
  .title{
    padding-bottom:16px;
  }
  div{
    font-size: 14px;
    color: rgba(0,0,0,0.50);
    line-height: 24px;
  }
}

.code-pop-style{
  background: #FFFFFF;
  box-shadow: 0 0 16px 0 rgba(0,0,0,0.20), 0 16px 32px 0 rgba(0,0,0,0.12);
  .code-form{
    /deep/ .el-form-item__content{
      margin-left:0px;
    }
    .btn-style{
      float: right;
    }
  }

}
</style>

vue中el-form循环绑定

在我们开发过程中,有时会遇到el-form循环绑定校验,并且后台返回的是动态表单list的形式,并且动态绑定是否必填

<el-form
	ref="addForm"
	:model="submitForm"   //绑定的表单对象
	label-width="125px"
    :rules="rules"		  //绑定的校验规则

>
	<el-form-item v-for="(item,index) in formArr" :key="index" :label="item.fieldName + ' : ' " :required="item.isRequired==1?true:false" :prop="item.fieldIdentify">
		<el-input v-if="item.fieldIdentify=='AAA'" style="width:100%;color:#DBDBDD;" type="text" v-model="submitForm.AAA"></el-input>
		<el-input v-if="item.fieldIdentify=='BBB'" style="width:100%;color:#DBDBDD;" type="text" v-model="submitForm.BBB"></el-input>
		<el-input v-if="item.fieldIdentify=='CCC'" style="width:100%;color:#DBDBDD;" type="text" v-model="submitForm.CCC"></el-input>
		<el-form-item  class="textarea form_textarea" v-if="item.fieldIdentify=='DDD'" :prop="item.fieldIdentify">
			<el-input style="width:650px;" type="textarea" v-model="submitForm.DDD"></el-input>
        </el-form-item>
	</el-form-item>

</el-form>
<script>
	export default {
		data(){
			return{
				submitForm:{
					AAA:'',
					BBB:'',
					CCC:'',
					DDD:''
				},
				//formArr数据类似
				formArr:[
					{
						fieldIdentify: "xxx",
						fieldName: "xxx",
						fieldValue: "xxx",
						isRequired: 1
					},
					{
						fieldIdentify: "xxx",
						fieldName: "xxx",
						fieldValue: "xxx",
						isRequired: 1
					},
					{
						fieldIdentify: "xxx",
						fieldName: "xxx",
						fieldValue: "xxx",
						isRequired: 1
					}
				],
				//注意在el-form-item的:prop值要和rules里面的值相同
				rules:{
					AAA:[{
						required: true, message: "AAA不能为空", trigger: ["blur","change"]
					}],
					BBB:[{
						required: true, message: "BBB不能为空", trigger: ["blur","change"]
					}],
					CCC:[{
						required: true, message: "CCC不能为空", trigger: ["blur","change"]
					}],
					DDD:[{
						required: true, message: "CCC不能为空", trigger: ["blur","change"]
					}]
				}
			}
		}
	}
</script>