mcpField
D
dido-frontend-nuxt

dido-frontend-nuxt

github.com/didomarchet开发064 次安装今天更新
代码仓库

关于 dido-frontend-nuxt

面向 Dido Nuxt 4 前端项目的编码规范技能,供 Claude Code 在实现与维护界面时参考。内容涵盖 Vue、SCSS、组件、国际化和配置规则,并以 Dido 的 Nuxt starter kit 为实现参照。

主要功能

  1. 01Nuxt 前端编码约定
  2. 02Vue 与组件组织规则
  3. 03SCSS 与设计系统规范
  4. 04国际化和配置指南
  5. 05Figma 设计稿核对流程

适用场景

  1. 01基于 Dido starter kit 开发网站
  2. 02按一致规则编写 Vue 组件
  3. 03将 Figma 设计转为响应式界面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/didomarchet/dido-skills --skill dido-frontend-nuxt

安装后在 Claude Code 中输入/dido-frontend-nuxt确认已加载。

附带文件riferimento/chiamate-al-backend.mdriferimento/skill-di-terzi.md

详细介绍

Dido 的 Nuxt 前端

Dido Nuxt 前端的实现方法。所需技能: dido-code-style,其中包含适用于所有技术栈的原则。这里只介绍前端特有的内容。

参考实现: Dido 的 Nuxt 4 脚手架(GitHub 上的 DidoMarchet/starter-kit-nuxt-webapp)。新网站从这里开始,并在这里查阅 composable、插件和配置的代码。脚手架的相关信息(已接入的内容、模块、渲染方式、SCSS 系统)见其 docs/project-architecture.md 和 docs/scss-system.md。

对于基于脚手架创建的项目,机器可检查的规则都由 lint 检查(eslint.starter.mjs、stylelint.starter.mjs):Claude Code 每写完一个文件后检查一次,每次提交前再检查一次。如果项目(其 CLAUDE.md 或文档,而非代码)中的说明与此技能不同,以项目为准,并在报告中指出差异,因为其中一个来源需要修正。

你准备处理的内容阅读
Dido 后端调用:登录、令牌、错误riferimento/chiamate-al-backend.md
admin(使用 Tailwind 的 SPA)文末的“Admin”章节
非 dido-* 技能(设计、GSAP、DatoCMS、Vue、FormKit……)riferimento/skill-di-terzi.md

原则

  • 使用设计系统进行组合。 使用 utility 类控制 margin 和 padding,使用 token 设置 gap,使用行(.row-*)控制宽度。复用已有的排版样式、按钮和卡片,不要自定义间距。
  • 务实使用 token。 Token 是以编号类形式公开的 SCSS 变量,并非正式的设计 token 系统。只使用一次的值直接写在使用处,重复出现的值才作为 token。
  • 规范数值。 使用整数或 5 的倍数,包括布局尺寸;字体大小使用 2 或 4 的倍数。如果项目有 Figma,数值以 Figma 为准,不要凭空设定,但要取整:不属于比例尺 token 或 5 的倍数的值,取两者中更接近的值(67 → 65,17 → 16)。此技能中的规则优先于布局,因此由此造成的与设计稿差异不算错误。
  • 以 Figma 为准。 如果有 Figma,编写代码前先通过 MCP 查看(结构、数值、正确节点的截图),并测量内容框、gap 和 padding:不要凭记忆估算。
  • 文本不要紧贴线条。 文本、标题或 CTA 不得紧贴可见线条(背景网格线、垂直分隔线);即使 Figma 中与线条对齐,也必须与线条之间留出最小 padding,并使用项目中的 token,通过类来应用。图片可以贴着线条。
  • 降级处理,不要崩溃。 缺失的数据或配置应产生可控状态(回退值、提示消息、隐藏相应区块),绝不能导致页面报错。但页面的主要内容除外:如果 CMS 无响应,页面应返回 500;如果内容不存在,则返回 404。在 SSR 中,读取可能缺失的对象属性会导致整个页面失败,因此必须在读取处提供安全值(config?.chiave ?? default)。

