Files
telegram-management-system/frontend-vben/apps/web-antd/docs/COMPONENTS.md
你的用户名 237c7802e5
Some checks failed
Deploy / deploy (push) Has been cancelled
Initial commit: Telegram Management System
Full-stack web application for Telegram management
- Frontend: Vue 3 + Vben Admin
- Backend: NestJS
- Features: User management, group broadcast, statistics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 15:37:50 +08:00

23 KiB
Raw Blame History

组件使用文档

本文档详细介绍了 Telegram 营销管理系统中使用的各种组件及其使用方法。

目录

  1. 基础组件
  2. 业务组件
  3. 表单组件
  4. 表格组件
  5. 图表组件
  6. 上传组件
  7. 权限组件
  8. 国际化组件
  9. 最佳实践

基础组件

1. BasicTable 基础表格

基础表格组件,提供完整的数据展示和操作功能。

基本用法

<template>
  <BasicTable @register="registerTable" />
</template>

<script setup lang="ts">
import { BasicTable, useTable } from '@/components/Table';

const columns = [
  {
    title: '用户名',
    dataIndex: 'username',
    key: 'username',
  },
  {
    title: '邮箱',
    dataIndex: 'email',
    key: 'email',
  },
  {
    title: '状态',
    dataIndex: 'status',
    key: 'status',
    customRender: ({ record }) => {
      return record.status === 1 ? '启用' : '禁用';
    },
  },
];

const [registerTable] = useTable({
  title: '用户列表',
  api: getUserList,
  columns,
  showIndexColumn: true,
  showTableSetting: true,
  useSearchForm: true,
  formConfig: {
    labelWidth: 80,
    schemas: [
      {
        field: 'username',
        label: '用户名',
        component: 'Input',
        colProps: { span: 8 },
      },
    ],
  },
  actionColumn: {
    title: '操作',
    dataIndex: 'action',
    slots: { customRender: 'action' },
    fixed: 'right',
    width: 120,
  },
});
</script>

主要配置项

  • api: 数据请求接口函数
  • columns: 表格列定义
  • showIndexColumn: 是否显示序号列
  • showTableSetting: 是否显示表格设置
  • useSearchForm: 是否使用搜索表单
  • formConfig: 搜索表单配置
  • actionColumn: 操作列配置

2. BasicForm 基础表单

基础表单组件,支持动态表单生成和验证。

基本用法

<template>
  <BasicForm @register="register" @submit="handleSubmit" />
</template>

<script setup lang="ts">
import { BasicForm, useForm } from '@/components/Form';

const schemas = [
  {
    field: 'username',
    label: '用户名',
    component: 'Input',
    required: true,
    componentProps: {
      placeholder: '请输入用户名',
    },
  },
  {
    field: 'password',
    label: '密码',
    component: 'InputPassword',
    required: true,
    componentProps: {
      placeholder: '请输入密码',
    },
  },
  {
    field: 'email',
    label: '邮箱',
    component: 'Input',
    rules: [
      { required: true, message: '请输入邮箱' },
      { type: 'email', message: '邮箱格式不正确' },
    ],
  },
];

const [register, { validate, setFieldsValue }] = useForm({
  labelWidth: 100,
  schemas,
  showActionButtonGroup: true,
  submitButtonOptions: {
    text: '提交',
  },
});

async function handleSubmit(values: any) {
  try {
    await someApi(values);
    // 处理成功逻辑
  } catch (error) {
    // 处理错误
  }
}
</script>

支持的组件类型

  • Input: 输入框
  • InputPassword: 密码输入框
  • InputNumber: 数字输入框
  • InputTextArea: 文本域
  • Select: 选择器
  • CheckboxGroup: 多选框组
  • RadioButtonGroup: 单选按钮组
  • DatePicker: 日期选择器
  • RangePicker: 日期范围选择器
  • Upload: 上传组件

3. BasicModal 基础弹窗

基础弹窗组件,提供完整的弹窗功能。

基本用法

<template>
  <BasicModal @register="registerModal" title="用户详情" @ok="handleSubmit">
    <BasicForm @register="registerForm" />
  </BasicModal>
</template>

<script setup lang="ts">
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';

const [registerForm, { validate, setFieldsValue }] = useForm({
  schemas: [
    {
      field: 'username',
      label: '用户名',
      component: 'Input',
      required: true,
    },
  ],
});

const [registerModal, { setModalProps, closeModal }] = useModalInner(
  async (data) => {
    // 接收父组件传入的数据
    if (data.record) {
      setFieldsValue(data.record);
    }
  },
);

