普通视图

发现新文章,点击刷新页面。
昨天 — 2026年1月16日掘金 前端

Logoly:几秒钟做出「P站 风格」Logo 的开源小工具

作者 修己xj
2026年1月16日 23:15

很多人做个人主页、公众号封面、技术分享 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. 自定义文字内容

页面中有一个文本输入框,只需要:

  1. 把默认的文字改成你想要的(英文、数字、甚至中文都可以尝试)
  2. 实时预览 Logo 效果

很适合搞一些有梗、容易被记住的短词。

3. 自定义颜色和字体大小

你可以自由调整:

  • 左右两段文字的颜色
  • 中间色块的背景色(具体视当前主题而定)
  • 整体字号大小,用来适配不同场景(比如头像 vs 横幅)

简单几下滑动/点选,就能做出风格迥异的变体。

4. 导出 PNG / SVG

完成之后,点击 Export 按钮可以导出两种格式:

  • PNG:适合直接作为图片使用(社交头像、封面图、PPT、聊天表情等)
  • SVG:矢量格式,放大不会糊,适合用在网页、印刷、再加工设计中

对于前端开发者来说,SVG 的可编辑性也很友好,可以进一步嵌入到自己的项目里。

在线使用教程:4 步搞定一个 Logo

作者已经把使用流程总结成 4 步,实际体验下来也确实非常简单:

  1. 打开网站:
    访问 logoly.pro/

  2. 编辑文字:
    把默认的文本替换成你想要的单词/短句

  3. 调整样式:

    • 修改颜色
    • 调整字体大小
  4. 导出图片:
    点击 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 非常值得收藏一下。

你可以先用线上版做几个 Logo 玩玩。

解锁Vue新姿势:5种定义全局方法的实用技巧,让你的代码更优雅!

作者 北辰alk
2026年1月16日 22:31
解锁Vue新姿势:5种定义全局方法的实用技巧,让你的代码更优雅! 无论你是Vue新手还是有一定经验的开发者,相信在工作中都遇到过这样的场景:多个组件需要用到同一个工具函数,比如格式化日期、权限验证、H

详细说一下nuxt generate是干啥的

作者 江湖文人
2026年1月16日 18:21

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 和低成本部署的场景。对于适合静态化的项目,它能提供极佳的用户体验。

小程序增加用户协议

作者 一涯
2026年1月16日 17:55
在小程序中增加一个用户协议

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'

跳转传参and接收参数

2026年1月16日 17:34
  • route:读取当前路由信息(参数、查询参数、路径等)
  • router:进行路由跳转操作(push、replace、go等)
  1. 跳转
<script setup lang="ts">
import { useRouter } from 'vue-router'

const router = useRouter()

// 跳转到指定路径
router.push('/dashboard')

// 带参数跳转
router.push({ path: '/dashboard', query: { id: '123' } })

// 命名路由跳转
router.push({ name: 'dashboard', params: { id: '123' } })

// 替换当前路由
router.replace('/dashboard')

// 前进/后退
router.go(1)
router.go(-1)
</script>
  1. 读取:
1.直接访问queryd对象

import { useRoute } from 'vue-router'

const route = userRouter()

const id = route.query.id

2.使用computed响应式获取

import { useRoute } from 'vue-router'

import { conputed } from 'vue'

const route = userRouter()

const userId = computed(()=>route.query.id)

3.获取多个参数

import { useRoute } from 'vue-router'

const route = userRouter()

const { id,name,type } = route.query

4.在页面中的生命周期里面接收参数

import { onMounted } from 'vue'

import { useRoute } from 'vue-router'

const route = userRouter()

//生命周期中的挂载后(最常用)

onMounted(()=>{

const id = route.quert.id

console.log("id",id)

})

5.监听路由参数变化

import { watch } from 'vue'

import {useRoute } from 'vue-router'

const route = useRoute()

//监听query参数变化

