阅读视图

发现新文章,点击刷新页面。

谷歌被指在安卓应用分发领域实施垄断控制

Alphabet Inc.正面临一起新的反垄断诉讼,竞争对手应用商店Aptoide S.A.指控该公司在安卓应用分发和计费系统领域实行垄断。Aptoide表示,谷歌利用其权力,通过使开发者更难进入其自家的Google Play生态系统来阻碍竞争。该公司认为,谷歌的“限制性做法”使其无法提供更优的价格和服务。该诉讼已提交至美国联邦法院,要求赔偿损失并请求法院责令谷歌停止相关行为。(新浪财经)

互联互通工具持续丰富,公募大力布局香港市场ETF

港交所日前宣布,其指数组合新增两只科技类指数——香港交易所韩国交易所半导体指数、香港交易所科技及美国科技100指数。五家内地公募境外子公司获得指数授权,将在港股市场推出相关ETF产品。根据这次新增科技指数的编制原则,相关ETF产品在港交所上市后,有望纳入互联互通ETF范畴。值得注意的是,近年来,内地公募机构发力香港市场ETF的迹象明显,2025年港交所上市ETF数量创历史新高,超过半数为内地公募境外子公司旗下产品。今年以来新上市的ETF中,这一占比更是超过七成。 (中证网)

绩优基金一季报密集披露:多只基金规模大增,看多AI相关产业链

基金2026年一季报进入密集披露期。季报显示,今年一季度,多只绩优基金规模大幅增长。综合绩优基金经理观点看,基于行业景气度,后市看多AI相关产业链,包括AI电力、AI应用等方向。截至一季度末,任飞管理的中欧周期优选混合基金规模从15.75亿元增至44.09亿元。类似的还有叶培培管理的中欧资源精选混合基金,规模从26.49亿元增至38.76亿元。截至4月14日,这两只基金近一年收益率均超过110%。(上证报)

EIA称美国石油出口总量跃升至纪录高位

美国石油出口上周飙升至创纪录水平。美国能源信息署(EIA)公布上周的数据显示,上周原油出口量突破每日500万桶大关,升至2025年9月以来最高水平。总体来看,加上成品油,上周美国石油出口规模接近每日1300万桶。(财联社)

美国启动非法关税退款流程,已有上亿美元申请待处理

当地时间4月15日,美国财政部长贝森特表示,进口商已可开始申请此前被裁定为非法关税的退款,但相关流程由美国海关与边境保护局(CBP)负责执行。贝森特指出,财政部主要承担支付角色,具体申请需由企业向海关部门提交。目前约有1.27亿美元的退款申请已进入处理流程,相关资金“正在推进中”。他表示,退款处理时间表尚不明确,需视具体审核进展而定。(央视新闻)

联合利华以448亿美元出售食品业务,并以12亿美元收购Grüns

向健康领域的转变正开始重塑消费品公司的资本配置方式,联合利华正更加果断地转向高增长品类。联合利华近期的战略举措表明,该公司有意从增长较慢的食品领域转向。公司同意以448亿美元的价格将其食品业务出售给味好美公司,同时通过收购不断扩大其在健康领域的影响力。其最新交易——以12亿美元收购复合维生素软糖品牌Grüns——使其进一步涉足一个势头更为强劲的细分领域。(新浪财经)

欧盟委员会初步认定元宇宙平台公司调整政策后仍违规

欧盟委员会4月15日发布公报称,已向美国元宇宙平台公司发出补充异议声明,初步认定该公司在调整了限制第三方人工智能(AI)助手接入其旗下即时通信应用程序WhatsApp的相关措施后,仍违反欧盟反垄断规则,拟要求该公司恢复第三方AI助手对WhatsApp的访问权限。(新华社)

特斯拉完成AI5自动驾驶芯片流片

特斯拉公司在其制造自动驾驶汽车的道路上达成了又一个目标。首席执行官埃隆·马斯克声称,该公司已完成了AI5处理器的流片。这意味着设计阶段已经结束,该芯片现已可以进入生产环节。(新浪财经)

小鹏GX上市,预售价39.98万元

36氪获悉,4月15日,小鹏大六座旗舰SUV小鹏GX上市,共推出增程和纯电两个版本,预售价39.98万元起。何小鹏在发布会上提到,小鹏GX是中国首款前装量产的Robotaxi原型车,完全按照L4自动驾驶标准打造,搭载四颗图灵芯片,有效算力达3000TOPS,搭载小鹏第二代VLA。

OpenAI据悉计划推出ChatGPT广告新定价方案

4月15日消息,据报道,OpenAI计划开始根据用户是否点击广告而非仅仅根据广告的展示次数来定价部分ChatGPT广告。OpenAI同时也计划推出旨在引导用户采取特定行动(例如购买商品或下载应用)的广告,但尚未给出具体的上线时间表。(界面)

