mcpField
B
blade-conventions

blade-conventions

github.com/bagisto开发1360 次安装2 周前更新
代码仓库

关于 blade-conventions

blade-conventions 是用于创建和编辑 Bagisto Blade 视图的编码技能,涵盖组件用法、Blade 与 Vue 绑定规则、包命名空间和格式规范,帮助新模板符合代码库惯例。

主要功能

  1. 01说明 Blade 组件模式
  2. 02解释 Blade 与 Vue 绑定
  3. 03涵盖包命名空间和翻译
  4. 04介绍表单与扩展钩子
  5. 05规定模板格式规范

适用场景

  1. 01创建 Admin 或 Shop 视图
  2. 02构建可复用的 Blade 组件
  3. 03连接 Vue 组件与表单
  4. 04使自定义模块符合格式惯例

安装

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

$npx skills add https://github.com/bagisto/agent-skills --skill blade-conventions

安装后在 Claude Code 中输入/blade-conventions确认已加载。

详细介绍

Bagisto Blade 约定

概览

Bagisto 的 UI 是 Blade 模板,大量使用 Blade 组件 和内联的 Vue 3 层。新视图必须与周围代码风格一致:采用相同的组件惯例、相同的 : 与 :: 绑定规则,以及相同的缩进和属性格式。本指南总结了这些约定,确保生成的 Blade 与代码库完全一致。

所有包都遵循相同的约定——Admin、Shop 以及任何自定义的 Webkul 风格模块。不同包之间只有一组 命名空间标记 不同(x-<ns>:: 组件前缀、<ns>:: 翻译命名空间和 view_render_event 名称);详见下文的“各包命名空间”。如有疑问,请打开同一包中的相邻视图并参照其写法。

核心 UI 位于 packages/Webkul/Admin/src/Resources/views/ 和 packages/Webkul/Shop/src/Resources/views/ 下;其他包(包括你自己的包)也会在各自的 src/Resources/views/ 下采用相同的目录结构。每个提供组件的包都有一个 components/example.blade.php,用于演示组件用法——请将其视为权威参考。

各包命名空间(Admin、Shop、自定义模块)

包的标记来自其 ServiceProvider::boot() 中的两个调用:

// Registers the `<ns>::` view + translation namespace  →  view('<ns>::…'), @lang('<ns>::…')
$this->loadViewsFrom(__DIR__.'/../Resources/views', '<ns>');

// Registers the `x-<ns>::` anonymous component prefix   →  <x-<ns>::button />
Blade::anonymousComponentPath(__DIR__.'/../Resources/views/components', '<ns>');

因此,Admin 注册 admin(→ x-admin::、@lang('admin::…')),Shop 注册 shop,而你自己的包(例如 RMA)注册 rma(→ x-rma::、@lang('rma::…'))。

在 Admin/Shop 之外开发时,这意味着:

  • 可自由复用共享组件。 x-admin:: 和 x-shop:: 组件/布局已全局注册,因此 RMA 管理页面仍使用 <x-admin::layouts> 包裹页面,并使用 <x-admin::datagrid>、<x-admin::form.control-group.*>、<x-admin::modal> 等组件。面向客户的页面则使用 <x-shop::layouts> 和 Shop 组件。无需重新实现这些组件。
  • 仅为自己的新组件添加包命名空间前缀: <x-rma::return-request-card>。
  • 翻译使用自己的命名空间: @lang('rma::app.return-requests.index.title'),对应的键位于 packages/Webkul/RMA/src/Resources/lang/ 下。
  • view_render_event 名称遵循包/区域命名规则,例如 bagisto.rma.return-requests.list.before(参照该包中相邻事件的命名方式)。
  • 其他一切保持不变: :/:: 绑定规则、Vue <v-x> + x-template 模式、@props、格式、ACL 和脚本堆叠方式都原样适用。

切换包时可快速参照以下替换表:

标记AdminShop你的包(示例:RMA)
自有组件前缀x-admin::x-shop::x-rma::
翻译命名空间admin::app.…shop::app.…rma::app.…
外层布局x-admin::layoutsx-shop::layouts复用 x-admin::/x-shop:: 布局
事件前缀bagisto.admin.…bagisto.shop.…bagisto.rma.…

适用场景

以下情况请启用本指南:

  • 在 Admin 或 Shop 包下创建或编辑任何 .blade.php 文件
  • 构建可复用的 Blade 组件(匿名组件或 Vue 驱动组件)
  • 配置表单、数据网格、模态框、抽屉、标签页或布局
  • 遵循项目的属性绑定、缩进和空行风格

目录与组件结构

Admin 和 Shop 的目录结构相互对应。页面位于功能文件夹下(catalog/、sales/、checkout/、customers/ 等);共享组件位于 components/ 下。两者常见的组件包括:accordion, button, datagrid, drawer, dropdown, form, layouts, media, modal, quantity-changer, shimmer, table, tabs, tinymce。

命名空间调用形式:<x-admin::name> / <x-shop::name>;嵌套组件使用点号:<x-admin::form.control-group.control>、<x-admin::charts.bar>。

组件调用与数据绑定(最重要的规则)

三种不同的属性形式——请谨慎选择:

语法解析为用于示例
attr="text"静态字符串字面值name="quantity"
:attr="expr"Blade/PHP 表达式PHP 值、路由、trans()、old():src="route('admin.sales.orders.index')"
::attr="expr"将 : 转义为 → Vue 的字面量 :attr传入 Vue 组件的数据::value="item?.quantity"、::labels="chartLabels"

::(双冒号)是 Blade 对单个 : 的转义,因此渲染后的 HTML 中会包含供 Vue 在运行时绑定的 :attr="expr"。正确区分 : 和 :: 是最常见的错误来源。

:: 仅适用于 Blade 组件标签——<x-admin::…>、<x-shop::…>、<x-marketplace::…>。Blade 在编译组件时会去掉多余的冒号。普通自定义元素(<v-quantity-changer>、<v-field>,以及你自己编写的任何 <v-…>)会作为字面量 HTML 原样传递,因此 ::attr 会以两个冒号的形式到达浏览器,Vue 无法识别它是绑定,属性也就不会传入。对于普通的 <v-…> 元素,请使用单个冒号:

<x-admin::quantity-changer ::value="item.qty" />   {{-- component  → renders :value  --}}

<v-quantity-changer :value="item.qty"></v-quantity-changer>   {{-- plain element → write : --}}

这种故障不会报错,很容易被误判:属性值为 undefined,因此任何遍历它的计算属性都会在渲染时抛出错误,组件会停留在上一次绘制的内容——通常是加载动画。如果某个 <v-…> 组件一直显示占位内容,请先检查冒号。

命名插槽使用 <x-slot:name> … </x-slot>:

<x-admin::drawer>
    <x-slot:toggle>Toggle</x-slot>
    <x-slot:content>Body</x-slot>
</x-admin::drawer>

两种组件类型

1. 匿名 Blade 组件(@props + $attributes)
@props([
    'isActive' => false,
    'position' => 'right',
])

<div {{ $attributes->merge(['class' => 'box-shadow rounded bg-white dark:bg-gray-900']) }}>
    {{ $slot }}
</div>
  • 使用 @props([...]) 声明输入。
  • 使用 $attributes->merge([...]) 转发额外属性。
  • 使用 {{ $slot }} 接收默认插槽,使用 {{ $toggle }} 等接收命名插槽。
2. Vue 驱动的组件(主要模式)

一个精简的自定义元素包装器 + 内联 x-template + 在全局 app 上注册:

@props([
    'name'  => '',
    'value' => 1,
])

<v-quantity-changer
    {{ $attributes->merge(['class' => 'flex items-center']) }}
    name="{{ $name }}"
    value="{{ $value }}"
>
</v-quantity-changer>