JS 和 Vue

  • 使用箭头函数、完整名称,不要使用 console.log:参见 dido-code-style 中的“代码形式”。
  • 始终使用 <script setup>,不要使用 Options API。Nuxt 根据路径命名组件(components/widget/media-text/index.vue 对应 WidgetMediaText),因此不要使用 defineOptions({ name })。
  • 操作 DOM、滚动和动画的副作用放在 composable(app/composables/、useX)中,由它公开状态和操作,不包含标记。纯逻辑放在 app/utils/ 中,并为其编写测试。查询和调用放在专用目录中,绝不要写在组件里。
  • 模板中不要写静态文本:品牌名称从配置(siteName)读取,标签使用 i18n,内容来自 CMS。

命名

  • 文件和文件夹始终使用 kebab-case(widget/media-text)。模板中的标签保留 PascalCase(<WidgetMediaText>),因为自动导入会拼接路径。
  • Composable 使用 useX,工具函数使用动作名称(buildTrack、isLightHex),插件则在需要时添加环境后缀(*.client.js)。
  • BEM 类名:块 .componente、__elemento、--modificatore。变体按区块或品牌命名,绝不按颜色命名(--footer,而不是 --dark)。
  • 只使用一套编号的排版比例(.title-1…4、.text-1…3),绝不同时使用两套命名。
  • 颜色按系列分级($black-1、$grey-2),不使用语义化名称。仅在需要时使用 ID(锚点、aria-*、JS),并采用 kebab-case。大写通过 text-transform 实现,绝不直接写在文本中。

组件

  • 每个组件都放在 components/<area>/<nome>/ 文件夹中,包含 index.vue、style.scss 及其资源,并按领域分组(article/、navigation/)。
  • widget/ 中只放通用且可复用的组件:通过 prop 接收所有内容,并提供 slot。重复出现的模式(如轮播)只作为一个 widget 存在一次。特定区块的组件留在其所属区域。
  • 同类元素在整个网站中行为一致:重复出现的模式只实现一次,绝不让某个页面拥有行为各异的控件。所有轮播和可滚动的行都以相同方式拖动、滚动并响应方向键,无论其中包含什么内容;所有卡片、下划线、悬停效果、淡入淡出和图像切换都以相同方式动效呈现,使用相同的缓动和按下状态。组件文件只包含属于它自身的内容:重复出现的内容(行为、悬停效果、下划线、手形光标、动效值)只在负责实现它的 widget、composable、mixin 或全局类中定义一次,组件只需使用它。
  • .vue 中的顺序:<template>、<script setup>、<style lang="scss" src="./style.scss"></style>。样式不使用 scoped。
  • 组件只使用自身块的类和设计系统中的类,并通过 prop、事件、slot 或 store 与其他组件通信,绝不通过查找其他组件的 DOM 来通信。样式与所属组件一同存在:移除组件后,或在没有该组件的页面上,其类也会消失。
  • 永久资源放在 app/assets/img/ 中,使用描述性且利于 SEO 的 kebab-case 名称(<azienda>-logo.svg);占位资源单独放在一个文件夹中。在 <img> 中,路径放在 src 里;只有当 URL 以 JS 结尾时才使用 import。图标使用 SVG 图片,并通过第二个文件或 mask 重新着色。