美股三大指数收盘涨跌不一,纳指11连涨创收盘新高

36氪获悉,4月15日收盘,美股三大指数涨跌不一,纳指涨1.59%,为连续11个交易日上涨并创收盘新高,这11个交易日累计涨幅超15%,标普500指数涨0.8%创历史新高,道指跌0.15%。大型科技股普涨,特斯拉涨超7%,微软涨超4%,苹果涨近3%,英伟达、谷歌、Meta涨超1%。热门中概股涨跌不一,百度涨超2%,阿里巴巴、腾讯音乐涨超1%,理想汽车跌超3%,蔚来跌超2%,小鹏集团跌超1%。

ps, ai, ae插件都可以用html和js开发了

Adobe ScriptUI 学习之旅:从标记语言到跨应用交互

1. 缘起:对 Duik 插件的探索

作为 After Effects 用户,我一直对 Duik 这个强大的角色动画插件库充满好奇。Duik 提供了丰富的骨骼动画工具,极大地简化了角色动画的制作流程。然而,当我尝试深入学习它的代码结构时,却遇到了一些挑战:

  • 文档收费:完整的 Duik 文档需要付费购买
  • 代码结构问题:Duik 没有采用模块化设计,而是大量使用全局变量作为功能实现方式,导致代码可读性差,难以维护和扩展
  • 学习曲线陡峭:对于初学者来说,理解其代码逻辑较为困难

2. 寻找官方文档的历程

为了更好地理解 Adobe 脚本开发,我开始从 After Effects 的帮助菜单中寻找相关文档。有趣的是,Adobe 官方并没有直接提供完整的 ScriptUI 文档,而是在帮助菜单的链接中指向了第三方资源:

通过这些文档,我了解到 ScriptUI 不仅适用于 After Effects,还可以在其他 Adobe 应用中使用,并且支持跨应用交互,这为我的开发思路打开了新的大门。

3. 痛点:ScriptUI 构建的复杂性

在学习过程中,我发现 ScriptUI 的构建方式相对繁琐:

  • 需要通过脚本代码手动创建每个 UI 组件
  • 布局管理需要编写大量代码
  • 修改 UI 结构时需要调整多处代码

Duik 虽然抽象了一些方法来简化这个过程,但对于我来说仍然不够直观。我开始思考:是否可以使用更简洁的标记语言来描述 UI,然后编译成 ScriptUI 代码?

4. 解决方案:标记语言到 ScriptUI 的转换

基于这个想法,我创建了一个代码仓库,实现了从 HTML 标记语言到 Adobe ScriptUI JSX 代码的转换。在开发初期,我考虑过使用 Vue Compiler 来处理标记语言,但后来发现 Vue 支持的特性和 HTML 差别较大,而且我们并不需要 HTML 的全部功能,因此最终决定自己实现一个轻量级的转换引擎。

核心功能

  • HTML 标记语言支持:使用熟悉的 HTML 标签来描述 UI 结构
  • 组件映射:将 HTML 标签映射到对应的 ScriptUI 组件
  • 事件处理:支持内联 onClick 事件绑定
  • 实时编译:通过文件监视器实时将 HTML 变更编译为 JSX

项目结构

项目采用 monorepo 结构,包含三个核心包:

  1. adobe-scriptui-html-to-jsx:核心转换引擎,负责将 HTML 标记转换为 ScriptUI JSX 代码
  2. adobe-scriptui-watcher:文件监视器,监视 HTML 文件变化并自动触发编译
  3. adobe-scriptui-ae-helper:示例应用,展示了如何使用该工具构建实际的 After Effects 助手工具

这种拆包设计的目的是为了模块化和可扩展性,现在主要用于 After Effects,但未来可以轻松扩展到其他 Adobe 应用中。

5. 实际应用:AE 助手工具

通过这个工具,我构建了一个功能丰富的 After Effects 助手工具,包含:

  • 弹性表达式:为属性添加弹性动画效果
  • 常用表达式:快速应用 loop、wiggle、random 等常用表达式
  • AI 文件导入:支持从 Illustrator 导入文件到 After Effects
  • 用户友好的界面:通过 HTML 标记语言快速构建和修改 UI

6. 未来展望

这个项目只是我对 Adobe 脚本开发探索的开始。未来,我计划:

业务调研与痛点解决

  • 表达式管理系统:针对设计师使用表达式的痛点,开发一个更智能的表达式管理系统。目前设计师们通常需要从文本文件中复制粘贴表达式,修改参数困难,应用到其他属性时容易出错,最终只好放弃使用表达式而回到关键帧动画。
  • 可视化表达式编辑器:开发一个可视化的表达式编辑器,让设计师可以通过界面调整参数,而不需要直接编写代码。

