blog's People
blog's Issues
防抖
防抖
需求
移动端上滑触底,加载下一页数据
为啥需要防抖
1. 性能:此处没有必要时时刻刻监听滚动事件
2. 用户滑动屏幕过程中,Safari浏览器不执行js代码,用户停止滑动操作后,方可执行业务代码。
3. 在满足条件的时间里,用户滑动过程中会多次触发请求,需用户停止滑动一段时间后判断是否触发请求。
代码部分:
...
componentDidMount () {
// 200ms内只监听一次滚动事件
window.addEventListener('scroll', this.debounce(this.handleScroll, 200))
}
...
// 防抖
debounce = (fun, delay) => {
return (args) => {
clearTimeout(fun.id)
fun.id = setTimeout( () => {
fun.call(this, args)
}, delay)
}
}
// 滚动事件
handleScroll = async(e) => {
// 可视区域高度
let clientHeight = document.documentElement.clientHeight;
// 滚动条滚动高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// 滚动内容高度
let scrollHeight =document.documentElement.scrollHeight;
if(clientHeight + scrollTop > scrollHeight - 1) {
...
}
}
...
触底加载下一页的触发条件
可视区域高度 加 滚动条滚动高度 和 滚动内容高度 的大小关系
clientHeight + scrollTop scrollHeight
判断是否是微信浏览器
判断是否是微信浏览器
代码部分:
isWechat = ()=> {
let ua = navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == "micromessenger") {
return true
} else {
return false
}
}
React onClick
// React中:
<button onClick={this.handleClick}>
...
</button>
// 相当于原生js中
<button onclick="handleClick()">
...
</button>
// React 可能需要绑定this(取决于是类组件还是函数组件)
// 如果是类组件,绑定this的方式
// 第一种 参考 https://zh-hans.reactjs.org/docs/handling-events.html
constructor(props) {
...
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
...
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
...
render() {
return (
<button onClick={this.handleClick}>
...
</button>
);
}
// 第二种 箭头函数,也方便传参数
handleClick = () => {
// 写成箭头函数是为了不使用bind也能拿到this
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
<button onClick={() => this.handleClick()}>
...
</button>
// 第二种的 onClick={() => this.handleClick()}
// 实际上是
xxx = () => {
// 下面代码能用this
}
前端框架技术选型,Vue/React
vue 对比 react:
对比vue2和vue3,vue3的组合式API无疑是更好的选择,以下优势针对于vue3版本而言;react也仅指使用create react app方式构建的项目
- 优势
- 使用vite搭建vue3项目框架,自带路由管理,sass,less支持的很好,yarn装一下包即可使用,无需多余配置;react需要手动配置各种loader
- 组合式API,逻辑抽离方便,不再受限于生命周期函数;react也可以做到
- vite使用Rollup打包,速度大幅优于webpack,开发体验更好;react大多使用webpack,打包速度一般
- 劣势
- vue不支持IE11(reactive使用的是Proxy,IE全系不支持),react进行相关配置后支持IE9-IE11
- vue TS支持不够好;react天生支持
- vue2升级vue3有代价,社区相关轮子未必及时适配;react社区活跃度高,大版本升级更早,适配大概更全
总的来说,react就是f(state) = UI,仅仅是一个UI框架,其他的交给开发者自己配置;而vite生成的vue项目是一个功能更全的脚手架,帮开发者做了很多其他事(比如路由)。
能详细出一个React的umi-request的代理配置代码吗?
blog/笔记/umi-request&axios.md
docker run 传参数自定义前端请求proxy
.Dockerfile
# Base on offical Node.js Alpine image
FROM node:alpine
# Set working directory
WORKDIR /usr/app
# Install PM2 globally
RUN npm install --global pm2
# Copy package.json and package-lock.json before other files
# Utilise Docker cache to save re-installing dependencies if unchanged
COPY ./package*.json ./
# Install dependencies
RUN npm install --production
# Copy all files
COPY ./ ./
# Build app
RUN npm run build
# Expose the listening port
EXPOSE 8008
# Run container as non-root (unprivileged) user
# The node user is provided in the Node.js Alpine base image
USER node
# RUN sed -i "s/TRANSPORT_URL/$TRANSPORT_URL/g" ./next.config.js;
# Run npm start script with PM2 when container starts
CMD sed -i "s/TRANSPORT_URL/$TRANSPORT_URL/g" ./next.config.js; npm run build; "pm2-runtime" "npm" "--" "start"
docker build
docker build -t chatgpt-web:test .
docker run
// 注意使用"\\" 对字符 "/"进行转义
// 该命令输出sha256id
// chatgpt-web
docker run -u root -p 8008:8008 -d -e TRANSPORT_URL=159.1.32.95:3000\\/chat chatgpt-web:test
// chatgpt-transport
docker run -u root -p 3000:3000 -d -e OUTER_URL=192.168.0.75:12710 chatgpt-transport:test
查看logs
// 3分钟后左右查看日志,若出现: ready - started server on 0.0.0.0:8008, url: http://localhost:8008, 则说明前端项目重新打包并且启动完成
// 1分钟左右后查看日志,若出现: Nest application successfully started, 则说明后端项目重新打包并且启动完成
docker logs --since 5m sha256id
centos 8部署ChatGPT
下载安装node v18.10.0
wget https://nodejs.org/dist/v18.10.0/node-v18.10.0-linux-x64.tar.xz
tar -xvf node...tar.xz
mv node-v18.0.0-linux-x64 nodejs
ln -s /where/nodejs/bin/npm /usr/local/bin/
ln -s /where/nodejs/bin/node /usr/local/bin/ // where指该文件所在路径
node -v // 验证
安装nestjs
npm i -g @nestjs/cli
安装pm2
npm install pm2@latest -g
find / -name pm2
ln -s /where/bin/pm2 /usr/local/bin/ // where指该文件所在路径
安装yarn
npm install --global yarn
find / -name yarn
ln -s /where/bin/yarn /usr/local/bin/ // where指该文件所在路径
安装git
yum install curl-devel expat-devel gettext-devel \
openssl-devel zlib-devel
yum -y install git-core
git --version // 验证
配置.ssh_key
拉取代码并打包
git clone http://coderepo.com/projectname.git
cd projectname
yarn
yarn build
pm2启动
pm2 start dist/main.js --name <application_name>
本地存储历史记录
本地存储历史记录
需求
需要记录搜索记录,和后端同学交流后,决定浏览器本地记录数据
方案一 localStorage
localStorage可以存储5M左右的数据,且永久保存,存取方便
代码部分:
componentDidMount () {
// 取值,记得倒序
let history = localStorage.getItem('history') ? localStorage.getItem('history').split('+') : []
...
}
// 跳转搜索方法
goSearchPage () => {
...
// 存值
localStorage.setItem('history', localStorage.getItem('history') ? `${localStorage.getItem('history')}+${searchValue}` : searchValue )
...
}
后来因为seo优化,四个频道拥有独立的子域名,localStorage无法满足跨域存取,上网搜索后可以通过postmessage方案解决,但时间比较紧张,解决方法看起来比较复杂,遂放弃,转向方案二
方案二 cookie
cookie只能皴4k左右的数据,一个汉字占两个字节,故能存取2000个左右汉字,关键是cookie能够跨域存取(二级域名要相同)
代码部分:
componentDidMount () {
// 取值,记得倒序
let history = !getCookie('mobileHistory')||isEmpty(getCookie('mobileHistory')) ? [] : [...JSON.parse(decodeURI(getCookie('mobileHistory')))]
...
}
// 跳转搜索方法
goSearchPage () => {
...
// 存值
let domain = '.znzmo.com'
let oldCookie = getCookie('mobileHistory') ? decodeURI(JSON.parse(getCookie('mobileHistory'))) : {}
if (!getCookie('mobileHistory')||isEmpty(oldCookie)) {
setCookie('mobileHistory', JSON.stringify([encodeURI(searchValue)]), 100000, domain)
} else {
// 只存最新的10个
if (oldCookie.split.length>10) {
setCookie('mobileHistory', JSON.stringify([...map(oldCookie.split(',').slice(-10), (item)=> encodeURI(item)), encodeURI(searchValue)]), 100000, domain)
} else {
setCookie('mobileHistory', JSON.stringify([...map(oldCookie.split(','), (item)=> encodeURI(item)), encodeURI(searchValue)]), 100000, domain)
}
}
...
}
实际开发中,遇到了一个非常🐶的问题,本地调的没问题了,测试环境用Chrome的手机模拟器使用也没问题,但到手机端直接报错。装了vconsole后,打印出来,提示cookie取值出了问题,再去网上搜一波,得知Safari不能存储非ASCII码的数据。故不得不在存值的时候encodeURI,在取值的时候decodeURI,解决。
附上封装的存取方法
// 设置cookie
export const setCookie = (cname, cvalue, exdays, domain) => {
var d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toUTCString();
domain = domain ? `domain = ${domain};` : ''
document.cookie = `${cname}=${cvalue};${expires};${domain}path=/`
}
// 获取cookie
export const getCookie = (cname) => {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
Flutter 记录
屏幕宽度百分比:MediaQuery.of(context).size.width * per
row均分
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
//交叉轴的布局方式,对于column来说就是水平方向的布局方式
crossAxisAlignment: CrossAxisAlignment.center,
//就是字child的垂直布局方向,向上还是向下
verticalDirection: VerticalDirection.down,
children: <Widget>[
Text('我是第1个'),
Text('我是第2个'),
Text('我是第3个'),
],
)
全局监听点击空白区域收起键盘
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
builder: (context, child) => Scaffold(
body: GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus &&
currentFocus.focusedChild != null) {
FocusManager.instance.primaryFocus.unfocus();
}
},
),
),
home: DismissKeyboardDemo(),
);
解决键盘弹起引发内容溢出的bug
body: SingleChildScrollView(
physics: new NeverScrollableScrollPhysics(), // 阻止滚动
child: ConstrainedBox(
constraints: new BoxConstraints(
minHeight: 300
),
child: Column(
children: <Widget>[]
)
)
给任意元素添加点击事件
InkWell(onTap: null, child: null) // 包裹的任意区域都可以触发点击
GestureDetector(onTap: null, child: null) // 包裹的有内容的区域可以触发点击
删除iOS编译文件,重新生成
flutter create -i swift .
base64转PDF 插件:
import 'dart:io';
import 'dart:convert';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart'; // full_pdf_viewer_scaffold
import 'dart:async';
import 'package:path_provider/path_provider.dart';
...
@override
void initState() {
asyncInit(202011061615348).then((f) {
print(f.path);
setState(() {
pathPDF = f.path;
});
});
super.initState();
}
...
Future<File> asyncInit (id) async {
var filename=id;
var pdfBase64 = await WorkOrderServe().getReport(id);
var pdfDataBytes = base64.decode(pdfBase64);
String dir = (await getApplicationDocumentsDirectory()).path;
File file = new File('$dir/${filename}.pdf');
await file.writeAsBytes(pdfDataBytes);
return file;
}
...
Build:
if (pathPDF!='') {
print(11);
return PDFViewerScaffold(
appBar: AppBar(
title: Text('诊断报告'),
backgroundColor: Color(int.parse('16212E',radix:16)).withAlpha(255),
),
path: pathPDF,
);
}
Unimplemented handling of missing static target 报错
Flutter Hot Restart 或者重启
修改.gitignore后
git rm -r --cached .
git add .
git commit -am "Remove ignored files"
git pull origin branch-name
git push origin branch-name
项目启动报错 Error connecting to the service protocol: failed to connect to http://127.0.0.1:49855/tUl8jpG1mhw=/
这是因为挂了代理,在~/.bash_profile中加export NO_PROXY=localhost,127.0.0.1,然后source 它,然后重启电脑。
如果是zsh,则加在~/.zshrc
📦 .apk后不能掉接口
在android/src/main/AndroidManifest.xml中添加:
...
</application>
// 新增代码
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> // 访问电话状态
<uses-permission android:name="android.permission.INTERNET" /> // 允许全部网络访问
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> // 获取网络信息状态
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> // 获取当前WiFi接入的状态以及WLAN热点的信息
...
📦 .apk方法 https://www.jianshu.com/p/fabcfd621e01
📦.apk报错 Could not determine the dependencies of task ':app:compileReleaseJavaWithJavac
在 工程名/android/bulid.gradle文件中修改
allprojects {
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
maven { url 'http://download.flutter.io' }
maven { url "https://storage.googleapis.com/download.flutter.io" }
}
}
Unhandled Exception: inheritFromWidgetOfExactType(_LocalizationsScope) or inheritFromElement() was called before _ScreenState.initState() completed
解决方法:把ininstate中的逻辑放到Future.delayed()中,或者放到didChangeDependencies生命周期中
输入格式限制代码如下,且需要 import 'package:flutter/services.dart';
inputFormatters: [
// 只能输入数字和英文字母
WhitelistingTextInputFormatter(
RegExp(
"[a-zA-Z]|[0-9]"
),
),
LengthLimitingTextInputFormatter(20), // 长度限制
],
自定义TabBar
Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Color(0xFFEEEEEE), width: 1))
),
child: TabBar(
controller: _tabController,
unselectedLabelColor: Color(0xFF74787C),
labelColor: Color(0xFF0860AB),
indicatorColor: Color(0xFF0860AB),
labelStyle: TextStyle(fontSize: 16),
indicator: UnderlineTabIndicator(
borderSide: BorderSide(width: 1.5),
insets: EdgeInsets.symmetric(horizontal: 34.0), // 自定义指示器的size
),
tabs: [
Tab(
child: Container(
height: 22,
child: Text('进度处理'),
),
),
Tab(
child: Container(
height: 22,
child: Text('故障分析'),
),
),
Tab(
child: Container(
height: 22,
child: Text('维修反馈'),
),
)
]
),
)
TabBarView报错
给Tabview外层包一个有宽高的容器
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: TabBarView(
controller: _tabController,
children: [
...
]),
),
顶部固定,底部固定的布局
class _WorkOrderDetailState extends State<WorkOrderDetail> with TickerProviderStateMixin {
final _topPartNotifier = ValueNotifier<int>(0);
GlobalKey tabsKey = GlobalKey();
TabController _tabController;
@override
void initState() {
_tabController = TabController(length: 3, vsync: this);
() async {
await Future.delayed(Duration.zero);
RenderBox renderBox = tabsKey.currentContext.findRenderObject(); // 获取TabBar对象
_topPartNotifier.value = renderBox.size.height.toInt() + MediaQueryData.fromWindow(window).padding.top.toInt() + kToolbarHeight.toInt();
// TabBar高度 + 状态条高度(需要 import 'dart:ui';) + appBar高度
// import 'dart:ui'; 拿 MediaQueryData.fromWindow(window)
}();
super.initState();
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('工单详情'),
),
body: SingleChildScrollView(
padding: EdgeInsets.only(bottom: 0),
physics: new NeverScrollableScrollPhysics(), // 阻止滚动
child: ConstrainedBox(
constraints: new BoxConstraints(
minHeight: 300
),
child: Column(
children: [
Container(
key: tabsKey,
child: TabBar(
controller: _tabController,
...
tabs: [
Tab(),
... ]
),
),
ValueListenableBuilder(
valueListenable: _topPartNotifier,
builder: (_, value, __) => Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - value,
// TabBarView外层需要包一个固定大小的容器,其中
// 高度 = 屏幕高度 - 顶部固定高度(状态条高度、appBar高度、TabBar高度);
// TabBarView 子组件中再使用一次SingleChildScrollView布局,
// 底部固定需要再使用Stack, Positioned 布局
child: TabBarView(
controller: _tabController,
children: [
DetailProgress(),
FailureAnalysis(),
RepairFeedback(),
]),
),
),
],
),
)
)
);
}
}
initState 拿不到context
void initState() {
() async {
await Future.delayed(Duration.zero);
... use(context, ...)
}();
}
报错 Type 'List' is not a subtype of type 'Widget'
解决方法:
children: ...List<Widget>
获取当前时间戳 DateTime.now().millisecondsSinceEpoch
flutter_local_notifications iOS端不能使用
在AppDelegate.swift文件中(使用vs code搜索关键词)加一些代码
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 增加的部分
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
判断手机系统平台
import 'dart:io' show Platform;
if (Platform.isAndroid) {
// Android-specific code
} else if (Platform.isIOS) {
// iOS-specific code
}
flutter 抓包
static void init() {
// 在调试模式下需要抓包调试,所以使用代理,并禁用HTTPS证书校验
if(!Global.isRelease) {
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.findProxy = (uri) {
return "PROXY localhost:8866";
};
// 代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以禁止证书校验
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
};
}
}
无context使用Provider https://github.com/fluttercommunity/get_it
Could not open settings remapped class cache for 3n8ygcxrc7nelqcw5tzib8bo6
https://stackoverflow.com/questions/49758849/flutter-io-android-license-status-unknown
Unable to find bundled Java version on Flutter
https://stackoverflow.com/questions/51281702/unable-to-find-bundled-java-version-on-flutter
Flutter Insecure http is not allowed by platform
https://stackoverflow.com/questions/64172791/flutter-insecure-http-is-not-allowed-by-platform
iPhone新机型吸底适配
iPhone新机型吸底适配
问题:
iPhoneX 及后来的苹果机型取消了物理按键,改成底部小黑条,导致常规吸底效果体验很差。
解决方案:
解决方法分三步
1. 设置网页在可视窗口的布局方式
viweport-fit 属性,使得页面内容完全覆盖整个窗口:
contain: 可视窗口完全包含网页内容(左图)
cover:网页内容完全覆盖可视窗口(右图)
auto:默认值,跟 contain 表现一致
<meta name="viewport" content="width=device-width, viewport-fit=cover">
2. 页面主体内容限定在安全区域内
如果不设置这个值,可能存在小黑条遮挡页面最底部内容的情况
body {
// 兼容 iOS < 11.2
padding-bottom: constant(safe-area-inset-bottom);
// 兼容 iOS >= 11.2
padding-bottom: env(safe-area-inset-bottom);
}
3. fixed 元素的适配
注意,这个方案需要吸底条必须是有背景色的,因为扩展的部分背景是跟随外容器的,否则出现镂空情况。
// 可以通过加内边距 padding 扩展高度:
{
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
// 或者通过计算函数 calc 覆盖原来高度:
{
height: calc(60px(实际情况决定) + constant(safe-area-inset-bottom));
height: calc(60px(实际情况决定) + env(safe-area-inset-bottom));
}
neovim安装配置
E117 Uknown function plug#begin
$ cd .config/nvim/
$ ln -s ~/.vim/autoload/ .
fix resuelto
吸顶
吸顶
代码部分:
...
componentDidMount () {
// filterOffsetTop 目标元素初始位置距离顶部高度
filterOffsetTop = document.getElementById('filter').offsetTop
// 监听滚动事件
window.addEventListener('scroll', this.ceiling)
}
...
ceiling = () => {
//滚动条滚动高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// headerHeight 目标元素吸顶时距离顶部的高度
if(scrollTop + headerHeight > filterOffsetTop) {
this.setState({
isFixedNavTab: true,
})
} else {
this.setState({
isFixedNavTab: false,
})
}
}
...
render () {
return (
<div id='filter' className={!isFixedNavTab ? style.navTabWrap : [style.navTabWrap, style.fixedNavTabWrap].join(' ')}>
...
</div>
)
}
为了实现“平滑”的吸顶效果,需要
1. 获取精确的目标元素的初始高度
filterOffsetTop = document.getElementById('filter').offsetTop
2. 计算精确吸顶的临界状态
滚动条滚动高度 加 目标元素吸顶状态时距离顶部的高度(通常为0,实际需求确定) 和 目标元素初始高度 大小的关系,区分是否加载fixed样式类
scrollTop + headerHeight filterOffsetTop
解决第三方资源跨域和防盗链问题
解决跨域问题
资源中存在 crossorigin="anonymous",要去除,如
detailHtml?.replace(/crossorigin="anonymous"/g, '')
解决放到链问题
去除请求头中的referrer字段
<meta name="referrer" content="never">
// 如果不想去除所有请求头中的该字段,可动态修改 ,如
// 可在head中先不去除
<meta id="referrer" name="referrer" content="always" />
// 需要去除时去除
const referrer = document.getElementById("referrer");
referrer.setAttribute("content", "never")
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.