async function handleSubmit() {
  try {
    const values = await validate();
    await updateUser(values);
    closeModal();
  } catch (error) {
    console.error(error);
  }
}
</script>

业务组件

1. AccountSelector 账号选择器

用于选择 Telegram 账号的业务组件。

基本用法

<template>
  <AccountSelector v-model:value="selectedAccounts" multiple />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { AccountSelector } from '@/components/Business';

const selectedAccounts = ref<string[]>([]);
</script>

属性配置

  • value: 选中的账号ID
  • multiple: 是否支持多选
  • disabled: 是否禁用
  • placeholder: 占位文本

2. GroupSelector 群组选择器

用于选择 Telegram 群组的业务组件。

基本用法

<template>
  <GroupSelector
    v-model:value="selectedGroups"
    :account-id="accountId"
    show-member-count
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { GroupSelector } from '@/components/Business';

const selectedGroups = ref<string[]>([]);
const accountId = ref('account-123');
</script>

3. MessageTemplate 消息模板

用于消息模板编辑和管理的组件。

基本用法

<template>
  <MessageTemplate
    v-model:content="messageContent"
    :variables="templateVariables"
    @preview="handlePreview"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { MessageTemplate } from '@/components/Business';

const messageContent = ref('');
const templateVariables = ref([
  { key: 'name', label: '姓名', default: '用户' },
  { key: 'product', label: '产品', default: '产品名称' },
]);

function handlePreview(content: string) {
  console.log('预览内容:', content);
}
</script>

表单组件

1. 高级搜索表单

用于复杂条件搜索的表单组件。

<template>
  <div class="search-form-container">
    <BasicForm @register="registerSearchForm" />
  </div>
</template>

<script setup lang="ts">
import { BasicForm, useForm } from '@/components/Form';

const searchSchemas = [
  {
    field: 'keyword',
    label: '关键字',
    component: 'Input',
    componentProps: {
      placeholder: '请输入搜索关键字',
    },
    colProps: { span: 8 },
  },
  {
    field: 'status',
    label: '状态',
    component: 'Select',
    componentProps: {
      options: [
        { label: '全部', value: '' },
        { label: '启用', value: 1 },
        { label: '禁用', value: 0 },
      ],
    },
    colProps: { span: 8 },
  },
  {
    field: 'dateRange',
    label: '时间范围',
    component: 'RangePicker',
    componentProps: {
      format: 'YYYY-MM-DD',
      valueFormat: 'YYYY-MM-DD',
    },
    colProps: { span: 8 },
  },
];

const [registerSearchForm, { getFieldsValue, resetFields }] = useForm({
  labelWidth: 80,
  schemas: searchSchemas,
  showActionButtonGroup: true,
  submitButtonOptions: {
    text: '搜索',
  },
  resetButtonOptions: {
    text: '重置',
  },
  autoSubmitOnEnter: true,
});
</script>

2. 动态表单

支持动态添加和删除字段的表单。

<template>
  <BasicForm @register="registerDynamicForm" />
</template>

<script setup lang="ts">
import { reactive } from 'vue';
import { BasicForm, useForm } from '@/components/Form';

const dynamicSchemas = reactive([
  {
    field: 'contacts',
    label: '联系人列表',
    component: 'Input',
    slot: 'contactsSlot',
  },
]);

const [registerDynamicForm] = useForm({
  schemas: dynamicSchemas,
  showActionButtonGroup: false,
});

// 动态添加联系人字段
function addContact() {
  const index = Math.random().toString(36).substr(2, 9);
  dynamicSchemas.push({
    field: `contact_${index}`,
    label: `联系人${dynamicSchemas.length}`,
    component: 'Input',
    componentProps: {
      placeholder: '请输入联系人信息',
    },
  });
}
</script>

表格组件

1. 可编辑表格

支持行内编辑的表格组件。

<template>
  <BasicTable @register="registerEditTable">
    <template #bodyCell="{ column, record, index }">
      <template v-if="column.dataIndex === 'name'">
        <Input
          v-if="editingKey === record.key"
          v-model:value="record.name"
          @pressEnter="save(record.key)"
        />
        <span v-else>{{ record.name }}</span>
      </template>

      <template v-if="column.dataIndex === 'action'">
        <span v-if="editingKey === record.key">
          <Button type="link" @click="save(record.key)">保存</Button>
          <Button type="link" @click="cancel">取消</Button>
        </span>
        <span v-else>
          <Button type="link" @click="edit(record.key)">编辑</Button>
        </span>
      </template>
    </template>
  </BasicTable>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { BasicTable, useTable } from '@/components/Table';
import { Button, Input } from 'ant-design-vue';

const editingKey = ref('');