@pushOnce('scripts')
    <script
        type="text/x-template"
        id="v-quantity-changer-template"
    >
        <div>
            <span
                class="icon-minus cursor-pointer"
                role="button"
                @click="decrease"
            ></span>

            <p>@{{ quantity }}</p>
        </div>
    </script>

    <script type="module">
        app.component("v-quantity-changer", {
            template: '#v-quantity-changer-template',

            props: ['name', 'value'],

            data() {
                return {
                    quantity: this.value,
                };
            },

            methods: {
                decrease() {
                    this.$emit('change', --this.quantity);
                },
            },
        });
    </script>
@endPushOnce

此模式的规则:

  • 包装元素为 <v-name>;模板 ID 为 #v-name-template。
  • 使用 app.component("v-name", { template: '#v-name-template', ... }) 注册。
  • 使用 @pushOnce('scripts') … @endPushOnce 包裹脚本(布局会渲染 @stack('scripts'),因此无论组件使用多少次,该代码块都只会输出一次)。
  • 使用 @{{ expr }} 输出 Vue 原样的大括号表达式,避免 Blade 尝试渲染它们。
  • 通过 ::attr(Vue 绑定)或 attr="{{ $php }}"(服务端值)传入数据。

页面骨架

<x-admin::layouts>
    <x-slot:title>
        @lang('admin::app.catalog.attributes.index.title')
    </x-slot>

    <div class="flex items-center justify-between">
        <p class="text-xl font-bold text-gray-800 dark:text-white">
            @lang('admin::app.catalog.attributes.index.title')
        </p>

        @if (bouncer()->hasPermission('catalog.attributes.create'))
            <a href="{{ route('admin.catalog.attributes.create') }}">
                <div class="primary-button">
                    @lang('admin::app.catalog.attributes.index.create-btn')
                </div>
            </a>
        @endif
    </div>

    {!! view_render_event('bagisto.admin.catalog.attributes.list.before') !!}

    <x-admin::datagrid :src="route('admin.catalog.attributes.index')" />

    {!! view_render_event('bagisto.admin.catalog.attributes.list.after') !!}
</x-admin::layouts>

商城页面使用 <x-shop::layouts>,并可通过 @push('meta') 添加 SEO 标签,或通过 @inject('helper', '...') 注入视图辅助工具。

通用惯用模式

  • 可扩展性钩子 — 使用 {!! view_render_event('bagisto.<area>.<path>.before') !!} 和 .after 将有意义的内容括起来。以点分隔的名称遵循视图路径。大多数页面中都有这些钩子。
  • 翻译 — 切勿将界面文本硬编码。使用 @lang('admin::app.…') / @lang('shop::app.…') 或 trans('…'),并始终使用包命名空间。添加键时,请将其加入 Resources/lang/ 下的所有语言区域文件。
  • ACL(仅限管理后台) — 使用 @if (bouncer()->hasPermission('resource.action')) 对创建/编辑/删除按钮和数据网格操作进行权限控制。
  • 表单 — <x-admin::form :action="route(...)" method="POST"> 会包装一个 VeeValidate v-form。使用以下控件组组合构建字段:
    <x-admin::form.control-group>
        <x-admin::form.control-group.label class="required">
            @lang('...')
        </x-admin::form.control-group.label>
    
        <x-admin::form.control-group.control
            type="text"
            name="admin_name"
            rules="required"
            :value="old('admin_name')"
            :label="trans('...')"
        />
    
        <x-admin::form.control-group.error control-name="admin_name" />
    </x-admin::form.control-group>
    
    验证在客户端进行;错误组件上的 control-name 必须与字段的 name 匹配。
  • 数据网格 — <x-admin::datagrid :src="route('…')" />。列、筛选器和操作在 PHP DataGrid 类中定义;Blade 标签只需指向 JSON 端点。
  • Blade ↔ Vue 转义 — @{{ vueVar }} 会输出字面量 Vue 大括号表达式;将代码块放在 v-pre 中,可避免 Blade 处理其中的 @/{{ }}。
  • 主题 / 国际化外壳 — 布局会设置 class="… dark …" 和 dir="{{ core()->getCurrentLocale()->direction }}";使用 Tailwind 的 dark: 和 ltr:/rtl: 变体来适配主题和 RTL 布局。