功能抽象

  • 跨应用组件库:构建一套适用于所有 Adobe 应用的组件库
  • 模板系统:创建可复用的 UI 模板
  • 跨应用工作流:实现不同 Adobe 应用之间的无缝交互

开发流程优化

  • AI 辅助开发:利用 AI 工具生成初始代码,然后根据文档进行调整
  • 可视化编辑器:开发一个可视化 UI 编辑器,进一步简化开发流程
  • 社区贡献:鼓励社区贡献,共同完善这个工具

7. 结语

通过对 Adobe ScriptUI 的学习和探索,我不仅解决了 UI 构建的痛点,还为 Adobe 脚本开发提供了一种新的思路。标记语言到 ScriptUI 的转换,使得 UI 开发变得更加直观和高效。

虽然这个项目还在不断发展中,但它已经展示了如何通过创新的方法来简化复杂的开发流程。我相信,随着更多功能的加入和社区的参与,这个工具将成为 Adobe 脚本开发者的有力助手,同时也能帮助设计师们更轻松地使用和理解表达式,提升动画制作的效率和质量。


提示:如果你也对 Adobe 脚本开发感兴趣,欢迎访问我的 GitHub 仓库 adobe-script-ui-helper,一起探索 Adobe 脚本开发的无限可能!

每日一题-距离最小相等元素查询🟡

给你一个 环形 数组 nums 和一个数组 queries 。

对于每个查询 i ,你需要找到以下内容:

  • 数组 nums 中下标 queries[i] 处的元素与 任意 其他下标 j(满足 nums[j] == nums[queries[i]])之间的 最小 距离。如果不存在这样的下标 j,则该查询的结果为 -1

返回一个数组 answer,其大小与 queries 相同,其中 answer[i] 表示查询i的结果。

 

示例 1:

输入: nums = [1,3,1,4,1,3,2], queries = [0,3,5]

输出: [2,-1,3]

解释:

  • 查询 0:下标 queries[0] = 0 处的元素为 nums[0] = 1 。最近的相同值下标为 2,距离为 2。
  • 查询 1:下标 queries[1] = 3 处的元素为 nums[3] = 4 。不存在其他包含值 4 的下标,因此结果为 -1。
  • 查询 2:下标 queries[2] = 5 处的元素为 nums[5] = 3 。最近的相同值下标为 1,距离为 3(沿着循环路径:5 -> 6 -> 0 -> 1)。

示例 2:

输入: nums = [1,2,3,4], queries = [0,1,2,3]

输出: [-1,-1,-1,-1]

解释:

数组 nums 中的每个值都是唯一的,因此没有下标与查询的元素值相同。所有查询的结果均为 -1。

 

提示:

  • 1 <= queries.length <= nums.length <= 105
  • 1 <= nums[i] <= 106
  • 0 <= queries[i] < nums.length

整洁架构三连问:是什么,怎么做,为什么要用

整洁架构问答

Q1:什么是整洁架构?

由 Robert C. Martin(Uncle Bob)提出,核心思想:业务逻辑独立于框架、UI、数据库等外部细节,依赖关系只能从外层指向内层。

四层结构

┌─────────────────────────────────┐
   Frameworks & Drivers             Web数据库UI(最易变)
   ┌─────────────────────────┐   
     Interface Adapters           控制器网关Presenter
     ┌───────────────────┐     
       Application              用例(Use Cases)
       Business Rules        
       ┌─────────────┐      
        Enterprise           实体(Entities)最稳定
        Business          
        Rules             
       └─────────────┘      
     └───────────────────┘     
   └─────────────────────────┘   
└─────────────────────────────────┘
  1. 实体层:核心业务规则,零依赖
  2. 用例层:编排实体完成应用逻辑
  3. 接口适配层:数据转换,连接内外
  4. 框架与驱动层:具体技术实现

关键收益

  • 可测试:业务逻辑不依赖外部,独立单元测试
  • 可替换:换数据库/框架不影响核心逻辑
  • 可维护:修改外层不影响内层

一句话:让业务逻辑成为核心,让数据库、框架等成为可插拔的细节。


Q2:整洁架构和普通架构有什么区别?

普通架构按技术类型分层,整洁架构按依赖方向分层。

直观对比

普通架构(传统分层)
src/
├── components/    ← UI 组件
├── api/           ← 接口调用
├── store/         ← 状态管理
├── utils/         ← 工具函数
└── views/         ← 页面

依赖方向:随意交叉

组件 ←→ store ←→ api
 ↕        ↕       ↕
