2、文字(水印、镂空、阴影)、盒子阴影,背景,线性渐变(边框渐变、文字渐变),径向渐变,动画(闪烁、过渡、转换),数据加密、js编码、数据上传、数据下载、数据展示、文档编辑,算法知识|显示屏尺寸|彩礼|编程绝句(4350行)

//===上面,向远方发送请求,远方将上传路由放到cookie里。此处,隐式从cookie获取上传路由,用ajax-promise上传“数据”===
//===单图片先裁切后上传逻辑:把数据读取为url,作为:img配置;通过ref配置,获取裁切后的数据;<vueCropper :img="option.clientImg" ref="cropper"/>===
//===多视频先单个上传后集体生效的逻辑:单个视频信息追加到表格;监听视频可播放事件,先抽帧后上传帧,更新表格数据;上传视频并监听进度,更新表格数据;重复前面步骤;上传表格数据===
一、文字(水印、镂空、阴影)、盒子阴影,背景,线性渐变(边框渐变、文字渐变),径向渐变,动画(闪烁、过渡、转换)
1、文字(水印、镂空、阴影)、盒子阴影
  <!DOCTYPE html>
  <html>
    <head lang="en">
      <meta charset="UTF-8">
      <title></title>
      <style type="text/css">
          .text-watermark{
              z-index: 1;
              position: absolute;
              opacity: 0.2;
              transform: rotate(-45deg) translate(30px);
              user-select: none;
          }
          .text-uesd{
              z-index: 2;
              position: relative;
          }
          .text-stroke{
              font-size: 24px;
              font-weight: 900;
              color: transparent;
              -webkit-text-stroke: 1px gray;/* 文本轮廓的线宽、颜色 */
          }
          .text-shadow{
              font-size: 24px;
              font-weight: 900;
              color: #000;
              text-shadow: -5px -5px 3px gray, 5px 5px 3px #ccc;
          }
          .box-shadow{
              width: 1000px;
              margin: 30px 30px 30px 130px ;
              padding-top: 15px;
              font-size: 15px;
              border-radius: 10px;
              box-shadow: 0 0 10px 20px gray, 0 0 10px 20px #ccc inset;
          }
      </style>
    </head>
    <body>
      <pre style="padding-left: 50px;">
        1/4、文字水印
        <pre class="text-watermark">
          文字水印样式
          .text-watermark{
              z-index: 1;
              position: absolute;
              opacity: 0.2;
              transform: rotate(-45deg) translate(30px);
              user-select: none;
          }
        </pre>
        <pre class="text-uesd">
          文字主体样式
          .text-uesd{
              z-index: 2;
              position: relative;
          }
        </pre>
      </pre>
      <pre class="text-stroke">
        2/4、文字镂空
        .text-stroke{
            font-size: 24px;
            font-weight: 900;
            color: transparent;
            -webkit-text-stroke: 1px gray;/* 文本轮廓的线宽、颜色 */
        }
      </pre>
      <pre class="text-shadow">
        3/4、文字阴影
        .text-shadow{
            font-size: 20px;
            font-weight: 900;
            color: #000;
            text-shadow: -5px -5px 3px gray <左上阴影>, 5px 5px 3px #ccc <右下阴影>;
        }
        以下text-shadow的取值说明,
        来源,https://www.runoob.com/cssref/css3-pr-box-shadow.html
        text-shadow: h-shadow v-shadow blur color;
            h-shadow,必需,水平阴影的位置;负值左阴影,0底阴影,正值右阴影
            v-shadow,必需,垂直阴影的位置;负值上阴影,0底阴影,正值下阴影
            blur,可选,模糊的距离
            color,可选,阴影的颜色
      </pre>
      <pre class="box-shadow">
        4/4、盒子阴影
        .box-shadow{
            box-shadow: 0 0 10px 10px gray;
            box-shadow: 0 0 10px 10px gray <外阴影>, 0 0 10px 10px #ccc inset <内阴影>;
        }
        以下box-shadow的取值说明
            box-shadow: h-shadow v-shadow blur spread color inset;
                h-shadow,必需,水平阴影的位置;负值左阴影,0左右阴影,正值右阴影
                v-shadow,必需,垂直阴影的位置;负值上阴影,0上下阴影,正值下阴影
                blur,可选,模糊距离
                spread,可选,阴影的大小
                color,可选,阴影的颜色;在CSS颜色值寻找颜色值的完整列表
                inset,可选,从外层的阴影(开始时)改变阴影内侧阴影
      </pre>
    </body>
  </html>
2、背景
  <!DOCTYPE html>
  <html lang="en">
    <style>
      .bg{
        border: 10px dotted black;
        width: 1200px;
        height: 200px;
        border-radius: 10px;
        padding: 30px 0;
        background: 
          /* (1)background-color */gray 
          /* (2)background-image */url("https://tse1.mm.bing.net/th/id/OIP.edTU9om2gp7gLPZvnTJ3OwHaCy") 
          /* (3)background-position */left center 
          /* (4)background-size */ /* cover,contain */
          /* (5)background-repeat */no-repeat
          /* (6)background-origin */padding-box
          /* (7)background-clip */border-box
          /* (8)background-attachment */scroll
      }
      .pre{
        border: 2px solid black;
        width: 1200px;
        margin: 30px;
        border-radius: 10px;
        padding: 30px 0;
      }
      .span{
        font-size: 20px;
        font-weight: 900;
      }
    </style>
    <head>
      <meta charset="UTF-8">
      <title>背景</title>
    </head>
    <body>
      <div class="bg"></div>
      <pre class="pre">
        用法来源,https://www.runoob.com/cssref/css3-pr-background.html
          background: bg-color bg-image bg-position bg-size bg-repeat bg-origin bg-clip bg-attachment initial|inherit;
        background的取值说明
          (1)background-color,指定要使用的背景颜色,默认值:transparent
          (2)background-image,指定要使用的一个或多个背景图像,默认值:none==========常用1/3=============
            url('url'),图像的URL
            linear-gradient(),创建一个线性渐变的"图像"(从上到下)
            radial-gradient(),用径向渐变创建"图像"
            repeating-linear-gradient(),创建重复的线性渐变"图像"
            repeating-radial-gradient(),创建重复的径向渐变"图像"
            附、background-image多个背景图片示例
              #example1 {
                background-image: url(img_flwr.gif), url(paper.gif);
                <span class="span">多背景图片,从后向前渲染!</span>
                background-position: right bottom, left top;
                background-repeat: no-repeat, repeat;
              }
          (3)background-position,指定背景图像的位置(自悟-开始填充的方位),默认值:0% 0%;(3)、(4)二选一
            left top,
            left center,
            left bottom,
            right top,
            right center,
            right bottom,
            center top,
            center center,
            center bottom,指定一个,另一个是"center"
            x% y%,指定一个,另一个是50%
            xpx ypx,指定一个,另一个是50%
          (4)background-size,指定背景图片的大小(自悟-覆盖的范围),默认值:auto==========常用2/3=============
            auto,设置背景图片高度和宽度为原始的高度和宽度;
            length,设置背景图片高度和宽度;
              第1个值,设置宽度
              第2个值,设置的高度
              如果只给出1个值,第2个是设置为 auto(自动)
            percentage,将计算相对于背景定位区域的百分比;
              第1个值,设置宽度
              第2个值,设置的高度
              如果只给出1个值,第2个是设置为"auto(自动)"
              注意,有时超过100%,比如107%,才能把目标区域完全覆盖,是因为原背景图片有近乎透明的阴影,占用了一些尺寸
            cover,此时会保持图像的纵横比并将图像缩放成将完全覆盖背景定位区域的最小大小
            contain,此时会保持图像的纵横比并将图像缩放成将适合背景定位区域的最大大小
            <span class="span">各种渐变,如果不设置background-size属性,那么1个渐变将占满整个盒子!</span>
          (5)background-repeat,指定如何重复背景图像,默认值:repeat
            repeat,背景图像将向垂直和水平方向重复
            repeat-x,只有水平位置会重复背景图像
            repeat-y,只有垂直位置会重复背景图像
            no-repeat,不重复
          (6)background-origin,指定背景图像的定位区域(自悟-开始填充的区域),默认值:padding-box
            padding-box,背景图像填充框的相对位置
            border-box,背景图像边界框的相对位置
            content-box,背景图像的相对位置的内容框
          (7)background-clip,指定背景图像的绘画区域(自悟-开始显示的区域),默认值:border-box==========常用3/3=============
            border-box,背景绘制在边框方框内(剪切成边框方框)
            padding-box,背景绘制在衬距方框内(剪切成衬距方框)
            content-box,背景绘制在内容方框内(剪切成内容方框)
          (8)background-attachment,设置背景图像是否固定或者随着页面的其余部分滚动,默认值:initial
            scroll,背景图片随着页面的滚动而滚动,这是默认的
            fixed,背景图片不会随着页面的滚动而滚动
            local,背景图片会随着元素内容的滚动而滚动
            initial,设置该属性的默认值,https://www.runoob.com/cssref/css-initial.html
      </pre>
    </body>
  </html>
