---
name: stoly-vue-component
description: >-
  基于 ant-design-vue 4.x 封装的 Vue 组件库 @stoly/vue-component 的开发与使用指南。
  当任务涉及 VcTable、VcTable.Context、VcTableForm、VcModal、VcExcel、VcConfirmOptions 时使用。
  触发场景：使用 @stoly/vue-component 构建表格查询页面、弹窗、Excel 导出，
  或在项目中引用该库的组件和类型。
---

# @stoly/vue-component 使用指南

基于 ant-design-vue 4.2.6 + Vue 3 + @vueuse/core 的业务组件库，提供表格、查询表单、弹窗等开箱即用的组件。

## 核心概念：Query 数据流

所有表格相关组件围绕 `SearchQuery<T>` 协议协作：

```
useQuery → query → VcTableForm(set) → VcTable(get/refresh)
```

`SearchQuery<T>` 接口：

- `formState` — 响应式查询对象，表单项直接 `v-model:value="query.formState.xxx"`
- `get()` — 获取当前查询条件
- `set(q)` — 合并更新，触发表格重新请求
- `refresh()` — 使用当前条件重新请求

## 组件速查

### VcTable — 数据表格

```vue
<script setup lang="ts">
import { Space } from 'ant-design-vue'
import type { ColumnsType } from 'ant-design-vue/es/table'
import { VcModal, VcTable, VcTableForm } from '@stoly/vue-component'
import type { VcBaseResult, VcDataFormat } from '@stoly/vue-component'

const query = VcTableForm.useQuery<MyQuery>()

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

const deleteApi = async (id?: number): Promise<VcBaseResult<unknown>> => {
  // ...
  return { success: true }
}

const handleDelete = (record: Row, tableData: VcDataFormat<Row>) => {
  VcModal.confirm(
    { api: deleteApi, params: record.id, query, tableData },
    { title: '删除', content: `确认删除「${record.name}」？` },
  )
}
</script>

<template>
  <VcTable
    :query="query"
    :api="fetchList"
    :query-format="(q) => ({ page: q.pageNum, size: q.pageSize, ...q })"
    :data-format="(res) => ({ rows: res.data.list, total: res.data.total })"
    :columns="columns"
    show-index
    exported
    row-key="id"
  >
    <template #bodyCell="{ column, record }">
      <template v-if="column.key === 'operate'">
        <Space :size="8">
          <a @click="modal.open({ id: record.id })">编辑</a>
          <VcTable.Context v-slot="{ tableData }">
            <a @click="() => handleDelete(record, tableData as VcDataFormat<Row>)">删除</a>
          </VcTable.Context>
        </Space>
      </template>
    </template>
  </VcTable>
</template>
```

**Props（继承 ant-design-vue TableProps，移除 dataSource/loading/components/onChange/rowSelection）：**

| Prop | 类型 | 说明 |
|------|------|------|
| `query` | `SearchQuery<Q>` | 查询条件（必传） |
| `api` | `(params: P) => Promise<BaseResult<R>>` | 数据请求（必传） |
| `queryFormat` | `(q: Q) => P` | 查询条件 → 接口参数（必传） |
| `dataFormat` | `(res) => DataFormat<T>` | 响应 → `{ rows, total }`（必传） |
| `showIndex` | `boolean \| ColumnType<T>` | 序号列 |
| `exported` | `boolean` | 右键导出 Excel |
| `onSelectedRows` | `(rows: T[]) => void` | 启用行选择 |

**Ref：** `getColumns()` 返回当前展示列 title 数组

**静态属性：**

- `VcTable.OperateColumnProps` — 操作列默认配置 `{ align:'center', title:'操作', width:120, fixed:'right', className:'noCopy' }`
- `VcTable.Context` — 见下文

**内置功能：** 列显隐、列拖拽排序、密度切换、回到顶部、右键菜单

#### VcTable.Context — 获取当前页表格数据

通过 **scoped slot** 读取 `VcTable` 内部的 `{ rows, total }`（类型 `DataFormat<T>`），等价于 React 版 `RcTable.Context` 的 render props。

**使用约束：**

- 必须渲染在 **`VcTable` 子树内**（`provide/inject`），常见位置是 `#bodyCell` 操作列
- **不能** 作为 `VcTable` 的兄弟节点使用（拿不到数据）

**模板写法：**

```vue
<VcTable.Context v-slot="{ tableData }">
  <!-- tableData: { rows: T[], total: number } -->
  <span>当前页 {{ tableData.rows.length }} 条，共 {{ tableData.total }} 条</span>
</VcTable.Context>
```

**与 `VcModal.confirm` 配合（删除后自动刷新 / 翻页）：**

操作列内用 `Context` 取出当前页 `tableData`，传给 `confirm` 的 `tableData` 字段，删除成功后若当前页仅剩 1 条且 `pageNum > 1` 会自动 `set` 到上一页，否则 `query.refresh()`。

```vue
<VcTable.Context v-slot="{ tableData }">
  <a @click="() => handleDelete(record, tableData as VcDataFormat<Row>)">删除</a>
</VcTable.Context>
```

```ts
const handleDelete = (record: Row, tableData: VcDataFormat<Row>) => {
  VcModal.confirm(
    { api: deleteApi, params: record.id, query, tableData },
    { title: '删除', content: '确认删除？' },
  )
}
```

