# @stoly/vue-component

基于 **ant-design-vue 4.x** 封装的 Vue 3 业务组件库，提供表格查询、弹窗、Excel 导出等开箱即用的能力。

## 安装

```bash
pnpm add @stoly/vue-component
```

## 开发（本仓库）

```bash
pnpm install
pnpm dev
pnpm build
pnpm lint
```

### 安装 Agent Skill

本库附带 [Agent Skill](https://skills.sh/)，安装后 AI 编码助手（Cursor / Claude Code 等）能自动获得组件用法指引。

```bash
# 安装到当前项目（需先 pnpm add @stoly/vue-component）
pnpm dlx skills add ./node_modules/@stoly/vue-component

# 全局安装（跨项目可用）
pnpm dlx skills add ./node_modules/@stoly/vue-component -g
```

### 样式

引入组件时会自动加载样式（`index.js` 顶部含 `import './index.css'`，由消费方打包工具解析）：

```ts
import { VcTable, VcTableForm, VcModal } from '@stoly/vue-component'
```

若需手动控制样式加载，可额外使用：

```ts
import '@stoly/vue-component/style.css'
```

### Peer Dependencies

请确保项目中已安装以下依赖：

```json
{
  "@ant-design/icons-vue": ">=7.0.0",
  "@vueuse/core": ">=10",
  "ant-design-vue": "4.2.6",
  "vue": ">=3.2.0",
  "lodash-es": ">=4.17.21",
  "copy-to-clipboard": "^3.3.3",
  "exceljs": "^4.4.0",
  "scroll-into-view-if-needed": "^3.1.0"
}
```

## 快速开始

一个典型的「表格查询 + 弹窗」页面：

```vue
<script setup lang="ts">
import { App, Flex, Form, Input } from 'ant-design-vue'
import type { ColumnsType } from 'ant-design-vue/es/table'
import {
  VcModal,
  VcTable,
  VcTableForm,
  type VcBaseResult,
  type VcConfirmOptions,
  type VcQueryData,
} from '@stoly/vue-component'

interface Query extends VcQueryData {
  name?: string
}

interface Row {
  id: number
  name: string
}

const query = VcTableForm.useQuery<Query>()
const controller = VcTableForm.useController()
const modal = VcModal.useModal<{ id: number; name: string }>()

const columns: ColumnsType<Row> = [
  { title: '姓名', dataIndex: 'name', key: 'name' },
  {
    ...VcTable.OperateColumnProps,
    key: 'operate',
  },
]

const fetchList = async (params: Query): Promise<VcBaseResult<{ list: Row[]; total: number }>> => {
  // ...
  return { success: true, data: { list: [], total: 0 } }
}

const deleteApi = async (id: number) => {
  // ...
  return { success: true }
}
</script>

<template>
  <App>
    <VcTableForm :controller="controller" :query="query" layout="inline">
      <Flex :gap="16" wrap="wrap">
        <Form.Item label="姓名" name="name">
          <Input v-model:value="query.formState.name" placeholder="按姓名查询" allow-clear />
        </Form.Item>
      </Flex>
    </VcTableForm>

    <VcTable
      :query="query"
      :api="fetchList"
      :query-format="(q) => q"
      :data-format="(res) => ({ rows: res.data?.list ?? [], total: res.data?.total ?? 0 })"
      :columns="columns"
      show-index
      row-key="id"
    >
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'operate'">
          <a @click="modal.open({ id: record.id, name: record.name })">查看</a>
          <a
            @click="
              VcModal.confirm(
                { api: deleteApi, params: record.id, query } satisfies VcConfirmOptions<number, VcBaseResult<unknown>>,
              )
            "
          >
            删除
          </a>
        </template>
      </template>
    </VcTable>

    <VcModal
      :title="`详情 - ${modal.params?.name ?? ''}`"
      :open="modal.visible"
      full-screen-modal
      @cancel="modal.close"
      @ok="modal.close"
    >
      <p>当前选中：{{ modal.params }}</p>
    </VcModal>
  </App>
</template>
```

## 组件

### VcTable 数据表格

对 ant-design-vue Table 的增强封装，集成数据请求、分页、排序、右键菜单、列配置、密度切换等功能。

#### Props

> 继承 ant-design-vue `TableProps<T>`（移除 `dataSource` / `loading` / `components` / `onChange` / `rowSelection`）

| 属性 | 类型 | 必传 | 说明 |
|------|------|:----:|------|
| `query` | `SearchQuery<Q>` | ✅ | 查询条件，由 `VcTableForm.useQuery` 创建 |
| `api` | `(params: P) => Promise<BaseResult<R>>` | ✅ | 数据请求接口 |
| `queryFormat` | `(q: Q) => P` | ✅ | 查询条件 → 接口参数 |
| `dataFormat` | `(res) => { rows, total }` | ✅ | 接口返回值 → 表格数据 |
| `showIndex` | `boolean \| ColumnType<T>` | - | 显示序号列（可自定义列配置） |
| `exported` | `boolean` | - | 启用右键导出 Excel（仅适用于简单表头） |
| `onSelectedRows` | `(rows: T[]) => void` | - | 传入则启用行选择，翻页/查询会清空选择 |

#### Ref

```ts
const tableRef = ref<VcTableInstance>()
const titles = tableRef.value?.getColumns() // 当前展示列的 title 数组
```

#### 静态属性

- **`VcTable.OperateColumnProps`** — 操作列默认配置：`{ align: 'center', title: '操作', width: 120, fixed: 'right', className: 'noCopy' }`
- **`VcTable.Context`** — 通过 slot 获取当前表格 `{ rows, total }`

#### 列声明

支持 `columns` 数组，或通过 default slot 声明列（与 ant-design-vue `convertChildrenToColumns` 一致），配合 `#bodyCell` 自定义单元格。

#### 内置功能

- **右键菜单**：刷新、复制单元格、复制行、导出行/导出当前页（需开启 `exported`）
- **列配置**：齿轮按钮切换列可见性、拖拽排序
- **密度切换**：默认 / 紧凑
- **回到顶部**：滚动超过 100px 时显示

---

### VcTableForm 查询表单

与 `VcTable` 配合使用的查询表单，自动管理查询/重置/分页逻辑。

#### Props

> 继承 ant-design-vue `FormProps`（移除内置 form 绑定方式，由组件接管 `model`）

| 属性 | 类型 | 必传 | 说明 |
|------|------|:----:|------|
| `query` | `SearchQuery<T>` | ✅ | 查询条件 |
| `controller` | `TableFormController` | ✅ | 表单控制器 |
| `defaultShowMore` | `boolean` | - | 默认展开更多筛选 |

#### 插槽

- `default` — 表单项
- `customButtons` — 替换默认查询/重置按钮
- `extra` — 按钮区额外内容

#### 静态方法

```ts
const query = VcTableForm.useQuery<Query>(initValues?)
const controller = VcTableForm.useController()

controller.submit()
controller.clear(fields?)
```

#### formState 绑定

`useQuery` 返回的 `query.formState` 为响应式对象，表单项可直接 `v-model:value="query.formState.xxx"`，无需额外维护本地 state。

#### 自动展开/收起

当表单内容高度超过一行时，自动出现「高级筛选」展开/收起按钮。

---

### VcModal 可拖拽弹窗

对 ant-design-vue Modal 的增强，支持拖拽标题栏移动和全屏切换。

#### Props

> 继承 `ModalProps`（移除 `modalRender` / `wrapClassName` / `visible`）

| 属性 | 类型 | 必传 | 说明 |
|------|------|:----:|------|
| `fullScreenModal` | `boolean` | - | 显示全屏/退出全屏按钮 |
| `autoFullScreen` | `boolean` | - | 默认以全屏打开 |

#### 事件

| 事件 | 说明 |
|------|------|
| `fullScreenChange` | 全屏状态变化，模板：`@full-screen-change` |

#### VcModal.useModal

```ts
const modal = VcModal.useModal<{ id: number }>()

modal.open({ id: 1 })
modal.close()
modal.visible  // 是否可见
modal.params   // 当前参数
```

#### VcModal.confirm

确认操作弹窗，内置成功提示和表格刷新逻辑：

```ts
// 推荐：对象参数形式
VcModal.confirm(
  {
    api: deleteUser,
    params: { id: record.id },
    query,
    tableData: { rows, total },
    callback: (res) => {},
  },
  {
    title: '删除',
    content: '确认删除该用户？',
  },
)

// 兼容：多参数形式
VcModal.confirm(deleteUser, { id: record.id }, query)
VcModal.confirm(deleteUser, { id: record.id }, (res) => {})
```

类型 `VcConfirmOptions<P, R>` 可用于 `satisfies` 或变量注解。

#### 弹窗页面 Props

```ts
interface VcModalProps<P = unknown> {
  modal: ModalInstance<P>  // { visible, params, open, close }
  query?: SearchQuery<QueryData>
}
```

---

## 工具

### VcExcel

Excel 导出工具，基于 exceljs（**动态导入**，不影响首屏加载）。

```ts
import { VcExcel, type VcExcelData } from '@stoly/vue-component'

await VcExcel.export(
  {
    header: [
      { header: '姓名', key: 'name', width: 10 },
      { header: '年龄', key: 'age' },
    ],
    data: [{ name: '张三', age: 28 }],
  },
  '用户列表',
)

VcExcel.downloadExcel(blob, '文件名')
```

> `VcExcel.export` 为 **async**，返回 `Promise<void>`。

---

## 类型导出

```typescript
import type {
  VcBaseResult,
  VcBaseResultData,
  VcCacheColumn,
  VcConfirmOptions,
  VcDataFormat,
  VcListPager,
  VcQueryData,
  VcSearchQuery,
  VcSort,
  VcTableFormController,
  VcTableProps,
  VcTableInstance,
  VcTableFormProps,
  VcModalProps,
  FullScreenModalProps,
  VcExcelData,
} from '@stoly/vue-component'
```

## 与 @stoly/react-component 的差异

| 能力 | React (`Rc*`) | Vue (`Vc*`) |
|------|---------------|-------------|
| 布局组件 | `RcLayoutHeader` / `RcLayoutSider` / `RcLayoutTabPage` | 暂无 |
| 通用 Hooks | `useSimpleReducer` 等 | 暂无 |
| Modal 可见性 | `modal.visable` | `modal.visible` |
| 全屏回调 | prop `onFullScreenChange` | emit `fullScreenChange` |
| 表格 ref | `getCurrentData()` | `getColumns()` |
| 行选择 | antd `rowSelection` | `onSelectedRows` 回调 |
| 查询表单绑定 | `Form.Item name` | `query.formState` + `v-model` |

## 注意事项

1. 所有接口返回值必须包含 `success: boolean` 字段
2. `VcTable` 的 `exported` 仅适用于简单表头，复杂嵌套表头请直接调用 `VcExcel.export()`
3. `VcModal.confirm` 传入 `tableData` 时会自动处理「删除最后一条数据后翻页到上一页」
4. `VcTable` 不支持多字段排序，多列排序会提示错误
5. 使用 `VcTable` / `VcTableForm` 时建议在根节点包裹 ant-design-vue 的 `App` 组件以正确消费 `message` 等上下文

## License

ISC