3、线性渐变
  <!DOCTYPE html>
  <html lang="en">
    <style>
      .blackBorder{
        border: 2px solid black;
      }
      .gradientBorder{
        background-image: linear-gradient(#fff,#fff),linear-gradient(98deg,#624AFF,#1CF7CC,#1697F8);
        background-clip: content-box,border-box;
        border: 2px solid transparent;
        padding: 0;
      }
      .gradientText{
        background-image: -webkit-linear-gradient(right,#624AFF,#1CF7CC,#1697F8);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
      }
      .padding{
        padding: 10px;
      }
      .div{
        width: 1200px;
        margin: 30px;
        border-radius: 10px;
      }
      .linearGradientYes0{
        background-image: linear-gradient(to right, red 0, yellow 600px, green 1200px);
      }
      .linearGradientNo0{
        background-image: linear-gradient(to right, red 20%, yellow 50%, green 80%);
      }
      .repeatingLinearGradientYes0{
        background-image: repeating-linear-gradient(to right, red 0, yellow 150px, green 300px);
      }
      .repeatingLinearGradientNo0{
        background-image: repeating-linear-gradient(to right, red 8%, yellow 20%, green 30%);
      }
    </style>
    <head>
      <meta charset="UTF-8">
      <title>线性渐变</title>
    </head>
    <body>
      <pre>
        background-image,用法来源,https://www.runoob.com/cssref/pr-background-image.html
      </pre>
      <div class="div gradientBorder">
        <div class="padding">示例:文字渐变、边框渐变</div>
        <pre class="gradientText padding">
          .text{
            background-image: -webkit-linear-gradient(right,#624AFF,#1CF7CC,#1697F8);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
          }
          .border{
            background-image: linear-gradient(#fff,#fff),linear-gradient(98deg,#624AFF,#1CF7CC,#1697F8);
            background-clip: content-box,border-box;
            border: 2px solid transparent;
          }
        </pre>
      </div>
      <div class="div blackBorder linearGradientYes0">
        <div class="padding">1、线性渐变-以0为起点</div>
        <pre>
          linear-gradient(direction, color-stopA, color-stopB, ...);
            A、direction 用角度值指定渐变的方向(或角度);从上到下(默认)
            B、color-stopA, color-stopB, ... , color-stopN color-stopA/N用于指定“整个渐变”开始/结束时的颜色和位置
            C、示例,background-image: linear-gradient(to right, red 0, yellow 600px, green 1200px);
            D、从red过渡到yellow,再过渡到green;
        </pre>
      </div>
      <div class="div blackBorder linearGradientNo0">
        <div class="padding">2、线性渐变-不以0为起点</div>
        <pre>
          linear-gradient(direction, color-stopA, color-stopB, ...);
            A、direction 用角度值指定渐变的方向(或角度);从上到下(默认)
            B、color-stopA, color-stopB, ... , color-stopN color-stopA/N用于指定“整个渐变”开始/结束时的颜色和位置
            C、示例,background-image:linear-gradient(to right, red 20%, yellow 50%, green 80%);
            D、0到20%为red,80%到100%为green;20%到80%,从red过渡到yellow,再过渡到green;
        </pre>
      </div>
      <div class="div blackBorder repeatingLinearGradientYes0">
        <div class="padding">3、重复线性渐变-以0为起点</div>
        <pre>
          repeating-linear-gradient(direction, color-stopA, color-stopB, ...);
            A、direction 用角度值指定渐变的方向(或角度);从上到下(默认)
            B、colorA A%, colorB B%, ... , colorN N%,A%、N%用于指定第1、2个渐变的结束位置,colorA-colorN,用于指定1个完整渐变的颜色构成
            C、示例,background-image: repeating-linear-gradient(to right, red 0, yellow 150px, green 300px);
            D、上例中,数据释义如下
              0,第1个渐变的结束位置
              300px,第2个渐变的结束位置
              300px,300px-0px,1个完整渐变的占比,颜色与位置对应为red 0, yellow 150, green 300
              900px,3*300px,3个完整渐变的占比
              900px,最后1个渐变的开始位置
        </pre>
      </div>
      <div class="div blackBorder repeatingLinearGradientNo0">
        <div class="padding">4、重复线性渐变-不以0为起点</div>
        <pre>
          repeating-linear-gradient(direction, color-stopA, color-stopB, ...);
            A、direction 用角度值指定渐变的方向(或角度);从上到下(默认)
            B、colorA A%, colorB B%, ... , colorN N%,A%、N%用于指定第1、2个渐变的结束位置,colorA-colorN,用于指定1个完整渐变的颜色构成
            C、示例,background-image: repeating-linear-gradient(to right, red 8%, yellow 20%, green 30%);
            D、上例中,数据释义如下
              8%,第1个渐变的结束位置
              30%,第2个渐变的结束位置
              22%,30%-8%,1个完整渐变的占比,颜色与位置对应为red 0, yellow 12, green 22
              88%,4*22%,4个完整渐变的占比
              96%,8%+88%,最后1个渐变的开始位置
        </pre>
      </div>
    </body>
  </html>
4、径向渐变
  <!DOCTYPE html>
  <html>
    <head lang="en">
      <meta charset="UTF-8">
      <title>(重复)径向渐变</title>
      <style type="text/css">
          .common{
              width: 1200px;
              font-size: 15px;
              font-weight: 900;
              color: black;
              border-radius: 10px;
              margin: 50px;
              padding: 0px;
          }
          .params{
            border: 2px solid black;
            width: 1200px;
            margin: 30px;
            border-radius: 10px;
            padding: 30px 0;
          }
          .paddingLeft{
            padding-left: 10px;
          }
          .backgroundSize{
              background-size:370px 220px;
          }
          /* .backgroundRepeat{
              background-repeat: no-repeat;
          } */
          .radialNumber{
              height: 400px;
              background-image: radial-gradient(250px 35% at center,red 60px,yellow 90px,green 130px);
          }
          .radialRate{
              height: 400px;
              background-image: radial-gradient(250px 35% at center,red 20%, yellow 30%, green 45%);
          }
          .repeatNumber{
              height: 400px;
              background-image: repeating-radial-gradient(250px 35% at center,red 60px,yellow 90px,green 130px);
          }
          .repeatRate{
              height: 400px;
              background-image: repeating-radial-gradient(250px 35% at center,red 20%, yellow 30%, green 45%);
          }
          .money{
              background-image:repeating-radial-gradient(
                circle,
                gray 0,gray 5px,transparent 5px,transparent 20px,
                gray 20px,gray 25px,transparent 25px,transparent 50px
              );
              background-size:90px 90px;
              display: flex;
              justify-content: flex-end;
              align-items: flex-end;
          }
      </style>
    </head>
    <body>
      <pre class="params">
        <div class="paddingLeft">径向渐变-参数说明</div>
        <div>
          background-image: radial-gradient(shape size at position, start-color, ..., last-color);
          1、shape,径向渐变的形状,默认为ellipse
            circle,指定圆形的径向渐变
              background-image: radial-gradient(circle, red, yellow, green);
            ellipse,指定椭圆形的径向渐变
              background-image: radial-gradient(ellipse closest-corner, red, yellow, green);
            特别说明,当指定size为“数字”时,“指定形状”,必须根据数字的个数,否则无渐变效果,因此“指定形状”是多余的
              background-image: radial-gradient(circle 400px, red, yellow, green);
              background-image: radial-gradient(ellipse 400px 40%, red, yellow, green);
            特别说明,当指定size为“关键字”时,“指定形状”可以没有、可以任意,“指定形状”不是多余的
              background-image: radial-gradient(farthest-side, red, yellow, green);
              background-image: radial-gradient(circle farthest-side, red, yellow, green);
          2、size,径向渐变的宽高,默认为farthest-corner,可以用“省略”或“一个关键字”或“一、两个数字”
            farthest-corner,指定径向渐变的半径长度为从圆心到离圆心最远的角
            closest-side,指定径向渐变的半径长度为从圆心到离圆心最近的边
            closest-corner,指定径向渐变的半径长度为从圆心到离圆心最近的角
            farthest-side,指定径向渐变的半径长度为从圆心到离圆心最远的边
          3、position,定义渐变的位置,默认为center,可以用“省略”或“一个关键字”或“两个数字”
            center,设置中间为径向渐变圆心的纵坐标值
            top,设置顶部为径向渐变圆心的纵坐标值
            bottom,设置底部为径向渐变圆心的纵坐标值
            left,设置左侧为径向渐变圆心的横坐标值
            right,设置右侧为径向渐变圆心的横坐标值
              background-image: radial-gradient(circle, red, yellow, yellow);
              background-image: radial-gradient(circle at center, red, yellow, green);
              background-image: radial-gradient(400px 40% at 156px 60%, red, yellow, green);
          4、start-color, ..., last-color,用于指定“整个渐变”开始/结束时的颜色和位置
        </div>
        <div class="paddingLeft">径向渐变-案例解析</div>
        <div>
          1、案例解析1
            例1、background-image: radial-gradient(250px-A处 35%-B处 at center,red 60px,yellow 90px,green 130px);
            例2、background-image: repeating-radial-gradient(250px-A处 35%-B处 at center,red 60px,yellow 90px,green 130px);
            (1)例1中,数据释义如下
                60px,径向渐变开始位置,如果大于0,那么0到开始位置的颜色为开始位置的颜色
                130px,径向渐变结束位置,如果小于100%,那么结束位置到100%的颜色为结束位置的颜色
            (2)例2中,数据释义如下
                60px,重复径向渐变第1层椭圆的结束位置,即该层椭圆的X轴半径是60px
                130px,重复径向渐变第2层椭圆的结束位置,即该层椭圆的X轴半径是130px
          2、案例解析2
            例1、background-image: radial-gradient(250px-A处 35%-B处 at center, red 20%, yellow 30%, green 45%);
            例2、background-image: repeating-radial-gradient(250px-A处 35%-B处 at center, red 20%, yellow 30%, green 45%);
            (1)例1中,数据释义如下
                20%,径向渐变开始位置,即250px的20%,如果大于0,那么0到开始位置的颜色为开始位置的颜色
                45%,径向渐变结束位置,即250px的45%,如果小于100%,那么结束位置到100%的颜色为结束位置的颜色
            (2)例2中,数据释义如下
                20%,重复径向渐变第1层椭圆的结束位置,即该层椭圆的X轴半径是250px的20%
                45%,重复径向渐变第2层椭圆的结束位置,即该层椭圆的X轴半径是250px的45%
          3、上面案例中,
            (1)如果A处或B处为百分数,那么先通过盒子的width或height计算出对应值,进而计算出椭圆的X轴(先)或Y轴(后)半径
            (2)center,将上面的椭圆放到盒子的正中间,当center改为10px 30px时,将上面的椭圆中心放到盒子左边右侧10px,上边下方30px的地方,
            (3)重复径向渐变,各个色带(层距)从外向里填充颜色,当中心椭圆的半径小于层距时,椭圆中心不会出现色带内侧的颜色
          4、盒子的width或height只含content,但background-image默认是从border开始覆盖的
        </div>
      </pre>
      <pre class="common radialNumber backgroundRepeat">
        <div class="paddingLeft">1、径向渐变-像素</div>
        <div>
          .radialNumber{
              height: 400px;
              background-image: radial-gradient(250px 35% at center,red 60px,yellow 90px,green 130px);
          }
          上例中,径向渐变数据释义如下
              椭圆的X轴半径是130
              椭圆的Y轴半径是72.8,(400*35%)/250*130,y中心/x中心*x半径
        </div>
      </pre>
      <pre class="common radialNumber backgroundRepeat backgroundSize">
        <div class="paddingLeft">2、径向渐变-像素,含backgroundSize</div>
        <div>
          .radialNumber{
              height: 400px;
              background-image: radial-gradient(250px 35% at center,red 60px,yellow 90px,green 130px);
          }
          .backgroundSize{
              background-size:370px 220px;
          }
          上例中,径向渐变数据释义如下
              椭圆的X轴半径是130
              椭圆的Y轴半径是40.04,(220*35%)/250*130,y中心/x中心*x半径
        </div>
      </pre>
      <pre class="common radialRate backgroundRepeat">
        <div class="paddingLeft">3、径向渐变-百分比</div>
        <div>
          .radialRate{
              height: 400px;
              background-image: radial-gradient(250px 35% at center,red 20%, yellow 30%, green 45%);
          }
          上例中,径向渐变数据释义如下
              椭圆的X轴半径是112.5,250*45%
              椭圆的Y轴半径是63,(400*35%)/250*112.5,y中心/x中心*x半径
        </div>
      </pre>
      <pre class="common radialRate backgroundRepeat backgroundSize">
        <div class="paddingLeft">4、径向渐变-百分比,含backgroundSize</div>
        <div>
          .radialRate{
              height: 400px;
              background-image: radial-gradient(250px 35% at center,red 20%, yellow 30%, green 45%);
          }
          .backgroundSize{
              background-size:370px 220px;
          }
          上例中,径向渐变数据释义如下
              椭圆的X轴半径是112.5,250*45%
              椭圆的Y轴半径是34.65,(220*35%)/250*112.5,y中心/x中心*x半径
        </div>
      </pre>
      <pre class="common repeatNumber backgroundRepeat">
        <div class="paddingLeft">5、重复径向渐变-像素</div>
        <div>
          .repeatNumber{
              height: 400px;
              background-image: repeating-radial-gradient(250px 35% at center,red 60px,yellow 90px,green 130px);
          }
          上例中,重复径向渐变数据释义如下
              第1层椭圆的X轴半径是60
              第2层椭圆的X轴半径130
              X轴层距70,130-60
              第1层椭圆的Y轴半径33.6,(400*35%)/250*60,y中心/x中心*1层x半径
              第2层椭圆的Y轴半径72.8,(400*35%)/250*130,y中心/x中心*2层x半径
              Y轴层距39.2,72.8-33.6 
        </div>
      </pre>
      <pre class="common repeatNumber backgroundRepeat backgroundSize">
        <div class="paddingLeft">6、重复径向渐变-像素,含backgroundSize</div>
        <div>
          .repeatNumber{
              height: 400px;
              background-image: repeating-radial-gradient(250px 35% at center,red 60px,yellow 90px,green 130px);
          }
          .backgroundSize{
              background-size:370px 220px;
          }
          上例中,重复径向渐变数据释义如下
              第1层椭圆的X轴半径是60
              第2层椭圆的X轴半径130
              X轴层距70,130-60
              第1层椭圆的Y轴半径18.48,(220*35%)/250*60,y中心/x中心*1层x半径
              第2层椭圆的Y轴半径40.04,(220*35%)/250*130,y中心/x中心*2层x半径
              Y轴层距21.56,40.04-18.48 
        </div>
      </pre>
      <pre class="common repeatRate backgroundRepeat">
        <div class="paddingLeft">7、重复径向渐变-百分比</div>
        <div>
          .repeatRate{
              height: 400px;
              background-image: repeating-radial-gradient(250px 35% at center,red 20%, yellow 30%, green 45%);
          }
          上例中,重复径向渐变数据释义如下
              第1层椭圆的X轴半径是50,250*20%
              第2层椭圆的X轴半径112.5,250*45%
              X轴层距62.5,112.5-50
              第1层椭圆的Y轴半径28,(400*35%)/250*50,y中心/x中心*1层x半径
              第2层椭圆的Y轴半径63,(400*35%)/250*112.5,y中心/x中心*2层x半径
              Y轴层距35,63-28 
        </div>
      </pre>
      <pre class="common repeatRate backgroundRepeat backgroundSize">
        <div class="paddingLeft">8、重复径向渐变-百分比,含backgroundSize</div>
        <div>
          .repeatRate{
              height: 400px;
              background-image: repeating-radial-gradient(250px 35% at center,red 20%, yellow 30%, green 45%);
          }
          .backgroundSize{
              background-size:370px 220px;
          }
          上例中,重复径向渐变数据释义如下
              第1层椭圆的X轴半径是50,250*20%
              第2层椭圆的X轴半径112.5,250*45%
              X轴层距62.5,112.5-50
              第1层椭圆的Y轴半径15.4,(220*35%)/250*50,y中心/x中心*1层x半径
              第2层椭圆的Y轴半径34.65,(220*35%)/250*112.5,y中心/x中心*2层x半径
              Y轴层距19.25,34.65-15.4 
        </div>
      </pre>
      <pre class="common money">
        <div>
          附、重复径向渐变-铜钱效果
            .money{
              background-image:repeating-radial-gradient(circle,
                gray 0,gray 5px,transparent 5px,transparent 20px,
                gray 20px,gray 25px,transparent 25px,transparent 50px
              );
              background-size:90px 90px;
              display: flex;
              justify-content: flex-end;
              align-items: flex-end;
            }
        </div>
      </pre>
    </body>
  </html>
5、动画示例
  (1)单帧动画(含动画说明)
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>正常动画</title>
        <style>
          *{
            padding: 0;
            margin: 0;
          }
          .divUp {
            top: 150px;
            left: 800px;
            width: 1000px;
            height: 500px;
            line-height: 500px;
            background: rgb(131, 128, 128);
            border-radius: 60px;
            text-align: center;
            font-size: 150px;
            color: #000;
            position: relative;
            animation: upMove/*名称*/ 2s/*周期*/ ease/*速度*/ 0.1s/*延迟*/ infinite/*循环次数*/ alternate/*交替方向*/;
            /* animation,https://www.runoob.com/cssref/css3-pr-animation.html
              速度,https://www.runoob.com/cssref/css3-pr-animation-timing-function.html
                ease,默认,动画以低速开始,然后加快,在结束前变慢
                ease-in,动画以低速开始
                ease-out,动画以低速结束
                ease-in-out,动画以低速开始和结束
                linear,动画从头到尾的速度是相同的
              循环次数,https://www.runoob.com/cssref/css3-pr-animation-iteration-count.html
                number,一个数字,定义应该播放多少次动画
                infinite,指定动画应该播放无限次(永远)
              交替方向,https://www.runoob.com/cssref/css3-pr-animation-direction.html
                normal,正常播放,默认值
                reverse,反向播放
                alternate,奇正向播放,动画在奇数次(1、3、5...)正向播放,在偶数次(2、4、6...)反向播放
                alternate-reverse,奇反向播放,动画在奇数次(1、3、5...)反向播放,在偶数次(2、4、6...)正向播放
            */
          }
          @keyframes upMove {
            /* 这里只写需要改变的项,且to中各项应与.divUp中各项一致,否则动画结束时,运动区域会缩小或放大或其它改变 */
            from {
              top: 0px;
              left: 0px;
              width: 200px;
              height: 100px;
              line-height: 100px;
              background: rgb(252, 249, 249);
              border-radius: 0px;
              font-size: 20px;
              color: #a59999;
            }
            to {
              top: 150px;
              left: 800px;
              width: 1000px;
              height: 500px;
              line-height: 500px;
              background: rgb(131, 128, 128);
              border-radius: 60px;
              font-size: 150px;
              color: #000;
            }
          }
          /* 以上,动画效果-上 */
          /* 以下,动画效果-下 */
          .divDown {
            height: 100px;
            width: 940px;
            margin-top: 200px;/* 相对于未定位的.divUp的底部,定位后的.divUp脱离了文档流 */
            margin-left: 800px;
            background: rgb(131, 128, 128);
            animation: downMove 2s ease 0.1s infinite alternate;
            overflow: hidden;
            padding: 30px;
          }
          @keyframes downMove {
            /* 这里只写需要改变的项,且to中各项应与.divDown中各项一致,否则动画结束时,运动区域会缩小或放大或其它改变 */
            from {
              height: 0px;
            }
            to {
              height: 100px;
            }
          }
        </style>
      </head>
      <body>
        <div class="divUp">动画效果</div>
        <div class="divDown">
          <div>overflow: hidden; 1/10</div>
          <div>overflow: hidden; 2/10</div>
          <div>overflow: hidden; 3/10</div>
          <div>overflow: hidden; 4/10</div>
          <div>overflow: hidden; 5/10</div>
          <div>overflow: hidden; 6/10</div>
          <div>overflow: hidden; 7/10</div>
          <div>overflow: hidden; 8/10</div>
          <div>overflow: hidden; 9/10</div>
          <div>overflow: hidden; 10/10</div>
        </div>
      </body>
    </html>
  (2)多帧动画(含页面闪烁)
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>多帧动画(含页面闪烁)</title>
        <style>
          *{
            padding: 0;
            margin: 0;
          }
          .divSize{
            margin: 30px auto;
            color: rgb(27, 25, 25);
            background: rgb(131, 128, 128);
            display: flex;
            justify-content: center;
            align-items: center;
          }
          .fontSize{
            font-size: 200px;
          }
          .padding20{
            padding: 20px;
            width: 960px;
          }
          .height360{
            height: 360px;
            width: 1000px;
          }
          /* 以下动画触发 */
          .divDown {
            animation: widthMove 2s ease 0.1s infinite normal, heightMove 2s ease 0.1s infinite normal;
          }
          .divDown:hover {
            animation: widthMove 2s ease 0.1s infinite normal, heightMove 2s ease 0.1s infinite normal;
          }
          /* 以下动画定义 */
          @keyframes heightMove {
            from {
              height: 0px;
              font-size: 15px;
            }
            to {
              height: 360px;
              font-size: 200px;
            }
          }
          @keyframes widthMove {
            from {
              width: 0px;
              background: rgb(161, 95, 95);
            }
            to {
              width: 1000px;
              background: rgb(131, 128, 128);
            }
          }
        </style>
      </head>
      <body>
        <div class="divSize padding20">
          <pre>
            1、单用下面这种方式触发动画,页面正常
                .divDown {
                  widthMove 2s ease 0.1s infinite normal, heightMove 2s ease 0.1s infinite normal;
                }
            2、单用下面这种方式触发动画,<span style="color:white">页面会闪烁</span>,原因是
                在初始位置,面积大,为100%,hover触发动画,因为from面积小,为0,失去hover,回到初始位置,面积大,为100%,又造成hover触发动画
                .divDown:hover {
                  widthMove 2s ease 0.1s infinite normal, heightMove 2s ease 0.1s infinite normal;
                }
            3、以上2个同时使用,则第2个失效
          </pre>
        </div>
        <div class="divSize height360 fontSize divDown">居中</div>
      </body>
    </html>
6、过渡示例    
  (1)单个属性过渡(转换,内含过渡说明和转换说明)
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>单个属性过渡(转换)</title>
        <style> 
          div{
            width:600px;
            height:200px;
            margin-top: 200px;
            margin-left: 150px;
            background: #ccc;
            display: flex;
            justify-content: center;
            align-items: center;
            /* 初始时,指定“需要过渡的属性”及相关参数 */
            transition: transform/*CSS属性名*/ 2s/*过渡时长*/ ease-in/*运动速度*/ 0s/*延迟时长*/;
          }
          /* 悬停时,“需要过渡的属性”,由初始值或默认值-过渡到-指定值 */
          div:hover{
            transform: rotate(7deg)/*旋转*/ skew(20deg)/*倾斜*/ scale(2)/*缩放*/ translate(120px)/*移动*/;  
          }
        </style>
      </head>
      <body>
        <div>
          <pre>
            transform,转换,对元素使用2D或3D转换,百度翻译,使改变
            使用方法,https://www.runoob.com/cssref/css3-pr-transform.html
          </pre>
        </div>
      </body>
    </html>   
  (2)多个属性过渡(宽、高、字体大小,内含动画和过渡的区别) 
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>多个属性过渡(宽、高、字体大小)</title>
        <style> 
          div{
            width: 800px;
            height: 220px;
            font-size: 14px;
            transition: width 2s ease-in 0s, height 2s ease-in 0s, font-size 2s ease-in 0s;
          }
          div:hover{
            width: 1600px;
            height: 440px;
            font-size: 28px;
          }
          .div{
            background:rgb(95, 95, 97);
            padding: 50px 0;/* padding设置不能出现auto,否则发生padding:0的效果 */
          }
        </style>
      </head>
      <body>
        <div class="div">
          <pre>
            animation(动画)和transition(过渡)<span style="color: #fff;">的区别</span>
              来源,https://blog.csdn.net/qq_38290251/article/details/133644402
            A、应用场景:
              a、animation,用于实现复杂的动画效果,
              b、transition,用于实现简单的动画效果,
            B、实现方式:
              a、animation,先定义关键帧,然后指定关键帧及相关参数;可手动、自动触发的;可以循环播放
              b、transition,直接指定属性及相关参数;仅手动(悬停)触发;1次触发1次播放
            C、属性值,都可以是多组
              a、animation,每个属性值可以改变多个其它属性
              b、transition,每个属性值只能改变一个其它属性
          </pre>
        </div>
      </body>
    </html>

二、数据加密-AES和RSA
附、window编码与解码
(1)非中文
  var encodedData = window.btoa(123456);//'MTIzNDU2'
  var decodedData = window.atob(encodedData);//'123456'
(2)中文
  var encodedData = window.btoa(window.unescape(window.encodeURIComponent('你好')));//5L2g5aW9
  var decodedData = window.decodeURIComponent(window.escape(window.atob(encodedData)));//你好
1、AES加密 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- 引入 CDN Crypto.js 开始 AES加密 注意引入顺序 -->
<script type="text/javascript">
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/core.min.js
!function(t,n){"object"==typeof exports?module.exports=exports=n():"function"==typeof define&&define.amd?define([],n):t.CryptoJS=n()}(this,function(){var t=t||function(f){var t;if("undefined"!=typeof window&&window.crypto&&(t=window.crypto),!t&&"undefined"!=typeof window&&window.msCrypto&&(t=window.msCrypto),!t&&"undefined"!=typeof global&&global.crypto&&(t=global.crypto),!t&&"function"==typeof require)try{t=require("crypto")}catch(t){}function i(){if(t){if("function"==typeof t.getRandomValues)try{return t.getRandomValues(new Uint32Array(1))[0]}catch(t){}if("function"==typeof t.randomBytes)try{return t.randomBytes(4).readInt32LE()}catch(t){}}throw new Error("Native crypto module could not be used to get secure random number.")}var e=Object.create||function(t){var n;return r.prototype=t,n=new r,r.prototype=null,n};function r(){}var n={},o=n.lib={},s=o.Base={extend:function(t){var n=e(this);return t&&n.mixIn(t),n.hasOwnProperty("init")&&this.init!==n.init||(n.init=function(){n.$super.init.apply(this,arguments)}),(n.init.prototype=n).$super=this,n},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var n in t)t.hasOwnProperty(n)&&(this[n]=t[n]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}},p=o.WordArray=s.extend({init:function(t,n){t=this.words=t||[],this.sigBytes=null!=n?n:4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var n=this.words,e=t.words,i=this.sigBytes,r=t.sigBytes;if(this.clamp(),i%4)for(var o=0;o<r;o++){var s=e[o>>>2]>>>24-o%4*8&255;n[i+o>>>2]|=s<<24-(i+o)%4*8}else for(o=0;o<r;o+=4)n[i+o>>>2]=e[o>>>2];return this.sigBytes+=r,this},clamp:function(){var t=this.words,n=this.sigBytes;t[n>>>2]&=4294967295<<32-n%4*8,t.length=f.ceil(n/4)},clone:function(){var t=s.clone.call(this);return t.words=this.words.slice(0),t},random:function(t){for(var n=[],e=0;e<t;e+=4)n.push(i());return new p.init(n,t)}}),a=n.enc={},c=a.Hex={stringify:function(t){for(var n=t.words,e=t.sigBytes,i=[],r=0;r<e;r++){var o=n[r>>>2]>>>24-r%4*8&255;i.push((o>>>4).toString(16)),i.push((15&o).toString(16))}return i.join("")},parse:function(t){for(var n=t.length,e=[],i=0;i<n;i+=2)e[i>>>3]|=parseInt(t.substr(i,2),16)<<24-i%8*4;return new p.init(e,n/2)}},u=a.Latin1={stringify:function(t){for(var n=t.words,e=t.sigBytes,i=[],r=0;r<e;r++){var o=n[r>>>2]>>>24-r%4*8&255;i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var n=t.length,e=[],i=0;i<n;i++)e[i>>>2]|=(255&t.charCodeAt(i))<<24-i%4*8;return new p.init(e,n)}},d=a.Utf8={stringify:function(t){try{return decodeURIComponent(escape(u.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return u.parse(unescape(encodeURIComponent(t)))}},h=o.BufferedBlockAlgorithm=s.extend({reset:function(){this._data=new p.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=d.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(t){var n,e=this._data,i=e.words,r=e.sigBytes,o=this.blockSize,s=r/(4*o),a=(s=t?f.ceil(s):f.max((0|s)-this._minBufferSize,0))*o,c=f.min(4*a,r);if(a){for(var u=0;u<a;u+=o)this._doProcessBlock(i,u);n=i.splice(0,a),e.sigBytes-=c}return new p.init(n,c)},clone:function(){var t=s.clone.call(this);return t._data=this._data.clone(),t},_minBufferSize:0}),l=(o.Hasher=h.extend({cfg:s.extend(),init:function(t){this.cfg=this.cfg.extend(t),this.reset()},reset:function(){h.reset.call(this),this._doReset()},update:function(t){return this._append(t),this._process(),this},finalize:function(t){return t&&this._append(t),this._doFinalize()},blockSize:16,_createHelper:function(e){return function(t,n){return new e.init(n).finalize(t)}},_createHmacHelper:function(e){return function(t,n){return new l.HMAC.init(e,n).finalize(t)}}}),n.algo={});return n}(Math);return t});
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/enc-base64.min.js
!function(r,e){"object"==typeof exports?module.exports=exports=e(require("./core")):"function"==typeof define&&define.amd?define(["./core"],e):e(r.CryptoJS)}(this,function(r){var s;return s=r.lib.WordArray,r.enc.Base64={stringify:function(r){var e=r.words,t=r.sigBytes,a=this._map;r.clamp();for(var n=[],o=0;o<t;o+=3)for(var i=(e[o>>>2]>>>24-o%4*8&255)<<16|(e[o+1>>>2]>>>24-(o+1)%4*8&255)<<8|e[o+2>>>2]>>>24-(o+2)%4*8&255,f=0;f<4&&o+.75*f<t;f++)n.push(a.charAt(i>>>6*(3-f)&63));var c=a.charAt(64);if(c)for(;n.length%4;)n.push(c);return n.join("")},parse:function(r){var e=r.length,t=this._map,a=this._reverseMap;if(!a){a=this._reverseMap=[];for(var n=0;n<t.length;n++)a[t.charCodeAt(n)]=n}var o=t.charAt(64);if(o){var i=r.indexOf(o);-1!==i&&(e=i)}return function(r,e,t){for(var a=[],n=0,o=0;o<e;o++)if(o%4){var i=t[r.charCodeAt(o-1)]<<o%4*2,f=t[r.charCodeAt(o)]>>>6-o%4*2,c=i|f;a[n>>>2]|=c<<24-n%4*8,n++}return s.create(a,n)}(r,e,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="},r.enc.Base64});
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/md5.min.js
!function(r,e){"object"==typeof exports?module.exports=exports=e(require("./core")):"function"==typeof define&&define.amd?define(["./core"],e):e(r.CryptoJS)}(this,function(i){return function(h){var r=i,e=r.lib,t=e.WordArray,n=e.Hasher,o=r.algo,b=[];!function(){for(var r=0;r<64;r++)b[r]=4294967296*h.abs(h.sin(r+1))|0}();var a=o.MD5=n.extend({_doReset:function(){this._hash=new t.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(r,e){for(var t=0;t<16;t++){var n=e+t,o=r[n];r[n]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8)}var a=this._hash.words,i=r[e+0],s=r[e+1],c=r[e+2],f=r[e+3],h=r[e+4],u=r[e+5],v=r[e+6],d=r[e+7],l=r[e+8],_=r[e+9],p=r[e+10],y=r[e+11],D=r[e+12],H=r[e+13],M=r[e+14],g=r[e+15],m=a[0],w=a[1],x=a[2],B=a[3];m=j(m,w,x,B,i,7,b[0]),B=j(B,m,w,x,s,12,b[1]),x=j(x,B,m,w,c,17,b[2]),w=j(w,x,B,m,f,22,b[3]),m=j(m,w,x,B,h,7,b[4]),B=j(B,m,w,x,u,12,b[5]),x=j(x,B,m,w,v,17,b[6]),w=j(w,x,B,m,d,22,b[7]),m=j(m,w,x,B,l,7,b[8]),B=j(B,m,w,x,_,12,b[9]),x=j(x,B,m,w,p,17,b[10]),w=j(w,x,B,m,y,22,b[11]),m=j(m,w,x,B,D,7,b[12]),B=j(B,m,w,x,H,12,b[13]),x=j(x,B,m,w,M,17,b[14]),m=k(m,w=j(w,x,B,m,g,22,b[15]),x,B,s,5,b[16]),B=k(B,m,w,x,v,9,b[17]),x=k(x,B,m,w,y,14,b[18]),w=k(w,x,B,m,i,20,b[19]),m=k(m,w,x,B,u,5,b[20]),B=k(B,m,w,x,p,9,b[21]),x=k(x,B,m,w,g,14,b[22]),w=k(w,x,B,m,h,20,b[23]),m=k(m,w,x,B,_,5,b[24]),B=k(B,m,w,x,M,9,b[25]),x=k(x,B,m,w,f,14,b[26]),w=k(w,x,B,m,l,20,b[27]),m=k(m,w,x,B,H,5,b[28]),B=k(B,m,w,x,c,9,b[29]),x=k(x,B,m,w,d,14,b[30]),m=q(m,w=k(w,x,B,m,D,20,b[31]),x,B,u,4,b[32]),B=q(B,m,w,x,l,11,b[33]),x=q(x,B,m,w,y,16,b[34]),w=q(w,x,B,m,M,23,b[35]),m=q(m,w,x,B,s,4,b[36]),B=q(B,m,w,x,h,11,b[37]),x=q(x,B,m,w,d,16,b[38]),w=q(w,x,B,m,p,23,b[39]),m=q(m,w,x,B,H,4,b[40]),B=q(B,m,w,x,i,11,b[41]),x=q(x,B,m,w,f,16,b[42]),w=q(w,x,B,m,v,23,b[43]),m=q(m,w,x,B,_,4,b[44]),B=q(B,m,w,x,D,11,b[45]),x=q(x,B,m,w,g,16,b[46]),m=z(m,w=q(w,x,B,m,c,23,b[47]),x,B,i,6,b[48]),B=z(B,m,w,x,d,10,b[49]),x=z(x,B,m,w,M,15,b[50]),w=z(w,x,B,m,u,21,b[51]),m=z(m,w,x,B,D,6,b[52]),B=z(B,m,w,x,f,10,b[53]),x=z(x,B,m,w,p,15,b[54]),w=z(w,x,B,m,s,21,b[55]),m=z(m,w,x,B,l,6,b[56]),B=z(B,m,w,x,g,10,b[57]),x=z(x,B,m,w,v,15,b[58]),w=z(w,x,B,m,H,21,b[59]),m=z(m,w,x,B,h,6,b[60]),B=z(B,m,w,x,y,10,b[61]),x=z(x,B,m,w,c,15,b[62]),w=z(w,x,B,m,_,21,b[63]),a[0]=a[0]+m|0,a[1]=a[1]+w|0,a[2]=a[2]+x|0,a[3]=a[3]+B|0},_doFinalize:function(){var r=this._data,e=r.words,t=8*this._nDataBytes,n=8*r.sigBytes;e[n>>>5]|=128<<24-n%32;var o=h.floor(t/4294967296),a=t;e[15+(64+n>>>9<<4)]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8),e[14+(64+n>>>9<<4)]=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8),r.sigBytes=4*(e.length+1),this._process();for(var i=this._hash,s=i.words,c=0;c<4;c++){var f=s[c];s[c]=16711935&(f<<8|f>>>24)|4278255360&(f<<24|f>>>8)}return i},clone:function(){var r=n.clone.call(this);return r._hash=this._hash.clone(),r}});function j(r,e,t,n,o,a,i){var s=r+(e&t|~e&n)+o+i;return(s<<a|s>>>32-a)+e}function k(r,e,t,n,o,a,i){var s=r+(e&n|t&~n)+o+i;return(s<<a|s>>>32-a)+e}function q(r,e,t,n,o,a,i){var s=r+(e^t^n)+o+i;return(s<<a|s>>>32-a)+e}function z(r,e,t,n,o,a,i){var s=r+(t^(e|~n))+o+i;return(s<<a|s>>>32-a)+e}r.MD5=n._createHelper(a),r.HmacMD5=n._createHmacHelper(a)}(Math),i.MD5});
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/evpkdf.min.js
!function(e,t){"object"==typeof exports?module.exports=exports=t(require("./core"),require("./sha1"),require("./hmac")):"function"==typeof define&&define.amd?define(["./core","./sha1","./hmac"],t):t(e.CryptoJS)}(this,function(e){var t,r,i,u,n,o,a;return r=(t=e).lib,i=r.Base,u=r.WordArray,n=t.algo,o=n.MD5,a=n.EvpKDF=i.extend({cfg:i.extend({keySize:4,hasher:o,iterations:1}),init:function(e){this.cfg=this.cfg.extend(e)},compute:function(e,t){for(var r,i=this.cfg,n=i.hasher.create(),o=u.create(),a=o.words,c=i.keySize,f=i.iterations;a.length<c;){r&&n.update(r),r=n.update(e).finalize(t),n.reset();for(var s=1;s<f;s++)r=n.finalize(r),n.reset();o.concat(r)}return o.sigBytes=4*c,o}}),t.EvpKDF=function(e,t,r){return a.create(r).compute(e,t)},e.EvpKDF});
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/cipher-core.min.js
!function(e,t){"object"==typeof exports?module.exports=exports=t(require("./core"),require("./evpkdf")):"function"==typeof define&&define.amd?define(["./core","./evpkdf"],t):t(e.CryptoJS)}(this,function(m){m.lib.Cipher||function(){var e=m,t=e.lib,r=t.Base,a=t.WordArray,i=t.BufferedBlockAlgorithm,n=e.enc,c=(n.Utf8,n.Base64),o=e.algo.EvpKDF,s=t.Cipher=i.extend({cfg:r.extend(),createEncryptor:function(e,t){return this.create(this._ENC_XFORM_MODE,e,t)},createDecryptor:function(e,t){return this.create(this._DEC_XFORM_MODE,e,t)},init:function(e,t,r){this.cfg=this.cfg.extend(r),this._xformMode=e,this._key=t,this.reset()},reset:function(){i.reset.call(this),this._doReset()},process:function(e){return this._append(e),this._process()},finalize:function(e){return e&&this._append(e),this._doFinalize()},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(i){return{encrypt:function(e,t,r){return f(t).encrypt(i,e,t,r)},decrypt:function(e,t,r){return f(t).decrypt(i,e,t,r)}}}});function f(e){return"string"==typeof e?g:k}t.StreamCipher=s.extend({_doFinalize:function(){return this._process(!0)},blockSize:1});var p,d=e.mode={},h=t.BlockCipherMode=r.extend({createEncryptor:function(e,t){return this.Encryptor.create(e,t)},createDecryptor:function(e,t){return this.Decryptor.create(e,t)},init:function(e,t){this._cipher=e,this._iv=t}}),u=d.CBC=((p=h.extend()).Encryptor=p.extend({processBlock:function(e,t){var r=this._cipher,i=r.blockSize;_.call(this,e,t,i),r.encryptBlock(e,t),this._prevBlock=e.slice(t,t+i)}}),p.Decryptor=p.extend({processBlock:function(e,t){var r=this._cipher,i=r.blockSize,n=e.slice(t,t+i);r.decryptBlock(e,t),_.call(this,e,t,i),this._prevBlock=n}}),p);function _(e,t,r){var i,n=this._iv;n?(i=n,this._iv=void 0):i=this._prevBlock;for(var c=0;c<r;c++)e[t+c]^=i[c]}var l=(e.pad={}).Pkcs7={pad:function(e,t){for(var r=4*t,i=r-e.sigBytes%r,n=i<<24|i<<16|i<<8|i,c=[],o=0;o<i;o+=4)c.push(n);var s=a.create(c,i);e.concat(s)},unpad:function(e){var t=255&e.words[e.sigBytes-1>>>2];e.sigBytes-=t}},y=(t.BlockCipher=s.extend({cfg:s.cfg.extend({mode:u,padding:l}),reset:function(){var e;s.reset.call(this);var t=this.cfg,r=t.iv,i=t.mode;this._xformMode==this._ENC_XFORM_MODE?e=i.createEncryptor:(e=i.createDecryptor,this._minBufferSize=1),this._mode&&this._mode.__creator==e?this._mode.init(this,r&&r.words):(this._mode=e.call(i,this,r&&r.words),this._mode.__creator=e)},_doProcessBlock:function(e,t){this._mode.processBlock(e,t)},_doFinalize:function(){var e,t=this.cfg.padding;return this._xformMode==this._ENC_XFORM_MODE?(t.pad(this._data,this.blockSize),e=this._process(!0)):(e=this._process(!0),t.unpad(e)),e},blockSize:4}),t.CipherParams=r.extend({init:function(e){this.mixIn(e)},toString:function(e){return(e||this.formatter).stringify(this)}})),v=(e.format={}).OpenSSL={stringify:function(e){var t=e.ciphertext,r=e.salt;return(r?a.create([1398893684,1701076831]).concat(r).concat(t):t).toString(c)},parse:function(e){var t,r=c.parse(e),i=r.words;return 1398893684==i[0]&&1701076831==i[1]&&(t=a.create(i.slice(2,4)),i.splice(0,4),r.sigBytes-=16),y.create({ciphertext:r,salt:t})}},k=t.SerializableCipher=r.extend({cfg:r.extend({format:v}),encrypt:function(e,t,r,i){i=this.cfg.extend(i);var n=e.createEncryptor(r,i),c=n.finalize(t),o=n.cfg;return y.create({ciphertext:c,key:r,iv:o.iv,algorithm:e,mode:o.mode,padding:o.padding,blockSize:e.blockSize,formatter:i.format})},decrypt:function(e,t,r,i){return i=this.cfg.extend(i),t=this._parse(t,i.format),e.createDecryptor(r,i).finalize(t.ciphertext)},_parse:function(e,t){return"string"==typeof e?t.parse(e,this):e}}),x=(e.kdf={}).OpenSSL={execute:function(e,t,r,i){i=i||a.random(8);var n=o.create({keySize:t+r}).compute(e,i),c=a.create(n.words.slice(t),4*r);return n.sigBytes=4*t,y.create({key:n,iv:c,salt:i})}},g=t.PasswordBasedCipher=k.extend({cfg:k.cfg.extend({kdf:x}),encrypt:function(e,t,r,i){var n=(i=this.cfg.extend(i)).kdf.execute(r,e.keySize,e.ivSize);i.iv=n.iv;var c=k.encrypt.call(this,e,t,n.key,i);return c.mixIn(n),c},decrypt:function(e,t,r,i){i=this.cfg.extend(i),t=this._parse(t,i.format);var n=i.kdf.execute(r,e.keySize,e.ivSize,t.salt);return i.iv=n.iv,k.decrypt.call(this,e,t,n.key,i)}})}()});
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/aes.min.js
!function(e,r){"object"==typeof exports?module.exports=exports=r(require("./core"),require("./enc-base64"),require("./md5"),require("./evpkdf"),require("./cipher-core")):"function"==typeof define&&define.amd?define(["./core","./enc-base64","./md5","./evpkdf","./cipher-core"],r):r(e.CryptoJS)}(this,function(t){return function(){var e=t,r=e.lib.BlockCipher,i=e.algo,f=[],u=[],h=[],y=[],a=[],p=[],v=[],_=[],k=[],l=[];!function(){for(var e=[],r=0;r<256;r++)e[r]=r<128?r<<1:r<<1^283;var i=0,o=0;for(r=0;r<256;r++){var t=o^o<<1^o<<2^o<<3^o<<4;t=t>>>8^255&t^99,f[i]=t;var n=e[u[t]=i],c=e[n],s=e[c],d=257*e[t]^16843008*t;h[i]=d<<24|d>>>8,y[i]=d<<16|d>>>16,a[i]=d<<8|d>>>24,p[i]=d;d=16843009*s^65537*c^257*n^16843008*i;v[t]=d<<24|d>>>8,_[t]=d<<16|d>>>16,k[t]=d<<8|d>>>24,l[t]=d,i?(i=n^e[e[e[s^n]]],o^=e[e[o]]):i=o=1}}();var S=[0,1,2,4,8,16,32,64,128,27,54],o=i.AES=r.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var e=this._keyPriorReset=this._key,r=e.words,i=e.sigBytes/4,o=4*(1+(this._nRounds=6+i)),t=this._keySchedule=[],n=0;n<o;n++)n<i?t[n]=r[n]:(d=t[n-1],n%i?6<i&&n%i==4&&(d=f[d>>>24]<<24|f[d>>>16&255]<<16|f[d>>>8&255]<<8|f[255&d]):(d=f[(d=d<<8|d>>>24)>>>24]<<24|f[d>>>16&255]<<16|f[d>>>8&255]<<8|f[255&d],d^=S[n/i|0]<<24),t[n]=t[n-i]^d);for(var c=this._invKeySchedule=[],s=0;s<o;s++){n=o-s;if(s%4)var d=t[n];else d=t[n-4];c[s]=s<4||n<=4?d:v[f[d>>>24]]^_[f[d>>>16&255]]^k[f[d>>>8&255]]^l[f[255&d]]}}},encryptBlock:function(e,r){this._doCryptBlock(e,r,this._keySchedule,h,y,a,p,f)},decryptBlock:function(e,r){var i=e[r+1];e[r+1]=e[r+3],e[r+3]=i,this._doCryptBlock(e,r,this._invKeySchedule,v,_,k,l,u);i=e[r+1];e[r+1]=e[r+3],e[r+3]=i},_doCryptBlock:function(e,r,i,o,t,n,c,s){for(var d=this._nRounds,f=e[r]^i[0],u=e[r+1]^i[1],h=e[r+2]^i[2],y=e[r+3]^i[3],a=4,p=1;p<d;p++){var v=o[f>>>24]^t[u>>>16&255]^n[h>>>8&255]^c[255&y]^i[a++],_=o[u>>>24]^t[h>>>16&255]^n[y>>>8&255]^c[255&f]^i[a++],k=o[h>>>24]^t[y>>>16&255]^n[f>>>8&255]^c[255&u]^i[a++],l=o[y>>>24]^t[f>>>16&255]^n[u>>>8&255]^c[255&h]^i[a++];f=v,u=_,h=k,y=l}v=(s[f>>>24]<<24|s[u>>>16&255]<<16|s[h>>>8&255]<<8|s[255&y])^i[a++],_=(s[u>>>24]<<24|s[h>>>16&255]<<16|s[y>>>8&255]<<8|s[255&f])^i[a++],k=(s[h>>>24]<<24|s[y>>>16&255]<<16|s[f>>>8&255]<<8|s[255&u])^i[a++],l=(s[y>>>24]<<24|s[f>>>16&255]<<16|s[u>>>8&255]<<8|s[255&h])^i[a++];e[r]=v,e[r+1]=_,e[r+2]=k,e[r+3]=l},keySize:8});e.AES=r._createHelper(o)}(),t.AES});
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/pad-pkcs7.min.js
!function(e,r){"object"==typeof exports?module.exports=exports=r(require("./core"),require("./cipher-core")):"function"==typeof define&&define.amd?define(["./core","./cipher-core"],r):r(e.CryptoJS)}(this,function(e){return e.pad.Pkcs7});
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/mode-ecb.min.js
!function(e,o){"object"==typeof exports?module.exports=exports=o(require("./core"),require("./cipher-core")):"function"==typeof define&&define.amd?define(["./core","./cipher-core"],o):o(e.CryptoJS)}(this,function(e){var o;return e.mode.ECB=((o=e.lib.BlockCipherMode.extend()).Encryptor=o.extend({processBlock:function(e,o){this._cipher.encryptBlock(e,o)}}),o.Decryptor=o.extend({processBlock:function(e,o){this._cipher.decryptBlock(e,o)}}),o),e.mode.ECB});
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/enc-utf8.min.js
!function(e,o){"object"==typeof exports?module.exports=exports=o(require("./core")):"function"==typeof define&&define.amd?define(["./core"],o):o(e.CryptoJS)}(this,function(e){return e.enc.Utf8});
//https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/enc-hex.min.js
!function(e,o){"object"==typeof exports?module.exports=exports=o(require("./core")):"function"==typeof define&&define.amd?define(["./core"],o):o(e.CryptoJS)}(this,function(e){return e.enc.Hex});
</script>
<!-- 引入 CDN Crypto.js 结束 -->
</head>
<body>
<p>AES简介:</p>
<p>高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法,</p>
<p>也就是加密和解密用相同的密钥</p>
<p>微信小程序加密传输就是用这个加密算法的</p>
<p>我们公司禁止使用简易加密,如MD5编码、Base64编码、URL编码。</p>
<p>后台加密步骤为:对象=>JSON字符串=>加密=>JSON字符串=>前端</p>
<p>前端解密步骤为:解密=>JSON对象</p>
</body>
</html>
<script>
function getCrypt(){
  var crypt_key = '049d65f30e854b08';//由后台提供
  var crypt_iv = '98da515e3935c342';//由后台提供
  var aes_key = CryptoJS.enc.Utf8.parse(crypt_key);  //解析后的key
  var new_iv = CryptoJS.enc.Utf8.parse(crypt_iv); //解析后的iv
  var obj = {
    iv: new_iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  }
  function encrypt(data) {//以下加密
    encrypted = CryptoJS.AES.encrypt(data, aes_key, obj);
    return encrypted.toString()
  }
  function decrypt(data) {//以下解密
    decrypted = CryptoJS.AES.decrypt(data, aes_key, obj);
    return decrypted.toString(CryptoJS.enc.Utf8)
  }
  return{
    encrypt:encrypt,
    decrypt:decrypt
  }
}
var crypt= getCrypt();
var strCrypt = 'zhangqiang,你好';
var getEncrypt = crypt.encrypt(strCrypt);
console.log(getEncrypt);
var getDecrypt = crypt.decrypt(getEncrypt);
console.log(getDecrypt);
//可以通过正则匹配将非JSON格式的字符串getDecrypt转化为JSON格式的字符串,再通过JSON.parse转化为JSON格式的对象,供页面使用。
//如果内层的某个对象需要以单引号字符串的形式展示在页面上,那么可以通过JSON.stringify将该对象转化为JSON格式的字符串,再通过正则匹配将双引号换成单引号
</script>
2、RSA加密
(1)jsencrypt.min.js 
/*! For license information please see jsencrypt.min.js.LICENSE.txt */
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.JSEncrypt=e():t.JSEncrypt=e()}(window,(()=>(()=>{var t={155:t=>{var e,i,r=t.exports={};function n(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function o(t){if(e===setTimeout)return setTimeout(t,0);if((e===n||!e)&&setTimeout)return e=setTimeout,setTimeout(t,0);try{return e(t,0)}catch(i){try{return e.call(null,t,0)}catch(i){return e.call(this,t,0)}}}!function(){try{e="function"==typeof setTimeout?setTimeout:n}catch(t){e=n}try{i="function"==typeof clearTimeout?clearTimeout:s}catch(t){i=s}}();var h,a=[],u=!1,c=-1;function f(){u&&h&&(u=!1,h.length?a=h.concat(a):c=-1,a.length&&l())}function l(){if(!u){var t=o(f);u=!0;for(var e=a.length;e;){for(h=a,a=[];++c<e;)h&&h[c].run();c=-1,e=a.length}h=null,u=!1,function(t){if(i===clearTimeout)return clearTimeout(t);if((i===s||!i)&&clearTimeout)return i=clearTimeout,clearTimeout(t);try{i(t)}catch(e){try{return i.call(null,t)}catch(e){return i.call(this,t)}}}(t)}}function p(t,e){this.fun=t,this.array=e}function g(){}r.nextTick=function(t){var e=new Array(arguments.length-1);if(arguments.length>1)for(var i=1;i<arguments.length;i++)e[i-1]=arguments[i];a.push(new p(t,e)),1!==a.length||u||o(l)},p.prototype.run=function(){this.fun.apply(null,this.array)},r.title="browser",r.browser=!0,r.env={},r.argv=[],r.version="",r.versions={},r.on=g,r.addListener=g,r.once=g,r.off=g,r.removeListener=g,r.removeAllListeners=g,r.emit=g,r.prependListener=g,r.prependOnceListener=g,r.listeners=function(t){return[]},r.binding=function(t){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(t){throw new Error("process.chdir is not supported")},r.umask=function(){return 0}}},e={};function i(r){var n=e[r];if(void 0!==n)return n.exports;var s=e[r]={exports:{}};return t[r](s,s.exports,i),s.exports}i.d=(t,e)=>{for(var r in e)i.o(e,r)&&!i.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var r={};return(()=>{"use strict";function t(t){return"0123456789abcdefghijklmnopqrstuvwxyz".charAt(t)}function e(t,e){return t&e}function n(t,e){return t|e}function s(t,e){return t^e}function o(t,e){return t&~e}function h(t){if(0==t)return-1;var e=0;return 0==(65535&t)&&(t>>=16,e+=16),0==(255&t)&&(t>>=8,e+=8),0==(15&t)&&(t>>=4,e+=4),0==(3&t)&&(t>>=2,e+=2),0==(1&t)&&++e,e}function a(t){for(var e=0;0!=t;)t&=t-1,++e;return e}i.d(r,{default:()=>ot});var u,c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";function f(t){var e,i,r="";for(e=0;e+3<=t.length;e+=3)i=parseInt(t.substring(e,e+3),16),r+=c.charAt(i>>6)+c.charAt(63&i);for(e+1==t.length?(i=parseInt(t.substring(e,e+1),16),r+=c.charAt(i<<2)):e+2==t.length&&(i=parseInt(t.substring(e,e+2),16),r+=c.charAt(i>>2)+c.charAt((3&i)<<4));(3&r.length)>0;)r+="=";return r}function l(e){var i,r="",n=0,s=0;for(i=0;i<e.length&&"="!=e.charAt(i);++i){var o=c.indexOf(e.charAt(i));o<0||(0==n?(r+=t(o>>2),s=3&o,n=1):1==n?(r+=t(s<<2|o>>4),s=15&o,n=2):2==n?(r+=t(s),r+=t(o>>2),s=3&o,n=3):(r+=t(s<<2|o>>4),r+=t(15&o),n=0))}return 1==n&&(r+=t(s<<2)),r}var p,g={decode:function(t){var e;if(void 0===p){var i="= \f\n\r\t \u2028\u2029";for(p=Object.create(null),e=0;e<64;++e)p["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(e)]=e;for(p["-"]=62,p._=63,e=0;e<i.length;++e)p[i.charAt(e)]=-1}var r=[],n=0,s=0;for(e=0;e<t.length;++e){var o=t.charAt(e);if("="==o)break;if(-1!=(o=p[o])){if(void 0===o)throw new Error("Illegal character at offset "+e);n|=o,++s>=4?(r[r.length]=n>>16,r[r.length]=n>>8&255,r[r.length]=255&n,n=0,s=0):n<<=6}}switch(s){case 1:throw new Error("Base64 encoding incomplete: at least 2 bits missing");case 2:r[r.length]=n>>10;break;case 3:r[r.length]=n>>16,r[r.length]=n>>8&255}return r},re:/-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/,unarmor:function(t){var e=g.re.exec(t);if(e)if(e[1])t=e[1];else{if(!e[2])throw new Error("RegExp out of sync");t=e[2]}return g.decode(t)}},d=1e13,v=function(){function t(t){this.buf=[+t||0]}return t.prototype.mulAdd=function(t,e){var i,r,n=this.buf,s=n.length;for(i=0;i<s;++i)(r=n[i]*t+e)<d?e=0:r-=(e=0|r/d)*d,n[i]=r;e>0&&(n[i]=e)},t.prototype.sub=function(t){var e,i,r=this.buf,n=r.length;for(e=0;e<n;++e)(i=r[e]-t)<0?(i+=d,t=1):t=0,r[e]=i;for(;0===r[r.length-1];)r.pop()},t.prototype.toString=function(t){if(10!=(t||10))throw new Error("only base 10 is supported");for(var e=this.buf,i=e[e.length-1].toString(),r=e.length-2;r>=0;--r)i+=(d+e[r]).toString().substring(1);return i},t.prototype.valueOf=function(){for(var t=this.buf,e=0,i=t.length-1;i>=0;--i)e=e*d+t[i];return e},t.prototype.simplify=function(){var t=this.buf;return 1==t.length?t[0]:this},t}(),m=/^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/,y=/^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;function b(t,e){return t.length>e&&(t=t.substring(0,e)+"…"),t}var T,S=function(){function t(e,i){this.hexDigits="0123456789ABCDEF",e instanceof t?(this.enc=e.enc,this.pos=e.pos):(this.enc=e,this.pos=i)}return t.prototype.get=function(t){if(void 0===t&&(t=this.pos++),t>=this.enc.length)throw new Error("Requesting byte offset ".concat(t," on a stream of length ").concat(this.enc.length));return"string"==typeof this.enc?this.enc.charCodeAt(t):this.enc[t]},t.prototype.hexByte=function(t){return this.hexDigits.charAt(t>>4&15)+this.hexDigits.charAt(15&t)},t.prototype.hexDump=function(t,e,i){for(var r="",n=t;n<e;++n)if(r+=this.hexByte(this.get(n)),!0!==i)switch(15&n){case 7:r+="  ";break;case 15:r+="\n";break;default:r+=" "}return r},t.prototype.isASCII=function(t,e){for(var i=t;i<e;++i){var r=this.get(i);if(r<32||r>176)return!1}return!0},t.prototype.parseStringISO=function(t,e){for(var i="",r=t;r<e;++r)i+=String.fromCharCode(this.get(r));return i},t.prototype.parseStringUTF=function(t,e){for(var i="",r=t;r<e;){var n=this.get(r++);i+=n<128?String.fromCharCode(n):n>191&&n<224?String.fromCharCode((31&n)<<6|63&this.get(r++)):String.fromCharCode((15&n)<<12|(63&this.get(r++))<<6|63&this.get(r++))}return i},t.prototype.parseStringBMP=function(t,e){for(var i,r,n="",s=t;s<e;)i=this.get(s++),r=this.get(s++),n+=String.fromCharCode(i<<8|r);return n},t.prototype.parseTime=function(t,e,i){var r=this.parseStringISO(t,e),n=(i?m:y).exec(r);return n?(i&&(n[1]=+n[1],n[1]+=+n[1]<70?2e3:1900),r=n[1]+"-"+n[2]+"-"+n[3]+" "+n[4],n[5]&&(r+=":"+n[5],n[6]&&(r+=":"+n[6],n[7]&&(r+="."+n[7]))),n[8]&&(r+=" UTC","Z"!=n[8]&&(r+=n[8],n[9]&&(r+=":"+n[9]))),r):"Unrecognized time: "+r},t.prototype.parseInteger=function(t,e){for(var i,r=this.get(t),n=r>127,s=n?255:0,o="";r==s&&++t<e;)r=this.get(t);if(0==(i=e-t))return n?-1:0;if(i>4){for(o=r,i<<=3;0==(128&(+o^s));)o=+o<<1,--i;o="("+i+" bit)\n"}n&&(r-=256);for(var h=new v(r),a=t+1;a<e;++a)h.mulAdd(256,this.get(a));return o+h.toString()},t.prototype.parseBitString=function(t,e,i){for(var r=this.get(t),n="("+((e-t-1<<3)-r)+" bit)\n",s="",o=t+1;o<e;++o){for(var h=this.get(o),a=o==e-1?r:0,u=7;u>=a;--u)s+=h>>u&1?"1":"0";if(s.length>i)return n+b(s,i)}return n+s},t.prototype.parseOctetString=function(t,e,i){if(this.isASCII(t,e))return b(this.parseStringISO(t,e),i);var r=e-t,n="("+r+" byte)\n";r>(i/=2)&&(e=t+i);for(var s=t;s<e;++s)n+=this.hexByte(this.get(s));return r>i&&(n+="…"),n},t.prototype.parseOID=function(t,e,i){for(var r="",n=new v,s=0,o=t;o<e;++o){var h=this.get(o);if(n.mulAdd(128,127&h),s+=7,!(128&h)){if(""===r)if((n=n.simplify())instanceof v)n.sub(80),r="2."+n.toString();else{var a=n<80?n<40?0:1:2;r=a+"."+(n-40*a)}else r+="."+n.toString();if(r.length>i)return b(r,i);n=new v,s=0}}return s>0&&(r+=".incomplete"),r},t}(),E=function(){function t(t,e,i,r,n){if(!(r instanceof w))throw new Error("Invalid tag value.");this.stream=t,this.header=e,this.length=i,this.tag=r,this.sub=n}return t.prototype.typeName=function(){switch(this.tag.tagClass){case 0:switch(this.tag.tagNumber){case 0:return"EOC";case 1:return"BOOLEAN";case 2:return"INTEGER";case 3:return"BIT_STRING";case 4:return"OCTET_STRING";case 5:return"NULL";case 6:return"OBJECT_IDENTIFIER";case 7:return"ObjectDescriptor";case 8:return"EXTERNAL";case 9:return"REAL";case 10:return"ENUMERATED";case 11:return"EMBEDDED_PDV";case 12:return"UTF8String";case 16:return"SEQUENCE";case 17:return"SET";case 18:return"NumericString";case 19:return"PrintableString";case 20:return"TeletexString";case 21:return"VideotexString";case 22:return"IA5String";case 23:return"UTCTime";case 24:return"GeneralizedTime";case 25:return"GraphicString";case 26:return"VisibleString";case 27:return"GeneralString";case 28:return"UniversalString";case 30:return"BMPString"}return"Universal_"+this.tag.tagNumber.toString();case 1:return"Application_"+this.tag.tagNumber.toString();case 2:return"["+this.tag.tagNumber.toString()+"]";case 3:return"Private_"+this.tag.tagNumber.toString()}},t.prototype.content=function(t){if(void 0===this.tag)return null;void 0===t&&(t=1/0);var e=this.posContent(),i=Math.abs(this.length);if(!this.tag.isUniversal())return null!==this.sub?"("+this.sub.length+" elem)":this.stream.parseOctetString(e,e+i,t);switch(this.tag.tagNumber){case 1:return 0===this.stream.get(e)?"false":"true";case 2:return this.stream.parseInteger(e,e+i);case 3:return this.sub?"("+this.sub.length+" elem)":this.stream.parseBitString(e,e+i,t);case 4:return this.sub?"("+this.sub.length+" elem)":this.stream.parseOctetString(e,e+i,t);case 6:return this.stream.parseOID(e,e+i,t);case 16:case 17:return null!==this.sub?"("+this.sub.length+" elem)":"(no elem)";case 12:return b(this.stream.parseStringUTF(e,e+i),t);case 18:case 19:case 20:case 21:case 22:case 26:return b(this.stream.parseStringISO(e,e+i),t);case 30:return b(this.stream.parseStringBMP(e,e+i),t);case 23:case 24:return this.stream.parseTime(e,e+i,23==this.tag.tagNumber)}return null},t.prototype.toString=function(){return this.typeName()+"@"+this.stream.pos+"[header:"+this.header+",length:"+this.length+",sub:"+(null===this.sub?"null":this.sub.length)+"]"},t.prototype.toPrettyString=function(t){void 0===t&&(t="");var e=t+this.typeName()+" @"+this.stream.pos;if(this.length>=0&&(e+="+"),e+=this.length,this.tag.tagConstructed?e+=" (constructed)":!this.tag.isUniversal()||3!=this.tag.tagNumber&&4!=this.tag.tagNumber||null===this.sub||(e+=" (encapsulates)"),e+="\n",null!==this.sub){t+="  ";for(var i=0,r=this.sub.length;i<r;++i)e+=this.sub[i].toPrettyString(t)}return e},t.prototype.posStart=function(){return this.stream.pos},t.prototype.posContent=function(){return this.stream.pos+this.header},t.prototype.posEnd=function(){return this.stream.pos+this.header+Math.abs(this.length)},t.prototype.toHexString=function(){return this.stream.hexDump(this.posStart(),this.posEnd(),!0)},t.decodeLength=function(t){var e=t.get(),i=127&e;if(i==e)return i;if(i>6)throw new Error("Length over 48 bits not supported at position "+(t.pos-1));if(0===i)return null;e=0;for(var r=0;r<i;++r)e=256*e+t.get();return e},t.prototype.getHexStringValue=function(){var t=this.toHexString(),e=2*this.header,i=2*this.length;return t.substr(e,i)},t.decode=function(e){var i;i=e instanceof S?e:new S(e,0);var r=new S(i),n=new w(i),s=t.decodeLength(i),o=i.pos,h=o-r.pos,a=null,u=function(){var e=[];if(null!==s){for(var r=o+s;i.pos<r;)e[e.length]=t.decode(i);if(i.pos!=r)throw new Error("Content size is not correct for container starting at offset "+o)}else try{for(;;){var n=t.decode(i);if(n.tag.isEOC())break;e[e.length]=n}s=o-i.pos}catch(t){throw new Error("Exception while decoding undefined length content: "+t)}return e};if(n.tagConstructed)a=u();else if(n.isUniversal()&&(3==n.tagNumber||4==n.tagNumber))try{if(3==n.tagNumber&&0!=i.get())throw new Error("BIT STRINGs with unused bits cannot encapsulate.");a=u();for(var c=0;c<a.length;++c)if(a[c].tag.isEOC())throw new Error("EOC is not supposed to be actual content.")}catch(t){a=null}if(null===a){if(null===s)throw new Error("We can't skip over an invalid tag with undefined length at offset "+o);i.pos=o+Math.abs(s)}return new t(r,h,s,n,a)},t}(),w=function(){function t(t){var e=t.get();if(this.tagClass=e>>6,this.tagConstructed=0!=(32&e),this.tagNumber=31&e,31==this.tagNumber){var i=new v;do{e=t.get(),i.mulAdd(128,127&e)}while(128&e);this.tagNumber=i.simplify()}}return t.prototype.isUniversal=function(){return 0===this.tagClass},t.prototype.isEOC=function(){return 0===this.tagClass&&0===this.tagNumber},t}(),D=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997],x=(1<<26)/D[D.length-1],R=function(){function i(t,e,i){null!=t&&("number"==typeof t?this.fromNumber(t,e,i):null==e&&"string"!=typeof t?this.fromString(t,256):this.fromString(t,e))}return i.prototype.toString=function(e){if(this.s<0)return"-"+this.negate().toString(e);var i;if(16==e)i=4;else if(8==e)i=3;else if(2==e)i=1;else if(32==e)i=5;else{if(4!=e)return this.toRadix(e);i=2}var r,n=(1<<i)-1,s=!1,o="",h=this.t,a=this.DB-h*this.DB%i;if(h-- >0)for(a<this.DB&&(r=this[h]>>a)>0&&(s=!0,o=t(r));h>=0;)a<i?(r=(this[h]&(1<<a)-1)<<i-a,r|=this[--h]>>(a+=this.DB-i)):(r=this[h]>>(a-=i)&n,a<=0&&(a+=this.DB,--h)),r>0&&(s=!0),s&&(o+=t(r));return s?o:"0"},i.prototype.negate=function(){var t=I();return i.ZERO.subTo(this,t),t},i.prototype.abs=function(){return this.s<0?this.negate():this},i.prototype.compareTo=function(t){var e=this.s-t.s;if(0!=e)return e;var i=this.t;if(0!=(e=i-t.t))return this.s<0?-e:e;for(;--i>=0;)if(0!=(e=this[i]-t[i]))return e;return 0},i.prototype.bitLength=function(){return this.t<=0?0:this.DB*(this.t-1)+C(this[this.t-1]^this.s&this.DM)},i.prototype.mod=function(t){var e=I();return this.abs().divRemTo(t,null,e),this.s<0&&e.compareTo(i.ZERO)>0&&t.subTo(e,e),e},i.prototype.modPowInt=function(t,e){var i;return i=t<256||e.isEven()?new O(e):new A(e),this.exp(t,i)},i.prototype.clone=function(){var t=I();return this.copyTo(t),t},i.prototype.intValue=function(){if(this.s<0){if(1==this.t)return this[0]-this.DV;if(0==this.t)return-1}else{if(1==this.t)return this[0];if(0==this.t)return 0}return(this[1]&(1<<32-this.DB)-1)<<this.DB|this[0]},i.prototype.byteValue=function(){return 0==this.t?this.s:this[0]<<24>>24},i.prototype.shortValue=function(){return 0==this.t?this.s:this[0]<<16>>16},i.prototype.signum=function(){return this.s<0?-1:this.t<=0||1==this.t&&this[0]<=0?0:1},i.prototype.toByteArray=function(){var t=this.t,e=[];e[0]=this.s;var i,r=this.DB-t*this.DB%8,n=0;if(t-- >0)for(r<this.DB&&(i=this[t]>>r)!=(this.s&this.DM)>>r&&(e[n++]=i|this.s<<this.DB-r);t>=0;)r<8?(i=(this[t]&(1<<r)-1)<<8-r,i|=this[--t]>>(r+=this.DB-8)):(i=this[t]>>(r-=8)&255,r<=0&&(r+=this.DB,--t)),0!=(128&i)&&(i|=-256),0==n&&(128&this.s)!=(128&i)&&++n,(n>0||i!=this.s)&&(e[n++]=i);return e},i.prototype.equals=function(t){return 0==this.compareTo(t)},i.prototype.min=function(t){return this.compareTo(t)<0?this:t},i.prototype.max=function(t){return this.compareTo(t)>0?this:t},i.prototype.and=function(t){var i=I();return this.bitwiseTo(t,e,i),i},i.prototype.or=function(t){var e=I();return this.bitwiseTo(t,n,e),e},i.prototype.xor=function(t){var e=I();return this.bitwiseTo(t,s,e),e},i.prototype.andNot=function(t){var e=I();return this.bitwiseTo(t,o,e),e},i.prototype.not=function(){for(var t=I(),e=0;e<this.t;++e)t[e]=this.DM&~this[e];return t.t=this.t,t.s=~this.s,t},i.prototype.shiftLeft=function(t){var e=I();return t<0?this.rShiftTo(-t,e):this.lShiftTo(t,e),e},i.prototype.shiftRight=function(t){var e=I();return t<0?this.lShiftTo(-t,e):this.rShiftTo(t,e),e},i.prototype.getLowestSetBit=function(){for(var t=0;t<this.t;++t)if(0!=this[t])return t*this.DB+h(this[t]);return this.s<0?this.t*this.DB:-1},i.prototype.bitCount=function(){for(var t=0,e=this.s&this.DM,i=0;i<this.t;++i)t+=a(this[i]^e);return t},i.prototype.testBit=function(t){var e=Math.floor(t/this.DB);return e>=this.t?0!=this.s:0!=(this[e]&1<<t%this.DB)},i.prototype.setBit=function(t){return this.changeBit(t,n)},i.prototype.clearBit=function(t){return this.changeBit(t,o)},i.prototype.flipBit=function(t){return this.changeBit(t,s)},i.prototype.add=function(t){var e=I();return this.addTo(t,e),e},i.prototype.subtract=function(t){var e=I();return this.subTo(t,e),e},i.prototype.multiply=function(t){var e=I();return this.multiplyTo(t,e),e},i.prototype.divide=function(t){var e=I();return this.divRemTo(t,e,null),e},i.prototype.remainder=function(t){var e=I();return this.divRemTo(t,null,e),e},i.prototype.divideAndRemainder=function(t){var e=I(),i=I();return this.divRemTo(t,e,i),[e,i]},i.prototype.modPow=function(t,e){var i,r,n=t.bitLength(),s=H(1);if(n<=0)return s;i=n<18?1:n<48?3:n<144?4:n<768?5:6,r=n<8?new O(e):e.isEven()?new V(e):new A(e);var o=[],h=3,a=i-1,u=(1<<i)-1;if(o[1]=r.convert(this),i>1){var c=I();for(r.sqrTo(o[1],c);h<=u;)o[h]=I(),r.mulTo(c,o[h-2],o[h]),h+=2}var f,l,p=t.t-1,g=!0,d=I();for(n=C(t[p])-1;p>=0;){for(n>=a?f=t[p]>>n-a&u:(f=(t[p]&(1<<n+1)-1)<<a-n,p>0&&(f|=t[p-1]>>this.DB+n-a)),h=i;0==(1&f);)f>>=1,--h;if((n-=h)<0&&(n+=this.DB,--p),g)o[f].copyTo(s),g=!1;else{for(;h>1;)r.sqrTo(s,d),r.sqrTo(d,s),h-=2;h>0?r.sqrTo(s,d):(l=s,s=d,d=l),r.mulTo(d,o[f],s)}for(;p>=0&&0==(t[p]&1<<n);)r.sqrTo(s,d),l=s,s=d,d=l,--n<0&&(n=this.DB-1,--p)}return r.revert(s)},i.prototype.modInverse=function(t){var e=t.isEven();if(this.isEven()&&e||0==t.signum())return i.ZERO;for(var r=t.clone(),n=this.clone(),s=H(1),o=H(0),h=H(0),a=H(1);0!=r.signum();){for(;r.isEven();)r.rShiftTo(1,r),e?(s.isEven()&&o.isEven()||(s.addTo(this,s),o.subTo(t,o)),s.rShiftTo(1,s)):o.isEven()||o.subTo(t,o),o.rShiftTo(1,o);for(;n.isEven();)n.rShiftTo(1,n),e?(h.isEven()&&a.isEven()||(h.addTo(this,h),a.subTo(t,a)),h.rShiftTo(1,h)):a.isEven()||a.subTo(t,a),a.rShiftTo(1,a);r.compareTo(n)>=0?(r.subTo(n,r),e&&s.subTo(h,s),o.subTo(a,o)):(n.subTo(r,n),e&&h.subTo(s,h),a.subTo(o,a))}return 0!=n.compareTo(i.ONE)?i.ZERO:a.compareTo(t)>=0?a.subtract(t):a.signum()<0?(a.addTo(t,a),a.signum()<0?a.add(t):a):a},i.prototype.pow=function(t){return this.exp(t,new B)},i.prototype.gcd=function(t){var e=this.s<0?this.negate():this.clone(),i=t.s<0?t.negate():t.clone();if(e.compareTo(i)<0){var r=e;e=i,i=r}var n=e.getLowestSetBit(),s=i.getLowestSetBit();if(s<0)return e;for(n<s&&(s=n),s>0&&(e.rShiftTo(s,e),i.rShiftTo(s,i));e.signum()>0;)(n=e.getLowestSetBit())>0&&e.rShiftTo(n,e),(n=i.getLowestSetBit())>0&&i.rShiftTo(n,i),e.compareTo(i)>=0?(e.subTo(i,e),e.rShiftTo(1,e)):(i.subTo(e,i),i.rShiftTo(1,i));return s>0&&i.lShiftTo(s,i),i},i.prototype.isProbablePrime=function(t){var e,i=this.abs();if(1==i.t&&i[0]<=D[D.length-1]){for(e=0;e<D.length;++e)if(i[0]==D[e])return!0;return!1}if(i.isEven())return!1;for(e=1;e<D.length;){for(var r=D[e],n=e+1;n<D.length&&r<x;)r*=D[n++];for(r=i.modInt(r);e<n;)if(r%D[e++]==0)return!1}return i.millerRabin(t)},i.prototype.copyTo=function(t){for(var e=this.t-1;e>=0;--e)t[e]=this[e];t.t=this.t,t.s=this.s},i.prototype.fromInt=function(t){this.t=1,this.s=t<0?-1:0,t>0?this[0]=t:t<-1?this[0]=t+this.DV:this.t=0},i.prototype.fromString=function(t,e){var r;if(16==e)r=4;else if(8==e)r=3;else if(256==e)r=8;else if(2==e)r=1;else if(32==e)r=5;else{if(4!=e)return void this.fromRadix(t,e);r=2}this.t=0,this.s=0;for(var n=t.length,s=!1,o=0;--n>=0;){var h=8==r?255&+t[n]:q(t,n);h<0?"-"==t.charAt(n)&&(s=!0):(s=!1,0==o?this[this.t++]=h:o+r>this.DB?(this[this.t-1]|=(h&(1<<this.DB-o)-1)<<o,this[this.t++]=h>>this.DB-o):this[this.t-1]|=h<<o,(o+=r)>=this.DB&&(o-=this.DB))}8==r&&0!=(128&+t[0])&&(this.s=-1,o>0&&(this[this.t-1]|=(1<<this.DB-o)-1<<o)),this.clamp(),s&&i.ZERO.subTo(this,this)},i.prototype.clamp=function(){for(var t=this.s&this.DM;this.t>0&&this[this.t-1]==t;)--this.t},i.prototype.dlShiftTo=function(t,e){var i;for(i=this.t-1;i>=0;--i)e[i+t]=this[i];for(i=t-1;i>=0;--i)e[i]=0;e.t=this.t+t,e.s=this.s},i.prototype.drShiftTo=function(t,e){for(var i=t;i<this.t;++i)e[i-t]=this[i];e.t=Math.max(this.t-t,0),e.s=this.s},i.prototype.lShiftTo=function(t,e){for(var i=t%this.DB,r=this.DB-i,n=(1<<r)-1,s=Math.floor(t/this.DB),o=this.s<<i&this.DM,h=this.t-1;h>=0;--h)e[h+s+1]=this[h]>>r|o,o=(this[h]&n)<<i;for(h=s-1;h>=0;--h)e[h]=0;e[s]=o,e.t=this.t+s+1,e.s=this.s,e.clamp()},i.prototype.rShiftTo=function(t,e){e.s=this.s;var i=Math.floor(t/this.DB);if(i>=this.t)e.t=0;else{var r=t%this.DB,n=this.DB-r,s=(1<<r)-1;e[0]=this[i]>>r;for(var o=i+1;o<this.t;++o)e[o-i-1]|=(this[o]&s)<<n,e[o-i]=this[o]>>r;r>0&&(e[this.t-i-1]|=(this.s&s)<<n),e.t=this.t-i,e.clamp()}},i.prototype.subTo=function(t,e){for(var i=0,r=0,n=Math.min(t.t,this.t);i<n;)r+=this[i]-t[i],e[i++]=r&this.DM,r>>=this.DB;if(t.t<this.t){for(r-=t.s;i<this.t;)r+=this[i],e[i++]=r&this.DM,r>>=this.DB;r+=this.s}else{for(r+=this.s;i<t.t;)r-=t[i],e[i++]=r&this.DM,r>>=this.DB;r-=t.s}e.s=r<0?-1:0,r<-1?e[i++]=this.DV+r:r>0&&(e[i++]=r),e.t=i,e.clamp()},i.prototype.multiplyTo=function(t,e){var r=this.abs(),n=t.abs(),s=r.t;for(e.t=s+n.t;--s>=0;)e[s]=0;for(s=0;s<n.t;++s)e[s+r.t]=r.am(0,n[s],e,s,0,r.t);e.s=0,e.clamp(),this.s!=t.s&&i.ZERO.subTo(e,e)},i.prototype.squareTo=function(t){for(var e=this.abs(),i=t.t=2*e.t;--i>=0;)t[i]=0;for(i=0;i<e.t-1;++i){var r=e.am(i,e[i],t,2*i,0,1);(t[i+e.t]+=e.am(i+1,2*e[i],t,2*i+1,r,e.t-i-1))>=e.DV&&(t[i+e.t]-=e.DV,t[i+e.t+1]=1)}t.t>0&&(t[t.t-1]+=e.am(i,e[i],t,2*i,0,1)),t.s=0,t.clamp()},i.prototype.divRemTo=function(t,e,r){var n=t.abs();if(!(n.t<=0)){var s=this.abs();if(s.t<n.t)return null!=e&&e.fromInt(0),void(null!=r&&this.copyTo(r));null==r&&(r=I());var o=I(),h=this.s,a=t.s,u=this.DB-C(n[n.t-1]);u>0?(n.lShiftTo(u,o),s.lShiftTo(u,r)):(n.copyTo(o),s.copyTo(r));var c=o.t,f=o[c-1];if(0!=f){var l=f*(1<<this.F1)+(c>1?o[c-2]>>this.F2:0),p=this.FV/l,g=(1<<this.F1)/l,d=1<<this.F2,v=r.t,m=v-c,y=null==e?I():e;for(o.dlShiftTo(m,y),r.compareTo(y)>=0&&(r[r.t++]=1,r.subTo(y,r)),i.ONE.dlShiftTo(c,y),y.subTo(o,o);o.t<c;)o[o.t++]=0;for(;--m>=0;){var b=r[--v]==f?this.DM:Math.floor(r[v]*p+(r[v-1]+d)*g);if((r[v]+=o.am(0,b,r,m,0,c))<b)for(o.dlShiftTo(m,y),r.subTo(y,r);r[v]<--b;)r.subTo(y,r)}null!=e&&(r.drShiftTo(c,e),h!=a&&i.ZERO.subTo(e,e)),r.t=c,r.clamp(),u>0&&r.rShiftTo(u,r),h<0&&i.ZERO.subTo(r,r)}}},i.prototype.invDigit=function(){if(this.t<1)return 0;var t=this[0];if(0==(1&t))return 0;var e=3&t;return(e=(e=(e=(e=e*(2-(15&t)*e)&15)*(2-(255&t)*e)&255)*(2-((65535&t)*e&65535))&65535)*(2-t*e%this.DV)%this.DV)>0?this.DV-e:-e},i.prototype.isEven=function(){return 0==(this.t>0?1&this[0]:this.s)},i.prototype.exp=function(t,e){if(t>4294967295||t<1)return i.ONE;var r=I(),n=I(),s=e.convert(this),o=C(t)-1;for(s.copyTo(r);--o>=0;)if(e.sqrTo(r,n),(t&1<<o)>0)e.mulTo(n,s,r);else{var h=r;r=n,n=h}return e.revert(r)},i.prototype.chunkSize=function(t){return Math.floor(Math.LN2*this.DB/Math.log(t))},i.prototype.toRadix=function(t){if(null==t&&(t=10),0==this.signum()||t<2||t>36)return"0";var e=this.chunkSize(t),i=Math.pow(t,e),r=H(i),n=I(),s=I(),o="";for(this.divRemTo(r,n,s);n.signum()>0;)o=(i+s.intValue()).toString(t).substr(1)+o,n.divRemTo(r,n,s);return s.intValue().toString(t)+o},i.prototype.fromRadix=function(t,e){this.fromInt(0),null==e&&(e=10);for(var r=this.chunkSize(e),n=Math.pow(e,r),s=!1,o=0,h=0,a=0;a<t.length;++a){var u=q(t,a);u<0?"-"==t.charAt(a)&&0==this.signum()&&(s=!0):(h=e*h+u,++o>=r&&(this.dMultiply(n),this.dAddOffset(h,0),o=0,h=0))}o>0&&(this.dMultiply(Math.pow(e,o)),this.dAddOffset(h,0)),s&&i.ZERO.subTo(this,this)},i.prototype.fromNumber=function(t,e,r){if("number"==typeof e)if(t<2)this.fromInt(1);else for(this.fromNumber(t,r),this.testBit(t-1)||this.bitwiseTo(i.ONE.shiftLeft(t-1),n,this),this.isEven()&&this.dAddOffset(1,0);!this.isProbablePrime(e);)this.dAddOffset(2,0),this.bitLength()>t&&this.subTo(i.ONE.shiftLeft(t-1),this);else{var s=[],o=7&t;s.length=1+(t>>3),e.nextBytes(s),o>0?s[0]&=(1<<o)-1:s[0]=0,this.fromString(s,256)}},i.prototype.bitwiseTo=function(t,e,i){var r,n,s=Math.min(t.t,this.t);for(r=0;r<s;++r)i[r]=e(this[r],t[r]);if(t.t<this.t){for(n=t.s&this.DM,r=s;r<this.t;++r)i[r]=e(this[r],n);i.t=this.t}else{for(n=this.s&this.DM,r=s;r<t.t;++r)i[r]=e(n,t[r]);i.t=t.t}i.s=e(this.s,t.s),i.clamp()},i.prototype.changeBit=function(t,e){var r=i.ONE.shiftLeft(t);return this.bitwiseTo(r,e,r),r},i.prototype.addTo=function(t,e){for(var i=0,r=0,n=Math.min(t.t,this.t);i<n;)r+=this[i]+t[i],e[i++]=r&this.DM,r>>=this.DB;if(t.t<this.t){for(r+=t.s;i<this.t;)r+=this[i],e[i++]=r&this.DM,r>>=this.DB;r+=this.s}else{for(r+=this.s;i<t.t;)r+=t[i],e[i++]=r&this.DM,r>>=this.DB;r+=t.s}e.s=r<0?-1:0,r>0?e[i++]=r:r<-1&&(e[i++]=this.DV+r),e.t=i,e.clamp()},i.prototype.dMultiply=function(t){this[this.t]=this.am(0,t-1,this,0,0,this.t),++this.t,this.clamp()},i.prototype.dAddOffset=function(t,e){if(0!=t){for(;this.t<=e;)this[this.t++]=0;for(this[e]+=t;this[e]>=this.DV;)this[e]-=this.DV,++e>=this.t&&(this[this.t++]=0),++this[e]}},i.prototype.multiplyLowerTo=function(t,e,i){var r=Math.min(this.t+t.t,e);for(i.s=0,i.t=r;r>0;)i[--r]=0;for(var n=i.t-this.t;r<n;++r)i[r+this.t]=this.am(0,t[r],i,r,0,this.t);for(n=Math.min(t.t,e);r<n;++r)this.am(0,t[r],i,r,0,e-r);i.clamp()},i.prototype.multiplyUpperTo=function(t,e,i){--e;var r=i.t=this.t+t.t-e;for(i.s=0;--r>=0;)i[r]=0;for(r=Math.max(e-this.t,0);r<t.t;++r)i[this.t+r-e]=this.am(e-r,t[r],i,0,0,this.t+r-e);i.clamp(),i.drShiftTo(1,i)},i.prototype.modInt=function(t){if(t<=0)return 0;var e=this.DV%t,i=this.s<0?t-1:0;if(this.t>0)if(0==e)i=this[0]%t;else for(var r=this.t-1;r>=0;--r)i=(e*i+this[r])%t;return i},i.prototype.millerRabin=function(t){var e=this.subtract(i.ONE),r=e.getLowestSetBit();if(r<=0)return!1;var n=e.shiftRight(r);(t=t+1>>1)>D.length&&(t=D.length);for(var s=I(),o=0;o<t;++o){s.fromInt(D[Math.floor(Math.random()*D.length)]);var h=s.modPow(n,this);if(0!=h.compareTo(i.ONE)&&0!=h.compareTo(e)){for(var a=1;a++<r&&0!=h.compareTo(e);)if(0==(h=h.modPowInt(2,this)).compareTo(i.ONE))return!1;if(0!=h.compareTo(e))return!1}}return!0},i.prototype.square=function(){var t=I();return this.squareTo(t),t},i.prototype.gcda=function(t,e){var i=this.s<0?this.negate():this.clone(),r=t.s<0?t.negate():t.clone();if(i.compareTo(r)<0){var n=i;i=r,r=n}var s=i.getLowestSetBit(),o=r.getLowestSetBit();if(o<0)e(i);else{s<o&&(o=s),o>0&&(i.rShiftTo(o,i),r.rShiftTo(o,r));var h=function(){(s=i.getLowestSetBit())>0&&i.rShiftTo(s,i),(s=r.getLowestSetBit())>0&&r.rShiftTo(s,r),i.compareTo(r)>=0?(i.subTo(r,i),i.rShiftTo(1,i)):(r.subTo(i,r),r.rShiftTo(1,r)),i.signum()>0?setTimeout(h,0):(o>0&&r.lShiftTo(o,r),setTimeout((function(){e(r)}),0))};setTimeout(h,10)}},i.prototype.fromNumberAsync=function(t,e,r,s){if("number"==typeof e)if(t<2)this.fromInt(1);else{this.fromNumber(t,r),this.testBit(t-1)||this.bitwiseTo(i.ONE.shiftLeft(t-1),n,this),this.isEven()&&this.dAddOffset(1,0);var o=this,h=function(){o.dAddOffset(2,0),o.bitLength()>t&&o.subTo(i.ONE.shiftLeft(t-1),o),o.isProbablePrime(e)?setTimeout((function(){s()}),0):setTimeout(h,0)};setTimeout(h,0)}else{var a=[],u=7&t;a.length=1+(t>>3),e.nextBytes(a),u>0?a[0]&=(1<<u)-1:a[0]=0,this.fromString(a,256)}},i}(),B=function(){function t(){}return t.prototype.convert=function(t){return t},t.prototype.revert=function(t){return t},t.prototype.mulTo=function(t,e,i){t.multiplyTo(e,i)},t.prototype.sqrTo=function(t,e){t.squareTo(e)},t}(),O=function(){function t(t){this.m=t}return t.prototype.convert=function(t){return t.s<0||t.compareTo(this.m)>=0?t.mod(this.m):t},t.prototype.revert=function(t){return t},t.prototype.reduce=function(t){t.divRemTo(this.m,null,t)},t.prototype.mulTo=function(t,e,i){t.multiplyTo(e,i),this.reduce(i)},t.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)},t}(),A=function(){function t(t){this.m=t,this.mp=t.invDigit(),this.mpl=32767&this.mp,this.mph=this.mp>>15,this.um=(1<<t.DB-15)-1,this.mt2=2*t.t}return t.prototype.convert=function(t){var e=I();return t.abs().dlShiftTo(this.m.t,e),e.divRemTo(this.m,null,e),t.s<0&&e.compareTo(R.ZERO)>0&&this.m.subTo(e,e),e},t.prototype.revert=function(t){var e=I();return t.copyTo(e),this.reduce(e),e},t.prototype.reduce=function(t){for(;t.t<=this.mt2;)t[t.t++]=0;for(var e=0;e<this.m.t;++e){var i=32767&t[e],r=i*this.mpl+((i*this.mph+(t[e]>>15)*this.mpl&this.um)<<15)&t.DM;for(t[i=e+this.m.t]+=this.m.am(0,r,t,e,0,this.m.t);t[i]>=t.DV;)t[i]-=t.DV,t[++i]++}t.clamp(),t.drShiftTo(this.m.t,t),t.compareTo(this.m)>=0&&t.subTo(this.m,t)},t.prototype.mulTo=function(t,e,i){t.multiplyTo(e,i),this.reduce(i)},t.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)},t}(),V=function(){function t(t){this.m=t,this.r2=I(),this.q3=I(),R.ONE.dlShiftTo(2*t.t,this.r2),this.mu=this.r2.divide(t)}return t.prototype.convert=function(t){if(t.s<0||t.t>2*this.m.t)return t.mod(this.m);if(t.compareTo(this.m)<0)return t;var e=I();return t.copyTo(e),this.reduce(e),e},t.prototype.revert=function(t){return t},t.prototype.reduce=function(t){for(t.drShiftTo(this.m.t-1,this.r2),t.t>this.m.t+1&&(t.t=this.m.t+1,t.clamp()),this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3),this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);t.compareTo(this.r2)<0;)t.dAddOffset(1,this.m.t+1);for(t.subTo(this.r2,t);t.compareTo(this.m)>=0;)t.subTo(this.m,t)},t.prototype.mulTo=function(t,e,i){t.multiplyTo(e,i),this.reduce(i)},t.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)},t}();function I(){return new R(null)}function N(t,e){return new R(t,e)}var P="undefined"!=typeof navigator;P&&"Microsoft Internet Explorer"==navigator.appName?(R.prototype.am=function(t,e,i,r,n,s){for(var o=32767&e,h=e>>15;--s>=0;){var a=32767&this[t],u=this[t++]>>15,c=h*a+u*o;n=((a=o*a+((32767&c)<<15)+i[r]+(1073741823&n))>>>30)+(c>>>15)+h*u+(n>>>30),i[r++]=1073741823&a}return n},T=30):P&&"Netscape"!=navigator.appName?(R.prototype.am=function(t,e,i,r,n,s){for(;--s>=0;){var o=e*this[t++]+i[r]+n;n=Math.floor(o/67108864),i[r++]=67108863&o}return n},T=26):(R.prototype.am=function(t,e,i,r,n,s){for(var o=16383&e,h=e>>14;--s>=0;){var a=16383&this[t],u=this[t++]>>14,c=h*a+u*o;n=((a=o*a+((16383&c)<<14)+i[r]+n)>>28)+(c>>14)+h*u,i[r++]=268435455&a}return n},T=28),R.prototype.DB=T,R.prototype.DM=(1<<T)-1,R.prototype.DV=1<<T,R.prototype.FV=Math.pow(2,52),R.prototype.F1=52-T,R.prototype.F2=2*T-52;var M,L,j=[];for(M="0".charCodeAt(0),L=0;L<=9;++L)j[M++]=L;for(M="a".charCodeAt(0),L=10;L<36;++L)j[M++]=L;for(M="A".charCodeAt(0),L=10;L<36;++L)j[M++]=L;function q(t,e){var i=j[t.charCodeAt(e)];return null==i?-1:i}function H(t){var e=I();return e.fromInt(t),e}function C(t){var e,i=1;return 0!=(e=t>>>16)&&(t=e,i+=16),0!=(e=t>>8)&&(t=e,i+=8),0!=(e=t>>4)&&(t=e,i+=4),0!=(e=t>>2)&&(t=e,i+=2),0!=(e=t>>1)&&(t=e,i+=1),i}R.ZERO=H(0),R.ONE=H(1);var F,U,K=function(){function t(){this.i=0,this.j=0,this.S=[]}return t.prototype.init=function(t){var e,i,r;for(e=0;e<256;++e)this.S[e]=e;for(i=0,e=0;e<256;++e)i=i+this.S[e]+t[e%t.length]&255,r=this.S[e],this.S[e]=this.S[i],this.S[i]=r;this.i=0,this.j=0},t.prototype.next=function(){var t;return this.i=this.i+1&255,this.j=this.j+this.S[this.i]&255,t=this.S[this.i],this.S[this.i]=this.S[this.j],this.S[this.j]=t,this.S[t+this.S[this.i]&255]},t}(),k=null;if(null==k){k=[],U=0;var _=void 0;if("undefined"!=typeof window&&window.crypto&&window.crypto.getRandomValues){var z=new Uint32Array(256);for(window.crypto.getRandomValues(z),_=0;_<z.length;++_)k[U++]=255&z[_]}var Z=0,G=function(t){if((Z=Z||0)>=256||U>=256)window.removeEventListener?window.removeEventListener("mousemove",G,!1):window.detachEvent&&window.detachEvent("onmousemove",G);else try{var e=t.x+t.y;k[U++]=255&e,Z+=1}catch(t){}};"undefined"!=typeof window&&(window.addEventListener?window.addEventListener("mousemove",G,!1):window.attachEvent&&window.attachEvent("onmousemove",G))}function $(){if(null==F){for(F=new K;U<256;){var t=Math.floor(65536*Math.random());k[U++]=255&t}for(F.init(k),U=0;U<k.length;++U)k[U]=0;U=0}return F.next()}var Y=function(){function t(){}return t.prototype.nextBytes=function(t){for(var e=0;e<t.length;++e)t[e]=$()},t}(),J=function(){function t(){this.n=null,this.e=0,this.d=null,this.p=null,this.q=null,this.dmp1=null,this.dmq1=null,this.coeff=null}return t.prototype.doPublic=function(t){return t.modPowInt(this.e,this.n)},t.prototype.doPrivate=function(t){if(null==this.p||null==this.q)return t.modPow(this.d,this.n);for(var e=t.mod(this.p).modPow(this.dmp1,this.p),i=t.mod(this.q).modPow(this.dmq1,this.q);e.compareTo(i)<0;)e=e.add(this.p);return e.subtract(i).multiply(this.coeff).mod(this.p).multiply(this.q).add(i)},t.prototype.setPublic=function(t,e){null!=t&&null!=e&&t.length>0&&e.length>0?(this.n=N(t,16),this.e=parseInt(e,16)):console.error("Invalid RSA public key")},t.prototype.encrypt=function(t){var e=this.n.bitLength()+7>>3,i=function(t,e){if(e<t.length+11)return console.error("Message too long for RSA"),null;for(var i=[],r=t.length-1;r>=0&&e>0;){var n=t.charCodeAt(r--);n<128?i[--e]=n:n>127&&n<2048?(i[--e]=63&n|128,i[--e]=n>>6|192):(i[--e]=63&n|128,i[--e]=n>>6&63|128,i[--e]=n>>12|224)}i[--e]=0;for(var s=new Y,o=[];e>2;){for(o[0]=0;0==o[0];)s.nextBytes(o);i[--e]=o[0]}return i[--e]=2,i[--e]=0,new R(i)}(t,e);if(null==i)return null;var r=this.doPublic(i);if(null==r)return null;for(var n=r.toString(16),s=n.length,o=0;o<2*e-s;o++)n="0"+n;return n},t.prototype.setPrivate=function(t,e,i){null!=t&&null!=e&&t.length>0&&e.length>0?(this.n=N(t,16),this.e=parseInt(e,16),this.d=N(i,16)):console.error("Invalid RSA private key")},t.prototype.setPrivateEx=function(t,e,i,r,n,s,o,h){null!=t&&null!=e&&t.length>0&&e.length>0?(this.n=N(t,16),this.e=parseInt(e,16),this.d=N(i,16),this.p=N(r,16),this.q=N(n,16),this.dmp1=N(s,16),this.dmq1=N(o,16),this.coeff=N(h,16)):console.error("Invalid RSA private key")},t.prototype.generate=function(t,e){var i=new Y,r=t>>1;this.e=parseInt(e,16);for(var n=new R(e,16);;){for(;this.p=new R(t-r,1,i),0!=this.p.subtract(R.ONE).gcd(n).compareTo(R.ONE)||!this.p.isProbablePrime(10););for(;this.q=new R(r,1,i),0!=this.q.subtract(R.ONE).gcd(n).compareTo(R.ONE)||!this.q.isProbablePrime(10););if(this.p.compareTo(this.q)<=0){var s=this.p;this.p=this.q,this.q=s}var o=this.p.subtract(R.ONE),h=this.q.subtract(R.ONE),a=o.multiply(h);if(0==a.gcd(n).compareTo(R.ONE)){this.n=this.p.multiply(this.q),this.d=n.modInverse(a),this.dmp1=this.d.mod(o),this.dmq1=this.d.mod(h),this.coeff=this.q.modInverse(this.p);break}}},t.prototype.decrypt=function(t){var e=N(t,16),i=this.doPrivate(e);return null==i?null:function(t,e){for(var i=t.toByteArray(),r=0;r<i.length&&0==i[r];)++r;if(i.length-r!=e-1||2!=i[r])return null;for(++r;0!=i[r];)if(++r>=i.length)return null;for(var n="";++r<i.length;){var s=255&i[r];s<128?n+=String.fromCharCode(s):s>191&&s<224?(n+=String.fromCharCode((31&s)<<6|63&i[r+1]),++r):(n+=String.fromCharCode((15&s)<<12|(63&i[r+1])<<6|63&i[r+2]),r+=2)}return n}(i,this.n.bitLength()+7>>3)},t.prototype.generateAsync=function(t,e,i){var r=new Y,n=t>>1;this.e=parseInt(e,16);var s=new R(e,16),o=this,h=function(){var e=function(){if(o.p.compareTo(o.q)<=0){var t=o.p;o.p=o.q,o.q=t}var e=o.p.subtract(R.ONE),r=o.q.subtract(R.ONE),n=e.multiply(r);0==n.gcd(s).compareTo(R.ONE)?(o.n=o.p.multiply(o.q),o.d=s.modInverse(n),o.dmp1=o.d.mod(e),o.dmq1=o.d.mod(r),o.coeff=o.q.modInverse(o.p),setTimeout((function(){i()}),0)):setTimeout(h,0)},a=function(){o.q=I(),o.q.fromNumberAsync(n,1,r,(function(){o.q.subtract(R.ONE).gcda(s,(function(t){0==t.compareTo(R.ONE)&&o.q.isProbablePrime(10)?setTimeout(e,0):setTimeout(a,0)}))}))},u=function(){o.p=I(),o.p.fromNumberAsync(t-n,1,r,(function(){o.p.subtract(R.ONE).gcda(s,(function(t){0==t.compareTo(R.ONE)&&o.p.isProbablePrime(10)?setTimeout(a,0):setTimeout(u,0)}))}))};setTimeout(u,0)};setTimeout(h,0)},t.prototype.sign=function(t,e,i){var r=function(t,e){if(e<t.length+22)return console.error("Message too long for RSA"),null;for(var i=e-t.length-6,r="",n=0;n<i;n+=2)r+="ff";return N("0001"+r+"00"+t,16)}((X[i]||"")+e(t).toString(),this.n.bitLength()/4);if(null==r)return null;var n=this.doPrivate(r);if(null==n)return null;var s=n.toString(16);return 0==(1&s.length)?s:"0"+s},t.prototype.verify=function(t,e,i){var r=N(e,16),n=this.doPublic(r);return null==n?null:function(t){for(var e in X)if(X.hasOwnProperty(e)){var i=X[e],r=i.length;if(t.substr(0,r)==i)return t.substr(r)}return t}(n.toString(16).replace(/^1f+00/,""))==i(t).toString()},t}(),X={md2:"3020300c06082a864886f70d020205000410",md5:"3020300c06082a864886f70d020505000410",sha1:"3021300906052b0e03021a05000414",sha224:"302d300d06096086480165030402040500041c",sha256:"3031300d060960864801650304020105000420",sha384:"3041300d060960864801650304020205000430",sha512:"3051300d060960864801650304020305000440",ripemd160:"3021300906052b2403020105000414"},Q={};Q.lang={extend:function(t,e,i){if(!e||!t)throw new Error("YAHOO.lang.extend failed, please check that all dependencies are included.");var r=function(){};if(r.prototype=e.prototype,t.prototype=new r,t.prototype.constructor=t,t.superclass=e.prototype,e.prototype.constructor==Object.prototype.constructor&&(e.prototype.constructor=e),i){var n;for(n in i)t.prototype[n]=i[n];var s=function(){},o=["toString","valueOf"];try{/MSIE/.test(navigator.userAgent)&&(s=function(t,e){for(n=0;n<o.length;n+=1){var i=o[n],r=e[i];"function"==typeof r&&r!=Object.prototype[i]&&(t[i]=r)}})}catch(t){}s(t.prototype,i)}}};var W={};void 0!==W.asn1&&W.asn1||(W.asn1={}),W.asn1.ASN1Util=new function(){this.integerToByteHex=function(t){var e=t.toString(16);return e.length%2==1&&(e="0"+e),e},this.bigIntToMinTwosComplementsHex=function(t){var e=t.toString(16);if("-"!=e.substr(0,1))e.length%2==1?e="0"+e:e.match(/^[0-7]/)||(e="00"+e);else{var i=e.substr(1).length;i%2==1?i+=1:e.match(/^[0-7]/)||(i+=2);for(var r="",n=0;n<i;n++)r+="f";e=new R(r,16).xor(t).add(R.ONE).toString(16).replace(/^-/,"")}return e},this.getPEMStringFromHex=function(t,e){return hextopem(t,e)},this.newObject=function(t){var e=W.asn1,i=e.DERBoolean,r=e.DERInteger,n=e.DERBitString,s=e.DEROctetString,o=e.DERNull,h=e.DERObjectIdentifier,a=e.DEREnumerated,u=e.DERUTF8String,c=e.DERNumericString,f=e.DERPrintableString,l=e.DERTeletexString,p=e.DERIA5String,g=e.DERUTCTime,d=e.DERGeneralizedTime,v=e.DERSequence,m=e.DERSet,y=e.DERTaggedObject,b=e.ASN1Util.newObject,T=Object.keys(t);if(1!=T.length)throw"key of param shall be only one.";var S=T[0];if(-1==":bool:int:bitstr:octstr:null:oid:enum:utf8str:numstr:prnstr:telstr:ia5str:utctime:gentime:seq:set:tag:".indexOf(":"+S+":"))throw"undefined key: "+S;if("bool"==S)return new i(t[S]);if("int"==S)return new r(t[S]);if("bitstr"==S)return new n(t[S]);if("octstr"==S)return new s(t[S]);if("null"==S)return new o(t[S]);if("oid"==S)return new h(t[S]);if("enum"==S)return new a(t[S]);if("utf8str"==S)return new u(t[S]);if("numstr"==S)return new c(t[S]);if("prnstr"==S)return new f(t[S]);if("telstr"==S)return new l(t[S]);if("ia5str"==S)return new p(t[S]);if("utctime"==S)return new g(t[S]);if("gentime"==S)return new d(t[S]);if("seq"==S){for(var E=t[S],w=[],D=0;D<E.length;D++){var x=b(E[D]);w.push(x)}return new v({array:w})}if("set"==S){for(E=t[S],w=[],D=0;D<E.length;D++)x=b(E[D]),w.push(x);return new m({array:w})}if("tag"==S){var R=t[S];if("[object Array]"===Object.prototype.toString.call(R)&&3==R.length){var B=b(R[2]);return new y({tag:R[0],explicit:R[1],obj:B})}var O={};if(void 0!==R.explicit&&(O.explicit=R.explicit),void 0!==R.tag&&(O.tag=R.tag),void 0===R.obj)throw"obj shall be specified for 'tag'.";return O.obj=b(R.obj),new y(O)}},this.jsonToASN1HEX=function(t){return this.newObject(t).getEncodedHex()}},W.asn1.ASN1Util.oidHexToInt=function(t){for(var e="",i=parseInt(t.substr(0,2),16),r=(e=Math.floor(i/40)+"."+i%40,""),n=2;n<t.length;n+=2){var s=("00000000"+parseInt(t.substr(n,2),16).toString(2)).slice(-8);r+=s.substr(1,7),"0"==s.substr(0,1)&&(e=e+"."+new R(r,2).toString(10),r="")}return e},W.asn1.ASN1Util.oidIntToHex=function(t){var e=function(t){var e=t.toString(16);return 1==e.length&&(e="0"+e),e},i=function(t){var i="",r=new R(t,10).toString(2),n=7-r.length%7;7==n&&(n=0);for(var s="",o=0;o<n;o++)s+="0";for(r=s+r,o=0;o<r.length-1;o+=7){var h=r.substr(o,7);o!=r.length-7&&(h="1"+h),i+=e(parseInt(h,2))}return i};if(!t.match(/^[0-9.]+$/))throw"malformed oid string: "+t;var r="",n=t.split("."),s=40*parseInt(n[0])+parseInt(n[1]);r+=e(s),n.splice(0,2);for(var o=0;o<n.length;o++)r+=i(n[o]);return r},W.asn1.ASN1Object=function(){this.getLengthHexFromValue=function(){if(void 0===this.hV||null==this.hV)throw"this.hV is null or undefined.";if(this.hV.length%2==1)throw"value hex must be even length: n="+"".length+",v="+this.hV;var t=this.hV.length/2,e=t.toString(16);if(e.length%2==1&&(e="0"+e),t<128)return e;var i=e.length/2;if(i>15)throw"ASN.1 length too long to represent by 8x: n = "+t.toString(16);return(128+i).toString(16)+e},this.getEncodedHex=function(){return(null==this.hTLV||this.isModified)&&(this.hV=this.getFreshValueHex(),this.hL=this.getLengthHexFromValue(),this.hTLV=this.hT+this.hL+this.hV,this.isModified=!1),this.hTLV},this.getValueHex=function(){return this.getEncodedHex(),this.hV},this.getFreshValueHex=function(){return""}},W.asn1.DERAbstractString=function(t){W.asn1.DERAbstractString.superclass.constructor.call(this),this.getString=function(){return this.s},this.setString=function(t){this.hTLV=null,this.isModified=!0,this.s=t,this.hV=stohex(this.s)},this.setStringHex=function(t){this.hTLV=null,this.isModified=!0,this.s=null,this.hV=t},this.getFreshValueHex=function(){return this.hV},void 0!==t&&("string"==typeof t?this.setString(t):void 0!==t.str?this.setString(t.str):void 0!==t.hex&&this.setStringHex(t.hex))},Q.lang.extend(W.asn1.DERAbstractString,W.asn1.ASN1Object),W.asn1.DERAbstractTime=function(t){W.asn1.DERAbstractTime.superclass.constructor.call(this),this.localDateToUTC=function(t){return utc=t.getTime()+6e4*t.getTimezoneOffset(),new Date(utc)},this.formatDate=function(t,e,i){var r=this.zeroPadding,n=this.localDateToUTC(t),s=String(n.getFullYear());"utc"==e&&(s=s.substr(2,2));var o=s+r(String(n.getMonth()+1),2)+r(String(n.getDate()),2)+r(String(n.getHours()),2)+r(String(n.getMinutes()),2)+r(String(n.getSeconds()),2);if(!0===i){var h=n.getMilliseconds();if(0!=h){var a=r(String(h),3);o=o+"."+(a=a.replace(/[0]+$/,""))}}return o+"Z"},this.zeroPadding=function(t,e){return t.length>=e?t:new Array(e-t.length+1).join("0")+t},this.getString=function(){return this.s},this.setString=function(t){this.hTLV=null,this.isModified=!0,this.s=t,this.hV=stohex(t)},this.setByDateValue=function(t,e,i,r,n,s){var o=new Date(Date.UTC(t,e-1,i,r,n,s,0));this.setByDate(o)},this.getFreshValueHex=function(){return this.hV}},Q.lang.extend(W.asn1.DERAbstractTime,W.asn1.ASN1Object),W.asn1.DERAbstractStructured=function(t){W.asn1.DERAbstractString.superclass.constructor.call(this),this.setByASN1ObjectArray=function(t){this.hTLV=null,this.isModified=!0,this.asn1Array=t},this.appendASN1Object=function(t){this.hTLV=null,this.isModified=!0,this.asn1Array.push(t)},this.asn1Array=new Array,void 0!==t&&void 0!==t.array&&(this.asn1Array=t.array)},Q.lang.extend(W.asn1.DERAbstractStructured,W.asn1.ASN1Object),W.asn1.DERBoolean=function(){W.asn1.DERBoolean.superclass.constructor.call(this),this.hT="01",this.hTLV="0101ff"},Q.lang.extend(W.asn1.DERBoolean,W.asn1.ASN1Object),W.asn1.DERInteger=function(t){W.asn1.DERInteger.superclass.constructor.call(this),this.hT="02",this.setByBigInteger=function(t){this.hTLV=null,this.isModified=!0,this.hV=W.asn1.ASN1Util.bigIntToMinTwosComplementsHex(t)},this.setByInteger=function(t){var e=new R(String(t),10);this.setByBigInteger(e)},this.setValueHex=function(t){this.hV=t},this.getFreshValueHex=function(){return this.hV},void 0!==t&&(void 0!==t.bigint?this.setByBigInteger(t.bigint):void 0!==t.int?this.setByInteger(t.int):"number"==typeof t?this.setByInteger(t):void 0!==t.hex&&this.setValueHex(t.hex))},Q.lang.extend(W.asn1.DERInteger,W.asn1.ASN1Object),W.asn1.DERBitString=function(t){if(void 0!==t&&void 0!==t.obj){var e=W.asn1.ASN1Util.newObject(t.obj);t.hex="00"+e.getEncodedHex()}W.asn1.DERBitString.superclass.constructor.call(this),this.hT="03",this.setHexValueIncludingUnusedBits=function(t){this.hTLV=null,this.isModified=!0,this.hV=t},this.setUnusedBitsAndHexValue=function(t,e){if(t<0||7<t)throw"unused bits shall be from 0 to 7: u = "+t;var i="0"+t;this.hTLV=null,this.isModified=!0,this.hV=i+e},this.setByBinaryString=function(t){var e=8-(t=t.replace(/0+$/,"")).length%8;8==e&&(e=0);for(var i=0;i<=e;i++)t+="0";var r="";for(i=0;i<t.length-1;i+=8){var n=t.substr(i,8),s=parseInt(n,2).toString(16);1==s.length&&(s="0"+s),r+=s}this.hTLV=null,this.isModified=!0,this.hV="0"+e+r},this.setByBooleanArray=function(t){for(var e="",i=0;i<t.length;i++)1==t[i]?e+="1":e+="0";this.setByBinaryString(e)},this.newFalseArray=function(t){for(var e=new Array(t),i=0;i<t;i++)e[i]=!1;return e},this.getFreshValueHex=function(){return this.hV},void 0!==t&&("string"==typeof t&&t.toLowerCase().match(/^[0-9a-f]+$/)?this.setHexValueIncludingUnusedBits(t):void 0!==t.hex?this.setHexValueIncludingUnusedBits(t.hex):void 0!==t.bin?this.setByBinaryString(t.bin):void 0!==t.array&&this.setByBooleanArray(t.array))},Q.lang.extend(W.asn1.DERBitString,W.asn1.ASN1Object),W.asn1.DEROctetString=function(t){if(void 0!==t&&void 0!==t.obj){var e=W.asn1.ASN1Util.newObject(t.obj);t.hex=e.getEncodedHex()}W.asn1.DEROctetString.superclass.constructor.call(this,t),this.hT="04"},Q.lang.extend(W.asn1.DEROctetString,W.asn1.DERAbstractString),W.asn1.DERNull=function(){W.asn1.DERNull.superclass.constructor.call(this),this.hT="05",this.hTLV="0500"},Q.lang.extend(W.asn1.DERNull,W.asn1.ASN1Object),W.asn1.DERObjectIdentifier=function(t){var e=function(t){var e=t.toString(16);return 1==e.length&&(e="0"+e),e},i=function(t){var i="",r=new R(t,10).toString(2),n=7-r.length%7;7==n&&(n=0);for(var s="",o=0;o<n;o++)s+="0";for(r=s+r,o=0;o<r.length-1;o+=7){var h=r.substr(o,7);o!=r.length-7&&(h="1"+h),i+=e(parseInt(h,2))}return i};W.asn1.DERObjectIdentifier.superclass.constructor.call(this),this.hT="06",this.setValueHex=function(t){this.hTLV=null,this.isModified=!0,this.s=null,this.hV=t},this.setValueOidString=function(t){if(!t.match(/^[0-9.]+$/))throw"malformed oid string: "+t;var r="",n=t.split("."),s=40*parseInt(n[0])+parseInt(n[1]);r+=e(s),n.splice(0,2);for(var o=0;o<n.length;o++)r+=i(n[o]);this.hTLV=null,this.isModified=!0,this.s=null,this.hV=r},this.setValueName=function(t){var e=W.asn1.x509.OID.name2oid(t);if(""===e)throw"DERObjectIdentifier oidName undefined: "+t;this.setValueOidString(e)},this.getFreshValueHex=function(){return this.hV},void 0!==t&&("string"==typeof t?t.match(/^[0-2].[0-9.]+$/)?this.setValueOidString(t):this.setValueName(t):void 0!==t.oid?this.setValueOidString(t.oid):void 0!==t.hex?this.setValueHex(t.hex):void 0!==t.name&&this.setValueName(t.name))},Q.lang.extend(W.asn1.DERObjectIdentifier,W.asn1.ASN1Object),W.asn1.DEREnumerated=function(t){W.asn1.DEREnumerated.superclass.constructor.call(this),this.hT="0a",this.setByBigInteger=function(t){this.hTLV=null,this.isModified=!0,this.hV=W.asn1.ASN1Util.bigIntToMinTwosComplementsHex(t)},this.setByInteger=function(t){var e=new R(String(t),10);this.setByBigInteger(e)},this.setValueHex=function(t){this.hV=t},this.getFreshValueHex=function(){return this.hV},void 0!==t&&(void 0!==t.int?this.setByInteger(t.int):"number"==typeof t?this.setByInteger(t):void 0!==t.hex&&this.setValueHex(t.hex))},Q.lang.extend(W.asn1.DEREnumerated,W.asn1.ASN1Object),W.asn1.DERUTF8String=function(t){W.asn1.DERUTF8String.superclass.constructor.call(this,t),this.hT="0c"},Q.lang.extend(W.asn1.DERUTF8String,W.asn1.DERAbstractString),W.asn1.DERNumericString=function(t){W.asn1.DERNumericString.superclass.constructor.call(this,t),this.hT="12"},Q.lang.extend(W.asn1.DERNumericString,W.asn1.DERAbstractString),W.asn1.DERPrintableString=function(t){W.asn1.DERPrintableString.superclass.constructor.call(this,t),this.hT="13"},Q.lang.extend(W.asn1.DERPrintableString,W.asn1.DERAbstractString),W.asn1.DERTeletexString=function(t){W.asn1.DERTeletexString.superclass.constructor.call(this,t),this.hT="14"},Q.lang.extend(W.asn1.DERTeletexString,W.asn1.DERAbstractString),W.asn1.DERIA5String=function(t){W.asn1.DERIA5String.superclass.constructor.call(this,t),this.hT="16"},Q.lang.extend(W.asn1.DERIA5String,W.asn1.DERAbstractString),W.asn1.DERUTCTime=function(t){W.asn1.DERUTCTime.superclass.constructor.call(this,t),this.hT="17",this.setByDate=function(t){this.hTLV=null,this.isModified=!0,this.date=t,this.s=this.formatDate(this.date,"utc"),this.hV=stohex(this.s)},this.getFreshValueHex=function(){return void 0===this.date&&void 0===this.s&&(this.date=new Date,this.s=this.formatDate(this.date,"utc"),this.hV=stohex(this.s)),this.hV},void 0!==t&&(void 0!==t.str?this.setString(t.str):"string"==typeof t&&t.match(/^[0-9]{12}Z$/)?this.setString(t):void 0!==t.hex?this.setStringHex(t.hex):void 0!==t.date&&this.setByDate(t.date))},Q.lang.extend(W.asn1.DERUTCTime,W.asn1.DERAbstractTime),W.asn1.DERGeneralizedTime=function(t){W.asn1.DERGeneralizedTime.superclass.constructor.call(this,t),this.hT="18",this.withMillis=!1,this.setByDate=function(t){this.hTLV=null,this.isModified=!0,this.date=t,this.s=this.formatDate(this.date,"gen",this.withMillis),this.hV=stohex(this.s)},this.getFreshValueHex=function(){return void 0===this.date&&void 0===this.s&&(this.date=new Date,this.s=this.formatDate(this.date,"gen",this.withMillis),this.hV=stohex(this.s)),this.hV},void 0!==t&&(void 0!==t.str?this.setString(t.str):"string"==typeof t&&t.match(/^[0-9]{14}Z$/)?this.setString(t):void 0!==t.hex?this.setStringHex(t.hex):void 0!==t.date&&this.setByDate(t.date),!0===t.millis&&(this.withMillis=!0))},Q.lang.extend(W.asn1.DERGeneralizedTime,W.asn1.DERAbstractTime),W.asn1.DERSequence=function(t){W.asn1.DERSequence.superclass.constructor.call(this,t),this.hT="30",this.getFreshValueHex=function(){for(var t="",e=0;e<this.asn1Array.length;e++)t+=this.asn1Array[e].getEncodedHex();return this.hV=t,this.hV}},Q.lang.extend(W.asn1.DERSequence,W.asn1.DERAbstractStructured),W.asn1.DERSet=function(t){W.asn1.DERSet.superclass.constructor.call(this,t),this.hT="31",this.sortFlag=!0,this.getFreshValueHex=function(){for(var t=new Array,e=0;e<this.asn1Array.length;e++){var i=this.asn1Array[e];t.push(i.getEncodedHex())}return 1==this.sortFlag&&t.sort(),this.hV=t.join(""),this.hV},void 0!==t&&void 0!==t.sortflag&&0==t.sortflag&&(this.sortFlag=!1)},Q.lang.extend(W.asn1.DERSet,W.asn1.DERAbstractStructured),W.asn1.DERTaggedObject=function(t){W.asn1.DERTaggedObject.superclass.constructor.call(this),this.hT="a0",this.hV="",this.isExplicit=!0,this.asn1Object=null,this.setASN1Object=function(t,e,i){this.hT=e,this.isExplicit=t,this.asn1Object=i,this.isExplicit?(this.hV=this.asn1Object.getEncodedHex(),this.hTLV=null,this.isModified=!0):(this.hV=null,this.hTLV=i.getEncodedHex(),this.hTLV=this.hTLV.replace(/^../,e),this.isModified=!1)},this.getFreshValueHex=function(){return this.hV},void 0!==t&&(void 0!==t.tag&&(this.hT=t.tag),void 0!==t.explicit&&(this.isExplicit=t.explicit),void 0!==t.obj&&(this.asn1Object=t.obj,this.setASN1Object(this.isExplicit,this.hT,this.asn1Object)))},Q.lang.extend(W.asn1.DERTaggedObject,W.asn1.ASN1Object);var tt,et,it=(tt=function(t,e){return tt=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i])},tt(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function i(){this.constructor=t}tt(t,e),t.prototype=null===e?Object.create(e):(i.prototype=e.prototype,new i)}),rt=function(t){function e(i){var r=t.call(this)||this;return i&&("string"==typeof i?r.parseKey(i):(e.hasPrivateKeyProperty(i)||e.hasPublicKeyProperty(i))&&r.parsePropertiesFrom(i)),r}return it(e,t),e.prototype.parseKey=function(t){try{var e=0,i=0,r=/^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/.test(t)?function(t){var e;if(void 0===u){var i="0123456789ABCDEF",r=" \f\n\r\t \u2028\u2029";for(u={},e=0;e<16;++e)u[i.charAt(e)]=e;for(i=i.toLowerCase(),e=10;e<16;++e)u[i.charAt(e)]=e;for(e=0;e<r.length;++e)u[r.charAt(e)]=-1}var n=[],s=0,o=0;for(e=0;e<t.length;++e){var h=t.charAt(e);if("="==h)break;if(-1!=(h=u[h])){if(void 0===h)throw new Error("Illegal character at offset "+e);s|=h,++o>=2?(n[n.length]=s,s=0,o=0):s<<=4}}if(o)throw new Error("Hex encoding incomplete: 4 bits missing");return n}(t):g.unarmor(t),n=E.decode(r);if(3===n.sub.length&&(n=n.sub[2].sub[0]),9===n.sub.length){e=n.sub[1].getHexStringValue(),this.n=N(e,16),i=n.sub[2].getHexStringValue(),this.e=parseInt(i,16);var s=n.sub[3].getHexStringValue();this.d=N(s,16);var o=n.sub[4].getHexStringValue();this.p=N(o,16);var h=n.sub[5].getHexStringValue();this.q=N(h,16);var a=n.sub[6].getHexStringValue();this.dmp1=N(a,16);var c=n.sub[7].getHexStringValue();this.dmq1=N(c,16);var f=n.sub[8].getHexStringValue();this.coeff=N(f,16)}else{if(2!==n.sub.length)return!1;if(n.sub[0].sub){var l=n.sub[1].sub[0];e=l.sub[0].getHexStringValue(),this.n=N(e,16),i=l.sub[1].getHexStringValue(),this.e=parseInt(i,16)}else e=n.sub[0].getHexStringValue(),this.n=N(e,16),i=n.sub[1].getHexStringValue(),this.e=parseInt(i,16)}return!0}catch(t){return!1}},e.prototype.getPrivateBaseKey=function(){var t={array:[new W.asn1.DERInteger({int:0}),new W.asn1.DERInteger({bigint:this.n}),new W.asn1.DERInteger({int:this.e}),new W.asn1.DERInteger({bigint:this.d}),new W.asn1.DERInteger({bigint:this.p}),new W.asn1.DERInteger({bigint:this.q}),new W.asn1.DERInteger({bigint:this.dmp1}),new W.asn1.DERInteger({bigint:this.dmq1}),new W.asn1.DERInteger({bigint:this.coeff})]};return new W.asn1.DERSequence(t).getEncodedHex()},e.prototype.getPrivateBaseKeyB64=function(){return f(this.getPrivateBaseKey())},e.prototype.getPublicBaseKey=function(){var t=new W.asn1.DERSequence({array:[new W.asn1.DERObjectIdentifier({oid:"1.2.840.113549.1.1.1"}),new W.asn1.DERNull]}),e=new W.asn1.DERSequence({array:[new W.asn1.DERInteger({bigint:this.n}),new W.asn1.DERInteger({int:this.e})]}),i=new W.asn1.DERBitString({hex:"00"+e.getEncodedHex()});return new W.asn1.DERSequence({array:[t,i]}).getEncodedHex()},e.prototype.getPublicBaseKeyB64=function(){return f(this.getPublicBaseKey())},e.wordwrap=function(t,e){if(!t)return t;var i="(.{1,"+(e=e||64)+"})( +|$\n?)|(.{1,"+e+"})";return t.match(RegExp(i,"g")).join("\n")},e.prototype.getPrivateKey=function(){var t="-----BEGIN RSA PRIVATE KEY-----\n";return(t+=e.wordwrap(this.getPrivateBaseKeyB64())+"\n")+"-----END RSA PRIVATE KEY-----"},e.prototype.getPublicKey=function(){var t="-----BEGIN PUBLIC KEY-----\n";return(t+=e.wordwrap(this.getPublicBaseKeyB64())+"\n")+"-----END PUBLIC KEY-----"},e.hasPublicKeyProperty=function(t){return(t=t||{}).hasOwnProperty("n")&&t.hasOwnProperty("e")},e.hasPrivateKeyProperty=function(t){return(t=t||{}).hasOwnProperty("n")&&t.hasOwnProperty("e")&&t.hasOwnProperty("d")&&t.hasOwnProperty("p")&&t.hasOwnProperty("q")&&t.hasOwnProperty("dmp1")&&t.hasOwnProperty("dmq1")&&t.hasOwnProperty("coeff")},e.prototype.parsePropertiesFrom=function(t){this.n=t.n,this.e=t.e,t.hasOwnProperty("d")&&(this.d=t.d,this.p=t.p,this.q=t.q,this.dmp1=t.dmp1,this.dmq1=t.dmq1,this.coeff=t.coeff)},e}(J),nt=i(155),st=void 0!==nt?null===(et=nt.env)||void 0===et?void 0:"3.3.1":void 0;const ot=function(){function t(t){void 0===t&&(t={}),t=t||{},this.default_key_size=t.default_key_size?parseInt(t.default_key_size,10):1024,this.default_public_exponent=t.default_public_exponent||"010001",this.log=t.log||!1,this.key=null}return t.prototype.setKey=function(t){this.log&&this.key&&console.warn("A key was already set, overriding existing."),this.key=new rt(t)},t.prototype.setPrivateKey=function(t){this.setKey(t)},t.prototype.setPublicKey=function(t){this.setKey(t)},t.prototype.decrypt=function(t){try{return this.getKey().decrypt(l(t))}catch(t){return!1}},t.prototype.encrypt=function(t){try{return f(this.getKey().encrypt(t))}catch(t){return!1}},t.prototype.sign=function(t,e,i){try{return f(this.getKey().sign(t,e,i))}catch(t){return!1}},t.prototype.verify=function(t,e,i){try{return this.getKey().verify(t,l(e),i)}catch(t){return!1}},t.prototype.getKey=function(t){if(!this.key){if(this.key=new rt,t&&"[object Function]"==={}.toString.call(t))return void this.key.generateAsync(this.default_key_size,this.default_public_exponent,t);this.key.generate(this.default_key_size,this.default_public_exponent)}return this.key},t.prototype.getPrivateKey=function(){return this.getKey().getPrivateKey()},t.prototype.getPrivateKeyB64=function(){return this.getKey().getPrivateBaseKeyB64()},t.prototype.getPublicKey=function(){return this.getKey().getPublicKey()},t.prototype.getPublicKeyB64=function(){return this.getKey().getPublicBaseKeyB64()},t.version=st,t}()})(),r.default})()));
(2)封装
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
//密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSKLqzcWmOzbfj64K8ZIgOdH\n' +
  'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4jvbuccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