> slot 推断的 `tableData` 默认为 `DataFormat<object>`，按行类型断言为 `VcDataFormat<Row>` 即可。

**对比 React：**

```tsx
<RcTable.Context>
  {(tableData) => (
    <a onClick={() => RcModal.confirm({ api, params, query, tableData })}>删除</a>
  )}
</RcTable.Context>
```

### VcTableForm — 查询表单

```vue
<script setup lang="ts">
const query = VcTableForm.useQuery<MyQuery>()
const controller = VcTableForm.useController()
</script>

<template>
  <VcTableForm :query="query" :controller="controller" layout="inline">
    <Form.Item label="姓名" name="name">
      <Input v-model:value="query.formState.name" />
    </Form.Item>
  </VcTableForm>
</template>
```

**静态方法：**

- `VcTableForm.useQuery<T>(initValues?)` — 默认 `{ pageNum: 1, pageSize: 20 }`
- `VcTableForm.useController()` — `submit()` / `clear(fields?)`

**插槽：** `default`、`customButtons`、`extra`

### VcModal — 可拖拽弹窗

```vue
<script setup lang="ts">
const modal = VcModal.useModal<EditParams>()
</script>

<template>
  <VcModal
    title="编辑"
    :open="modal.visible"
    full-screen-modal
    @cancel="modal.close"
    @full-screen-change="(v) => console.log(v)"
  >
    <EditForm :modal="modal" :query="query" />
  </VcModal>
</template>
```

**Props：** `fullScreenModal`、`autoFullScreen`

**事件：** `fullScreenChange`（`@full-screen-change`）

**弹窗页面 Props：**

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

**静态方法：**

- `VcModal.useModal<P>()` — `{ visible, params, open, close }`
- `VcModal.confirm(opts, options?)` — 确认框，成功后刷新/翻页

**confirm 对象模式（推荐）：**

```ts
import type { VcConfirmOptions, VcDataFormat } from '@stoly/vue-component'

// tableData 优先从 VcTable.Context 获取（见 VcTable 章节）
VcModal.confirm({
  api: deleteApi,
  params: record.id,
  query,
  tableData,  // VcDataFormat<Row>，用于删最后一条自动翻页
  callback: (res) => {},
}, { title: '删除', content: '确认删除？' })
```

类型：`VcConfirmOptions<P, R extends VcBaseResult<unknown>>`

**多参数形式（旧兼容）：** `VcModal.confirm(deleteApi, record.id, query)`

### VcExcel — Excel 导出

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

await VcExcel.export({
  header: [{ header: '姓名', key: 'name', width: 10 }],
  data: [{ name: '张三' }],
}, '文件名')
```

exceljs 动态加载，`export` 为 async。

## 共享类型

```typescript
interface BaseResult<T> { success: boolean; data?: T; resultStatus?: number; msg?: string; count?: number }
type BaseResultData<T> = /* 从 data/object 推断 */
interface QueryData { pageNum: number; pageSize: number; sort?: Sort }
interface Sort { field?: string; order?: 'asc' | 'desc' }
interface DataFormat<T> { rows: T[]; total: number }
type VcConfirmOptions<P, R extends BaseResult<unknown>> = { api; params?; tableData?; query?; callback? }
```

## 与 React 版 (@stoly/react-component) 差异

| 项 | React | Vue |
|----|-------|-----|
| 布局 | RcLayout* | 无 |
| Hooks | useSimpleReducer 等 | 无 |
| Modal 可见 | `modal.visable` | `modal.visible` |
| 全屏回调 | `onFullScreenChange` prop | `fullScreenChange` emit |
| 表格 ref | `getCurrentData()` | `getColumns()` |
| 表格当前页数据 | `RcTable.Context` render props | `VcTable.Context` scoped slot `{ tableData }` |
| 行选择 | `rowSelection` | `onSelectedRows` |
| 表单绑定 | `Form.Item name` | `query.formState` + v-model |

## 强制规则

1. **弹窗统一用 `VcModal.useModal`**，子组件 props 使用 `VcModalProps<P>`，不要自行维护 `visible` + `params` state。

2. **二次确认操作统一用 `VcModal.confirm`**（删除、禁用等），传入 `query` 自动刷新。`tableData` 优先在操作列通过 **`VcTable.Context`** 获取后传入，自动处理「删最后一条翻页」。

   ```vue
   <VcTable.Context v-slot="{ tableData }">
     <a @click="() => handleDelete(record, tableData as VcDataFormat<Row>)">删除</a>
   </VcTable.Context>
   ```

3. **列表页根节点包裹 `App`**（ant-design-vue），保证 `message` 等上下文可用。

4. **表单项绑定 `query.formState`**，查询/重置由 `VcTableForm` 与 `controller` 协作完成。

## 样式

`import { VcTable, ... } from '@stoly/vue-component'` 时会自动带上库内样式（构建产物 `index.js` 顶部 `import './index.css'`）。`package.json` 需保留 `sideEffects: ["**/*.css"]`，避免消费方 tree-shaking 删掉 CSS。

## 注意事项

- 接口返回值必须含 `success: boolean`
- `exported` 仅适用于简单表头
- `VcTable` 不支持多字段排序
- `VcExcel.export` 为 async，需 `await` 或 `void`
