NodeJS
快来分享你的内容吧~
- 2025-04-03·Java后端
- 2025-03-17·云兮导航: https://www.xd
- 2024-12-31·Java后端查看全文PS C:\Users\HUAWEI> nvm install 20.11.0 Downloading node.js version 20.11.0 (64-bit)... Extracting node and npm... Complete ...编程导航小智:这个问题可能是由于系统环境变量没有正确配置导致的。要解决这个问题,需要确保以下几个步骤正确执行:1. 检查nvm的安装路径是否正确。你可以在命令行中运行"where nvm"命令来查看nvm的安装路径。确保该路径已添加到系统的环境变量中。2. 重启你的命令行终端,或者再次打开一个新的命令行终端。有时候环境变量需要重新加载才能生效。3. 确保你已经正确安装了Node.js。你可以运行"node -v110分享
error installing 14.21.3: open C:\Users\qcdfz\AppData\Local\Temp\nvm-npm-1073152608\npm-v6.14.18.zip
今天使用nvm安装nodejs14版本的是时候报了如下的错误 使用命令:`nvm install 14` ```shell error installing 14.21.3: open C:\Users\qcdfz\AppData\Local\Temp\nvm-npm-1073152608\npm-v6.14.18.zip: The system cannot find the file specified. ``` 原因: nvm版本太高,将nvm卸载,重装1.1.12即可。 在nvm的安装目录双击unins000.exe卸载nvm 下载nvm 1.1.12版本连接 [https://github.com/coreybutler/nvm-windows/releases/download/1.1.12/nvm-setup.exe](https://github.com/coreybutler/nvm-windows/releases/download/1.1.12/nvm-setup.exe) 转载:[https://blog.csdn.net/m0_73442728/article/details/146308810](https://blog.csdn.net/m0_73442728/article/details/146308810)
LangFuse自定义Graph节点调试信息收集
## 开端 在使用Langfuse的过程中,官方自带的集成langGraph中,进行收集到的节点和LLM的调用信息,无法进行定制化,只要调用就收集,并且如果Graph嵌套较多,Trace收集到的日志嵌套很深,有用的信息会被循环和嵌套给“淹没”掉,并且成本是很高的,一次运行可以达到1000-1500的次数收集 所以我使用原生的langfuseSDK的方法进行定制,只收集我想要收集的,这样可以控制日志的链路同时也可以控制成本 一句话:**一次注入、全链共享;只记录关键、剔除冗馀** > 使用自定义之后的日志收集,嵌套层级最大只有3层,并且无循环,只有干练的关键节点的信息,并且收集到的日志成本变为20-50左右,降低50倍 ## 1. 设计目标 & 痛点 | 痛点 | 目标 | | --------------- | ------------------------------------ | | Trace 层级过深,视图臃肿 | **单 Trace 统御全链** | | 全量回调,成本高 | **仅记录关键 Span + LLM Generation** | | Tracing 逻辑散落业务 | **装饰器自动化**,业务函数零耦合 | | 代码入侵严重 | Tracing 信息深藏 `metadata.tracing`,接口干净 | ## 2. 总览架构图  ## 3、关键概念 * **lf** — `new Langfuse({...})` 实例,入口处一次性创建并塞进 config * **traceId** — 由 Langfuse 自动生成的 UUID,贯穿整条调用链 * **@GraphNode** — 节点装饰器,负责 1. 开 Span 2. (可选)注入 LLM Generation 回调 3. 收尾 Span * **withGen** — 装饰器参数 * `true` → 记录 LLM Generation * `false` → 仅记录 Span * **inputKeys - span节点的Input输入** 使用这个参数可以控制span节点的输入信息,从state中筛选出来我们需要查询的参数,而不是全部 ## 4. 实现细节 ### 4.1 入口初始化 ```javascript const lf = new Langfuse({ publicKey, secretKey }); const trace = lf.trace({ userId, sessionId, tags: ['v2'] }); await graph.streamEvents(data, { version: 'v2', configurable: { trace, lf }, }); ``` ### 4.2 GraphNode 装饰器核心 ```javascript // decorators/GraphNode.ts import { CallbackHandler } from 'langfuse-langchain'; // 获取 input,只保留关心的键 function pickInput(state, inputKeys) { if (!inputKeys?.length) return { userInput: state?.userInput }; return inputKeys.reduce((obj, key) => { if (Object.prototype.hasOwnProperty.call(state, key)) obj[key] = state[key]; return obj; }, {}); } // 合并 callbacks,保证为数组 function mergeCallbacks(cfg, span, withGen) { let callbacks = []; if (cfg.callbacks) { callbacks = Array.isArray(cfg.callbacks) ? cfg.callbacks : [cfg.callbacks]; } if (withGen) { callbacks.push(new CallbackHandler({ root: span })); } return callbacks; } //LangFuse日志收集-装饰器 export function GraphNode(name: string, withGen = false, inputKeys: string[] = []) { return function <T extends (...args: any[]) => Promise<any>>(orig: T): T { const wrapped = (async (...args: any[]) => { const [state, cfg = {}] = args; const { configurable = {} } = cfg; const { lf, trace } = configurable; if (!lf || !trace) { throw new Error(`缺少 lf 信息:请确保在 metadata.tracing 中传入 lf 与 traceId`); } // 关心的 input const safeInput = pickInput(state, inputKeys); // 开 span const span = trace.span({ name, input: safeInput }); // runtimeCfg 传递下去 const runtimeCfg = { ...cfg, configurable: { ...configurable, trace: span }, callbacks: mergeCallbacks(cfg, span, withGen), }; try { const result = await orig.apply(null, [state, runtimeCfg]); console.log('开始span结束'); await span.end({ output: result }); return result; } catch (e) { await span.end({ error: String(e) }); throw e; } }) as T; return wrapped; }; } // 方法装饰器,将类方法包装为 GraphNode export function GraphNodeMethod(name: string, withGen = false, inputKeys: string[] = []) { return function (_target: any, _propertyKey: string, descriptor: PropertyDescriptor) { const fn = descriptor.value; descriptor.value = GraphNode(name, withGen, inputKeys)(fn); }; } ``` ### 4.3 节点书写范式 ```javascript class SupervisorNodeHandlers { // //1、第一步进行意图识别 @GraphNodeMethod('intentRecognitionNode', true, ['userInput', 'inputParam']) async intentRecognitionNode(state: typeof InputStateAnnotation.State, config: any) { const { userInput, inputParam, lf, trace } = state; let { sessionId, messageId } = inputParam; // 意图识别-函数执行 const result: any = await intentRecognitionModuleV2(userInput, config); let goto = END; return new Command({ goto: goto, update: { intentResult: result, intentResultType, envelopeTotalAgent }, }); } } ``` 业务层只需 **透传** `**config**`,其余由装饰器托管。 ### 4.4 子图透传策略 ```javascript const result = await executeGraph.invoke( { plannerPartResult: currentItem, plannerPartIndex: currentIndex, inputParam: inputParam, plannerResult: plannerResult, userInput: userInput, intentResultType: intentResultType, envelopeTotalAgent, runId, }, config ); ``` ### 4.5 LLM 调用规范 ```javascript export async function intentRecognitionModuleV2(userInput: string, config: any) { const chain = prompt.pipe(model).pipe(parse); let result = await chain.invoke({ userInput: userInput }, config); // 明确指定非流式); } ``` ## 5. 总结 刚开始的实现,我是没有先考虑装饰器的,因为我喜欢函数式编程,并不是很喜欢类,并且使用装饰器要创建类,装饰器只能在类的方法上面使用,所以我刚开始考虑的并不是装饰器,而是函数内部调用功能函数就可以 但是发现不够优雅,并且节点函数本来应该只负责业务逻辑的,加过多的系统函数进行,会导致业务逻辑模糊,很大程度上会降低代码的可读性 于是我找到高阶函数的使用,在函数外面包一层函数\*\*,如果我想要的功能过多,其实就需要包多层函数,有点像回调函数的“回调地狱”,所以我没考虑高阶函数\*\*,到此或许函数式编程无法在满足优雅实现功能的需求啦,所以我考虑使用类中的方法来 这样创建类的方法,使用的时候只需要实例化即可,并且可以使用优雅的装饰器
nvm-desktop 超好用的可视化node版本管理工具
## 前言 在网上看到好的前端项目的时候,想拉下来跑一跑,但是原项目用的node版本可能有的低,有的高。 但是用偏高或者偏低的node版本,都有可能导致项目运行报错,深有同感的 +1。 于是需要一个管理版本的的工具,这里绝大部分人选择了 `nvm`,当然也很方便,只需要输入对应的 `命令` 即可。 而今天介绍的 `nvm-desktop` 是一个桌面可视化应用程序,能够帮助你可视化管理多个 Node.js 版本,支持 `Windows、MacOS、Linux` 系统。 Github:https://github.com/1111mp/nvm-desktop ## 功能 支持为系统全局和项目单独设置 Node 引擎版本 管理 Node 的命令行工具 支持英文和简体中文 支持自定义下载镜像地址 自动检查更新 ## 下载 下载地址:https://github.com/1111mp/nvm-desktop/releases 选择对应的系统版本下载后安装即可。 可自定义配置 `nodejs 安装目录` 和 `下载镜像地址` ,我这里使用的是阿里云镜像 <img src="https://pic.code-nav.cn/post_picture/1636188275477630977/uPTWYbUb7r2RCRKC.webp" alt="" width="100%" /> 然后就可以安装使用需要的node版本了 <img src="https://pic.code-nav.cn/post_picture/1636188275477630977/eiJjeGUepxxZ974q.webp" alt="" width="100%" /> 然后就可以自由管理node版本,并且在运行不同前端项目时自由切换node版本了。
【Node】99 起始页
# 99 起始页 地址:[99 起始页](http://123.60.153.252:99) 一个美观、灵活的个性化应用启动页,支持自定义网址图标和组件系统。 <img src="https://pic.code-nav.cn/post_picture/1631847729158258689/tUVZ0kYM3EGCxvmd.webp" alt="预览" width="100%" /> ## 注意 请暂时不要添加数据,后续还会有较多相关问题。若需查看网站最新页面,清除本网站缓存数据才行。 <img src="https://pic.code-nav.cn/post_picture/1631847729158258689/vbibHHZISvkgWQ5I.webp" alt="PixPin_2025-04-03_18-16-33.png" width="100%" /> 或者点击 <img src="https://pic.code-nav.cn/post_picture/1631847729158258689/0H9ofGyObpdVIdb9.webp" alt="PixPin_2025-04-03_18-33-03.png" width="100%" /> ## 功能特点 - 🔍 内置必应搜索功能,快速访问网络 - 🔄 拖拽排序,自定义网站和组件顺序 - 📊 完善的组件库系统,便于管理组件 - 🌐 支持自定义网站添加、编辑和删除 - 💾 本地数据存储,保存用户设置 ## 当前功能 - **自定义网站管理**:添加、编辑和删除网站链接 - **网站图标选项**:支持自动获取图标、Font Awesome 图标或文字图标 - **右键菜单**:便捷管理网站和组件 - **拖拽排序**:自由调整网站和组件的顺序 - **组件库**:从组件库添加和管理组件 - **设置面板**:系统设置(开发中) ## 组件系统 应用中心内置了一个灵活的组件系统,允许用户从组件库添加官方组件。 ### 内置组件 - 🎬 **哔哩哔哩随机视频** - 随机跳转到一个 B 站视频,发现有趣的内容 ### 即将推出的组件库 我计划推出更多组件,例如: - 天气预报组件 - 今日热点新闻 - 倒计时工具 - 备忘录 - 更多实用工具... ## 技术栈 - 前端:HTML, CSS, JavaScript, Tailwind CSS, Font Awesome - 后端:Node.js, Express - 通信:Axios 用于 API 请求 - 组件:原生 JavaScript 面向对象编程 - 拖拽排序:Sortable.js ## 参考 - [抓鱼鸭](https://www.zhuayuya.com) - [iTab 新标签页](https://go.itab.link) - [摸鱼岛-标签页🎣](https://fish.codebug.icu/home)
npm 安装包的路径在哪里
欢迎关注:https://xdr630.blog.csdn.net/ # 1、npm 将软件包安装到哪里 - 当使用 npm 安装软件包时,可以执行两种安装类型: 1. 本地安装 2. 全局安装 ## 1. 本地安装 - 默认情况下,当输入 `npm install` 命令时,例如: ```bash npm install lodash ``` - 软件包会被安装到当前文件树中的 `node_modules` 子文件夹下。 - 在这种情况下,`npm` 还会在当前文件夹中存在的 `package.json` 文件的 `dependencies` 属性中添加 `lodash` 条目。 - 使用 `-g` 标志可以执行全局安装: - `npm install xxx` ,则是将模块下载到当前命令行所在目录。 ```bash 例如: c:\123>npm install xxx 将会安装到 c:\123\node_modules\xxx ``` - 这种方式显然是不好的,所以一般都会使用全局安装方式统一安装的一个目录中去,这样既方便管理、结构清晰还可以重复利用。 - 通过 `npm config get prefix` 来获取当前设置的目录。 ## 2. 全局安装 ```bash npm install -g lodash ``` - 在这种情况下,`npm` 不会将软件包安装到本地文件夹下,而是使用全局的位置。 - `npm install xxx -g` 时, 模块将被下载安装到【全局目录】中。【全局目录】通过 `npm config set prefix "目录路径"` 来设置。 - 通过 `npm config get prefix` 来获取当前设置的全局目录。 <img src="https://pic.code-nav.cn/post_picture/1624066347312943106/Ic0Cq6nwz4nOqK1b.webp" alt="在这里插入图片描述" width="100%" /> - 全局的位置到底在哪里? - `npm root -g` 命令会告知其在计算机上的确切位置。 - 如下是我重新配置的全局安装路径,详情请访问:[npm 设置全局变量安装路径及环境配置](https://xdr630.blog.csdn.net/article/details/111308202) <img src="https://pic.code-nav.cn/post_picture/1624066347312943106/jCYukPNCd7jOexZo.webp" alt="在这里插入图片描述" width="100%" /> - 在 macOS 或 Linux 上,此位置可能是 `/usr/local/lib/node_modules`。 在 Windows 上,可能是 `C:\Users\YOU\AppData\Roaming\npm\node_modules`。 - 如:我云服务器上的全局安装路径 <img src="https://pic.code-nav.cn/post_picture/1624066347312943106/cwvMDQ0LV8EJXfno.webp" alt="在这里插入图片描述" width="100%" /> - 但是,如果使用 `nvm` 管理 `Node.js` 版本,则该位置会有所不同。 - 例如,使用 `nvm`,则软件包的位置可能为 `/Users/joe/.nvm/versions/node/v8.9.0/lib/node_modules`。 # 2、如何使用或执行 npm 安装的软件包 - 当使用 `npm` 将软件包安装到 `node_modules` 文件夹中或 **全局安装** 时,如何在 `Node.js` 代码中使用它? - 假设使用以下命令安装了流行的 JavaScript 实用工具库 `lodash`: ```bash npm install lodash ``` - 这会把软件包安装到**本地的** `node_modules` 文件夹中。 - 若要在代码中使用它,则只需使用 `require` 将其导入到程序中: ```bash const _ = require('lodash') ``` - 如果软件包是可执行文件,该怎么办? - 在这种情况下,它会把可执行文件放到 `node_modules/.bin/` 文件夹下。 - 验证这一点的简单示例是 `cowsay`。 - `cowsay` 软件包提供了一个命令行程序,可以执行该程序以使母牛说些话(以及其他动物也可以说话)。 - 当使用 `npm install cowsay` 安装软件包时,它会在 `node_modules` 文件夹中安装自身以及一些依赖包: <img src="https://pic.code-nav.cn/post_picture/1624066347312943106/TJwycR0KYfQMUf17.webp" alt="在这里插入图片描述" width="100%" /> - 有一个隐藏的 `.bin` 文件夹,其中包含指向 `cowsay` 二进制文件的符号链接: <img src="https://pic.code-nav.cn/post_picture/1624066347312943106/HPB5cIXLS4tFEbbc.webp" alt="在这里插入图片描述" width="100%" /> - 如何执行这些文件? - 可以输入 `./node_modules/.bin/cowsay` 来运行它,但是最新版本的 `npm`(自 5.2 起)中包含的 `npx` 是更好的选择。 只需运行: ```bash npx cowsay ``` - 则 `npx` 会找到程序包的位置。 <img src="https://pic.code-nav.cn/post_picture/1624066347312943106/YSrJ7XNgf3VQaPmJ.webp" alt="在这里插入图片描述" width="100%" />
【Node】data.json 版 仿照编程导航 自娱自乐 只有发布功能
地址:http://123.60.153.252:8101 (1Panel 部署) 介绍:AI 生成代码,node data.json 版 仿照编程导航 自娱自乐 只有发布功能 技术栈:node、ejs、data.json 工具: 页面:https://mastergo.com 截图生成页面,样式为 **tailwindcss**(**强烈推荐这个工具,完全一比一复制页面**) AI:deepseek 2.5 (官网太卡了,先用这个) > 现在很喜欢用 node 搞些玩的小项目,因为只占服务器不到 50M 内存,Java 的话一个要占1G **灵感:** [模仿编程导航的网站 - 编程导航 - 程序员编程学习交流社区](https://www.codefather.cn/post/1833508478520188929#heading-0) 今天摸鱼搞的,后面再慢慢加些新功能 <img src="https://pic.code-nav.cn/post_picture/1631847729158258689/cJMjBoRD1WOL5luU.webp" alt="PixPin_2025-02-08_17-44-04.png" width="698px" /> **项目结构:** <img src="https://pic.code-nav.cn/post_picture/1631847729158258689/ozbeeqdBLgGXNiHS.png" alt="PixPin_2025-02-08_17-47-41.png" width="187px" /> **代码:** app.js: ```js const express = require("express"); const bodyParser = require("body-parser"); const fs = require("fs"); const app = express(); const port = 8101; // 设置模板引擎为 EJS app.set("view engine", "ejs"); app.set("views", "./views"); // 静态文件服务 app.use(express.static("public")); // 解析 POST 请求体 app.use(bodyParser.urlencoded({ extended: true })); // 模拟数据库,存储发布的内容 let posts = []; // 读取 data.json 文件中的数据 const loadPosts = () => { try { const data = fs.readFileSync("data.json", "utf8"); posts = JSON.parse(data); } catch (err) { posts = []; } }; // 保存数据到 data.json 文件 const savePosts = () => { fs.writeFileSync("data.json", JSON.stringify(posts), "utf8"); }; // 标签数据 const tags = ["日常", "Java", "Python", "后端", "前端", "个人介绍"]; // 首页路由 app.get("/", (req, res) => { res.render("index", { posts: posts, tags: tags, activeTag: null }); // 默认 activeTag 为 null }); // 交流路由,展示没有 title 的内容 app.get("/exchange", (req, res) => { const exchangePosts = posts.filter((post) => !post.title); res.render("index", { posts: exchangePosts, tags: tags, activeTag: null }); }); // 文章路由,展示有 title 的内容 app.get("/articles", (req, res) => { const articlePosts = posts.filter((post) => post.title); res.render("index", { posts: articlePosts, tags: tags, activeTag: null }); }); // 发布内容路由 app.post("/post", (req, res) => { const content = req.body.content; const selectedTags = req.body.tags || []; // 获取选择的标签 const title = req.body.title; // 获取文章标题 if (content) { const timestamp = Date.now().toString(); // 生成 3 位随机数字 const randomPart = Math.floor(Math.random() * 900) + 100; const id = `${timestamp}${randomPart}`; // 组合时间戳和随机数 posts.unshift({ id: id, // 添加 id username: "溜达丶99", avatar: "https://ygr99.oss-cn-shenzhen.aliyuncs.com/utools/%E5%A4%B4%E5%83%8F.jpg", content: content, tags: selectedTags, // 存储标签 title: title, // 存储文章标题 time: new Date().toLocaleString(), }); savePosts(); // 保存数据到文件 } res.redirect("/"); }); // 标签过滤路由 app.get("/tag/:tag", (req, res) => { const tag = req.params.tag; const filteredPosts = posts.filter((post) => post.tags.includes(tag)); res.render("index", { posts: filteredPosts, tags: tags, activeTag: tag }); // 传递 activeTag }); // 获取帖子详情路由 app.get("/post/:id", (req, res) => { const postId = req.params.id; const post = posts.find((p) => p.id === postId); // 假设每个帖子都有一个唯一的 id if (post) { res.render("detail", { post: post }); } else { res.status(404).send("帖子不存在"); } }); // 启动服务器 app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); loadPosts(); // 启动时加载数据 }); ``` index.ejs: ```ejs <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>阿柑笔记 - 技术分享社区</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=Pacifico&display=swap" rel="stylesheet" /> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet" /> <script src="https://cdn.tailwindcss.com"></script> <script> tailwind.config = { theme: { extend: { colors: { primary: "#1677ff", secondary: "#4096ff", }, borderRadius: { none: "0px", sm: "2px", DEFAULT: "4px", md: "8px", lg: "12px", xl: "16px", "2xl": "20px", "3xl": "24px", full: "9999px", button: "4px", }, }, }, }; </script> <style> body { min-height: 1024px; } .logo { font-family: "Pacifico", serif; } input:focus { outline: none; } input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } .tag-panel { display: none; position: absolute; top: 100%; right: 0; z-index: 10; background: white; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); padding: 8px; width: 200px; } .tag-panel.active { display: block; } </style> </head> <body class="bg-gray-50"> <header class="fixed top-0 left-0 right-0 bg-white shadow-sm z-50"> <div class="max-w-[1440px] mx-auto px-6 h-[60px] flex items-center justify-between" > <div class="flex items-center gap-12"> <a href="/" class="flex items-center gap-2"> <img src="https://ai-public.mastergo.com/ai/img_res/c9f0c303e3a496eb261ff5d39583b5eb.jpg" alt="Logo" class="w-8 h-8" /> <span class="logo text-xl text-gray-800">阿柑笔记</span> </a> <nav class="flex items-center gap-8"> <a href="/" class="text-primary font-medium">首页</a> <a href="/exchange" class="text-gray-600 hover:text-primary" >交流</a > <a href="/articles" class="text-gray-600 hover:text-primary" >文章</a > <a href="/questions" class="text-gray-600 hover:text-primary" >问答</a > </nav> </div> <div class="flex items-center gap-4"> <div class="relative"> <input type="text" placeholder="搜索" class="w-[240px] h-[36px] px-4 pl-10 bg-gray-100 rounded-button text-sm" /> <i class="fas fa-search absolute left-3 top-[10px] text-gray-400 text-sm" ></i> </div> <button class="h-[36px] px-4 bg-primary text-white rounded-button whitespace-nowrap hover:bg-primary/90" > 发布 </button> <div class="relative"> <button class="relative"> <i class="far fa-bell text-gray-600 text-xl"></i> <span class="absolute -top-1 -right-1 w-4 h-4 bg-red-500 rounded-full text-white text-xs flex items-center justify-center" >2</span > </button> </div> <a href="/profile" class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center" > <i class="fas fa-user text-gray-500"></i> </a> </div> </div> </header> <main class="max-w-[1440px] mx-auto px-6 pt-[84px] flex gap-6"> <div class="flex-1"> <div class="bg-white rounded-lg p-4 mb-6"> <form action="/post" method="POST"> <div class="flex gap-4 mb-4"> <button type="button" class="flex-1 h-[40px] bg-primary text-white rounded-button whitespace-nowrap hover:bg-primary/90" > <i class="fas fa-comment-dots mr-2"></i>随便聊 </button> <button type="button" class="flex-1 h-[40px] bg-gray-100 text-gray-600 rounded-button whitespace-nowrap hover:bg-gray-200" > <i class="fas fa-pen mr-2"></i>写文章 </button> </div> <div id="article-form" style="display: none"> <input type="text" name="title" placeholder="请输入文章标题" class="w-full h-[40px] px-4 bg-gray-50 rounded-lg mb-4 text-sm" /> </div> <div class="relative"> <textarea name="content" placeholder="快来跟大家分享吧~" class="w-full h-[120px] p-4 bg-gray-50 rounded-lg resize-none text-sm" ></textarea> <div class="absolute right-4 bottom-4 flex items-center gap-4"> <button type="button" id="tag-button" class="text-gray-400 hover:text-primary" > <i class="fas fa-hashtag text-lg"></i> </button> <button type="button" class="text-gray-400 hover:text-primary"> <i class="far fa-image text-lg"></i> </button> <button type="submit" class="px-4 h-[32px] bg-primary text-white rounded-button whitespace-nowrap hover:bg-primary/90" > 发布 </button> </div> <!-- 标签选择面板 --> <div id="tag-panel" class="tag-panel"> <% tags.forEach(tag => { %> <label class="flex items-center gap-2 p-1 hover:bg-gray-100 cursor-pointer" > <input type="checkbox" name="tags" value="<%= tag %>" class="tag-checkbox" /> <span><%= tag %></span> </label> <% }) %> </div> </div> <!-- 已选标签展示区域 --> <div id="selected-tags" class="flex mt-3 space-x-2"></div> </form> </div> <div class="bg-white rounded-lg p-4"> <div class="flex items-center gap-6 mb-6 pb-4 border-b overflow-x-auto"> <a href="#" class="text-gray-600 hover:text-primary whitespace-nowrap">关注</a> <!-- 最新标签的特殊处理 --> <a href="/" class="text-gray-600 hover:text-primary whitespace-nowrap <%= activeTag === null ? 'font-medium text-primary' : '' %>" >最新</a > <a href="#" class="text-gray-600 hover:text-primary whitespace-nowrap">热门</a> <% tags.forEach(tag => { %> <% const isActive = activeTag === tag ? 'font-medium text-primary' : ''; %> <a href="/tag/<%= tag %>" class="text-gray-600 hover:text-primary whitespace-nowrap <%= isActive %>" ><%= tag %></a> <% }) %> </div> <!-- 内容区域 --> <div class="space-y-6"> <% if (posts.length > 0) { %> <% posts.forEach(post => { %> <article class="pb-6 border-b last:border-none"> <div class="flex items-center gap-3 mb-3"> <img src="<%= post.avatar %>" alt="Avatar" class="w-10 h-10 rounded-full" /> <div> <h3 class="font-medium"><%= post.username %></h3> <p class="text-sm text-gray-500"><%= post.time %></p> </div> </div> <% if (post.title) { %> <h2 class="font-bold text-xl mb-2"><%= post.title %></h2> <% } %> <% if (post.content.length > 200) { %> <p class="text-gray-600 mb-4"> <%= post.content.substring(0, 200) %>... <a href="/post/<%= post.id %>" class="text-primary hover:underline">显示更多</a> </p> <% } else { %> <p class="text-gray-600 mb-4"><%= post.content %></p> <% } %> <!-- 展示标签 --> <% if (post.tags && post.tags.length > 0) { %> <div class="flex mt-3 space-x-2"> <% post.tags.forEach(tag => { %> <span class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded" ><%= tag %></span > <% }) %> </div> <% } %> <div class="flex items-center gap-6"> <button class="flex items-center gap-1 text-gray-500 hover:text-primary" > <i class="far fa-thumbs-up"></i> <span>0</span> </button> <button class="flex items-center gap-1 text-gray-500 hover:text-primary" > <i class="far fa-comment"></i> <span>0</span> </button> <button class="flex items-center gap-1 text-gray-500 hover:text-primary" > <i class="far fa-star"></i> <span>收藏</span> </button> <button class="flex items-center gap-1 text-gray-500 hover:text-primary" > <i class="far fa-share-square"></i> <span>分享</span> </button> </div> </article> <% }) %> <!-- 当有数据时,在最后一条数据的下面加上“已经到底了”,居中显示 --> <div class="text-center text-gray-500 mt-4">已经到底了</div> <% } else { %> <!-- 没有数据的时候,显示“暂无数据” --> <div class="text-center text-gray-500 mt-4">暂无数据</div> <% } %> </div> </div> </div> </div> <aside class="w-[300px] space-y-6"> <div class="bg-white rounded-lg p-4"> <div class="flex items-center gap-3 mb-4"> <img src="https://ai-public.mastergo.com/ai/img_res/aaef73f7b8c8438d00067702db009aa8.jpg" alt="Avatar" class="w-16 h-16 rounded-full" /> <div> <h3 class="font-medium mb-1">未登录</h3> <p class="text-sm text-gray-500">这个人还没有介绍自己</p> </div> </div> <div class="flex items-center justify-around pb-4 border-b"> <div class="text-center"> <div class="font-medium">0</div> <div class="text-sm text-gray-500">关注</div> </div> <div class="text-center"> <div class="font-medium">0</div> <div class="text-sm text-gray-500">粉丝</div> </div> </div> <button class="w-full h-[36px] mt-4 bg-primary text-white rounded-button whitespace-nowrap hover:bg-primary/90" > 登录/注册 </button> </div> <div class="bg-white rounded-lg p-4"> <div class="flex items-center justify-between mb-4"> <h3 class="font-medium">热门话题</h3> <a href="#" class="text-sm text-gray-500 hover:text-primary" >更多</a > </div> <div class="space-y-4"> <a href="#" class="flex items-center justify-between group"> <span class="group-hover:text-primary">日常</span> <span class="text-sm text-gray-500">9</span> </a> <a href="#" class="flex items-center justify-between group"> <span class="group-hover:text-primary">Java</span> <span class="text-sm text-gray-500">8</span> </a> <a href="#" class="flex items-center justify-between group"> <span class="group-hover:text-primary">Python</span> <span class="text-sm text-gray-500">8</span> </a> <a href="#" class="flex items-center justify-between group"> <span class="group-hover:text-primary">后端</span> <span class="text-sm text-gray-500">8</span> </a> <a href="#" class="flex items-center justify-between group"> <span class="group-hover:text-primary">前端</span> <span class="text-sm text-gray-500">1</span> </a> </div> </div> <div class="bg-white rounded-lg p-4"> <div class="flex items-center justify-between mb-4"> <h3 class="font-medium">热门文章</h3> <a href="#" class="text-sm text-gray-500 hover:text-primary" >更多</a > </div> <div class="space-y-4"> <a href="#" class="block group"> <h4 class="text-sm group-hover:text-primary line-clamp-2"> 深入理解 Java 虚拟机:JVM 调优实战指南 </h4> <div class="flex items-center gap-2 mt-1"> <span class="text-xs text-gray-500">阅读 2890</span> <span class="text-xs text-gray-500">评论 156</span> </div> </a> <a href="#" class="block group"> <h4 class="text-sm group-hover:text-primary line-clamp-2"> React 18 新特性解析与最佳实践 </h4> <div class="flex items-center gap-2 mt-1"> <span class="text-xs text-gray-500">阅读 2156</span> <span class="text-xs text-gray-500">评论 89</span> </div> </a> <a href="#" class="block group"> <h4 class="text-sm group-hover:text-primary line-clamp-2"> 微服务架构设计与实现:从理论到实践 </h4> <div class="flex items-center gap-2 mt-1"> <span class="text-xs text-gray-500">阅读 1890</span> <span class="text-xs text-gray-500">评论 78</span> </div> </a> </div> </div> </aside> </main> <script> // 标签功能逻辑 const tagButton = document.getElementById("tag-button"); const tagPanel = document.getElementById("tag-panel"); const selectedTagsContainer = document.getElementById("selected-tags"); const tagCheckboxes = document.querySelectorAll(".tag-checkbox"); // 显示/隐藏标签面板 tagButton.addEventListener("click", () => { tagPanel.classList.toggle("active"); }); // 选择标签 tagCheckboxes.forEach((checkbox) => { checkbox.addEventListener("change", () => { updateSelectedTags(); }); }); // 更新已选标签 function updateSelectedTags() { selectedTagsContainer.innerHTML = ""; const selectedTags = []; tagCheckboxes.forEach((checkbox) => { if (checkbox.checked) { selectedTags.push(checkbox.value); } }); selectedTags.forEach((tag) => { const tagElement = document.createElement("span"); tagElement.className = "px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded flex items-center gap-1"; tagElement.innerHTML = ` ${tag} <button onclick="removeTag('${tag}')" class="text-gray-400 hover:text-red-500"> <i class="fas fa-times"></i> </button> `; selectedTagsContainer.appendChild(tagElement); }); } // 移除标签 window.removeTag = function (tag) { const checkbox = document.querySelector( `.tag-checkbox[value="${tag}"]` ); if (checkbox) { checkbox.checked = false; updateSelectedTags(); } }; // 切换随便聊聊和写文章 document.querySelectorAll('button[type="button"]').forEach((button) => { button.addEventListener("click", () => { const isArticle = button.textContent.includes("写文章"); document.getElementById("article-form").style.display = isArticle ? "block" : "none"; document.querySelector('textarea[name="content"]').placeholder = isArticle ? "请输入文章内容" : "快来跟大家分享吧~"; }); }); </script> </body> </html> ``` detail.ejs: ```ejs <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>阿柑笔记 - 帖子详情</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=Pacifico&display=swap" rel="stylesheet" /> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet" /> <!-- 引入 highlight.js 的 CSS 文件 --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" /> <script src="https://cdn.tailwindcss.com"></script> <script> tailwind.config = { theme: { extend: { colors: { primary: "#1677ff", secondary: "#4096ff", }, borderRadius: { none: "0px", sm: "2px", DEFAULT: "4px", md: "8px", lg: "12px", xl: "16px", "2xl": "20px", "3xl": "24px", full: "9999px", button: "4px", }, }, }, }; </script> <style> body { min-height: 1024px; } .logo { font-family: "Pacifico", serif; } input:focus { outline: none; } input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } /* Markdown 内容样式 */ #markdown-content { color: #4b5563; /* 文字颜色 */ line-height: 1.6; } #markdown-content h1, #markdown-content h2, #markdown-content h3, #markdown-content h4, #markdown-content h5, #markdown-content h6 { font-weight: bold; margin-top: 1.5rem; margin-bottom: 1rem; } #markdown-content h1 { font-size: 2rem; } #markdown-content h2 { font-size: 1.75rem; } #markdown-content h3 { font-size: 1.5rem; } #markdown-content p { margin-bottom: 1rem; } #markdown-content pre { background-color: #f8f8f8; padding: 16px; border-radius: 4px; overflow-x: auto; margin-bottom: 1rem; } #markdown-content code { font-family: monospace; background-color: #f8f8f8; padding: 2px 4px; border-radius: 4px; } #markdown-content blockquote { border-left: 4px solid #ddd; padding-left: 1rem; color: #666; margin: 1rem 0; } #markdown-content table { width: 100%; border-collapse: collapse; margin: 1rem 0; } #markdown-content th, #markdown-content td { border: 1px solid #ddd; padding: 8px; } #markdown-content th { background-color: #f4f4f4; } #markdown-content ul, #markdown-content ol { margin-bottom: 1rem; padding-left: 1.5rem; } #markdown-content li { margin-bottom: 0.5rem; } #markdown-content a { color: #0066ff; } </style> </head> <body class="bg-gray-50"> <header class="fixed top-0 left-0 right-0 bg-white shadow-sm z-50"> <div class="max-w-[1440px] mx-auto px-6 h-[60px] flex items-center justify-between" > <div class="flex items-center gap-12"> <a href="/" class="flex items-center gap-2"> <img src="https://ai-public.mastergo.com/ai/img_res/c9f0c303e3a496eb261ff5d39583b5eb.jpg" alt="Logo" class="w-8 h-8" /> <span class="logo text-xl text-gray-800">阿柑笔记</span> </a> <nav class="flex items-center gap-8"> <a href="/" class="text-primary font-medium">首页</a> <a href="/exchange" class="text-gray-600 hover:text-primary" >交流</a > <a href="/articles" class="text-gray-600 hover:text-primary" >文章</a > <a href="/questions" class="text-gray-600 hover:text-primary" >问答</a > </nav> </div> <div class="flex items-center gap-4"> <div class="relative"> <input type="text" placeholder="搜索" class="w-[240px] h-[36px] px-4 pl-10 bg-gray-100 rounded-button text-sm" /> <i class="fas fa-search absolute left-3 top-[10px] text-gray-400 text-sm" ></i> </div> <button class="h-[36px] px-4 bg-primary text-white rounded-button whitespace-nowrap hover:bg-primary/90" > 发布 </button> <div class="relative"> <button class="relative"> <i class="far fa-bell text-gray-600 text-xl"></i> <span class="absolute -top-1 -right-1 w-4 h-4 bg-red-500 rounded-full text-white text-xs flex items-center justify-center" >2</span > </button> </div> <a href="/profile" class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center" > <i class="fas fa-user text-gray-500"></i> </a> </div> </div> </header> <main class="max-w-[1440px] mx-auto px-6 pt-[84px] flex gap-6"> <div class="flex-1"> <div class="bg-white rounded-lg p-4 mb-6"> <article class="pb-6"> <div class="flex items-center gap-3 mb-3"> <img src="<%= post.avatar %>" alt="Avatar" class="w-10 h-10 rounded-full" /> <div> <h3 class="font-medium"><%= post.username %></h3> <p class="text-sm text-gray-500"><%= post.time %></p> </div> </div> <% if (post.title) { %> <h2 class="font-bold text-xl mb-2"><%= post.title %></h2> <% } %> <!-- Markdown 内容容器 --> <div id="markdown-content"><%= post.content %></div> <!-- 展示标签 --> <% if (post.tags && post.tags.length > 0) { %> <div class="flex mt-3 space-x-2"> <% post.tags.forEach(tag => { %> <span class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded" ><%= tag %></span > <% }) %> </div> <% } %> <div class="flex items-center gap-6"> <button class="flex items-center gap-1 text-gray-500 hover:text-primary" > <i class="far fa-thumbs-up"></i> <span>0</span> </button> <button class="flex items-center gap-1 text-gray-500 hover:text-primary" > <i class="far fa-comment"></i> <span>0</span> </button> <button class="flex items-center gap-1 text-gray-500 hover:text-primary" > <i class="far fa-star"></i> <span>收藏</span> </button> <button class="flex items-center gap-1 text-gray-500 hover:text-primary" > <i class="far fa-share-square"></i> <span>分享</span> </button> </div> </article> </div> </div> </main> <!-- 引入 marked 和 highlight.js 的 JavaScript 文件 --> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> <script> // 使用 marked.js 渲染 Markdown 内容 const markdownContent = document.getElementById("markdown-content"); const markdownText = markdownContent.textContent; // 获取原始的 Markdown 文本 markdownContent.innerHTML = marked.parse(markdownText); // 渲染 Markdown 为 HTML // 使用 highlight.js 高亮代码块 document.querySelectorAll("pre code").forEach((block) => { hljs.highlightBlock(block); }); </script> </body> </html> ```
PS C:\Users\HUAWEI> nvm install 20.11.0 Downloading node.js version 20.11.0 (64-bit)... Extracting node and npm... Complete Installation complete. If you want to use this version, type: nvm use 20.11.0 PS C:\Users\HUAWEI> nvm ls No installations recognized. 有小伙伴遇到过这个问题吗,装了nvm,也不报错,显示安装成功,但ls了没有版本
实现一个前端开发都使用过的工具
最近在系统学习 nodejs,学到 http 模块的时候,想着写个东西熟悉一下相关的 API,那写个什么呢?也不知道怎么得鼠标点击了一下右键发现了它 ,相信每个前端都不会陌生,我们最开始学习前端都会安装的插件:https://open-vsx.org/extension/ritwickdey/LiveServer,它能帮我们启动具有**静态**和**动态页面实时重新加载**功能的**本地开发服务器**。 <img src="https://pic.code-nav.cn/post_picture/1619930914211520514/broiurXngOH88fAI.webp" alt="image-20241215213702976" width="100%" /> 我们一般都会使用它来打开我们的 html 文件,使用它启动有很多好处: - 实时预览,文件更改自动刷新游览器,支持静态和动态页面的实时更新 - 解决不能访问本地文件问题 - 使用简单,点击一键启动服务 这么神奇的能力,其实实现也很简单,今天就借助 node 来实现一个阉割版本的 liveServer, ## 需求分析 先不急写代码,让我们先分析一下我们要如何实现一个简易版的 liveServer,也就是需求分析,梳理清楚我们的实现思路,这样写代码也就水到渠成,很简单了,而且这样还有一个非常大的好处,我们后面说。实现思路: 1. 创建一个 http 服务器 2. 当启动服务器后,用户访问服务器不同的路由,也就是访问我们本地对应文件夹下的静态资源 3. 首先我们需要把 url 的路径,转为我们本地资源的路径 4. 然后我们需要判断是否存在这个文件,如果存在则读取返回 5. 如果不存在,需要再次判断**是否是目录**,如果是目录,则判断下面是否有默认文件 `index.html` ,如果有则返回文件内容,没有则返回 404 6. 如果不是目录,和文件不存在逻辑一致,返回 404 7. 这样我们就完成了基本的一个静态资源服务器,但是还有几个细节需要处理 1)我们静态资源放在哪里才能被正确读取? 2)当读取的文件后缀不同,如何也做不同的展示形式 3)如何实现静态资源更新网页也实时进行热更新 ## 功能实现 已经知道要干什么了,我们先完成初始功能代码: ```js // 搭建本地静态资源服务器 const http = require("http"); const fs = require("fs"); const path = require("path"); // 记录请求和错误 /** * 记录请求和错误 * @param {string} message - 要记录的信息 */ const log = (message) => { console.log(new Date().toISOString() + ': ' + message); }; /** * 处理请求路径并返回文件路径 * @param {string} p - 请求路径 * @returns {string} - 解析后的文件路径 */ const resolvePath = (p) => { // 去除开头的 / const filePath = p.startsWith("/") ? p.slice(1) : p; // assets 文件夹作为根目录 const fullPath = path.join(__dirname, "../assets", filePath); // 如果文件不存在 if (!fs.existsSync(fullPath)) { log(`File not found: ${fullPath}`); return null; } return fullPath; }; /** * 获取文件的内容类型 * @param {string} filePath - 文件路径 * @returns {string} - 内容类型 */ const getContentType = (filePath) => { const ext = path.extname(filePath); switch (ext) { case '.html': return 'text/html;charset=utf-8'; case '.css': return 'text/css;charset=utf-8'; case '.js': return 'application/javascript;charset=utf-8'; case '.json': return 'application/json;charset=utf-8'; default: return 'application/octet-stream'; } }; /** * 处理接受的请求和发送合适的响应 * @param {*} req HTTP 请求的请求对象,包含标头、正文和查询参数等属性。 * @param {*} res 响应对象用于将响应发送回客户端,允许您设置状态代码和响应数据。 */ const handler = (req, res) => { const filePath = resolvePath(req.url); if (filePath) { // 如果文件存在 if (fs.statSync(filePath).isFile()) { // 读取文件 fs.readFile(filePath, (err, data) => { if (err) { log(`Error reading file: ${filePath} - ${err.message}`); res.statusCode = 500; res.end('500 Internal Server Error'); return; } // 设置状态码和响应头 res.statusCode = 200; res.setHeader("Content-Type", getContentType(filePath)); // 发送响应 res.end(data); }); return; } else if (fs.statSync(filePath).isDirectory()) { // 如果是目录 // index.html 作为目录的默认文件 const indexPath = path.join(filePath, "index.html"); if (fs.existsSync(indexPath)) { // 读取 index.html fs.readFile(indexPath, (err, data) => { if (err) { log(`Error reading index file: ${indexPath} - ${err.message}`); res.statusCode = 500; res.end('500 Internal Server Error'); return; } // 设置状态码和响应头 res.statusCode = 200; res.setHeader("Content-Type", getContentType(indexPath)); // 发送响应 res.end(data); }); return; } } } // 如果文件不存在 res.statusCode = 404; res.setHeader("Content-Type", "text/html;charset=utf-8"); res.end("404 Not Found"); }; const server = http.createServer(handler); server.listen(3003, () => { log('Server running at http://localhost:3003'); }); ``` 这里我们解决了 1 和 2 两个问题,我们约定了上一级的 assets 为静态资源存放地点,所以我们需要约定式的把要静态资源放在对应的位置;我们 getContentType 函数读取访问资源的后缀,来设置 `Content-Type` ,从而实现不同资源展示不同形式 那如何实现热更新呢? ## 热更新 这里我们需要安装依赖来实现了,我们需要借助 `livereload` 和 `connect-livereload` ,使用 express 来搭建一个静态资源访问服务器,我们使用 `livereload` 创建一个服务器来监控文件变化,并使用 `connect-livereload` 中间件在 HTML 文件中注入 LiveReload 脚本,也就实现了实时静态服务器 ```js const fs = require("fs"); const path = require("path"); const livereload = require("livereload"); const connectLivereload = require("connect-livereload"); const express = require("express"); // 创建 Express 应用 const app = express(); // 使用 connect-livereload 中间件 app.use(connectLivereload()); // 记录请求和错误 const log = (message) => { console.log(new Date().toISOString() + ": " + message); }; // 处理请求路径并返回文件路径 const resolvePath = (p) => { const filePath = p.startsWith("/") ? p.slice(1) : p; const fullPath = path.join(__dirname, "assets", filePath); if (!fs.existsSync(fullPath)) { log(`File not found: ${fullPath}`); return null; } return fullPath; }; // 获取文件的内容类型 const getContentType = (filePath) => { const ext = path.extname(filePath); switch (ext) { case ".html": return "text/html;charset=utf-8"; case ".css": return "text/css;charset=utf-8"; case ".js": return "application/javascript;charset=utf-8"; case ".json": return "application/json;charset=utf-8"; default: return "application/octet-stream"; } }; // 处理接受的请求和发送合适的响应 app.get("*", (req, res) => { const filePath = resolvePath(req.url); if (filePath) { if (fs.statSync(filePath).isFile()) { fs.readFile(filePath, (err, data) => { if (err) { log(`Error reading file: ${filePath} - ${err.message}`); res.status(500).send("500 Internal Server Error"); return; } res.status(200).type(getContentType(filePath)).send(data); }); } else if (fs.statSync(filePath).isDirectory()) { const indexPath = path.join(filePath, "index.html"); if (fs.existsSync(indexPath)) { fs.readFile(indexPath, (err, data) => { if (err) { log(`Error reading index file: ${indexPath} - ${err.message}`); res.status(500).send("500 Internal Server Error"); return; } res.status(200).type(getContentType(indexPath)).send(data); }); } } } else { res.status(404).type("text/html;charset=utf-8").send("404 Not Found"); } }); // 创建 LiveReload 服务器并监控 assets 目录 const liveReloadServer = livereload.createServer(); liveReloadServer.watch(path.join(__dirname, "assets")); // 启动服务器 const PORT = 3003; app.listen(PORT, () => { log(`Server running at http://localhost:${PORT}`); }); ``` 如果你要尝试的话,记得 `npm init -y` ,然后安装依赖:`npm i express livereload connect-livereload` 现在我们再启动,就是一个实时静态服务器了 这个工具虽然简单,但是里面的知识还是很多的: - 使用 http 模块搭建服务器 - 使用 path 解析请求路径 - 使用 fs 模块进行文件的校验,读取 - 通过拆解需求,来模块化进行开发,让代码好阅读和理解 - 最后的引入两个依赖基于 express 搭建一个静态资源服务器 最后,如果你把我们上面的需求分析喂给 AI ,它大概率也能给出正确的代码,AI 时代了解知识并且知道如何通过知识解决问题的能力变得尤为重要
三五小星团队内推!一位优秀的JavaScript工程师(偏向Node后端)!
岗位职责: 1. 参与开发和维护公司的教育技术产品的前端部分及维护基于Node.js的后端服务,包括但不限于网页、小程序和移动端应用; 2. 与产品和设计团队紧密合作,确保技术实现符合用户体验和教育目标; 3. 使用前端技术栈实现界面和功能,确保代码质量通过代码审查,及后端代码的编写、测试和维护; 4. 跟踪最新的前端技术趋势、后端开发技术和框架,探索和学习新技术,以提升开发效率和产品质量; 任职要求: 1. 熟悉HTML, CSS, JavaScript,等前端基础技术; 2. 熟练掌握JavaScript,理解原型及原型链,闭包,异步编程,事件循环机制,熟悉ES6及其新特性 3. 熟悉Node.js及相关框架(如Express、Koa、Nest等),理解事件驱动和非阻塞I/O模型,掌握中间件概念,熟悉Node.js内置模块以及MongoDB或MySQL数据库操作; 4. 对React、Taro及小程序开发有一定了解和使用经验者优先 5. 高效使用ChatGPT者,优先(很重要); 6. 具备良好的编码习惯,能够编写清晰、可维护的代码,并关注开发过程中的代码规范、配置管理和文档撰写。 如果你真正地喜欢技术,喜欢coding,想要打造好产品,有强烈的自我驱动,欢迎你!请在投简历时做简要说明。 投递入口(邮箱): xiongjinkang@35xiaoxing.com (简历请以附件的形式发送,可以在正文中加入自我介绍,让我们快速了解优秀的你)
JavaScript-Vue
# 前端视图开发: html 标签 +css 样式 + js 交互 ### 前端给后端传数据:注册页面 1. 用户在表单中修改数据(修改视图 View) 2. 视图改变后,会同时改变js中的数据 (修改Model模型) 3. 直接将js中的数据传给后端 (修改Controller控制器) ### 前端展示后端数据:学生列表 1. 将后端的数据传入到js data中(修改Model模型) 2. 前端页面上有一个空的表格来获取data中的数据(修改视图View) # Vue 入门总结 ## 1. Vue的基本使用 ### 1.1 Vue的基本使用 1. 引入vue.js文件 2. 创建Vue实例 3. 挂载到某个元素上 4. 使用Vue的语法 5. 使用Vue的指令 ### 1.2 Vue的指令 1. 插值表达式: `{{}} 替换文本内容` 2. v-bind: 单项数据绑定 v-bind:属性名="数据" ```html <div id="app"> <img v-bind:src="imgUrl" alt=""> </div> <script> let app = new Vue({ el: '#app', data: { imgUrl: 'https://www.baidu.com/img/bd_logo1.png' } }) </script> ``` 3. v-on: 事件绑定 v-on:事件名="函数名" ```html <div id="app"> <button v-on:click="btnClick">按钮</button> </div> <script> let app = new Vue({ el: '#app', data: { imgUrl: 'https://www.baidu.com/img/bd_logo1.png' }, methods: { btnClick: function () { console.log('按钮被点击了') } } }) </script> ``` 4. v-if 和 v-show 的区别 - v-if: 控制元素的显示和隐藏, 如果为false, 元素会被移除 - v-show: 控制元素的显示和隐藏, 如果为false, 元素会被隐藏 ```html <div id="app"> <button v-on:click="btnClick">按钮</button> <div v-if="isShow">显示</div> <div v-show="isShow">显示</div> </div> <script> let app = new Vue({ el: '#app', data: { isShow: true }, methods: { btnClick: function () { this.isShow = !this.isShow } } }) </script> ``` 5. v-for: 循环遍历 ```html <div id="app"> <ul> <li v-for="item in list">{{item}}</li> </ul> <table> <tr> <th>姓名</th> <th>年龄</th> </tr> <tr v-for="stu in students"> <!-- <td>{{stu.name}}</td>--> <!-- <td>{{stu.age}}</td>--> <td v-for="value in stu">{{value}}</td> <!-- <td v-for="(value,key,index) in stu">{{value}}</td>--> </tr> </table> </div> <script> let app = new Vue({ el: '#app', data: { list: ['a', 'b', 'c'], students: [ {name: '张三', age: 18}, {name: '李四', age: 19}, {name: '王五', age: 20}, ] } }) </script> ``` 6. v-model: 双向数据绑定 ```html <div id="app"> <input type="text" v-model="message"> <p>{{message}}</p> </div> <script> let app = new Vue({ el: '#app', data: { message: 'hello world' } }) </script> ```