const privateKey = 'MIIBVAIBADANBgFAddddgggggAAkEAqhHyZfSsYourNxaY\n' +
  '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
  'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
  'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
  'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
  'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
  'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
  'UP8iWi1Qw0Y='
//加密
export function encrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey) //设置公钥
  return encryptor.encrypt(txt) //对数据进行加密
}
//解密
export function decrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPrivateKey(privateKey) //设置私钥
  return encryptor.decrypt(txt) //对数据进行解密
}
(3)使用
import { encrypt, decrypt } from "@/utils/jsencrypt";
function handleLogin() {
	proxy.$refs.loginRef.validate((valid) => {
		if (valid) {
			loading.value = true;
			//勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
			if (loginForm.value.rememberMe) {
				Cookies.set("username", loginForm.value.name, { expires: 30 });
				Cookies.set("password", encrypt(loginForm.value.pwd), {
					expires: 30,
				});
				Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 });
			} else {
				//否则移除
				Cookies.remove("username");
				Cookies.remove("password");
				Cookies.remove("rememberMe");
			}
			//调用action的登录方法
			userStore
				.login(loginForm.value)
				.then(() => {
          
					router.push({ path: redirect.value || "/" });
				})
				.catch((error) => {
          console.log(error)
					loading.value = false;
					//重新获取验证码
				});
		}
	});
}