const columns = [
  {
    title: '姓名',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: '操作',
    dataIndex: 'action',
    key: 'action',
  },
];

const [registerEditTable, { getDataSource, setTableData }] = useTable({
  columns,
  dataSource: [],
  showIndexColumn: false,
  pagination: false,
});

function edit(key: string) {
  editingKey.value = key;
}

function save(key: string) {
  editingKey.value = '';
  // 保存逻辑
}

function cancel() {
  editingKey.value = '';
  // 取消编辑,恢复原始数据
}
</script>

2. 树形表格

支持树形结构展示的表格。

<template>
  <BasicTable @register="registerTreeTable" />
</template>

<script setup lang="ts">
import { BasicTable, useTable } from '@/components/Table';

const treeColumns = [
  {
    title: '名称',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: '类型',
    dataIndex: 'type',
    key: 'type',
  },
];

const [registerTreeTable] = useTable({
  columns: treeColumns,
  api: getTreeData,
  isTreeTable: true,
  pagination: false,
  childrenColumnName: 'children',
  indentSize: 20,
});
</script>

图表组件

1. 基础图表

基于 ECharts 的图表组件封装。

<template>
  <div class="chart-container">
    <EChart :option="chartOption" :height="400" />
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import { EChart } from '@/components/Chart';

const chartData = ref([
  { name: '周一', value: 120 },
  { name: '周二', value: 200 },
  { name: '周三', value: 150 },
  { name: '周四', value: 80 },
  { name: '周五', value: 70 },
  { name: '周六', value: 110 },
  { name: '周日', value: 130 },
]);

const chartOption = computed(() => ({
  title: {
    text: '每日发送量统计',
    left: 'center',
  },
  tooltip: {
    trigger: 'axis',
  },
  xAxis: {
    type: 'category',
    data: chartData.value.map((item) => item.name),
  },
  yAxis: {
    type: 'value',
  },
  series: [
    {
      name: '发送量',
      type: 'line',
      data: chartData.value.map((item) => item.value),
      smooth: true,
    },
  ],
}));
</script>

2. 实时图表

支持实时数据更新的图表组件。

<template>
  <div class="realtime-chart">
    <EChart :option="realtimeOption" :height="300" />
    <div class="chart-controls">
      <Button @click="startRealtime">开始实时更新</Button>
      <Button @click="stopRealtime">停止更新</Button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onUnmounted } from 'vue';
import { EChart } from '@/components/Chart';
import { Button } from 'ant-design-vue';

const realtimeData = ref<number[]>([]);
const categories = ref<string[]>([]);
let intervalId: NodeJS.Timeout | null = null;

const realtimeOption = computed(() => ({
  xAxis: {
    type: 'category',
    data: categories.value,
  },
  yAxis: {
    type: 'value',
  },
  series: [
    {
      name: '实时数据',
      type: 'line',
      data: realtimeData.value,
      smooth: true,
    },
  ],
}));

function startRealtime() {
  if (intervalId) return;

  intervalId = setInterval(() => {
    const now = new Date();
    const timeStr = now.toLocaleTimeString();
    const value = Math.floor(Math.random() * 100);

    realtimeData.value.push(value);
    categories.value.push(timeStr);

    // 保持最近20个数据点
    if (realtimeData.value.length > 20) {
      realtimeData.value.shift();
      categories.value.shift();
    }
  }, 1000);
}

function stopRealtime() {
  if (intervalId) {
    clearInterval(intervalId);
    intervalId = null;
  }
}

onUnmounted(() => {
  stopRealtime();
});
</script>

上传组件

1. 基础文件上传

<template>
  <BasicUpload
    v-model:value="fileList"
    :api="uploadApi"
    :maxCount="5"
    :maxSize="10"
    accept=".jpg,.png,.pdf"
    @change="handleChange"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { BasicUpload } from '@/components/Upload';

const fileList = ref([]);

function handleChange(files: any[]) {
  console.log('文件列表变化:', files);
}

async function uploadApi(params: any) {
  // 自定义上传逻辑
  const formData = new FormData();
  formData.append('file', params.file);

  const response = await fetch('/api/upload', {
    method: 'POST',
    body: formData,
  });

  return response.json();
}
</script>

2. 图片上传预览

<template>
  <ImageUpload
    v-model:value="imageList"
    :preview="true"
    :multiple="true"
    :maxCount="10"
    @preview="handlePreview"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { ImageUpload } from '@/components/Upload';

const imageList = ref([]);

function handlePreview(file: any) {
  console.log('预览图片:', file);
}
</script>

3. 文件拖拽上传