utils ←─ views ←─ components
整洁架构
src/
├── domain/           ← 纯业务(零依赖)
├── application/      ← 用例编排
├── infrastructure/   ← 技术实现
└── ui/               ← 界面渲染

依赖方向:只从外向内

ui → application → domain
infrastructure → application → domain
         ↘          ↓          ↗
          任何外层都可以依赖内层
          内层永远不知道外层存在

具体差异对比

维度 普通架构 整洁架构
分层依据 按技术类型(组件、API、工具) 按依赖方向(外层依赖内层)
业务逻辑位置 散落在组件、store、utils 集中在 domain 层
框架耦合 业务逻辑依赖 Vue/React domain 层零框架依赖
改数据库 改 api/ + store/ + 组件 只改 infrastructure/
换框架 几乎重写 只重写 ui/ 层
单元测试 需要 mock 框架、mount 组件 直接测纯 TS 函数
API 字段变了 改多处 只改 Repository 转换逻辑

代码对比:同一个需求

普通架构写法
// store/cart.ts
import axios from 'axios'

export const useCartStore = defineStore('cart', () => {
  const items = ref([])

  async function addItem(productId: string) {
    // 业务规则散落在 store 里
    if (items.value.length >= 20) {
      throw new Error('购物车已满')
    }
    // 直接耦合 axios
    const res = await axios.post('/api/cart', { productId })
    // 直接操作原始 JSON
    items.value.push(res.data)
  }

  return { items, addItem }
})
<!-- CartPage.vue -->
<script setup>
import { useCartStore } from '@/store/cart'

const cartStore = useCartStore()

// 组件里也有业务逻辑
const canAdd = computed(() => cartStore.items.length < 20)
</script>

问题:业务规则在 store 和组件里都有,换框架全废,换 axios 要改 store。

整洁架构写法
// domain/aggregates/Cart.ts — 纯 TS,零依赖
export class Cart {
  private items: CartItem[] = []

  addItem(item: CartItem): void { /* 规则在这里 */ }
  canAddMore(): boolean { return this.items.length < 20 }
}
// application/usecases/AddToCartUseCase.ts — 编排
export class AddToCartUseCase {
  constructor(private repo: CartRepository) {}
  async execute(id: string) {
    const cart = await this.repo.getCart()
    cart.addItem(item)           // 调领域方法
    await this.repo.save(cart)   // 持久化
  }
}
// infrastructure/repositories/ApiCartRepository.ts — 适配
export class ApiCartRepository implements CartRepository {
  async getCart() { /* axios → 领域对象 */ }
  async save(cart) { /* 领域对象 → axios */ }
}
<!-- ui/pages/CartPage.vue — 只渲染 -->
<script setup>
const { addItem } = useCart()  // composable 调用用例
</script>

好处:业务规则只在 Cart.ts,换框架/换 axios 都不影响它。

什么时候用哪个

场景 建议
简单 CRUD、几个页面 普通架构够用,别过度设计
中等复杂度、有业务规则 提取 domain 层,用 composable 做应用层
复杂业务(交易、审批、计费) 完整整洁架构 + DDD

一句话总结:普通架构按技术分文件夹,整洁架构按依赖方向分层。前者简单但业务逻辑散落各处,后者前期成本高但业务逻辑内聚、可替换、可测试。


Q3:目前 Vue CLI 是不是都是整洁架构?

不是。 Vue CLI 生成的项目默认不符合整洁架构

Vue CLI 默认结构

src/
├── components/    ← 组件(UI + 业务逻辑混在一起)
├── views/         ← 页面(UI + 业务逻辑混在一起)
├── router/        ← 路由
├── store/         ← Vuex/Pinia(状态管理)
├── api/           ← API 调用
├── utils/         ← 工具函数
└── App.vue

为什么不是整洁架构

整洁架构要求 Vue CLI 默认 问题
依赖从外指向内 各层互相 import,方向混乱 无依赖规则约束
领域层纯 TS,零框架依赖 业务逻辑写在 .vue 领域层耦合了 Vue
用例层编排领域对象 没有 use case 层 业务逻辑散落在组件和 store
Repository 接口在领域层定义 直接在组件里调 axios 数据获取和 UI 耦合

典型 Vue 组件的问题

<script setup>
// ❌ UI、业务规则、API 调用全混在一起
import axios from 'axios'

const cart = ref([])

async function addItem(id) {
  if (cart.value.length >= 20) {       // 业务规则在组件里
    alert('购物车已满')
    return
  }
  const res = await axios.post('/api/cart', { id })  // 直接调 API
  cart.value.push(res.data)
}
</script>

Vue CLI 能改成整洁架构吗

能,但需要手动改造,Vue CLI 不会自动帮你分层:

