Write the Code. Change the World.

分类目录
4月 13

npm start 的时候,可能会遇到 Something is already running on port 8000. 这样的问题。要不关掉占用 8080 端口的进程,要不使用别的端口来运行 npm server 。

win 下关闭 8080 端口,可以直接使用任务管理器。建议还是用其他端口比较好,可能 8080 端口的进程刚好是自己需要的。

# 使用 8090 端口来启动进程
port=8090 npm start
4月 10

官网文档:https://pro.ant.design/docs/i18n-cn

使用

  1. 先配置。
可以配置在 src/locales/ 中,也可以配置在各自的组件目录中。
比如,新建了一个模块 TestForm, locales 就可以放在这里边。个人觉得,各个模块的 locales 放在各自的目录下表好。包括 model service 这些。

# locales/zh-CN.ts
export default {
    'login.formlogin.login': '登录',
    'login.formlogin.register': '注册',
    'login.formlogin.account': '账号',
    'login.formlogin.account-placeholder': '请输入账号',
}  
# locales/zh-TW.ts
export default {
    'login.formlogin.login': '登錄',
    'login.formlogin.register': '註冊',
    'login.formlogin.account': '賬號',
    'login.formlogin.account-placeholder': '请输入賬號',
}
  1. 再使用。
# 使用 FormattedMessage 或 使用 formatMessage 

import { FormattedMessage, formatMessage } from "umi";

<Form.Item label={<FormattedMessage 
id="login.formlogin.account" />} 
rules={[{ required: true, message: formatMessage({id:'login.formlogin.account-placeholder'}) }]}>
    <Input placeholder={formatMessage({id:'login.formlogin.account-placeholde'})}/>
</Form.Item>

不过,使用 formatMessage 的时候,会报下边的警告。

Warning: Using this API will cause automatic refresh when switching languages, please use useIntl or injectIntl.

使用此 api 会造成切换语言的时候无法自动刷新,请使用 useIntl 或 injectIntl。

可是对 placeholder 如果不使用 formatMessage 又不行。真为难。

下边一个简单的 login 的列子

不喜欢官方的登录注册页,不仅繁琐,还不好看。

index.tsx

import { connect } from 'umi';
import { ConnectState } from '@/models/connect';

import FormLogin from './FormLogin';

const Login = () => {
    const onLoginSubmit = (value:any) => {
        console.log(value);
    }

    return (
        <FormLogin onLoginSubmit={onLoginSubmit} ></FormLogin>
    );
};


export default connect(({ login, loading }: ConnectState) => ({
    userLogin: login,
    submitting: loading.effects['login/login'],
}))(Login);

locales/zh-CN.ts

export default {
    'login.formlogin.login': '登录',
    'login.formlogin.register': '注册',
    'login.formlogin.account': '账号',
    'login.formlogin.account-placeholder': '请输入账号',
    'login.formlogin.account-rule': '请输入正确的账号格式(手机号码)',
    'login.formlogin.password': '密码',
    'login.formlogin.password-placeholder': '请输入密码',
    'login.formlogin.password-rule': '密码不能为空',
    'login.formlogin.remember-password': '记住密码',
    'login.formlogin.forget-password': '忘记密码',
    'login.formlogin.none-account': '如果没有账号,请',
}

FormLogin/index.tsx

import React from "react";
import styles from "./index.less";
import { FormattedMessage, formatMessage } from "umi";
import { Card, Form, Input, Button, Checkbox } from "antd";

const itemLayout = {
  labelCol: {
    xs: { span: 24 },
    sm: { span: 6 },
  },
  wrapperCol: {
    xs: { span: 24 },
    sm: { span: 12 },
  },
};

const tailLayout = {
  wrapperCol: {
    xs: { span: 24 },
    sm: { offset:6, span: 12 }
  },
};

const NormalLoginForm = (props:any) => {
  return (
    <Card bordered={false} title={<FormattedMessage id='login.formlogin.login' />} style={{ width: 720 }}>
      <Form
        {...itemLayout}
        initialValues={{ remember: true }}
        onFinish={props.onLoginSubmit}
      >
        <Form.Item name="account" label={<FormattedMessage id='login.formlogin.account' />} rules={[{ required: true, pattern: /^1[3456789]\d{9}$/ , message: <FormattedMessage id='login.formlogin.account-rule' /> }]}>
          <Input placeholder={ formatMessage({ id: 'login.formlogin.account-placeholder' })} />
        </Form.Item>

        <Form.Item name="password" label={<FormattedMessage id="login.formlogin.password" />} rules={[{ required: true, message: <FormattedMessage id='login.formlogin.password-rule' /> }]}>
          <Input.Password placeholder={formatMessage({ id: 'login.formlogin.password-placeholder' })} />
        </Form.Item>

        <Form.Item {...tailLayout} name="remember" valuePropName="checked">
          <Checkbox>
            <FormattedMessage id="login.formlogin.remember-password" />
          </Checkbox>
        </Form.Item>

        <Form.Item {...tailLayout}>
          <Button type="primary" htmlType="submit">
            <FormattedMessage id='login.formlogin.login' />
          </Button>

          <Button type="link" href="https://baidu.com" target="_blank">
            <FormattedMessage id='login.formlogin.forget-password' />
          </Button>
        </Form.Item>

        <Form.Item {...tailLayout} className="other-way">
            <span><FormattedMessage id='login.formlogin.none-account' /></span>
            <Button type="link" href="https://baidu.com" size="small">
              <FormattedMessage id='login.formlogin.register' />
            </Button>
        </Form.Item>
      </Form>
    </Card>
  );
};