watch(

()=>route.query.id,

(newId,oldId)=>{

console.log(阐述变化:',oldId,'->',newId)

})

//监听所有query参数变化

watch(()=>route.query,

(newQuery)=>{

console.log('所有参数变化',newQuery)

},

{deep:true}

)

6.在模板中直接使用
<div>ID:{{$route.query.id}}</div>

7.类型安全的方式

import { useRoute } from 'vue-router'

import { conputed } from 'vue'

const route = userRouter()

const query = computed(()=>route.query.id as string | undefined)

if(query.value){
console.log('ID',queryId.value)
}

8.实际应用写在onMounted里面

你知道项目需要什么 node 版本吗?哪个包管理工具的什么版本?

作者 Carry345
2026年1月16日 17:11

近期接手了一个新项目,clone下来发现

  1. readme 无迹可循,node 版本等信息,只能口口相传,强依赖于上一个开发者
  2. 项目中有 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需要:

  1. 开发者本地存在corepack或Node.js 版本 ≥ 16.9 <25(自带,也不完全自带,如果是 volta下载的,就不会带),且需要corepack enable
  2. 无论 packageManager 配置了什么,都不限制 npm install
  3. 只能限制 corepack下载的包版本工具,但哪个前端开发笔记本不安装几个包管理工具?

发现用它来指PM版本也存在问题:

Corepack 不管理 npm, 配置了npm@10.25.0但是任何版本的npm都会直接执行,不会下载指定版本

结论:❌ 多少有些不可靠,现在能想到的应用场景就只有辅助 yarn 版本切换了

绕死我了!🙂‍↔️🙂‍↔️🙂‍↔️

Nuxt state状态如何管理,3秒手把手帮你

作者 江湖文人
2026年1月16日 17:01

useState。有响应式和支持ssr共享。

useState是支持ssrref替代方案。其中,在后端渲染后(前端水合)会被保留,并通过唯一键在所有组件之间共享。

useState中的数据将序列化为JSON

例子

基本用法

用组件本地的计算器状态。任何用useState('counter')的组件都共享相同的响应式状态。

// app.vue
<script setup lang="ts">
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>

<template>
  <div>
    计算器:{{ counter }}
    <button @click="counter++">
      +
    </button>
    <button @click="counter--">
      -
    </button>
  </div>
</template>

初始化状态

异步解析去初始化状态。

// app.vue
<script setup lang="ts">
const websiteConfig = useState('config')

await callOnce(async () => {
  websiteConfig.value = await $fetch('https://my-cms.com/api/website-config')
})
</script>

与Pinia一起用

Pinia 模块创建全局存储并在整个应用中使用它。

// stores/website.ts
export const useWebsiteStore = defineStore('websiteStore', {
  state: () => ({
    name: '',
    description: ''
  }),
  actions: {
    async fetch() {
      const infos = await $fetch('https://api.nuxt.com/modules/pinia')
      
      this.name = infos.name
      this.description = infos.description
    }
  }
})

高级用法

// composables/locale.ts

export const useLocale = () => {
  return useState('locale', () => useDefaultLocale().value)
}

export cosnt useDefautLocale = (fallback = 'en-US') => {
  const locale = ref(fallback)
  return locale
}

export const useLocales = () => {
  const locale = useLocale()
  const locales = ref([
    'en-US',
    'en-GB',
    ...
  ])
  if (!locales.value.includes(locale.value)) {
    locales.value.unshift(locale.value)
  }
  return locales
}

export const useLocaleDate = (date: Ref<Date> | Date, locale = useLocale()) => {
  return computed(() => new Intl.DateTimeFormat(locale.value, {
    dateStyle: 'full'
  }).format(unref(date)))
}
// app.vue
<script setup lang="ts">
const locales = useLocales()
const locale = useLocale()
const date = useLocaleDate(new Date('2026-1-16'))
</script>

<template>
  <div>
    <h1>生日</h1>
    <p>{{ date }}</p>
    <label for="locale-chooser">语言</label>
    <select id="locale-chooser" v-model="locale">
      <option v-for="loc of locales" :key="loc" :value="loc">
        {{ loc }}
      </option>
    </select>
  </div>
</template>

共享状态

自动导入的组合式函数

// composables/states.ts

export const useColor = () => useState<string>('color', () => 'pink')
// app.vue
<script setup lang="ts">
const useColor = () => useState<string>('color', () => 'pink')

const color = useColor() // 与 useState('color')相同
</script>

<template>
  <p>{{ color }}</p>
</template>

用库 - 第三方的

  • Pinia
  • Harlem
  • XState

你的手势冲突解决了吗?鸿蒙事件拦截机制全解析

2026年1月16日 16:00
哈喽,兄弟们,我是 V 哥! 在鸿蒙开发中,尤其是做复杂的交互页面(比如列表里套按钮、横滑菜单、地图缩放)时,手势事件就像是一群调皮的孩子,谁都想抢着接盘。如果你不管好他们,App的体验会差强人意。
❌
❌