<template>
  <DraggerUpload
    v-model:value="draggerFiles"
    :multiple="true"
    accept=".xlsx,.csv"
    @success="handleUploadSuccess"
  >
    <p class="ant-upload-drag-icon">
      <InboxOutlined />
    </p>
    <p class="ant-upload-text">点击或拖拽文件到此处上传</p>
    <p class="ant-upload-hint">支持单个或批量上传严禁上传敏感数据</p>
  </DraggerUpload>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { DraggerUpload } from '@/components/Upload';
import { InboxOutlined } from '@ant-design/icons-vue';

const draggerFiles = ref([]);

function handleUploadSuccess(response: any) {
  console.log('上传成功:', response);
}
</script>

权限组件

1. 权限指令

使用 v-permission 指令控制元素显示。

<template>
  <div>
    <!-- 单个权限 -->
    <Button v-permission="'user:create'" type="primary"> 新增用户 </Button>

    <!-- 多个权限满足任意一个 -->
    <Button v-permission="['user:edit', 'user:delete']" type="default">
      编辑用户
    </Button>

    <!-- 角色权限 -->
    <Button v-role="'admin'" danger> 删除用户 </Button>
  </div>
</template>

2. 权限组件

使用组件方式控制权限。

<template>
  <div>
    <Authority :value="'user:create'">
      <Button type="primary">新增用户</Button>
    </Authority>

    <Authority :value="['user:edit', 'user:delete']" mode="any">
      <Button type="default">编辑用户</Button>
    </Authority>

    <RoleAuthority :value="'admin'">
      <Button danger>管理员功能</Button>
    </RoleAuthority>
  </div>
</template>

<script setup lang="ts">
import { Authority, RoleAuthority } from '@/components/Authority';
import { Button } from 'ant-design-vue';
</script>

3. 权限路由守卫

// 在路由配置中使用权限
export const routes = [
  {
    path: '/user',
    component: UserLayout,
    meta: {
      title: '用户管理',
      permissions: ['user:view'], // 需要的权限
      roles: ['admin', 'user'], // 允许的角色
    },
    children: [
      {
        path: 'list',
        component: UserList,
        meta: {
          title: '用户列表',
          permissions: ['user:list'],
        },
      },
      {
        path: 'create',
        component: UserCreate,
        meta: {
          title: '新增用户',
          permissions: ['user:create'],
        },
      },
    ],
  },
];

国际化组件

1. 国际化文本

<template>
  <div>
    <!-- 基本用法 -->
    <h1>{{ $t('common.title') }}</h1>

    <!-- 带参数 -->
    <p>{{ $t('common.welcome', { name: userName }) }}</p>

    <!-- 使用组件 -->
    <I18nText keypath="common.description" />

    <!-- 复杂格式化 -->
    <I18nText keypath="common.count" :count="itemCount">
      <template #count>
        <strong>{{ itemCount }}</strong>
      </template>
    </I18nText>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { I18nText } from '@/components/I18n';

const userName = ref('张三');
const itemCount = ref(10);
</script>

2. 语言切换器

<template>
  <LocalePicker :reload="true" :showText="true" />
</template>

<script setup lang="ts">
import { LocalePicker } from '@/components/I18n';
</script>

3. 表单国际化

<template>
  <BasicForm @register="registerI18nForm" />
</template>

<script setup lang="ts">
import { BasicForm, useForm } from '@/components/Form';
import { useI18n } from 'vue-i18n';

const { t } = useI18n();

const i18nSchemas = [
  {
    field: 'name',
    label: t('form.name'),
    component: 'Input',
    componentProps: {
      placeholder: t('form.namePlaceholder'),
    },
    rules: [{ required: true, message: t('form.nameRequired') }],
  },
  {
    field: 'email',
    label: t('form.email'),
    component: 'Input',
    componentProps: {
      placeholder: t('form.emailPlaceholder'),
    },
    rules: [
      { required: true, message: t('form.emailRequired') },
      { type: 'email', message: t('form.emailInvalid') },
    ],
  },
];

const [registerI18nForm] = useForm({
  labelWidth: 100,
  schemas: i18nSchemas,
});
</script>

最佳实践

1. 组件开发规范

组件命名

  • 使用 PascalCase 命名组件
  • 组件文件名与组件名保持一致
  • 业务组件添加业务前缀
// ✅ 正确
export default defineComponent({
  name: 'UserAccountSelector',
  // ...
});

// ❌ 错误
export default defineComponent({
  name: 'userAccountSelector',
  // ...
});

Props 定义

  • 使用 TypeScript 类型定义
  • 提供默认值和验证
  • 添加注释说明