三、js编码,
1、基本概念,位->字节->字符->字符集
(1)bit,位,1个bit有2种状态,0或1,比特
(2)byte,字节,1个字节由8个位构成
(3)字符,文字或符号,1个字符消耗1--2个字节,1个英文字符消耗1个字节,1个汉字字符消耗2个字节
(4)字符集,字符的集合,字符集的读取规则,内置于计算机,用户在html或img标签中指明使用哪种字符集,如ASCII、base64、Unicode、gb-2312
2、ASCII字符集,八位二进制,共256个字符,1967年第一次发表
(1)状态,1个二进制位2个状态、8个二进制位256个状态(从0000000到11111111)
(2)字符,1个状态对应1个字符,256个状态对应256个字符,以0开始的128个状态对应128个英文字符,以1开始的128个状态对应128个非英文字符
(3)128个英文字符包含,33个控制字符或通信专用字符、64个base64字符、31个其它字符!"#$%&'()*,-.:;<=>?@[\]^_`{|}~空 
(4)应用,这256个字符能够描述所有格式的数据,如文字、符号、图像、音频、视频
(5)ASCII定义,American Standard Code for Information Interchange,美国信息交换标准代码,是最通用的信息交换标准
3、base64字符集,六位二进制,共64个字符,1987年,第一次提及
(1)状态,1个二进制位2个状态、6个二进制位64个状态(从00000到111111)
(2)字符,1个状态对应1个字符,64个状态对应64个字符,
(3)64个字符包含,52个大小写字母、10个数字、1个+、1个/,另有第65个填充字符=
(4)应用,这64个字符能够描述所有格式的数据,如文字、符号、图像、音频、视频
4、Unicode字符集,1990年开始研发,1994年正式发布1.0版本,2022年9月13日发布15.0版本
  来源,https://c.runoob.com/front-end/3602/
  来源,https://wenku.baidu.com/aggs/58fb790203d8ce2f00662377.html?_wkts_=1697536003150&bdQuery=unicode%E7%BC%96%E7%A0%81%E5%AF%B9%E7%85%A7%E8%A1%A8