格式与样式规则

这些规则在 .blade.php 中手动执行(Pint 不会格式化 Blade),因此请严格遵守。

缩进

  • 使用 4 个空格,不用制表符。每增加一层嵌套,统一增加 4 个空格。

属性布局

  • 属性超过一个 → 每行一个属性,每个属性相对于标签缩进 +4 个空格,闭合 >(或 />)单独占一行,并与开头的 <tag 对齐:
    <x-admin::form.control-group.control
        type="text"
        name="admin_name"
        :value="old('admin_name')"
    />
    
  • 单个属性 → 放在行内:<x-admin::form.control-group.error control-name="admin_name" />。
  • 属性行必须连续排列——同一个标签内的属性之间不能有空行。
  • 属性顺序可灵活安排:控制流/Vue 指令(v-if、v-else)→ class / :class → 普通属性和 aria-* 属性 → 事件处理器(@click、@change)放在最后。

@props — 对齐 =>

@props([
    'name'      => '',
    'value'     => 1,
    'minValue'  => 1,
    'removable' => false,
])

调整键的填充空格,使 => 箭头对齐。@props 代码块是供读者像查看表格一样浏览的声明,因此这里是唯一值得对齐的地方。

@php … @endphp 中的 PHP — 手动遵循 Pint 规则

在 @php 代码块中编写的是纯 PHP,因此必须严格遵循 vendor/bin/pint 格式化 .php 文件时的输出——最明显的是,=> 两侧各有一个空格,绝不通过填充空格来对齐:

@php
    $steps = [
        'label' => trans($prefix.'.source'),
        'hint' => trans($prefix.'.source-hint'),
        'validate' => ['file'],
    ];
@endphp

Pint 不会格式化 .blade.php 文件,因此没有任何机制会强制执行此规则——必须手动遵守。若不确定某个结构的格式,请在临时 .php 文件中编写,运行 Pint,再复制结果。Pint 输出的其他部分也同样适用:空格、运算符、尾随逗号和短闭包。

@php 之外的内容——@props、指令参数、行内 {{ }} 表达式——都属于 Blade 自身的层级,并遵循本页的 Blade 规则。

空行

  • 同级代码块/元素之间,以及 view_render_event 钩子周围,各留一个空行。
  • @props([...]) 代码块后、标记之前留一个空行。

指令大小写 / 命名

  • 标准的 push 代码块:@pushOnce('scripts') … @endPushOnce(注意 @endPushOnce 中的 P 大写)。'styles' 很少用于 CSS。

注释

package-development 技能中的两条规则也适用于 Blade,且看似相互矛盾——两者都要遵循:

  • “每个方法都要有文档块”——<script> 代码块中的 Vue method 或 computed 都是方法。应添加 /** … */,用首字母大写且以句号结尾的句子描述其功能。@php 代码块中的任何函数也同样如此。
  • “只注释代码本身无法说明的内容”——在方法体内部和标记中,默认不写注释。仅说明局部代码中不可见的约束、变通方案,或看似可删除却不可或缺的代码行;不要重复说明标记或调用的含义。

以下是各层应使用的语法。

