普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月1日首页

Vue Router 深度解析:从基础概念到高级应用实践

2026年1月1日 18:15

引言

在现代Web应用开发中,单页应用(SPA)已经成为主流开发模式。Vue.js作为当前最流行的前端框架之一,其官方路由库Vue Router在构建复杂SPA应用中扮演着至关重要的角色。本文将通过分析一个完整的Vue Router项目实例,深入探讨Vue Router的核心概念、使用方法和最佳实践,帮助开发者全面掌握这一强大的路由管理工具。

一、Vue Router基础概念解析

1.1 SPA应用的本质

单页Web应用(Single Page Web Application, SPA)的核心特点是整个应用只有一个完整的HTML页面。与传统的多页应用不同,SPA在用户与应用程序交互时,不会重新加载整个页面,而是通过JavaScript动态更新页面的部分内容。这种模式带来了更流畅的用户体验,减少了页面切换时的白屏时间,使应用更接近原生应用的体验。

从提供的代码中可以看到,App.vue作为应用的根组件,通过<router-view>标签动态渲染不同的路由组件,这正是SPA架构的典型实现。

1.2 路由的基本概念

在Vue Router中,路由本质上是路径(path)与组件(component)之间的映射关系。这种映射关系使得当用户访问特定URL时,能够展示对应的Vue组件。

javascript

复制下载

// 路由配置示例
const router = new VueRouter({
  routes: [
    {
      path: '/about',
      component: UserAbout
    },
    {
      path: '/home',
      component: UserHome
    }
  ]
})

二、Vue Router的核心配置与使用

2.1 路由器的创建与配置

index.js文件中,我们可以看到完整的路由器配置示例。路由器通过VueRouter构造函数创建,接收一个配置对象,其中routes数组定义了所有的路由规则。

javascript

复制下载

import VueRouter from 'vue-router'
import UserAbout from '../pages/UserAbout.vue'
import UserHome from '../pages/UserHome.vue'

const router = new VueRouter({
  routes: [
    {
      name: 'guanyu',
      path: '/about',
      component: UserAbout,
      meta: { title: '关于' }
    }
  ]
})

2.2 路由组件与一般组件的区别

Vue Router的一个重要实践是将路由组件和一般组件分离存储:

  • 路由组件通常存放在pagesviews文件夹中
  • 一般组件通常存放在components文件夹中

这种分离有助于代码的组织和维护,使项目结构更加清晰。从代码中可以看到,UserAboutUserHome等路由组件都存放在pages目录下,而UserBanner这样的展示组件则存放在components目录下。

2.3 多级路由(嵌套路由)的实现

Vue Router支持嵌套路由,允许在父路由组件中嵌套子路由组件。这在构建复杂布局时非常有用。

javascript

复制下载

{
  name: 'zhuye',
  path: '/home',
  component: UserHome,
  children: [
    {
      name: 'xinwen',
      path: 'news',  // 注意:此处不加/,表示相对路径
      component: UserNews
    },
    {
      name: 'xiaoxi',
      path: 'message',
      component: UserMessage,
      children: [
        {
          name: 'xiangqing',
          path: 'detail',
          component: MessageDetail
        }
      ]
    }
  ]
}

UserHome.vue组件中,通过<router-view>标签来渲染子路由组件,实现了嵌套路由的展示。

三、路由参数传递的多种方式

3.1 Query参数传递

Query参数是Vue Router中最常用的参数传递方式之一。它通过URL的查询字符串传递参数,适合传递可选参数。

vue

复制下载

<!-- UserMessage.vue中的query参数传递 -->
<router-link :to="{
  path: '/home/message/detail',
  query: {
    id: message.id,
    title: message.title
  }
}">
  {{message.title}}
</router-link>

在目标组件MessageDetail.vue中,可以通过$route.query获取这些参数:

vue

复制下载

<template>
  <ul>
    <li>id: {{$route.query.id}}</li>
    <li>title: {{$route.query.title}}</li>
  </ul>
</template>

3.2 Params参数传递

Params参数通过URL路径的一部分传递,适合传递必选参数。使用params参数时需要注意路由配置:

javascript

复制下载

{
  path: 'detail/:id/:title',  // 定义params参数
  component: MessageDetail
}

传递params参数时,必须使用name配置而非path配置:

vue

复制下载

<router-link :to="{
  name: 'xiangqing',
  params: {
    id: message.id,
    title: message.title
  }
}">
  {{message.title}}
</router-link>

3.3 Props配置简化参数接收

Vue Router提供了props配置,可以将路由参数作为组件的props传递,使组件更加独立和可复用。

javascript

复制下载

{
  name: 'xiangqing',
  path: 'detail',
  component: Detail,
  
  // 第一种写法:props值为对象
  props: { a: 900 }
  
  // 第二种写法:props值为布尔值
  props: true  // 将params参数作为props传递
  
  // 第三种写法:props值为函数
  props(route) {
    return {
      id: route.query.id,
      title: route.query.title
    }
  }
}

四、编程式路由导航

除了使用<router-link>进行声明式导航外,Vue Router还提供了编程式导航API,使路由跳转更加灵活。

4.1 基本导航方法

javascript

复制下载

// UserMessage.vue中的编程式导航示例
methods: {
  pushShow(m) {
    this.$router.push({
      path: '/home/message/detail',
      query: {
        id: m.id,
        title: m.title
      }
    })
  },
  replaceShow(m) {
    this.$router.replace({
      path: '/home/message/detail',
      query: {
        id: m.id,
        title: m.title
      }
    })    
  }
}

4.2 历史记录管理

Vue Router提供了多种历史记录管理方法:

javascript

复制下载

// UserBanner.vue中的历史记录控制
methods: {
  forward() {
    this.$router.forward();
  },
  back() {
    this.$router.back();
  },
  go() {
    this.$router.go(-2);  // 后退两步
  }
}

五、高级路由功能

5.1 缓存路由组件

在某些场景下,我们需要保持组件的状态,避免组件在切换时被销毁。Vue Router通过<keep-alive>组件实现路由组件的缓存。

vue

复制下载

<!-- UserHome.vue中的缓存配置 -->
<keep-alive include="UserNews">
  <router-view></router-view>
</keep-alive>

include属性指定需要缓存的组件名称(注意是组件name,不是路由name)。如果需要缓存多个组件,可以使用数组:

vue

复制下载

<keep-alive :include="['UserNews', 'UserMessage']">
  <router-view></router-view>
</keep-alive>

5.2 组件生命周期扩展

当使用<keep-alive>缓存组件时,Vue组件会获得两个新的生命周期钩子:

javascript

复制下载

// UserNews.vue中的生命周期示例
activated() {
  console.log('UserNews组件被激活了');
  // 启动定时器
  this.timer = setInterval(() => {
    this.opacity -= 0.01;
    if(this.opacity <= 0) {
      this.opacity = 1;
    }
  }, 16)
},
deactivated() {
  console.log('UserNews组件被停用了');
  // 清除定时器
  clearInterval(this.timer);
}

这两个钩子只在被缓存的组件中生效,分别在组件激活和失活时触发,非常适合处理如定时器、事件监听等需要在组件不显示时清理的资源。

六、路由守卫系统

路由守卫是Vue Router中用于权限控制和路由拦截的强大功能。

6.1 全局守卫

全局守卫作用于所有路由,包括全局前置守卫和全局后置守卫。

javascript

复制下载

// 全局前置守卫
router.beforeEach((to, from, next) => {
  // 权限检查示例
  if(to.meta.isAuth) {
    alert('需要授权才能查看');
    // 可以在这里进行登录检查等操作
  } else {
    next();  // 放行
  }
});

// 全局后置守卫
router.afterEach((to, from) => {
  // 修改页面标题
  document.title = to.meta.title || '默认标题';
});

6.2 独享守卫

独享守卫只作用于特定路由,在路由配置中直接定义:

javascript

复制下载

{
  path: '/about',
  component: UserAbout,
  beforeEnter(to, from, next) {
    // 进入/about路由前的逻辑
    next();
  }
}

6.3 组件内守卫

组件内守卫定义在组件内部,提供了更细粒度的控制:

javascript

复制下载