SCSS

  • 每个 style.scss 和每个 partial 都以工具箱开头,它提供 token 和 mixin,但不会输出 CSS:@use "~/assets/styles/scss/core" as *;。它也会通过 additionalData 引入,但写在文件顶部能让文件更易读。
  • core 没有 @forward 的 token,在使用它的文件中就不存在:vars/ 或 mixins/ 中的新文件必须添加到对应的 _index.scss。
  • 使用 slamp(min, max) 实现流体缩放。视口范围在每个项目中配置一次,只有使用不同范围时才显式传入。
  • 字体和较大的间距始终使用流体缩放:字号阶梯中的标题、组件中字号从 40px 起的字体,以及从 80px 起的 margin、padding、gap、top 和 bottom。最大值取自 Figma 桌面稿,最小值取自 Figma 移动稿中的同一元素。如果没有移动稿,估算最小值(约为一半,再取整)。仅通过 react("large") 改变的值,改为在两个值之间使用 slamp。正文当前字号和较小的间距 token 保持固定。标题的负间距使用 em,这样会随字号缩小。
  • 控件和卡片从移动端到桌面端一起缩放:使用少量共享的流体 token(控件高度和文字、卡片内边距和标题、CTA 高度),取值采用 Figma 中 1920px 画板上的值,并供所有组件使用。不能让某个组件仍保持桌面尺寸,而相邻组件已经缩小。
  • 除非 Dido 有特别指示,否则左右边距和内边距绝不使用固定值:页面边距使用 slamp 从移动端缩放到桌面端,并使用项目 token(例如 $side-margin: slamp($grid-gutter, $grid-margin));Figma 桌面稿中的固定宽度列在桌面端以下要收窄。若固定使用 1920px 的值,在 1024px 时边距和侧边栏会挤占内容空间。
  • 媒体查询使用 @include react("chiave")(large、<medium),绝不手写 >=。悬停样式始终放在 react("pointer-and-hover") 内,避免在触屏设备上保持悬停状态。
  • 每个可点击元素都要有悬停效果,并采用网站的过渡效果(时长使用 token,缓动使用网站设定):绝不瞬间切换,即使 Figma 中如此设计也不例外。操作文本(“关闭”、“筛选”、链接)都使用项目统一的动画下划线,即使原本就有下划线也一样(这种情况下,下划线会重新绘制);悬停时文本颜色加深一档。图标按钮使用更深的色调或背景,实心按钮使用同色系的更深色调。按下时(:active)也应用相同状态,这是触屏上的触碰反馈:项目 mixin 同时定义悬停和按下状态,实心按钮轻微缩小(0.97),并在减少动态效果时保持静止。默认情况下:不设置 -webkit-tap-highlight-color;链接和按钮设置 touch-action: manipulation;按钮文本不可选中。
  • 与背景融合(mix-blend-mode)的图像绝不对自身或其容器应用动画 opacity、filter 或 transform:浏览器为过渡创建的新图层会让图像连同白色背景一起闪烁。要让图像显现,在其上方淡入一层背景色薄纱,只为颜色设置动画。图标通过 mask 和 currentcolor 改变颜色,不使用 filter。
  • 标题旁的操作文本(“关闭”、“确认”)字号比标题小一级,并与标题基线对齐,避免与标题混淆。不同列中同一行的标题(名称和旁边的第一张卡片)共用基线,即使其中一个使用流体缩放也一样。
  • 通过按钮执行操作后的确认信息(链接已复制)应为附着在该按钮上的工具提示,尖端指向按钮,而不是远处的提示。
  • 面板、抽屉或模态框每次重新打开时都要回到初始状态:滚动到顶部,并重置之前的浏览状态(最简单的做法是每次打开时使用新的 :key 重新挂载)。
  • 移动端的卡片选项应以全宽垂直列表呈现,而不是横向滚动的一排:在手机上横向滚动会让人看不清自己在选什么。
  • 使用类,而非 mixin:通过对比例尺执行 @each 生成编号类,并放在正确的 layer 中(layout/、typo/)。mixin 仅用于响应式辅助功能和交互行为,例如链接的动画下划线。
  • 使用类设置外边距和内边距,在组件样式中使用 token 设置 gap,使用 .row-* 设置宽度。全页背景应作为一个带有 row 的 section 实现。
  • 动效统一使用网站的缓动效果,时长使用 vars/_timings.scss 中的 $speed-* token。