(1)获取字符对应的十六进制数字,console.log('中'.charCodeAt(0).toString(16))
(2)获取十六进制数字对应的字符,console.log(String.fromCharCode(parseInt('4e2d',16)))
(3)该字符集的1个字符用1个编码对应,100多万个字符对应100多万个编码
(4)应用,可以容纳世界上所有的字符
(5)Unicode字符集的编码规则有,UTF-8、UTF-16、UTF-32等,有的由字符前缀加进制序号构成,有的由ASCII字符构成。。。
  A、UTF-8,对不同范围的字符使用不同长度的编码
  B、UTF-16BE,%u编码与解码,console.log(escape('中'));console.log(unescape('%u4E2D'));各种前缀加各种进制的数字组成的字符串,
5、ASCII和base64之间的转换
(1)window.atob,base64字符转换为ASCII字符
  A、参数为base64字符
  B、返回值为ASCII字符
  C、常用于重新编码;console.log(window.atob('ISIjJCUm'))
(2)window.btoa,ASCII字符转换为base64字符
  A、参数为ASCII字符
  B、返回值为base64字符
  C、常用于图片显示;console.log(window.btoa('!"#$%&'))
6、不同字符集之间的转换,来源,https://blog.csdn.net/qq_35273097/article/details/131317281
  function fromTextToText(text) {
    //function expand(a,b,c){console.log(a,b,c);} expand(...[1,2,3])//1 2 3
    //正转换,base64<--ASCII<--Unicode​<--Uint8Array<--原文
    var before = btoa(String.fromCharCode(...new TextEncoder().encode(text)));
    console.log('ASCII编码结果为base64数据:' + before);
    //反转换,原文<--​Uint8Array<--Unicode<--ASCII<--base64
    var after = new TextDecoder().decode(Uint8Array.from(atob(before),function(item){return item.charCodeAt(0)}))
    console.log('TextDecoder解码结果为Unicode数据:' + after);
  }
  var text = "Hello,你好!";
  fromTextToText(text);  
7、数据的操作
 附、JS二进制“数据格式”有, ​ArrayBuffer​,​Blob​,​DataView​,​​File(继承了Blob),Uint8Array​​,来源,https://www.w3cschool.cn/qoyhx/qoyhx-8h5w3q7u.html
(1)new ArrayBuffer(16),创建一个长度为16的buffer
  A、参数是一个数字
  B、ArrayBuffer,二进制数组,对固定长度的连续内存空间的引用  
(2)new Uint8Array([228, 189, 160, 229, 165, 189]);
  A、参数为1个数组,数组的每项为0到255之间的单个数字,因每个字节是8位,称为“8位无符号整数”;2的8次方是256
  B、字符-字节(数字)
(3)TextEncoder、TextDecoder,文字编码与解码,只支持utf-8编码,来源,https://www.w3cschool.cn/qoyhx/qoyhx-f3dz3q7v.html
  A、TextEncoder,编码,将字符串转换为uint8Array数组,数组的每1项都是1个Unicode编码,1个汉字需要3个编码
    var encoder = new TextEncoder();
    var uint8Array = encoder.encode("张三");
    console.log(uint8Array); //​Uint8Array,[229, 188, 160, 228, 184, 137]
  B、TextDecoder,解码,将uint8Array数组转换为字符串
    var uint8Array = new Uint8Array([228, 189, 160, 229, 165, 189]);
    var text =  new TextDecoder().decode(uint8Array); //参数为要被解码的BufferSource
    console.log( text ); //你好
(4)File、FileReader,文件与文件读取,来源,https://www.w3cschool.cn/qoyhx/qoyhx-l63p3q7x.html
  A、readAsArrayBuffer,将数据读取为二进制格式的​ArrayBuffer​
  B、readAsBinaryString,读取数据为二进制字符串
  C、readAsDataURL,读取二进制数据,并将其编码为base64的data url,常用于img​标签的​src属性,
  D、readAsText,将数据读取为给定编码(默认为utf-8​编码)的文本字符串,用法示例
    var reader = new FileReader();
    reader.readAsText(thisBlob, 'utf8');
    reader.onload = function() {
      console.log( '原文:' + this.result )
    }
(5)相关方法  
 A、charCodeAt,参数为字符串索引,返回值字符的Unicode编码,范围为0到1114111,来源,https://www.w3cschool.cn/typescript/typescript_string_charcodeat.html
 B、String.fromCharCode,参数为1到多个数字(Unicode编码),返回值ASCII字符串,来源,https://www.w3cschool.cn/nwfchn/kd35bozt.html
 C、Uint8Array.from,参数为(字符串,函数),返回值Uint8Array数组,来源,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
8、blob数据的生成与读取
 来源,https://www.w3cschool.cn/qoyhx/qoyhx-t6jq3q7w.html
(1)二进制大对象(binary large object),1970年代出现,是一种数据格式
(2)生成blob数据,发给后台
  //流程,前端base64--ASCII--Unicode编码--Blob后台
  function base64toBlob(data) {
    var array = data.split(',') //将整个图片分成两个部分,第1部分是图片说明,第2部分是图片自身
    var mime = array[0].match(/:(.*?);/)[1] //先获取图片说明,后获取图片后缀
    var image = array[1]; //获取图片自身
    var atobStr = atob(image) //1、将base64编码,转化为“ASCII编码”
    var length = atobStr.length
    var u8Array = new Uint8Array(length) 
    while (length--) {
      u8Array[length] = atobStr.charCodeAt(length) //2、将ASCII编码的字符,转化为Unicode编码,并用Uint8Array存储
    }
    const obj = new Blob([u8Array], { type: mime }) //3、将Unicode编码,转化为“Blob编码”,type指定blob的类型
    //obj为一个新创建的Blob对象,其内容由参数中给定的数组拼接组成,上传到服务器
  }
(3)接收后台blob数据,读取为文字
  //此处接收后台blob数据,thisBlob
  var reader = new FileReader();
  reader.readAsText(thisBlob, 'utf8'); //把后台的blob字符串转化为最初的文字
  reader.onload = function() {
    console.log('原文:' + this.result )
  }
(4)blob数据的生成与读取
  function fromTextToText(text) {
    var base64Str = btoa(String.fromCharCode(...new TextEncoder().encode(text))) //1、把原字符串转化为base64字符串
    var atobStr = atob(base64Str); //2、把base64字符串转化为ASCII字符串
    var length = atobStr.length;
    var uint8Array = new Uint8Array(length);
    while (length--) {
      uint8Array[length] = atobStr.charCodeAt(length); //3、把ASCII字符串转化为Unicode编码,存储在Uint8Array里
    }
    console.log( uint8Array )
    var thisBlob = new Blob([uint8Array]); //4、把ASCII字符串转化为blob字符串,发给后台
    console.log( thisBlob )
    var reader = new FileReader();
    reader.readAsText(thisBlob, 'utf8'); //5、把后台的blob字符串转化为最初的文字
    reader.onload = function() {
      console.log('原文:' + this.result )
    }
  }
  var text = "Hello,你好!";
  fromTextToText(text);  
9、前后台数据交换,
(1)前端先把自己能收集到的数据格式“转化”为后台所需要的数据格式,然后发给后台
(2)前端接到后台返回的数据格式后,先“转化”成前端能读取的数据格式,然后插到页面

四、数据上传
===上面,向远方发送请求,远方将上传路由放到cookie里。此处,隐式从cookie获取上传路由,用ajax-promise上传“数据”===
来源,https://blog.csdn.net/zyx042299/article/details/128008334
附1、label标签的for属性,与表单元素的id值绑定,点击label标签内的文本,就会触发绑定的表单元素
  //来源,https://www.w3school.com.cn/tags/att_label_for.asp
  显式的联系
    <label for="SSN">身份证号码:</label>
    <input type="text" name="IdNum" id="IN" />
  隐式的联系
    <label>生日:<input type="text" name="DofB" /></label>
附2、通过 <input type="file" onchange="changeFile(value)" v-model="value"/> 通过onchange事件或model属性获取要上传的(二进制)数据
1、普通上传,普通的键值对上传
(1)自动上传
  <form
    name="myForm"//new formData(document.forms["myForm"]).set('value', 'value')
    action="demo_form.php"//提交表单时,服务器的接收地址
    enctype="multipart/form-data" 
    //(1)application/x-www-form-urlencoded 在发送前编码所有字符(默认)
    //(2)multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
    //(3)text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
  >
    <input name="myInput" type="text"/> //input标签的name属性需要与它的type属性同时使用,
    <input name="inputFile" type="file"/> //type的取值可以是text、password、radio、checkbox
    <select name="mySelect"></select> //设置了name属性的表单元素,才能在提交表单时,自动传递它们的值
    <input type="submit" value="默认提交"/> //点击就上传数据
  </form>
(2)手动上传
  A、为空追加
    <form>
      <input v-model="value" type="file"/>
      <input type="button" value="自定义提交" @click="submitForm()">
    </form>
    function submitForm() {
      var formData = new formData();//先弄一个空的
      formData.set('value', 'value');//用新值覆盖已有的值
      formData.append('value', 'value1');//把新值添加到已有值集合的后面
      $.ajax({
        url: '',
        type: 'post',
        data: formData,
      });
    }
  B、非空追加
    <form 
      name="myForm"
    >//document.forms["myForm"]
      <input name="inputValue" type="text"/> 
      <select name="mySelect"></select>
      <input name="inputFile" type="file"/>
      <input name="protocolScript" type="file"/> //这是angular1的相关内容
      <input type="button" value="自定义提交" onclick="submitForm()">
      <input type="button" value="自定义提交" ng-click="submitForm()">
    </form>
    function submitForm() {
      var myForm = document.forms["myForm"];
      var formData= new formData(myForm);//格式化具有name属性的字段
      formData.set('value', 'value');//用新值覆盖已有的值
      formData.append('value', 'value1');//把新值添加到已有值集合的后面
      $.ajax({
        url: '',
        type: 'post',
        data: formData,
      });
    }
  C、angular1示例
    <input name="protocolScript" type="file" //这是angular1的相关内容
      onchange="angular.element(this).scope().onChangeFile(this)" //原生事件,实参this是input上传的内容 
      ng-change="ngChangeFile(value)" //非原生事件,value为空
    />
    $scope.ngChangeFile = function (ele) {
      $scope.files_list = ele.files;
      $scope.$apply();
    };
  D、elementUI上传(PC端)
    <el-upload 
      action=""
      :auto-upload="false"
      accept=".jpg,.png"
      :show-file-list="false"
      :on-change="handlChange"
    >
      <el-button icon="plus" type="primary">上传文档</van-button>
    </el-upload>
    on-change,文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用,通过onchange事件或model属性获取要上传的(二进制)数据
    var handlChange = function(file, fileList) {
      this.fileList = fileList;
    }
  E、vantUI上传(移动端)
    <van-uploader accept=".doc, .docx" accept="*" :before-read="uploadVideo">
      <van-button icon="plus" type="primary">上传文档</van-button>
    </van-uploader>
    before-read,文件读取(编码)前的回调函数,作用类似于onchange
    var uploadVideo = function(file, detail) {
      console.log( fileInfo.raw );
      //首先根据文件的大小和后缀来判断,是否拦截上传并提出提示
    }
2、单图片先裁切后上传
  ===单图片先裁切后上传逻辑,把数据读取为url,作为:img配置,通过ref配置,获取裁切后的数据 <vueCropper :img="option.clientImg" ref="cropper"/>===
  来源,http://www.zzvips.com/article/212420.html
  来源,https://blog.csdn.net/qq_45886894/article/details/129486002
  附、前端把代码转化读取为图片的2个方案,先在本地读取图片,裁切后上传,裁剪
    //方案1,readAsDataURL,适用于小文件
    change(file) {
      const reader = new FileReader() //实例化文件读取对象
      reader.readAsDataURL(file.raw) //(将图片)作为数据URL读取,即base64编码
      reader.onload = (event) => { //文件读取成功完成时触发
        this.option.clientImg = event.target.result
        this.cutDialogVisible = true
      }
    }
    //方案2,createObjectURL,适用于大文件
    change(file) {
      const URL = window.URL||window.webkitURL;
      this.option.clientImg = URL.createObjectURL(file.raw);
      this.cutDialogVisible = true;
    }
  附、前端将base64图片代码转化为Blob图片代码 
    cutImg() {
      this.$refs.cropper.getCropData((data) => {
        //getCropData,data,获取图片的base64数据
        //getCropBlob,data,获取图片的blob数据。???为什么不用这个函数,直接获取blob数据呢???
        var array = data.split(',') 
        var mime = array[0].match(/:(.*?);/)[1] 
        var image = array[1]; 
        var atobStr = atob(image); 
        var length = atobStr.length
        var u8Array = new Uint8Array(length) 
        while (length--) {
          u8Array[length] = atobStr.charCodeAt(length) 
        }
        const obj = new Blob([u8Array], { type: mime }) 
      })
    }
(1)ImgCut,该组件单独使用,没使用上传进度的功能
  说明,用cos-js-sdk-v5封装的uploadClient上传图片,另附,上传封装
  A、依赖
    "dependencies": {
      "vue-cropperjs": "^4.2.0",
    },
  B、组件调用
    <el-form-item
      label="合集封面"
      class="audio-form-pic_loading"
      element-loading-text="正在上传图片"
      element-loading-background="rgba(0, 0, 0, 0.6)"
      prop="poster"
    >
      <ImgCut @urlGet='producePoster' :url="form.collectionImg" />
      <div class="produce-form-pic-tip">请上传封面图片,文件大小不得超过5MB,格式为jpg/png/jpeg</div>
    </el-form-item>
    producePoster(imgUrl) {
      this.form.collectionImg = imgUrl;
		},
  C、组件定义,内含组件el-upload和vueCropper的调用
    <template>
      <div>
        <el-upload
          action=""
          accept=".jpg,.png"
          class="poster-uploader"
          :auto-upload="false"
          :show-file-list="false"
          :on-success="handleAvatarSuccess"
          :before-upload="beforeAvatarUpload"
          :on-change="uploadChange"
        >
          <img v-if="isShow" :src="newUrl" class="poster" />
          <i v-else class="el-icon-plus poster-uploader-icon"></i>
        </el-upload>
        <el-dialog title="裁切/裁剪" :append-to-body='true' :visible.sync="cutDialogVisible" width="800px">
          <div class="cropper-content">
            <div class="cropper" style="text-align: center">
              <vueCropper
                ref="cropper"
                :img="option.clientImg"
                :output-size="option.outputSize"
                :output-type="option.outputType"
                :info="true"
                :full="option.full"
                :can-move="option.canMove"
                :can-move-box="option.canMoveBox"
                :original="option.original"
                :auto-crop="option.autoCrop"
                :auto-crop-width="option.autoCropWidth"
                :auto-crop-height="option.autoCropHeight"
                :fixed="option.fixed"
                :fixed-number="option.fixedNumber"
                :center-box="option.centerBox"
                :info-true="option.infoTrue"
                :fixed-box="option.fixedBox"
                @real-time="realTime"
              />
            </div>
            <div v-if="previews.div" class="show-preview">
              <div
                :style="{
                  width: previews.div.width,
                  height: previews.div.height,
                  transformOrigin: '0 0',
                  transform: 'scale(' + 150 / previews.w + ')',
                  overflow: 'hidden',
                }"
              >
                <img :src="previews.url" :style="previews.img">
              </div>
            </div>
          </div>
          <div slot="footer" class="poster-uploader-footer dialog-footer">
            <div class="upload-form-cropper">
              <el-button size="mini" @click="cutDialogVisible = false" >取 消</el-button>
              <el-button type="primary" size="mini" @click="cutImg">确认</el-button>
            </div>
          </div>
        </el-dialog>
      </div>
    </template>
    /* ===单图片先裁切后上传逻辑,把数据读取为url,作为:img配置,通过ref配置,获取裁切后的数据 <vueCropper :img="option.clientImg" ref="cropper"/>=== */
    <script>
      import VueCropper from "vue-cropperjs";
      import uploadClient from '@/utils/upload';
      export default {
        components: { VueCropper },
        methods: {
          uploadChange(fileInfo) {
            if (!this.checkSize(fileInfo)) {
              return
            }
            const image = fileInfo.raw //获取图片
            const reader = new FileReader() //实例化文件读取对象
            reader.readAsDataURL(image) //将文件读取为 DataURL,也就是base64编码
            reader.onload = (ev) => {//文件读取成功完成时触发
              /* this.serverImg = ev.target.result */
              this.option.clientImg = ev.target.result
              this.cutDialogVisible = true
            }
          },
          cutImg() {
            this.$refs.cropper.getCropData((data) => {//data是裁切/裁剪后的base64图片数据
              /* this.serverImg = data */
              this.cutDialogVisible = false
              uploadClient.upload(data).then((res) => {
                this.$emit('urlGet', res.Url)//把图片的存储地址告诉form的model属性
                this.newUrl = data//把base64图片渲染在本地页面
                this.isShow = true
              })
            })
          },
        },
      }
    </script>
  附、项目open-cctv的上传方法封装
    路径'@/utils/upload'下的上传文件upload.js,
    导出的对象为uploadClient,里面引入cos-js-sdk-v5,含上传进度的功能
    cos-js-sdk-v5,腾讯cos存储,云对象存储(Cloud Object Storage,COS),另见“阿里”
    /**
     * 文件上传工具类
     * 使用upload方法
     * 第一次调用的时候会初始化COS sdk
     * 参数 fileInfo 需要两个属性一个 fileInfo.fileUrl 文件路径
     * 一个 fileInfo.raw 文件内容
     * 参数 progress 上传进度回调方法
     *
     */ 
    import store from '@/store'
    let cosObject = null
    const COS = require('cos-js-sdk-v5')
    import { getCosToken, getCosImg } from '@/api/upload.js'
    export default {
      initCOS() {
        const uid =  store.getters.user.uid 
        if (cosObject) {////////////////////////////////////////////////////////////////////////////////////////////////////
          return new Promise((resolve, reject) => {
            resolve(cosObject)
          })
        }
        return getCosToken({
          tokenName: 'web',
          uid: uid
        }).then(res => {//获取传输同意,生成传输实例,内置隐式路由
          cosObject = new COS({//
            getAuthorization: (options, callback) => {
              var credentials = res.data && res.data.credentials
              if (!res || !credentials) { return console.error('credentials invalid') }
              callback({
                TmpSecretId: credentials.tmpSecretId,
                TmpSecretKey: credentials.tmpSecretKey,
                XCosSecurityToken: credentials.sessionToken,
                // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
                StartTime: res.data.startTime, // 时间戳,单位秒,如:1580000000
                ExpiredTime: res.data.expiredTime // 时间戳,单位秒,如:1580000900
                // ScopeLimit: true, // 细粒度控制权限需要设为 true,会限制密钥只在相同请求时重复使用
              })
            }
          })
          return cosObject // 返回promise实例时,将上层的resolve注入自己;返回其它值时,将自己传入上层的resolve,进而传入success
        })
      },
      upload(fileInfo, progress) {
        return new Promise((resolve, reject) => {
          this.initCOS().then(res => {
            cosObject.putObject(//此处可以用res.putObject,
            //===上面,向远方发送请求,远方将上传路由放到cookie里。此处,隐式从cookie获取上传路由,用ajax-promise上传“数据”===
              {
                Bucket: "gongweiedu-1257784922" // 必须,
                Region: "ap-beijing", // 必须,存储桶所在地域
                Key: fileInfo.fileUrl, // 必须
                StorageClass: "STANDARD",
                Body: fileInfo.raw, // 上传文件对象 
                onProgress: progress // 此处配置上传进度
              },
              (err, data) => {
                if (!err) {
                  cosObject.getObjectUrl(
                    {
                    // 获取文件访问地址
                      Method: "PUT",
                      Key: fileInfo.fileUrl,
                      Bucket: "gongweiedu-1257784922",
                      Region: "ap-beijing",
                      Sign: false
                    },
                    (err, data) => {
                      if(err){
                        reject(err)
                      }else {
                        // data.Url = data.Url
                        // console.log(data)
                        resolve(data)//uploadClient.upload(fileInfo).then(function(data){})
                      }
                      // data.Url = data.Url
                    }
                  )
                } else {
                  reject()
                }
              }
            )
          })
        })
      },
      getPoster(res) {
        return new Promise((resolve, reject) => {
          //res.Url = res.Url.replace('om.app.cctv.com','gongweiedu-1257784922.cos.ap-beijing.myqcloud.com')
          const url = res.Url.split('?')[0]
          const param = {
            uid: uid,
            url: url
          }
          getCosImg(param).then(res => {
            if (res.code === 200) {
              resolve(res.data)
            } else {
              reject()
            }
          })
        })
      }
    }
    附、upload.js //import { getCosToken, getCosImg } from '@/api/upload.js'
    import request from '@/utils/request'
    export function getCosToken(params) {
      return request({
        url: '/comm/file/cosToken',
        method: 'get',
        params
      })
    }
    export function getCosImg(params) {
      return request({
        url: 'platform-server/api/user/getcosimg',
        method: 'get',
        params
      })
    }