// UserAbout.vue中的组件内守卫
export default {
  name: 'UserAbout',
  
  // 进入守卫
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this`
    next();
  },
  
  // 离开守卫
  beforeRouteLeave(to, from, next) {
    // 在导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
    next();
  }
}

七、路由模式选择

Vue Router支持两种路由模式:hash模式和history模式。

7.1 Hash模式

Hash模式使用URL的hash部分(#后面的内容)来模拟完整的URL,而不触发页面重新加载。这是Vue Router的默认模式。

特点:

  • 兼容性好,支持所有浏览器
  • 不需要服务器端特殊配置
  • URL中带有#号,不够美观

7.2 History模式

History模式利用HTML5 History API实现,提供了更清洁的URL。

javascript

复制下载

const router = new VueRouter({
  mode: 'history',  // 启用history模式
  routes: [...]
})

特点:

  • URL更美观,没有#号
  • 需要服务器端配置支持,避免刷新时出现404错误
  • 兼容性相对较差(现代浏览器都支持)

八、项目架构与最佳实践

8.1 项目结构组织

基于提供的代码,我们可以总结出良好的Vue Router项目结构:

text

复制下载

src/
├── components/     # 一般组件
│   └── UserBanner.vue
├── pages/         # 路由组件
│   ├── UserAbout.vue
│   ├── UserHome.vue
│   ├── UserNews.vue
│   ├── UserMessage.vue
│   └── MessageDetail.vue
├── router/        # 路由配置
│   └── index.js
└── App.vue       # 根组件

8.2 路由配置管理

对于大型项目,建议将路由配置拆分到多个文件中:

javascript

复制下载

// router/modules/home.js
export default {
  path: '/home',
  component: () => import('@/pages/UserHome.vue'),
  children: [...]
}

// router/index.js
import homeRoutes from './modules/home'
import aboutRoutes from './modules/about'

const routes = [
  homeRoutes,
  aboutRoutes
]

8.3 路由懒加载

使用Webpack的动态导入语法实现路由懒加载,可以显著提升应用性能:

javascript

复制下载

{
  path: '/about',
  component: () => import(/* webpackChunkName: "about" */ '../pages/UserAbout.vue')
}

九、常见问题与解决方案

9.1 路由重复点击警告

当重复点击当前激活的路由链接时,Vue Router会抛出警告。可以通过以下方式解决:

javascript

复制下载

// 方法1:在push时捕获异常
this.$router.push('/path').catch(err => {
  if (err.name !== 'NavigationDuplicated') {
    throw err
  }
})

// 方法2:重写VueRouter原型方法
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err)
}

9.2 页面滚动行为控制

Vue Router允许自定义页面滚动行为:

javascript

复制下载

const router = new VueRouter({
  routes: [...],
  scrollBehavior(to, from, savedPosition) {
    // 返回滚动位置
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }
  }
})

十、总结

Vue Router作为Vue.js生态中不可或缺的一部分,为构建复杂的单页应用提供了完整的路由解决方案。从基础的路由配置到高级的守卫系统,从简单的参数传递到复杂的嵌套路由,Vue Router都提供了简洁而强大的API。

通过本文对示例代码的深入分析,我们可以总结出使用Vue Router的最佳实践:

  1. 合理组织项目结构:分离路由组件和一般组件,使代码更易维护
  2. 合理使用路由参数:根据场景选择query或params参数传递方式
  3. 善用路由守卫:实现灵活的权限控制和路由拦截
  4. 优化组件生命周期:合理使用缓存和对应的生命周期钩子
  5. 考虑路由模式:根据项目需求选择合适的路由模式
  6. 实现代码分割:使用路由懒加载优化应用性能

随着Vue 3的普及,Vue Router 4也带来了更多新特性和改进,但核心概念和设计思想与Vue Router 3保持一致。掌握本文介绍的核心概念和实践技巧,将为开发者构建高效、可维护的Vue.js应用奠定坚实基础。

cloudflare的worker中的Environment环境变量和不同环境配置

作者 1024小神
2026年1月1日 18:11

大家好,我是1024小神,想进 技术群 / 私活群 / 股票群 或 交朋友都可以私信我,如果你觉得本文有用,一键三连 (点赞、评论、关注),就是对我最大的支持~

在cloudflare中配置不同的环境变量和环境是开发中肯定会遇到的,比如密钥不能明文存储,比如开发环境和测试环境隔离,这里的配置和在vite中配置环境变量还是不一样的,所以这里记录一下。官方文档:developers.cloudflare.com/workers/wra…

环境变量

环境变量的文档:developers.cloudflare.com/workers/wra…

或者在wrangler.jsonc同级目录配置.env文件:注意.env文件不应该被git记录

API_HOST="value"
API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"

就是在wrangler.jsonc中定义变量名称,然后在代码中获取:

export default {
  async fetch(request, env, ctx) {
    return new Response(`API host: ${env.API_HOST}`);
  },
};

这里有更详细的用法说明:developers.cloudflare.com/workers/con…

当然wrangler.jsonc定义的是配置会被git同步到仓库中,肯定是不安全的,所以这里配置的一定是不重要的或测试环境的变量,在后台worker中可以配置生产环境的变量:

不同的环境

为不同的环境配置不同的环境变量也是必须的,这里有两种方式,一个是在Wrangler.jsonc中配置,另外一个就是通过配置文件.env.test、.env.prod等实现,就和在前端中配置一样简单。我这里推荐使用配置文件的方式,因为这种方式可以避免环境变量泄漏风险。

配置.env.test文件:

使用命令启动:

wrangler dev --env test

就可以看到加载的环境变量:

或者写一个接口来查询环境变量信息:

得到的结果:

如果你有好的想法或需求,都可以私信我,我这里有很多程序员朋友喜欢用代码来创造丰富多彩的计算机世界

终端环境:zsh、oh-my-zsh与 7 个效率插件

作者 ____xl
2026年1月1日 17:40

终端环境:zsh、oh-my-zsh、提示主题与 7 个效率插件

本文主要介绍如何构建一个高效的终端环境,包括 zsh 与 bash 的对比、zsh 的安装、oh-my-zsh 框架的使用与主题配置,以及 7 个实用插件(含 5 个内置插件 + 2 个社区插件),提升命令行效率。


1. 为什么使用 zsh?

zsh(Z Shell)是一个功能强大的 shell,相较 bash 有以下优势::

  • 更强的自动补全:不仅补全命令,还能补全参数、选项和文件名,同时可显示简短帮助提示。
  • 更好的脚本与插件支持:拥有活跃社区和丰富插件生态,可大幅增强 shell 功能。
  • 主题与提示符高度可定制:自定义命令行外观和显示内容(如 Git 分支、环境信息等)。
  • 智能交互体验:支持拼写纠正与近似完成等功能,提高使用便捷性。
  • 增强文件匹配及配置灵活性:支持扩展通配符和历史共享等高级特性。

2. 安装 zsh

在不同操作系统下安装 zsh 的方式各不相同,常见命令示例:

# Debian / Ubuntu
apt install zsh

# CentOS
yum install -y zsh

# Arch
pacman -S zsh

# Fedora
dnf install zsh

macOS 自 2019 起默认使用 zsh,无需手动安装。若需要安装或更新,可使用 brew install zsh。安装后,用 chsh -s /bin/zsh 将 zsh 设置为默认 shell。


3. oh-my-zsh 框架

oh-my-zsh 是一个用于管理 zsh 配置的轻量框架,内置大量主题与插件。安装命令如下:

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

安装完成后,将自动启用基础主题与插件。可通过编辑 ~/.zshrc 来更改主题和插件配置。


4. 主题配置

oh-my-zsh 内置了多个提示符主题,可在 ~/.zshrc 中更改:

ZSH_THEME="agnoster"

也可将 ZSH_THEME 设置为 random,随机选取主题。


5. 内置插件(5 个)

插件启用方式

~/.zshrc 中指定:

plugins=(git web-search jsontools z vi-mode)

插件介绍

  1. git – 提供常用 Git 命令别名,提高操作效率。
  2. web-search – 在终端直接打开浏览器并执行搜索。
  3. jsontools – 提供 JSON 格式化等基本处理工具。
  4. z – 基于历史访问目录的快速跳转工具。
  5. vi-mode – 允许使用 vi 键盘模式编辑命令行。

6. 社区插件(2 个)

这两款插件需要从 GitHub 下载并配置到 oh-my-zsh:

git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

并将其加入插件列表:

plugins=(... zsh-syntax-highlighting zsh-autosuggestions)

插件详情

  • zsh-syntax-highlighting:提供命令实时语法高亮,语法错误时以不同颜色提示。
  • zsh-autosuggestions:根据历史命令和补全提示提供智能建议,可通过自定义快捷键(如 Tab)来接受建议。

7. 总结

一般以上插件基本满足99%的场景使用,如果其它推荐欢迎补充!

dify案例分享-免费体验Dify + Qwen-Image-2512 文生图图生图全流程

作者 wwwzhouhui
2026年1月1日 17:32

1.前言

在AI图像生成领域快速迭代的今天,如何用低成本、低门槛的方式体验最新的文生图、图生图技术,成为了小伙伴们关注的焦点。传统的AI绘画工具要么需要复杂的本地部署、要么需要高昂的API调用费用,普通用户想要"玩转"AI绘画往往望而却步。

好家伙!阿里通义千问团队这2天又放大招了!继8月发布Qwen-Image基础模型后,12月又重磅推出了Qwen-Image-2512文生图模型,同时11月发布的Qwen-Image-Edit-2511图生图模型也正式上线魔搭社区。这两款模型在AI Arena超过1万局的用户盲测中,开源模型表现最优,甚至与多款闭源模型对比中依然展现出显著竞争力!

image-20260101154038359

之前给大家做过一个基于Qwen-Image文生图 和图生图的dify插件,今天上午也升级了。另外也使用最新的Qwen-Image-2512验证测试了一下。今天我们就在Dify平台手把手教大家部署这个AI绘画工作流,体验和感受一下这两款最新模型的强大能力。话不多说,我们开始吧!

2.模型介绍

在正式开始工作流制作之前,我们先来了解一下这次更新的两款重磅模型。

Qwen-Image-2512(文生图模型)

Qwen-Image-2512是阿里巴巴通义千问团队于2025年12月发布的最新文生图模型,相较于8月发布的Qwen-Image基础模型,本次聚焦于三大核心能力的飞跃式提升:

✨ 三大核心升级

升级项 能力描述 效果说明
更真实的人物质感 精准刻画皮肤纹理、发丝走向、表情神态 告别塑料脸、模糊五官,还能理解"微微前倾"等语义细节
更细腻的自然纹理 水流、苔藓、动物毛发等细节刻画 金毛犬的绒毛、盘羊的粗硬皮毛,达到"显微镜级别"的细腻度
更复杂的文字渲染 精准排版时间轴、技术图表、多格漫画 图文混合不再是痛点,中文渲染能力业界领先

更真实的人物质感

image-20260101171907960

更细腻的自然纹理

9184a942-4d86-4ab7-a882-3f79ec58a51d

更复杂的文字渲染

jimeng-2026-01-01-4091-这是一张现代风格的科技感幻灯片,整体采用深蓝色渐变背景。标题是 “Dify 发展...

🏆 性能表现

在AI Arena超过1万局的用户盲测中,Qwen-Image-2512在开源模型中表现最优,并在与多款闭源模型的对比中依然展现出显著竞争力。

1767259065580

Qwen-Image-Edit-2511(图生图模型)

Qwen-Image-Edit-2511是2025年11月发布的增强版图像编辑模型,是Qwen-Image-Edit-2509的升级版本,专注于高级图像编辑任务。

✨ 核心特点

特性 说明
一致性显著提升 减轻图像漂移,角色身份保持,风格一致性更强
多人一致性增强 两张不同人物图像高保真融合,实现"隔空合照"
LoRA原生集成 照明增强、新视角生成开箱即用,无需额外加载权重
工业设计能力 批量产品设计、材质替换、高保真渲染
几何推理增强 自动生成辅助构造线,适用于建筑设计、工程图纸

image-20260101153547398

image-20260101153621027

🎯 应用场景

  • 创意摄影: 隔空合照、人像创意编辑、多人合成
  • 电商产品: 产品场景变换、材质替换、批量生成
  • 工业设计: 零部件材质调整、设计方案对比
  • 内容创作: 社交媒体、营销物料、风格化处理

image-20260101153658437

版本对比

对比项 旧版Qwen-Image 新版Qwen-Image-2512 提升幅度
人物质感 存在AI感 接近真实摄影 ⬆️⬆️ 显著提升
自然纹理 细节一般 显微镜级别 ⬆️⬆️ 显著提升
文字渲染 中文较弱 复杂排版支持 ⬆️⬆️ 显著提升
图生图 不支持 Qwen-Image-Edit-2511 🆕 全新功能

之前给大家介绍过dify插件开发,其中使用就是阿里Qwen-Image模型。当时实现的是文本生成模型。前段时间我也把这个插件上传到dify插件市场了。

img

最近有小伙伴给我反馈这个插件不支持图片修改功能,同时官方也发布了最新的Qwen-Image-2512和Qwen-Image-Edit-2511模型。于是我更新了这个插件,目前已经支持最新的文生图和图生图模型了。工作流效果如下:

img

那么这个工作流是如何制作的呢?下面给大家简单介绍一下。

3.工作流制作

插件安装

制作这个工具流之前我们先去dify插件市场查找这个插件。搜索关键字"Text2image" 新版本插件我已经提交了,注意最新版本是0.0.4

如果没有的可以在文本找一下离线插件。

img

搜到到这个插件后安装即可。

img

安装或者更新这插件后,我们可以在魔搭API进行相关授权。

魔搭API配置

去魔搭社区官方网站找到你的API

img

把这个值复制到刚才的插件api key输入区域

img

这样我们就完成模型授权。

img

接下来我们给大家介绍一下工作流详细步骤。

开始节点

这个开始节点有2个部分组成:type类型 和 picture 图片

type类型是一个下拉选项,主要是提供用户的文生图、图生图选择项

img

picture 图片是由单个文件图片构成

img

以上我们就完成了开始节点的配置。

条件分支

条件分支这里我们可以实现文生图和图生图的判断。我们可以设置如下信息

img

文生图(Qwen-Image-2512)

这个地方就是我们可以从添加节点-选择我们上面安装好的插件。

img

我们选中文生图插件。

提示词部分我们直接获取sys.query

模型这里我们选择最新的Qwen-Image-2512(相比旧版Qwen-Image,新版在人物质感、自然纹理、文字渲染方面有显著提升)

img

图生图(Qwen-Image-Edit-2511)

图生图和上面文生图的操作类似。从添加节点 - 工具选择 图生图

img

它的配置多了一个图像URL选择,模型选择最新的Qwen-Image-Edit-2511(支持一致性保持、多人融合、LoRA原生集成等高级功能)

img

直接回复

这个直接回复比较简单,就是把文生图和图生视频的信息返回

img

以上我们就配置了最简单的基于Qwen-Image-2512和Qwen-Image-Edit-2511插件的文生图、图生图功能了。

有的小伙伴说这个文生图的提示词太简单了,能不能给我扩写成一个专业的基于Qwen-Image的提示词呢?当然这个也是可以的。

提示词生成

我这里有一份Qwen-Image提示词指南

核心要点:
抓重点:主体 + 背景 + 细节,不要跑题
补特征:人要写清姿态表情,物要写清材质颜色
写文字:用引号标明,还要写清位置和字体
定风格:纪实/国风/童趣,风格统一更稳定
理空间:左上右下,前后层级要讲明
正向写:别说"不要",直接说你要什么
去赘余:画面里没的东西,就别写

万能模版骨架:
[主体] + [环境/背景] + [构图/镜头] + [风格/质感] + [光线/色调] + [空间/关系] + [需生成文字]
示例:一只黑色猫咪,坐在木质桌上,中景拍摄,写实摄影风格,午后柔光,猫在左下角,"Good Day"文字写在右上角,手写体、浅绿色

小技巧:
把否定词改成正向表达:
"不要复杂背景""纯色背景"
"不要太暗""整体偏明亮"
"不要拥挤""留白充足"

请基于以上内容编写一个编写提示词,使用LangGPT提示词(prompt)语法编写一个Qwen-Image文生图提示词专家。

我们把上面的提示词发给AI让它给我们生成出来

img

img

AI很快就帮我生成好提示词了。

img

LLM大语言模型

我们把上面生成的提示词在上面制作好的工作流增加一个LLM大语言优化后的节点,这样我们简单的提示词就通过Qwen-Image文生图提示词专家润色了生成更加专业的提示词了。

模型这里我们选择魔搭社区提供的免费的qwen3-Coder-30B-A3B-Instruct模型

img

系统提示词

# Role: Qwen-Image文生图提示词专家

## Profile
- Author: 周辉
- Version: 1.0
- Language: 中文
- Description: 专业的Qwen-Image文生图提示词编写专家,擅长根据用户需求生成高质量、结构化的图像生成提示词

## Skills
1. 熟练掌握Qwen-Image模型的提示词规则和特点
2. 能够将用户模糊描述转化为精确的结构化提示词
3. 擅长运用万能模版骨架进行提示词构建
4. 精通正向表达技巧,避免否定词使用
5. 熟悉各种艺术风格和拍摄技法的专业术语

## Rules
1. 严格遵循"抓重点、补特征、写文字、定风格、理空间、正向写、去赘余"七大核心要点
2. 必须使用万能模版骨架:[主体] + [环境/背景] + [构图/镜头] + [风格/质感] + [光线/色调] + [空间/关系] + [需生成文字]
3. 所有否定表达必须转换为正向表达
4. 人物描述必须包含姿态和表情
5. 物体描述必须包含材质和颜色
6. 文字内容用引号标明,并说明位置和字体
7. 空间关系要明确(左上右下、前后层级)
8. 避免描述画面中不存在的元素

## Workflow
1. **需求分析**:理解用户的图像需求,识别关键元素
2. **要素提取**:从用户描述中提取主体、背景、风格等核心要素
3. **结构构建**:按照万能模版骨架组织提示词结构
4. **正向优化**:将所有否定表达转换为正向描述
5. **细节补充**:为人物补充姿态表情,为物体补充材质颜色
6. **质量检查**:确保提示词符合七大核心要点

## OutputFormat
【提示词】:[按万能模版骨架生成的完整提示词]

【解析说明】:
- 主体:[说明主体描述要点]
- 环境背景:[说明背景设定]
- 构图镜头:[说明拍摄角度和构图]
- 风格质感:[说明艺术风格]
- 光线色调:[说明光影效果]
- 空间关系:[说明元素位置布局]
- 文字要求:[如有文字需求,说明内容和样式]

## Example
用户需求:我想要一张可爱的小女孩在花园里的照片

【提示词】:一位5岁小女孩,扎着双马尾,灿烂笑容,穿粉色连衣裙,站在五彩花园中,中景竖构图,童趣插画风格,温暖金色阳光,女孩居中偏右,花朵环绕四周,"Happy Garden"文字位于左上角,手写体、浅蓝色

【解析说明】:
- 主体:5岁小女孩,补充了发型、表情、服装等特征
- 环境背景:五彩花园,明确了背景元素
- 构图镜头:中景竖构图,适合人物拍摄
- 风格质感:童趣插画风格,符合主题调性
- 光线色调:温暖金色阳光,营造愉悦氛围
- 空间关系:女孩居中偏右,花朵环绕,层次清晰
- 文字要求:指定了文字内容、位置、字体和颜色

## Initialization
你好!我是Qwen-Image文生图提示词专家。我将根据Qwen-Image的特点和最佳实践,为您生成高质量的文生图提示词。

请告诉我您想要生成什么样的图像,我会运用专业的结构化方法,为您量身定制精准的提示词。无论是人物、风景、静物还是抽象艺术,我都能帮您转化为Qwen-Image能够完美理解的描述语言。

用户提示词

请根据用户输入的{{#sys.query#}}扩展这个文生图提示词

img

添加后的LLM大语言模型后,text-to-image这里输入提示词需要修改成从llm大语言模型输入

img

以上我们就通过LLM大语言模型扩展了文生图提示词。

4.验证及测试

文生图测试

76d642e1-c092-4f51-b4f0-1e15b4669d82

image-20260101162541732

图生图测试

e8801994-56b3-49b4-ad9b-e4841f770b14

【提示词】:猴子头上带个紧箍咒

image-20260101163537514

Qwen-Image-2512 新特性体验

Qwen-Image-2512在人物质感方面有了显著提升,我们来体验一下:

人物质感测试提示词示例

一位中国女性大学生,性别女,年龄约20岁左右,超短发发型略带柔和文艺感,发丝自然垂落遮住部分脸颊,整体风格偏向假小子(tomboy)气质。她肤色冷白,五官清秀,表情略显羞涩又带着一丝拽劲,嘴角微微歪起,流露出痞帅又青春的神态。身穿一字领露肩短袖上衣,露出一侧肩膀,身材匀称。画面为近景自拍构图,人物占据主体位置,背景清晰可见宿舍环境。

image-20260101164018155

自然纹理测试提示词示例

一只花猫的超写实特写肖像,置于柔和自然日光下的户外场景中;毛发细节极为精细 —— 根根分明,橘白黑三色的斑纹自然交错,色泽从暖橘色到纯净白色再到深邃黑色过渡得丝滑流畅,微光在毛尖轻盈跳跃,微风拂过带来轻微蓬松感;底层绒毛柔软浓密,外层护毛修长分明,层次清晰可见;双眼清澈湿润、富有情感,像透亮的琉璃珠子,鼻头微润并带有细腻的高光反光。

image-20260101165330096

新版模型在这些场景下的表现确实令人惊艳,皮肤纹理、发丝走向、动物毛发都能精准刻画到"显微镜级别"。

体验地址

工作流地址:dify.duckcloud.fun/chat/rk31bv…

备用地址:http://14.103.204.132/chat/rk31bvsH0gWasqDW

插件下载

离线安装包: qwen_text2image_0.0.4.difypkg

通过网盘分享的文件:qwen_text2image_0.0.4.difypkg 链接: pan.baidu.com/s/1EK5mJxJA… 提取码: segu

5.总结

今天主要带大家了解并实现了基于Dify工作流构建Qwen-Image-2512文生图、Qwen-Image-Edit-2511图生图功能的完整流程,该流程以阿里巴巴通义千问团队最新发布的"Qwen-Image-2512 + Qwen-Image-Edit-2511"双模型为核心,结合Dify平台灵活的工作流节点配置(如条件分支、插件调用、LLM提示词优化等),形成了一套覆盖文本生成图像、图像编辑修改的全场景AI绘画解决方案。

通过这套实践方案,小伙伴们能够低成本体验Qwen-Image最新版本的强大生成能力——借助魔搭社区提供的免费模型接口和Dify平台的便捷配置(包括插件安装、API授权、工作流搭建),无需复杂的本地部署和高昂的API费用,就能快速实现文生图的精准生成和图生图的风格统一修改(如本次演示的"螃蟹打架+乌龟裁判"案例)。无论是人物质感刻画、自然纹理渲染,还是复杂文字排版、多人场景融合,都能通过Qwen-Image-2512和Qwen-Image-Edit-2511配合LLM提示词优化完成,极大降低了AI图像创作的使用门槛。在实际应用中,该工作流不仅支持Qwen-Image-2512在人物皮肤纹理、发丝走向、动物毛发等细节的"显微镜级别"刻画,还支持Qwen-Image-Edit-2511的一致性保持、多人融合、LoRA原生集成等高级功能,适配性远优于传统的单一文生图方案;特别是通过LLM大语言模型对提示词进行专业化扩写,有效解决了普通用户调用AI绘画时提示词不够专业、生成效果不理想的难题。

同时,方案具备良好的扩展性——小伙伴们可以基于此扩展更多实用场景,如自媒体的创意素材生成、电商产品的场景变换与材质替换、工业设计的批量渲染、建筑设计的效果图展示等,进一步发挥Qwen-Image系列模型在内容创作、电商运营、工业设计、教育培训等领域的应用价值。感兴趣的小伙伴可以按照文中提供的步骤进行实践,根据实际业务需求调整提示词和工作流配置。今天的分享就到这里结束了,我们下一篇文章见。

从零开始调用大模型:使用 OpenAI SDK 实现歌词生成,手把手实战指南

作者 栀秋666
2026年1月1日 17:23

引言

在 AIGC 浪潮席卷全球的今天,大语言模型(LLM) 已不再是科研实验室里的“黑科技”,而是每一个开发者都能轻松调用的强大工具。

而作为行业标杆的 OpenAI,通过其简洁高效的 API 和完善的 SDK 支持,让我们只需几行代码就能让 AI 写诗、作词、编程甚至写周报!

本文将以一个真实场景为例——让 GPT 为汪峰写一首献给“森林北”的情歌,带你从环境搭建到文本生成,完整走通一次大模型调用流程。

全程无坑、代码可运行,新手友好,老鸟也能收获细节技巧,建议收藏+点赞,防止走丢!


🌱 一、开发准备:5分钟初始化项目

我们使用 Node.js + npm 搭建后端服务环境,轻量高效,适合快速验证 AI 能力。

1. 初始化项目

npm init -y

这条命令会自动生成 package.json 文件,记录你的项目依赖和配置信息。

2. 安装 OpenAI SDK

npm install openai

OpenAI 官方提供的 SDK 封装了所有 API 接口,省去手动处理 HTTP 请求的繁琐工作,一行引入,即刻调用

⚠️ 注意:SDK 需要 Node.js 版本 >= 16,请确保本地环境满足要求。


🔑 二、配置客户端:拿到通往 AI 世界的钥匙

接下来是关键一步:创建 OpenAI 客户端实例

import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: 'sk-RUP7SvQy4trgMCDsbBXxjgpNSR235Kqa7tjIh8jv1NBlMnzI', // 替换为你自己的 Key
  baseURL: 'https://api.302.ai/v1' // 可选:代理地址,解决网络访问问题
});

关键参数说明:

参数 说明
apiKey 访问权限凭证,相当于“密码”,务必保密!
baseURL API 地址,默认为 https://api.openai.com/v1,此处使用第三方代理(如无法直连 OpenAI 可用)

💡 小贴士

  • Key 可在 OpenAI 官网 获取。
  • 若你在国内,推荐使用稳定中转服务(如 302.ai、FastGPT 等),避免请求超时。

🎤 三、调用模型:让 AI 成为林夕级别的作词人

我们的目标很明确:

“以林夕风格,为汪峰写一首关于‘森林北’的爱情歌曲,100 字左右。”

使用 completions.create() 接口

这是 OpenAI 最经典的文本生成接口,适用于单次输入、输出任务,比如写文案、写歌词、补全文本等。

const response = await openai.completions.create({
  model: 'gpt-3.5-turbo-instruct',
  prompt: `
    假如你是林夕这样的爱情歌曲作词大家,
    请你写一首100字,为汪峰,写一首他爱上森林北的歌曲。
    森林北是一位美丽,勇敢,会骑马的女孩儿。
  `,
  max_tokens: 800,
  temperature: 1
});

参数详解:

参数 作用 推荐值
model 指定模型版本 gpt-3.5-turbo-instruct 性价比之王
prompt 提示词,决定生成内容方向 描述越细,结果越准 ✅
max_tokens 控制最大输出长度 一般设为 512~1024
temperature 创意随机性控制 0.7~1.0 平衡创意与稳定性

🎯 Prompt 工程技巧

  • 明确角色:“假如你是林夕”
  • 给出对象:“汪峰爱上了森林北”
  • 设定特征:“会骑马、勇敢、自由的灵魂”
  • 限制格式:“100字以内,押韵优先”

✅ 优质 Prompt = 高质量输出!


📤 四、获取结果:把 AI 的灵感打印出来

API 返回的是 JSON 格式数据,我们需要从中提取生成的文本。

const song = response.choices[0].text.trim();
console.log('🎵 歌词是:\n' + song);

示例输出(模拟):

image.png 是不是已经有汪峰那味儿了?🎸


🛡️ 五、健壮性增强:加入错误处理机制

实际开发中不能忽略异常情况。网络波动、Key 失效、请求超时都可能导致程序崩溃。

建议用 try/catch 包裹调用逻辑:

try {
  const response = await openai.completions.create({ /* ... */ });
  const song = response.choices[0].text.trim();
  console.log('✅ 成功生成歌词:\n' + song);
} catch (error) {
  console.error('❌ 调用失败:', error.message);
  if (error.status === 401) {
    console.log('👉 检查 API Key 是否正确');
  }
}

常见错误码:

  • 401: Key 错误
  • 429: 请求频率过高
  • 500: 模型服务器异常

🔄 六、进阶思考:Completion vs Chat 接口怎么选?

你可能会问:现在主流都是 chat.completions,为什么还用 completion

对比表格:

特性 completions chat.completions
适用场景 单轮生成任务 多轮对话系统
输入格式 纯文本 prompt 消息数组(role-based)
上下文记忆 ❌ 不支持 ✅ 支持历史对话
成本 较低 略高(尤其长上下文)
推荐用途 写作、摘要、填空 客服机器人、聊天应用

📌 结论

  • 如果只是一次性生成内容(如写歌词、写邮件),completion 更简单直接;
  • 如果要做智能对话系统,必须上 chat 接口。

🚀 七、拓展玩法:你可以这样玩得更嗨

学会了基础调用,下一步就是创造价值!以下是一些延展思路:

1. 批量生成歌词片段

结合数据库或 CSV,批量为不同人物生成专属情歌。

2. 构建 Web 页面

用 Express 或 Next.js 搭个网页,让用户填写“歌手+恋人名字+关键词”,实时生成歌词。

3. 微调模型(Fine-tuning)

收集林夕风格歌词进行微调,打造专属“林夕Bot”。

4. 结合语音合成

用 TTS(Text-to-Speech)把歌词念出来,做成 AI 演唱 Demo!


💬 八、结语:掌握核心能力,才能驾驭 AI 浪潮

通过这篇文章,你已经掌握了:

✅ 如何初始化 Node.js 项目
✅ 如何安装并配置 OpenAI SDK
✅ 如何编写高质量 Prompt
✅ 如何调用 completion 接口生成文本
✅ 如何处理异常、优化用户体验

更重要的是,你理解了:

大模型不是魔法,而是工具;真正的魔法,在于你怎么使用它。

无论你是前端、后端、全栈还是产品经理,只要学会调用大模型,就能为自己赋能十倍效率。


面试官 : “ 说一下 localhost 和127.0.0.1 的区别 ? ”

作者 千寻girling
2026年1月1日 17:01

localhost 是主机名(域名) ,属于应用层概念;

127.0.0.1 是IPv4 回环地址,属于网络层概念。

两者都用于访问本机服务,但 localhost 必须通过解析才能映射到具体 IP(默认是 127.0.0.1 或 IPv6 的 ::1),而 127.0.0.1 是直接的网络层标识,无需解析。


一、本质定义与协议层次

概念 localhost 127.0.0.1
本质 互联网标准规定的特殊主机名(RFC 6761 定义) IPv4 协议规定的回环地址(RFC 5735 定义)
协议层次 应用层(DNS 协议解析范畴) 网络层(IP 协议寻址范畴)
归属 属于域名系统(DNS) 属于 IP 地址体系
默认映射 IPv4: 127.0.0.1;IPv6: ::1 仅 IPv4 回环网段(127.0.0.0/8)的第一个地址

关键补充

  1. 127.0.0.0/8 网段:不只是 127.0.0.1,整个 127.x.x.x 网段(共 16777216 个地址)都属于回环地址,访问任何一个都会指向本机。
  2. localhost 的特殊性:它是一个保留主机名,不能被注册为公共域名,且操作系统会优先通过 hosts 文件解析,而非公共 DNS 服务器。

二、解析流程的根本差异

这是两者最核心的区别 ——是否需要解析,以及解析的顺序

1. localhost 的解析流程(应用层 → 网络层)

当你在浏览器输入 http://localhost:3000 时,操作系统会执行以下步骤:

  1. 检查本地 hosts 文件

    • Windows 路径:C:\Windows\System32\drivers\etc\hosts
    • Linux/macOS 路径:/etc/hosts
    • 如果 hosts 文件中有如下映射:127.0.0.1 localhost 或 ::1 localhost,则直接使用对应的 IP。
  2. 若 hosts 文件无映射,查询本地 DNS 缓存

    • 操作系统会检查之前是否解析过 localhost,若有缓存则直接使用。
  3. 若缓存无结果,查询本地 DNS 服务器

    • 但由于 localhost 是保留主机名,公共 DNS 服务器通常也会返回 127.0.0.1 或 ::1
  4. 解析完成后,转换为 IP 地址进行网络请求

    • 此时才进入网络层,使用解析后的 IP 连接本机服务。

2. 127.0.0.1 的访问流程(直接进入网络层)

当你输入 http://127.0.0.1:3000 时,跳过所有解析步骤

  1. 操作系统直接识别这是一个 IPv4 回环地址。
  2. 直接将网络请求发送到本机的网络接口(回环接口,lo 接口)。
  3. 目标服务监听 127.0.0.1 或 0.0.0.0 时,即可响应请求。

三、功能与使用上的具体差异

1. 协议支持差异

  • localhost:支持 IPv4 和 IPv6 双协议

    • 若你的系统开启了 IPv6,localhost 可能优先解析为 ::1(IPv6 回环地址)。
    • 例如:在 Node.js 中,server.listen(3000, 'localhost') 会同时监听 IPv4 的 127.0.0.1:3000 和 IPv6 的 ::1:3000
  • 127.0.0.1仅支持 IPv4

    • 无论系统是否开启 IPv6,使用 127.0.0.1 都只会走 IPv4 协议。
    • 例如:server.listen(3000, '127.0.0.1') 仅监听 IPv4 地址。

2. 性能差异

  • 127.0.0.1 略快:因为跳过了 DNS 解析流程(即使是本地 hosts 文件解析,也需要一次文件读取和匹配)。
  • 差异极小:在开发环境中,这种性能差异几乎可以忽略不计,除非是高频次的请求(如每秒上万次)。

3. 服务监听的差异

服务端程序的监听地址,会影响是否能被 localhost 或 127.0.0.1 访问:

监听地址 能否被 localhost 访问 能否被 127.0.0.1 访问 能否被局域网其他设备访问
localhost ✅(IPv4 解析时)
127.0.0.1 ✅(解析为 127.0.0.1 时)
0.0.0.0 ✅(通过本机局域网 IP)
::1(IPv6) ✅(解析为 ::1 时)

4. 自定义映射的差异

  • localhost 可以被自定义映射

    • 你可以修改 hosts 文件,将 localhost 映射到任意 IP,例如:

      192.168.1.100   localhost
      
    • 此时访问 localhost 会指向局域网的 192.168.1.100,而不是本机。

  • 127.0.0.1 无法被自定义

    • 它是 IPv4 协议规定的回环地址,无论如何修改配置,访问 127.0.0.1 都只会指向本机。

5. 兼容性差异

  • 老旧系统 / 服务:某些非常古老的程序(如早期的 DOS 程序、嵌入式设备程序)可能不识别 localhost 主机名,但一定能识别 127.0.0.1
  • IPv6 专属服务:某些服务仅监听 IPv6 的 ::1,此时只能通过 localhost 访问(解析为 ::1),而 127.0.0.1 无法访问。

四、实际开发中的选择建议

  1. 优先使用 localhost

    • 理由:兼容性更好,支持双协议,符合开发习惯,且无需关心 IPv4/IPv6 配置。
    • 场景:本地开发、测试环境、前端代理配置(如 Vite、Webpack 的 devServer.host: 'localhost')。
  2. 使用 127.0.0.1 的场景

    • 强制使用 IPv4:当服务仅监听 IPv4 地址,或系统 IPv6 配置有问题时。
    • 避免自定义映射:当你怀疑 hosts 文件被修改,localhost 被映射到非本机地址时。
    • 某些工具的特殊要求:部分 CLI 工具或服务(如某些数据库客户端)默认只识别 127.0.0.1
  3. 特殊场景:0.0.0.0

    • 这不是回环地址,而是通配地址,表示监听本机所有网络接口(包括回环接口、局域网接口、公网接口)。
    • 场景:需要让局域网其他设备访问本机服务时(如手机测试前端页面)。

五、验证两者差异的小实验

实验 1:修改 hosts 文件,观察 localhost 映射

  1. 打开 /etc/hosts(Linux/macOS)或 C:\Windows\System32\drivers\etc\hosts(Windows)。
  2. 添加一行:192.168.1.1 localhost
  3. 执行 ping localhost,会发现 ping 的是 192.168.1.1,而非 127.0.0.1
  4. 执行 ping 127.0.0.1,仍然 ping 本机。
  5. 恢复 hosts 文件默认配置:127.0.0.1 localhost 和 ::1 localhost

实验 2:查看服务监听的地址

  1. 在 Node.js 中运行以下代码:

    const http = require('http');
    const server = http.createServer((req, res) => {
      res.end('Hello World!');
    });
    // 监听 localhost
    server.listen(3000, 'localhost', () => {
      console.log('Server running on localhost:3000');
    });
    
  2. 执行 netstat -tulpn | grep 3000(Linux/macOS)或 netstat -ano | findstr 3000(Windows)。

  3. 会发现服务同时监听 127.0.0.1:3000 和 ::1:3000(IPv4 + IPv6)。

  4. 若将监听地址改为 127.0.0.1,则仅监听 127.0.0.1:3000


六、总结:核心区别一览表

对比维度 localhost 127.0.0.1
本质 主机名(域名) IPv4 回环地址
协议层次 应用层(DNS) 网络层(IP)
解析需求 必须解析(hosts → DNS) 无需解析
协议支持 IPv4 + IPv6 仅 IPv4
自定义映射 可通过 hosts 文件修改 不可修改,固定指向本机
服务监听 可同时监听 IPv4/IPv6 仅监听 IPv4
兼容性 现代系统支持,老旧系统可能不支持 所有支持 IPv4 的系统都支持
性能 略慢(解析开销) 略快(无解析开销)

我是千寻, 这期内容到这里就结束了,我们有缘再会😂😂😂 !!!

详解把老旧的vue2+vue-cli+node-sass项目升级为vite

作者 mikan
2026年1月1日 16:21

🎯 实在是太慢了,影响开发效率,

📄 既然做了那就彻底点,原来的yarn 也换为了 pnpm , node-sass 换为了 dart-sass, vue-cli 换为 vite 7+

首先要做好思想准备,你的耐心是有限的,但是面对你的是无穷无尽的报错和各种奇怪的原因跑不起来的深渊,不过我觉得报错才是我们的朋友,因为白屏才是最可怕的,什么信息也没有,要自己去找

下面就开始了,我们想一想,如果升级打包工具的话,哪些写法要变呢,聪明的你应该已经想到了,原来依赖vue-cli(webpack)环境的部分肯定是用不了了的,所以我们要找到这些替代方案,比如垫片和适配器和改为新的api方法.还有就是原来解析.vue文件的loader没了,现在要换人了那就是 vite-plugin-vue2 ,还有其他的插件和loader,根据你的项目而定,都需要被替换.

大部分插件都可以在这里面找到 vite插件列表

1 更换环境

首先vite和 vite-plugin-vue2 装上,有两个版本,2.7以上和非2.7以上版本,我这边项目很老,而且还有些原来外包魔改后的库依赖2.5,我就不升级了,老实用vue2.5x的版本

Vue2 plugin for Vite

Vite plugin for Vue 2.7

之后就是需要告诉vite如何打包我们的项目,我们只需要写vite.config.js就好了

  • 其中 ClosePlugin 是解决因为打包的时候会挂在built in 那个地方不正确退出
  • vueDevTools 在vue2中用不了,看了下issue,只能用浏览器插件了,所以只能 codeInspectorPlugin 将就下了
  • css预处理器,一般情况下都不需要配置,只需要安装sass等依赖就好了,但是我这边不一样,原来外包的人用了很多的骚操作
  • 至于babel和core-js,我直接移除,都这么多年了你还用不支持这些语法的浏览器是不是该升级一下了😊😊😊
  • 其他的配置视情况而定,在插件列表中找到对应的插件就好

下面是我的配置,给大家用来参考

import { defineConfig, loadEnv } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'
import { codeInspectorPlugin } from 'code-inspector-plugin'
import { resolve } from 'path'
import ClosePlugin from './vite-plugin-close.js'

// 使用 defineConfig 定义 Vite 配置
export default defineConfig(({ mode, command }) => {
  const env = loadEnv(mode, process.cwd())
  console.log(env)

  return {
    base: './',
    server: {
      port: 8090,
      host: true,
      open: true,
      proxy: {
        '/api-test': {
          target: 'http://172.20.203.30:18000',
          changeOrigin: true,
          rewrite: (p) => {
            const s = p.replace('/api-test', '')
            // console.log(`proxy to test =>  ${p}`);
            return s
          }
        }
      }
    },

    // 配置插件
    plugins: [
      ClosePlugin(),
      codeInspectorPlugin({
        bundler: 'vite'
      }),

      //  oops vue2 不支持 😭😭😭
      // vueDevTools({
      //   launchEditor: env.VITE_LAUNCH_EDITOR ?? 'code'
      // }),
    ],
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: (content, filename) => {
            return getAdditionalData(filename) + content
          },
          // 或者指定禁用特定警告
          silenceDeprecations: [
            'legacy-js-api',      // 旧版 JS API
            'global',             // 全局变量
            'import',             // @import 语法
            'color',              // 旧的颜色函数
            'division',           // 除法运算
            'mixin',              // 混合器警告
            'selector'           // 选择器警告
          ]
        }
      }
    },

    // 配置模块解析规则
    resolve: {
      // 配置路径别名
      alias: {
        '@': resolve('src'),
        'element-ui-theme': resolve('node_modules/element-ui/packages/theme-chalk/src')
      },
      // https://cn.vitejs.dev/config/#resolve-extensions
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
    }
  }
})

function getAdditionalData(filePath) {
  // 根据文件路径决定是否注入
  if (filePath.includes('xr-theme.scss')) {
    return '' // 不向 xr-theme.scss 中的文件注入
  }

  return `
    @import "@/styles/xr-theme.scss";
    @import "element-ui/packages/theme-chalk/src/common/var.scss";
  `
}
css 相关
  • 首先如果你换了dart-sass 的话,会有很多的报错 比如 /deep/ 需要用 ::v-deep 来替换

  • 原来scss中还用了这种特殊的语法,在js中使用css变量

// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
  colorPrimary: $--color-primary; //0052CC
  colorBlack: $--color-black; //  172B4D
  colorSuccess: $--color-success; //  36B37E
  colorWarning: $--color-warning; //  FFAB00
  colorDanger: $--color-danger; //  FF5630
  colorN40: $--color-n40; //  DFE1E6
}

在vite中暂时没有找到很好的支持,所以建立了一个js文件用于存放这些变量

export const XRTheme = {
  colorPrimary: '#0052CC',
  colorBlack: '#172B4D',
  colorSuccess: '#36B37E',
  colorWarning: '#FFAB00',
  colorDanger: '#FF5630',
  colorN40: '#DFE1E6',

  //   ......
}

之后用regex全局替换,要把全部的import xrTheme from '@/styles/xr-theme.scss' 这种替换为 import { XRTheme as xrTheme } from '@/common/cssTheme.js'.

大概就是这个意思

"import xrTheme from '@/styles/xr-theme.scss'".replace(/import +(xrTheme) +from +'@\/styles\/xr-theme.scss'/,"import { XRTheme as $1} from '@/common/cssTheme.js'")

原来人还有一个骚操作,全局注入导入

在vue-cli中的配置如下

const oneOfsMap = config.module.rule('scss').oneOfs.store
oneOfsMap.forEach((item) => {
  item
    .use('sass-resources-loader')
    .loader('sass-resources-loader')
    .options({
      resources: [
        resolve('src/styles/xr-theme.scss'),
        resolve(
          'node_modules/element-ui/packages/theme-chalk/src/common/var.scss'
        )
      ]
    })
    .end()
})

vite中scss有支持,我写为函数形式是因为他们要注入 @import "@/styles/xr-theme.scss"; 但是向 xr-theme.scss 文件注入会无限递归,所以需要限制,但是不知道为什么vue-cli中能自动解决这个问题😒

css: {
  preprocessorOptions: {
    scss: {
      additionalData: (content, filename) => {
        return getAdditionalData(filename) + content
      },
    }
  }
}


function getAdditionalData(filePath) {
  // 根据文件路径决定是否注入
  if (filePath.includes('xr-theme.scss')) {
    return '' // 不向 xr-theme.scss 中的文件注入
  }

  return `
    @import "@/styles/xr-theme.scss";
    @import "element-ui/packages/theme-chalk/src/common/var.scss";
  `
}

当然还会有些警告,比如scss某些语法已经过时了,但是感觉暂时也没有很好的方法来换他们,因为感觉不是一行regex能搞定的,最好有语法分析,至于官方的迁移工具是不支持.vue文件的.所以暂时没时间搞了

这一通操作下来改了180+个文件

js和环境相关

原来导入了一些函数,但是不存在,cli中不会报错,但是vite中会找不到,需要对这些前人留下的坑全部给填掉

process.env.VUE_APP_CONTACT_URL 这种变量需要被替换为 import.meta.env.VITE_CONTACT_URL 记得VUE开头要换位VITE开头,不然找不到

process.env.VUE_APP_([^ ]+) 替换为 import.meta.env.VITE_($1)

require api替换 require('@/assets/callCenter/ring.png') 替换为 new URL('@/assets/img/head.png', import.meta.url).href

regex 如下 "require('@/assets/callCenter/ring.png')".replace(/require\('([^']+)'\)/,"new URL('$1', import.meta.url).href")

其中store 和 router 使用了动态导入,但是require.context是webpack的语法,看vite官方文档,有一个api比较像,注意eager要为true,不然返回的是一个Map<string,Promise<Module>>的类型

importAll(require.context('./modules', false, /.js$/)) 替换为 importAll(import.meta.glob('./modules/*.js', { eager: true }))

importAll中是modules.keys().forEach()写法,需要替换为Object.keys(modules),而且key可能和原来预想的不同,我就是因为这个原因直接白屏了,因为后面模块名不对导致加载逻辑全错,下面贴出代码对比

 const syncRouters = []
 const asyncRouterMap = []
 
-function importAll(r) {
+function importAll(modules) {
   let manageIndex = -1
-  r.keys().forEach(key => {
-    const fileName = key.slice(2, -3)
-    const itemRouter = r(key).default
+
+  Object.keys(modules).forEach(moduleKey => {
+    const fileName = moduleKey.slice(2, -3)
+    const itemRouter = modules[moduleKey].default
+    console.log(fileName,moduleKey,itemRouter)
+
     if (fileName.includes('sync')) {
       syncRouters.push(...itemRouter)
     } else {
       asyncRouterMap.unshift(asyncRouterMap.splice(oaIndex, 1)[0])
     }
   }
 }
 
-importAll(require.context('./modules', false, /\.js$/))
+importAll(import.meta.glob('./modules/*.js', { eager: true }))
 
 export {
   syncRouters,

原来还在vue代码中用过path.resolve 这种nodejs中的api,直接写一个垫片resolve之后导入,全局替换导入模块,当然你也可以写vite插件替换或者模拟虚拟模块解析之后映射,至于实现我是找ai写的就不贴了

 import { mapGetters } from 'vuex'
 import { getNavMenus } from './components/utils'
-import path from 'path'
+import { path } from '@/common/path'
 
 export default {
   name: 'CrmLayout',
           auth = this.$auth(item.permissions.join('.'))
         }
         if (!item.hidden && auth) {
           sideMenus.push({
             ...item,
             path: path.resolve(mainPath, item.path)

这一波又是60+个文件的修改

第三方依赖兼容

外包使用的vue2-org-tree 的库中找不到某个文件,其实是路径解析的问题,我们需要明确后缀,注意:vite会查看pkg.json,优先使用module

这个操作需要改源码,直接用pnpm patch功能就好,非常方便

//   pkg.json
{
    "main": "dist/index.js",
    "module": "src/main.js",
}

//   index.js
- import Vue2OrgTree from './components/org-tree'
+ import Vue2OrgTree from './components/org-tree.vue'

项目中外包使用了自己魔改的el-ui库,并且基于魔改库重写了插件,比如 el-bigdata-table 中的 render.js 用到了jsx语法,感觉没必要为了一个依赖引入jsx,所以建了一个子工程,写一套打包配置来打包为h函数版本,最后拷贝到项目,聪明的你可能要问为什么要自己写打包配置,因为他的包中只有源码,没有上传打包配置😂,下面是jsx版本

export default function render(h, oldRender) {
  return (
    <div
      style={[{height: `${this.table.virtualBodyHeight}px`}]}
      class={['el-table__virtual-wrapper', {'el-table--fixed__virtual-wrapper': this.fixed}]}
      v-mousewheel={this.table.handleFixedMousewheel}
    >
      <div style={[{transform: `translateY(${this.table.innerTop}px)`}]}>
      {
        oldRender.call(this, h)
      }
      </div>
    </div>
  );
}
打包部分

打包还有点小插曲,就是卡built in xxx.xxxs这里,看国外stack overflow中和一些文章中说要写个插件,其实就是在结束的钩子中调用 process.exit(0) 系统调用

export default function ClosePlugin() {
  return {
    name: 'ClosePlugin', // required, will show up in warnings and errors

    // use this to catch errors when building
    buildEnd(error) {
      if (error) {
        console.error('Error bundling')
        console.error(error)
        process.exit(1)
      } else {
        console.log('Build ended')
      }
    },

    // use this to catch the end of a build without errors
    closeBundle(id) {
      console.log('Bundle closed')
      process.exit(0)
    }
  }
}

模板文件修改,主要是加入 <script type="module" src="/src/main.js"></script>,这样vite才知道入口

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="icon" href="favicon.ico">
    <title>CRM</title>
  </head>
  <body>
    <noscript>
    </noscript>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

记得发布的vite的base参数也要调整,nginx也是

可能还有遗漏,但是大概就是这些了,感谢观看😊😊😊!

C# 上位机开发中的动态绑定与虚拟化

作者 七宝三叔
2026年1月1日 16:16

一、动态绑定 (Dynamic Binding)

1. 数据绑定基础

csharp

// Model类
public class DeviceData : INotifyPropertyChanged
{
    private string _deviceName;
    private double _temperature;
    private DateTime _timestamp;
    
    public string DeviceName
    {
        get => _deviceName;
        set
        {
            _deviceName = value;
            OnPropertyChanged();
        }
    }
    
    public double Temperature
    {
        get => _temperature;
        set
        {
            _temperature = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(TemperatureFormatted));
        }
    }
    
    public DateTime Timestamp
    {
        get => _timestamp;
        set
        {
            _timestamp = value;
            OnPropertyChanged();
        }
    }
    
    // 计算属性
    public string TemperatureFormatted => $"{_temperature:F1}°C";
    
    // INotifyPropertyChanged 实现
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. 动态绑定集合

csharp

// ViewModel
public class DeviceViewModel : INotifyPropertyChanged
{
    private ObservableCollection<DeviceData> _devices;
    private DeviceData _selectedDevice;
    
    public ObservableCollection<DeviceData> Devices
    {
        get => _devices;
        set
        {
            _devices = value;
            OnPropertyChanged();
        }
    }
    
    public DeviceData SelectedDevice
    {
        get => _selectedDevice;
        set
        {
            _selectedDevice = value;
            OnPropertyChanged();
        }
    }
    
    // 动态添加设备
    public void AddDevice(string name)
    {
        var device = new DeviceData
        {
            DeviceName = name,
            Temperature = 25.0,
            Timestamp = DateTime.Now
        };
        
        Devices.Add(device);
    }
    
    // 动态更新数据
    public void UpdateDeviceTemperature(string deviceName, double temperature)
    {
        var device = Devices.FirstOrDefault(d => d.DeviceName == deviceName);
        if (device != null)
        {
            device.Temperature = temperature;
            device.Timestamp = DateTime.Now;
        }
    }
}

3. XAML绑定示例

xml

<!-- MainWindow.xaml -->
<Window x:Class="MonitorApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <Grid>
        <!-- DataGrid动态绑定 -->
        <DataGrid ItemsSource="{Binding Devices}"
                  SelectedItem="{Binding SelectedDevice}"
                  AutoGenerateColumns="False"
                  VirtualizingPanel.IsVirtualizing="True">
            <DataGrid.Columns>
                <DataGridTextColumn Header="设备名称" 
                                    Binding="{Binding DeviceName}"/>
                <DataGridTextColumn Header="温度" 
                                    Binding="{Binding TemperatureFormatted}"/>
                <DataGridTextColumn Header="更新时间" 
                                    Binding="{Binding Timestamp, StringFormat=HH:mm:ss}"/>
            </DataGrid.Columns>
        </DataGrid>
        
        <!-- 实时数据显示 -->
        <ItemsControl ItemsSource="{Binding Devices}"
                      VirtualizingPanel.IsVirtualizing="True">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Margin="5" Padding="10" Background="LightGray">
                        <StackPanel>
                            <TextBlock Text="{Binding DeviceName}" 
                                       FontWeight="Bold"/>
                            <TextBlock Text="{Binding TemperatureFormatted}" 
                                       Foreground="Red"/>
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</Window>

二、虚拟化 (Virtualization)

1. UI虚拟化实现

csharp

// 自定义虚拟化数据源
public class VirtualizedDataSource<T> : IList, INotifyCollectionChanged
{
    private readonly List<T> _data;
    private readonly int _pageSize;
    private Dictionary<int, T> _cache;
    
    public VirtualizedDataSource(IEnumerable<T> data, int pageSize = 100)
    {
        _data = data.ToList();
        _pageSize = pageSize;
        _cache = new Dictionary<int, T>();
    }
    
    // 虚拟化访问
    public object this[int index]
    {
        get
        {
            if (!_cache.ContainsKey(index))
            {
                // 加载数据页
                LoadPage(index / _pageSize);
            }
            return _cache[index];
        }
        set => throw new NotImplementedException();
    }
    
    private void LoadPage(int pageNumber)
    {
        int start = pageNumber * _pageSize;
        int end = Math.Min(start + _pageSize, _data.Count);
        
        for (int i = start; i < end; i++)
        {
            _cache[i] = _data[i];
        }
    }
    
    public int Count => _data.Count;
    
    // 实现其他必要接口...
}

// 使用示例
public class LargeDataViewModel
{
    public VirtualizedDataSource<DeviceData> VirtualizedDevices { get; }
    
    public LargeDataViewModel()
    {
        // 生成大量数据
        var data = Enumerable.Range(1, 100000)
            .Select(i => new DeviceData
            {
                DeviceName = $"设备_{i}",
                Temperature = 20 + i % 30,
                Timestamp = DateTime.Now.AddSeconds(-i)
            });
        
        VirtualizedDevices = new VirtualizedDataSource<DeviceData>(data);
    }
}

2. 高级虚拟化配置

xml

<!-- 虚拟化ListView -->
<ListView ItemsSource="{Binding VirtualizedDevices}"
          VirtualizingPanel.IsVirtualizing="True"
          VirtualizingPanel.VirtualizationMode="Recycling"
          VirtualizingPanel.ScrollUnit="Pixel"
          VirtualizingPanel.CacheLength="100,100"
          ScrollViewer.CanContentScroll="True"
          ScrollViewer.IsDeferredScrollingEnabled="True">
    
    <ListView.View>
        <GridView>
            <GridViewColumn Header="ID" 
                            DisplayMemberBinding="{Binding DeviceName}"/>
            <GridViewColumn Header="温度"
                            DisplayMemberBinding="{Binding TemperatureFormatted}"/>
        </GridView>
    </ListView.View>
    
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel VirtualizingPanel.ScrollUnit="Pixel"/>
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
</ListView>

<!-- 虚拟化TreeView -->
<TreeView VirtualizingStackPanel.IsVirtualizing="True"
          VirtualizingStackPanel.VirtualizationMode="Recycling">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

3. 异步虚拟化加载

csharp

public class AsyncVirtualizedCollection<T> : ObservableCollection<T>, IList, INotifyCollectionChanged
{
    private readonly Func<int, Task<IEnumerable<T>>> _dataLoader;
    private readonly int _pageSize;
    private readonly Dictionary<int, List<T>> _loadedPages;
    private int _totalCount;
    
    public AsyncVirtualizedCollection(
        Func<int, int, Task<IEnumerable<T>>> dataLoader, 
        int totalCount, 
        int pageSize = 50)
    {
        _dataLoader = (page) => dataLoader(page * pageSize, pageSize);
        _totalCount = totalCount;
        _pageSize = pageSize;
        _loadedPages = new Dictionary<int, List<T>>();
    }
    
    // 重写索引器实现虚拟化
    public new T this[int index]
    {
        get
        {
            int page = index / _pageSize;
            int pageIndex = index % _pageSize;
            
            if (!_loadedPages.ContainsKey(page))
            {
                // 异步加载页面
                LoadPageAsync(page).Wait();
            }
            
            return _loadedPages[page][pageIndex];
        }
        set => base[index] = value;
    }
    
    private async Task LoadPageAsync(int page)
    {
        var data = await _dataLoader(page);
        _loadedPages[page] = data.ToList();
        
        // 触发UI更新
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Reset));
    }
    
    // 实现其他必要方法...
}

// 使用示例
public class DataService
{
    public async Task<IEnumerable<DeviceData>> GetDevicesAsync(int skip, int take)
    {
        // 模拟数据库查询
        await Task.Delay(100);
        
        return Enumerable.Range(skip, take)
            .Select(i => new DeviceData
            {
                DeviceName = $"Device_{i}",
                Temperature = 20 + i % 30
            });
    }
}

三、性能优化技巧

1. 绑定优化

csharp

// 1. 使用延迟绑定
public class DelayedBindingHelper
{
    public static readonly DependencyProperty DelayedTextProperty =
        DependencyProperty.RegisterAttached(
            "DelayedText",
            typeof(string),
            typeof(DelayedBindingHelper),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnDelayedTextChanged,
                null,
                false,
                UpdateSourceTrigger.LostFocus));
    
    // 2. 绑定验证和转换优化
    public class TemperatureConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is double temp)
            {
                return $"{temp:F2} °C";
            }
            return string.Empty;
        }
        
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is string str && double.TryParse(str, out double temp))
            {
                return temp;
            }
            return 0.0;
        }
    }
}

2. 内存管理

csharp

public class MemoryOptimizedListBox : ListBox
{
    // 实现自定义虚拟化面板
    protected override void PrepareContainerForItemOverride(
        DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
        
        // 为可见项设置数据上下文
        if (element is FrameworkElement frameworkElement)
        {
            frameworkElement.DataContext = item;
        }
    }
    
    protected override void ClearContainerForItemOverride(
        DependencyObject element, object item)
    {
        // 清理资源
        if (element is FrameworkElement frameworkElement)
        {
            frameworkElement.DataContext = null;
        }
        
        base.ClearContainerForItemOverride(element, item);
    }
}

四、实际应用示例

监控系统示例

csharp

public class MonitoringSystem
{
    private readonly Timer _updateTimer;
    private readonly Random _random;
    private readonly DeviceViewModel _viewModel;
    
    public MonitoringSystem(DeviceViewModel viewModel)
    {
        _viewModel = viewModel;
        _random = new Random();
        _updateTimer = new Timer(UpdateData, null, 0, 1000);
    }
    
    private void UpdateData(object state)
    {
        // 模拟实时数据更新
        foreach (var device in _viewModel.Devices)
        {
            device.Temperature = 20 + _random.NextDouble() * 15;
            device.Timestamp = DateTime.Now;
        }
    }
    
    // 动态添加/移除设备
    public void AddNewDevice()
    {
        Application.Current.Dispatcher.Invoke(() =>
        {
            _viewModel.AddDevice($"设备_{Guid.NewGuid().ToString().Substring(0, 8)}");
        });
    }
}

配置虚拟化参数

xml

<!-- App.xaml 或 资源字典 -->
<Application.Resources>
    <Style TargetType="ListBox">
        <Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/>
        <Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling"/>
        <Setter Property="VirtualizingPanel.CacheLength" Value="50,50"/>
        <Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
    </Style>
    
    <Style TargetType="DataGrid">
        <Setter Property="EnableRowVirtualization" Value="True"/>
        <Setter Property="EnableColumnVirtualization" Value="True"/>
        <Setter Property="RowHeight" Value="25"/>
        <Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel"/>
    </Style>
</Application.Resources>

五、最佳实践总结

  1. 动态绑定

    • 使用 ObservableCollection<T> 实现动态集合
    • 实现 INotifyPropertyChanged 实现属性变更通知
    • 使用 Dispatcher 在UI线程更新
  2. 虚拟化优化

    • 大量数据时启用虚拟化
    • 使用回收模式 (VirtualizationMode="Recycling")
    • 合理设置缓存长度
    • 避免嵌套虚拟化容器
  3. 性能监控

    csharp

    // 监控虚拟化性能
    PresentationTraceSources.DataBindingSource.Switch.Level = 
        SourceLevels.Warning | SourceLevels.Error;
    
  4. 内存优化

    • 及时释放不需要的绑定
    • 使用弱引用处理大型对象
    • 分页加载大数据集

这些技术结合使用,可以显著提升C#上位机应用程序的性能和响应速度,特别是在处理大量实时数据时。

救命!Python 这些基础操作居然能省一半工作量

2026年1月1日 14:48

作为一个常年和 Python 打交道的 “搬砖人”,我发现很多新手甚至老手,都会忽略一些基础但超实用的小技巧。明明一行代码能搞定的事,非要写个循环绕半天,属实是给自己加工作量了。

今天就来分享几个我日常高频使用的 Python 基础操作,简单好记,用起来是真的香!

1. 交换变量?不用临时变量也能行

新手交换两个变量,第一反应可能是定义一个临时变量中转:

a = 10
b = 20
# 新手写法
temp = a
a = b
b = temp

但 Python 里有更简洁的写法,直接一行搞定,逻辑还清晰:

a = 10
b = 20
# 简洁写法
a, b = b, a

不仅能交换两个变量,多个变量交换也同样适用,比如a, b, c = c, a, b,直接打乱顺序重新赋值,超方便。

2. 列表去重?别再写循环遍历了

面对一个有重复元素的列表,想快速去重,很多人会写个 for 循环,逐个判断是否在新列表里。

lst = [1, 2, 2, 3, 4, 4, 5]
new_lst = []
for i in lst:
    if i not in new_lst:
        new_lst.append(i)

但其实用 Python 的集合特性,一行就能去重,效率还更高:

lst = [1, 2, 2, 3, 4, 4, 5]
new_lst = list(set(lst))

不过要注意,集合是无序的,如果需要保持原列表的顺序,可以用dict.fromkeys(lst),再转成列表就行。

3. 快速拼接字符串?加号不如 join

拼接多个字符串时,用+号虽然直观,但效率很低,尤其是字符串数量多的时候。

str_list = ["我", "爱", "Python", "编程"]
result = ""
for s in str_list:
    result += s

推荐用join方法,不仅代码简洁,执行效率也提升不少:

str_list = ["我", "爱", "Python", "编程"]
result = "".join(str_list)

如果想给字符串之间加个分隔符,比如逗号,直接把引号里的内容改成,就行,","join(str_list)就能得到"我,爱,Python,编程"

4. 字典合并?三种方法任你选

日常开发中经常需要合并两个字典,新手可能会用循环逐个添加,其实 Python 有好几种简洁的写法。

dict1 = {"name": "张三", "age": 20}
dict2 = {"gender": "男", "city": "北京"}

# 方法1:用update方法
dict1.update(dict2)

# 方法2:用**解包
result = {**dict1, **dict2}

# 方法3:Python3.9+ 可用|运算符
result = dict1 | dict2

三种方法都能实现字典合并,需要注意的是,update会修改原字典,而另外两种方法会生成一个新字典,根据需求选择就行。

5. 快速生成列表?列表推导式 yyds

想根据一个列表生成新的列表,比如把所有元素乘以 2,新手可能会写循环:

lst = [1, 2, 3, 4, 5]
new_lst = []
for i in lst:
    new_lst.append(i * 2)

用列表推导式的话,一行就能搞定,代码更紧凑:

lst = [1, 2, 3, 4, 5]
new_lst = [i * 2 for i in lst]

还能加上条件判断,比如只生成偶数的倍数:[i * 2 for i in lst if i % 2 == 0],实用性拉满。

每日一题-加一🟢

2026年1月1日 00:00

给定一个表示 大整数 的整数数组 digits,其中 digits[i] 是整数的第 i 位数字。这些数字按从左到右,从最高位到最低位排列。这个大整数不包含任何前导 0

将大整数加 1,并返回结果的数字数组。

 

示例 1:

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
加 1 后得到 123 + 1 = 124。
因此,结果应该是 [1,2,4]。

示例 2:

输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
加 1 后得到 4321 + 1 = 4322。
因此,结果应该是 [4,3,2,2]。

示例 3:

输入:digits = [9]
输出:[1,0]
解释:输入数组表示数字 9。
加 1 得到了 9 + 1 = 10。
因此,结果应该是 [1,0]。

 

提示:

  • 1 <= digits.length <= 100
  • 0 <= digits[i] <= 9
  • digits 不包含任何前导 0

2025年度稀土掘金影响力榜单如约而至!

作者 掘金酱
2025年12月31日 15:57

稀土掘金社区2025年度影响力榜单正式公布

1303-734主视觉 (1) (3).jpg

时光向前,2025年即将落下句点。回首这一程,幸而有伴 —— 每一次深夜的打磨,每一段真诚的分享,每一回评论区里的倾力相助,都为技术探索的漫漫长路,点亮了一盏盏暖灯。

感谢这一年里,将实操经验凝注于代码、把深度思考落笔成文字的创作者。是每一个具体的 “你”,让稀土掘金始终活力满满。

今天,我们以这份榜单,记录那些值得被看见的光芒。它们来自团队扎实的沉淀,来自创作者持久的热情,也来自那些真正帮助过许多人的走心好文。

榜单地址aicoding.juejin.cn/pens/758993…

2025年度优秀创作者 | The Best 10 Creative Writers of 2025

他们是社区里的“解惑人”,把复杂讲得简单,把枯燥变得生动。他们的文字,曾陪伴无数掘友走出技术探索的迷茫时刻。

站内昵称 个人主页
Moment juejin.cn/user/378276…
ConardLi juejin.cn/user/394910…
何贤 juejin.cn/user/277499…
洛卡卡了 juejin.cn/user/888839…
why技术 juejin.cn/user/370281…
恋猫de小郭 juejin.cn/user/817692…
苏三说技术 juejin.cn/user/465848…
coder_pig juejin.cn/user/414261…
张风捷特烈 juejin.cn/user/149189…
德莱厄斯 juejin.cn/user/391911…

2025年度影响力团队 -The Most Influential Teams of 2025

他们以团队的力量,把一线实践沉淀成可复用的经验,如一张张清晰的技术地图,帮助不少同行找到了方向。

团队名称 团队主页
DevUI团队 juejin.cn/user/712139…
vivo互联网技术 juejin.cn/user/993614…
得物技术 juejin.cn/user/239295…
古茗前端团队 juejin.cn/user/323304…
货拉拉技术 juejin.cn/user/176848…
京东零售技术 juejin.cn/user/423357…
奇舞精选 juejin.cn/user/438890…
37手游后端团队 juejin.cn/user/154852…
哔哩哔哩技术 juejin.cn/user/303070…
转转技术团队 juejin.cn/user/606586…

2025年度爆款好文 | High hits articles in 2025

这20篇文章,是从海量分享中脱颖而出的“年度之选”。它们或视角新颖、或剖析深入、或实战性强,在各自领域内获得了认可,成了许多掘友收藏或推荐的那一篇。

文章标题 文章链接 所属作者
前端
《40岁老前端2025年上半年都学了什么?》 juejin.cn/post/752454… 张鑫旭
《前端仔如何在公司搭建 AI Review 系统》 juejin.cn/post/753259… 唐某人丶
《因为写了一个前端脚手架,这个月的KPI 打满了!!!》 juejin.cn/post/745793… 赵小川
《因网速太慢我把20M+的字体压缩到了几KB》 juejin.cn/post/749033… 古茗前端团队
《历经4个月,基于 Tiptap 和 NestJs 打造一款 AI 驱动的智能文档协作平台 🚀🚀🚀》 juejin.cn/post/755316… Moment
后端
《MCP 很火,来看看我们直接给后台管理系统上一个 MCP?》 juejin.cn/post/748149… Hamm
《还在用WebSocket实现即时通讯?试试MQTT吧,真香!》 juejin.cn/post/753935… MacroZheng
《我做了套小红书一键发布系统,运营小姐姐说她不想离开我了》 juejin.cn/post/755248… 洛卡卡了
《Java 实现责任链模式 + 策略模式:优雅处理多级请求的方式》 juejin.cn/post/745736… 后端出路在何方
《CompletableFuture还能这么玩》 juejin.cn/post/745556… 一只叫煤球的猫
移动端
《2025 跨平台框架更新和发布对比,这是你没看过的全新版本》 juejin.cn/post/750557… 恋猫de小郭
《月下载 40 万次的框架是怎么练成的?》 juejin.cn/post/754740… Android轮子哥
《[targetSDK升级为35] 恶心的EdgeToEdge适配 (v7)》 juejin.cn/post/749717… snwrking
《gson很好,但我劝你在Kotlin上使用kotlinx.serialization》 juejin.cn/post/745929… 沈剑心
《开箱即食Flutter通用脚手架》 juejin.cn/post/748278… SunshineBrother
人工智能
《一天 AI 搓出痛风伴侣 H5 程序,前后端+部署通吃,还接入了大模型接口(万字总结)》 juejin.cn/post/751749… 志辉AI编程
《AI 应用开发入门:前端也可以学习 AI》 juejin.cn/post/751719… 唐某人丶
《如何把你的 DeePseek-R1 微调为某个领域的专家?》 juejin.cn/post/747330… ConardLi
《3天,1人,从0到付费产品:AI时代个人开发者的生存指南》 juejin.cn/post/757765… HiStewie
《全网最细,一文带你弄懂 MCP 的核心原理!》 juejin.cn/post/749345… ConardLi

好的社区,是人与人相互照亮

这份榜单,与其说是评选,不如说是一次郑重的致谢。谢谢所有分享者,也谢谢每一位静静学习、默默点赞、热心评论的掘友。

技术之路,日常而长远。2026年,愿你继续在这里写下自己的章节,发出自己的光。我们相信,每一个人的微小光芒,终将汇聚成行业的星辰。

*注:以上排名不分先后,随机排序。本榜单依据2025年1月1日至12月21日期间的数据综合评定,涵盖文章质量、互动热度、创作者影响力、分类分布及评审团意见等多维度,最终解释权归稀土掘金所有。

简单题,简单做(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2025年9月17日 14:30

题目让我们模拟加一运算。

比如 $23999+1 = 24000$,$999+1=1000$。

算法:

  1. 从右往左找第一个不等于 $9$ 的数,记作 $\textit{digits}[i]$。
  2. 进位,把 $\textit{digits}[i]$ 加一,把下标在 $[i+1,n-1]$ 中的数全变成 $0$。
  3. 特别地,如果所有数都等于 $9$,那么答案为 $[1,0,0,\dots,0]$,其中有 $n$ 个 $0$。
class Solution:
    def plusOne(self, digits: List[int]) -> List[int]:
        for i in range(len(digits) - 1, -1, -1):
            if digits[i] < 9:
                digits[i] += 1  # 进位
                return digits
            digits[i] = 0  # 进位数字的右边数字都变成 0

        # digits 全是 9,加一后变成 100...00
        return [1] + digits
class Solution {
    public int[] plusOne(int[] digits) {
        int n = digits.length;
        for (int i = n - 1; i >= 0; i--) {
            if (digits[i] < 9) {
                digits[i]++; // 进位
                return digits;
            }
            digits[i] = 0; // 进位数字的右边数字都变成 0
        }

        // digits 全是 9,加一后变成 100...00
        int[] ans = new int[n + 1];
        ans[0] = 1;
        return ans;
    }
}
class Solution {
public:
    vector<int> plusOne(vector<int>& digits) {
        for (int i = digits.size() - 1; i >= 0; i--) {
            if (digits[i] < 9) {
                digits[i]++; // 进位
                return digits;
            }
            digits[i] = 0; // 进位数字的右边数字都变成 0
        }

        // digits 全是 9,加一后变成 100...00
        digits.push_back(0);
        digits[0] = 1;
        return digits;
    }
};
int* plusOne(int* digits, int digitsSize, int* returnSize) {
    for (int i = digitsSize - 1; i >= 0; i--) {
        if (digits[i] < 9) {
            digits[i]++; // 进位
            *returnSize = digitsSize;
            return digits;
        }
        digits[i] = 0; // 进位数字的右边数字都变成 0
    }

    // digits 全是 9,加一后变成 100...00
    *returnSize = digitsSize + 1;
    int* ans = calloc(digitsSize + 1, sizeof(int));
    ans[0] = 1;
    return ans;
}
func plusOne(digits []int) []int {
    for i, d := range slices.Backward(digits) {
        if d < 9 {
            digits[i]++ // 进位
            return digits
        }
        digits[i] = 0 // 进位数字的右边数字都变成 0
    }

    // digits 全是 9,加一后变成 100...00
    digits = append(digits, 0)
    digits[0] = 1
    return digits
}
var plusOne = function(digits) {
    for (let i = digits.length - 1; i >= 0; i--) {
        if (digits[i] < 9) {
            digits[i]++; // 进位
            return digits;
        }
        digits[i] = 0; // 进位数字的右边数字都变成 0
    }

    // digits 全是 9,加一后变成 100...00
    digits.push(0);
    digits[0] = 1;
    return digits;
};
impl Solution {
    pub fn plus_one(mut digits: Vec<i32>) -> Vec<i32> {
        for d in digits.iter_mut().rev() {
            if *d < 9 {
                *d += 1; // 进位
                return digits;
            }
            *d = 0; // 进位数字的右边数字都变成 0
        }

        // digits 全是 9,加一后变成 100...00
        digits.push(0);
        digits[0] = 1;
        digits
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(m)$,其中 $m$ 是 $\textit{digits}$ 末尾 $9$ 的个数。
  • 空间复杂度:$\mathcal{O}(1)$。返回值不计入。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

画解算法:66. 加一

作者 guanpengchn
2019年6月17日 08:13

解题思路

  • 标签:数组遍历
  • 这道题需要整理出来有哪几种情况,在进行处理会更舒服
  1. 末位无进位,则末位加一即可,因为末位无进位,前面也不可能产生进位,比如 45 => 46
  2. 末位有进位,在中间位置进位停止,则需要找到进位的典型标志,即为当前位 $%10$ 后为 0,则前一位加 1,直到不为 0 为止,比如 499 => 500
  3. 末位有进位,并且一直进位到最前方导致结果多出一位,对于这种情况,需要在第 2 种情况遍历结束的基础上,进行单独处理,比如 999 => 1000
  • 在下方的 Java 和 JavaScript 代码中,对于第三种情况,对其他位进行了赋值 0 处理,Java 比较 tricky 直接 new 数组即可,JavaScript 则使用了 ES6 语法进行赋值
  • 时间复杂度:$O(n)$

代码

###Java

class Solution {
    public int[] plusOne(int[] digits) {
        int len = digits.length;
        for(int i = len - 1; i >= 0; i--) {
            digits[i]++;
            digits[i] %= 10;
            if(digits[i]!=0)
                return digits;
        }
        digits = new int[len + 1];
        digits[0] = 1;
        return digits;
    }
}

###JavaScript

/**
 * @param {number[]} digits
 * @return {number[]}
 */
var plusOne = function(digits) {
    const len = digits.length;
    for(let i = len - 1; i >= 0; i--) {
        digits[i]++;
        digits[i] %= 10;
        if(digits[i]!=0)
            return digits;
    }
    digits = [...Array(len + 1)].map(_=>0);;
    digits[0] = 1;
    return digits;
};

画解

<frame_00001.png,frame_00002.png,frame_00003.png,frame_00004.png>

想看大鹏画解更多高频面试题,欢迎阅读大鹏的 LeetBook:《画解剑指 Offer 》,O(∩_∩)O

Java 数学解题

作者 yhhzw
2019年5月10日 17:55

解题思路:

根据题意加一,没错就是加一这很重要,因为它是只加一的所以有可能的情况就只有两种:

  1. 除 $9$ 之外的数字加一;
  2. 数字 $9$。

加一得十进一位个位数为 $0$ 加法运算如不出现进位就运算结束了且进位只会是一。

所以只需要判断有没有进位并模拟出它的进位方式,如十位数加 $1$ 个位数置为 $0$,如此循环直到判断没有再进位就退出循环返回结果。

然后还有一些特殊情况就是当出现 $99$、$999$ 之类的数字时,循环到最后也需要进位,出现这种情况时需要手动将它进一位。

###Java

class Solution {
    public int[] plusOne(int[] digits) {
        for (int i = digits.length - 1; i >= 0; i--) {
            digits[i]++;
            digits[i] = digits[i] % 10;
            if (digits[i] != 0) return digits;
        }
        digits = new int[digits.length + 1];
        digits[0] = 1;
        return digits;
    }
}

PS:本人并非大佬,这是第一次写思路解释,如有写的不好的地方请多多包涵,哈哈哈

泰国2025年游客人次10年来首次下降

2026年1月1日 15:30
泰国旅游与体育部星期二(12月30日)说,截至12月28日,泰国共接待3260万名外国游客,比前年同期下降超过7%。这是泰国10年来除却冠病疫情期间,旅客人数首次下滑。另外,外国游客带来的旅游收入总计达1.5万亿泰铢(约600亿新元),低于前年1.67万亿泰铢。其中,马来西亚游客以450万人次居首,其次是440万人次的中国游客,排名第三的是250万人次来自印度的游客。

加拿大股市全年累涨28% 创2009年以来最大涨幅

2026年1月1日 15:15
在矿业及金融企业的带动下,标普/多伦多证交所综合指数全年累计上涨28%,创下2009年以来的最大年度涨幅。 矿业与银行股是此次上涨的核心推手,原材料分类指数涨幅近乎翻倍,金融板块涨幅也超30%。 尽管市场对银行估值及石油前景心存担忧,但部分策略师认为,在降息及贵金属行情持续走强等因素的推动下,加拿大股市指数在2026年有望进一步走高。
❌
❌