export default (props:any) => (
  <div className={styles.container}>
    <div id="form-login">
      <NormalLoginForm onLoginSubmit={props.onLoginSubmit} />
    </div>
  </div>
);

FormLogin/index.less

.container {
  height: 100%;

  :global {
    #form-login {
      display: flex;
      flex-direction: row;
      justify-content: center;
      align-items: center;
      width: 100%;
      height: 100%;
      background-color: rgba(255,0,0,0.1);

      .login-form {
        width: 100%;
        max-width: 720px;
      }
    }

    .other-way {
      margin-top: 20px;
      margin-bottom: 0;
    }
  }
}

到目前为止,service,model 都是空的。要实现登录注册的功能,就得完善它们。

1月 11

为了早点体验到 ant design pro 的页面,注释了部分配置,显示出了 welcome 页面。可是当你刷新页面时候,到你面前的就是 404 。这里,就要先解决 404 问题。

Ant Design Pro 使用的 Umi 支持两种路由方式:browserHistoryhashHistory。默认就是 browserHistory。这样漂亮好看。而默认就是这种方式。具体请看:

https://pro.ant.design/docs/deploy-cn#%E5%89%8D%E7%AB%AF%E8%B7%AF%E7%94%B1%E4%B8%8E%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%9A%84%E7%BB%93%E5%90%88

虽然这种方式漂亮好看。可对于解析认识是个问题。需要服务端进行处理。因为只使用过 nginx,这里就说下 nginx 下的解决方法。

找到对应的 nginx 的配置文件,并修改文件。