(2)ImageCropper,该组件作为组件Upload的组成部分使用,使用了上传进度的功能
  说明,用cos-js-sdk-v5直接上传图片
  A、依赖
  "dependencies": {
    "vue-cropperjs": "^4.2.0",
  },
  B、组件调用,调用Upload组件,调用该组件,
    //以下调用组件Upload(不完整)
    <Upload 
      :sizeRadio="[16,9]" 
      :coverImgUrl="ruleForm.coverImgUrl"  
      @getUrl="getImgUrl" 
    />
    getImgUrl(url){
      this.ruleForm.categoryUrl = url
    },
  附、项目online-class-manage的上传组件Upload的定义,
    内含组件el-upload和ImageCropper的调用,
    先通过前者用:on-change判断文件大小是否合适
    后通过后者用@toServerProgress有进度地发送到服务器
    <template>
      <div class="uploadContainer">
        <el-upload
          :accept="accept"
          :auto-upload="false"
          :file-list="arrlist"
          :limit="1"
          :on-change="fileUpload"
          :show-file-list="false"
          action=""
          class="upload"
        >
          <div class="el-upload" v-show="!progressFlag">
            <img v-if="uploadImg" :src="uploadImg" class="avatar" />
            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
          </div>
        </el-upload>
        <div class="progress" v-show="progressFlag">
          <el-progress :percentage="uploadPercent" :width="84" type="circle" />//此处显示上传进度
        </div>
        //以下是,组件ImageCropper的调用
        <ImageCropper
          v-if="uploadFile"
          ref="cropperCom"
          :key="uploadFile.lastModified"
          :upload-file="uploadFile"
          :sizeRadio="sizeRadio"
          @toServerProgress="toServerProgress"
          @closeHadle="closeCropper"
        />
      </div>
    </template>
    <script>
      "dependencies": {
        "cos-js-sdk-v5": "^1.2.18",
      },
      const COS = require("cos-js-sdk-v5");
      const TcVod = require("vod-js-sdk-v6");
      import { getCosImg } from "@/api/upload.js";
      import ImageCropper from "@/components/ImageCropper/index.vue"
      export default {
        name: "Upload",
        props: {
          accept: {
            default: "image/*",
            type: String
          },
          coverImgUrl: {
            default: "",
            type: String
          },
          size: {
            default: 20,
            type: Number
          },
          sizeRadio: {
            type: Array,
            default: function() {
              return []
            }
          },
          mold: {
            default: () => ["jpg", "jpeg", "png", "gif","webp"],
            type: Array
          }
        },
        components: {
          ImageCropper
        },
        data() {
          return {
            uploadImg: "",
            arrlist: [],
            fileUrl: "",
            uploadPercent: 0,
            progressFlag: false,
            uploadFile: '',
          };
        },
        created() {
          this.uploadImg = this.coverImgUrl;
        },
        mounted() {
          this.initCOS();
        },
        methods: {
          initCOS() {
            getCosImg().then(res => {
              /* eslint-disable */
              this.COS = new COS({
                getAuthorization: (options, callback) => {
                  var credentials = res.data && res.data.credentials;
                  if (!res || !credentials)
                    return console.error("credentials invalid");
                  callback({
                    TmpSecretId: credentials.tmpSecretId,
                    TmpSecretKey: credentials.tmpSecretKey,
                    XCosSecurityToken: credentials.sessionToken,
                    // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
                    StartTime: res.data.startTime, // 时间戳,单位秒,如:1580000000
                    ExpiredTime: res.data.expiredTime // 时间戳,单位秒,如:1580000900
                    // ScopeLimit: true, // 细粒度控制权限需要设为 true,会限制密钥只在相同请求时重复使用
                  });
                }
              });
            });
          },
          closeCropper(){
            //清空已上传的文件列表
            this.arrlist = [];
          },
          toServerProgress(fileInfo){ //在裁剪后执行,有进度地上传-裁剪后的文件
            let fileType = fileInfo.name.substring(fileInfo.name.lastIndexOf(".") + 1).toLowerCase();
            //发送请求
            let fileUrl = `zxkt/img/${new Date().getTime()}.${fileType}`;
            this.COS.sliceUploadFile(//===上面,向远方发送请求,远方将上传路由放到cookie里。此处,隐式从cookie获取上传路由,用ajax-promise上传“数据”===
              {
                Bucket: "sharepool-1257784922", /* 必须 */
                Region: "ap-beijing", /* 存储桶所在地域,必须字段 */
                Key: fileUrl, /* 必须 */
                StorageClass: "STANDARD",
                Body: fileInfo, // 上传文件对象
                onProgress: progressData => {// 此处配置上传进度
                  this.progressFlag = true;
                  this.uploadPercent = Math.round(progressData.percent * 100);
                }
              },
              (err, data) => {
                if (!err) {
                  this.COS.getObjectUrl(
                    {
                      // 获取文件访问地址
                      Method: "PUT",
                      Key: fileUrl,
                      Bucket: "sharepool-1257784922",
                      Region: "ap-beijing",
                      Sign: false
                    },
                    (err, data) => {
                      let res = err || data;
                      this.$message({ type: "success", message: "上传成功!" });
                      this.progressFlag = false;
                      this.arrlist = [];
                      this.fileUrl = res.Url.split("?")[0];
                      // this.uploadImg = "https://om.app.cctv.com/" + fileUrl;
                      this.uploadImg = "https://sharepool-1257784922.cos.ap-beijing.myqcloud.com/" + fileUrl;
                      this.$emit("getUrl", this.uploadImg);
                    }
                  );
                } else {
                  this.progressFlag = false;
                  this.$message({
                    type: "warning",
                    message: "上传失败,请重新上传!"
                  });
                }
              }
            );
          },
          fileUpload(fileInfo) {
            this.uploadFile = fileInfo.raw;
            let fileType = fileInfo.name.substring(fileInfo.name.lastIndexOf(".") + 1).toLowerCase();
            let filetTypeBlen = false;
            if (this.mold.includes(fileType)) {
              filetTypeBlen = true;
            } else {
              this.$message({
                type: "warning",
                message: `请上传${this.mold.join(",")}格式的文件!`
              });
              this.arrlist = [];
              return false;
            }
            const sizeBlen = fileInfo.size / 1024 / 1024 < this.size;
            if (!sizeBlen) {
              this.$message({
                type: "warning",
                message: `上传文件大小不能超过 ${this.size}Mb!`
              });
              this.arrlist = [];
              return false;
            }
            if (filetTypeBlen && sizeBlen) {
              //开始裁剪
              this.$nextTick(() => {
                this.$refs.cropperCom.open();
              })
            }
          }
        }
      };
    </script>
    <style lang="scss" scoped>
    </style>
  C、组件定义,内含组件vueCropper的调用
    <template>
      <div v-if="showImage" class="container">
        <el-dialog title="图片剪裁工具" :visible.sync="visible" :append-to-body="true" @close="closeHadle" height="500px">
          <vueCropper
            ref="cropper"
            :key="showImage"
            :src="showImage"
            @ready="ready"
          />
          <div style="margin-top: 10px;">
            <el-button @click="ratio(4 / 5)">4 : 5</el-button>
            <el-button @click="ratio(4 / 3)">4 : 3</el-button>
            <el-button @click="ratio(16 / 9)">16 : 9</el-button>
            <el-button @click="ratio(2 / 3)">2 : 3</el-button>
            <el-button @click="ratio(1)">1 : 1</el-button>
            <el-button type="primary" @click="origin">裁剪保存</el-button>
            <el-button type="primary" @click="defaultPic">原图保存</el-button>
          </div>
        </el-dialog>
      </div>
    </template>
    <script>
      import VueCropper from "vue-cropperjs";
      import "cropperjs/dist/cropper.css";
      export default {
        components: { VueCropper },
        props: {
          // eslint-disable-next-line vue/require-prop-types
          uploadFile: {
            required: true
          },
          sizeRadio: {
            type: Array,
            default: function() {
              return []
            }
          }
        },
        data() {
          return {
            visible: false,
            showImage: ""
          };
        },
        mounted() {
          this.showImage = window.URL.createObjectURL(this.uploadFile);
        },
        methods: {
          closeHadle(){
            this.$emit('closeHadle')
          },
          open() {
            this.visible = true;
          },
          ready() {
            const imageInfo = this.$refs.cropper.getData();
            const imageW = this.sizeRadio[0]||imageInfo.width;
            const imageH = this.sizeRadio[1]||imageInfo.height;
            this.$refs.cropper.setAspectRatio(imageW / imageH);
          },
          ratio(aspectRatio) {
            this.$refs.cropper.setAspectRatio(aspectRatio);
          },
          origin() {
            // 裁剪上传
            this.$refs.cropper.getCroppedCanvas().toBlob(blob => {
              const file = new File([blob], this.uploadFile.name, {
                type: blob.type,
                lastModified: Date.now()
              });
              this.visible = false;
              this.$emit("toServerProgress", file);
            });
          },
          defaultPic() {
            // 原图上传
            this.visible = false;
            this.$emit("toServerProgress", this.uploadFile);
          }
        }
      };
    </script>
    <style lang="scss" scoped>
    </style>
3、多文件上传,批量
(1)先把多张图片或多个视频,分别先在本地读出基本信息并放在列表中展示,然后上传至服务器,并获取存放地址;最后提交时,把所有存放地址传给服务器
(2)示例,online-class-manage
  //getDuration,在本地获取视频的封面图片,然后上传至服务器,最后更新到列表中
  //handelUploadFile,上传视频,获取上传进度,更新到列表中
  <template>
    <div class="content-container">
      <el-breadcrumb separator="/">
        <el-breadcrumb-item>课程管理</el-breadcrumb-item>
        <el-breadcrumb-item>内容管理</el-breadcrumb-item>
      </el-breadcrumb>
      <div class="content-table">
        <div class="content-table-header">
          <div class="content-table-header_title">上传视频</div>
        </div>
        <div class="form-wrap">
          <el-form
            :model="ruleForm"
            :rules="rules"
            ref="ruleForm"
            label-width="100px"
            class="demo-ruleForm"
          >
            <el-form-item label="上传文件" prop="mediaUrl">
              <div
                :class="[
                  'uploadHeader',
                  mediaName === '' ? 'baseLine' : 'centerLine',
                ]"
              >
                <el-upload
                  class="upload-video"
                  action=""
                  multiple
                  accept="video/*"
                  :limit="10"
                  :disabled="isDisabled"
                  :file-list="fileList"
                  :on-change="mediaUpload"
                  :show-file-list="false"
                  :auto-upload="false"
                >
                  <el-button
                    size="small"
                    type="primary"
                    :disabled="isDisabled"
                    plain
                    ><i class="el-icon-upload2"></i>  选择文件</el-button
                  >
                </el-upload>
              </div>
            </el-form-item>
            <el-form-item label="" prop="">
              <el-table :data="tableData" style="width: 100%">
                <el-table-column label="封面图" width="120" prop="coverImgUrl">
                  <template slot-scope="scope">

                    <img
                      v-if="scope.row.coverImgUrl"
                      :src="scope.row.coverImgUrl"
                      alt=""
                      width="100"
                      height="56"
                    />
                    <span v-else>上传后显示</span>
                  </template>
                </el-table-column>
                <el-table-column prop="oldTitle" label="文件名称" width="180">
                </el-table-column>
                <el-table-column  label="视频大小" width="90">
                  <template slot-scope="scope"> {{ scope.row.size }}MB </template>
                </el-table-column>
                <el-table-column label="视频名称" width="200">
                  <template slot-scope="scope">
                    <el-input
                      v-model="scope.row.title"
                      size="mini"
                      style="width: 100%"
                    ></el-input>
                  </template>
                </el-table-column>
                <el-table-column label="上传进度" width="80">
                  <template slot-scope="scope">
                    <el-progress v-if="scope.row.uploadPercent" type="circle" :width="56" :percentage="scope.row.uploadPercent"></el-progress>
                    <div class="uploadStatus">
                      <span v-if="scope.row.uploadStatus === 1">上传中</span>
                      <span v-if="scope.row.uploadStatus === 2">上传完成</span>
                      <span  v-if="scope.row.uploadStatus === 3" style="color: #d54949">上传失败</span>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column label="操作" width="140">
                  <template slot-scope="scope">
                    <change-img :size="3" @getUrl="getImgUrl" :uid="scope.row.uid"></change-img>
                    <el-button type="text" size="small" @click="delItem(scope.row.uid)">删除</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </el-form-item>
            <el-form-item label="选择课程合集" prop="collectionId">
              <el-select
                v-model="ruleForm.collectionId"
                placeholder="请所属课程合"
              >
                <el-option
                  :label="item.title"
                  :value="item.sid"
                  v-for="item in collectionList"
                  :key="item.sid"
                ></el-option>
              </el-select>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="submitForm('ruleForm')"
                >保存</el-button
              >
              <el-button @click="toContent">返回</el-button>
            </el-form-item>
          </el-form>
        </div>
      </div>
    </div>
  </template>
  <script>
    const COS = require("cos-js-sdk-v5");
    import { getCosImg } from "@/api/upload.js";
    import { getCollectionList,createCourse } from "@/api/course";
    import ChangeImg from "@/components/upload/changeImg"
    export default {
      components: {
        ChangeImg
      },
      data() {
        return {
          isDisabled:false,
          COS: {},
          collectionList: [],
          fileList: [],
          tableData: [
          ],
          ruleForm: {
            collectionId:''
          },
          rules: {
          },
          videoType: ["mp4", "mov", "avi"],
          uploadPercent: 0, //上传进度
          progressFlag: false,
          mediaName: "", //文件名
          changeCoveimg: false,
          changeIndex: 0,
          coverimgTime: [],
          isVideo: false,
          totalTime: "",
          ruleFromt: {},
          changeLoading: "",
          loading: "",
          type: 0, //类型:0=视频;1=音频
        };
      },
      created() {
        this.getCollectionList();
        this.initCOS()
      },
      mounted() {},
      methods: {
        getImgUrl(data){
          this.tableData = this.tableData.map((item)=>{
            if(item.uid === data.uid){
              item.coverImgUrl = data.url
            }
            return item
          })
        },
        mediaUpload(fileInfo) {
          if (fileInfo.status !== "ready") return;
          //如果标题为空,将上传的文件名作为标题
          let fileType = fileInfo.name
            .substring(fileInfo.name.lastIndexOf(".") + 1)
            .toLowerCase();
          if (!this.videoType.includes(fileType)) {
            this.$message({
              type: "warning",
              message: `不支持上传${fileType}格式文件!`,
            });
            this.fileList = [];
            return false;
          }
          let fileSize = 500;
          let sizeBlen = fileInfo.size / 1024 / 1024 < fileSize;
          if (!sizeBlen) {
            this.$message({
              type: "warning",
              message: `上传文件大小不能超过 ${fileSize}MB!`,
            });
            this.fileList = [];
            return false;
          }
          let itemObj = {}
          itemObj.uid = fileInfo.uid
          itemObj.type = 0
          itemObj.coverImgUrl = ''
          itemObj.collectionId = ''
          itemObj.url = ''
          itemObj.duration = ''
          itemObj.oldTitle = fileInfo.name
          itemObj.size = (fileInfo.size / 1024 / 1024).toFixed(2)
          itemObj.title = fileInfo.name.substring(0,fileInfo.name.lastIndexOf(".")-1)
          itemObj.uploadPercent = 0
          itemObj.uploadStatus = 0
          this.tableData.push(itemObj)//增加表格数据
          //发送请求
          this.getDuration(fileInfo)//生成缩略图并上传
          this.handelUploadFile(fileInfo)//上传文件并监控进度
        },
        //===多视频先单个上传后集体生效的逻辑:单个视频信息追加到表格;监听视频可播放事件,先抽帧后上传帧,更新表格数据;上传视频并监听进度,更新表格数据;重复前面步骤;上传表格数据===
        getDuration(fileInfo){
          this.asyncImgChecked(fileInfo).then(res=>{//生成缩略图
            this.tableData = this.tableData.map((item)=>{
              if(item.uid === fileInfo.uid){
                item.duration = String(res.duration) 
                this.handelUploadImg(fileInfo,this.dataURLtoBlob(res.imgSrc))//上传缩略图
              }
              return item
            })
          })
        },
        //以下用插件cos-js-sdk-v5上传
        handelUploadFile(fileInfo){
          let fileType = fileInfo.name
          let fileUrl = `zxkt/video/${new Date().getTime()}-${fileInfo.uid}.${fileType}`;
          this.COS.sliceUploadFile(//===上面,向远方发送请求,远方将上传路由放到cookie里。此处,隐式从cookie获取上传路由,用ajax-promise上传“数据”===
            {
              Bucket: "sharepool-1257784922" /* 必须 */,
              Region: "ap-beijing" /* 存储桶所在地域,必须字段 */,
              Key: fileUrl /* 必须 */,
              StorageClass: "STANDARD",
              Body: fileInfo.raw, // 上传文件对象
              onProgress: (progressData) => {
                this.tableData = this.tableData.map((item)=>{
                  if(item.uid === fileInfo.uid){
                    item.uploadStatus = 1
                    item.uploadPercent = Math.round(progressData.percent * 100);
                  }
                  return item
                })
              },
            },
            (err, data) => {
              if (!err) {
                this.COS.getObjectUrl(
                  {
                    // 获取文件访问地址
                    Method: "PUT",
                    Key: fileUrl,
                    Bucket: "sharepool-1257784922",
                    Region: "ap-beijing",
                    Sign: false,
                  },
                  (err, data) => {
                    let res = err || data;
                    this.tableData = this.tableData.map((item)=>{
                      if(item.uid === fileInfo.uid){
                        item.uploadStatus = 2
                        item.url = "https://sharepool-1257784922.cos.ap-beijing.myqcloud.com/" + fileUrl;
                      }
                      return item
                    })
                    //this.fileUrl = res.Url.split("?")[0];
                  }
                );
              } else {
                this.tableData = this.tableData.map((item)=>{
                  if(item.uid === fileInfo.uid){
                    item.uploadStatus = 3
                  }
                  return item
                })
              }
            }
          );
        },
        /* 
        来源,https://cloud.tencent.com/developer/ask/260695
        以下用插件vod-js-sdk-v6上传视频时,
          1、报错
            “Error: ugc upload | signature verify forbidden”,
          2、原因,
            你向后台索要签名,后台用小权限“腾讯账户”向腾讯索要签名,
            腾讯给后台小权限签名,后台给你小权限签名,你将小权限签名发给腾讯,腾讯给你报错
          3、解决办法,
            让后台换用大权限“腾讯账户”
        */
        //以下用插件vod-js-sdk-v6上传(多次获取签名)
        handelUploadFile(fileInfo){
          const getSignature = function(){
            return request({
              url: 'https://media-t.app.gstv.cn/zxkt-platform-server/api/vod/getSign',
              method: 'get',
            }).then(function(response){
              return response.data
            })
          }
          const tcVod = new TcVod.default({
            getSignature: getSignature,
          })
          const uploader = tcVod.upload({//===上面,向远方发送请求,远方将上传路由放到cookie里。此处,隐式从cookie获取上传路由,用ajax-promise上传“数据”===
            mediaFile: fileInfo.raw, 
          })
          uploader.on('media_progress', (info) => {
            this.tableData = this.tableData.map((item)=>{
              if(item.uid === fileInfo.uid){
                item.uploadStatus = 1
                item.uploadPercent = Math.round(info.percent * 100);
              }
              return item
            })
          })
          uploader.done().then((doneResult) => {
            this.tableData = this.tableData.map((item)=>{
              if(item.uid === fileInfo.uid){
                item.url =  doneResult.video.url
                item.fileId =  doneResult.fileId
              }
              return item
            })
          }).catch(function (err) {
            console.log( err );
          })
        },
        //以下用插件vod-js-sdk-v6上传(1次获取签名)
        handelUploadFile(fileInfo){
          request({
            url: 'https://media-t.app.gstv.cn/zxkt-platform-server/api/vod/getSign',
            method: 'get',
          }).then((response) =>{
            const tcVod = new TcVod.default({
              getSignature: function(){
                return response.data
              }
            })
            const uploader = tcVod.upload({
              mediaFile: fileInfo.raw, 
            })
            uploader.on('media_progress', (info) => {
              this.tableData = this.tableData.map((item)=>{
                if(item.uid === fileInfo.uid){
                  item.uploadStatus = 1
                  item.uploadPercent = Math.round(info.percent * 100);
                }
                return item
              })
            })
            uploader.done().then((doneResult) => {
              this.tableData = this.tableData.map((item)=>{
                if(item.uid === fileInfo.uid){
                  item.url =  doneResult.video.url
                  item.fileId =  doneResult.fileId
                }
                return item
              })
            }).catch(function (err) {
              console.log( err );
            })
          })
        },
        handelUploadImg(fileInfo,blob){
          let fileType = fileInfo.name
          let fileUrl = `zxkt/img/${new Date().getTime()}-${fileInfo.uid}.${fileType}.png`;
          this.COS.sliceUploadFile(
            {
              Bucket: "sharepool-1257784922" /* 必须 */,
              Region: "ap-beijing" /* 存储桶所在地域,必须字段 */,
              Key: fileUrl /* 必须 */,
              StorageClass: "STANDARD",
              Body: blob, // 上传文件对象
            },
            (err, data) => {
              if (!err) {
                this.COS.getObjectUrl(
                  {
                    // 获取文件访问地址
                    Method: "PUT",
                    Key: fileUrl,
                    Bucket: "sharepool-1257784922",
                    Region: "ap-beijing",
                    Sign: false,
                  },
                  (err, data) => {
                    let res = err || data;
                    this.tableData = this.tableData.map((item)=>{
                      if(item.uid === fileInfo.uid){
                        item.coverImgUrl = "https://sharepool-1257784922.cos.ap-beijing.myqcloud.com/" + fileUrl;
                      }
                      return item
                    })
                  }
                );
              } 
            }
          );
        },
        //base64Url转bolob
        dataURLtoBlob(dataurl){
          var arr = dataurl.split(',');
          var mime = arr[0].match(/:(.*?);/)[1];
          var bstr = atob(arr[1]);
          var n = bstr.length;
          var u8arr = new Uint8Array(n);
          while (n--) {
              u8arr[n] = bstr.charCodeAt(n);
          }
          return new Blob([u8arr], { type: mime });
        },
        // 以下是改进代码,与原代码等效
        // 获取视频标题+时长+缩略图+宽高
        asyncImgChecked(file) {
          return new Promise((resolve, reject) => {
            URL.createObjectURL(file.raw);
            //let reader = new FileReader();
            //reader.readAsDataURL(file.raw); // 必须用file.raw
            //reader.onload = (e)=> { // 让页面中的img标签的src指向读取的路径
            let video = document.createElement('video');
            let canvas = document.createElement('canvas');//<canvas>是图形容器,没有绘图能力,用户需用脚本绘图
            let ctx = canvas.getContext('2d');//ctx提供了在画布上绘图的方法和属性
            let imgSrc;
            video.src = URL.createObjectURL(file.raw);//=======1/3,将视频数据渲染到video标签
            video.currentTime = 3; //设置视频的当前播放位置(为第3秒),可用于实现“===视频抽帧===”的功能
            video.setAttribute('width', 100);
            video.setAttribute('height', 100);
            canvas.setAttribute("width",100);
            canvas.setAttribute("height",100);
            video.oncanplay = ()=>{//在视频/音频可以播放时触发
              ctx.drawImage(video, 0, 0, video.width, video.height);//=======2/3,将video的第3秒绘制到canvas里
              //下面来源,https://www.runoob.com/tags/canvas-drawimage.html
              //context.drawImage(img,x,y,width,height);在画布上定位图像,并规定图像的宽度和高度
              //img,规定要使用的图像、画布或视频
              //x,在画布上放置图像的x坐标位置
              //y,在画布上放置图像的y坐标位置
              //width,可选,要使用的图像的宽度(伸展或缩小图像)
              //height,可选,要使用的图像的高度(伸展或缩小图像)
              imgSrc = canvas.toDataURL('image/png');//=======3/3,将canvas转化为base64图片
              resolve({
                duration:parseInt(video.duration),//得到整数的视频时长
                imgSrc:imgSrc,//base64的缩略图图片路径
                width:video.videoWidth,
                height:video.videoHeight,
                videoName:file.name
              })
              URL.revokeObjectURL(video.src);//释放一个之前已经存在的、通过调用URL.createObjectURL()创建的URL对象
            }
            //};
          })
        },
        // 以下是原代码
        /* // 获取视频标题+时长+缩略图+宽高
        asyncImgChecked(file) {
          return new Promise((resolve, reject) => {
            URL.createObjectURL(file.raw);
            //let reader = new FileReader();
            //reader.readAsDataURL(file.raw); // 必须用file.raw
            //reader.onload = (e)=> { // 让页面中的img标签的src指向读取的路径
            let video = document.createElement('video');
            video.src = URL.createObjectURL(file.raw);
            video.currentTime = 3; //截取缩略图时的视频时长,一定要设置,不然大概率白屏
            video.oncanplay = ()=>{
              const canvas = document.createElement('canvas');
              const ctx = canvas.getContext('2d');
              canvas.setAttribute("width",100);
              canvas.setAttribute("height",100);
              video.setAttribute('width', 100);
              video.setAttribute('height', 100);
              ctx.drawImage(video, 0, 0, video.width, video.height);
              const imgSrc = canvas.toDataURL('image/png');
              resolve({
                duration:parseInt(video.duration),//得到整数的视频时长
                imgSrc,//base64的缩略图图片路径
                width:video.videoWidth,
                height:video.videoHeight,
                videoName:file.name
              })
              URL.revokeObjectURL(video.src);
            }
            //};
          })
        }, */
        delItem(uid){
          this.tableData = this.tableData.filter((item)=>{
            return item.uid !== uid
          })
        },
        initCOS() {
          getCosImg().then((res) => {
            /* eslint-disable */
            this.COS = new COS({
              getAuthorization: (options, callback) => {
                var credentials = res.data && res.data.credentials;
                if (!res || !credentials)
                  return console.error("credentials invalid");
                callback({
                  TmpSecretId: credentials.tmpSecretId,
                  TmpSecretKey: credentials.tmpSecretKey,
                  XCosSecurityToken: credentials.sessionToken,
                  // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
                  StartTime: res.data.startTime, // 时间戳,单位秒,如:1580000000
                  ExpiredTime: res.data.expiredTime, // 时间戳,单位秒,如:1580000900
                  // ScopeLimit: true, // 细粒度控制权限需要设为 true,会限制密钥只在相同请求时重复使用
                });
              },
            });
          });
        },
        openHadle(url) {
          window.open(url);
        },
        getCollectionList(url) {
          getCollectionList().then((res) => {
            if (res.code === 200) {
              this.collectionList = res.data;
            }
          });
        },
        toContent() {
          this.$router.push("/course/content");
        },
        handleExceed(files, fileList) {
          this.$message.warning(
            `当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
              files.length + fileList.length
            } 个文件`
          );
        },
        submitForm(formName) {
          this.$refs[formName].validate((valid) => {
            if (valid) {
              this.handleSubmit()
            } else {
              console.log("error submit!!");
              return false;
            }
          });
        },
        handleSubmit(){
          if (this.tableData && this.tableData.length > 0) {
            let flag = true
            this.tableData .forEach((ele)=>{
              if (!ele.title) {
                this.$message.error('请填写标题!')
                flag = false
                return
              }
              if (!ele.url) {
                this.$message.error('请上传视频!')
                flag = false
                return
              }
              if (this.ruleForm.collectionId) {
                ele.collectionId = this.ruleForm.collectionId
              }
            })
            if (!flag) {
              return
            }
            createCourse(this.tableData).then((res) => {
              if (res.data > 0 ) {
                this.$message.success('视频上传成功!')
                this.toContent()
              }
            });
          }else{
            this.$message.error('请先上传视频!')
          }
          
        }
      },
    };
  </script>
  <style scoped lang="scss">
  </style>
4、大文件分片上传,带上传进度,切片
(1)webuploader.js,PC端,分片上传,带上传进度
  //在数据上传本地完成后,此插件自动获取、切片数据并根据配置上传至服务器并监听上传过程
  <script src="./common/webuploader.js"></script>
  <style>
    #updataButton input[type="file"] {
      display: none;
    }
  </style>  
  $scope.up_file = function () {
    var files;
    var task_id = WebUploader.Base.guid();
    var uploader = WebUploader.create({
      server: './system/devinfo/upload',
      pick: '#updataButton',//webuploader.js插件给此元素内添加了<input type="file"/>,点击此元素手动出现上传弹窗,点击内部的input直接出现上传弹窗
      auto: false,//需要手动上传
      chunked: true,//需要分片
      chunkSize: 20 * 1024 * 1024,//每片大小
      chunkRetry: 3,//如果某个分片由于网络问题出错,允许自动重传3次
      threads: 1,//允许同时最大上传进程数为1
      duplicate: true,//需要去重
      formData: {//文件上传请求的参数表,每次发送都会发送此对象中的参数
        task_id: task_id,
      },
      headers: {
        business_resource: 156
      },
      fileVal:'rule'//设置文件上传域的name
    });
    uploader.on('fileQueued', function (file) {
      $scope.file_.file = file;
      //交互一:列队完成,告知后台,准备接收数据
      auditApi
        .query({
          method: 'get',
          root: 'set',
          url: '/maintain/verify_file',
          data: {
            filename: file.name
          }
        })
        .then(function () {
          $scope.file_.uploader.upload();//交互二:开始上传到server配置指向的服务器
        })
        .catch(function () {
          $scope.file_.uploader.removeFile($scope.file_.file);
        });
    }).on('fileDequeued', function () {
      //console.log('remove queue success.');
    }).on('startUpload', function () {
      $scope.updata();//无前后台交互,出现弹窗,告知正在上传
    }).on('uploadProgress', function (file, percentage) {
      //上传中,出现进度条,无前后台交互,插件自己计算进度的百分比
    }).on('uploadSuccess', function (file) {
      //交互三、告知后台,数据发送完毕
      auditApi
        .query({
          method: 'get',
          root: 'set',
          url: '/maintain/complete',
          params: {
            task_id: task_id,
            filename: file.source['name']
          },
          timeout: 1000 * 60 * 3
        })
        .then(function () {
          $scope.updating(); //交互四、定时询问后台,是否更新完毕
        })
    }).on('uploadError', function () {
      //上传出错
    });
  }
  $scope.up_file();
  //说明,
  //A、页面初始化时执行$scope.up_file();
  //B、根据pick配置,找到“升级”按钮,并初始化
  //C、点击“升级”后,插件调出window系统自身的弹窗,用户选中文件,点击确定,开始上传至初始化时的服务器,依次执行监听函数
