2021-04-16

实话实说,币安除了能买币其他一无是处,收益之类的做的更烂。所以我只能自己动手了

一位不愿意透露姓名的朋友表示,确实很烂实话实说

我只是一个刚入行的小韭菜,听说币圈的同志们抄币还要做 Excel ,所以分享出来。希望可以帮助大家管理自己资产(工具还在初级阶段,行情只是第一个模块,有什么建议可以在本网站给我留言😉)

安装步骤

先了解一下 Chrome 插件

打开拓展程序

打开开发者模式 -> 加载已解压的拓展程序 (点击下载拓展程序包

使用介绍

行情数据接入的是火币交易所,买入卖出扣除千分之一手续费,数据计算会跟交易所有一点点区别,但是差距不大,CNY汇率目前是固定 6.53

工具比较简单,有几个需要注意的点:

  • 本工具不记录任何用户隐私,用户数据全部在Chrome本地储存
  • 行情接入的是币安,所以币安没有的上架的是无法查看的(后续会接入更多)
  • 设置本金:因为CNY和USDT是不等的(买入USDT差价),所以这一块需要自己填入。

如果你要什么建议优化可以在本网站留言,如果这款工具有帮到您,给作者一个小小的打赏,买一杯咖啡继续奋斗👨‍💻

阅读全文

2021-04-07

起因

某天晚上我在某牙看直播时,发现他们新推了一个表情包还挺逗,大概是这样的 

如果你看到这段字说明你的浏览器不支持

于是我突发奇想做一个表情包,这样的 

拆分APNG帧动画以及生成ZIP下载

由于PS不支持APNG的帧动画拆分,所以我只能自己动手了 👨‍💻

简单介绍一下APNG

GIF:

  • 最多支持 8 位 256 色,色阶过渡糟糕,图片具有颗粒感
  • 不支持 Alpha 透明通道,边缘有杂边

APNG:

  • 支持 24 位真彩色图片
  • 支持 8 位 Alpha 透明通道
  • 向下兼容 PNG

GIF的动画每帧都是完整图片

拆分APNG帧动画以及生成ZIP下载

APNG会通过算法计算帧之间的差异,只存储帧之前的差异,而不是存储全帧。

拆分APNG帧动画以及生成ZIP下载

APNG兼容性

拆分APNG帧动画以及生成ZIP下载

apng-js

Github:https://github.com/davidmz/apng-js

apng-js 是一个拆分APNG文件并且提供播放能力的一个库,由于我想做一个拆分APNG动画帧然后生成ZIP文件下载的工具库,所以我抽取了一些核心代码重新实现了一个拆分APNG生成图片的功能。(源码

因为APNG后面的图片只有差异帧,所以会导致图片大小不一,在canvas上重新绘制一遍,统一所有图片大小。这里只帖部分代码,感兴趣的可以点击源码查看。

createImage(width, height) {
    return new Promise((resolve, reject) => {
        const url = URL.createObjectURL(this.imageData);
        const img = document.createElement('img');
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext("2d");

        /* 计算屏幕分辨率 */
        const resolution = this.getScreenResolution(ctx);
        canvas.width = width * resolution;
        canvas.height = height * resolution;
        canvas.style.width = width + "px";
        canvas.style.height = height + "px";
        ctx.scale(resolution, resolution);

        img.onload = () => {
            URL.revokeObjectURL(url);
            ctx.drawImage(img, this.left, this.top);
            this.canvas = canvas;
            resolve(canvas);
        };
        img.onerror = () => {
            URL.revokeObjectURL(url);
            reject(new Error("Image creation error"));
        };
        img.src = url;
    });
}

HTMLCanvasElement.toBlob

HTMLCanvasElement.toBlob() 方法创造Blob对象,用以展示canvas上的图片;这个图片文件可以被缓存或保存到本地,由用户代理端自行决定。如不特别指明,图片的类型默认为 image/png,分辨率为96dpi。

HTMLCanvasElement.toBlob 可以生成一个Blob对象,后续生成文件的时候就可以使用这个方法。

jszip

Github:https://github.com/Stuk/jszip

jszip是一个生成zip的库,使用方式也很简单。作者也提供了普通版本以及min版本的js,都在dist目录下。我这边简单封装了一下生成图片ZIP然后下载的方法。

ZIPjs.images = (fileName, dataSource: imagesZipProps[]) => {
    var zip = new window.JSZip();
    var img = zip.folder(fileName);

    dataSource.forEach(({ name, imgData }) => {
        img.file(name, imgData, { base64: true });
    });

    // 生成zip文件并下载
    zip.generateAsync({
        type: 'blob'
    }).then(function (content) {
        // 创建隐藏的可下载链接
        var eleLink = document.createElement('a');
        eleLink.download = fileName + '.zip';
        eleLink.style.display = 'none';
        // 下载内容转变成blob地址
        eleLink.href = URL.createObjectURL(content);
        // 触发点击
        document.body.appendChild(eleLink);
        eleLink.click();
        // 然后移除
        document.body.removeChild(eleLink);
    });
}

在线体验地址:柚子青年 - 工具

阅读全文

2021-02-20

安装 Docker jenkins

docker run -d -u root  --privileged=true  --name jenkins -p 8080:8080 -p 50000:50000 -v /opt/jenkins:/var/jenkins_home -v /etc/localtime:/etc/localtime docker.io/jenkins/jenkins
  • -d 后台运行镜像
  • -p 8080:8080  将镜像的8080端口映射到服务器的80端口
  • -p 50000:50000  将镜像的50000端口映射到服务器的50000端口
  • -v /opt/jenkins:/var/jenkins_home  /var/jenkins_home目录为jenkins工作目录,我们将硬盘上的一个目录挂载到这个位置,方便后续更新镜像后继续使用原来的工作目录。
  • -v /etc/localtime:/etc/localtime  让容器使用和服务器同样的时间设置。
  • --name jenkins 给容器起一个别名

访问jenkins

访问链接 http://127.0.0.1:8080/,第一次需要初始化,骚等一会

Docker + jenkins 自动化构建前端工程

解锁jenkins

docker exec -it jenkins /bin/bash bash-5.0$ cat /var/jenkins_home/secrets/initialAdminPassword

Docker + jenkins 自动化构建前端工程

Docker + jenkins 自动化构建前端工程

Docker + jenkins 自动化构建前端工程

Docker + jenkins 自动化构建前端工程

新增node插件

打开 插件管理 -> 可选插件 -> 搜索node -> 安装

Docker + jenkins 自动化构建前端工程

打开 全局工具配置 -> 选择node -> 保存

Docker + jenkins 自动化构建前端工程

新建任务

Docker + jenkins 自动化构建前端工程

Docker + jenkins 自动化构建前端工程

Docker + jenkins 自动化构建前端工程

选执行shell

Docker + jenkins 自动化构建前端工程

Docker + jenkins 自动化构建前端工程

点击立即构建

jenkins 忘记密码

  • 找到jenkins config.xml,本文档在 /var/jenkins_home/config.xml,复制到外部,因为docker没有vim编辑功能。 docker cp jenkins:/var/jenkins_home/config.xml /root/1.xml
       
  • 修改 useSecurity 为false <useSecurity>false</useSecurity>
  • 把修改后的内容复制回去 docker cp /root/1.xml jenkins:/var/jenkins_home/config.xml
  • 重启 docker restart jenkins
  • 重新打开 http://127.0.0.1:8080/
  • 打开 全局安全配置

Docker + jenkins 自动化构建前端工程

注:先打开安全域,重新设置用户信息,再修改授权策略

Docker + jenkins 自动化构建前端工程

疑是引起密码错误原因

有时候jenkins密码会错误,不知道是不是用户信息回填的时候不是原密码导致的。(建议打开用户信息设置一定要重写密码,貌似不点确定密码也会改变😑)

Docker + jenkins 自动化构建前端工程

阅读全文

2021-02-18

本教程只基于Formily SchemaForm的使用,Demo展示 codesandbox

Formily是什么

Formily 是阿里巴巴开源的一个表单框架,致力于解决所有中后台表单场景。目前只有React版本,支持Antd和next UI组件库。[查看详情]

Formily有自己的状态管理,内部也有自己的生命周期,在渲染的时候也做到了最小更新,所以即使你是大型表单性能也不会很差。

0. Formily - 入门

SchemaMarkupField - 用于描述表单字段 [查看详情]

使用Formily实现一个基础表单

这是一个使用 SchemaForm 实现的一个最简单的表单,里面有一个nickname字段,当用户点击提交的时候,会触发 SchemaForm 的 onSubmit 事件(如果没有值,则会返回一个空对象 ‘{}’)。

// demo0
import {
    SchemaForm,
    Submit,
    SchemaMarkupField as Field
} from '@formily/antd';

<SchemaForm
    components={{ Input }}
    onSubmit={console.log}
>
    <Field title="昵称" name="nickname" x-component="Input" />
    <Submit>提交</Submit>
</SchemaForm>

表单校验

在上一个表单的基础上增加校验功能。

// demo1
import {
    SchemaForm,
    Submit,
    SchemaMarkupField as Field
} from '@formily/antd';

<SchemaForm
    components={{ Input }}
    onSubmit={console.log}
>
    {/* 只需要一个必填校验,增加required属性即可,这时message提示为 “该字段是必填字段” */}
    <Field required title="昵称" name="nickname0" x-component="Input" />
    {/* 自定义message则需要使用 x-rules,属性:ValidatePatternRules */}
    <Field
        title="昵称"
        name="nickname1"
        x-component="Input"
        x-rules={{ required: true, message: "昵称不能为空" }}
    />
    {/* x-rules 支持数组,可以进行多个判断 */}
    <Field
        title="url"
        name="url"
        x-component="Input"
        x-rules={[
            { required: true, message: "url不能为空" },
            { format: "url", message: "url格式有误" },
            /* 自定义校验函数:CustomValidator:() => ValidateResponse */
            (value, description, rules) => {
                console.log(value, description, rules);
                return {
                    type: 'error',
                    message: "string"
                }
            }
        ]}
    />
    <Submit>提交</Submit>
</SchemaForm>

自定义校验正则

自定义正则校验,需要先使用registerValidationFormats注册,然后在x-rules内使用。

// demo2
import {
    SchemaForm,
    Submit,
    SchemaMarkupField as Field,
    registerValidationFormats
} from '@formily/antd';

// 内置format正则:InternalFormats

registerValidationFormats({
    integer: /^d+$/
});

<SchemaForm
    components={{ Input }}
    onSubmit={console.log}
>
    <Field
        title="电话"
        name="phoneNumber"
        x-component="Input"
        x-rules={{ format: "integer", message: "请输入纯数字" }}
    />
    <Submit>提交</Submit>
</SchemaForm>

常用表单示例

官方示例

// demo3
import { Input, InputNumber, Select, DatePicker } from "antd";
import { SchemaForm, Submit, SchemaMarkupField as Field } from "@formily/antd";
import { Range, Checkbox, Radio } from "@formily/antd-components";

<SchemaForm
    components={{
        Input,
        TextArea: Input.TextArea,
        Password: Input.Password,
        InputNumber,
        DatePicker,
        RangePicker: DatePicker.RangePicker,
        Checkbox,
        CheckboxGroup: Checkbox.Group,
        Radio,
        RadioGroup: Radio.Group,
        Select,
        Range
    }}
    onSubmit={console.log}
>
    <Field title="String" name="string" x-component="Input" />
    <Field title="Password" name="password" x-component="Password" />
    <Field
        title="InputNumber"
        name="inputNumber"
        x-component="InputNumber"
    />
    <Field title="TextArea" name="textarea" x-component="TextArea" />

    <Field
        title="Simple Select"
        name="simpleSelect"
        x-component="Select"
        enum={["1", "2", "3", "4"]}
    />
    <Field
        title="Object Select"
        name="objSelect"
        x-component="Select"
        enum={[
            { label: "One", value: "1" },
            { label: "Two", value: "2" },
            { label: "Three", value: "3" },
            { label: "Four", value: "4" }
        ]}
    />
    <Field title="Switch" name="switch" x-component="Switch" />
    <Field title="DatePicker" name="datePicker" x-component="DatePicker" />
    <Field
        title="RangePicker"
        name="[start,end]"
        x-component="RangePicker"
    />
    <Field
        title="Range"
        name="range"
        x-component="Range"
        x-component-props={{
            min: 0,
            max: 1024,
            marks: [0, 1024]
        }}
    />
    <Field
        title="Simple Checkbox"
        name="simpleCheckbox"
        x-component="CheckboxGroup"
        enum={["1", "2", "3", "4"]}
    />
    <Field
        title="Object Checkbox"
        name="objectCheckbox"
        x-component="CheckboxGroup"
        enum={[
            { label: "One", value: "1" },
            { label: "Two", value: "2" },
            { label: "Three", value: "3" },
            { label: "Four", value: "4" }
        ]}
    />

    <Field
        title="Simple Radio"
        name="simpleRadio"
        x-component="RadioGroup"
        enum={["1", "2", "3", "4"]}
    />
    <Field
        title="Object Radio"
        name="objectRadio"
        x-component="RadioGroup"
        enum={[
            { label: "One", value: "1" },
            { label: "Two", value: "2" },
            { label: "Three", value: "3" },
            { label: "Four", value: "4" }
        ]}
    />
    <Submit>提交</Submit>
</SchemaForm>

Interfaces

InternalFormats

export type InternalFormats =
  | 'url'
  | 'email'
  | 'ipv6'
  | 'ipv4'
  | 'idcard'
  | 'taodomain'
  | 'qq'
  | 'phone'
  | 'money'
  | 'zh'
  | 'date'
  | 'zip'
  | string

ValidatePatternRules

export type ValidatePatternRules =
  | InternalFormats
  | CustomValidator
  | ValidateDescription
  | ValidateArrayRules

CustomValidator

export type CustomValidator = (
  value: any,
  description?: ValidateDescription,
  rules?: ValidateRulesMap
) => ValidateResponse

ValidateResponse

export type ValidateResponse = SyncValidateResponse | AsyncValidateResponse

欢迎大家留言,可以告诉我你不清楚的地方以及你的其他见解 🙂

阅读全文

2021-02-17

Editor.md 是一个很好用的 markdown 编辑器,在使用过程中遇到一个图片上传的问题。

插件原方法是使用了一个 Form 来提交图片资源,我在这个基础上添加了一个 upload 方法,用来上传图片资源。储存图片使用的是阿里云的OSS。

源代码

var dialogContent = ((settings.imageUpload) ?
    "<form action='" + action + "' target='" + iframeName + "' method='post' enctype='multipart/form-data' class='" + classPrefix + "form'>" :
    "<div class='" + classPrefix + "form'>") +
    ((settings.imageUpload) ? "<iframe name='" + iframeName + "' id='" + iframeName + "' guid='" + guid + "'></iframe>" : "") +
    "<label>" + imageLang.url + "</label>" +
    "<input type='text' data-url />" + (function () {
        return (settings.imageUpload) ? "<div class='" + classPrefix + "file-input'>" +
            "<input type='file' name='" + classPrefix + "image-file' accept='image/*' />" +
            "<input type='submit' value='" + imageLang.uploadButton + "' />" +
            "</div>" : "";
    })() +
    "<br/>" +
    "<label>" + imageLang.alt + "</label>" +
    "<input type='text' value='" + selection + "' data-alt />" +
    "<br/>" +
    "<label>" + imageLang.link + "</label>" +
    "<input type='text' value='http://' data-link />" +
    "<br/>" +
    ((settings.imageUpload) ? "</form>" : "</div>");

这段代码作用是动态生成上传图片Dialog的dom,需要删掉form以及iframe,替换submit按钮为一个div就好了。[查看源码]

var submitHandler = function () {
    var uploadIframe = document.getElementById(iframeName);

    uploadIframe.onload = function () {

        loading(false);

        var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body;
        var json = (body.innerText) ? body.innerText : ((body.textContent) ? body.textContent : null);

        json = (typeof JSON.parse !== "undefined") ? JSON.parse(json) : eval("(" + json + ")");

        if (!settings.crossDomainUpload) {
            if (json.success === 1) {
                dialog.find("[data-url]").val(json.url);
            }
            else {
                alert(json.message);
            }
        }

        return false;
    };
};

dialog.find("[type='submit']").bind("click", submitHandler).trigger("click");

这段代码作用是当触发上传文件之后,手动触发submit按钮并执行submitHandler,这段代码直接干掉。[查看源码]

新增代码

var upload = function (url, file, callback) {
    let formData = new FormData();

    for (const key in file) {
        formData.append(key, file[key]);
    }

    $.ajax({
        url,
        type: "POST",
        data: formData,
        async: false,
        cache: false,
        contentType: false,
        processData: false,
        success(res) { callback(res.data) },
        error(err) { console.log(err); callback() }
    });
};

在13行增加一个自己的upload方法。

upload(settings.imageUploadURL, { [fileName]: e.target.files[0] }, (url) => {
    loading(false);
     if(url) {
         dialog.find("[data-url]").val(url); 
     }
});

在155行调用upload方法,增加回调方法设置input value。

阅读全文

2021-02-16

修改npm镜像地址

# 临时使用
npm --registry https://registry.npm.taobao.org install express
# 校验
npm config get registry

# 持久修改
npm config set registry https://registry.npm.taobao.org
# 校验
npm info express

# 还原默认
npm config set registry https://registry.npmjs.org/

TS配置alias别名

解决TS编译别名报错,vscode错误提示。

webpack.config.js

resolve: {
    alias: {
        '@': path.resolve('src'),
    }
}

index.js

- import xxx from '../components/xxx';

+ import xxx from '@/components/xxx';

tsconfig.json

"paths": {
  "@/*": ["./src/*"],
}

vscode 编辑器只在打开项目工程的时候加载一次 tsconfig.json 的配置信息。如果编译无误,配置完重启一下vscode就好了。

VS Code

配置shell快捷方式 原文链接

vscode shell配置

Nginx

# 重启
nginx -s reload

# 关闭
nginx -s stop

GO

多平台编译

Golang 支持在一个平台下生成另一个平台可执行程序的交叉编译功能。

1、Mac下编译Linux, Windows平台的64位可执行程序:

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build test.go
$ CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build test.go

2、Linux下编译Mac, Windows平台的64位可执行程序:

$ CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build test.go
$ CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build test.go

3、Windows下编译Mac, Linux平台的64位可执行程序:

$ SET CGO_ENABLED=0SET GOOS=darwin3 SET GOARCH=amd64 go build test.go
$ SET CGO_ENABLED=0 SET GOOS=linux SET GOARCH=amd64 go build  test.go

注:如果编译web等工程项目,直接cd到工程目录下直接执行以上命令

GOOS:目标可执行程序运行操作系统,支持 darwin,freebsd,linux,windows
GOARCH:目标可执行程序操作系统构架,包括 386,amd64,arm

阅读全文

2021-01-12

//获取远端仓库一切变更
git fetch

git fetch origin

// 拉取分支
git checkout -b feature/xxx origin/feature/xxx

// 同步远端主库变更
git merge origin master

// 删除某个分支
git branch -D feature/xxx

// 指定某个分支推送
git push origin feature/xxx

// 重置到某个提交
git reset --hard [commit-id]

// 撤回到上一次提交
git reset --soft HEAD^
--mixed 不删除工作空间改动代码,撤销 commit ,并且撤销 git add . 操作
--soft 不删除工作空间改动代码,撤销 commit,不撤销 git add .
--hard 删除工作空间改动代码,撤销commit,撤销git add .

// 查看提交日志
git log

// 查看当前分支, 提交情况
git status

// 查看commit之间的差别
git diff [commit-id]

// 撤销在缓存区内的提交
git checkout [file-name]

// 跳过代码检查
git commit --no-verify -m "commit"

// 重新编辑提交注释
git commit --amend

阅读全文

2021-01-04

起因

当我在 Hooks 内使用 addEventListener 监听 scroll 事件时,然后使用 removeEventListener 移除监听发现移除失效。

const scroll = () => {
    useEffect(() => {
        document.addEventListener("scroll", scrollCallback);
    }, []);

    const scrollCallback = (e) => {
        console.log(e)
    }

    const clearScroll = () => {
        document.removeEventListener("scroll", scrollCallback);
    }

    return <button onClick={clearScroll}>Clean</button>;
}

这是一个在初始化完成后会在 document 添加一个 scroll 事件监听,点击按钮之后会清除监听事件。

但是事实上并不会按照预想的方式执行,在点击按钮之后并不会清除 scroll 事件;

每一次渲染都有它自己的事件处理函数

Hooks 每次渲染的时候事件都是自己的内部重新定义的,所以 removeEventListener 在移除的时候判定到的 scrollCallback 不是同一个回调函数, 这里有更详细的介绍。

useCallback

const memoizedCallback = useCallback(
    () => {
        doSomething(a, b);
    },
    [a, b],
);

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。这里有更详细的介绍。

改造

const scroll = () => {
    const eventListener = useCallback((e) => scrollCallback(e), []);

    useEffect(() => {
        document.addEventListener("scroll", eventListener);
    }, []);

    const scrollCallback = (e) => {
        console.log(e)
    }

    const clearScroll = () => {
        document.removeEventListener("scroll", eventListener);
    }

    return <button onClick={clearScroll}>Clean</button>;
}


阅读全文

2021-01-04

安装 font-spider

npm install font-spider

index.html

<div class="content">0123456789</div>

index.css

@font-face {
    font-family: "DINAlternateBold";
    src: url("./font/DIN-Alternate-Bold.ttf")
}
.content {
    font-family: DINAlternateBold;
}

执行命令打包新字体库

# html 路径
font-spider index.html

注:执行后会生成一个新的字体包,只有在html内的文字才会在字体包内,适用于数字英文特殊字体处理

阅读全文

2021-01-03

轮播图的几个关键点:翻页按钮,分页器,悬停静止,无限轮播,清理动画叠加。

文中使用了构造函数的方式把所有细节解构去耦合,欢迎大家点评

HTML

<div class="swiper swiper1">
    <div class="wrapper">
        <div class="slider">1</div>
        <div class="slider">2</div>
        <div class="slider">3</div>
        <div class="slider">4</div>
        <div class="slider">5</div>
    </div>
    <div class="btn">
        <span class="prev"><</span>
        <span class="next">></span>
    </div>
</div>

CSS

.swiper { position: relative; width: 700px; height: 400px; background: #efefef; overflow: hidden; margin-bottom: 10px; }

.wrapper { position: relative; left: 0; display: flex; width: 9999999%; }

.slider { display: flex; align-items: center; justify-content: center; }

.btn { position: absolute; top: 50%; left: 0; width: 100%; z-index: 1; }

.btn span { position: absolute; top: 0; font-size: 25px; color: #666; height: 70px; width: 50px; line-height: 70px; text-align: center; margin-top: -35px; cursor: pointer; background: rgba(102, 102, 102, .2); transition: all .3s ease; }

.btn span:hover { background: rgba(102, 102, 102, .6); }

.btn span.next { right: 0; }

.pagination { position: absolute; left: 0; bottom: 10px; display: flex; justify-content: center; width: 100%; }

.pagination span { width: 8px; height: 8px; margin: 0 5px; border-radius: 50%; background: #fff; cursor: pointer;}

.pagination span.active { background: #00f; }

JS

定义一个构造函数 Swiper

const Swiper = function ({el, pagination, delay}) {
    this._el = el;  // 最外层容器 swiper1
    this._prev = el.querySelector(".prev"); // 上一页按钮
    this._next = el.querySelector(".next"); // 下一页按钮
    this._slider = el.querySelectorAll(".slider"); // 获取 swiper1 下的所有 slider
    this._wrapper = el.querySelector(".wrapper"); // 获取 swiper1 下的 wrapper
    this._width = el.offsetWidth; // 获取 swiper1 宽度
    this._height = el.offsetHeight; // 获取 swiper1 高度
    this._size = this._slider.length;
    this._index = 0;  // 记录当前展示slider的下标
    this._timer = null; // 轮播定时器
    this._animate = null; // 动画定时器
    this._isHover = false; // 是否悬浮 swiper1 上
    this._pagination = pagination || false; // 是否启用分页器
    this._delay = delay || 3000; // 轮播间隔时长
    this.init();
};

初始化 this.init

Swiper.prototype.init = function () {
  this.style();
  this.auto();
  this.mouseEnter();
};

设置基础样式 复制头尾实现无限轮播 this.style

Swiper.prototype.style = function () {
  this._slider.forEach(item => {
    item.style.width = this._width + "px";
    item.style.height = this._height + "px";
  });

  const firstDom = this._slider[0].cloneNode(true);
  const lastDom = this._slider[this._size - 1].cloneNode(true);

  this._wrapper.appendChild(firstDom);
  this._wrapper.insertBefore(lastDom, this._el.querySelector(".slider"));
  this._wrapper.style.left = -this._width + "px"; // 归位到第一张

  if (this._pagination) {
    const pagination = document.createElement("div");
    pagination.className = "pagination";

    this._slider.forEach((item, i) => {
      const span = document.createElement("span");
      if (i === this._index) span.className = "active";

      span.onclick = function () {
        this.index(i);
      }.bind(this)
      pagination.appendChild(span);
    });

    this._el.appendChild(pagination);
  }
}

设置当前active 调整分页选中 this.active

Swiper.prototype.active = function () {
  this._el.querySelector(`.pagination span.active`).className = "";

  const index = this._index < 0 ? this._size : this._index >= this._size ? 1 : this._index + 1;

  this._el.querySelector(`.pagination span:nth-child(${index})`).className = "active";
};

自动轮播 this.auto 

Swiper.prototype.auto = function () {
  if (this._timer) clearTimeout(this._timer);

  this._timer = setTimeout(() => {
    if (this._isHover) {
      clearTimeout(this._timer);
      this.auto();
      return;
    }
    this.next();
  }, this._delay)
};

轮播图移动核心处理 this.move

Swiper.prototype.move = function () {
  if (this._index > this._size) {
    this._index = 1;
    this.styleLeft(-1 * this._width);
  }

  if (this._index < -1) {
    this._index = this._size - 2;
    this.styleLeft(-(this._size) * this._width);
  }

  this.start((-this._index - 1) * this._width); 
  this.active();
  this.auto();
};

轮播动画 this.animate

Swiper.prototype.animate = function (start, end) {
  const step = (start - end > 0 ? -1 : 1) * (Math.abs(start - end) / 10); // 设置一个滚动基数

  if (this._animate) clearInterval(this._animate);

  this._animate = setInterval(() => {
    start += step;

    if (step < 0 && end - start > step || step > 0 && end - start < step) {
      clearInterval(this._animate);
      this.styleLeft(end);
    } else {
      this.styleLeft(start);
    }
  }, 40)
};

计算动画开始时 left偏移量 this.start

Swiper.prototype.start = function (end) {
  const start = parseFloat(this._wrapper.style.left);

  this.animate(start, end);
};

设置 Swiper left偏移量 this.styleLeft

Swiper.prototype.styleLeft = function (left) {
  this._wrapper.style.left = left + "px";
};

上一页 this.prev

Swiper.prototype.prev = function () {
  this._index--;
  this.move();
}

下一页 this.next

Swiper.prototype.next = function () {
  this._index++;
  this.move();
};

分页器切换 this.index

Swiper.prototype.index = function (index) {
  this._index = index;
  this.move();
};

添加鼠标事件 this.mouseEnter

Swiper.prototype.mouseEnter = function () {
  this._el.onmouseenter = () => {
    this._isHover = true;
  };

  this._el.onmouseleave = () => {
    this._isHover = false;
  };

  if (this._prev) {
    this._prev.onclick = () => {
      this.prev();
    }
  }

  if (this._next) {
    this._next.onclick = () => {
      this.next();
    }
  }
};

调用

 new Swiper({
  el: document.querySelector(".swiper1"),
  pagination: true,
  delay: 2000
});


阅读全文

2021-01-03

xlsx 转 json

const fs = require('fs');
const xlsx2json = require('xlsx2json');
xlsx2json(
    './文档.xlsx', // url
    {
        dataStartingRow: 3,  // 第几行开始
        mapping: {    // 解析 key value
            'key1': 'A',
            'key2': 'B',
        }
    }
).then(jsonArray => {  // 输出数组  格式自行log
    fs.writeFileSync('./文档.json', JSON.stringify(jsonArray));
});

json 转 xlsx

const fs = require('fs');
const json2xls = require('json2xls');
const json = require("./json");
let jsonArr = [];
for (let jsonKey in json) {
    jsonArr.push({
        "A": json[jsonKey].cn, // A 内容
        "B": json[jsonKey].en, // B 内容
    });
}
fs.writeFileSync('./data.xlsx', json2xls(jsonArr), 'binary');


阅读全文