interface Props {
  /** 选中的值 */
  value?: string | string[];
  /** 是否支持多选 */
  multiple?: boolean;
  /** 是否禁用 */
  disabled?: boolean;
  /** 占位文本 */
  placeholder?: string;
  /** 最大选择数量 */
  maxCount?: number;
}

const props = withDefaults(defineProps<Props>(), {
  value: undefined,
  multiple: false,
  disabled: false,
  placeholder: '请选择',
  maxCount: 10,
});

事件定义

  • 使用 defineEmits 定义事件
  • 提供类型约束
  • 添加事件说明
interface Emits {
  /** 值变化事件 */
  (e: 'update:value', value: string | string[]): void;
  /** 选择变化事件 */
  (e: 'change', value: string | string[], option: any): void;
  /** 清空事件 */
  (e: 'clear'): void;
}

const emit = defineEmits<Emits>();

2. 组件使用最佳实践

性能优化

  • 合理使用 v-memo 缓存组件
  • 大列表使用虚拟滚动
  • 图表组件按需加载
<template>
  <!-- 使用 v-memo 缓存 -->
  <div v-memo="[user.id, user.status]">
    <UserCard :user="user" />
  </div>

  <!-- 虚拟滚动大列表 -->
  <VirtualList :items="userList" :item-height="60" :height="400">
    <template #item="{ item, index }">
      <UserItem :user="item" :index="index" />
    </template>
  </VirtualList>
</template>

错误处理

  • 组件内部处理异常
  • 提供错误边界组件
  • 用户友好的错误提示
<template>
  <ErrorBoundary>
    <Suspense>
      <template #default>
        <AsyncComponent />
      </template>
      <template #fallback>
        <Loading />
      </template>
    </Suspense>

    <template #error="{ error, retry }">
      <ErrorDisplay :error="error" @retry="retry" />
    </template>
  </ErrorBoundary>
</template>

样式管理

  • 使用 CSS Modules 或 Scoped CSS
  • 遵循 BEM 命名规范
  • 支持主题自定义
<template>
  <div :class="$style.container">
    <div :class="$style.header">
      <h3 :class="$style.title">{{ title }}</h3>
    </div>
    <div :class="$style.content">
      <slot />
    </div>
  </div>
</template>

<style module>
.container {
  border: 1px solid var(--border-color);
  border-radius: 4px;
}

.header {
  padding: 16px;
  border-bottom: 1px solid var(--border-color);
  background-color: var(--header-bg);
}

.title {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
}

.content {
  padding: 16px;
}
</style>

3. 测试建议

单元测试

  • 测试组件的输入输出
  • 测试用户交互
  • 测试边界情况
import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import UserSelector from '../UserSelector.vue';

describe('UserSelector', () => {
  it('should render correctly', () => {
    const wrapper = mount(UserSelector, {
      props: {
        value: ['user1', 'user2'],
        multiple: true,
      },
    });

    expect(wrapper.exists()).toBe(true);
    expect(wrapper.findAll('.user-item')).toHaveLength(2);
  });

  it('should emit change event when selection changes', async () => {
    const wrapper = mount(UserSelector);

    await wrapper.find('.user-option').trigger('click');

    expect(wrapper.emitted('change')).toBeTruthy();
    expect(wrapper.emitted('update:value')).toBeTruthy();
  });
});

组件文档

  • 使用 Storybook 展示组件
  • 提供使用示例
  • 记录 API 文档
// UserSelector.stories.ts
import type { Meta, StoryObj } from '@storybook/vue3';
import UserSelector from './UserSelector.vue';

const meta: Meta<typeof UserSelector> = {
  title: 'Components/UserSelector',
  component: UserSelector,
  parameters: {
    docs: {
      description: {
        component: '用户选择器组件,支持单选和多选模式',
      },
    },
  },
  argTypes: {
    multiple: {
      control: 'boolean',
      description: '是否支持多选',
    },
    disabled: {
      control: 'boolean',
      description: '是否禁用',
    },
  },
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
  args: {
    multiple: false,
    placeholder: '请选择用户',
  },
};

export const Multiple: Story = {
  args: {
    multiple: true,
    placeholder: '请选择多个用户',
  },
};

总结

本文档涵盖了系统中主要组件的使用方法和最佳实践。在实际开发中,建议:

  1. 遵循组件设计原则:单一职责、可复用、可测试
  2. 保持 API 一致性:统一的命名规范和参数格式
  3. 注重性能优化:合理使用缓存和懒加载
  4. 完善错误处理:提供友好的错误提示和恢复机制
  5. 编写完整文档包括使用示例、API 说明和最佳实践

如有疑问或需要添加新的组件说明,请联系开发团队。