(2)ali-oss,移动端,分片上传,内含上传进度的功能,videoUploadPercent,
  ali-oss,阿里OSS存储,开放存储服务(open storage service,OSS),另见“腾讯”
  附、import OSS from "ali-oss";
  <van-form @submit="beforeSubmit" ref="form" >
    <template v-for="(item,idx) in formList" >
      <div class="sign-form" :key="idx" v-if="item.inputType == 5">
        <div class="van-form_card">
          <div class="field__label">{{item.topicName}}</div>
          <div class="field__desc">支持扩展名:avi、MP4、rmvb</div>
        </div>
        <van-field name="name" type="hidden" :rules="[{ validator: valiFormData, message: '请输入正确内容',index:idx }]"  v-model="formData[idx]"  	>
        <template #input >
          <div class="flex-col">
            <div class="video-upload" v-if="showUpload">
              <span>视频上传中...</span>
              <van-progress :show-pivot="false"	 :percentage="videoUploadPercent" />
            </div>
            <span class="link" v-if="formData[idx]" @click="href(idx)">{{videoName[idx]}}</span>
            <van-uploader accept="*" v-if="!showUpload"  :name="idx"  :before-read="uploadVideo">
              <van-button icon="plus" type="primary">上传视频</van-button>
            </van-uploader>
          </div>
        </template>  
        </van-field>
      </div>
    </template>
    <div class="vt-foot ">
      <van-button native-type="submit"  :disabled="!promise" :loading="loading" loading-text="保存中..."	block color="#fdb235">提交</van-button>
    </div>
  </van-form>
  来源,https://help.aliyun.com/document_detail/383952.html
  //以下是,
  //A、点击上传按钮,选中上传文件,触发选中事件,
  //B、执行下面的函数,获取上传文件,发送上传文件
  "dependencies": {
    "ali-oss": "^6.16.0"
  },
  import OSS from "ali-oss";
  uploadVideo(file,detail){//file,二进制数据
    let type = file.name.substring(file.name.lastIndexOf(".") + 1).toLowerCase()
    if(this.videoType.indexOf(type) < 0 || file.type.indexOf("video") < 0){
      this.$dialog.alert({
        title: '提示',
        message: '仅支持 .avi, .mp4, .rmvb, .mov 格式的视频',
        theme: 'round-button',
      });
      return;
    }
    let max = 1024*1024*1024;
    if(file.size > max){
      this.$dialog.alert({
        title: '提示',
        message: '上传视频大小不能超过 1G!',
        theme: 'round-button',
      }).then(() => {});
      return;
    }
    getALiYunOSSToken({ //交互一:告知后台,准备接收数据
      tokenName: "ios",
    }).then(data=>{ //后台同意后,给出同意参数
      let oss = new OSS({ //前端根据同意参数new出传输实例
        region: "oss-cn-beijing",
        accessKeyId: data.data.data.accessKeyId,
        accessKeySecret: data.data.data.accessKeySecret,
        bucket: data.data.data.bucketName,
        stsToken: data.data.data.securityToken,
      });
      const suffix = file.name.substr(file.name.indexOf("."));
      let fileUrl = `test/${new Date().getTime()}${suffix}`;
      oss.multipartUpload( //交互二:开始上传到fileUrl指向的服务器。不用告知后台,数据发送完毕;不用向后台询问,是否更新完毕
          fileUrl, //文件在服务器的存储位置
          file, //文件自身
          { 
            progress: (p) => { // 配置上传进度
              this.videoUploadPercent = Math.round(p * 100);
              this.showUpload = true;
            },
            partSize: 1024*100, //切片大小
          }
        )
        .then((res) => {
          this.showUpload = false;
          this.videoName[detail.name] = res.name;
          let url = res.res.requestUrls[0];
          this.formData[detail.name] = url.substring(0,url.indexOf("?"));
          let temp = this.formData[detail.name];
          this.formData.splice(detail.name,1,temp);
        })
        .catch((err) => {
          this.showUpload = false;
          console.log(err);
          this.$dialog.alert({
          title: '提示',
          message: '上传失败',
          theme: 'round-button',
        })
      });
    })
  },
  //以下是上传辅助
  const getALiYunOSSToken=(params)=>{
    return axios.request({
      url:"https://mpapi.app.cctv.com/comm/file/getALiYunOSSToken",
      params,
      method: 'get'
    })
  }
  //以下是后提交 
  beforeSubmit(){
    if(this.loading){
      return;
    }
    this.loading = true;
    this.onSubmit();
  },
  onSubmit(){     
    this.$ajaxPost(url,data).then(res=>{})
  },
(3)cos-js-sdk-v5,PC端,分片上传,带上传进度
  说明,用cos-js-sdk-v5封装uploadClient
  //A、cctv-chose定义
  <template>
    <el-upload
      action=""
      :auto-upload="false"
      :accept="accept"
      :show-file-list="false"
      :on-change="upload"
      :http-request="up"
      ref="upload"
    >
      <div class="cctv-chose">
        <span class="cctv-chose-add">+</span>
        <div class="cctv-chose-footer" v-if="false">
          <svg-icon icon-class="upload"  class-name="cctv-chose-upload" />
          <i></i>
          <svg-icon icon-class="su" @click="selectSource" class-name="cctv-chose-su" />
        </div>
      </div>
    </el-upload>
  </template>
  import uploadClient from '@/utils/upload';
  methods: {
    upload(fileInfo) {
      uploadClient.upload(
        {
          raw: fileInfo.raw,
          fileUrl: `${this.baseUrl}${new Date().getTime()}.${fileType}`
        },
        (progressData) => {
          let percent = Math.round(progressData.percent * 100)
          this.$emit('pro',progressData)
          this.$emit('progress',percent)//告诉父级上传进度
        }
      ).then((res) => {
        this.$emit('backUrl', res.Url)
      })
    },
  },
  //B、cctv-chose调用,上传多张图片,带列表展示
  <el-form-item
    label="上传图片"
    prop="image"
    class="dynamic-form-image"
  >
    <div class="dynamic-warp">
      <div class="dynamic-img"  v-for="(item,index) in dynamicForm.images" :key="index">
        <img :src="item|parseUrl">
        <i class="el-icon-error" @click="remove(index)" />
      </div>
      <CctvChose :limit="5" 
        v-if="dynamicForm.images && dynamicForm.images.length<15" 
        @backUrl="catchUrl"  
        class="dynamic-chose" 
        @selectSource="showDynamic" 
      />
    </div>
    <div class="dynamic-tip">图片格式bmp、png、webp、jpeg,图片大小5MB以内</div>
  </el-form-item>
  //C、cctv-chose调用,上传单个视频,带进度条
  <div class="produce-form-header">
    <div v-show="!fileInfo">
      <CctvChose
        class="produce-chose"
        @selectFile="selectFile"
        @selectSource="selectSource"
        :limit="800"
        @backUrl="setUrl"
        @progress="changePercent"
        @pro="getProgressData"
        :type="1"
      ></CctvChose>
      <div class="produce-tip">请添加文件上传 支持MP4、MOV格式</div>
    </div>
    <div class="produce-form-info" v-if="fileInfo">
      <div class="produce-form-info-title">
        {{ fileInfo.name }}
      </div>
      <div class="produce-form-delete" v-if="percent==100">
        <el-button type="text" @click="reUpload">删除</el-button>
      </div>
    </div>
    <div v-if="fileInfo">
      <el-progress //使用上传进度
        class="produce-form-progress-bar"
        :percentage="percent"
        :show-text="false"
        :stroke-width="26"
      />
    </div>
  </div>
  methods: {
    changePercent(per) {
      this.percent = per;//父级接收上传进度
      if(this.percent==100){
        this.loading = false;
        this.$refs.audioForm.form.videoTitle = this.fileInfo.name.split('.')[0].slice(0,40);
      }else {
        this.loading = true
      }
    },
  },

五、数据下载
1、a元素下载
(1)将后台返回的JSON数据导出
  function down(data) {
    var a = document.createElement('a');
    //document.body.appendChild(a);
    a.href = URL.createObjectURL(data);
    a.download = '111.txt';
    a.click();
    //a.remove();
  };
(2)将后台返回的Blob数据导出
  function down(data) {
    var blob = new Blob(data)
    var a = document.createElement('a')
    a.href = window.URL.createObjectURL(blob)
    a.download = 'file.docx'
    a.click()
  };
(3)将后台返回的url文件导出
  示例1
  function down(url) {
    var a = document.createElement('a');
    //document.body.appendChild(a);
    a.href = url;
    a.download = '111.pdf';
    a.click();
    //a.remove();
  };
  示例2
  <a href = "https://cdn.shopify.com/s/Manaul.pdf" download = "test.pdf"></a>
2、file-saver下载,它是在浏览器中直接保存文件的包
  import { saveAs } from 'file-saver'
  function down(data) {
    saveAs(data, 'file.docx')
  };
3、Excel下载,将后台返回的JSON数据用excel导出
  <html>
    <head>
      <script type="text/javascript" src="https://cdn.bootcss.com/xlsx/0.12.7/xlsx.core.min.js"></script>
      <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/alasql/0.0.50/alasql.min.js"></script>
    </head>
    <body>
      <button onclick="down()">下载</button>
    </body>
  </html>
  <script>
    function down(){
      var tableTh=["times [时间]", "user [用户]", "ip [IP地址]", "result [登录结果]"];
      var title="文件名"
      var dataArray=[
        {
          times: "2003-03-04",//0
          user: "张三",//1
          ip: "216.94.168.85",//2
          result: 0,//3
          ServiceType: "yhzuu",
          id: 1,
          operation: "刷新页面",
        },{
          times: "2003-03-05",//0
          user: "李四",//1
          ip: "216.94.168.85",//2
          result: 1,//3
          ServiceType: "yhzuu",
          id: 2,
          operation: "刷新页面", 
        }
      ];
      dataArray.forEach(function(item,index){
        if(item.result===0){
          item.result="失败"
        }else if(item.result===1){
          item.result="成功"
        }
      });
      alasql('SELECT '+ tableTh+ ' INTO XLSX("'+ title+ '.xlsx",{headers:true}) FROM ?', [dataArray]);
      //上面这行,字符串'SELECT '的后面和' INTO XLSX("'的前面,都必须有空格,没有空格会导致excel表格下载失败
    }
  </script>

六、数据展示
  附1、3个媒体元素
    (1)标签
      A、audio
        <audio controls>
          <source src="horse.ogg" type="audio/ogg">
          <source src="horse.mp3" type="audio/mpeg">
          您的浏览器不支持 audio 元素。
        </audio>
      B、picture
        <picture>
          <source media="(min-width:650px)" srcset="img_pink_flowers.jpg">
          <source media="(min-width:465px)" srcset="img_white_flower.jpg">
          <img src="img_orange_flowers.jpg" alt="Flowers" style="width:auto;">
        </picture>
      C、video
        <video width="320" height="240" controls>
          <source src="movie.mp4" type="video/mp4">
          <source src="movie.ogg" type="video/ogg">
          您的浏览器不支持 video 标签。
        </video>
    (2)audio和video的标签的共有属性
      A、autoplay,autoplay,在就绪后马上播放
      B、controls,controls,向用户显示控件(比如播放/暂停按钮)
      C、loop,loop,每当播放结束时重新开始播放
      D、muted,muted,输出为静音
      E、preload,auto/metadata/none,是否默认被加载以及如何被加载
      F、src,URL,规定文件的URL
    (3)video的标签专有属性
      A、width,pixels,设置视频播放器的宽度
      B、height,pixels,设置视频播放器的高度
      C、poster,URL,规定视频正在下载时显示的图像,直到用户点击播放按钮
    (4)source标签,为媒体元素指定多个媒体资源
      来源,https://www.w3school.com.cn/tags/tag_source.asp
      来源,https://www.w3school.com.cn/tags/att_source_srcset.asp
      A、src,用于指定媒体文件的URL
      B、srcset,用于指定在不同情况下使用的图像的URL,<source srcset="https://static.noob-logo.png"></source>
      C、media,媒体查询,<source media="(min-width:650px)"></source>
      D、type,规定资源的MIME类型,<source type="video/mp4"></source>
        MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型
        指定浏览器用哪个应用程序打开文件
  附2、videojs插件
      来源,https://videojs.com
      来源,https://cloud.tencent.com/developer/article/1615717
    (1)简介
      Video.js是一款web视频播放器,支持html5和flash两种播放方式
    (2)依赖
      A、<link href="//vjs.zencdn.net/7.3.0/video-js.min.css" rel="stylesheet"></link>
      B、<script src="//vjs.zencdn.net/7.3.0/video.min.js"></script>
    (3)配置
      var player = videojs(document.getElementById('myVideo'), {
        controls: true, // 是否显示控制条
        poster: 'xxx', // 视频封面图地址
        preload: 'auto',
        autoplay: false,
        fluid: true, // 自适应宽高
        language: 'zh-CN', // 设置语言
        muted: false, // 是否静音
        inactivityTimeout: false,
        controlBar: { // 设置控制条组件
          playToggle:true, //播放暂停按钮
          volumeMenuButton:true, //音量控制
          currentTimeDisplay:true, //当前播放时间
          timeDivider:true, // '/' 分隔符
          durationDisplay:true, //总时间
          progressControl:true, //点播流时,播放进度条,seek控制
          liveDisplay:true, //直播流时,显示LIVE
          remainingTimeDisplay:true, //当前播放时间
          playbackRateMenuButton:true, //播放速率,当前只有html5模式下才支持设置播放速率
          fullscreenToggle:true, //全屏控制
          /* 使用children的形式可以控制每一个控件的位置,以及显示与否 */
          children: [
            {name: 'playToggle'}, // 播放按钮
            {name: 'currentTimeDisplay'}, // 当前已播放时间
            {name: 'progressControl'}, // 播放进度条
            {name: 'durationDisplay'}, // 总时间
            { // 倍数播放
              name: 'playbackRateMenuButton',
              'playbackRates': [0.5, 1, 1.5, 2, 2.5]
            },
            {
              name: 'volumePanel', // 音量控制
              inline: false, // 不使用水平方式
            },
            {name: 'FullscreenToggle'} // 全屏
          ]
        },
        sources: [ // 视频源
          {
            src: '//vjs.zencdn.net/v/oceans.mp4',
            type: 'video/mp4',
            poster: '//vjs.zencdn.net/v/oceans.png'
          }
        ]
      }, function (){
        console.log('视频可以播放了',this);
      });
      someButton.onclick=function(){
        var data = {
          src: 'xxx.mp4',
          type: 'video/mp4'
        };
        player.pause();
        player.src(data);
        player.load(data);//重新加载src指定的资源
        player.posterImage.setSrc('xxx.jpg');
        player.play();
        player.dispose();
      }
1、视频展示(videojs插件使用实例)
(1)弹窗内播放视频
  import { MessageBox } from 'element-ui';
  export default function player(option){
    console.log(MessageBox)
    MessageBox.alert('<video id="video" width="600" controls  height="337" autoplay muted src="'+option.url+'" ></video>', option.title,{
      dangerouslyUseHTMLString: true,
      showConfirmButton:false,
      customClass:'video-warp',
      beforeClose:()=>{
        let video =  document.getElementById('video')
        video.pause()
        video.load();
        MessageBox.close()
        }
    })
  }
(2)页面内视频播放  
  A、依赖
    "dependencies": {
      "video.js": "^7.20.3",
    },
  B、组件调用
    <player ref="playerRef" :path="data.pageInfo.url" :poster="data.pageInfo.coverImgUrl" @ended="endedHadler" @play="playHadler" @timeupdate="timeupdateHadler"></player>
    <script setup>
      import player from "@/components/player/index.vue";
      import { createWebSocket, closeWebSocket, sendSock, getSock } from '@/utils/socket';
      let getPageInfo = (courseId, isInit = true) => { //isInit是否是初始化页面,初始化需要查询课程合集
        getCourse({ courseId, userId }).then(res => {
          data.pageInfo = res.data;
          collectionId = res.data.collectionId;
          nextTick(() => {
            thumbsUpAni.value = new ThumbsUpAni([zanIcon]);
          })
          if (isInit) {
            getCourseCollection({ collectionId, userId }).then(response => {
              data.listInfo = response.data;
            })
          }
          var url = '/zxkt_course_api_link?userId=' + userId + '&collectionId=' + collectionId + '&courseId=' + courseId;
          createWebSocket(url);
        })
      }
      getPageInfo(data.courseId);
      onBeforeUnmount(() => {
        if (getSock()) {
          closeWebSocket();
        }
      })
      const endedHadler = (currentTime) => {
        let params = {
          "code": "20001", //固定值
          "data": {
            "collectionId": collectionId,//课程集id
            "courseId": data.courseId,//课程id
            "viewDuration": currentTime //当前视频的播放进度(秒)
          }
        }
        sendSock(params, (res) => {}, data.courseId)
      }
      const timeupdateHadler = (currentTime) => {
        if (strategy(currentTime)) {
          let params = {
            "code": "20001", //固定值
            "data": {
              "collectionId": collectionId,//课程集id
              "courseId": data.courseId,//课程id
              "viewDuration": currentTime //当前视频的播放进度(秒)
            }
          }
          sendSock(params, (res) => {}, data.courseId)
        }
      }
    </script>
    附、'@/utils/socket'(完整)
      var websock = null;
      let rec; //断线重连后,延迟5秒重新创建WebSocket连接  rec用来存储延迟请求的代码
      let isConnect = false; //连接标识 避免重复连接
      var globalCallback = new Map();
      let createWebSocket = (paramsUrl) => {
        try {
          initWebSocket(paramsUrl); //初始化websocket连接
        } catch (e) {
          //console.log("尝试创建连接失败");
          reConnect(); //如果无法连接上webSocket 那么重新连接!可能会因为服务器重新部署,或者短暂断网等导致无法创建连接
        }
      };
      //定义重连函数
      let reConnect = () => {
        //console.log("尝试重新连接");
        if (isConnect) return; //如果已经连上就不在重连了
        rec && clearTimeout(rec);
        rec = setTimeout(function () { // 延迟5秒重连  避免过多次过频繁请求重连
          createWebSocket();
        }, 5000);
      };
      //设置关闭连接
      let closeWebSocket = () => {
        websock.close();
      };
      // 初始化websocket
      function initWebSocket(paramsUrl) {
        // ws地址
        const wsUri = import.meta.env.VITE_APP_SOKET_API+paramsUrl;
        websock = new WebSocket(wsUri)
        websock.onmessage = function (e) {
          websocketonmessage(e)
        }
        websock.onopen = function () {
          isConnect = true;
          //console.log('连接成功');
        }
        // 连接发生错误的回调方法
        websock.onerror = function () {
          //console.log('WebSocket连接发生错误')
          isConnect = false; //连接断开修改标识
          reConnect(); //连接错误 需要重连
        }
      }
      // 实际调用的方法
      function sendSock(agentData, callback, key) {
        if (!websock){
          initWebSocket()
        }
        globalCallback.set(key,callback)
        if (websock.readyState === websock.OPEN) {
          // 若是ws开启状态
          websocketsend(agentData,callback,key)
        } else if (websock.readyState === websock.CONNECTING) {
          // 若是 正在开启状态,则等待1s后重新调用
          setTimeout(function () {
            sendSock(agentData, callback,key)
          }, 2000)
        } else {
          // 若未开启 ,则等待1s后重新调用
          setTimeout(function () {
            sendSock(agentData, callback,key)
          }, 2000)
        }
      }
      function getSock(key,callback) {
        globalCallback.set(key,callback)
      }
      // 数据接收
      function websocketonmessage(e) {
        //console.log(e)
        let ret = e.data
        if (ret.msg === "websocket connect success") {} else {
          if (ret.method === "webSocket_device_transport"){
            const callback = globalCallback.get(ret.sn)
            if (callback && typeof callback === "function")
            callback(ret);
          }else if(ret.method === "webSocket_device_alarm"){ 
            const callback = globalCallback.get('deviceAlert')
            if (callback && typeof callback === "function")
            callback(ret);
          }
        }
        // globalCallback(JSON.parse(e.data))
        function decodeUnicode(str) {
          str = str.replace(/\\/g, "%");
          //转换中文
          str = unescape(str);
          //将其他受影响的转换回原来
          str = str.replace(/%/g, "\\");
          //对网址的链接进行处理
          str = str.replace(/\\/g, "");
          return str;
        }
      }
      // 数据发送
      function websocketsend(agentData) {
        websock.send(JSON.stringify(agentData))
      }
      // 关闭
      function websocketclose(e) {
        isConnect = false ; //断开后修改标识
      }
      // 创建 websocket 连接
      function websocketOpen(e) {
        isConnect = true
      }
      //登录成功后再建立连接
      //initWebSocket()
      // 将方法暴露出去
      export {
        sendSock,
        getSock,
        createWebSocket,
        closeWebSocket,
        initWebSocket
      }
  C、组件定义 //两点最重要,其一配置,其二事件监听
    <template>
      <div class="videoPlayer">
        <video ref="videoRef" class="video-js vjs-default-skin vjs-big-play-centered" controls>
          <source :src="path" />
        </video>
      </div>
    </template>
    <script setup>
      import { nextTick, onBeforeUnmount, onMounted, ref, watch,defineExpose } from 'vue';
      import videojs from 'video.js'
      import 'video.js/dist/video-js.css'
      import zh from "video.js/dist/lang/zh-CN.json"
      const props = defineProps({
        path: {
          type: String,
          required: true,
          default: '',
        },
        autoplay: {
          type: Boolean,
          default: false,
        },
        poster:{
          type: String,
          default: '',
        },
        w: {
          type: Number,
          default: 1200,
        },
        h: {
          type: Number,
          default: 675,
        }
      })
      const videoRef = ref()
      let player = null;
      let videoPoster = "";
      const emit = defineEmits(['ended','error','timeupdate','seeking'])
      const initPlayer = async () => {
        videojs.addLanguage('zh-CN',zh)
        await nextTick()
        const options = {
          width:props.w,
          height:props.h,
          poster:props.poster,
          autoplay: props.autoplay,
          playbackRates: [0.5, 1, 1.5, 2],
          language: 'zh-CN',
          preload: 'auto', // 预加载
          notSupportedMessage: 'Ajiang此视频暂无法播放,请稍后再试',
          controlBar: {
            children: [ //自定义controlBar内容显示顺序
              { name: 'playToggle' },
              { name: 'progressControl' },
              { name: 'remainingTimeDisplay', displayNegative:false }, //默认情况下,剩余时间显示为负时间。不显示负号 controlBar.remainingTimeDisplay.displayNegative 设置为 false。
              { name: 'playbackRateMenuButton', playbackRates: [0.5, 1, 1.5, 2] },
              { name: 'volumePanel',inline: false},
              { name: 'pictureInPictureToggle' },
              // {name: 'AutoPlayNext'},     //控制是否自动播放下一个
              { name: 'fullscreenToggle' }
            ],
          }
        };
        player = videojs(videoRef.value, options, () => {
          videojs.log('播放器已经准备好了!');
          if(props.poster){
            videoPoster = props.poster;
          }else{
            videoPoster = getPoster();
          }
          if (props.autoPlay && props.path) {
            player.play()
          }
          player.on('play', () => {
            emit('play')
            videojs.log('开始播放了!');
          });
          player.on('ended', () => {
            emit('ended',parseInt(player.currentTime()))
            videojs.log('播放结束了!');
          });
          player.on('error', () => {
            emit('error')
            videojs.log('播放器解析出错!');
          })
          player.on('seeking', () => {
            emit('seeking')
            // videojs.log('每次当视频跳到一个新的时间都会触发!');
          })
          player.on('timeupdate', () => {
            emit('timeupdate',parseInt(player.currentTime()))
            // videojs.log('当当前的播放位置改变的时候触发!');
          })
        });
      }
      const getPoster = ()=>{
        videoRef.value.currentTime = 1;
        // 创建canvas元素
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d')
        // canvas画图
        canvas.width = props.w
        canvas.height = props.h
        ctx?.drawImage(videoRef.value, 0, 0, canvas.width, canvas.height)
        // 把canvas转成base64编码格式
        return canvas.toDataURL('image/png')
      }
        //方法
      const operation = (param)=> {
        console.log(param)
        if ("play" === param) {	//开始播放
          player.play();
        } else if ("stop" === param) {	//停止播放
          player.pause();
        } else if ("fastForward" === param) { //快进
          let whereYouAt = player.currentTime();
          player.currentTime(10 + whereYouAt);
        } else if ("reload" === param) {	//重新加载
          player.pause();
          player.load();
          player.play();
        } else if ("back" === param) {	//后退
          let whereYouAt = player.currentTime();
          player.currentTime(whereYouAt - 10);
        } else if ("fullScreen" === param) {	//全屏
          player.requestFullscreen();
        } else if ("exitFullScreen" === param) {	//退出全屏
          player.exitFullscreen();
        } else if ("volumeUp" === param) {	//音量加
          let howLoudIsIt = player.volume();
          player.volume(howLoudIsIt + 10);
        } else if ("volumeDown" === param) {	//音量减
          let howLoudIsIt = player.volume();
          console.log(howLoudIsIt)
          player.volume(howLoudIsIt - 10);
        } else if ("reset" === param) {	//重置,视频不会播放	
          player.reset();
        }
      }
      onMounted(() => {
        initPlayer()
      })
      watch(() => props.path, () => {
        player.pause()
        player.src(props.path)
        player.load()
        if (props.path) {
            player.play()
        }
      })
      onBeforeUnmount(() => {
        player?.dispose()
      })
      defineExpose({
        operation
      })
    </script>
    <style lang="scss">
    </style>
2、PDF展示
(1)jquery-media.js
  A、类库:jquery.js、jquery-media.js
  B、前端用Jinja2语法把总变量分散插进HTML里,交给后台
  C、后台给总变量赋值,然后把HTML渲染成PDF,
  D、CSS分页:此前分页page-break-before: always;此后分页page-break-after: always;
    $('#pdf').media({
      width: '100%',
      height: '680px',
      autoplay: true,
      src: '/flow_audit/media?file_name=myNAme'
    });
(2)vue3-pdf-app(用于ai-web项目,素材库-文章编辑,媒体大模型)
  A、依赖
    "dependencies": {
      "vue3-pdf-app": "^1.0.3",
    },
  B、组件调用
   <pdfView :fileVisible="showPDFView" :item="modelFile" @close="hidePDFVisible"></pdfView>
  C、组件定义
    <script setup>
      import VuePdfApp from "vue3-pdf-app";
      import "vue3-pdf-app/dist/icons/main.css";
      import { onBeforeUnmount, ref, shallowRef, onMounted, watch } from 'vue'
      const props = defineProps({
        fileVisible: {
          type: Boolean,
          default: false,
        },
        item:{
          type: Object,
          default:()=>{
          }
        }
      });
      const show = ref(false)
      const item = ref(props.item)
      const config = ref({
        toolbar: {
          toolbarViewerRight:false,
          toolbarViewerLeft:{
            findbar:false
          },
          secondaryToolbarToggle:false
        }
      });
      const heightValue = ref('calc(100vh - 200px)');
      watch(props,(now,last) => {
        show.value = now.fileVisible
        item.value = now.item
        console.log(item.value.url)
      })
      const emit = defineEmits(['close'])
      const close = ()=>{
        emit('close')
      }
    </script>
    <template>
      <el-dialog v-model="show" top="5vh" title="文档预览" width="992px" @close="close">
        <div class="article-edit-content" style="overflow-y: scroll" :style="{ height: heightValue }">
          <vue-pdf-app :config="config" :pdf="item.url"></vue-pdf-app>
        </div>
      </el-dialog>
    </template>
    <style lang="scss">
    </style>    
3、docx文档展示
(1)用node包实现
  A、依赖
    "dependencies": {
      "docx-preview": "^0.1.20",
    }, 
  B、组件调用 
    <docView :fileVisible="showDOCView" :item="modelFile" @close="hideDOCVisible"></docView>
  C、组件定义
    <script setup>
      import { renderAsync } from "docx-preview"
      import { onBeforeUnmount,defineEmits, ref, shallowRef, onMounted, watch ,defineProps} from 'vue'
      const props = defineProps({
        fileVisible: {
          type: Boolean,
          default: false,
        },
        item:{
          type: Object,
          default:()=>{
          }
        }
      });
      const show = ref(false)
      const item = ref(props.item)
      const loading = ref(false)
      const heightValue = ref('calc(100vh - 200px)');
      watch(props,(now,last) => {
        show.value = now.fileVisible
        item.value = now.item
        previewfile()
      })
      const emit = defineEmits(['close'])
      const close = ()=>{
        emit('close')
      }
      const previewfile = () => {
        loading.value = true;
        fetch(item.value.url)
          .then((response) => {
            let docData = response.blob();
            let docxDiv= document.getElementsByClassName("docxDiv");
            renderAsync(docData, docxDiv[0], null, {
              inWrapper: false, // 启用围绕文档内容渲染包装器
              ignoreWidth: false, // 禁止页面渲染宽度
              ignoreHeight: false, // 禁止页面渲染高度
              ignoreFonts: false, // 禁止字体渲染
              breakPages: true, // 在分页符上启用分页
              ignoreLastRenderedPageBreak: true, //禁用lastRenderedPageBreak元素的分页
              experimental: false, //启用实验性功能(制表符停止计算)
              trimXmlDeclaration: true, //如果为真,xml声明将在解析之前从xml文档中删除
              debug: false,
            }).then((res) => {
              loading.value = false;
            });
          })
          .catch((error) => {
            console.log(error);
          });
      };
    </script>
    <template>
      <el-dialog v-model="show" top="5vh" title="文档预览" width="992px" @close="close">
        <div  :style="{ height: heightValue }"
        ref="docxDiv"
        class="docxDiv article-edit-content"
        v-loading="loading"
        ></div>
    
      </el-dialog>
    </template>
    <style lang="scss">
    </style>
4、json展示
(1)用node包实现
  A、依赖
    "dependencies": {
      "vue-json-viewer": "^2.2.19",
    }, 
  B、组件调用 
    import JsonViewer from "vue-json-viewer";
    <json-viewer
      :value="JSON.parse(logInfo.responseParam)"
      :expand-depth="5"
      :copyable="{
        copyText: '复制',
        copiedText: '复制成功',
        timeout: 2000
      }"
      boxed
      sort
    ></json-viewer>
