跳到主要内容

公共组件

跨端公共组件位于 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列表页提示信息
BackendTabsAdmin/Seller 统一的顶部页签
PageActionBar二级页返回、保存及附加主操作

表单与输入

组件用途
MoneyInput金额输入,默认两位小数、最小值 0
UnitNumberInput带单位的数值输入,默认整数、最小值 0
UploadField统一上传字段
BehaviorCaptchaDialog行为验证码弹窗

业务选择器

组件用途
ActivityPicker活动选择
BrandPicker品牌选择
CategoryPicker商品分类选择
CouponPicker优惠券选择
GoodsPickerAdmin/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 的端内组件。确认至少两个应用真实复用后:

  1. packages/components/<ComponentName>/index.vue 实现组件。
  2. 使用明确的 props、emits 和类型,不通过全局变量传递业务状态。
  3. packages/components/index.ts 增加命名导出。
  4. 组件不能导入任何 apps/* 内部文件。
  5. 用户文案接入翻译,反馈使用公共服务。
  6. 样式使用公共主题变量,并同时检查 Admin/Seller 明暗主题。

如果只是单端业务组件,应留在 app 内;第二个真实使用方出现后再评估提升,避免共享包承载单端业务细节。

验证

修改公共组件至少执行:

pnpm typecheck
pnpm build:dev

交互组件还应在每个实际使用端验证:

  • props、v-model 和 emits 的双向行为。
  • Loading、禁用、空数据、错误和重复点击状态。
  • 对话框关闭后重新打开的表单校验生命周期。
  • 键盘操作、焦点、替代文字和必要的 ARIA 信息。
  • 明暗主题、窄视口、长文本和表格横向滚动。
  • 页面卸载后没有遗留请求、定时器、监听器或固定底栏占位。