src/
├── domain/           ← 手动添加:纯 TS 领域层
│   ├── value-objects/
│   ├── entities/
│   ├── aggregates/
│   └── repositories/    ← 接口定义
├── application/      ← 手动添加:用例层
│   ├── usecases/
│   └── composables/
├── infrastructure/   ← 手动添加:适配层
│   ├── repositories/    ← Repository 实现
│   └── api/
├── ui/               ← 原 components/views,只负责渲染
│   ├── components/
│   └── pages/
├── router/
└── App.vue

一句话总结

Vue CLI 是脚手架工具,只管项目初始化和构建配置,不管架构分层。默认生成的项目是传统分层(按技术类型分),不是整洁架构(按依赖方向分)。整洁架构需要开发者自己设计和实施。

前端整洁架构详解

前端整洁架构详解

以电商购物车系统为例,逐层讲解整洁架构在前端中的落地方式。


架构分层总览

┌──────────────────────────────────┐
│  UI 框架层(Vue/React 组件)      │  ← 最外层:页面、组件
│  ┌────────────────────────────┐  │
│  │  接口适配层                 │  │  ← Store/Composable、API 适配、Repository 实现
│  │  ┌────────────────────┐    │  │
│  │  │  应用层             │    │  │  ← 用例:编排领域逻辑
│  │  │  ┌────────────┐    │    │  │
│  │  │  │  领域层     │    │    │  │  ← 纯 JS/TS:实体、值对象、规则
│  │  │  └────────────┘    │    │  │
│  │  └────────────────────┘    │  │
│  └────────────────────────────┘  │
└──────────────────────────────────┘

依赖规则:依赖关系只能从外层指向内层,内层完全不知道外层的存在。


1. 领域层(纯 JS/TS,零依赖)

最内层,不引入任何框架,不 import Vue/React,不 import axios,纯业务逻辑。

1.1 值对象 — 封装校验和不可变概念

// domain/value-objects/Money.ts
export class Money {
  constructor(private amount: number, private currency: string = 'CNY') {
    if (amount < 0) throw new Error('金额不能为负数');
  }

  add(other: Money): Money {
    if (this.currency !== other.currency) throw new Error('币种不同');
    return new Money(this.amount + other.amount, this.currency);
  }

  multiply(factor: number): Money {
    return new Money(this.amount * factor, this.currency);
  }

  get value(): number { return this.amount; }
  toString(): string { return `${this.currency} ${this.amount.toFixed(2)}`; }
}
// domain/value-objects/Email.ts
export class Email {
  constructor(private value: string) {
    if (!/^[^\s@]+@[^\s@]+.[^\s@]+$/.test(value)) {
      throw new Error('邮箱格式不合法');
    }
  }

  get domain(): string { return this.value.split('@')[1]; }
  toString(): string { return this.value; }
}

为什么用值对象? 校验逻辑内聚,不会散落在各个组件里。任何地方拿到一个 Money,就保证它是合法的。

1.2 实体 — 有标识,有生命周期

// domain/entities/CartItem.ts
import { Money } from '../value-objects/Money';

export class CartItem {
  constructor(
    public readonly productId: string,  // 唯一标识
    public readonly name: string,
    private unitPrice: Money,
    private quantity: number,
  ) {
    if (quantity <= 0) throw new Error('数量必须大于0');
  }

  get totalPrice(): Money {
    return this.unitPrice.multiply(this.quantity);
  }

  changeQuantity(newQty: number): CartItem {
    return new CartItem(this.productId, this.name, this.unitPrice, newQty);
  }
}

1.3 聚合根 — 保证一致性边界

// domain/aggregates/Cart.ts
import { Money } from '../value-objects/Money';
import { CartItem } from '../entities/CartItem';

export class Cart {
  private items: CartItem[] = [];

  addItem(item: CartItem): void {
    const existing = this.items.find(i => i.productId === item.productId);
    if (existing) {
      // 已存在则合并数量
      this.items = this.items.map(i =>
        i.productId === item.productId
          ? i.changeQuantity(i.quantity + item.quantity)
          : i
      );
    } else {
      this.items.push(item);
    }
  }

  removeItem(productId: string): void {
    this.items = this.items.filter(i => i.productId !== productId);
  }

  get total(): Money {
    return this.items.reduce(
      (sum, item) => sum.add(item.totalPrice),
      new Money(0),
    );
  }

  get itemCount(): number {
    return this.items.length;
  }

  // 业务规则:购物车最多 20 件商品
  canAddMore(): boolean {
    return this.items.length < 20;
  }
}

关键点:所有业务规则都在这里——最多 20 件、合并同类商品、金额计算。组件不需要知道这些规则。

1.4 Repository 接口(领域层定义)

// domain/repositories/CartRepository.ts
import { Cart } from '../aggregates/Cart';