(2)jQuery实现
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8">
      <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.2.1/jquery.js"></script>
      <style > 
        .json-document {
          padding: 1em 2em;
        }
        ul.json-dict, ol.json-array {
          list-style-type: none;
          margin: 0 0 0 1px;
          border-left: 1px dotted #ccc;
          padding-left: 2em;
        }
        .json-string {
          color: #0B7500;
        }
        .json-literal {
          color: #1A01CC;
          font-weight: bold;
        }
        a.json-toggle {
          position: relative;
          color: inherit;
          text-decoration: none;
        }
        a.json-toggle:focus {
          outline: none;
        }
        a.json-toggle:before {
          font-size: 1.1em;
          color: #c0c0c0;
          content: "\25BC";/* https://unicode-table.com/cn/ */ 
          position: absolute;
          display: inline-block;
          width: 1em;
          text-align: center;
          line-height: 1em;
          left: -1.2em;
        }
        a.json-toggle:hover:before {
          color: #aaa;
        }
        a.json-toggle.collapsed:before {
          transform: rotate(-90deg);
        }
        a.json-placeholder {
          color: #aaa;
          padding: 0 1em;
          text-decoration: none;
        }
        a.json-placeholder:hover {
          text-decoration: underline;
        }
      </style>
    </head>
    <body>
      <div id="aaa"></div>
    </body>
  </html>
  <script>
    (function ($) {
      function isCollapsable(arg) {
        return arg instanceof Object && Object.keys(arg).length > 0;
      }
      function isUrl(string) {
        var urlRegexp = /^(https?:\/\/|ftps?:\/\/)?([a-z0-9%-]+\.){1,}([a-z0-9-]+)?(:(\d{1,5}))?(\/([a-z0-9\-._~:/?#[\]@!$&'()*+,;=%]+)?)?$/i;
        return urlRegexp.test(string);
      }
      function json2html(json, options) {
        var html = '';
        if (typeof json === 'string') {
          json = json
            .replace(/&/g, '&')
            .replace(/</g, '<')
            .replace(/>/g, '>')
            .replace(/'/g, '')
            .replace(/"/g, '"');
    
          if (options.withLinks && isUrl(json)) {
            html +=
              '<a href="' +
              json +
              '" class="json-string" target="_blank">' +
              json +
              '</a>';
          } else {
            if (json.indexOf('RED_SELF') > -1) {
              json = json.replace('RED_SELF', '');
              html +=
                '<span class="json-literal" style="color:red">' + json + '</span>';
            } else if (json.indexOf('RED_parent') > -1) {
              json = json.replace('RED_parent', '');
              html += '<span class="json-literal RED_parent">' + json + '</span>';
            } else {
              json = json.replace(/"/g, '\\"');
              html += '<span class="json-string">"' + json + '"</span>';
            }
          }
        } else if (typeof json === 'number') {
          html += '<span class="json-literal">' + json + '</span>';
        } else if (typeof json === 'boolean') {
          html += '<span class="json-literal">' + json + '</span>';
        } else if (json === null) {
          html += '<span class="json-literal">null</span>';
        } else if (json instanceof Array) {
          if (json.length > 0) {
            html += '[<ol class="json-array">';
            for (var i = 0; i < json.length; ++i) {
              html += '<li>';
              if (isCollapsable(json[i])) {
                html += '<a href class="json-toggle"></a>';
              }
              html += json2html(json[i], options);
              if (i < json.length - 1) {
                html += ',';
              }
              html += '</li>';
            }
            html += '</ol>]';
          } else {
            html += '[]';
          }
        } else if (typeof json === 'object') {
          var keyCount = Object.keys(json).length;
          if (keyCount > 0) {
            html += '{<ul class="json-dict">';
            for (var key in json) {
              if (Object.prototype.hasOwnProperty.call(json, key)) {
                html += '<li>';
                var keyRepr = options.withQuotes
                  ? '<span class="json-string">"' + key + '"</span>'
                  : key;
                if (isCollapsable(json[key])) {
                  html += '<a href class="json-toggle">' + keyRepr + '</a>';
                } else {
                  html += keyRepr;
                }
                html += ': ' + json2html(json[key], options);
                if (--keyCount > 0) {
                  html += ',';
                }
                html += '</li>';
              }
            }
            html += '</ul>}';
          } else {
            html += '{}';
          }
        }
        return html;
      }
      $.fn.jsonViewer = function (json, options) {
        options = Object.assign(
          {},
          {
            collapsed: false,
            rootCollapsable: true,
            withQuotes: false,
            withLinks: true
          },
          options
        );
        return this.each(function () {
          var html = json2html(json, options);
          if (options.rootCollapsable && isCollapsable(json)) {
            html = '<a href class="json-toggle"></a>' + html;
          }
          $(this).html(html);
          $(this).addClass('json-document');
          $(this).off('click');
          $(this).on('click', 'a.json-toggle', function () {
            var target = $(this)
              .toggleClass('collapsed')
              .siblings('ul.json-dict, ol.json-array');
            target.toggle();
            if (target.is(':visible')) {
              target.siblings('.json-placeholder').remove();
            } else {
              var count = target.children('li').length;
              var placeholder = count + (count > 1 ? ' items' : ' item');
              target.after(
                '<a href class="json-placeholder">' + placeholder + '</a>'
              );
            }
            return false;
          });
          $(this).on('click', 'a.json-placeholder', function () {
            $(this).siblings('a.json-toggle').click();
            return false;
          });
    
          if (options.collapsed == true) {
            $(this).find('a.json-toggle').click();
          }
        });
      };
    })(jQuery);
    var obj={
      "id": 1001,
      "type": "donut",
      "name": "Cake",
      "description": "https://en.wikipedia.org/wiki/Doughnut",
      "price": 2.55,
      "available": {
        "store": 42,
        "warehouse": 600
      },
      "toppings": [
        { "id": 5001, "type": "None" },
        { "id": 5002, "type": "Glazed" },
        { "id": 5005, "type": "Sugar" },
        { "id": 5003, "type": "Chocolate" },
        { "id": 5004, "type": "Maple" }
      ],
      "uuids": [
        "826b23ce-2669-4122-981f-3e2e4429159d",
        "e32111a0-6a87-49ab-b58f-a01bf8d28ba0",
        "c055a894-698e-41c0-b85f-7510a7351d9d",
      ],
    };
    $('#aaa').jsonViewer(obj);
  </script> 

七、文档编辑 
1、editormd           
(1)下载依赖包editormd并导入 git clone https://github.com/pandao/editor.md.git    
  <script src="./common/editor.md/editormd.min.js"></script>        
(2)如果编辑后台返回的数据,那么向后台发送请求,获取数据serverData
(3)相关代码
  html代码
  <div id="editormd" style="height: 580px">
    <textarea style="display: none"></textarea>
  </div>
  js代码,在请求成功后,执行下列代码
  that.testEditor = editormd("editormd", {
    width: "100%",
    height: "720",
    watch: false, //关闭分栏
    toolbar: false, //关闭工具栏
    value: serverData||'', //字符串
    path: "./common/editor.md/lib/",//依赖存放路径
  });
  setTimeout(function () {
    var value = that.testEditor.getValue();
    //这是前同事的代码,获取编辑前的数据,方便与编辑后的数据作比对,我以为没必要这样写
    //编辑前的数据就是字符串serverData,在定时器外存储一下即可,如var value = serverData
  }, 500);
(4)详细使用教程,百度“editor.md使用教程”
2、vue-ueditor-wrap(用于open-cctv项目,合集-文章编辑)
(1)依赖
  "dependencies": {
    "vue-ueditor-wrap": "^2.5.6",
  },
(2)使用组件
  <el-form-item label="文章内容" prop="region" class="article-form-editor">
    <vue-ueditor-wrap v-model="html" :config="myConfig" @ready="ueditorReady"></vue-ueditor-wrap>
  </el-form-item>
(3)配置组件
  import VueUeditorWrap from "vue-ueditor-wrap";
  export default {
    name: 'Article',
    components: {
      VueUeditorWrap,
    }
    data() {
      return {
        html: '',
        myConfig: {
          //按钮
          toolbars: [
            [
              'source', //源代码
              //'anchor', //锚点
              'undo', //撤销
              'redo', //重做
              'bold', //加粗
              'indent', //首行缩进
              'italic', //斜体
              'underline', //下划线
              'strikethrough', //删除线
              'superscript', //上标
              'subscript', //下标
              'fontborder', //字符边框
              'formatmatch', //格式刷
              'blockquote', //引用
              'pasteplain', //纯文本粘贴模式
              'selectall', //全选
              'removeformat', //清除格式
              'time', //时间
              'date', //日期
              'unlink', //取消链接
              'link', //超链接
              'cleardoc', //清空文档
              'touppercase', //字母大写
              'tolowercase', //字母小写
              'fontfamily', //字体
              'fontsize', //字号
              'paragraph', //段落格式
              'searchreplace', //查询替换
              'justifyleft', //居左对齐
              'justifyright', //居右对齐
              'justifycenter', //居中对齐
              'justifyjustify', //两端对齐
              'forecolor', //字体颜色
              'directionalityltr', //从左向右输入
              'directionalityrtl', //从右向左输入
              'rowspacingtop', //段前距
              'rowspacingbottom', //段后距
              'lineheight', //行间距
              'horizontal', //分隔线
              'clears', //分隔线
              'simpleupload', //单图上传
              //'backcolor', //背景色
              //'customstyle', //自定义标题
              //'pagebreak', //分页
              //'imageleft', //左浮动
              //'imageright', //右浮动
              //'imagecenter', //居中
              //'insertrow', //前插入行
              //'insertcol', //前插入列
              //'deleterow', //删除行
              //'deletecol', //删除列
              //'splittorows', //拆分成行
              //'splittocols', //拆分成列
              //'edittip ', //编辑提示
              //'imagenone', //默认
              //'insertcode', //代码语言
              //'snapscreen', //截图
              //'print', //打印
              //'preview', //预览
              //'mergeright', //右合并单元格
              //'mergedown', //下合并单元格
              //'splittocells', //完全拆分单元格
              //'deletecaption', //删除表格标题
              //'inserttitle', //插入标题
              //'insertparagraphbeforetable', //"表格前插入行"
              //'mergecells', //合并多个单元格
              //'deletetable', //删除表格
              'generateImage', //多图上传
              //'edittable', //表格属性
              //'edittd', //单元格属性
              //'emotion', //表情
              //'spechars', //特殊字符
              //'map', //Baidu地图
              //'gmap', //Google地图
              //'insertvideo', //视频
              //'help', //帮助
              //'insertorderedlist', //有序列表
              //'insertunorderedlist', //无序列表
              //'fullscreen', //全屏
              //'insertframe', //插入Iframe
              //'attachment', //附件
              //'wordimage', //图片转存
              //'autotypeset', //自动排版
              //'webapp', //百度应用
              //'background', //背景
              //'template', //模板
              //'scrawl', //涂鸦
              //'music', //音乐
              //'inserttable', //插入表格
              //'drafts', //从草稿箱加载
              //'charts', //图表
            ],
          ],
          labelMap: {
            simpleupload: "插入图片",
          },
          //编辑器不自动被内容撑高
          autoHeightEnabled: false,
          //初始容器高度
          initialFrameHeight: 300,
          //初始容器宽度
          initialFrameWidth: "95%",
          elementPathEnabled: false,
          //wordCount: false,
          wordCount: true, //是否开启字数统计
          maximumWords: 200000, //允许的最大字符数
          //字数统计提示,{#count}代表当前字数,{#leave}代表还可以输入多少字符数,留空支持多语言自动切换,否则按此配置显示
          wordCountMsg:
            "当前已输入 {#count} 个字符,您还可以输入 {#leave} 个字符", //当前已输入 {#count} 个字符,您还可以输入{#leave} 个字符
          serverUrl:process.env.VUE_APP_BASE_API +"ueditor/ueditorUpload",
          UEDITOR_HOME_URL: "./UEditor/",
          zIndex: 1,
        },
      }
    },
    methods: {
      ueditorReady(editorInstance) {
        this.ueditorOject = editorInstance;
        //console.log(`编辑器实例${editorInstance.key}: `, editorInstance)
      }
    },
  }
3、editor-for-vue(用于ai-web项目,素材库-文章编辑,媒体大模型)
(1)依赖
  "dependencies": {
    "@wangeditor/editor-for-vue": "^5.1.12",
  },
(2)使用组件
  <textView
    :fileVisible="fileVisible"
    :item="modelFile"
    :readonly="readonly"
    @submit="update"
    @close="hideFileVisible"
  ></textView>
(3)定义组件
  <template>
    <el-dialog v-model="show" top="5vh" title="文档预览" width="992px"  @close="close">
      <div  
        class="article-edit-content"  
        v-loading="loading"
        :element-loading-svg="loadSvg"
        element-loading-svg-view-box="-10, -10, 50, 50"  
        style="overflow-y:scroll;" 
        :style="{height:heightValue}"  
        v-if="showEdit"
      >
        <div class="article-edit-toolbar">
          <Toolbar
            style="border-bottom: 1px solid #ccc"
            :editor="editorRef"
            :defaultConfig="toolbarConfig"
            :mode="mode"
            v-if="!readonly"
          />
        </div>
        <div class="article-edit-warp">
          <div class="article-title" v-if="readonly" >
            {{ item.title }}
          </div>
          <el-input
            v-else
            v-model="item.title"
            autosize
            type="textarea"
            placeholder="请输入标题"
            class="article-textarea"
          />
          <Editor
            style=" overflow-y: hidden"
            v-model="item.htmlContent"
            :defaultConfig="editorConfig"
            :mode="mode"
            @onCreated="handleCreated" // 把-编辑区-交给-工具区
          />
        </div>
      </div>
      <template #footer v-if="!readonly">
        <span class="dialog-footer">
          <el-button @click="close">取消</el-button>
          <el-button type="primary" @click="submit">
            确定
          </el-button>
        </span>
      </template>
    </el-dialog>
  </template>
  <script setup>
    import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
    import '@wangeditor/editor/dist/css/style.css'
    import { updateMaterialDocument } from '@/api/materialDocument.js';
    import { onBeforeUnmount, ref, shallowRef, onMounted, watch } from 'vue'
    const props = defineProps({
      fileVisible: {
        type: Boolean,
        default: false,
      },
      readonly: {
        type: Boolean,
        default: true,
      },
      item:{
        type: Object,
        default:()=>{}
      }
    });
    const loading = ref(false)
    const show = ref(false)
    const item = ref(props.item)
    const readonly = ref(props.readonly)
    const showEdit = ref(false)
    const editorRef = shallowRef();
    const heightValue = ref('calc(100vh - 200px)');
    watch(props,(now,last) => {
      console.log(now)
      show.value = now.fileVisible
      if(show.value){
        setTimeout(()=>{
          showEdit.value = true
        },100)
      } else {
        showEdit.value = false
      }
      item.value = now.item
      item.value.htmlContent=item.value.htmlContent?item.value.htmlContent:item.value.summary
      readonly.value = now.readonly
      if(readonly.value){
        editorRef.value&&editorRef.value.disable()
        heightValue.value = 'calc(100vh - 200px)'
      } else{
        editorRef.value&&editorRef.value.enable()
        heightValue.value = 'calc(100vh - 400px)'
      }
    })
    const mode = ref('default')// 或 'simple'
    // 模拟 ajax 异步获取内容
    const toolbarConfig = {
      excludeKeys: [
        'divider','codeBlock','insertTable','group-video','group-image',
        'insertLink','emotion','fullScreen','blockquote','group-more-style'
      ],
      insertKeys:{
        index: 30,
        keys: ['clearStyle']
      }
    };
    const editorConfig = { placeholder: '' };
    // 组件销毁时,也及时销毁编辑器
    onBeforeUnmount(() => {
      const editor = editorRef.value;
      if (editor == null) return;
      editor.destroy();
    });
    const emit = defineEmits(['close','submit'])
    const submit =() =>{
      loading.value = true
      item.value.textContent =  editorRef.value.getText()
      updateMaterialDocument(item.value).then(res =>{
        loading.value = false
        ElMessage({
          message: '更新成功',
          type: 'success',
        })
        show.value = false
        emit('submit',item)
      }).catch(error => {
        loading.value = false
        ElMessage({
          message: '更新失败',
          type: 'error',
        })
      })
    }
    const close = () => {
      show.value = false
      editorRef.value && editorRef.value.destroy();
      editorRef.value = null
      emit('close')
    }
    const handleCreated = editor => {
      editorRef.value = editor; // 把-编辑区-交给-工具区
      if(readonly.value){
        editor.disable()
      } else{
        editor.enable()
      }
    }
  </script>
  <style lang="scss">
  </style>
(4)在editor-for-vue里插入图片
  附、兄弟组件传参(传值传数据),在兄组件内部发射数据,在父组件通过监听自定义事件,调用弟组件的方法并传参
  A、在父组件里,
    <Picture //生成备用图片
      ref="pictureRef" 
      @pictureSubmited="submitedPicture"  
      @pictureStart="startPicture"
    ></Picture>
    <Editor //把备用图片插到编辑区
      ref="editorRef" 
      @resultIntervene="onResultIntervene" 
      @deliverInfo="onEditorRrsult" 
      @exchange="onExchange" 
      @stopCreat="onStopCreat" 
      @adoptP="onAdoptP"  
      @disuseP="onDisuseP"
    ></Editor>
    <el-button type="primary" @click="generateImage">生成图片</el-button>
    import useSettingsStore from '@/store/modules/settings';
    const settingsStore = useSettingsStore()
    const pictureRef = ref(null);  
    const pictureRef = ref(null);  
    function generateImage(data){
      settingsStore.setStopOperate(true)
      pictureRef.value.submit()//在子组件Picture里,获取数据并发射
    }
    function startPicture(){
      editorRef.value.insertContent(true,false,{},2,1)
    }
    function submitedPicture(data,isFinish){
      editorRef.value.insertContent(false,true,data,2,1)//在子组件Editor里,接收数据并插入
      /* 
      if(isFinish){
        nextDisabled.value = false
      } */
    }
  B、在子组件Picture里 
    import { generateImageTask,fetchTask } from '@/api/image.js';
    function submit() {
      formRef.value.validate((valid, fields) => {
        if (valid) {
          emit('pictureStart')
          if (settingsStore.taskId) {
            subObj.articleTaskId = settingsStore.taskId
          }else{
            subObj.articleTaskId = uuid
            settingsStore.setTaskInfo(uuid);
          }
          subObj.size = form.size
          subObj.paragraphList[0].content = form.content
          generateImageTask(subObj).then((res)=>{
            settingsStore.setCurrentTab('picture')
            if (res.data.code === "successful") {
              let newObj = {
                articleTaskId:settingsStore.taskId,
                taskIdList:[res.data.data.taskList[0].taskId]
              }
              let intervalTimer = setInterval(() => {
                let data = null
                doFetchTask(newObj,(res2,imageList)=>{
                  data = res2
                  emit('pictureSubmited',res2,false)
                  if (imageList&&imageList.length>0) {
                    emit('pictureSubmited',res2,true)
                    clearInterval(intervalTimer)
                  }
                })
              }, 2000)
            }else{
              settingsStore.setStopOperate(false)
            }
          })
        } else {
          settingsStore.setStopOperate(false)
        }
      })
    }
    function doFetchTask(data,callback){
      fetchTask(data).then((res)=>{
        callback(res,[])
        if (res.data.code==="successful") {
          if (res.data.data.taskInfoList[0].taskStatus==="SUCCEEDED") {
            callback(res,res.data.data.taskInfoList[0].imageList)
          }
        } else {
          settingsStore.setStopOperate(false)
        }
      })
    }
    defineExpose({
      submit
    })
  C、在子组件Editor里,注意,自定义的Editor组件,包含插件的Editor组件
    附、工具栏配置,https://www.wangeditor.com/v5/toolbar-config.html#toolbarkeys
    <Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" />
    <Editor v-model="valueHtml" :defaultConfig="editorConfig" @onCreated="handleCreated" /> // 把-编辑区-交给-工具区
    import { render, h as vueH} from 'vue'
    import cctvRender from "@/components/editor/cctv-render.vue";
    const editor = null;
    const toolbarConfig = {
      toolbarKeys: [
        "undo","redo","clearStyle","|","headerSelect","fontSize","bold",
        "italic","through","underline","fontFamily","|","color","bgColor","|"
      ],
      excludeKeys: [
        'divider','codeBlock','insertTable','group-video','group-image',
        'insertLink','emotion','fullScreen','blockquote','group-more-style'
      ],
      insertKeys:{
        index: 30,
        keys: ['clearStyle']
      },
    };
    onBeforeUnmount(() => {
      if (editor == null) return
      editor.destroy()
      editorRef.value.destroy()
    })
    const handleCreated = (editorEle) => {
      editorRef.value = editorEle // “非常重要-1/4”:把-编辑区-交给-工具区
      editor = editorEle; 
    }
    const insertContent = (isCreat, isFinish, res, type, multipleNum = 1) => {
      isFinished.value = isFinish;
      multipleCount.value = multipleNum;
      if (typeof res === 'string') {
        response.value = JSON.parse(res);
      } else {
        response.value = res;
      }
      if (type === 1) {
        if (multipleNum > 1) {
          mold.value = 3;  //多篇文章
        } else {
          mold.value = 1; //单篇文章
        }
      } else {
        mold.value = type;
      }
      if (isCreat) {
        insertPlugin();
      }
    }
    const insertPlugin = () => {
      if (domId.value !== '') {
        let pluginDiv = document.getElementById(domId.value);
        pluginDiv.parentNode.removeChild(pluginDiv);
        domId.value = '';
      }
      const id = Math.random() * 1000 + '';
      domId.value = id;
      const node = { type: 'cctv', id: id, children: [{ text: '' }] }
      if (mold.value === 4 || mold.value === 5) {
        editor.focus();
        editor.moveReverse(editor.getText().length + 1000)
      } else {
        editor.focus();//“非常重要-2/4”:获取-焦点
      }
      editorRef.value.insertNode(node);//“非常重要-3/4”:在-焦点-处,插入-节点-含id的
      setTimeout(() => {
        render(
          vueH(
            cctvRender, //标签或组件
            { //标签或组件的属性对象
              isFinished: isFinished, 
              mold: mold, 
              response: response, 
              multipleCount: multipleCount,
              onStopCreat: stopCreatHandler,
              onAdopt: adoptHandler,
              onCancel: cancelHandler,
              onReplaceFun: replaceHandler,
              onDisuse: disuseHandler,
              onCompare: compareHandler,
              onResultIntervene: resultInterveneHandler,
            },
          ),
          document.getElementById(id)//“非常重要-4/4”:在-节点处-含id的,插入-标签或组件
        )
      }, 10)//渲染节点
      editor.disable();
      return;
    }
    defineExpose({
      insertContent
    })
  附、cctvRender //Editor的子组件,import cctvRender from "@/components/editor/cctv-render.vue";
    <script setup>
      import { reactive, defineProps, watch, ref, onBeforeMount, onUnmounted, nextTick, computed } from 'vue'
      import wanchengIcon from '@/assets/icons/editor/wancheng.png';
      import loadingIcon from '@/assets/icons/editor/loading.gif';
      import config from '@/config/index.js';
      import useSettingsStore from '@/store/modules/settings';
      const settingsStore = useSettingsStore()
      const emit = defineEmits(['stopCreat', 'adopt', 'disuse', 'cancel', 'replaceFun', 'compare','resultIntervene'])
      const props = defineProps({
        isFinished: {
          default: false,
        },
      })
      const contRef = ref(null);
      const renderInfo = reactive(
        {
          name: config.name,
          response: '',
        }
      )
      onBeforeMount(() => {
        if (props.mold === 2) {
          renderInfo.imageIsStart = false;
        }
      })
      onUnmounted(() => {
        if (renderInfo.picCreatTimer) {
          clearInterval(renderInfo.picCreatTimer)
        }
      })
      function checkValue(obj, target) {
        for (let key in obj) {}
        return false;
      }
      watch(props, newValue => {
        renderInfo.response = newValue.response;
        renderInfo.type = newValue.mold;
      })
      const addTitle = (obj) => {
        emit("adopt", obj)
      }
    </script>
    <template>
      <!-- 数据返回错误的处理 -->
      <div class="article-generation-fail" v-if="renderInfo.state === 9">
        <button type="button" class="btn cancel" @click="cancel">取消</button>
      </div>
      <div class="article-generation" v-else>
        <div class="article-generation-container">
          <div class="article-generation-header-box">
            <!-- 文章 -->
            <!-- 配图 -->
            <!-- 生成标题 -->
            <!-- 提取关键字 -->
          </div>
          <div class="article-generation-body">
            <!-- 文章 -->
            <!-- 配图 -->
            <!-- 生成标题 -->
            <!-- 生成摘要 -->
            <!-- 提取关键字 -->
          </div>
          <div class="article-generation-footer">
            <!-- 文章 -->
            <!-- 图片 -->
            <!-- 生成摘要 -->
            <!-- 提取关键字 -->
          </div>
        </div>
      </div>
    </template>

八、算法知识|显示屏尺寸|丈母娘要彩礼|编程绝句
1、算法知识
(1)查找算法:
通过和文件中的关键字进行比较来实现的,都采用平均查找长度来衡量算法的优劣。那么能否有一种方法,不必进行与关键字的比较而达到查找的目的,这样平均查找长度就为0。哈希查找的基本思想是:通过对给定值作某种运算,直接求得(((关键字等于给定值的)记录)在文件中的)位置。
(2)八种数据结构:
数组、栈、队列、链表、集合、字典、树、图(一组由边连接的顶点)!以数组作为数组元素的数组,叫做二维数组,又叫做矩阵。行数、列数相等的矩阵称为方阵。元素以主对角线为对称轴对应相等的方阵叫做对称矩阵。主对角线外都是零元素的方阵叫做对角矩阵。将矩阵的行列互换得到的新矩阵称为转置矩阵。存放图顶点间关系的矩阵叫做邻接矩阵。
(3)树与二叉树:
由有限节点组成,具有层级关系的数据结构叫做树;每个节点最多有两个子节点的树叫做二叉树。
2、显示屏尺寸
  function displayScreen(width,height,inch){
      var usual=0;
      if((width===4&&height===3)||(width===16&&height===9)||(width===16&&height===10)||(width===21&&height===9)||(width===32&&height===9)){
          usual=width*width+height*height
      }else{
          console.log('没有"'+width+":"+height+'"的显示屏');
          return
      }
      var radio=Math.sqrt(inch*inch/usual)*2.54;
      var line=(inch*2.54).toFixed(2);
      var singleWidth=(radio*width).toFixed(2);
      var singleHeight=(radio*height).toFixed(2);
      if(inch%1===0){inch=inch+'.0'}
      console.log(inch+"寸"+width+":"+height+"的显示屏,其对角线长为"+line+"厘米;宽为"+singleWidth+"厘米;高为"+singleHeight+"厘米");
  }
  for(var inch=20;inch<=70;inch+=1){displayScreen(16,9,inch)}
3、丈母娘要彩礼
(1)格式化版
  function formatMoney(total) {
    /** 
     * @formatMoney 格式化钱数为亿、万、元、角、分;效果为数字和汉字相间出现,这样分段更清晰,更容易读出 
     * @total 总钱数
     */
    var numStr = total.toString();
    var numArr = numStr.split('.');
    var numFront = numArr[0];
    var numBack = numArr[1];
    var frontLen = numFront.length;
    var yi = '',
      wan = '',
      yuan = '',
      jiao = '',
      fen = '';
    var jiaoFront = '';
    //以下处理整数部分
    if(frontLen > 12||numStr.indexOf('1e+')>-1){ 
      return '本函数最多格式化12位数'
    }  
    if (frontLen > 4) {//以下处理万级及以上数据
      if (frontLen > 8) {//以下处理亿级及以上数据
        yi = numFront.substring(0, frontLen - 8) + '亿';
        //以下处理十亿级及以上数据的亿位补零
        if(numFront.charAt(frontLen - 9) == '0' && numFront.charAt(frontLen - 8) != '0') yi += '零';
        //以下处理亿级及以上数据的千万、百万、十万位补零
        for (var i = frontLen - 8; i < frontLen - 4; i++) {
          if (numFront.charAt(i) && numFront.charAt(i) != '0') {
            wan = (frontLen - i < 8 ? '零' : '') + numFront.substring(i, frontLen - 4) + '万';
            break;
          }
        }
      } else {//以下处理亿级以下、万级及以上数据  
        wan = numFront.substr(0, frontLen - 4) + '万';
      }
      //以下处理十万级及以上数据的万位补零
      if(numFront.charAt(frontLen - 5) == '0' && numFront.charAt(frontLen - 4) != '0') yuan += '零';
      //以下处理万级及以上数据的千、百、十位补零
      for (var i = frontLen - 4; i < frontLen; i++) {
        if (numFront.charAt(i) && numFront.charAt(i) != '0') {
          yuan += (frontLen - i < 4 ? '零' : '') + numFront.substring(i) + '元';
          break;
        }
      }
    } else {//以下处理万级以下数据
      if (numFront != '0') {//排除整数部分为0的情况
        yuan = numFront.substr() + '元';
      }
    }
    var moneyFront = total + '元,即' + yi + wan + yuan;
    //以下处理小数部分
    var last1 = moneyFront.charAt(moneyFront.length - 1);
    var last2 = moneyFront.charAt(moneyFront.length - 2);
    if (numBack) {//如果存在小数
      //以下处理角
      if (numBack.charAt(0) == '0') {//如果十分位是0
        if(numFront != '0' && numBack.charAt(1) && numBack.charAt(1) != '0'){
          jiao = yuan? '零': '元零';//千、百、十、个位全为零时,yuan为空,需补元
        }
      } else {//如果十分位不是0
        if(last1 == '元' && last2 == '0'){
          jiaoFront = '零';
        }else if(last1 == '亿' || last1 == '万'){
          jiaoFront = '元零';
        } 
        jiao = jiaoFront + numBack.charAt(0) + '角'
      }
      //以下处理分
      if (numBack.charAt(1) && numBack.charAt(1) != '0') fen = numBack.charAt(1) + '分'
    }else{//如果不存在小数
      if(last1 == '亿'||last1 == '万'){//如果亿位或万位以后的整数都是0
        moneyFront = moneyFront + '元';
      }
    }
    //以下汇总并抛出
    var money = moneyFront + jiao + fen;
    return money 
  }
  function payMoney(startYuan, dayNum, time) {
    /**
     * @payMoney 算出每天钱数和所有天总钱数
     * @startYuan 第一天给多少钱
     * @dayNum 连续给多少天
     * @time 后一天给前一天多少倍
     */
    startYuan = startYuan || 0.01;
    dayNum = dayNum || 30;
    time = time || 2;
    //以下算出每天钱数
    var dayMoney = '';
    dayMoney += '丈母娘要彩礼:' + '\n';
    dayMoney += '第1天给' + formatMoney(startYuan) + '\n';
    dayMoney += '以后每天给前一天的' + time + '倍,连续给' + dayNum + '天。\n';
    for (var i = 1; i <= dayNum; i++) {
      var money = startYuan * Math.pow(time, i - 1);
      dayMoney += '第' + i + '天应该给丈母娘:' + formatMoney(money) + '\n';
    }
    //以下算出所有天钱数
    var total = 0;
    for (var i = 1; i <= dayNum; i++) {
      total += startYuan * Math.pow(time, i - 1);
    }
    //以下抛出最终结果
    return dayMoney + dayNum + '天共计:' + formatMoney(total);
  }
  console.log(payMoney())
  console.log('.........')
  /* 
  console.log(payMoney(1)) 
  console.log( formatMoney(0.1) );
  console.log( formatMoney(0.01) );
  console.log( formatMoney(300.1) );
  console.log( formatMoney(300.01) );
  console.log( formatMoney(10300.1) );
  console.log( formatMoney(10300.01) );
  console.log( formatMoney(100000000.1) );
  console.log( formatMoney(100000000.01) );
  console.log( formatMoney(1000010000.01) );
  console.log( formatMoney(1010100010.11) ); */
(2)非格式化版
  function payMoney(startYuan, dayNum, time) {
    startYuan = startYuan || 0.01;
    dayNum = dayNum || 30;
    time = time || 2;
    //以下算出每天钱数
    var dayMoney = '';
    dayMoney += '丈母娘要彩礼:' + '\n';
    dayMoney += '第1天给' + startYuan + '元' + '\n';
    dayMoney += '以后每天给前一天的' + time + '倍,连续给' + dayNum + '天。\n';
    for (var i = 1; i <= dayNum; i++) {
      var money = startYuan * Math.pow(time, i - 1);
      dayMoney += '第' + i + '天应该给丈母娘:' + money + '元' + '\n';
    }
    //以下算出所有天钱数
    var total = 0;
    for (var i = 1; i <= dayNum; i++) {
      total += startYuan * Math.pow(time, i - 1);
    }
    //以下抛出最终结果
    return dayMoney + dayNum + '天共计:' + total + '元';
  }
  console.log(payMoney())
  console.log('.........')
  console.log(payMoney(1))        
4、编程绝句
  我国清朝彭端淑曰:天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。
  吾本史学科班出身,为生计和兴趣计,经多次转行,最终以“必为”之心做了程序员。
  余自转行程序员以来,别妻、离子、抛清明、弃五一、丧端午、失中秋、沦国庆、陷元旦、缺周六、损周日。
  天明起床、半夜入睡,四季更替,周而复始,专心致志于公司、心无旁骛于陋室。希冀以勤补拙,技上层楼。
  个中辛苦,有诗为证:编程艰途如攻山, 拼上老命向上攀; 满身创伤终登顶, 无边群峰在眼前。
  编程其一:目视夕阳缓缓落,心盼技术速速升。周遭高楼依次亮,谁知我在中关村?   
  编程其二:十年之前论古今,誓改人间换乾坤。如今只为生计计,犹落风尘做编程。    
  编程其三:当年悟空五行山,腾云驾雾难施展。而今吾身无负重,苦无神技助冲天。 
  编程其四:编程艰途如攻山,拼上性命向上攀。满身创伤终至顶,还有更高在眼前。
  
  

  

posted @ 2019-06-03 13:15  WEB前端工程师_钱成  阅读(4304)  评论(0编辑  收藏  举报