国际化

  • 文本通过 $t 处理,模板中绝不使用按语言判断的三元表达式。
  • 词典按区域拆分:每种语言一个目录,每个区域一个文件;语言入口负责组合模块,不包含字符串。导入区域文件时需要使用别名 ~~,因为使用相对路径时,@nuxtjs/i18n v10 不会加载这些文件。
  • 各语言的内容必须保持一致:新增字符串时,要在所有语言中添加相同的键,并放在相同区域。单个文件应在第一个分区处拆分,而不是更晚。
  • 本地化 slug 放在 i18n 配置(pages)中。每个页面都必须在那里声明,即使两种语言的 slug 相同也不例外,否则页面不会进入站点地图。对于动态 slug,应按语言设置参数,这样 hreflang 和 canonical 才会指向正确的 slug。

配置

  • nuxt.config.ts 只负责展开 configuration/ 中按区域划分的配置片段,修改设置时应在对应的片段中进行。修改配置片段不会导致开发服务器重启。
  • 环境变量和密钥通过从 process.env 读取的 runtimeConfig 获取,绝不写入组件中。runtimeConfig.public 会进入客户端 bundle,因此其中只能包含本质上公开的值(例如只读 CMS token)。
  • robots 和 noindex 取决于环境:只有生产环境会被编入索引。

数据与第三方服务

  • 只能通过 useGraphql() 读取 CMS,并将查询和 fragment 放在 app/graphql/<cms>/ 中。内部使用 Nuxt 的 useAsyncData 和 @datocms/cda-client 的 executeQuery。只有 useGraphql() 和服务端数据源会导入 CMS 客户端。REST 请求通过 useApiFetch() 发起。页面和组件绝不直接调用 CMS 客户端或 HTTP 客户端。
  • CMS 出错不意味着页面不存在:页面应返回 500,只有内容不存在时才返回 404。
  • 在页面中使用 useSeo() 设置 SEO 和 hreflang。使用本地化 slug 时,必须使用 generateSeoLocaleParams 和 setI18nParams,否则 alternate 会声明不存在的 URL。
  • useLenis() 在整个应用中只初始化一次,放在布局或 app.vue 中。每个独立滚动的容器(模态框的内容区、抽屉、下拉菜单、筛选面板)都应使用自己的 Lenis 实现与页面相同的平滑滚动,并接入 GSAP 的 ticker。容器只能有一个子元素,因为 Lenis 会在第一个子元素尺寸变化时重新计算可滚动距离。Lenis 只会在自己的容器内查找 data-lenis-prevent,从上方另一个已打开的模态框传来的事件不得使其滚动。
  • 服务于整个站点的第三方服务(分析、错误跟踪、用户同意)应使用插件。服务于单个页面或组件的第三方服务(表单、预订小组件、播放器)应使用 composable 处理效果,并使用组件处理标记。
  • 第三方库只能由其对应的插件或 composable 导入;如果没有对应配置,它就不起作用。这样,只需删除这些文件、依赖项和使用位置,就能移除该第三方服务。如果它需要从站点接收数据,例如错误跟踪所需的 GraphQL 错误,应由它挂接到 hook(graphql:error,useGraphql() 会在发生错误时调用该 hook,或 vue:error)。
  • 嵌入内容应采用设计系统的样式:不使用服务自带的 CSS,而是使用自己的按钮类。如果服务通过 <iframe> 渲染,则无法为其设置样式,因此在承诺支持之前必须先确认。
  • 在说明第三方服务的功能之前,应测量它会联系哪些域名、写入哪些 cookie。
  • 服务端动态数据源(例如站点地图)应在服务端使用自己的客户端调用服务,不使用 useGraphql();按服务要求分页,并确保失败时不会导致构建失败。包含详情页的内容模型应通过查询每种语言来加入站点地图。
  • CMS 架构只能通过经过版本控制并在沙箱环境中测试的 migration 修改:绝不手动修改,沙箱环境也不例外。