export interface CartRepository {
  getCart(): Promise<Cart>;
  save(cart: Cart): Promise<void>;
  clear(): Promise<void>;
}

接口在领域层定义,实现在适配层——这是依赖反转的核心,让领域层不依赖具体的数据获取方式。


2. 应用层(用例 — 编排领域对象)

这一层只做编排,不包含业务规则本身。它协调领域对象完成一个完整操作。

2.1 添加商品到购物车

// application/usecases/AddToCartUseCase.ts
import { Cart } from '../../domain/aggregates/Cart';
import { CartItem } from '../../domain/entities/CartItem';
import { Money } from '../../domain/value-objects/Money';
import { CartRepository } from '../../domain/repositories/CartRepository';

export class AddToCartUseCase {
  constructor(private cartRepo: CartRepository) {}

  async execute(productId: string, name: string, price: number, qty: number): Promise<Cart> {
    // 1. 从仓库获取当前购物车
    const cart = await this.cartRepo.getCart();

    // 2. 业务规则检查(规则在领域对象里,用例只调用)
    if (!cart.canAddMore()) {
      throw new Error('购物车已满,最多 20 件商品');
    }

    // 3. 创建领域对象
    const item = new CartItem(productId, name, new Money(price), qty);

    // 4. 执行领域操作
    cart.addItem(item);

    // 5. 持久化
    await this.cartRepo.save(cart);

    return cart;
  }
}

2.2 结算下单

// application/usecases/CheckoutUseCase.ts
import { CartRepository } from '../../domain/repositories/CartRepository';
import { OrderRepository } from '../../domain/repositories/OrderRepository';
import { Order } from '../../domain/aggregates/Order';

export class CheckoutUseCase {
  constructor(
    private cartRepo: CartRepository,
    private orderRepo: OrderRepository,
  ) {}

  async execute(userId: string): Promise<Order> {
    const cart = await this.cartRepo.getCart();

    if (cart.itemCount === 0) {
      throw new Error('购物车为空,无法下单');
    }

    // 领域逻辑:创建订单
    const order = Order.createFromCart(cart, userId);

    await this.orderRepo.save(order);
    await this.cartRepo.clear();

    return order;
  }
}

用例层的特点:读数据 → 调领域方法 → 存数据。像导演一样编排,但不自己写业务规则。


3. 接口适配层(Store/Composable + API 适配)

3.1 Repository 实现(JSON ↔ 领域对象转换)

// infrastructure/repositories/ApiCartRepository.ts
import { CartRepository } from '../../domain/repositories/CartRepository';
import { Cart } from '../../domain/aggregates/Cart';
import { CartItem } from '../../domain/entities/CartItem';
import { Money } from '../../domain/value-objects/Money';
import { cartApi } from '../api/CartApi';

export class ApiCartRepository implements CartRepository {
  async getCart(): Promise<Cart> {
    // API 返回的是原始 JSON,需要转换成领域对象
    const raw = await cartApi.fetchCart();
    const cart = new Cart();
    for (const item of raw.items) {
      cart.addItem(new CartItem(item.productId, item.name, new Money(item.price), item.qty));
    }
    return cart;
  }

  async save(cart: Cart): Promise<void> {
    // 领域对象 → 原始 JSON,给 API
    const payload = {
      items: cart.items.map(i => ({
        productId: i.productId,
        name: i.name,
        price: i.unitPrice.value,
        qty: i.quantity,
      })),
    };
    await cartApi.updateCart(payload);
  }

  async clear(): Promise<void> {
    await cartApi.clearCart();
  }
}

关键:API 返回的 JSON → 领域对象的转换在这里完成。领域层永远不碰原始 JSON。

3.2 API 层(最外层细节)

// infrastructure/api/CartApi.ts
import axios from 'axios';

export const cartApi = {
  fetchCart: () => axios.get('/api/cart').then(r => r.data),
  updateCart: (data: any) => axios.put('/api/cart', data).then(r => r.data),
  clearCart: () => axios.delete('/api/cart').then(r => r.data),
};

3.3 Composable(连接用例和 UI)

// application/composables/useCart.ts
import { ref } from 'vue';
import { AddToCartUseCase } from '../usecases/AddToCartUseCase';
import { ApiCartRepository } from '../../infrastructure/repositories/ApiCartRepository';

const cartRepo = new ApiCartRepository();
const addToCartUseCase = new AddToCartUseCase(cartRepo);

export function useCart() {
  const cart = ref<Cart | null>(null);
  const loading = ref(false);
  const error = ref<string | null>(null);

  async function addItem(productId: string, name: string, price: number, qty: number) {
    loading.value = true;
    error.value = null;
    try {
      cart.value = await addToCartUseCase.execute(productId, name, price, qty);
    } catch (e) {
      error.value = e.message;
    } finally {
      loading.value = false;
    }
  }

  return { cart, loading, error, addItem };
}