如果 ant design pro 编译后的文件直接在根目录下,可以在配置文件中增加

    location /  {
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

如果 ant design pro 编译后的文件不再根目录下(比如在 dashboard 下),可以在配置文件中增加

    location /dashboard  {
        index index.html index.htm;
        try_files $uri $uri/ /dashboard/index.html;
    }

然后保存重启 nginx ,再刷新页面,发现不再是 404 了。

nginx -s reload

homestead 中,虚拟机里的配置文件在 /etc/nginx/sites-available/ 下,可以单独修改,重启。但这样重启后,会再生。清理的脚步在本地的 Homesteadscripts/clear-nginx.sh 中。如果单独对某个域名处理,最好在本地的 scripts/site-types/ 下新建配置,然后 Homestead.yml 文件中,对具体域名指定配置。

多的话就不说了,具体还是 nginx 的修改配置。 至于 nginx 的 try_files 是什么意思了,可以了解下。

try_files 的核心目的就是替代。比如,

location /dashboard  {
    index index.html index.htm;
    try_files $uri $uri/ /index.php?q=$uri&$args;
}

以 根目录(对应站点的根目录)下的 dashboard 目录为参照,先查找第一个节点,看文件是否存在,如果不存在再看文件夹(有斜杠)是否存在,如果再不存在,就指向到第三个参数。

比如具体的 url 是这样的。

https://www.mlxiu.com/dashboard/love

它会先检查 love 这个文件是否存在,如果不存在 love 这个文件则检查 love 这个文件夹。当然,文件的后缀,上边已先列出。

具体看 nginx 的 try_files

https://blog.csdn.net/a519640026/article/details/9138329

https://www.cnblogs.com/boundless-sky/p/9459775.html

1月 03

socket.io 默认支持断开重连。可经常断开长连也很不好。短时间内(比如一分钟)断开重连这种现象,很容易观察到,并且可以观察到时间都很准时,每经过某个时间就会断开然后重连。

遇到这个现象,该怎么处理呢。如果熟悉 socket.io 的机制,可能想到心跳问题。这里先跳过。

遇到这个现象 ,可能你会找服务端的原因。是 nginx 出问题了,还是实现 socket.io 的服务出现问题了。(socket.io 是 websocket 的包装版)

  1. 观察 nginx,因为 nginx 的代码最少。发现 nginx timeout 都注释掉了。
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout: Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds; At the same time, this close behavior is also affected by heartbeat setting of Swoole.
# proxy_read_timeout 60s;
  1. 分析框架源码,这个是最漫长,和可能性大的。毕竟用到别人的框架。这里使用 swoole + laravels + websocket 的 socket.io 的封装。找了好久也没找到问题。

  2. 找不到具体原因,然后去看 socket.io 的文档。才发现,心跳没发送是一个原因。

https://socket.io/docs/server-api/#new-Server-httpServer-options

重述具体现象

前端页面连接好 socket.io 后,如果前端不发送任何消息,60 秒内,服务端就会断开服务。然后重连。如果在 60 秒之内,你有发送消息,这个断开时间就会延长。直到你不发消息后的 60 秒。那如果这样,利用 socket.io 的心跳不就解决了。可是,从始至终,都没看见过发送过心跳。原来是服务端设置的心跳时间太长了,远远超过了 60 秒。也就是心跳还没来得及发,服务端就已经断开了,前端就已经又重连了。既然这样,那控制心跳时间小于 60 秒不就好了。

既然是一种方法,那么前端 socket.io 怎么发送这个心跳呢。

前端连接好服务端后,服务端会推送一个消息下来,这个消息包裹以下信息:

0{"sid":"NWUwZWY4NzBiYjk1OQ==","upgrades":[],"pingInterval":44000,"pingTimeout":36000}

就是以 pingInterval 这个频率来发送心跳。 pingInterval 要大于 pingTimeout。 这样就可以很直接的看到心跳的间隔。修改服务端代码,onOpen 的时候,推送这个消息过来就好,并 pingInterval 是正确的值。

心跳就是 ping pong。一来一回,建立起来就好。

末了

socket.io 是 websocket 的封装版,就是在 websocket 上加入了约定好的算法以及计算,来实现 socket.io 的这种连接方式。

上边虽然解决了不再一分钟断开又重连。可是这个心跳频率还是有点高。因为上边提到的一分钟就断开,导致没办法把心跳设置大一点。这个问题需要处理。还是得去了解源码,进一步分析吧。

12月 04

npm config set registry https://registry.npm.taobao.org 

npm config get registry 
– 或npm info express

安装 ant design pro

npm create umi

# 选择 ant design pro
# 选择 typescript或javascript
9月 06

layer.open 弹出一个对象,有时候会遇到高度自适应的问题。该怎么处理呢。文档虽然说明了怎么做,也的确是高度随着对象的变化,高度也变化了。但是弹层并没有对位置自适应。也就是没居中。其实在对象变化后,手动调用一次 $(window).resize(); 即可。

操作

  1. layer.open 的时候,area 只设置一个宽度值,或设置成 auto
  2. 弹出对象变化的时候,调用一次 $(window).resize();
layer.open({
    ……,
    area: '720px',
    ……
});

# 千万不要下边这种
layer.open({
    ……,
    area: ['720px'],
    ……
});

# 虽然 area 设置成数组形式表示的是宽高。一个值时就是宽。但是只设置一个值时和上边的非数组形式不一样。也就是不会出现自适应。

layer.open({
    ……,
    area: ['720px', '450px'],
    ……
});

就这么一点点了。

8月 31

layui 有第三方比较好的三级联动,多级联动插件。但有时候,因为特殊要求,满足不了需要。这个时候就需要修改源码了。

先看插件文档以及实例。

https://fly.layui.com/extend/selectN,selectM/#doc

https://moretop.gitee.io/layui-select-ext/

特殊需要

1,需要回调。虽然不需要回调 form 提交一样可以拿到数据。可有时候前端就是需要有回调,需要看到具体的变化。这个时候就得改源码了。

2,删除对象时候,需要 confirm 确定后,才能删除。

修改

selectN 三级联动全只读。在初始化的时候,删除 select dom 即可。当然,也需要从 config 中添加配置,读配置。

//只读删除 select 组件,删除图标
if (c.allRead) {
    $E.find('.layui-anim').remove();
    $E.find('.layui-edge').remove();    
    $E.find('input').attr("readonly", true);
}

selectM 回调。在 config 中配置回调函数,并在初始化的时候指定好回调函数。在数据变化的时候,判断下是否有回调函数。存在就回调过去。

if (c.change && typeof c.change === "function" && typeof c.change.nodeType !== "number") {
        c.change(values);
}

selectM 删除确定。同样在 config 中配置变量,默认不需要确定。初始化的时候配置好即可。当需要删除对象的时候,先看是否存在需要确认的变量。不需要则直接删除。需要则先弹出 confirm 框,看操作。

$E.on('click','a i',function(e){
    this.delete = function() {
        var _this = $(this).prev('span');
        var v = _this.attr('lay-value');
        if(v){
            var _dd = $(c.elem).find('dd[lay-value='+v+']');
            _dd.removeClass('layui-this');
            _dd.find('.layui-form-checkbox').removeClass('layui-form-checked');
        }
        o.setSelected();
        _this.parent().remove();
        e.stopPropagation();
    };

    if (o && o.config && o.config.confirm) {
        layer.confirm('您确定要删除该选项?', {btn:['确定']}, (index) =>{
            this.delete();
            layer.close(index);
        });
        return;
    }
    this.delete();
});