动效

  • 动画使用 GSAP:进入和退出、模态框、底部面板和抽屉、下拉菜单、标签页、滚动时的显现、序列和微交互。只有按钮和链接的悬停与按压效果(颜色、背景、下划线、按压时的缩放)保留在 CSS 中。
  • 按组件制作动画。重复出现的动效只定义一次,放在 composable(useXMotion)或组件中,其他组件复用它,而不是各自编写时间轴。模态框是一个统一组件,使用 GSAP 实现进入和退出,供底部面板、抽屉和搜索使用。两个内容之间的淡入淡出由一个统一包装器处理:一个使用 GSAP 钩子的 <Transition :css="false">。
  • 组件的动画在 gsap.context() 内创建,并以组件元素为作用域;组件卸载时通过 revert() 清理。动画只在客户端运行,从 onMounted 开始。
  • 打开和关闭使用同一条时间轴,通过 play() 和 reverse() 控制,键盘触发关闭(Esc)时也一样:如果在打开到一半时关闭,就从当前位置反向播放,而不是从头开始。过渡中的所有部分(清空的页头和显现的搜索框)都放在同一条时间轴中,绝不一半用 CSS、一半用 GSAP。
  • 全站只使用一种缓动曲线,时长使用 token。只有与滚动相关的动效和连续循环(跑马灯、加载指示器)使用线性缓动。固定定位使用 position: sticky,不用 JS。
  • 缓动曲线、时长和 stagger 只定义在 SCSS token(vars/_timings.scss 中的 $ease-site、$speed-*、$stagger)里,JS 通过 SCSS 模块读取它们:绝不在 JS 中重复写数字。一个客户端插件负责注册 GSAP 插件,并通过 gsap.defaults 一次性设置全站缓动曲线和基础时长。
  • 跟随滚动的动效(视差、文字点亮、页头隐藏)使用与 Lenis 连接的 ScrollTrigger,以便在同一帧更新。由程序控制的滚动(锚点、“返回顶部”)通过 lenis.scrollTo 实现,绝不用 window.scrollTo 或第二套滚动系统。惯性使用 GSAP 实现。
  • 弹出层或下拉菜单从其触发按钮处展开(transformOrigin 朝向按钮)。首次显示工具提示后,只要指针仍停留在原处,后续工具提示就立即显示。可拖动的元素(底部面板、轮播)根据手势速度关闭或滚动,并在边缘提供阻力:使用 GSAP 的 Draggable 和 InertiaPlugin。
  • 每个动画都以屏幕刷新率运行(每秒 60 或 120 帧),不掉帧:
    • 只通过 transform 和 opacity(x、y、scale、rotation、autoAlpha)移动和缩放,绝不用 width、top、margin 或 padding。只有在下方内容需要移动时(手风琴)才动画改变高度,并且只作用于较小的区块。只有在无法使用 transform 时,才在小面积表面上使用 clip-path 和 filter;
    • 对于运动过程中不会改变的值(transformPerspective、transformOrigin),要先用 gsap.set 固定:在 to() 中设置这些值时,GSAP 会从零开始对它们做动画,导致形状在最初几帧发生跳变;
    • GSAP 在主线程上运行,因此动画期间主线程要保持空闲。开始前先挂载并测量内容(先完成所有读取,再进行写入),图片提前加载,Vue 不要在同一时刻重新绘制大型内容;
    • 只在移动的表面上使用 will-change,对多个相同元素使用 token 中的 stagger,用 gsap.quickTo 处理跟随指针的值;
    • 通过减慢 GSAP(gsap.globalTimeline.timeScale(0.1))并逐帧检查来验证:相邻帧之间形状不会跳变。
  • 模态框的遮罩层与表面放在同一条时间轴上,因为 GSAP 无法对 ::backdrop 做动画,而它会保持透明。底部面板或抽屉滑入时不做淡入淡出,且 <dialog> 要设置 overflow: clip:否则,表面从外部进入时会出现它自己的滚动条。不要使用 hidden,因为代码仍可滚动隐藏内容:showModal() 将焦点设在内容上,而内容会随着表面一起停留在屏幕外,导致 dialog 滚动到焦点所在位置;表面一边由 GSAP 移动,一边会直接出现在最终位置。
  • 横跨屏幕移动的元素(底部面板、抽屉、面板)的时长根据移动距离确定:使用一个参考 token(例如 $speed-medium 对应 1000px),按距离的平方根计算,并限制在两个 token 时长之间。手机上更短,桌面上更长。
  • 替换其他内容显示的内容(标签页、使用 v-show 的面板)通过淡入淡出包装器淡入,绝不突然切换。
  • 进入动画中的模态框表面要设置 will-change: transform:如果浏览器在动画结束时移除其合成层,那么第一次重绘(第一次悬停)时文字和图片就会闪烁。
  • 图片库中的图片都放在同一个框内,彼此叠放,且只有当前图片可见:切换时,下一张图片已经加载。始终使用 NuxtImg。
  • 始终遵循 prefers-reduced-motion:不做缩放或位移动画,最多使用淡入淡出。要在动画开始时读取该设置,因为它可能在浏览过程中发生变化。
  • 绝不在悬停时缩放图片:最多显示遮罩层或文字。

