跳到主要内容

Vue 模板规范

BBC 8.0 页面主要编写 Vue 3 单文件组件,不再以手写完整 HTML 页面为主要开发方式。模板应保持语义清晰、可访问,并让 Prettier 负责缩进和属性换行。

单文件组件结构

新组件优先使用 <script setup lang="ts">

<script setup lang="ts">
import { computed } from "vue";
import { Button as TButton } from "tdesign-vue-next";
import { useTranslate } from "@shop-tnt/components/useTranslate";

const props = defineProps<{ disabled?: boolean }>();
const emit = defineEmits<{ submit: [] }>();
const t = useTranslate();
const canSubmit = computed(() => !props.disabled);
</script>

<template>
<section class="example-form">
<h1>{{ t("示例表单") }}</h1>
<TButton type="button" :disabled="!canSubmit" @click="emit('submit')">
{{ t("提交") }}
</TButton>
</section>
</template>

<style lang="scss" scoped>
.example-form {
color: var(--shop-color-text-primary);
}
</style>

组件结构可以按文件实际需要省略 style,但不要把业务请求和状态初始化写进模板表达式。

语义与层级

  • 页面主体使用 mainsectionheadernav 等合适元素。
  • 标题按信息层级使用 h1h6,不要用 div 加粗模拟标题。
  • 可点击操作使用 button 或路由链接,不用无键盘行为的 div @click
  • 普通按钮明确 type="button",避免在表单中意外提交。
  • 列表使用 ul/olli;表格数据使用表格组件或语义化 table。
  • 仅为布局需要时再增加容器,避免无意义的多层 wrapper。

属性与指令

  • 静态属性使用双引号。
  • 动态值使用 :,事件使用 @,不拼接大段 HTML 字符串。
  • prop 在脚本中使用 camelCase,在模板中使用 kebab-case。
  • 一个元素属性较多时交给 Prettier 换行,不手工对齐空格。
  • v-if 用于真正创建或销毁内容;频繁切换且需要保留状态时再考虑 v-show
<GoodsPicker
v-model:visible="pickerVisible"
owner-type="admin"
type="sku"
:default-data="selectedSkuIds"
:limit="10"
@confirm="handleGoodsConfirm"
/>

列表渲染

v-for 必须使用稳定业务主键:

<li v-for="goods in goodsList" :key="goods.goods_id">
{{ goods.goods_name }}
</li>

不要使用数组下标作为可增删、排序或异步刷新的列表 key。后端 Long ID 应按字符串保留,不能转成可能失真的 Number 后再作为 key。

不要在同一元素同时使用 v-ifv-for。先用 computed 过滤数据,或把条件放到内部元素。

表单

  • TFormItemname 与表单数据字段一致。
  • label、placeholder、校验提示和按钮文案都通过翻译入口获取。
  • 文本输入选择合适的 autocompleteinputmode 和最大长度。
  • 金额用 MoneyInput,带单位数值用 UnitNumberInput,不要退回普通文本框。
  • 提交按钮绑定 loading/disabled,防止重复提交。
  • 验证结果用 isFormValidateSuccess() 统一判断。
<TFormItem :label="t('金额')" name="amount">
<MoneyInput v-model="form.amount" :max="99999999" />
</TFormItem>

TDesign Dialog 默认保留子树。含动态规则、change 规则或异步回显的表单,重新打开时必须重建表单会话;不能依赖关闭弹窗自动销毁验证状态,也不能用多层 nextTicksetTimeout 掩盖竞态。

文案与国际化

用户可见的标题、按钮、表单、提示、状态和空数据文案使用 useTranslate()

<span>{{ t('已选择 {v0} 项', { v0: selectedRows.length }) }}</span>

需要变量的句子使用完整带参数文案,不通过字符串拼接拆散语序。后端业务编码、固定协议值和仅开发者可见的日志不强制翻译。

富文本

禁止在业务页面直接使用 v-html 展示未知内容。需要显示后端富文本时使用公共 SafeHtml

<SafeHtml :html="article.content" />

即使使用公共组件,也要确认内容来源和清洗边界。不要把 token、个人信息或未验证的 URL 拼入富文本。

可访问性

  • 图片必须提供有意义的 alt;纯装饰图片使用空 alt。
  • 图标按钮必须有可读的 titlearia-label
  • 异步状态需要在必要位置使用 aria-live,但不要让高频变化制造语音噪音。
  • 键盘用户应能完成打开、选择、提交、关闭等核心流程。
  • 表单错误要与具体字段关联,不能只在页面顶部显示一条模糊提示。
  • 新窗口链接应明确目的;使用 target="_blank" 时补充安全的 rel

条件与权限

模板中的 v-if 只能控制显示,不是安全边界。权限、功能开关和业务状态还必须在路由或请求层验证,避免用户通过直接 URL 或手工请求绕过。

不推荐写法

<!-- 不要用 div 模拟按钮。 -->
<div @click="save">保存</div>

<!-- 不要使用不稳定下标。 -->
<div v-for="(item, index) in items" :key="index">{{ item.name }}</div>

<!-- 不要直接渲染未知 HTML。 -->
<div v-html="content"></div>

检查

pnpm lint
pnpm typecheck

只改单个应用时执行对应 pnpm build:dev:<端>;修改公共模板组件时执行 pnpm build:dev。浏览器中还要检查键盘操作、焦点、表单错误、空数据、长文本和明暗主题。