# TableLayout

根据 UI 统一设计，封装的表格常规布局组件。

# Feature

## 产品，UI 诉求

- 取消操作弹窗，仅保留提示类弹窗（提示类的定义包括删除，异常等需要确认的提示，以及不需要确认的弱提示）

- 在数据列表内，对当前页列表内数据编辑或新增时，统一从右侧拉出抽屉式子页面，左侧保留 3 列默认字段，右侧保存或返回时，关闭抽屉返回列表页

- 抽屉态下，如果是对某一确认数据行操作，则左侧对应行高亮，如果是全局，则左侧无可操作区域。

- 集团平台组织结构树保留，抽屉状态下 也展开，只是宽度缩小

# Usage

`TableLayout` 相关组件（由于 name 过长，这里采用缩写 `Tl`）。

- Tl
- TlMain
- TlDrawer
- TlFilter
- TlFilterLeft
- TlFilterRight
- TlFilterSecond

## 注册

一般采用全局注册的方式

```ts
import TableLayout from '@kt/unity-table-layout';
app.use(TableLayout);
```

## 基本布局

```html
<tl>
  <tl-filter>
    <a-form-item>
      <a-input placeholder="姓名" v-model:value="name"></a-input>
    </a-form-item>
    <a-form-item>...</a-form-item>

    <a-button type="primary" @click="handleSearch">查询</a-button>
    <a-button>...</a-button>
  </tl-filter>

  <tl-filter-second>
    <a-input placeholder="其他" size="small"></a-input>
  </tl-filter-second>

  <tl-table>
    <a-table ...>...</a-table>
  </tl-table>
</tl>
```

> tips：tl-filter-second 内部的所有表单元素都使用 size="small"

## tl-filter 左右布局

`tl-filter` 子级可被分成 `tl-filter-left` 和 `tl-filter-right` 两部分

```html
...
<tl-filter>
  <tl-filter-left>
    <a-form-item>
      <a-input placeholder="姓名" v-model:value="name"></a-input>
    </a-form-item>
    <a-form-item>...</a-form-item>
    <a-button type="primary" @click="handleSearch">查询</a-button>
    <a-button>...</a-button>
  </tl-filter-left>
  <tl-filter-right>
    <a-button type="link">导出</a-button>
  </tl-filter-right>
</tl-filter>
...
```

## tl-filter tabs + 右侧 search 布局

```html
...
<!-- 定义当前过滤栏角色 -->
<tl-filter role="tabs">
  <a-tabs v-model:activeKey="activeKey">
    <!-- 使用 antd 的 tabs：https://www.antdv.com/components/tabs-cn，但是 panel 需要自己控制 -->
    <a-tab-pane key="1" tab="Tab 1" />
    <a-tab-pane key="2" tab="Tab 2" />
    <a-tab-pane key="3" tab="Tab 3" />

    <!-- 使用 antd tabs 的右侧扩展 -->
    <template #rightExtra>
      <!-- 可新增 label，可能需要对后面的输入框进行解释 -->
      <a-form-item label="支付时间">
        <a-range-picker />
      </a-form-item>
      <a-form-item>
        <a-input placeholder="姓名" style="width: 200px" allow-clear></a-input>
      </a-form-item>
      <a-button type="primary" @click="handleSearch">查询</a-button>
      <!-- 传入 :visible="visible" 表示可以灵活配置按钮控制 tl-filter-second 显示-->
      <a-button @click="filterSecondVisible = !filterSecondVisible">
        {{ filterSecondVisible ? '收起' : '' }}更多条件
      </a-button>
      <a-button type="primary" @click="handleAdd">新增</a-button>
    </template>
  </a-tabs>
</tl-filter>

<!-- 传入 visible 属性，表示可控 -->
<tl-filter-second :visible="filterSecondVisible">
  <a-form-item>
    <a-input placeholder="内容" size="small" allow-clear></a-input>
  </a-form-item>
</tl-filter-second>
...
```

## 带有 drawer 的布局

`tl` 子级可被分成 `tl-main` 和 `tl-drawer` 两部分