无障碍访问与 SEO

  • 装饰性图片使用 alt="",信息性图片使用描述图片内容的 alt。文件名应考虑 SEO;在适当的地方使用 loading="lazy" 和 decoding="async"。
  • 每个页面只使用一个 <h1>,标题按顺序排列,并使用语义化标记和地标。
  • 对比度至少达到 WCAG AA(4.5:1,大号文本为 3:1)。未达标的配色组合应明确标出,不要默默选用。
  • 使用 :focus-visible 显示焦点,并使其区别于 :focus。
  • 异步操作完成后,将焦点移至消息上(tabindex="-1" 和 .focus()),以播报操作结果。只有在内容变化前就已存在于 DOM 中的实时区域才有效。
  • <noscript> 中只能包含文本,只能进行一次插值,CSS 中不得使用 display。启用 JavaScript 时,浏览器会将其读作文本;其中的标签会导致水合不匹配。
  • 图片按宽度调整尺寸,绝不设置固定高度(使用 block-size: auto,或使用经过专门选择的 object-fit 容器)。

验证

除 dido-code-style 的要求外:

  • 修改过的文件通过 lint,JS 和 CSS 均无问题;
  • 修改过的页面返回 200,控制台和日志中无错误,包括水合不匹配;
  • 修改过的 util 测试通过;
  • 将修改部分中的每个元素与网站上的同类元素进行比较(轮播与轮播、卡片与卡片、链接与链接):组件和行为一致,包括拖动、悬停、按压和过渡效果;
  • 使用项目的设计技能(impeccable、design-taste-frontend、mobile-native,如有)在 375、768、1024、1440 和 1920 的视口宽度下检查修改过的界面:内容易读、布局均衡、使用顺畅。仅仅没有显示异常还不够;
  • 如果修改了第三方服务,则测量域名和 Cookie;
  • 新增第三方服务时,即使没有配置其环境变量,页面也应返回 200。

管理后台

由 starterkit-backend-admin 创建的管理后台(SPA,ssr: false)遵循此技能,但技术栈不同的部分除外:

  • 样式:使用 Tailwind、app/assets/styles/main.css 中的 @theme 令牌以及 docs/design-system.md 中的模式,而不是 SCSS、slamp 和 react。颜色使用角色名称(--color-ink、--color-surface),字体使用 title-1…2、text-1…3 的字号阶梯。类中不得写入颜色或尺寸;尺寸问题由 lint 标出。
  • 配置:只使用一个 nuxt.config.ts,不使用 configuration/ 中的切片。
  • 动效:按照 docs/design-system.md 使用带有 motion-reduce 的 Tailwind 过渡效果。不要使用 GSAP。
  • 文本:只有管理后台支持多种语言时才使用 i18n;仅支持一种语言时,文本直接写在模板中。
  • 与网站保持一致:代码风格、<script setup>、代码块顺序、kebab-case 命名的文件和文件夹、组件单独放在一个文件夹中,以及仅使用 useApi() 调用后端(riferimento/chiamate-al-backend.md)。登录和密码重置除外:它们使用 useApiUrl() 直接 fetch;此时,401 表示凭据错误,而不是会话过期。

评论

支持 Markdown