普通视图
Bash Cheatsheet
【swift开发基础】34丨访问和操作数组:查找操作
一、访问和操作数组
1. 在末尾添加
- 可变性要求:数组必须是变量(用var定义),常量数组不可修改
- append方法:
- append(:):在数组末尾添加单个元素
- append(contentsOf:):在末尾添加多个元素(接受序列)
- 示例:
- numbers.append(100):在[2,3,4,5,6,7]后添加100 → [2,3,4,5,6,7,100]
- numbers.append(contentsOf: 100...105):添加100到105区间 → [2,3,4,5,6,7,100,101,102,103,104,105]
2. 在任意位置添加
- insert方法:
- insert(_:at:):在指定位置插入单个元素
- insert(contentsOf:at:):在指定位置插入多个元素
- 示例:
- numbers.insert(-1, at: 0):在0位置插入-1 → [-1,2,3,4,5,6,7]
- numbers.insert(contentsOf: -2...0, at: 0):在0位置插入-2到0 → [-2,-1,0,2,3,4,5,6,7]
3. 字符串也是Collection
-
特性:字符串是Character类型的集合,可直接作为内容插入字符数组
-
示例:
- chars.insert(contentsOf: "hello", at: 0):将"hello"分解为字符插入 → ["h","e","l","l","o","a","b","c"]
4. 移除单个元素
-
remove(at:):移除并返回指定位置元素(非空数组必须)
-
removeFirst():移除并返回首元素(数组为空会崩溃)
-
popFirst():安全移除首元素(返回Optional,数组为空返回nil)
-
示例:
- chars.remove(at: 1):移除索引1的元素"b" → ["a","c","d"]
- chars.removeFirst():移除"a" → ["b","c","d"]
- chars.popFirst():安全移除首元素(推荐使用)
5. 移除多个元素
-
带参数版本:
- removeFirst(_:):移除前N个元素
- removeLast(_:):移除后N个元素
-
示例:
- chars.removeFirst(2):移除前两个 → ["c","d"]
- chars.removeLast(2):移除后两个 → ["a","b"]
6. 移除多个元素
-
removeSubrange(_:):移除指定范围内的元素
-
removeAll():清空数组(释放内存)
-
removeAll(keepingCapacity:):清空但保留容量
-
容量优化:建议后续需要插入操作时使用keepingCapacity:true
-
示例:
- chars.removeSubrange(1...2):移除索引1-2 → ["a","d"]
- chars.removeAll(keepingCapacity: true):清空但容量保持4
7. 应用案例
1)例题:数组的添加和删除操作
-
实践要点:
- 添加操作必须使用变量数组
- popLast()比removeLast()更安全
- 批量操作时注意索引范围有效性
- 频繁增删时建议使用keepingCapacity保持性能
二、知识小结
| 知识点 | 核心内容 | 易混淆点 | 难度系数 |
|---|---|---|---|
| Swift数组添加元素 | 使用append在末尾添加单个元素,append(contentsOf:)添加多个元素 | 必须使用var定义可变数组 | ⭐⭐ |
| Swift数组插入元素 | 使用insert(_:at:)在指定位置插入单个元素,insert(contentsOf:at:)插入多个元素 | 插入位置索引越界会引发运行时错误 | ⭐⭐⭐ |
| Swift数组移除元素 | remove(at:)移除指定位置元素,removeFirst()/removeLast()移除首尾元素,popLast()安全移除尾元素 | removeFirst/removeLast在空数组调用会崩溃,而popLast返回可选值 | ⭐⭐⭐⭐ |
| Swift数组批量移除 | removeSubrange(_:)移除范围元素,removeAll(keepingCapacity:)保留容量清空数组 | keepingCapacity参数对性能优化的作用 | ⭐⭐⭐ |
| OC与Swift数组对比 | OC中NSArray不可变,只有NSMutableArray可修改;Swift用let/var区分 | OC需要显式使用可变类,Swift通过变量声明控制 | ⭐⭐ |
| 字符串作为集合处理 | 字符串可分解为字符插入字符数组,体现Swift的Collection协议统一性 | 字符串与字符数组的类型转换机制 | ⭐⭐⭐ |
Logoly:几秒钟做出「P站 风格」Logo 的开源小工具
很多人做个人主页、公众号封面、技术分享 PPT,都会遇到一个共同问题:
想要一个眼前一亮的 Logo,但又不会设计,也懒得打开 PS / Figma。
Logoly 就是为这种场景而生的一个开源项目:
一个可以在线生成 Pornhub / OnlyFans 风格 Logo 的小工具,只需要改几个字、调调颜色,几秒钟就能导出一张“似曾相识”的趣味 Logo。
![]()
👁️🗨️ Logoly 是什么?
Logoly 的作者给它的定义是:
A Simple Online Logo Generator for People Who Want to Design Logos Easily.
—— 让任何人都能轻松做 Logo 的在线生成器。
它最早因为可以生成类似 p站 风格的 Logo 在社区火了一把,后来又加入了 OnlyFans 风格的样式,逐渐变成一个 “恶搞 / 表情包 / 个人小品牌” 都能用的小工具。
特点概括一下:
- 在线使用,无需安装,打开网页就能玩
- 生成 P站 / OnlyFans 风格的 Logo
- 支持自定义文字内容
- 支持自定义颜色和字体大小
- 一键导出 PNG / SVG 两种格式
- 开源,基于 WTFPL 2 授权,几乎「想怎么玩就怎么玩」
github地址: github.com/bestony/log… 在线体验:www.logoly.pro/
该项目目前在github 上已有 7.9k ⭐️star
![]()
🎯 功能一览
1. 两种经典风格:P站风格 & OnlyFans
你可以一键做出这两种在互联网上辨识度极高的 Logo 风格,用来做:
- 朋友圈/微博的整活配图
- 技术分享 PPT 中的彩蛋
- 团队内部的梗图或文化海报
- 个人主页 / 博客上的趣味 Logo 等
风格本身非常强烈,但文字是你自定义的,可以天马行空。
注意:Logoly 本身不涉及任何成人内容,只是借鉴了这些品牌 Logo 的视觉风格。
2. 自定义文字内容
页面中有一个文本输入框,只需要:
- 把默认的文字改成你想要的(英文、数字、甚至中文都可以尝试)
- 实时预览 Logo 效果
很适合搞一些有梗、容易被记住的短词。
3. 自定义颜色和字体大小
你可以自由调整:
- 左右两段文字的颜色
- 中间色块的背景色(具体视当前主题而定)
- 整体字号大小,用来适配不同场景(比如头像 vs 横幅)
简单几下滑动/点选,就能做出风格迥异的变体。
4. 导出 PNG / SVG
完成之后,点击 Export 按钮可以导出两种格式:
- PNG:适合直接作为图片使用(社交头像、封面图、PPT、聊天表情等)
- SVG:矢量格式,放大不会糊,适合用在网页、印刷、再加工设计中
对于前端开发者来说,SVG 的可编辑性也很友好,可以进一步嵌入到自己的项目里。
在线使用教程:4 步搞定一个 Logo
作者已经把使用流程总结成 4 步,实际体验下来也确实非常简单:
-
打开网站:
访问 logoly.pro/ -
编辑文字:
把默认的文本替换成你想要的单词/短句 -
调整样式:
- 修改颜色
- 调整字体大小
-
导出图片:
点击 Export 按钮,选择 PNG 或 SVG 导出
整个过程基本不需要学习成本,几乎是「一看就会」。
![]()
以下是我生成的一些案例
![]()
![]()
![]()
✏️ 面向开发者:如何在本地运行 Logoly?
环境要求
项目对 Node / npm 版本有明确要求:
- Node.js 18+
- npm 10+
(官方说明:只支持 npm 作为包管理器,请不要提交其他管理器生成的 lockfile)
也就是说:
- 不建议使用 yarn / pnpm / Bun 来跑这个项目,以免锁文件不一致、CI 失败。
- CI 流水线里也是基于 npm 的脚本执行。
本地跑起来的步骤
在命令行中依次执行:
# 1. 克隆仓库
git clone https://github.com/bestony/logoly.git
cd logoly
# 2. 安装依赖
npm install
# 3. 启动开发服务器
npm run dev
启动成功后,在浏览器里访问提示的本地地址(通常是 http://localhost:xxxx),就能看到 Logoly 的开发版界面。
修改源码后即可实时看到效果。
开发完成后,可以进行构建:
npm run build
docker部署
如果你想使用docker部署,则只需要启动一个nginx 容器,将构建后的dist目录下的文件及文件夹挂载到容器中即可,不会部署nginx的家人们可以搜索下博主的历史文章,有介绍docker 部署nginx 及配置的博文。
🦆 总结
如果你:
- 需要一个有辨识度的 Logo,又不想折腾复杂设计工具
- 想给项目、团队或朋友做一个带梗的图标
- 想找一个小而美的前端开源项目学习 / 贡献
那么 Logoly 非常值得收藏一下。
- 在线生成:logoly.pro/
- 源码仓库:github.com/bestony/log…
你可以先用线上版做几个 Logo 玩玩。
解锁Vue新姿势:5种定义全局方法的实用技巧,让你的代码更优雅!
解锁Vue新姿势:5种定义全局方法的实用技巧,让你的代码更优雅!
无论你是Vue新手还是有一定经验的开发者,相信在工作中都遇到过这样的场景:多个组件需要用到同一个工具函数,比如格式化日期、权限验证、HTTP请求等。如果每个组件都单独引入,不仅代码冗余,维护起来也让人头疼。
今天我就为大家分享5种定义全局方法的实用方案,让你轻松解决这个问题!
🤔 为什么需要全局方法?
先来看一个真实的例子。假设你的项目中有三个组件都需要格式化日期:
// UserProfile.vue
methods: {
formatDate(date) {
return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
}
}
// OrderList.vue
methods: {
formatDate(date) {
return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
}
}
// Dashboard.vue
methods: {
formatDate(date) {
return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
}
}
发现了问题吗?同样的代码写了三遍! 这就是我们需要全局方法的原因。
📝 方案一:Vue.prototype(最经典的方式)
这是Vue 2时代最常用的方法,直接扩展Vue的原型链:
// main.js 或 plugins/global.js
import Vue from 'vue'
// 定义全局方法
Vue.prototype.$formatDate = function(date) {
const dayjs = require('dayjs')
return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
}
Vue.prototype.$checkPermission = function(permission) {
const user = this.$store.state.user
return user.permissions.includes(permission)
}
// 在组件中使用
export default {
mounted() {
console.log(this.$formatDate(new Date()))
if (this.$checkPermission('admin')) {
// 执行管理员操作
}
}
}
优点:
- • 使用简单,直接通过
this调用 - • 广泛支持,兼容性好
缺点:
- • 污染Vue原型链
- • 方法多了难以管理
- • TypeScript支持需要额外声明
🎯 方案二:全局混入(适合通用逻辑)
如果你有一组相关的全局方法,可以考虑使用混入:
// mixins/globalMethods.js
export default {
methods: {
$showSuccess(message) {
this.$message.success(message)
},
$showError(error) {
this.$message.error(error.message || '操作失败')
},
$confirmAction(title, content) {
return this.$confirm(content, title, {
type: 'warning'
})
}
}
}
// main.js
import Vue from 'vue'
import GlobalMixin from './mixins/globalMethods'
Vue.mixin(GlobalMixin)
// 组件中使用
export default {
methods: {
async deleteItem() {
try {
await this.$confirmAction('确认删除', '确定删除该记录吗?')
await api.deleteItem(this.id)
this.$showSuccess('删除成功')
} catch (error) {
this.$showError(error)
}
}
}
}
适合场景: UI反馈、确认对话框等通用交互逻辑。
🏗️ 方案三:独立模块 + Provide/Inject(Vue 3推荐)
Vue 3提供了更优雅的解决方案:
// utils/globalMethods.js
export const globalMethods = {
// 防抖函数
debounce(fn, delay = 300) {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
},
// 深度拷贝
deepClone(obj) {
return JSON.parse(JSON.stringify(obj))
},
// 生成唯一ID
generateId() {
return Math.random().toString(36).substr(2, 9)
}
}
// main.js
import { createApp } from 'vue'
import { globalMethods } from './utils/globalMethods'
const app = createApp(App)
// 通过provide提供给所有组件
app.provide('$global', globalMethods)
// 组件中使用
import { inject } from 'vue'
export default {
setup() {
const $global = inject('$global')
const handleInput = $global.debounce((value) => {
console.log('搜索:', value)
}, 500)
return { handleInput }
}
}
这是Vue 3的推荐方式,保持了良好的类型推断和代码组织。
📦 方案四:插件化封装(企业级方案)
对于大型项目,建议采用插件化的方式:
// plugins/globalMethods.js
const GlobalMethodsPlugin = {
install(app, options) {
// 添加全局方法
app.config.globalProperties.$http = async (url, config) => {
try {
const response = await fetch(url, config)
return await response.json()
} catch (error) {
console.error('请求失败:', error)
throw error
}
}
app.config.globalProperties.$validate = {
email(email) {
return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(email)
},
phone(phone) {
return /^1[3-9]\d{9}$/.test(phone)
}
}
// 添加全局属性
app.config.globalProperties.$appName = options?.appName || 'My App'
// 添加自定义指令
app.directive('focus', {
mounted(el) {
el.focus()
}
})
}
}
// main.js
import { createApp } from 'vue'
import GlobalMethodsPlugin from './plugins/globalMethods'
const app = createApp(App)
app.use(GlobalMethodsPlugin, {
appName: '企业管理系统'
})
// 组件中使用
export default {
mounted() {
// 使用全局方法
this.$http('/api/users')
// 使用验证
if (this.$validate.email(this.email)) {
// 邮箱有效
}
// 访问全局属性
console.log('应用名称:', this.$appName)
}
}
🌟 方案五:Composition API方式(最现代)
如果你使用Vue 3的Composition API,可以这样组织:
// composables/useGlobalMethods.js
import { readonly } from 'vue'
export function useGlobalMethods() {
// 定义所有全局方法
const methods = {
// 金额格式化
formatCurrency(amount) {
return '¥' + Number(amount).toFixed(2)
},
// 文件大小格式化
formatFileSize(bytes) {
const units = ['B', 'KB', 'MB', 'GB']
let size = bytes
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024
unitIndex++
}
return `${size.toFixed(1)} ${units[unitIndex]}`
},
// 复制到剪贴板
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text)
return true
} catch {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = text
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
return true
}
}
}
return readonly(methods)
}
// main.js
import { createApp } from 'vue'
import { useGlobalMethods } from './composables/useGlobalMethods'
const app = createApp(App)
// 挂载到全局
app.config.globalProperties.$globalMethods = useGlobalMethods()
// 组件中使用
import { getCurrentInstance } from 'vue'
export default {
setup() {
const instance = getCurrentInstance()
const $global = instance?.appContext.config.globalProperties.$globalMethods
// 或者在setup中直接引入
// const $global = useGlobalMethods()
return { $global }
},
mounted() {
console.log(this.$global.formatCurrency(1234.56))
}
}
📊 5种方案对比总结
| 方案 | 适用版本 | 优点 | 缺点 | 推荐指数 |
|---|---|---|---|---|
| Vue.prototype | Vue 2 | 简单直接 | 污染原型链 | ⭐⭐⭐ |
| 全局混入 | Vue 2/3 | 逻辑分组 | 可能造成冲突 | ⭐⭐⭐ |
| Provide/Inject | Vue 3 | 类型安全 | 使用稍复杂 | ⭐⭐⭐⭐ |
| 插件封装 | Vue 2/3 | 功能完整 | 配置复杂 | ⭐⭐⭐⭐⭐ |
| Composition API | Vue 3 | 现代灵活 | 需要Vue 3 | ⭐⭐⭐⭐⭐ |
💡 最佳实践建议
- 1. 按功能分类组织
// 不推荐:把所有方法堆在一个文件
// 推荐:按功能模块拆分
utils/
├── formatters/ # 格式化相关
├── validators/ # 验证相关
├── http/ # 请求相关
└── ui/ # UI交互相关
- 2. 添加TypeScript支持
// global.d.ts
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$formatDate: (date: Date) => string
$checkPermission: (permission: string) => boolean
}
}
- 3. 注意性能影响
- • 避免在全局方法中执行重逻辑
- • 考虑使用懒加载
- • 及时清理不再使用的方法
- 4. 保持方法纯净
- • 一个方法只做一件事
- • 做好错误处理
- • 添加详细的JSDoc注释
🎁 福利:一个实用的全局方法库
我整理了一些常用的全局方法,你可以直接使用:
// utils/essentials.js
export const essentials = {
// 下载文件
downloadFile(url, filename) {
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
},
// 获取URL参数
getUrlParam(name) {
const params = new URLSearchParams(window.location.search)
return params.get(name)
},
// 休眠函数
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
},
// 对象转FormData
objectToFormData(obj) {
const formData = new FormData()
Object.keys(obj).forEach(key => {
formData.append(key, obj[key])
})
return formData
}
}
✨ 结语
掌握全局方法的定义和使用,能够让你的Vue项目更加模块化、可维护、高效。不同的方案适用于不同的场景和需求,关键是要根据项目实际情况选择最合适的方式。
记住:好的代码不是写出来的,而是设计出来的。
希望今天的分享对你有帮助!如果你有更好的方案或实践经验,欢迎在评论区留言分享。
详细说一下nuxt generate是干啥的
nuxt generate 是 Nuxt.js 框架的一个**静态站点生成(Static Site Generation, SSG)**命令。我来详细解释一下它的含义和作用:
是什么?
nuxt generate 命令会将你的 Nuxt 应用预渲染为静态 HTML 文件。它会:
- 为每个路由生成对应的 HTML 文件
- 将生成的静态文件保存在
dist/目录中 - 包含必要的 CSS、JavaScript 和资源文件
主要作用
1. 性能优化
- 预生成的 HTML 文件无需服务器端渲染,加载速度极快
- CDN 友好,可以轻松缓存
- 减少服务器压力和响应时间
2. SEO 优化
- 搜索引擎可以直接抓取静态 HTML 内容
- 更好的 SEO 表现(相比于纯客户端渲染)
3. 部署简单
- 生成的文件可以部署到任何静态主机:
- Netlify、Vercel、GitHub Pages
- AWS S3、Firebase Hosting
- Nginx、Apache 等传统服务器
4. 成本效益
- 无需专门的 Node.js 服务器
- 可以使用廉价的静态托管服务
使用场景
适合使用 nuxt generate:
✅ 内容型网站:博客、文档、营销页面
✅ 数据不频繁变化:产品展示页、公司官网
✅ 需要优秀 SEO 的应用
✅ 高访问量 的只读页面
不适合使用(需要考虑 SSR 或 CSR):
❌ 用户个性化内容:每个用户看到的内容不同
❌ 实时数据:股票行情、聊天应用
❌ 频繁更新:社交媒体动态
❌ 需要身份验证 的页面(可通过混合模式解决)
基本使用
# 生成静态文件
nuxt generate
# 生成后预览
nuxt generate && nuxt start
# 构建并生成(常用)
npm run generate
# 在 package.json 中通常配置为:
# "scripts": {
# "generate": "nuxt generate"
# }
配置示例
// nuxt.config.js
export default {
target: 'static', // 明确指定为静态站点
generate: {
// 动态路由需要指定
routes: [
'/users/1',
'/users/2',
'/blog/post-1'
],
// 或者异步获取路由
async routes() {
const posts = await $fetch('/api/posts')
return posts.map(post => `/blog/${post.id}`)
}
}
}
工作流程
执行 nuxt generate
↓
Nuxt 启动构建过程
↓
为每个路由生成 HTML
↓
提取 CSS 和 JavaScript
↓
保存到 dist/ 目录
↓
完成!可以部署到任何静态主机
与 nuxt build 的区别
-
nuxt generate:生成静态 HTML 文件,用于静态托管 -
nuxt build:构建应用,用于服务器端渲染(SSR)部署
高级特性
1. 混合模式
// 部分页面静态生成,部分页面动态渲染
export default {
generate: {
exclude: [
'/dashboard', // 这个页面保持动态
'/admin/**' // 所有 admin 页面都动态
]
}
}
2. 增量静态再生
可以通过定时任务重新生成部分页面。
实际示例
# 1. 创建 Nuxt 项目
npx nuxi@latest init my-static-site
# 2. 安装依赖
cd my-static-site
npm install
# 3. 生成静态文件
npm run generate
# 4. 查看生成的文件
ls -la dist/
# 会看到 index.html, about.html 等
# 5. 本地测试生成的文件
npx serve dist/
总之,nuxt generate 是 Nuxt.js 强大的静态站点生成功能,特别适合需要优秀性能、SEO 和低成本部署的场景。对于适合静态化的项目,它能提供极佳的用户体验。
小程序增加用户协议
在小程序中增加一个用户协议
1.开发用户协议页面
在一个内部网站上开发一个用户协议的页面。
2.在小程序开发者后台添加业务域名
添加的时候,会给一个验证码文件。下载下来。
3. 将验证码文件放在用户协议所在网站的根目录
4. 在小程序中使用webview加载网页链接
<template>
<view class="settings-page">
<view class="list-card">
<view
class="list-item"
@tap="openPage(userAgreementUrl)"
>
<text>用户协议</text>
<view class="arrow" />
</view>
</view>
</view>
<view>
<web-view
v-if="userAgreementUrl"
:src="userAgreementUrl"
></web-view>
</template>
<script>
import Taro from '@tarojs/taro'
import { USER_AGREEMENT_URL } from '@/config/legal'
export default {
name: 'settings-page',
data() {
return {
userAgreementUrl: USER_AGREEMENT_URL,
}
},
methods: {
openPage(url) {
if (!url) {
Taro.showToast({
title: '链接未配置',
icon: 'none'
})
return
}
const target = encodeURIComponent(url)
Taro.navigateTo({
url: `/pages/webview/webview?url=${target}`
})
}
}
}
</script>
其中legal文件代码如下:
export const USER_AGREEMENT_URL = 'https:/XXXXXXX/privacy/user.html'
如何用一个 mcp 来教笨蛋 AI 好好干活
我们在写伪代码吗?
相信很多人都有这种感觉:写 Prompt 越来越像在写伪代码
- “先 xxx,再 xxx” → 对应代码的执行顺序
- “如果 xxx,就 xxx” →
if逻辑 - “一直执行直到 xxx” →
while循环
既然如此,我们能不能直接把一段“伪代码”丢给 AI?
kimi-k2 执行效果:
![]()
看起来还可以!
给它加一点限制,让它从 1+...+ 到 n,同时排除所有位数之和加起来为 5 的倍数的数字,不能调用脚本:
let count = Tool.AskUserQuestion(`please input a number`);
let sum = 0;
for (let i = 1; i <= count; i++) {
if (Prompt(`${i} 的每个位数加起来之和为 5 的倍数`)) {
} else {
sum += i;
}
}
Prompt(`最终计算结果为 ${sum}`)
![]()
好吧,这下原型毕露了,比如 86 这个完全不应该排除的数字,被它直接排除掉了。
而且它是先计算的所有值的和,再减去需要排除的值,其实没有严格按照我们的逻辑来执行。
其实好好想一想上面的过程,我们把一个伪代码丢给大模型来执行,期望于就像把代码丢给编译器来执行一样,但是 AI 有着很多的幻觉,这个“编译器”很不稳定。
在一些复杂任务的实测里,它会跳过逻辑胡乱执行,比如没按照预期调用 tool,或者直接在半路上认为自己已经成功了,特别是一些笨蛋 AI,每次执行过程和结果可能都不一样。
可以用代码驱动 AI 吗?
为了解决这种不稳定性,我们需要一种能强约束执行流程的工具。
在 Claude Code 或类似的 Agent 框架中,AI 可以根据 Tool 的返回决定下一步。那么,我们能不能反过来?由一段真实的代码来驱动 AI,AI 只负责完成其中的“自然语言函数”部分?
这正是 agent-workflow-mcp-tool 的核心思路:利用 MCP (Model Context Protocol) 协议,通过 TypeScript 的 Generator 函数,将 AI 变成流程中的一个执行单元。
下面的代码是可以真实执行的代码而非伪代码:
async function* Workflow() {
const count = yield* ClaudeCodeTools.AskUserQuestion(
`please input a number`,
z.number()
);
let sum = 0;
for (let i = 1; i <= count; i++) {
sum = yield* Prompt(`calculate ${sum} + ${i}`, z.number());
}
return sum;
}
github 地址:github.com/voderl/agen…
它有哪些优势呢:
- 用代码控制流程。 Agent lies, code not
- 使用 zod 强校验,避免模型幻觉
- 完善的 typescript 支持
- 支持 async await throw catch 等语法
- 对比其他工具特别轻量,完全基于 mcp 协议
- 配合 claude code 和 kimi-k2 & deepseek 工作良好
比如用我们再举上面的例子,如果用该工具去处理上面的问题,让 AI 从 1+...+n,同时排除所有位数之和加起来为 5 的倍数的数字,那么完整的写法如下:
import { ClaudeCodeTools, registerWorkflowTool, Prompt, z } from "agent-workflow-mcp-tool";
const server = new McpServer({
name: "agent-workflow",
version: "0.0.1",
});
registerWorkflowTool(
server,
"workflow",
{
title: "workflow",
description: `workflow control`,
},
async function* Workflow() {
const count = yield* ClaudeCodeTools.AskUserQuestion(
`please input a number`,
z.number()
);
let sum = 0;
for (let i = 1; i <= count; i++) {
if (
yield* Prompt(
`计算 "${i}" 的所有位数加起来之和是否为 5 的倍数`,
z.boolean()
)
) {
} else {
sum += i;
}
}
return sum;
}
);
这时你让 Claude Code 直接执行该 mcp
Ask: use mcp "agent-workflow" tool "workflow" directly
执行结果见下图:
![]()
kimi-k2 真的严格按照代码给定的流程,从 1 + 2 + 直到加到 87,对每一个数字判断其所有数字加起来之和是否为 5 的倍数,调用了 87 次 mcp tool,最终得出了正确的结果。
可以在图中看出,在大模型每次调用 mcp 时,mcp 会给出当前的任务,如果大模型执行成功,大模型需要在下次调用时带上上次任务的执行结果。
每一步都有完善的 zod 类型校验,如果传参不对会给大模型提示,避免大模型的传参幻觉。
基于这样的 workflow,我们也可以把“对每一个数字判断其所有数字加起来之和是否为 5 的倍数”这一步可能会有大模型幻觉产生的步骤,改为使用代码来执行保证。
async function* Workflow() {
const count = yield* ClaudeCodeTools.AskUserQuestion(
`please input a number`,
z.number()
);
let sum = 0;
for (let i = 1; i <= count; i++) {
if (sumDigits(i) % 5 === 0) {
} else {
sum = yield* Prompt(`calculate ${sum} + ${i}`, z.number())
}
}
return sum;
}
更多复杂场景
基于该工具,我们可以实现更复杂的逻辑,比如自动生成 commit 信息并在用户确认后提交代码:
registerWorkflowTool(
server,
"auto-commit",
{
title: "auto commit",
description: `auto commit`,
},
async function* Workflow() {
const filesChangeList = yield* Prompt(
`获取当前变更文件列表`,
z.array(z.string())
);
if (filesChangeList.length === 0) {
return `没有任何代码更改`;
}
const commitMessage = yield* Prompt(
`根据当前变更内容生成对应的 commit message,格式需满足:
(fix|feat|chore): 单行简洁的提示
多行详细变更内容`,
z.string()
);
const { is_confirm } = yield* ClaudeCodeTools.AskUserQuestion(
`commit message 为 ${commitMessage},是否确认继续`,
z.object({
is_confirm: z.boolean(),
})
);
if (!is_confirm) return `已取消`;
yield* Prompt(`将当前变更代码提交,commit message 为 ${commitMessage}`);
}
);
自动提交确认效果:
![]()
还可以基于上面的流程,在 commit 前获取所有变更文件,挨个给每一个文件都使用 AI review 一遍,可以试试看~
使用
npm install agent-workflow-mcp-tool
import { registerWorkflowTool, Prompt, ClaudeCodeTools, z } from 'agent-workflow-mcp-tool';
欢迎使用和反馈~
跳转传参and接收参数
你知道项目需要什么 node 版本吗?哪个包管理工具的什么版本?
近期接手了一个新项目,clone下来发现
-
readme无迹可循,node 版本等信息,只能口口相传,强依赖于上一个开发者 - 项目中有 npm, yan, pnpm 相关的配置,却无法知道明确应该使用那个包管理工具
于是开始致力于寻找解决之法
1. 指定 node 版本
1.1. 约束
问题:如果开发者任意使用了某个版本的 node,显然是不符合预期的,所以我们需要添加约束,来尽可能早的暴露错误
目的:开发者可以从项目配置获取到 node 版本信息,以及使用了不符合的 node 版本时warn 或 error
步骤一:最基础约束:package.json → engines
配置方式:
{
"engines": {
"node": ">=18 <21"
}
}
实际效果:
- npm / pnpm / yarn 会检查
不一样的包管理工具,或同一包管理工具的不同版本,对应行为的说法多种多样,最好自己试一下,下面贴我试下来的结果:
- npm 10.9.2
- pnpm 9.5.0
- yarn 1.22.22
npm: warn
![]()
pnpm: warn
![]()
yarn: error
![]()
步骤二:最基础约束基础上添加engine-strict=true
engine-strict=true
npm: error
![]()
pnpm: error
![]()
yarn: error (原本也是)
![]()
步骤三:脚本约束(最终防线 可选)
const [major] = process.versions.node.split('.').map(Number)
if (major < 18 || major >= 21) {
console.error(
`❌ Node.js version ${process.versions.node} is not supported.\n` +
`Required: >=18 <21`
)
process.exit(1)
}
{
"scripts":{
"preinstall": "node scripts/check-node.js"
}
}
1.2. node 版本切换辅助
目的:进入项目实现 node 版本自动切换,或简化手动版本切换
方案一:nvm + .nvmrc (手动)
1️⃣ 在项目根目录新建 .nvmrc
22.16.0
2️⃣ 进入项目执行 nvm use
开发者需要提前安装
nvm(macOS / Linux)- Windows 需要
nvm-windows
方案二:Volta (自动)
1️⃣ 在项目中 pin node
volta pin node@22.16.0
# 包管理工具一起固定
# volta pin pnpm@9.12.2 # 或 yarn@1.22.19 / npm@10.8.3
生成如下内容,会添加在 package.josn文件中
{
"volta": {
"node": "18.19.0"
}
}
2️⃣ 自动切换 node
不需要手动切换,进入项目后,volta 会在第一次用到 node 时自动切换为目标 node 版本(如果没有目标版本,会自动下载),进入项目 node -v可验证
开发者需要提前安装
- Volta
注意⚠️:现在 volta 不支持 uninstall node
原因:Volta 把 Node 当成“基础设施”,官方不支持、也不推荐卸载 node
偏方:自己找到文件夹的位置
~/.volta/tools/image/node/删掉
2. 指定包管理工具
包管理工具 packageManager (PM)
当看见项目中关于npm,pnpm,yarn包管理工具的配置都存在时,我一脸蒙,不知道应该用哪一个包管理工具,此时明确指定包管理工具才是预期,那么如何指定呢?
2.1. 约束
目的:开发者可以从项目配置获取到可以使用哪个包管理工具,以及使用了不符合的 node 版本时 error
步骤一:package.json -> packageManager(声明 软提醒)
比如指定 pnpm
{
"packageManager": "pnpm@10.18.3"
}
步骤二: only-allow(强制)
npx only-allow pnpm,npx only-allow npm,npx only-allow yarn
{
"preinstall": "npx only-allow pnpm"
}
如果有其他脚本,建议把这个放在前面 npx only-allow pnpm && node scripts/check-node.js,此时再用pnpm外的包管理工具可就不行了:
![]()
步骤三:脚本约束(最终防线 可选)
const userAgent = process.env.npm_config_user_agent || '';
if (!userAgent.includes('pnpm')) {
console.error('❌ 请使用 pnpm 安装依赖');
console.error('💡 运行: corepack enable && pnpm install');
process.exit(1);
}
{
"scripts":{
"preinstall": "npx only-allow pnpm && node scripts/check-node.js"
}
}
暂时用"preinstall": "node scripts/check-node.js"查看报错:
![]()
2.2. 包管理工具切换辅助 ❌
我们没法辅助开发者切换npm/ pnpm /yarn,因为他们本来就不是项目级工具,而是系统级工具,开发者想用哪个用哪个(他尽管用,我们在约束环节已经拦截)
3. 指定包管理工具版本
3.1. 约束
3.1.1. npm 专有约束
1️⃣ package.json#npm
{
"engines": {
"node": ">=18 <21",
"npm": "11.7.0"
},
}
2️⃣ .npmrc
engine-strict=true
![]()
3.1.2. pnpm 专有约束
1️⃣ package.json (声明)
{
"engines": {
"node": ">=18 <21",
"pnpm": "9.1.1"
},
}
![]()
3.1.3. 共享约束:脚本约束
三个PM都可以用的约束,以 pnpm@10.28.2 为例
import * as semver from 'semver';
import { execSync } from 'child_process';
const REQUIRED = '10.28.2';
const current = execSync('pnpm -v').toString().trim();
if (!semver.eq(current, REQUIRED)) {
console.error(`
❌ pnpm 版本不符合要求
当前版本: ${current}
要求版本: ${REQUIRED}
`);
process.exit(1);
}
3.2. 包管理工具版本辅助切换
3.2.1. pnpm 自身的版本管理
{
"packageManager": "pnpm@10.28.0",
}
pnpm 触发时,检查 packageManager字段,如果发现不一致会尝试下载并切换到packageManager指定的版本
3.2.2. yarn 依赖 packageManager+corepack
1️⃣ corepack enable
Node.js 版本 ≥ 16.9 <25 自带corepack,没有则先安装corepack
2️⃣ 提供PM信息
{
"packageManager": "yarn@1.22.20",
}
3️⃣ 自动切换
使用PM时,corepack会读packageManager如果发现版本不一致,触发自动下载
![]()
关于
corepack可以解决用什么PM(packageManager 包管理工具)?什么PM 版本?我持怀疑态度,理由如下
corepack出现的初衷本来就是为了统一PM的版本,而不是统一用户哪一个包版本工具,那都有人说它可以,那尝试一下硬着头皮用。发现用它来指定PM需要:
- 开发者本地存在
corepack或Node.js 版本 ≥ 16.9 <25(自带,也不完全自带,如果是 volta下载的,就不会带),且需要corepack enable- 无论 packageManager 配置了什么,都不限制
npm install- 只能限制
corepack下载的包版本工具,但哪个前端开发笔记本不安装几个包管理工具?发现用它来指PM版本也存在问题:
Corepack 不管理 npm, 配置了
npm@10.25.0但是任何版本的npm都会直接执行,不会下载指定版本结论:❌ 多少有些不可靠,现在能想到的应用场景就只有辅助 yarn 版本切换了
绕死我了!🙂↔️🙂↔️🙂↔️