公共组件
跨端公共组件位于 packages/components,公开入口为 @shop-tnt/components。只被一个应用使用的组件应留在对应 apps/<app>/src/components,不要为了“可能复用”提前提升到共享包。
导入方式
业务页面从包根入口按名称导入:
import {
MoneyInput,
PageActionBar,
TableLayout,
useFeedback,
} from "@shop-tnt/components";
Vite 构建插件会把公共 Vue 组件的根入口导入改写为对应组件文件,保持开发体验的同时避免整包 SFC 样式进入产物。不要绕过公开入口复制组件,也不要从另一个 app 的内部路径导入。
Admin、Seller 的 apps/<app>/src/components/index.ts 只导出端内组件,不再转发 PageActionBar 等共享能力。业务页面需要共享组件时必须直接从 @shop-tnt/components 导入,避免同一个组件出现包入口和端内入口两套用法。
少数明确的子路径能力按现有入口使用,例如:
import { useTranslate } from "@shop-tnt/components/useTranslate";
import AppShell from "@shop-tnt/components/layouts/AppShell.vue";
业务页面通常不应直接使用 AppShell;它由 Admin/Seller 根路由挂载。
当前公开组件
以下清单以 packages/components/index.ts 的实际导出为准。
后台页面骨架
| 组件 | 用途 |
|---|---|
TableLayout | 后台表格、工具栏、筛选、分页和抽屉的统一容器 |
TableSearch | 列表搜索与筛选,统一即时/手动触发行为 |
TableTips | 列表页提示信息 |
BackendTabs | Admin/Seller 统一的顶部页签 |
PageActionBar | 二级页返回、保存及附加主操作 |
表单与输入
| 组件 | 用途 |
|---|---|
MoneyInput | 金额输入,默认两位小数、最小值 0 |
UnitNumberInput | 带单位的数值输入,默认整数、最小值 0 |
UploadField | 统一上传字段 |
BehaviorCaptchaDialog | 行为验证码弹窗 |
业务选择器
| 组件 | 用途 |
|---|---|
ActivityPicker | 活动选择 |
BrandPicker | 品牌选择 |
CategoryPicker | 商品分类选择 |
CouponPicker | 优惠券选择 |
GoodsPicker | Admin/Seller 商品或 SKU 选择 |
MemberPicker | 会员选择 |
RegionPicker | 地区级联选择 |
ShopPicker | 店铺选择 |
展示、任务与辅助
| 组件 | 用途 |
|---|---|
Clipboard | 文本展示与复制 |
ExportButton | 导出任务入口 |
GoodsInfoCell | 商品信息单元格 |
ImportTaskDialog | 导入任务弹窗 |
MemberPreview | 会员摘要预览 |
SafeHtml | 受控富文本展示 |
ShopDetailPreview | 店铺详情预览 |
TableImagePreview | 表格图片预览 |
TaskRecordList | 导入导出等后台任务记录 |
反馈与表单工具
公共入口还提供:
useFeedback()、useOptionalUiFeedback():消息、确认框和反馈服务。createRequiredTextRule()、createRequiredChangeRule():常用必填规则。isFormValidateSuccess():统一判断 TDesign 表单验证结果。useBehaviorCaptchaGate():行为验证码门禁。useExportTaskProgressPrompt()等导出任务辅助。
import {
createRequiredTextRule,
isFormValidateSuccess,
useFeedback,
} from "@shop-tnt/components";
const feedback = useFeedback();
const rules = {
name: [createRequiredTextRule("请输入名称")],
};
async function save() {
const result = await formRef.value?.validate();
if (!result || !isFormValidateSuccess(result)) return;
await submit();
feedback.success("保存成功");
}
用户可见文案仍应通过 useTranslate() 获取;示例中的固定字符串应在真实页面替换为翻译结果。
后台页面示例
<script setup lang="ts">
import { ref } from "vue";
import { PageActionBar, TableLayout, TableSearch } from "@shop-tnt/components";
import { usePageBack } from "@shop-tnt/core";
const keyword = ref("");
const rows = ref([]);
const loading = ref(false);
const columns = [{ colKey: "name", title: "名称" }];
const { goBack } = usePageBack();
function search() {
// 调用当前端 API 并更新 rows。
}
</script>
<template>
<TableLayout :columns="columns" :table-data="rows" :loading="loading">
<template #search>
<TableSearch v-model:keyword="keyword" @search="search" />
</template>
</TableLayout>
<PageActionBar :show-save="false" @back="goBack" />
</template>
关键使用契约
TableLayout
- Admin/Seller 列表优先用它承载表格。
- 默认保留字段完整内容,并由表格横向滚动承载总宽度。
- 不要为普通列批量开启
ellipsis;订单号、流水号等标识符应完整展示。 - 可拖拽列宽或明确使用固定布局的表格,应在页面中显式处理列宽和换行。
PageActionBar
- 表单、审核、详情和长内容二级页优先使用。
- 只有返回动作时传
:show-save="false"。 - 保存、提交、审核等主操作通过
save-text和@save表达。 - 页面返回使用
usePageBack().goBack();不要手工添加底部 padding、空节点或第二套 fixed footer。
BackendTabs
- Admin/Seller 不直接使用 TDesign
Tabs构造另一套页签外观。 - 只展示页签头、内容渲染在组件外时使用
header-only。 - 不要覆盖
.t-tabs__header或.t-tabs__nav-*等内部结构样式。
MoneyInput 与 UnitNumberInput
- 金额字段使用
MoneyInput,带“人、小时、次、张”等单位的数值字段使用UnitNumberInput。 min: null表示明确取消默认下限;不要用字符串技巧绕过数值约束。- 精度和取值范围必须来自业务契约,不能只依赖组件默认值。
SafeHtml
展示后端富文本时使用 SafeHtml,不要在业务页面直接使用 v-html。富文本仍需确认来源与清洗策略,组件复用不等于可以信任任意输入。
新增公共组件
新增前先检索 packages/components 和三个 app 的端内组件。确认至少两个应用真实复用后:
- 在
packages/components/<ComponentName>/index.vue实现组件。 - 使用明确的 props、emits 和类型,不通过全局变量传递业务状态。
- 从
packages/components/index.ts增加命名导出。 - 组件不能导入任何
apps/*内部文件。 - 用户文案接入翻译,反馈使用公共服务。
- 样式使用公共主题变量,并同时检查 Admin/Seller 明暗主题。
如果只是单端业务组件,应留在 app 内;第二个真实使用方出现后再评估提升,避免共享包承载单端业务细节。
验证
修改公共组件至少执行:
pnpm typecheck
pnpm build:dev
交互组件还应在每个实际使用端验证:
- props、v-model 和 emits 的双向行为。
- Loading、禁用、空数据、错误和重复点击状态。
- 对话框关闭后重新打开的表单校验生命周期。
- 键盘操作、焦点、替代文字和必要的 ARIA 信息。
- 明暗主题、窄视口、长文本和表格横向滚动。
- 页面卸载后没有遗留请求、定时器、监听器或固定底栏占位。