npm start 的时候,可能会遇到 Something is already running on port 8000. 这样的问题。要不关掉占用 8080 端口的进程,要不使用别的端口来运行 npm server 。
win 下关闭 8080 端口,可以直接使用任务管理器。建议还是用其他端口比较好,可能 8080 端口的进程刚好是自己需要的。
# 使用 8090 端口来启动进程
port=8090 npm start
npm start 的时候,可能会遇到 Something is already running on port 8000. 这样的问题。要不关掉占用 8080 端口的进程,要不使用别的端口来运行 npm server 。
win 下关闭 8080 端口,可以直接使用任务管理器。建议还是用其他端口比较好,可能 8080 端口的进程刚好是自己需要的。
# 使用 8090 端口来启动进程
port=8090 npm start
官网文档:https://pro.ant.design/docs/i18n-cn
可以配置在 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': '请输入賬號',
}
# 使用 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 又不行。真为难。
不喜欢官方的登录注册页,不仅繁琐,还不好看。
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 都是空的。要实现登录注册的功能,就得完善它们。
为了早点体验到 ant design pro 的页面,注释了部分配置,显示出了 welcome 页面。可是当你刷新页面时候,到你面前的就是 404 。这里,就要先解决 404 问题。
Ant Design Pro 使用的 Umi 支持两种路由方式:browserHistory
和 hashHistory
。默认就是 browserHistory
。这样漂亮好看。而默认就是这种方式。具体请看:
虽然这种方式漂亮好看。可对于解析认识是个问题。需要服务端进行处理。因为只使用过 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/ 下,可以单独修改,重启。但这样重启后,会再生。清理的脚步在本地的 Homestead
的 scripts/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 这个文件夹。当然,文件的后缀,上边已先列出。
socket.io 默认支持断开重连。可经常断开长连也很不好。短时间内(比如一分钟)断开重连这种现象,很容易观察到,并且可以观察到时间都很准时,每经过某个时间就会断开然后重连。
遇到这个现象,该怎么处理呢。如果熟悉 socket.io
的机制,可能想到心跳问题。这里先跳过。
遇到这个现象 ,可能你会找服务端的原因。是 nginx 出问题了,还是实现 socket.io 的服务出现问题了。(socket.io 是 websocket 的包装版)
# 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;
找不到具体原因,然后去看 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 的这种连接方式。
上边虽然解决了不再一分钟断开又重连。可是这个心跳频率还是有点高。因为上边提到的一分钟就断开,导致没办法把心跳设置大一点。这个问题需要处理。还是得去了解源码,进一步分析吧。
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
uniapp 项目默认是没有 package.json,也就是默认不支持 npm 安装第三方包。有时候需要第三方包,这个时候自己就可以配置使用了。
npm init -y
npm install gsap
import { TweenLite } from 'gsap/TweenMax'
layer.open 弹出一个对象,有时候会遇到高度自适应的问题。该怎么处理呢。文档虽然说明了怎么做,也的确是高度随着对象的变化,高度也变化了。但是弹层并没有对位置自适应。也就是没居中。其实在对象变化后,手动调用一次 $(window).resize();
即可。
area
只设置一个宽度值,或设置成 auto
。$(window).resize();
layer.open({
……,
area: '720px',
……
});
# 千万不要下边这种
layer.open({
……,
area: ['720px'],
……
});
# 虽然 area 设置成数组形式表示的是宽高。一个值时就是宽。但是只设置一个值时和上边的非数组形式不一样。也就是不会出现自适应。
layer.open({
……,
area: ['720px', '450px'],
……
});
就这么一点点了。
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();
});