```html
<tl v-model:drawer="drawer.visible">
  <tl-main>
    <tl-filter>
      <a-form-item>
        <a-input placeholder="姓名" v-model:value="name"></a-input>
      </a-form-item>
      <a-form-item>...</a-form-item>

      <a-button type="primary" @click="handleSearch">查询</a-button>
      <a-button>...</a-button>
    </tl-filter>

    <tl-filter-second>
      <a-input placeholder="其他" size="small"></a-input>
    </tl-filter-second>

    <tl-table>
      <a-table ...>...</a-table>
    </tl-table>
  </tl-main>
  <tl-drawer :title="drawer.title" :spin="{ spinning: drawer.spinning }">
    <template #buttons>
      <a-button type="primary" size="small" @click="handleConfirm">
        修改
      </a-button>
      <a-button size="small" @click="drawer.visible = false">返回</a-button>
    </template>
  </tl-drawer>
</tl>
```

> tips：tl-drawer 的 buttons 插槽，内部的所有按钮都使用 size="small"

## 带有 drawer 的布局，且表格表头为复杂表头

ref：[antd table 复杂表头](https://www.antdv.com/components/table-cn#components-table-demo-grouping-columns)

背景：展开 drawer 时候会对表格的列进行选择性的显示/隐藏，这是内部通过 css 手段来控制的。但是对于复杂表头，打破了常规的一行表头的形式，css 就无法进行很好的控制。所以针对这种场景需要手动修改表头，在打开 drawer 的时候，手动修改“复杂表头”为“简单表头”（简单表头需保证只有一行）。

对于这种场景需要做好一下三步：

```ts
//...
// step1：维护复杂、简单表头
const complexColumns = [
  {
    title: 'ID',
    dataIndex: 'id'
  },
  {
    title: 'Other',
    children: [
      {
        title: '标题',
        dataIndex: 'title',
        align: 'left'
      },
      {
        title: '用户名',
        dataIndex: ['user', 'name']
      }
    ]
  },
  {
    title: '操作',
    dataIndex: 'action'
  }
];
const simpleColumns = [
  {
    title: 'ID',
    dataIndex: 'id'
  },
  {
    title: '标题',
    dataIndex: 'title',
    align: 'left'
  },
  {
    title: '用户名',
    dataIndex: ['user', 'name']
  },
  {
    title: '操作',
    dataIndex: 'action'
  }
];

// step2：初始化复杂表头
const tableOptions = reactive<UseTableOptions>({
  //...
  columns: complexColumns
  //...
});

// step3：监听 drawer 变化
watch(
  () => drawer.visible,
  (val: boolean) => {
    // 打开抽屉时，对表格复杂 head 进行改写
    tableOptions.columns = val ? simpleColumns : complexColumns;
  }
);
//...
```

## tl 嵌套 tl 的布局

```html
<tl v-model:drawer="drawer.visible">
  <tl-main>
    ...
    这里可以是一个表格，也可以是一个表单，或者其他。
    ...
  </tl-main>
  <tl-drawer>
    <tl>
      <tl-filter>...</tl-filt>
      <tl-table>...</tl-table>
    </tl>
  </tl-drawer>
</tl>
```

# API

## TableLayout

| 参数            | 说明                                 | 类型    | 默认值 |
| --------------- | ------------------------------------ | ------- | ------ |
| fixed           | 开启固定模式，<br>表格 body 开启滚动 | boolean | true   |
| drawer(v-model) | 是否显示右侧抽屉                     | boolean | false  |

## TableLayoutTable

| 参数           | 说明                                                             | 类型           | 默认值                    |
| -------------- | ---------------------------------------------------------------- | -------------- | ------------------------- |
| visibleColumns | 抽屉显示时，需要显示的列索引                                     | number[]       | [0,1,2]<br>默认表格前三列 |
| visibleAction  | 抽屉显示时，需要显示的操作列索引<br>(可以配置'last'表示最后一列) | number\|string | -                         |
| visibleButton  | 抽屉显示时，需要显示的操作列里的 button 索引                     | number         | -                         |

## TableFilter

| 参数 | 说明                                                                                                                                                                                                                                                                                                                                | 类型       | 默认值 |
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------ |
| role | 当前过滤栏角色，提供给外部更灵活的控制。<br>目前只有一种 tabs 场景，后续可扩展<br> 场景：[表格头部是 tab + 右侧搜索 1](https://lanhuapp.com/web/#/item/project/detailDetach?pid=ff997f2c-0ea3-4901-8f3a-49f76a11e747&project_id=ff997f2c-0ea3-4901-8f3a-49f76a11e747&image_id=a3bc7e9e-559b-4269-83f2-c720b4024494&fromEditor=true) | ''\|'tabs' | ''     |

## TableFilterSecond

> TableFilterSecond 有特殊设计，如果 TableLayout 包含了 TableFilterSecond 组件，意味着这个是一个可被收起展开的二级过滤栏，会在表单元素后面自动添加“更多条件”按钮。也可以显式传入属性 visible，把控制权交付到外部控制，外部就要手动添加一个按钮来控制。

| 参数    | 说明                                                                                                       | 类型    | 默认值 |
| ------- | ---------------------------------------------------------------------------------------------------------- | ------- | ------ |
| visible | 交给外部控制显示，⚠️ 外部需要新增按钮，按钮可灵活配置文案、位置，<br>一般与 TableFilter role=tabs 配合使用 | boolean | -      |

## TableLayoutDrawer

| 参数                                          | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | 类型                          | 默认值                                                                                 |
| --------------------------------------------- |   | ----------------------------- | -------------------------------------------------------------------------------------- |
| title                                         | drawer 标题                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | string \| slot                | ''                                                                                     |
| spin                                          | 参考 [antd 官网 spin](https://next.antdv.com/components/spin-cn)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | object                        | -                                                                                      |
| buttonsPosition                               | 按钮位置，位置可被计算，默认 auto<br>可灵活配置 append（追加），fixed（固定底部）<br>[蓝湖参考：fixed](https://lanhuapp.com/web/#/item/project/detailDetach?pid=ff997f2c-0ea3-4901-8f3a-49f76a11e747&image_id=309a9177-7630-4005-9003-0817e968d6c5&tid=3e189f00-e766-420d-a5d1-e963c303b08c&project_id=ff997f2c-0ea3-4901-8f3a-49f76a11e747&fromEditor=true)<br>[蓝湖参考：append](https://lanhuapp.com/web/#/item/project/detailDetach?pid=ff997f2c-0ea3-4901-8f3a-49f76a11e747&project_id=ff997f2c-0ea3-4901-8f3a-49f76a11e747&image_id=1a00e1a8-23e8-4a49-b3de-c811fea4d118&fromEditor=true) | 'append' \| 'fixed' \| 'auto' | 'auto'                                                                                 |
| drawerStyle（[建议全局配置](#global-config)） | 抽屉风格                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | 'custom' \| 'ant'             | 'custom'                                                                               |
| antDrawer（[建议全局配置](#global-config)）   | ant 抽屉 props 透传，当配置了 drawerStyle 为 "ant" 时有效                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | object                        | ` { keyboard: true, mask: true, maskClosable: true, width: '50%', placement: 'right'}` |

## TableLayoutDrawer Events

| 事件名               | 说明                       | 类型                |
| -------------------- | -------------------------- | ------------------- |
| close                | 关闭抽屉                   | `function(e) `      |
| after-visible-change | 切换抽屉时动画结束后的回调 | `function(visible)` |

## TableLayoutDrawer Slots

| 插槽名称 | 说明                            | 参数 |
| -------- | ------------------------------- | ---- |
| title    | 抽屉标题                        | -    |
| buttons  | 抽屉顶部按钮组                  | -    |
| default  | 默认插槽，一般放置表单/详情展示 | -    |

<a id="global-config"></a>

## 全局配置

```ts
import TableLayout, {
  setTableLayoutGlobalOptions
} from '@kt/unity-table-layout';

/* ... */
// 全局配置需要在 use 之前
setTableLayoutGlobalOptions({
  drawerStyle: 'ant'
});
app.use(TableLayout);
/* ... */
```

# 蓝湖参考

- [表格左右布局形式](https://lanhuapp.com/web/#/item/project/detailDetach?pid=ff997f2c-0ea3-4901-8f3a-49f76a11e747&project_id=ff997f2c-0ea3-4901-8f3a-49f76a11e747&image_id=c1241130-6f6c-406a-ad20-8861d361852b&fromEditor=true)

- [表格左右布局形式 2](https://lanhuapp.com/web/#/item/project/detailDetach?pid=ff997f2c-0ea3-4901-8f3a-49f76a11e747&project_id=ff997f2c-0ea3-4901-8f3a-49f76a11e747&image_id=058a7aef-1a4f-482f-84f3-94fb327bb435&fromEditor=true)

- [表格头部二级搜素栏](https://lanhuapp.com/web/#/item/project/detailDetach?tid=3e189f00-e766-420d-a5d1-e963c303b08c&pid=ff997f2c-0ea3-4901-8f3a-49f76a11e747&project_id=ff997f2c-0ea3-4901-8f3a-49f76a11e747&image_id=c1d72c0a-d04a-42a8-b9ed-f99ef568a637&fromEditor=true)

- [表格头部是 tab + 右侧搜索 1](https://lanhuapp.com/web/#/item/project/detailDetach?pid=ff997f2c-0ea3-4901-8f3a-49f76a11e747&project_id=ff997f2c-0ea3-4901-8f3a-49f76a11e747&image_id=a3bc7e9e-559b-4269-83f2-c720b4024494&fromEditor=true)

- [表格头部是 tab + 右侧搜索 2](https://lanhuapp.com/web/#/item/project/detailDetach?pid=ff997f2c-0ea3-4901-8f3a-49f76a11e747&project_id=ff997f2c-0ea3-4901-8f3a-49f76a11e747&image_id=45d02782-6a61-4bfc-bdce-264f46e0b9c4&fromEditor=true)

# 注意

- TableLayout 可以配置打开 drawer 时，左边还可以有操作按钮进行操作。如果开启了操作列，就意味着这可能是一个异步请求展示右边 drawer 的内容。这个行为可能会很频繁（我们不能保证用户的网络情况都良好），所以这里需要做接口的取消行为，保证请求数据的准确性

  参考 axios 取消请求：https://axios-http.com/zh/docs/cancellation

  ✨ demo

  ```js
  let controller: AbortController;
  const handleFoo = async (record: any) => {
    // 请求前取消上一次还在 pending 的接口
    controller?.abort();
    controller = new AbortController();

    // ...

    drawer.spinning = true;
    const { code, data, error } = await request.get(
      'foo/bar',
      {
        foo: 'bar'
      },
      {
        signal: controller.signal
      }
    );
    // 当前如果是被取消的话，不要关闭 loading，customCode 可自定义
    if (code === customCode.canceled) {
      drawer.spinning = true;
    } else if (code === 2000) {
      drawer.spinning = false;

      // ...
    }
  };
  ```

- `tl-filter` 组件内部的 `a-form-item` 可新增 `label` 属性。目前我们过滤栏 `a-form-item` 都采用了 `placeholer` 方式，没有使用 `label`，但是有场景需要对后面的 “`input`框” 进行解释，就需要添加 `label`，比如：
  ```html
  <a-form-item label="支付时间">
    <a-range-picker />
  </a-form-item>
  ```

# FAQ

- 什么时候使用 TableLayout 组件？

  TableLayout 组件包含了顶部的条件过滤以及灵活的 flex 布局会自动铺满 table body 的高度，溢出滚动，符合目前 UI 的规范。包含 drawer 抽屉功能：增、改、查都可以在抽屉体现。

- 点击表格的某个单元格，查看详情，详情是一个表格，之前是通过弹框，现在要用 TableLayout 的 drawer 吗？[蓝湖例子](https://lanhuapp.com/web/#/item/project/detailDetach?pid=ff997f2c-0ea3-4901-8f3a-49f76a11e747&image_id=a4ebde23-757f-480f-9b0d-ddf9a4cc3acd&tid=3e189f00-e766-420d-a5d1-e963c303b08c&project_id=ff997f2c-0ea3-4901-8f3a-49f76a11e747&fromEditor=true)

  是的。目前 TableLayout 有做了处理，可以实现 TableLayout 嵌套 TableLayout 来处理这种场景，也就是类似这样子的嵌套：

  ```html
  <tl>
    <tl-main>...</tl-main>
    <tl-drawer>
      <tl>
        <tl-filter>...</tl-filter>
        <tl-table>
          <a-table />
        </tl-table>
      </tl>
    </tl-drawer>
  </tl>
  ```

- 使用 TableLayout 打开抽屉，如果在抽屉这块还需要有交互，比如说修改表格的某一行，那怎么处理？

  这个时候就需要使用弹框来处理了。总结一下有两级交互，第一级是在 drawer 处理，如果 drawer 还有交互才在 modal（第二级） 处理。

- 什么时候使用 TableLayout 嵌套 TableLayout？

  当 tl 组件里的 tl-drawer 组件里需要展示 tl-table 的时候
