发动态
图文
列表
置顶
【元器件规范共建召集令】诚邀行业专家,定义行业规范新基准
当你在电子元器件选型时,是否因参数定义模糊反复试错?当你推进研发项目时,是否因标准不统一延误进度?如今,有一个能改变行业现状、为电子产业发展注入新动能的机会 —— 加入立创商城电子元器件规范共建项目,与更多行业专家携手,打造科学、完善、权威的元器件参数规范体系!立创商城深耕电子元器件电商领域多年,深知统一精准的参数规范对行业上下游的重要性。我们正启动一项开创性工程,现面向全国电子元器件行业规范制定人、电子行业从业者、电子专业教育从业者、资深领域电子爱好者等群体招募 20-50 名细分领域专家,涵盖接口芯片、时钟和定时、射频无线、传感器等 9 大核心方向,邀你成为这场 “规范革命” 的 “执笔人”。1、你将参与的核心领域(涵盖9大方向)接口芯片USB、PCIe、CAN芯片等接口芯片的设计关注核心参数范围划定及其参数名词解释时钟和定时晶振、定时器、时钟发生器等震荡器的设计关注核心参数范围划定及其参数名词解释射频无线RF芯片、天线模块、无线收发器等无线射频相关器件的的设计关注核心参数范围划定及其参数名词解释传感器温度、压力、光电等传感器的设计关注核心参数范围划定及其参数名词解释功能模块电源管理、信号调理模块等电子模块的设计关注核心参数范围划定及其参数名词解释物联网/通信模块5G、WiFi、蓝牙模块等无线通讯模块的设计关注核心参数范围划定及其参数名词解释单片机/微控制器ST、TI、STC等单片机器件的设计关注核心参数范围划定及其参数名词解释逻辑器件和数据转换ADC/DAC、逻辑门等与信号转换和数据转换相关的设计关注核心参数范围划定及其参数名词解释显示屏器件OLED、LCD等显示屏的设计关注核心参数范围划定及其参数名词解释 2、你的角色:从技术实践者到标准制定者评审与优化:针对公司内部团队起草的规范初稿(如参数定义、填写规范、案例模板),以专业视角审核逻辑严谨性,提出修改建议(例如隔离电压、CMTI等参数的单位换算、优先级规则);深度参与:基于实操经验,为芯片引脚定义、数据速率计算、温度范围界定等参数提供行业实践案例,确保规范兼具理论准确性与工程可行性;成果共创:与跨领域专家协作,构建类似“电子元器件维基百科”的公开规范网站,让技术标准真正服务行业生态。3、我们为你提供的四大价值回报「行业署名权」:每一份经你评审修改的规范,均将在最终版本中明确标注你的姓名与单位,成为个人技术生涯的权威背书;「品牌曝光度」:规范公开时,参与评审与编撰的专家名单将同步公示,通过公司官方渠道(行业媒体、技术社区)定向推送,提升行业影响力;「知识共享平台」:加入电子元器件规范维基网站建设,你的技术见解将被全球工程师查阅引用,成为领域内的“隐形标准制定者”;「多样激励体系」:任务制,每次任务均有丰厚报酬奖励,根据审核规范复杂度与贡献度可获取,包括且不限于京东E卡/采购晶/优惠券/实物奖励等,多劳多得激励形式:1、积分制每次任务,每人均可获得积分,根据每人贡献程度获得对应积分贡献程度人数获得积分皇冠125黄金315白银610青铜105 2、积分可兑换礼品积分数兑换礼品价值550E卡或50采购晶50元10100元E卡或100元采购晶100元20200元E卡或200元采购晶200元50500元E卡或500元采购晶500元1001000元E卡或1000元采购晶1000元2002000元E卡或2000元采购晶2000元 4、为什么工程师值得加入?技术价值升华:从“用标准”到“定标准”,让你的经验成为行业参照坐标; 资源链接机遇:与芯片原厂、方案商专家深度交流,拓展技术人脉圈; 职业发展加分:参与行业级规范制定的经历,是技术管理岗晋升的硬核背书。5、报名方式如果您在上述领域拥有多年以上研发/设计经验,或主导过元器件选型与参数验证项目,欢迎将个人简历(附技术专长说明)发送至:,邮件主题注明“【规范专家报名】+领域方向”。我们将在3个工作日内与您联系,共商规范共建蓝图。 电子元器件的每一个参数,都承载着工程师的智慧。现在,你就有机会成为定义行业规范的 “少数派”,让全球工程师使用你参与制定的标准。这不仅是一次技术实践,更是一段能为行业留下深刻印记、为职业增添高光的宝贵经历。立创商城期待与你携手,重塑元器件参数规范行业标杆,让你的技术印记,刻进行业未来! 注:“本次共建采用灵活协作模式,单次任务预计耗时2~4小时,全程线上进行,不影响日常工作。”
【元器件规范共建召集令】诚邀行业专家,定义行业规范新基准
立创商城
一、方案背景在前端中经常会出现多接口并发的场景,而针对请求常规方式会一股脑发送给浏览器,浏览器中会对大量请求进行排队,但大量请求推送至浏览器就会占用内存,最后达到一定量级导致页面卡顿,甚至假死常规请求图如下二、解决思路针对此模式,我们可以创建如下序列,待发送请求池作为中间层,只作为载体,每次推送固定数量到浏览器,当浏览器主线程中存在接口执行完毕后,代表主线程中接口数量减少,在从待发送请求中取出新的接口放入主线程,从而实现控制主线程发送数量避免内存溢出创建待发送请求池控制最大并行请求数量指定接口缓存,如数据源,结合请求,响应拦截器进行处理流程图如下<<<顺手说一个:有新的技术大厂在→要人,前端/后端还有测试,一线城市及双一线城市几乎都有坑位,待遇稳定性都还成,感兴趣的可以瞅瞅~三、具体代码实现请求并发控制方案概述这是一个基于请求池(Request Pool)的并发控制方案,用于管理前端应用中的 HTTP 请求。通过双队列机制控制并发数量,避免同时发起过多请求导致的性能问题和浏览器限制。核心特性并发控制:限制同时执行的请求数量(默认 20 个)双队列设计:待执行队列(pendingQueue)+ 执行中队列(runningQueue)请求取消:支持取消单个或全部请求,基于 AbortController缓存支持:可配置的请求结果缓存机制内存优化:请求完成后自动清理引用,防止内存泄漏非阻塞调度:使用 setTimeout(0) 让出主线程,避免阻塞 UI防重复调度:通过 isProcessing 标志位避免重复调度架构设计1. 状态管理const poolState = { pendingQueue: [], // 待执行队列 runningQueue: [], // 执行中队列 cacheStore: new Map(), // 缓存存储 maxConcurrent: 20, // 最大并发数 maxPendingQueue: Infinity, // 待执行队列最大长度 isProcessing: false // 防止重复调度标志位 }; 2、核心机制防重复调度使用 isProcessing 标志位防止多次调用 scheduleExecute() 时重复创建定时器: const scheduleExecute = () => { if (poolState.isProcessing) return; // 已在处理中,直接返回 poolState.isProcessing = true; setTimeout(() => { processQueue(); poolState.isProcessing = false; // 处理完成,重置标志 }, 0); }; 批量处理每次调度时,根据可用槽位批量启动请求:const processQueue = () => { // 计算可用槽位 const slots = poolState.maxConcurrent - poolState.runningQueue.length; const count = Math.min(slots, poolState.pendingQueue.length); // 批量启动 for (let i = 0; i < count; i++) { const task = poolState.pendingQueue.shift(); if (task) { poolState.runningQueue.push(task); executeRequest(task); } } }; API 文档addRequest(config, axiosInstance)添加请求到队列参数:config (Object): axios 请求配置对象axiosInstance (Function): axios 实例返回:Promise: 请求的 Promise 对象示例:import axios from 'axios'; import { addRequest } from '@/utils/requestPool'; const config = { method: 'get', url: '/api/users', params: { page: 1 } }; addRequest(config, axios) .then(response => console.log(response.data)) .catch(error => console.error(error)); setMaxConcurrent(max)设置最大并发数参数:max (Number): 最大并发数示例:import { setMaxConcurrent } from '@/utils/requestPool'; // 设置最大并发数为 10 setMaxConcurrent(10); clearAllRequests()取消所有请求(包括待执行和执行中的请求)返回:Object: 取消的请求数量统计pending (Number): 待执行队列中取消的数量running (Number): 执行中队列中取消的数量示例:import { clearAllRequests } from '@/utils/requestPool'; const count = clearAllRequests(); console.log(`取消了 ${count.pending} 个待执行请求`); console.log(`取消了 ${count.running} 个执行中请求`); getPoolState()获取请求池当前状态(用于调试和监控)返回:Object: 请求池状态信息示例:import { getPoolState } from '@/utils/requestPool'; const state = getPoolState(); console.log('待执行:', state.pendingCount); console.log('执行中:', state.runningCount); console.log('缓存数:', state.cacheCount); getCacheKey(config)生成缓存键(预留功能)参数:config (Object): axios 配置对象,需包含 cacheParams 字段返回:String | null: 缓存键,如果未配置 cacheParams 则返回 nullgetCache(key) / setCache(key, data) / clearCache(key)缓存操作方法(预留功能)使用示例1. 基础集成 - Axios 拦截器在 axios 实例中集成请求池:// src/utils/request.js import axios from 'axios'; import { addRequest, clearAllRequests } from './requestPool'; const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 }); // 请求拦截器 service.interceptors.request.use( config => { // 标记使用请求池 config.usePool = true; return config; }, error => Promise.reject(error) ); // 响应拦截器 service.interceptors.response.use( response => response.data, error => { console.error('请求失败:', error); return Promise.reject(error); } ); // 重写 axios 请求方法,使用请求池 const originalRequest = service.request.bind(service); service.request = function(config) { if (config.usePool !== false) { // 使用请求池 return addRequest(config, originalRequest); } // 不使用请求池,直接请求 return originalRequest(config); }; export default service; export { clearAllRequests }; 2. API 调用示例 // src/api/user.js import request from '@/utils/request'; // 获取用户列表 export const getUserList = (params) => { return request({ url: '/api/users', method: 'get', params }); }; // 获取用户详情 export const getUserDetail = (id) => { return request({ url: `/api/users/${id}`, method: 'get' }); }; // 创建用户 export const createUser = (data) => { return request({ url: '/api/users', method: 'post', data }); }; 3. 组件中使用<template> <div> <button @click="loadData">加载数据</button> <button @click="cancelAll">取消所有请求</button> <div>待执行: {{ poolState.pendingCount }}</div> <div>执行中: {{ poolState.runningCount }}</div> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { getUserList } from '@/api/user'; import { clearAllRequests, getPoolState } from '@/utils/requestPool'; const poolState = ref({ pendingCount: 0, runningCount: 0 }); // 更新请求池状态 const updatePoolState = () => { const state = getPoolState(); poolState.value = state; }; // 加载数据 const loadData = async () => { try { // 同时发起 50 个请求,但只有 20 个会并发执行 const promises = Array.from({ length: 50 }, (_, i) => getUserList({ page: i + 1 }) ); // 定时更新状态 const timer = setInterval(updatePoolState, 100); const results = await Promise.all(promises); clearInterval(timer); console.log('所有请求完成:', results.length); } catch (error) { console.error('请求失败:', error); } }; // 取消所有请求 const cancelAll = () => { const count = clearAllRequests(); console.log(`已取消 ${count.pending + count.running} 个请求`); updatePoolState(); }; onMounted(() => { updatePoolState(); }); </script> 4. 路由切换时取消请求// src/router/index.js import { createRouter, createWebHistory } from 'vue-router'; import { clearAllRequests } from '@/utils/requestPool'; const router = createRouter({ history: createWebHistory(), routes: [ // 路由配置... ] }); // 路由切换前取消所有请求 router.beforeEach((to, from, next) => { if (from.path !== '/') { const count = clearAllRequests(); console.log(`路由切换,取消了 ${count.pending + count.running} 个请求`); } next(); }); export default router; 5. 批量请求示例// 批量加载用户详情 async function loadUserDetails(userIds) { const promises = userIds.map(id => getUserDetail(id)); try { const results = await Promise.all(promises); console.log('加载完成:', results); return results; } catch (error) { console.error('批量加载失败:', error); throw error; } } // 使用示例 const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; loadUserDetails(userIds); 6. 动态调整并发数import { setMaxConcurrent, getPoolState } from '@/utils/requestPool'; // 根据网络状况动态调整 function adjustConcurrency() { const connection = navigator.connection; if (connection) { const effectiveType = connection.effectiveType; switch (effectiveType) { case 'slow-2g': case '2g': setMaxConcurrent(5); break; case '3g': setMaxConcurrent(10); break; case '4g': default: setMaxConcurrent(20); break; } console.log('网络类型:', effectiveType); console.log('当前并发数:', getPoolState().maxConcurrent); } } // 监听网络变化 if (navigator.connection) { navigator.connection.addEventListener('change', adjustConcurrency); } 7. 请求优先级(扩展示例)如果需要实现请求优先级,可以扩展 addRequest 方法:// 扩展版本 - 支持优先级 export const addRequestWithPriority = (config, axiosInstance, priority = 0) => { return new Promise((resolve, reject) => { const abortController = new AbortController(); const task = { config: { ...config, signal: abortController.signal }, resolve, reject, axiosInstance, abortController, priority // 添加优先级字段 }; // 根据优先级插入队列 const index = poolState.pendingQueue.findIndex(t => t.priority < priority); if (index === -1) { poolState.pendingQueue.push(task); } else { poolState.pendingQueue.splice(index, 0, task); } scheduleExecute(); }); }; // 使用示例 addRequestWithPriority(config, axios, 10); // 高优先级 addRequestWithPriority(config, axios, 0); // 普通优先级 性能优化要点1. 内存管理请求完成后自动清理任务对象的引用:task.config = null; task.resolve = null; task.reject = null; task.axiosInstance = null; task.abortController = null; 2. 非阻塞调度使用 setTimeout(0) 让出主线程:setTimeout(() => { processQueue(); poolState.isProcessing = false; }, 0); 3. 批量处理一次调度处理多个请求,减少调度次数:const count = Math.min(slots, poolState.pendingQueue.length); for (let i = 0; i < count; i++) { // 批量启动 } 注意事项并发数设置:默认 20 个,可根据实际情况调整请求取消:路由切换或组件卸载时记得取消请求错误处理:已取消的请求不会触发 reject,避免重复处理缓存功能:当前代码中已预留,可根据需要启用浏览器限制:不同浏览器对同域名并发请求有限制(通常 6-8 个),但通过请求池可以更好地控制——转载自:米西米西1
前端必看!接口并发方案
开源硬件平台
上期我们利用FFImageLoad实现了图片流的显示,之前也有一期简单介绍了一下利用SkiaSharp实现绘图。 本期我们来实现一个摇杆的实现。public class DrawAble {     private readonly Rocker view; public DrawAble(Rocker view) {         this.view = view; } private void Draw_Circle(SKSurface surface, SKRect bounds) { float centerX = bounds.MidX; float centerY = bounds.MidY; Midx = centerX; Midy = centerY; // 计算圆的半径(使用ChargingRingDrawable类中的rad属性) float radius = CirCleRad; // 使用SKPaint对象定义圆的样式(颜色、线条宽度等) using (SKPaint paint = new SKPaint()) { paint.Color = SKColors.Blue; paint.Style = SKPaintStyle.Stroke; paint.StrokeWidth = 20; // 画圆 surface.Canvas.DrawCircle(centerX, centerY, radius, paint); }     } public void Draw(SKSurface surface, SKRect bounds) { surface.Canvas.Clear();         Draw_Circle(surface, bounds);//画圆轮廓     } } 首先定义两个类,一个是画板类,他必须有最基本的Draw函数用来给Sharp实现接口。 其构造函数传入我们的Rocker类,这个类我们在下面定义。 public partial class Rocker : SKCanvasView {         private readonly DrawAble drawable; public Rocker() {             this.EnableTouchEvents = true;// this.drawable = new ChargingRingDrawable(this);         } protected override void OnPaintSurface(SKPaintSurfaceEventArgs e) { this.drawable.Draw(e.Surface, e.Info.Rect); this.InvalidateSurface(); } protected override void OnTouch(SKTouchEventArgs e) {             base.OnTouch(e);         } } } 接着定义一个类,命名为Rocker,这个类抽象自SKCanvasView(SkiaSharp的控件) 定义一个画板,构造的时候传入自身。 我们要保留两个处理函数,一个是OnPaintSurface,我们的控件刷新就会调用这个函数,还有一个是OnTouch函数,这个函数用来处理我们的触摸事件。private void Draw_Circle(SKSurface surface, SKRect bounds) {     float centerX = bounds.MidX;//获得画板中心 float centerY = bounds.MidY;     Midx = centerX;//用一个参数保存画板中心 Midy = centerY; // 计算圆的半径(使用ChargingRingDrawable类中的rad属性) float radius = CirCleRad; // 使用SKPaint对象定义圆的样式(颜色、线条宽度等) using (SKPaint paint = new SKPaint()) { paint.Color = SKColors.Blue; paint.Style = SKPaintStyle.Stroke; paint.StrokeWidth = 20; // 画圆 surface.Canvas.DrawCircle(centerX, centerY, radius, paint); } } 在DrawAble类中有这样子一个函数,其作用是画一个基本的圆,我设置的大小是400像素。private void Draw_InsertCircle(SKCanvas canvas, SKPoint touchLocation, float radius) { // 控件的中心点 float centerX = Midx; float centerY = Midy; // 计算手的位置与圆的交点 SKPoint intersectionPoint = CalculateIntersectionPoint(centerX, centerY, 300, touchLocation); // 使用SKPaint对象定义圆的样式(颜色、线条宽度等) using (SKPaint paint = new SKPaint()) { paint.Color = SKColors.Red; paint.Style = SKPaintStyle.Fill; // 在交点位置绘制圆 canvas.DrawCircle(intersectionPoint.X, intersectionPoint.Y, radius, paint); LocationPoint = intersectionPoint; } } private SKPoint CalculateIntersectionPoint(float centerX, float centerY, float radius, SKPoint touchLocation) { // 计算手的位置与圆的交点 float dx = touchLocation.X - centerX; float dy = touchLocation.Y - centerY; float distance = (float)Math.Sqrt(dx * dx + dy * dy); // 如果距离超出阈值,将交点移动到圆上最近的点 if (distance > radius) { float scale = radius / distance; float intersectionX = centerX + dx * scale; float intersectionY = centerY + dy * scale; return new SKPoint(intersectionX, intersectionY); } // 如果距离未超出阈值,则返回手的位置作为交点 return touchLocation; } 接着画内部的摇杆内容,我们计算这个圆和控件中心的位置,设置一个阈值,保证我们画的圆在这个阈值之内,如果超过了阈值,就计算手的位置和圆的交点,再进行画圆。 实现这样子的效果。 接着,我们补全Rocker中触摸事件protected override void OnTouch(SKTouchEventArgs e) { base.OnTouch(e); switch (e.ActionType) { case SKTouchAction.Pressed: // 处理按下事件 break; case SKTouchAction.Moved: // 处理移动事件 SKPoint touchLocation = e.Location; drawable.InsertCirclePoint = touchLocation; OnPositionChanged(new SKPoint(drawable.LocationPoint.X-drawable.Midx,drawable.LocationPoint.Y-drawable.Midy)); break; case SKTouchAction.Released: SKPoint InsertCirclePoint = new SKPoint(drawable.Midx,drawable.Midy); drawable.InsertCirclePoint = InsertCirclePoint; // 使得画布无效,触发重绘 InvalidateSurface(); OnPositionChanged(new SKPoint(0,0)); break; case SKTouchAction.Cancelled: // 处理取消事件 break; } // 标记事件已处理 e.Handled = true; } 当移动时,将位置传给DrawAble中,DrawAble会根据手的位置绘制摇杆位置。 并且在放下的时候重新归位。 同时我们定义一个事件,向MainPage中传入位置信息。private void Rocker_PositionChanged(object sender, SKPoint newPosition) {    Label.Text = $"Position: ({newPosition.X}, {newPosition.Y})"; } MainPage中打印我们的位置信息。
.NET MAUI的Android WiFi图传开发(6)——利用SkiaSharp制作一个摇杆
嘉立创PCB
B站demo演示视频:我把传感器塞进网球避震器,测了一下发球速度【项目背景】网球发球训练缺乏即时量化反馈工具。现有方案(Zepp、Babolat Play等)体积大、数据维度有限,无法捕捉发力结构细节。做了一个避震器形态的传感器模块,目标是在不显著改变球拍挥重的前提下,采集发球过程中的关键力学参数。【迭代过程】V1(已废弃):- 直接用蓝牙模组内置MCU做开发,没有独立单片机- 想法是单IMU做完整动作轨迹重建- 实测结果:误差累计太大,加速度二次积分漂移无法在这个成本和体积下解决- 教训:没搞过固件就上蓝牙模组MCU开发,调试手段有限,效率极低V2(当前版本):- 砍掉了"全动作记录"需求,聚焦发球训练——单次重复动作,每次击球间可归零,回避积分漂移问题- 加了独立STM32单片机,开发调试效率大幅提升- 换了高量程陀螺仪——发球动作核心是旋转发力(内旋),陀螺仪数据能覆盖主要需求,不再依赖加速度积分【当前方案概述】MCU:STM32C011F6P6IMU:LSM6DSVTR蓝牙模组:PB-03f供电:软包锂电核心模块重量:5.2g(不含外壳,未做工程优化)算法输出指标:- 拍头速度(发球瞬间)- 掉拍头幅度- 发力速度- 手臂内旋发力占比【当前进展】V2硬件完成并验证- 算法和数据链路跑通- 与手机App的BLE通信稳定- 已完成多次球场实测验证,四项指标实时输出【下阶段目标】做工程样机,小型化、外壳优化、电池选型及充电设计。发工程样机给网球爱好者、教练、运动员测试,吸收反馈迭代产品功能。【期望交流】希望能与做过类似消费电子/穿戴设备的,有工程化经验的朋友交流。我的邮箱:nospoon@qq.com小红书(记录了开发的部分过程):there is no spoon#DIY设计#
DIY网球发球训练数据采集模块,希望能与感兴趣的朋友交流
开源硬件平台
前言如果你点的椰果奶茶被做成了珍珠奶茶,虽然也能喝,但就是完全不是你想要的,至少对于我这种有点强迫症的人。那么 JavaScript 就是这样一个 “随性” 的奶茶店老板,而 TypeScript 就是那个拿着订单反复跟你确认 “少糖少冰” 的靠谱店员,从根源上避免了 “错单” 的尴尬。用一句话来说其实就是:TypeScript 是更严谨的 JavaScript。一、有了 JavaScript 为什么还要有 TypeScript ?写 JavaScript 就像开盲盒,你永远不知道下一个变量里装的是 数字、字符串还是 薛定谔的 undefined。我统称它们为 盲盒变量。比如这段代码:let n = 1, m = 0; n = 'hello'; // 数字秒变字符串,JS 主打一个“灵活” function add(a, b) { if (typeof a === 'number' && typeof b === 'number') { return a + b; } } // 传入字符串,函数直接返回 undefined,Bug 这不就来了 console.log(add(1, '2')); 你以为你在写 “动态灵活” 的代码,其实是在给未来的自己 埋雷。比如上面这段代码,可能你知道等会要传 2个number类型,但是如果别人直接拿来Ctrl + cv 用你封装的函数,传了一个 string类型 那就坏了。直到 TypeScript 出现,让变量从 “盲盒” 变成了 “明码标价的商品”。二、弱类型:自由过了火就是混乱在上面代码中有这样一个情况:let n = 1; n = 'hello'; // 在 JavaScript里面不报错 如果你是 C++、Java或者Go的工程师,你肯定会觉得这人怕不是敲代码敲疯了吧。在C++、Java或者Golang里面这代码直接就报错了。这就是因为 JavaScript 是典型的弱类型语言,变量不需要提前声明类型,随时可以 “变身”。打印结果为 hello:你可以让数字 a 一秒变成字符串,编辑器连个警告都没有。这种 “自由” 在小项目里或许能跑,但项目一复杂,就会出现 add(1, '2') 这种隐蔽 Bug,排查起来堪比大海捞针。三、强类型:给变量上 “户口”TypeScript 作为 JavaScript 的超集,核心就是给变量加上了类型声明。首先要用 TypeScript,我们需要去下载它:npm install -g typescript # 全局安装 TypeScript tsc -v # 查看 TypeScript的版本 tsc project.ts # project 是你的文件名,编译 TypeScript文件 当编译完你会发现编译器给你编译出了一份对等的JavaScript文件:这个时候你就可以用Node.js去跑这份文件,因为 TypeScript 本身不能直接运行,需要先编译成 JavaScript 再运行。不过现在有一些工具可以简化这个过程,比如 ts-node、deno 等。我这里简要介绍下 ts-node的使用:ls package.json # 首先检查项目是否有 package.json 文件 npm init -y # 如果没有,初始化一个 npm install -g ts-node # 然后安装 ts-node npm install --save-dev ts-node # 或本地安装 # 运行 TypeScript文件 ts-node 2.ts # 全局安装时 npx ts-node 2.ts # 本地安装时 一般 TypeScript 都是在 React项目 等环境下运行,所以直接运行一个文件的比较少见,这里我们主要看 TypeScript 语法的使用和基础知识。同样的代码,在 TypeScript 里面就会报错:let a: number = 1; a = 'hello'; // 编辑器直接标红:不能将类型 “string” 分配给类型 “number” console.log(a); 细心的你很快就发现了猫腻:TypeScript 相较于 JavaScript 不同的地方就在于 TypeScript 的写法中明确标注了变量是什么类型。就比如这里 a 被明确声明为 number 类型,如果你想把它改成字符串,TypeScript 会立刻报错,把问题扼杀在编码阶段。这样就使得文件更加严谨。<<<顺便说句,技术大厂,前后端-测试机会,一线双一线城市坑位充足,感兴趣可以看看这个~四、TypeScript 数据类型全家桶在之前我写过几篇 JavaScript数据类型 的文章,那我们现在来看看 TypeScript 类型全家桶,他们并不完全一样,但还是有很高的相似度。比如这段代码:let isDone: boolean = false; // boolean类型 let count: number = 123; // number类型 let str: string = 'Trae'; // string类型 const symbol: symbol = Symbol(); // symbol类型 let obj: object = { [symbol]: 'Trae' // object类型(对象) }; let list: number[] = [1, 2, 3]; // array类型(数组) enum Color { Red, Green, // 类似于结构体 Blue } let color: Color = Color.Red; let notSure: any = 10; // any类型 notSure = '123'; // any 类型可以随便变,是 TypeScript 里的 “漏网之鱼” let value: unknown = 10; // unknown类型 value = '123'; let abc: string = 'hello'; // unknown 类型不能直接赋值给其他类型,比 any 更安全 // abc = value; // 报错 abc = notSure; // 不报错 let tuple: [number, string] = [10, 'hello']; // 元组:固定长度和类型的数组 function user1(): number { return 123; } function user2(): Function { return function fn(): number { return 123; } } // 报错 // function user2(): string { // return 123; // } function user3(): void {} // void 表示没有返回值 let u: undefined = undefined; // undefined类型 let n: null = null; // null类型 基本上都与 JavaScript 相似,可以去看我之前写的 JavaScript数据类型。从基础的 boolean、number、string,到复杂的 enum、tuple、unknown,TypeScript 让每个变量都有了明确的 “身份”。这里有一个注意的点就是 unknown 类型 和 any类型。unknown 类型不能直接赋值给其他类型,而 any 类型可以随便变,所以下次报错的时候看看,是不是这个原因。五、对象与类型:不是所有空对象都一样TypeScript 对对象的类型约束更严格:const obj: object = {}; const obj2: Object = {}; const obj3: {} = {}; // 错误 // obj.a = 1; // 编译错误 // obj3.a = 1; // 编译错误 // 正确(类型断言) (obj2 as any).a = 1; console.log(obj2); // 输出: { a: 1 } const hello = 'hello'; const a: 'hello' = 'hello'; object、Object 和 {} 看似相似,实际约束力度不同;字面量类型更是把变量锁死在特定值上,杜绝了 “意外变身”。六、类型守卫🛡️:给你的代码装上 “火眼金睛”TypeScript 的类型守卫,就像给你的代码配上了一个智能安检员,能在运行时精准识别变量类型。// 类型守卫 interface Person { name: string; age: number; sex?: unknown; // 可选属性,不是每个人都需要填写 } const person: Person = { name: 'henry', age: 18, sex: '男' // 可选属性,写不写都不会报错 }; // 举个类型守卫的例子:判断一个值是不是 Person 类型 function isPerson(value: unknown): value is Person { return ( typeof value === 'object' && value !== null && 'name' in value && 'age' in value ); } function printUserInfo(value: unknown) { if (isPerson(value)) { // 进入这个分支后,TypeScript 就知道 value 是 Person 类型了 console.log(`姓名:${value.name},年龄:${value.age}`); if (value.sex) { console.log(`性别:${value.sex}`); } } else { console.log('这不是一个合法的 Person 对象'); } } printUserInfo(person); // 输出:姓名:henry,年龄:18 printUserInfo({ name: 'lucy' }); // 输出:这不是一个合法的 Person 对象 七、类型转换与组合:灵活不代表放纵如果遇到类型不确定的场景,TypeScript 提供了类型断言来 “手动担保”:let someValue: any = '123'; let strLength = (someValue as string).length; // 写法一 let strLength2 = (someValue).length; // 写法二 还可以用 type 定义联合类型和交叉类型:type Person = string | number | boolean; const a: Person = 'hello'; const b: Person = 123; const c: Person = true; type PartialX = {x: number} type Point = PartialX & {y: number} // 交叉类型:合并多个类型 const p: Point = { x: 10, y: 20 } 八、泛型:写一次,适配所有类型泛型是 TypeScript 的 “秘密武器”,让函数和组件更通用。function identity(value: T) { return value; } identity (100); // 指定 T 为 number 类型 function identity2 (value: T, msg: U): T { console.log(msg); return value; } identity2 (100, 'hello'); // 多泛型参数 let arr: Array = [1, 2, 3]; let arr2: Array = [1, 2, 3, 'hello']; 泛型让 identity 函数既能处理数字,也能处理字符串,不用写多个重复函数,代码复用性直接拉满。结语从 JavaScript 的 “盲盒变量” 到 TypeScript 的 “精准类型”,本质是从 “靠运气写代码” 到 “靠逻辑写代码” 的转变。写的代码都不严谨,那还写什么代码呢😄。TypeScript 不是给你套枷锁,而是给你装护栏 —— 它不会限制你的创造力,只会帮你提前避开那些低级 Bug。所以,不要害怕红色的报错,而是试着去解决它。——转载自:风止何安啊
为什么要有 TypeScript?让 JS 告别 “薛定谔的 Bug”
开源硬件平台
#技术干货# 【摘要】一款基于表格的研发项目管理工具,覆盖概念、系统设计、开发、测试、验收全流程,帮助研发团队规范过程管理、沉淀项目数据、实现需求追溯。适用于汽车电子 ECU 及其他嵌入式系统研发。 在研发项目管理中,你是否遇到过这些问题: 需求、设计、测试数据分散在多个 Excel 文件中,版本难以统一管理 项目成员各自维护自己的表格,信息不同步 需要追溯需求时,要在多个文件之间来回查找关联 项目结项后,经验教训没有系统沉淀,下一个项目继续踩坑 这些问题的核心在于:缺乏一个统一的数据管理平台。 零绪研发项目管理工具,正是为了解决这些问题而设计。 ## 零绪是什么? 零绪是一款基于表格的研发项目管理工具。 它提供标准化的表格模板,覆盖研发项目的完整生命周期:概念阶段、系统设计阶段、开发阶段、测试阶段、验收阶段。 每个阶段包含若干张专业设计的表格,用户通过填写表格完成项目数据的录入和管理。所有数据集中存储,团队成员访问同一份数据,避免多版本混乱。 ### 核心特点 类 Excel 操作体验   如果你熟悉 Excel,就能快速上手零绪。工具采用表格形式呈现,支持单元格编辑、数据筛选等常见操作。 标准化字段设计   每张表格的字段基于 ASPICE、ISO 26262 等行业标准设计,确保数据规范性和完整性。 集中化数据存储   所有项目数据存储在统一平台,支持跨阶段查询和统计,方便团队协作和知识沉淀。 可追溯的数据结构   通过 ID 引用建立需求、设计、测试之间的关联关系,支持追溯查询和覆盖率统计。 ## 零绪能管理什么? 零绪覆盖研发项目的五个核心阶段,每个阶段提供相应的表格模板: 概念阶段——收集客户 SOR 需求,进行可行性评估,定义系统需求,完成 HARA 分析和功能安全目标设定。 系统设计阶段——进行系统架构设计,分解硬件和软件需求,完成关键元器件选型,输出 DFMEA/PFMEA 分析,管理工作进度。 开发阶段——管理软件和硬件的架构设计、详细设计、单元测试用例及执行记录,支持线束定义。 测试阶段——管理集成测试、系统测试(功能/性能/环境/诊断/工况)、功能安全确认测试,覆盖测试计划、用例设计、执行记录全流程。 验收阶段——规划验收测试,记录测试结果和客户意见,生成交付物清单和验收报告,完成项目结项和经验总结。 此外,每个阶段还包含输入评审、输出评审、问题记录、变更管理四张公共表格,用于过程管理和质量控制。 项目信息查询模块提供跨阶段的数据总览,包括需求跟踪矩阵、评审汇总、问题分布统计、变更趋势分析等。 ##零绪如何使用?### 基本操作流程 创建项目:新建项目,填写项目基本信息 选择阶段:根据项目进展进入对应阶段 填写表格:按照字段定义逐项录入数据 建立关联:在关联字段中填写对应 ID,建立追溯关系 评审确认:组织评审并记录结论 问题跟踪:发现问题及时登记并跟踪解决 变更管理:变更时填写变更表,评估影响并实施 ### 数据录入方式 零绪中的数据需要手动录入,这是为了保证数据的准确性和责任明确: 需求描述由系统工程师根据客户 SOR 手工转化 设计内容由开发工程师根据设计方案填写 测试结果由测试工程师根据实际测试情况记录 评审结论由评审参与人员讨论后填写 工具的价值在于提供标准化模板、统一字段定义、集中存储管理、建立ID 引用,让数据录入更规范、查询更便捷、追溯更清晰。 ### 统计与查询 零绪支持多种数据统计和查询: 需求覆盖率统计 问题状态分布 变更趋势分析 进度可视化(甘特图) 追溯链查询 ## 谁适合使用零绪? 适用行业 汽车行业 ECU 研发(发动机控制器、变速箱控制器、车身控制器、网关、BMS、VCU、MCU 等) 其他嵌入式系统研发(工业控制、医疗设备、航空航天、轨道交通等) 团队规模 小型团队(5-10 人):快速建立规范化流程 中型团队(10-30 人):提升协同效率 大型团队(30 人以上):统一工作语言和模板 适用角色 项目经理、系统工程师、硬件工程师、软件工程师、测试工程师、质量工程师。 ## 为什么选择零绪? 专业设计——表格字段基于行业标准设计,符合车规级研发要求。 易于上手——类 Excel 的操作方式,无需复杂培训即可使用。 灵活适配——可根据团队实际需求调整,不强制绑定特定方法论。 持续沉淀——项目数据集中存储,形成组织过程资产。 合规支持——完整的评审记录、追溯关系、问题跟踪,为审核提供证据支持。 更多内容
01_零绪研发项目管理工具
硬创社
最近圈子炸了两次。第一次是Claude Code源码泄露事件。有人把Anthropic的核心代码扔到了GitHub上,虽然官方火速处理,但技术圈已经炸开了锅——大家突然发现,原来AI编程已经进化到了这个程度。第二次更刺激:豆包9块9。对,你没看错,9块9就能用上一个完整的AI编程工具。这价格,连一杯奶茶都买不到,但它已经开始抢程序员的饭碗了。然后就出现了两种声音:1.AI要把程序员干掉了2.AI最终不靠谱,锅还是要人来背的我专门花了三个月研究这件事,结论是——AI确实在抢饭碗,但抢的是另一群程序员的。什么意思?你会发现,现在最慌的不是那些真正写核心逻辑、做架构设计的人,而是那些每天写CRUD、做简单前端页面的"代码搬运工"。AI工具对这部分工作的替代效率,高得可怕。但反过来,那些真正能解决问题、能设计系统、能理解业务的人,AI不是来抢你饭碗的,它是来给你装上火箭助推器的。说白了,AI淘汰的不是程序员,而是低效的编程方式。你还在一个字母一个字母敲代码,别人已经在用AI生成框架、自动写测试、一键优化性能了。差距不是一点半点。(((顺便吆喝一句,技术大厂,前后端-测试机会,全国一线及双一线城市均有坑位坑位,待遇和稳定性还不错,感兴趣可以试试~所以问题来了:你和AI之间,是竞争关系,还是协作关系?这个问题的答案,决定了你是在2026年被淘汰,还是在2026年起飞。#嘉立创PCB#
AI 9块9抢程序员饭碗?我专门研究了三个月,结论可能和你想的不一样
开源硬件平台
这几天折磨死我了,因为这玩意我也是自己在琢磨,然后资料也没有多少,简直就是自己慢慢摸索。 本期介绍如何利用TCP接收图片信息并显示。 首先我们先新建一个类,将我们想要的内容全部写到这个类上(类的代码会放在最后面) cpListener用来创建监听,tcpClient用以发送。public AndroidWifiService() { } ~AndroidWifiService() { tcpClient?.Close(); tcpClient = null; tcpListener = null; MessageReceived = null; } public AndroidWifiService(IPAddress address,int Port) { StartListening(address, Port); } public AndroidWifiService(string address, int Port) { StartListening(IPAddress.Parse(address), Port); } 提供三种类型的构造函数和析构函数。protected virtual void OnMessageReceived(byte[] buffer) { // 触发事件 MessageReceived?.Invoke(this, buffer); } 设置一个事件,用以传递监听到的图片信息。public async void StartListening(IPAddress IP,int Port) { tcpListener = new TcpListener(IP, Port); tcpListener.Start(); while (true) { tcpClient = await tcpListener.AcceptTcpClientAsync(); Task.Run(() => HandleClient(tcpClient)); } } private void HandleClient(TcpClient client) { try { NetworkStream stream = client.GetStream(); byte[] buffer = new byte[1024]; // 使用固定大小的缓冲区 using (MemoryStream ms = new MemoryStream()) { int bytesRead; while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) { ms.Write(buffer, 0, bytesRead); } if (ms.Length > 0) { // 在主线程上更新 UI MainThread.BeginInvokeOnMainThread(() => { OnMessageReceived(ms.ToArray()); }); } } } catch (Exception ex) { Console.WriteLine($"Exception in HandleClient: {ex.Message}"); } } 创建一个监听函数,注意的是,我们是接受完一次完整的流再传递信息,而不是边接收边传递。private void OnMessageReceived(object sender, byte[] message) { MainThread.BeginInvokeOnMainThread(async () => { try { using (MemoryStream ms = new MemoryStream(message)) { SKBitmap bitmap = SKBitmap.Decode(ms); AndroidSaveClass save = new AndroidSaveClass(); string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "MAUI_Picture_Save"); if (!save.DoesFolderExist(folderPath)) { Directory.CreateDirectory(folderPath); } // 生成文件路径 string uniqueFileName = $"sample_{DateTime.Now:yyyyMMddHHmmssfff}.png"; string filePath = Path.Combine(folderPath, uniqueFileName); // 将 SKBitmap 编码并保存为 PNG 文件 using (var image = SKImage.FromBitmap(bitmap)) using (var data = image.Encode(SKEncodedImageFormat.Png, 100)) using (var stream = File.OpenWrite(filePath)) { data.SaveTo(stream); } Picture.Source = filePath; } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } }); } 再MainPage.xmal.cs中,我们新建一个这个类的变量,监听我们的窗口。并且设置回调函数用来处理事件。 事件中,我们将图片的字节数组转为图片,之后保存到程序的文件目录中,并且使用时间戳防止图片重复。 之后用Image控件来绑定图片源显示图片。
.NET MAUI的Android WiFi图传开发(3)——Android设备接收TCP信息并显示
嘉立创PCB
01|什么是 Claude Code一句话:👉 Claude Code = 终端里的 AI 执行助手你可以用一句人话,让它帮你:写代码改文件整理资料分析数据自动完成任务📌 和普通 AI 最大区别:ChatGPT:告诉你怎么做 Claude Code:直接帮你做02|快速安装核心只有一句话:👉 先装 Node → 再装 Claude Code安装 Node.jsnodejs.org选择 LTS 版本验证:node -v npm -v安装 Claude Code npm install -g [@anthropic](https://x.com/@anthropic) -ai/claude-code 验证 claude --version 不废话,技术大厂捞人:前端/后端/测试,全国多地可选,待遇和稳定度都在线。→试试试试不吃亏 03|连接国内大模型一、编辑或新增 settings.json 文件MacOS 在.claude文件夹下创建settings.json, ~/.claude/settings.jsonWindows 为用户目录/.claude/settings.json #新增或修改里面的env字段# 注意替换里面的 `your_deepseek_key` 为您上一步获取到的 API Key { "env": { "ANTHROPIC_AUTH_TOKEN": "your_deepseek_key", "ANTHROPIC_BASE_URL":"https://api.deepseek.com/anthropic", "API_TIMEOUT_MS": "3000000", "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": 1, "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.5-air", "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.7", "ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-4.7" } } 二、再编辑或新增 .claude.json 文件用去跳过MacOS & Linux 为 ~/.claude.jsonWindows 为用户目录/.claude.json #再编辑或新增`.claude.json`文件# 新增 `hasCompletedOnboarding` 参数 { "hasCompletedOnboarding": true } 三、进入ClaudeCode确认首先确认系统默认的模型名称然后输入/status命令查看baseUrl和模型名称,输入命令 /status ——转载自:想不到一个好的ID
Claude Code 初学者必看指南
开源硬件平台
各位工程师、采购和电子爱好者,你是否遇到过这样的问题——选型时拿不准器件的稳定性?量产之后才发现可靠性隐患?其实,民品也一样需要“高可靠”的底气。 这一次,我们邀请了一位“硬核”原厂——火炬电子,用航天同源的可靠性,降维解决民品顽疾,为电子工程师与爱好者提供专业、可靠的选型方案,再也不用为“莫名炸机”和批次不稳头疼! 4月15日 ,立创商城 × 火炬电子开启专场技术直播,深度拆解高可靠电容与功率器件,从航天级标准到工业民用落地,一站式解决选型、可靠性、失效分析等核心难题!现在扫码预约直播后,保存预约截图,点击链接参与互动还有机会得20采购晶!(采购晶可用于抵扣一定比例的现货商品金额,以及兑换采购晶专区的礼品) 直播全程福利狂撒,2000元现金红包、小炬玩偶礼盒、运动毛巾、冷感速干巾、样品册等实物礼品开送!  直播时间4月15日(周三)19:30 直播主题火炬电子电容器功率器件产品推介 直播嘉宾刘芳演 高级应用工程师黄倩萍 高级市场工程师 关于火炬电子 火炬电子始创于1989年,是国家高新技术企业,产品覆盖MLCC、钽电容、超级电容器、功率器件等品类,以高稳定性、高可靠性和长期服役能力著称,广泛应用于航空、航天、船舶、电力、轨道交通、新能源等重点领域与重大工程,为关键核心系统提供安全、稳定的基础支撑。 直播核心亮点 航天级电容解析:MLCC、钽电容、超级电容的高可靠设计与选型标准;功率器件硬核科普:MOS/SiC MOS等高可靠方案与工业/新能源应用;可靠性实战指南:电容失效、压电效应、金脆效应等问题一站式解决; 在线实时答疑:火炬电子嘉宾解答高可靠元器件选型、测试、应用疑问,若你的问题被嘉宾挑中回答,还可以获得立创送出的采购晶! 无论您在做电源、工控、汽车还是消费类产品,这场直播将帮您选对器件、提升产品可靠性。 立即预约,4月15日19:30,立创商城视频号见!
原厂揭秘:火炬电子高可靠产品推介,预约锁定专属福利
立创商城
电池包的热管理,是新能源汽车安全与续航的核心命脉。当电芯在大倍率充放电时,瞬间产生的热量若无法及时导出,不仅会导致电池加速衰减,更可能引发热失控风险。而在热管理系统中,一个小小的导热界面材料——导热硅脂,却常常成为决定成败的关键。 今天,我们将目光聚焦于华东某知名新能源汽车制造商的电池包散热改造项目。这是傲琪电子以专业热管理方案解决客户痛点、实现性能跃升的典型案例。   一、 项目背景:被忽视的“界面之困” 该车企在研发一款高能量密度电池包时,遇到了棘手的散热瓶颈。其设计方案采用液冷板贴合模组底部的方式散热,但在测试中发现:即使液冷板温度控制良好,电芯底部的温度依然居高不下,导致电池包整体温差超过8℃,严重影响了电池的一致性与循环寿命。 问题出在哪里?经过热成像分析与拆解,根源锁定在电芯与液冷板之间的导热界面。 电芯铝壳与液冷板表面虽然经过机加工,但微观上仍存在大量凹凸不平的缝隙。为了填充这些缝隙,原方案使用了一种常规导热垫片。然而,垫片硬度偏高,无法完全贴合曲面;且长期受压后,垫片发生应力松弛,接触热阻逐渐增大,导致热量“堵”在了电芯底部。 二、 傲琪方案:不仅仅是“换一种材料” 接到客户诉求后,傲琪电子技术团队立即介入。我们没有简单地推荐一款高导热系数的硅脂,而是从界面传热机理与工程可制造性两个维度展开分析。 1. 材料匹配:低热阻与长期稳定性并重 针对电池包内部空间狭小、长期震动、温度交变的特点,傲琪电子推荐了G500系列高导热硅脂作为核心填充材料,并配合导热灌封胶实现整体密封与散热。 G500系列导热硅脂:导热系数5.0W/(m·K),远高于常规垫片。其独特的低油离度(<0.05%) 配方确保了在40℃~150℃的极端温度循环中,基础油不会析出污染电芯极柱或连接器,避免了传统硅脂“泵出效应”导致的干涸失效。 导热灌封胶:在电池包底部形成一层兼具绝缘、导热、缓冲的整体密封层,将电芯产生的热量高效传导至液冷板,同时起到防护作用。 2. 工艺适配:实现微米级均匀涂覆 大尺寸电池模组对涂覆均匀性要求极高。傲琪团队协助客户改进了涂覆工艺:采用精密钢网印刷,将G500硅脂以80μm的厚度精准涂布在液冷板表面,确保了每个电芯底部接触面的热阻一致。随后,在模组装配时注入导热灌封胶,利用胶体的流动性二次填充细微间隙,形成“无死角”的导热网络。  三、 落地成果:热阻直降30%,温差缩至3℃ 改造后的电池包经历了严苛的第三方测试,数据令人振奋: 界面热阻降低30%:相比原垫片方案,G500硅脂与灌封胶的组合将接触热阻从0.62℃·in²/W降至0.43℃·in²/W。 最高温度下降8℃:在2C倍率连续充放电测试中,电芯最高温度由72℃降至64℃,有效延缓了电池衰减。 温差控制优于3℃:模组内各电芯间的最大温差从8℃以上缩小至2.8℃,大幅提升了电池一致性与循环寿命。 长期可靠性验证:经过1000小时高温高湿及500次冷热冲击,材料无开裂、无渗油,接触热阻变化小于5%。 客户热管理负责人感慨:“之前我们一直在垫片和硅脂之间犹豫,总以为垫片更可靠。傲琪用实测数据告诉我们,选对材料、做对工艺,硅脂不仅能胜任,还能带来意想不到的性能提升。现在这款电池包已经成功搭载在我们新款车型上。” 四、 技术解析:为什么傲琪能赢? 这个项目的成功,源于傲琪电子对导热材料底层技术的深耕: 填料复配技术:通过微米与纳米级导热填料的级配填充,G500系列在保证低粘度涂覆性的同时,构建了密集的导热通路,实现高导热与低接触热阻的平衡。 流变学设计:针对印刷工艺需求,精确控制硅脂的触变指数,确保涂覆时易刮平、停顿时不流淌、受压时不外溢。 系统级思维:不孤立看待单一材料,而是提供“硅脂+灌封胶+工艺优化”的组合方案,解决客户从材料到量产的全链条问题。 结语 新能源汽车的热管理是一场“毫米级”的较量,每一个界面的优化都关乎整车的安全与效能。傲琪电子在电池包项目中的实战证明:小小的导热硅脂,经过专业选型与精密工艺的加持,足以成为攻克散热瓶颈的利器。  
让电池包安然度过“火炉”与“极寒”: 傲琪电子高导热硅脂在新能源汽车热管理中的实战记
开源硬件平台
还在为每个列表页写重复的分页代码而烦恼吗? 还在复制粘贴 currentPage、pageSize、loading 等状态吗? 一个 Hook 帮你解决所有分页痛点,减少90%重复代码背景与痛点在后台管理系统开发中,分页列表查询非常常见,我们通常需要处理:当前页、页大小、总数等分页状态加载中、错误处理等请求状态搜索、刷新、翻页等分页操作数据缓存和重复请求处理这些重复逻辑分散在各个组件中,维护起来很麻烦。为了解决这个烦恼,我专门封装了分页数据管理 Hook。现在只需要几行代码,就能轻松实现分页查询,省时又高效,减少了大量重复劳动使用前提 - 接口格式约定查询接口返回的数据格式: { list: [ // 当前页数据数组 { id: 1, name: 'user1' }, { id: 2, name: 'user2' } ], total: 100 // 数据总条数 } 先看效果:分页查询只需几行代码! import usePageFetch from '@/hooks/usePageFetch' // 引入分页查询 Hook,封装了分页逻辑和状态管理 import { getUserList } from '@/api/user' // 引入请求用户列表的 API 方法 // 使用 usePageFetch Hook 实现分页数据管理 const { currentPage, // 当前页码 pageSize, // 每页条数 total, // 数据总数 data, // 当前页数据列表 isFetching, // 加载状态,用于控制 loading 效果 search, // 搜索方法 onSizeChange, // 页大小改变事件处理方法 onCurrentChange // 页码改变事件处理方法 } = usePageFetch( getUserList, // 查询API { initFetch: false } // 是否自动请求一次(组件挂载时自动拉取第一页数据) ) 这样子每次分页查询只需要引入hook,然后传入查询接口就好了,减少了大量重复劳动【顺便提一嘴】技术大厂,前端-后端-测试,全国均有机=会,感兴趣可以试试。待遇和稳定性都还不错~解决方案我设计了两个相互配合的 Hook:useFetch:基础请求封装,处理请求状态和缓存usePageFetch:分页逻辑封装,专门处理分页相关的状态和操作 usePageFetch (分页业务层) ├── 管理 page / pageSize / total 状态 ├── 处理搜索、刷新、翻页逻辑 ├── 统一错误处理和用户提示 └── 调用 useFetch (请求基础层) ├── 管理 loading / data / error 状态 ├── 可选缓存机制(避免重复请求) └── 成功回调适配不同接口格式 核心实现useFetch - 基础请求封装 // hooks/useFetch.js import { ref } from 'vue' const Cache = new Map() /** * 基础请求 Hook * @param {Function} fn - 请求函数 * @param {Object} options - 配置选项 * @param {*} options.initValue - 初始值 * @param {string|Function} options.cache - 缓存配置 * @param {Function} options.onSuccess - 成功回调 */ function useFetch(fn, options = {}) { const isFetching = ref(false) const data = ref() const error = ref() // 设置初始值 if (options.initValue !== undefined) { data.value = options.initValue } function fetch(...args) { isFetching.value = true let promise if (options.cache) { const cacheKey = typeof options.cache === 'function' ? options.cache(...args) : options.cache || `${fn.name}_${args.join('_')}` promise = Cache.get(cacheKey) || fn(...args) Cache.set(cacheKey, promise) } else { promise = fn(...args) } // 成功回调处理 if (options.onSuccess) { promise = promise.then(options.onSuccess) } return promise .then(res => { data.value = res isFetching.value = false error.value = undefined return res }) .catch(err => { isFetching.value = false error.value = err return Promise.reject(err) }) } return { fetch, isFetching, data, error } } export default useFetch usePageFetch - 分页逻辑封装 // hooks/usePageFetch.js import { ref, onMounted, toRaw, watch } from 'vue' import useFetch from './useFetch' // 即上面的hook ---> useFetch import { ElMessage } from 'element-plus' /** * 分页数据管理 Hook * @param {Function} fn - 请求函数 * @param {Object} options - 配置选项 * @param {Object} options.params - 默认参数 * @param {boolean} options.initFetch - 是否自动初始化请求 * @param {Ref} options.formRef - 表单引用 */ function usePageFetch(fn, options = {}) { // 分页状态 const page = ref(1) const pageSize = ref(10) const total = ref(0) const data = ref([]) const params = ref() const pendingCount = ref(0) // 初始化参数 params.value = options.params // 使用基础请求 Hook const { isFetching, fetch: fetchFn, error, data: originalData } = useFetch(fn) // 核心请求方法 const fetch = async (searchParams, pageNo, size) => { try { // 更新分页状态 page.value = pageNo pageSize.value = size params.value = searchParams // 发起请求 await fetchFn({ page: pageNo, pageSize: size, // 使用 toRaw 避免响应式对象问题 ...(searchParams ? toRaw(searchParams) : {}) }) // 处理响应数据 data.value = originalData.value?.list || [] total.value = originalData.value?.total || 0 pendingCount.value = originalData.value?.pendingCounts || 0 } catch (e) { console.error('usePageFetch error:', e) ElMessage.error(e?.msg || e?.message || '请求出错') // 清空数据,提供更好的用户体验 data.value = [] total.value = 0 } } // 搜索 - 重置到第一页 const search = async (searchParams) => { await fetch(searchParams, 1, pageSize.value) } // 刷新当前页 const refresh = async () => { await fetch(params.value, page.value, pageSize.value) } // 改变页大小 const onSizeChange = async (size) => { await fetch(params.value, 1, size) // 重置到第一页 } // 切换页码 const onCurrentChange = async (pageNo) => { await fetch(params.value, pageNo, pageSize.value) } // 组件挂载时自动请求 onMounted(() => { if (options.initFetch !== false) { search(params.value) } }) // 监听表单引用变化(可选功能) watch( () => options.formRef, (formRef) => { if (formRef) { console.log('Form ref updated:', formRef) } } ) return { // 分页状态 currentPage: page, pageSize, total, pendingCount, // 数据状态 data, originalData, isFetching, error, // 操作方法 search, refresh, onSizeChange, onCurrentChange } } export default usePageFetch 完整使用示例用element ui举例 <template> <el-form :model="searchForm" > <el-form-item label="用户名"> <el-input v-model="searchForm.username" /> </el-form-item> <el-form-item> <el-button type="primary" @click="handleSearch">搜索</el-button> </el-form-item> </el-form> <!-- 表格数据展示,绑定 data 和 loading 状态 --> <el-table :data="data" v-loading="isFetching"> <!-- ...表格列定义... --> </el-table> <!-- 分页组件,绑定当前页、页大小、总数,并响应切换事件 --> <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :total="total" @size-change="onSizeChange" @current-change="onCurrentChange" /> </template> <script setup> import { ref } from 'vue' import usePageFetch from '@/hooks/usePageFetch' // 引入分页查询 Hook,封装了分页逻辑和状态管理 import { getUserList } from '@/api/user' // 引入请求用户列表的 API 方法 // 搜索表单数据,响应式声明 const searchForm = ref({ username: '' }) // 使用 usePageFetch Hook 实现分页数据管理 const { currentPage, // 当前页码 pageSize, // 每页条数 total, // 数据总数 data, // 当前页数据列表 isFetching, // 加载状态,用于控制 loading 效果 search, // 搜索方法 onSizeChange, // 页大小改变事件处理方法 onCurrentChange // 页码改变事件处理方法 } = usePageFetch( getUserList, { initFetch: false } // 是否自动请求一次(组件挂载时自动拉取第一页数据) ) /** * 处理搜索操作 */ const handleSearch = () => { search({ username: searchForm.value.username }) } </script> 高级用法带缓存 const { data, isFetching, search } = usePageFetch(getUserList, { cache: (params) => `user-list-${JSON.stringify(params)}` // 自定义缓存 key }) 设计思路解析职责分离:useFetch 专注请求状态管理,usePageFetch 专注分页逻辑统一错误处理:在 usePageFetch 层统一处理错误智能缓存机制:支持多种缓存策略生命周期集成:自动在组件挂载时请求数据总结这套分页管理 Hook 的优势:开发效率高,减少90%的重复代码,新增列表页从 30 分钟缩短到 5 分钟状态管理完善,自动处理加载、错误、数据状态缓存机制,避免重复请求错误处理统一,用户体验一致易于扩展,支持自定义配置和回调——转载自:不一样的少年_
Vue3 后台分页写腻了?我用 1 个 Hook 删掉 90% 重复代码(附源码)
开源硬件平台
社区数据
今日帖子
-
今日互动量
-
在线人数
-
帖子总量
-
用户总量
-
功能讨论
()
主题
打赏记录
服务时间:周一至周六 9::00-18:00 · 联系地址:中国·深圳(福田区商报路奥林匹克大厦27楼) · 媒体沟通:pr@jlc.com · 集团介绍
移动社区