4. UI 框架层(组件 — 只负责渲染)

<!-- ui/components/ProductCard.vue -->
<script setup lang="ts">
import { useCart } from '@/application/composables/useCart';

const { addItem, loading, error } = useCart();

function handleAdd() {
  addItem(product.id, product.name, product.price, 1);
}
</script>

<template>
  <div class="product-card">
    <h3>{{ product.name }}</h3>
    <p>¥{{ product.price }}</p>
    <button :disabled="loading" @click="handleAdd">
      加入购物车
    </button>
    <p v-if="error" class="error">{{ error }}</p>
  </div>
</template>

组件只做三件事:展示数据、捕获用户操作、调用用例。零业务逻辑。


依赖关系总览

ProductCard.vue          ← 只渲染,零业务逻辑
  ↓ 调用
useCart()                ← 状态管理 + 调用用例
  ↓ 调用
AddToCartUseCase         ← 编排领域对象
  ↓ 使用
Cart / CartItem / Money  ← 纯业务规则
  ↓ 通过接口
CartRepository (接口)    ← 领域层定义接口
  ↓ 实现
ApiCartRepository        ← 适配层实现,转换 JSON ↔ 领域对象
  ↓ 调用
cartApi (axios)          ← 最外层:HTTP 请求细节

箭头方向 = 依赖方向,全部从外指向内。内层完全不知道外层存在。


项目目录结构

src/
├── domain/                          # 领域层(纯 TS,零框架依赖)
│   ├── value-objects/
│   │   ├── Money.ts
│   │   └── Email.ts
│   ├── entities/
│   │   └── CartItem.ts
│   ├── aggregates/
│   │   ├── Cart.ts
│   │   └── Order.ts
│   └── repositories/
│       ├── CartRepository.ts        # 接口定义
│       └── OrderRepository.ts
│
├── application/                     # 应用层(用例编排)
│   ├── usecases/
│   │   ├── AddToCartUseCase.ts
│   │   └── CheckoutUseCase.ts
│   └── composables/
│       └── useCart.ts
│
├── infrastructure/                  # 接口适配层
│   ├── repositories/
│   │   └── ApiCartRepository.ts     # Repository 实现
│   └── api/
│       └── CartApi.ts               # axios 调用
│
├── ui/                              # UI 框架层
│   ├── components/
│   │   └── ProductCard.vue
│   └── pages/
│       └── CartPage.vue

这样做的好处

场景 传统做法 整洁架构
换框架 Vue→React 重写所有业务逻辑 只重写 UI 层,领域层直接复用
API 字段名变了 改几十个组件 只改 ApiCartRepository 的转换逻辑
加新业务规则 在组件里到处加 if 在领域对象里加,组件无感知
单元测试 mock axios、mount 组件 直接测 Cart.addItem(),纯函数测试
后端复用 不可能 领域层是纯 TS,Node 端可直接引用

实际项目中的权衡

不是所有前端项目都需要完整分层

项目规模 建议做法
简单页面/CRUD 分离 API 调用和 UI 即可,不必过度设计
中等复杂度 提取领域层(纯 TS),用 Composable 做应用层
复杂业务系统 完整分层 + DDD 建模(如交易系统、审批流)

核心原则

领域层必须是纯 JS/TS,不依赖任何框架。 这样你的业务逻辑可以:

  • 在 Vue 和 React 之间迁移
  • 在 Node 后端复用(同构)
  • 独立写单元测试,不需要 mock 组件

框架是细节,业务逻辑才是核心。

我花一天时间Vibe Coding的开源AI工具,一键检测你的电脑能跑哪些AI大模型

最近一直在深耕 AI Agent 与大模型应用,比如 JitKnow AI 知识库、JitWord协同AI文档、Pxcharts 超级表格,同时也持续在给大家分享 GitHub 上真正能落地、能解决实际问题的优质AI开源项目。

图片

今天和大家分享一款我花了一天时间做的开源工具——ai-detector。

它是一款完全运行在浏览器端的免费硬件检测工具。能自动读取你电脑的内存、CPU、GPU 信息,智能匹配 21+ 主流开源 AI 大模型的兼容性,并给出本地运行速度预估,帮你在 5 秒内找到最适合自己电脑的 AI 模型。

无需安装、无需登录、无需上传任何数据,全程 100% 本地运行。

ps:最近小龙虾很火,但是又担心自己电脑配置不够的朋友,可以使用这款线上工具检测一下电脑适合哪些模型,告别AI恐惧啦~老规矩,先上开源地址。

github:github.com/MrXujiang/a…

