[{"data":1,"prerenderedAt":483},["ShallowReactive",2],{"content:\u002Fposts\u002F2025\u002Fatomic-design-vue-or-nuxt":3,"surround:\u002Fposts\u002F2025\u002Fatomic-design-vue-or-nuxt":473},{"id":4,"title":5,"body":6,"canonical":19,"categories":443,"date":445,"description":446,"draft":447,"extension":448,"image":449,"meta":450,"navigation":454,"path":455,"permalink":456,"readingTime":457,"recommend":456,"references":456,"seo":462,"sitemap":463,"stem":467,"tags":468,"type":456,"updated":471,"__hash__":472},"content\u002Fposts\u002F2025\u002Fatomic-design-vue-or-nuxt.md","【翻译】Vue 和 Nuxt 中的 Atomic Design",{"type":7,"value":8,"toc":422},"minimark",[9,23,26,30,33,46,49,52,59,62,80,91,94,111,155,159,164,169,179,182,185,193,197,201,207,210,216,220,227,231,237,244,248,255,262,272,405,408,416],[10,11,12],"auths-alex",{},[13,14,15,16],"p",{},"原文地址：",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Falexop.dev\u002Fposts\u002Fatomic-design-vue-or-nuxt\u002F",[21],"nofollow","Atomic Architecture: Revolutionizing Vue and Nuxt Project Structure",[24,25],"hr",{},[27,28,29],"h2",{"id":29},"简介",[13,31,32],{},"清晰的写作需要清晰的思考。这同样适用于编码。在启动个人项目时，将所有组件放入一个文件夹可能会起作用。但随着项目的增长，尤其是对于较大的团队，这种方法会导致问题：",[34,35,36,40,43],"ul",{},[37,38,39],"li",{},"重复的代码",[37,41,42],{},"超大的多用途组件",[37,44,45],{},"难以测试的代码",[13,47,48],{},"Atomic Design 提供了一个解决方案。让我们看看如何将其应用于 Nuxt 项目。",[27,50,51],{"id":51},"什么是原子设计",[13,53,54],{},[55,56],"img",{"alt":57,"src":58},"","https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F67aeee43ba0f2-78.webp",[13,60,61],{},"Brad Frost 开发了 Atomic Design 作为创建设计系统的方法。它分为五个级别，灵感来自化学：",[63,64,65,68,71,74,77],"ol",{},[37,66,67],{},"原子：基本构建块（例如表单标签、输入、按钮）",[37,69,70],{},"分子：简单的 UI 元素组（例如搜索表单）",[37,72,73],{},"生物体：由分子\u002F原子制成的复杂成分（例如头部）",[37,75,76],{},"模板：页面级布局",[37,78,79],{},"页面：包含内容的模板的特定实例",[81,82,83],"alert",{},[13,84,85,86],{},"为了更好地探索原子设计原则，我建议阅读 Brad Frost 的博客文章：",[17,87,90],{"href":88,"rel":89},"https:\u002F\u002Fbradfrost.com\u002Fblog\u002Fpost\u002Fatomic-web-design\u002F",[21],"原子 Web 设计",[13,92,93],{},"对于 Nuxt，我们可以调整这些定义：",[34,95,96,99,102,105,108],{},[37,97,98],{},"原子：纯净的单一用途组件",[37,100,101],{},"分子：具有最小逻辑的原子组合",[37,103,104],{},"生物体：更大的、独立的、可重复使用的组件",[37,106,107],{},"模板：定义页面结构的 Nuxt 布局",[37,109,110],{},"页面：处理数据和 API 调用的组件",[81,112,113,119,122,152],{},[114,115,116],"template",{"v-slot:title":57},[13,117,118],{},"生物体与分子：有什么区别？",[13,120,121],{},"分子和生物体可能会令人困惑。以下是考虑它们的简单方法：",[34,123,124,138],{},[37,125,126,127],{},"分子小而简单。它们就像乐高积木，可以卡在一起。例子：\n",[34,128,129,132,135],{},[37,130,131],{},"搜索栏（输入 + 按钮）",[37,133,134],{},"登录表单（用户名输入 + 密码输入 + 提交按钮）",[37,136,137],{},"星级评定（5 个星形图标 + 评定量编号）",[37,139,140,141],{},"生物体更大、更复杂。它们就像预先构建的乐高套装。例子：\n",[34,142,143,146,149],{},[37,144,145],{},"完整的网站标题（徽标 + 导航菜单 + 搜索栏）",[37,147,148],{},"商品卡（图片 + 标题 + 价格 + 加入购物车按钮）",[37,150,151],{},"评论部分（评论表单 + 评论列表）",[13,153,154],{},"请记住：分子是生物体的一部分，但生物体可以独立工作。",[27,156,158],{"id":157},"代码示例之前和之后","代码示例：之前和之后",[160,161,163],"h3",{"id":162},"考虑这个非-atomic-design-待办事项应用程序组件","考虑这个非 Atomic Design 待办事项应用程序组件",[13,165,166],{},[55,167],{"alt":57,"src":168},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F67aef6a3c9fe4-78.webp",[170,171,176],"pre",{"className":172,"code":174,"language":175,"meta":57},[173],"language-vue","\u003Cscript setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\n\ninterface Todo {\n    id: number\n    text: string\n}\n\nconst newTodo = ref('')\nconst todos = ref\u003CTodo[]>([])\n\nasync function fetchTodos() {\n    \u002F\u002F Simulating API call\n    todos.value = [\n        { id: 1, text: 'Learn Vue.js' },\n        { id: 2, text: 'Build a Todo App' },\n        { id: 3, text: 'Study Atomic Design' }\n    ]\n}\n\nasync function addTodo() {\n    if (newTodo.value.trim()) {\n    \u002F\u002F Simulating API call\n        const newTodoItem: Todo = {\n            id: Date.now(),\n            text: newTodo.value\n        }\n        todos.value.push(newTodoItem)\n        newTodo.value = ''\n    }\n}\n\nasync function deleteTodo(id: number) {\n    \u002F\u002F Simulating API call\n    todos.value = todos.value.filter(todo => todo.id !== id)\n}\n\nonMounted(fetchTodos)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cdiv class=\"container mx-auto p-4\">\n        \u003Ch1 class=\"text-2xl font-bold mb-4 text-gray-800 dark:text-gray-200\">\n            Todo App\n        \u003C\u002Fh1>\n\n        \u003C!-- Add Todo Form -->\n        \u003Cform class=\"mb-4\" @submit.prevent=\"addTodo\">\n            \u003Cinput\n                v-model=\"newTodo\"\n                type=\"text\"\n                placeholder=\"Enter a new todo\"\n                class=\"border p-2 mr-2 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded\"\n            >\n            \u003Cbutton type=\"submit\" class=\"bg-blue-500 hover:bg-blue-600 text-white p-2 rounded transition duration-300\">\n                Add Todo\n            \u003C\u002Fbutton>\n        \u003C\u002Fform>\n\n        \u003C!-- Todo List -->\n        \u003Cul class=\"space-y-2\">\n            \u003Cli\n                v-for=\"todo in todos\"\n                :key=\"todo.id\"\n                class=\"flex justify-between items-center p-3 bg-gray-100 dark:bg-gray-700 rounded shadow-sm\"\n            >\n                \u003Cspan class=\"text-gray-800 dark:text-gray-200\">{{ todo.text }}\u003C\u002Fspan>\n                \u003Cbutton\n                    class=\"bg-red-500 hover:bg-red-600 text-white p-1 rounded transition duration-300\"\n                    @click=\"deleteTodo(todo.id)\"\n                >\n                    Delete\n                \u003C\u002Fbutton>\n            \u003C\u002Fli>\n        \u003C\u002Ful>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue",[177,178,174],"code",{"__ignoreMap":57},[13,180,181],{},"这种方法会导致大型、难以维护的组件。让我们使用 Atomic Design 进行重构：",[160,183,184],{"id":184},"这将是重构的结构",[170,186,191],{"className":187,"code":189,"language":190},[188],"language-text","📐 Template (Layout)\n   │\n   └─── 📄 Page (TodoApp)\n        │\n        └─── 📦 Organism (TodoList)\n             │\n             ├─── 🧪 Molecule (TodoForm)\n             │    │\n             │    ├─── ⚛️ Atom (BaseInput)\n             │    └─── ⚛️ Atom (BaseButton)\n             │\n             └─── 🧪 Molecule (TodoItems)\n                  │\n                  └─── 🧪 Molecule (TodoItem) [multiple instances]\n                       │\n                       ├─── ⚛️ Atom (BaseText)\n                       └─── ⚛️ Atom (BaseButton)\n","text",[177,192,189],{"__ignoreMap":57},[194,195,196],"h4",{"id":196},"重构的组件",[198,199,200],"h5",{"id":200},"模板",[170,202,205],{"className":203,"code":204,"language":175,"meta":57},[173],"\u003Cscript setup lang=\"ts\">\nimport ThemeToggle from '~\u002Fcomponents\u002FThemeToggle.vue'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cdiv class=\"min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-300\">\n        \u003Cheader class=\"bg-white dark:bg-gray-800 shadow\">\n            \u003Cnav class=\"container mx-auto px-4 py-4 flex justify-between items-center\">\n                \u003CNuxtLink to=\"\u002F\" class=\"text-xl font-bold\">\n                    Todo App\n                \u003C\u002FNuxtLink>\n                \u003CThemeToggle \u002F>\n            \u003C\u002Fnav>\n        \u003C\u002Fheader>\n        \u003Cmain class=\"container mx-auto px-4 py-8\">\n            \u003Cslot \u002F>\n        \u003C\u002Fmain>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[177,206,204],{"__ignoreMap":57},[198,208,209],{"id":209},"页面",[170,211,214],{"className":212,"code":213,"language":175,"meta":57},[173],"\u003Cscript setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\n\nimport TodoList from '..\u002Fcomponents\u002Forganisms\u002FTodoList'\n\ninterface Todo {\n    id: number\n    text: string\n}\n\nconst todos = ref\u003CTodo[]>([])\n\nasync function fetchTodos() {\n    \u002F\u002F Simulating API call\n    todos.value = [\n        { id: 1, text: 'Learn Vue.js' },\n        { id: 2, text: 'Build a Todo App' },\n        { id: 3, text: 'Study Atomic Design' }\n    ]\n}\n\nasync function addTodo(text: string) {\n    \u002F\u002F Simulating API call\n    const newTodoItem: Todo = {\n        id: Date.now(),\n        text\n    }\n    todos.value.push(newTodoItem)\n}\n\nasync function deleteTodo(id: number) {\n    \u002F\u002F Simulating API call\n    todos.value = todos.value.filter(todo => todo.id !== id)\n}\n\nonMounted(fetchTodos)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cdiv class=\"container mx-auto p-4\">\n        \u003Ch1 class=\"text-2xl font-bold mb-4 text-gray-800 dark:text-gray-200\">\n            Todo App\n        \u003C\u002Fh1>\n        \u003CTodoList\n            :todos=\"todos\"\n            @add-todo=\"addTodo\"\n            @delete-todo=\"deleteTodo\"\n        \u002F>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[177,215,213],{"__ignoreMap":57},[198,217,219],{"id":218},"生物体-todolist","生物体 （TodoList）",[170,221,225],{"className":222,"code":223,"filename":224,"language":175,"meta":57},[173],"\u003Cscript setup lang=\"ts\">\nimport TodoForm from '..\u002Fmolecules\u002FTodoForm.vue'\nimport TodoItem from '..\u002Fmolecules\u002FTodoItem.vue'\n\ninterface Todo {\n    id: number\n    text: string\n}\n\ndefineProps\u003C{\n    todos: Todo[]\n}>()\n\ndefineEmits\u003C{\n    (e: 'add-todo', value: string): void\n    (e: 'delete-todo', id: number): void\n}>()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cdiv>\n        \u003CTodoForm @add-todo=\"$emit('add-todo', $event)\" \u002F>\n        \u003Cul class=\"space-y-2\">\n            \u003CTodoItem\n                v-for=\"todo in todos\"\n                :key=\"todo.id\"\n                :todo=\"todo\"\n                @delete-todo=\"$emit('delete-todo', $event)\"\n            \u002F>\n        \u003C\u002Ful>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","TodoList.vue",[177,226,223],{"__ignoreMap":57},[198,228,230],{"id":229},"分子todoform-和-todoitem","分子（TodoForm 和 TodoItem）",[170,232,235],{"className":233,"code":223,"filename":234,"language":175,"meta":57},[173],"TodoForm.vue",[177,236,223],{"__ignoreMap":57},[170,238,242],{"className":239,"code":240,"filename":241,"language":175,"meta":57},[173],"\u003Cscript setup lang=\"ts\">\nimport { ref } from 'vue'\n\nimport BaseButton from '..\u002Fatoms\u002FBaseButton.vue'\nimport BaseInput from '..\u002Fatoms\u002FBaseInput.vue'\n\nconst emit = defineEmits\u003C{\n    (e: 'add-todo', value: string): void\n}>()\nconst newTodo = ref('')\nfunction addTodo() {\n    if (newTodo.value.trim()) {\n        emit('add-todo', newTodo.value)\n        newTodo.value = ''\n    }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cform class=\"mb-4\" @submit.prevent=\"addTodo\">\n        \u003CBaseInput v-model=\"newTodo\" placeholder=\"Enter a new todo\" \u002F>\n        \u003CBaseButton type=\"submit\">\n            Add Todo\n        \u003C\u002FBaseButton>\n    \u003C\u002Fform>\n\u003C\u002Ftemplate>\n","TodoItem.vue",[177,243,240],{"__ignoreMap":57},[198,245,247],{"id":246},"原子basebutton-baseinput-basetext","原子（BaseButton， BaseInput， BaseText）",[170,249,253],{"className":250,"code":251,"filename":252,"language":175,"meta":57},[173],"\u003Cscript setup lang=\"ts\">\ndefineProps\u003C{\n    variant?: 'primary' | 'danger'\n}>()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cbutton\n        class=\"p-2 rounded transition duration-300\" :class=\"[\n            variant === 'danger'\n                ? 'bg-red-500 hover:bg-red-600 text-white'\n                : 'bg-blue-500 hover:bg-blue-600 text-white',\n        ]\"\n    >\n        \u003Cslot \u002F>\n    \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n","BaseButton.vue",[177,254,251],{"__ignoreMap":57},[170,256,260],{"className":257,"code":258,"filename":259,"language":175,"meta":57},[173],"\u003Cscript setup lang=\"ts\">\ndefineProps\u003C{\n    modelValue: string\n    placeholder?: string\n}>()\ndefineEmits\u003C{\n    (e: 'update:modelValue', value: string): void\n}>()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cinput\n        :value=\"modelValue\"\n        type=\"text\"\n        :placeholder=\"placeholder\"\n        class=\"border p-2 mr-2 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded\"\n        @input=\"$emit('update:modelValue', ($event.target as HTMLInputElement).value)\"\n    >\n\u003C\u002Ftemplate>\n","BaseInput.vue",[177,261,258],{"__ignoreMap":57},[81,263,264],{},[13,265,266,267],{},"想亲自查看完整示例吗？",[17,268,271],{"href":269,"rel":270},"https:\u002F\u002Fgithub.com\u002Falexanderop\u002Ftodo-app-example",[21],"点击我",[273,274,275,292],"table",{},[276,277,278],"thead",{},[279,280,281,286,289],"tr",{},[282,283,285],"th",{"align":284},"left","组件级别",[282,287,288],{"align":284},"工作",[282,290,291],{"align":284},"示例",[293,294,295,319,341,363,384],"tbody",{},[279,296,297,301,304],{},[298,299,300],"td",{"align":284},"原子",[298,302,303],{"align":284},"纯净的单一用途组件",[298,305,306,309,310,309,313,309,316],{"align":284},[177,307,308],{"code":308},"BaseButton"," ",[177,311,312],{"code":312},"BaseInput",[177,314,315],{"code":315},"BaseIcon",[177,317,318],{"code":318},"BaseText",[279,320,321,324,327],{},[298,322,323],{"align":284},"分子",[298,325,326],{"align":284},"具有最小逻辑的原子组合",[298,328,329,309,332,309,335,309,338],{"align":284},[177,330,331],{"code":331},"SearchBar",[177,333,334],{"code":334},"LoginForm",[177,336,337],{"code":337},"StarRating",[177,339,340],{"code":340},"Tooltip",[279,342,343,346,349],{},[298,344,345],{"align":284},"生物体",[298,347,348],{"align":284},"更大的、独立的、可重用的组件。可以执行副作用和复杂的作。",[298,350,351,309,354,309,357,309,360],{"align":284},[177,352,353],{"code":353},"TheHeader",[177,355,356],{"code":356},"ProductCard",[177,358,359],{"code":359},"CommentSection",[177,361,362],{"code":362},"NavigationMenu",[279,364,365,367,370],{},[298,366,200],{"align":284},[298,368,369],{"align":284},"定义页面结构的 Nuxt 布局",[298,371,372,309,375,309,378,309,381],{"align":284},[177,373,374],{"code":374},"DefaultLayout",[177,376,377],{"code":377},"BlogLayout",[177,379,380],{"code":380},"DashboardLayout",[177,382,383],{"code":383},"AuthLayout",[279,385,386,388,391],{},[298,387,209],{"align":284},[298,389,390],{"align":284},"处理数据和 API 调用的组件",[298,392,393,309,396,309,399,309,402],{"align":284},[177,394,395],{"code":395},"HomePage",[177,397,398],{"code":398},"UserProfile",[177,400,401],{"code":401},"ProductList",[177,403,404],{"code":404},"CheckoutPage",[27,406,407],{"id":407},"摘要",[13,409,410,411,415],{},"原子设计提供了一条通往更明显的代码结构的途径。它可以很好地作为许多项目的起点。但随着复杂性的增加，其他架构可能会更好地为您服务。想要探索更多选项？阅读我的帖子 ",[17,412,414],{"href":413},"\u002Fposts\u002F2025\u002Fhow-to-structure-vue-projects","如何选择 Vue 项目架构",".它涵盖了 Atomic Design 之外的方法，当您的项目超出其初始结构时。",[417,418],"link-banner",{"banner":419,"description":420,"link":413,"title":421},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F67aef11ea579d-78.webp","这篇文章涵盖了适合不同项目规模的各种 Vue 项目结构","【翻译】如何选择 Vue 项目架构",{"title":57,"searchDepth":423,"depth":423,"links":424},4,[425,427,428,442],{"id":29,"depth":426,"text":29},2,{"id":51,"depth":426,"text":51},{"id":157,"depth":426,"text":158,"children":429},[430,432],{"id":162,"depth":431,"text":163},3,{"id":184,"depth":431,"text":184,"children":433},[434],{"id":196,"depth":423,"text":196,"children":435},[436,438,439,440,441],{"id":200,"depth":437,"text":200},5,{"id":209,"depth":437,"text":209},{"id":218,"depth":437,"text":219},{"id":229,"depth":437,"text":230},{"id":246,"depth":437,"text":247},{"id":407,"depth":426,"text":407},[444],"译","2025-02-14 15:43:50","清晰的写作需要清晰的思考。对于编码也是有效的。启动个人项目时，将所有组件扔入一个文件夹中可能会起作用。但是随着项目的增长，尤其是在较大的团队中，这种方法会导致问题：",false,"md","https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F67aef51e90a66-78.webp",{"aside":451},[452,453],"toc","auth-alex",true,"\u002Fposts\u002F2025\u002Fatomic-design-vue-or-nuxt",null,{"text":458,"minutes":459,"time":460,"words":461},"8 min read",7.47,448200,1494,{"title":5,"description":446},{"loc":455,"images":464},[465,466],{"loc":58},{"loc":168},"posts\u002F2025\u002Fatomic-design-vue-or-nuxt",[469,470],"Vue","Nuxt","2026-02-07 23:23:50","kM-Hs6vyBnCsyqZ92BX-VcGNNyyalr2bcBW_677jafg",[474,477],{"title":421,"path":413,"stem":475,"date":476,"type":456,"children":-1},"posts\u002F2025\u002Fhow-to-structure-vue-projects","2025-02-14 14:18:31",{"title":478,"path":479,"stem":480,"date":481,"type":482,"children":-1},"近日小结【随机犯病篇】","\u002Fposts\u002F2025\u002Frecent-summary","posts\u002F2025\u002Frecent-summary","2025-02-19 12:37:18","story",1789642590157]