每种注释语法都属于其自身的层级——根据注释所在的位置选择语法,然后遵循该层级的大小写和标点规则。

  • Blade {{-- --}} — 用于 Blade/PHP 层的注释(文件头、@php 逻辑、控制流说明)。永远不会发送到浏览器。

    • 如果注释是句子(或多个句子),请将其写成散文,并以大写字母开头、以标点符号结尾。多行文件头应说明视图是什么以及原因:
      {{--
          The friendly "What kind of product?" create funnel (stepped scratch page only). It maps a
          plain kind + a "Does it have variations?" answer to a Bagisto product type behind the scenes.
      --}}
      
    • 如果是简短标签/标题(不是句子),使用标题式大小写,末尾不加句号:{{-- Media --}}、{{-- Type-Specific Controls --}}。
    • 不要放在 @php … @endphp 内。 Blade 不会剥离其中的 Blade 注释,而是将其原样复制到编译后的 PHP 中,从而导致解析错误。在 @php 块内编写的是普通 PHP,因此应使用 PHP 注释语法及相应的 PHP 规范;如果需要说明整个代码块,请在代码块上方添加 {{-- --}} 文件头:
      {{-- Everything the wizard needs to describe its steps. --}}
      @php
          // The upload-size config is stored in MB; VeeValidate's size rule expects KB.
          $maxUploadKb = $maxUploadMb * 1024;
      @endphp
      
  • HTML `` — 用作 x-template / 标记内部的分节符。保持简短:使用标题式大小写的标签或自然的问题——与该代码块渲染的标题保持一致。标签末尾不加句号;问题末尾可以加 ?:

    
    
    
    
  • JS /** … */(JSDoc 块) — 位于 <script> 内。这是 Vue 层唯一使用的注释形式——不要使用 // 行注释。每个 computed/method 都要添加一条,以大写字母开头并以句号结尾。方法内部的语句只有在确实难以理解时才需要注释:

    /**
     * The axis checkboxes only appear for a variable kind, once the seller has said "yes"
     * and picked a family.
     */
    showAxes() {
        return this.isVariable && this.hasVariations === true && !! this.familyId;
    }
    

    即使是简单的一行代码也要添加文档块——但只写一句话即可,不要对已经清晰易懂的语句堆叠额外注释。

  • CSS /** … */(JSDoc 块) — 位于 <style> 内,形式与 JS 相同:说明规则存在的原因(尤其是 :checked 驱动的状态或隐藏滚动条等不明显的规则),并写成带标点的句子:

    /**
     * Hide the navigation radios; they only drive the :checked state.
     */
    #rma-steps .rma-step-radio { … }
    

经验法则:句子 → 首字母大写并加标点;纯标题/标签 → 标题式大小写,不加句号。 在 <script>/<style> 中始终使用 /** … */ 块形式,绝不使用 // 或裸露的 /* */。

配方:新建 Vue 组件

  1. 创建 views/components/<name>/index.blade.php。
  2. 使用 @props([...]) 定义服务端输入(=> 对齐)。
  3. 渲染 <v-<name> {{ $attributes->merge([...]) }} …>,通过 attr="{{ $php }}" 传递数据。
  4. 使用 @pushOnce('scripts'):添加 <script type="text/x-template" id="v-<name>-template"> 和 <script type="module"> app.component("v-<name>", { template: '#v-<name>-template', props, data, computed, methods }) </script>;以 @endPushOnce 结束。
  5. 使用 @{{ … }} 输出运行时值;通过 ::attr 绑定父级数据。
  6. 按 <x-{admin|shop}::<name> ::value="…" @change="…" /> 的形式使用。

配方:新建页面

  1. 使用 <x-admin::layouts> / <x-shop::layouts> 包裹,并添加 <x-slot:title>。
  2. 添加标题行(标题 + 通过 bouncer() 控制权限的操作按钮)。
  3. 使用 view_render_event 的 .before/.after 事件包围主要内容。
  4. 所有字符串都使用带有包命名空间的 @lang/trans。

应做 / 不应做

  • 应复用现有的 <x-{admin|shop}::…> 组件和插槽;不要手写组件已有的标记。
  • 对 Vue 层使用的内容,应使用 ::;对 PHP 值,应使用 :。
  • 在 @props 块中,应对齐 =>;不要在 @php … @endphp 或 .php 文件中对齐——那是纯 PHP,因此适用 Pint 的单空格规则。
  • 应使用 @pushOnce('scripts') … @endPushOnce 包裹组件脚本。
  • 应通过 @lang/trans 为所有字符串添加命名空间,并将新键添加到每种语言环境中。
  • 不要在标签的属性之间插入空行;应在同级块之间插入空行。
  • 应使用 bouncer()->hasPermission(...) 控制后台操作的访问权限。

评论

支持 Markdown