演示地址:jitword.com/ai-detector

下面就和大家详细分享一下这款 AI Coding 出来的开源项目。

先上一个基础的功能演示比如我想在我的电脑里部署一个本地AI模型,但是又担心我的电脑配置不够,那么直接运行这个项目:

图片

点击开始检测, 不到5s,就会给出自己电脑的性能和适合运行哪些模型的详细报告:

图片

分析的非常准确,可能是我电脑年久失修,只给出了28分。。。ai-detector 还会为我们推荐基于当前电脑,适合运行的模型推荐:

图片

不仅如此,它还会对目前主流的数十个开源模型,对当前电脑进行分析评测,分析出部署这些大模型的性能,风险等信息,如下:

图片

对于比较吃电脑性能的模型,它会给我们全面的分析:

图片

最后会基于我们的硬件配置,估算各模型生成速度(tokens/秒),并输出可视化的分析报表:

图片

核心能力总结

下面和大家总结一下 ai-detector 的核心能力和亮点。

  1. 硬件自动检测
  • 系统内存通过 navigator.deviceMemory 读取 RAM 大小
  • CPU 核心数通过 navigator.hardwareConcurrency 读取逻辑核数
  • GPU 型号通过 WebGL WEBGL_debug_renderer_info 扩展识别显卡
  • 综合跑分基于内存与 CPU 计算 0-100 综合评分,直观了解你的 AI 能力等级

2. 21+ 大模型兼容性分析

覆盖当前最主流的开源大模型系列,一键发现哪些能跑、哪些跑不动:

系列 代表模型 参数规模
🦙 Llama TinyLlama、Llama 3.2、Llama 3.1 1.1B ~ 70B
🌐 Qwen Qwen2.5 3B/7B/14B/32B/72B 3B ~ 72B
💎 Phi Phi-3 Mini、Phi-3 Medium 3.8B ~ 14B
🌬️ Mistral Mistral 7B 7B
🧠 DeepSeek DeepSeek-R1、DeepSeek Coder 7B ~ 70B
👁️ 多模态 LLaVA 7B、MiniCPM-V 8B 7B ~ 8B
💫 Gemma Gemma 2 2B 2B
  1. 支持兼容性三级分类
  • 😊 流畅运行内存充裕,可稳定高速推理
  • ⚠️ 勉强运行:内存刚好满足,速度偏慢
  • ❌ 内存不足:当前配置无法加载该模型
  1. 运行速度排行

检测完成后自动生成可运行模型的速度排行榜,按 tokens/秒从高到低排列,帮你优先选出响应最快的模型。

  1. 量化模式切换

图片

  • Q4 量化内存占用更低,普通设备首选
  • Q8 量化精度更高,内存需求约为 Q4 的 2 倍
  • 一键切换,实时刷新所有模型兼容状态
  1. 个性化模型推荐基于我们的硬件配置,自动从模型库中精选 最均衡速度最快能力最强 三款推荐,省去选择烦恼。

  2. 一键复制检测报告

图片

生成包含硬件配置、综合评分、可运行模型的文本报告,方便分享或咨询。

完整使用流程总结

为了让大家轻松上手使用,我总结了一下7步使用法,大家可以参考一下:

  1. 打开页面 → 点击「开始硬件检测」按钮
  2. 等待扫描(约 3~5 秒)→ 自动完成内存、CPU、GPU 检测
  3. 查看结果 → 获得综合跑分与设备等级评价
  4. 浏览推荐 → 查看「为你推荐」区块,获取最适合你的 3 款模型
  5. 筛选模型 → 在「模型列表」中按兼容状态、类型筛选,支持关键词搜索
  6. 切换量化 → 尝试切换 Q4 / Q8 量化,查看内存需求变化
  7. 复制报告 → 一键复制检测结果,方便保存或分享

为什么要做这个开源项目这里分析一张图,大家就知道了:

特性 AI -Detector 其他工具
需要安装 ❌ 无需安装 ✅ 通常需要
需要登录 ❌ 无需注册 ✅ 通常需要
数据上传 ❌ 完全不上传 ⚠️ 部分上传
模型覆盖 ✅ 21+ 主流模型 ⚠️ 覆盖有限
速度预估 ✅ tokens/s 量化估算 ⚠️ 通常无此功能
升级建议 ✅ 智能提示内存升级方案 ❌ 无
量化切换 ✅ Q4 / Q8 实时切换 ❌ 无
开源免费 ✅ MIT 协议 ⚠️ 多数收费

主要是为了让任何没有技术基础的人,也能轻松拥有专业级AI模型选型能力,告别AI焦虑。

目前已开源,大家可以免费使用:

github:github.com/MrXujiang/a…

DEMO演示地址:jitword.com/ai-detector

❌