707 lines
28 KiB
TypeScript
707 lines
28 KiB
TypeScript
import { createApp, reactive, ref } from 'vue'
|
||
import { Plus, Search, Settings } from '@lucide/vue'
|
||
import {
|
||
G3Button,
|
||
G3ButtonGroup,
|
||
G3ButtonGroupSeparator,
|
||
G3Checkbox,
|
||
G3Col,
|
||
G3ColorPicker,
|
||
G3DatePicker,
|
||
G3Drawer,
|
||
G3Dropdown,
|
||
G3DropdownItem,
|
||
G3Form,
|
||
G3FormGroup,
|
||
G3FormItem,
|
||
G3Input,
|
||
G3Message,
|
||
G3Modal,
|
||
G3Notification,
|
||
G3Pagination,
|
||
G3Popover,
|
||
G3Radio,
|
||
G3RadioGroup,
|
||
G3RangePicker,
|
||
G3RichText,
|
||
G3Row,
|
||
G3Scrollbar,
|
||
G3Segmented,
|
||
G3Select,
|
||
G3SliderCaptcha,
|
||
G3Space,
|
||
G3Spin,
|
||
G3Splitter,
|
||
G3SplitterPanel,
|
||
G3Switch,
|
||
G3Table,
|
||
G3TabPane,
|
||
G3Tabs,
|
||
G3Tag,
|
||
G3TimeSelect,
|
||
G3Tooltip,
|
||
G3Tree,
|
||
} from '@g3soft/ui'
|
||
import '@g3soft/tokens'
|
||
|
||
/**
|
||
* playground —— 组件在真实浏览器里的落脚点。
|
||
*
|
||
* 约定:所有 E2E 与人工手测都通过 `data-testid` 找元素,
|
||
* 不要依赖文本或层级结构(文案会改、结构会调,testid 保持稳定)。
|
||
*/
|
||
|
||
const ROLE_OPTIONS = [
|
||
{ label: '管理员', value: 1 },
|
||
{ label: '会员', value: 2 },
|
||
{ label: '访客', value: 3 },
|
||
]
|
||
|
||
const TREE_DATA = [
|
||
{
|
||
key: '1',
|
||
title: '前端',
|
||
children: [
|
||
{ key: '1-1', title: 'Vue', children: [{ key: '1-1-1', title: '组合式 API' }] },
|
||
{ key: '1-2', title: 'React' },
|
||
],
|
||
},
|
||
{ key: '2', title: '后端', children: [{ key: '2-1', title: 'Node.js' }] },
|
||
]
|
||
|
||
const LONG_TEXT = Array.from({ length: 40 }, (_, index) => `第 ${index + 1} 行内容`).join('\n')
|
||
|
||
const App = {
|
||
components: {
|
||
G3Button,
|
||
G3ButtonGroup,
|
||
G3ButtonGroupSeparator,
|
||
G3Checkbox,
|
||
G3Col,
|
||
G3ColorPicker,
|
||
G3DatePicker,
|
||
G3Drawer,
|
||
G3Dropdown,
|
||
G3DropdownItem,
|
||
G3Form,
|
||
G3FormGroup,
|
||
G3FormItem,
|
||
G3Input,
|
||
G3Modal,
|
||
G3Pagination,
|
||
G3Popover,
|
||
G3Radio,
|
||
G3RadioGroup,
|
||
G3RangePicker,
|
||
G3RichText,
|
||
G3Row,
|
||
G3Scrollbar,
|
||
G3Segmented,
|
||
G3Select,
|
||
G3SliderCaptcha,
|
||
G3Space,
|
||
G3Spin,
|
||
G3Splitter,
|
||
G3SplitterPanel,
|
||
G3Switch,
|
||
G3Table,
|
||
G3TabPane,
|
||
G3Tabs,
|
||
G3Tag,
|
||
G3TimeSelect,
|
||
G3Tooltip,
|
||
G3Tree,
|
||
Plus,
|
||
Search,
|
||
Settings,
|
||
},
|
||
setup() {
|
||
const model = reactive({
|
||
name: '张三',
|
||
intro: '这是多行文本',
|
||
role: 1 as number | undefined,
|
||
city: [] as number[],
|
||
agree: true,
|
||
level: 2,
|
||
gender: 'male',
|
||
date: '2026-10-08 09:30:00',
|
||
range: ['2026-10-01', '2026-10-08'] as string[],
|
||
})
|
||
const tab = ref('a')
|
||
// 选项是中文原始值,初始值必须对上,否则没有选中项
|
||
const seg = ref('日')
|
||
const time = ref(new Date(2026, 9, 8, 9, 30, 0))
|
||
const modalOpen = ref(false)
|
||
const modalConfirmOpen = ref(false)
|
||
const drawerOpen = ref(false)
|
||
// 四个方向都要能验:上下面板的宽度必须撑满视口(曾因用 flex 对齐而缩到内容宽)
|
||
const drawerPlacement = ref<'left' | 'right' | 'top' | 'bottom'>('right')
|
||
// 挂载即展开的浮层:验证「初始 open」也能拿到 z-index
|
||
const popoverAOpen = ref(true)
|
||
const popoverBOpen = ref(true)
|
||
// 颜色选择器:给 RTL 用例验「取色区/色相条不镜像」用
|
||
const color = ref('#165dff')
|
||
// 滑块验证码:给 E2E 验真实指针拖拽、键盘与 RTL 起步边
|
||
const captchaPassed = ref(false)
|
||
const captchaDisabled = ref(false)
|
||
// 富文本编辑器:给样式核对用
|
||
const richHtml = ref('<h2>标题</h2><p>这是<b>加粗</b>、<i>斜体</i>、<sup>上标</sup>、<sub>下标</sub>的正文。</p><ul><li>列表项一</li><li>列表项二</li></ul>')
|
||
|
||
// RTL 开关:直接翻 <html dir>。CSS 侧靠 [dir='rtl'] 选择器,JS 侧靠 useDirection
|
||
// 观察 documentElement,两边都跟得上。也支持 ?dir=rtl 直接以 RTL 进入,
|
||
// 便于 E2E 与人工核对(不依赖点击顺序)。
|
||
const rtl = ref(new URLSearchParams(location.search).get('dir') === 'rtl')
|
||
function applyRtl() {
|
||
document.documentElement.setAttribute('dir', rtl.value ? 'rtl' : 'ltr')
|
||
}
|
||
function toggleRtl() {
|
||
rtl.value = !rtl.value
|
||
applyRtl()
|
||
}
|
||
applyRtl()
|
||
|
||
// 扁平树:两个节点都不是父节点,用来验「叶子不该留展开箭头占位」
|
||
const flatTreeData = [
|
||
{ key: 'flat-1', title: '节点一(无子节点)' },
|
||
{ key: 'flat-2', title: '节点二(无子节点)' },
|
||
]
|
||
const treeMenuItems = [
|
||
{ key: 'add', label: '新增子节点' },
|
||
{ key: 'rename', label: '重命名' },
|
||
{ key: 'delete', label: '删除', danger: true },
|
||
]
|
||
|
||
function toast(placement?: string) {
|
||
G3Message.success(`操作成功`, placement ? { placement: placement as never } : undefined)
|
||
}
|
||
|
||
function notify() {
|
||
G3Notification.info({ title: '定时任务完成', description: '本次共同步 128 条数据。' })
|
||
}
|
||
|
||
function openDrawer(placement: 'left' | 'right' | 'top' | 'bottom') {
|
||
drawerPlacement.value = placement
|
||
drawerOpen.value = true
|
||
}
|
||
|
||
function openCommandConfirm() {
|
||
G3Modal.confirm({
|
||
title: '删除确认',
|
||
content: '确定要删除这条记录吗?',
|
||
onConfirm: () => new Promise<void>((resolve) => setTimeout(resolve, 600)),
|
||
})
|
||
}
|
||
|
||
/* ---- Table 表格 ---- */
|
||
// 小数据量:用于验证「行数 ≤ 100 时关闭虚拟滚动」的门控
|
||
// 注意必须是 ref:模板里 v-model:columns 要能回写,否则拖列宽无处落库(松手回弹)
|
||
const tableColumns = ref([
|
||
{ dataIndex: 'name', title: '名称', width: 180 },
|
||
{
|
||
dataIndex: 'amount',
|
||
title: '金额',
|
||
width: 120,
|
||
align: 'right',
|
||
editor: {
|
||
type: 'number',
|
||
// 金额必须 > 0:让「校验态全表可见」在 E2E 里有可断言的 error 数据
|
||
validate: (v: unknown) => Number(v) > 0 || '金额必须大于 0',
|
||
},
|
||
},
|
||
{
|
||
dataIndex: 'status',
|
||
title: '状态',
|
||
width: 110,
|
||
editor: {
|
||
type: 'select',
|
||
options: [
|
||
{ label: '启用', value: 1 },
|
||
{ label: '停用', value: 0 },
|
||
],
|
||
// 按行禁用:enabled=0 的那一行状态不许改 —— 用来锁「动态灰底」这条视觉
|
||
// (e2e 断言第 2 行灰、第 1 行不灰,且都**不需要先进入编辑行**)
|
||
disabled: (row: { enabled?: number }) => row.enabled === 0,
|
||
},
|
||
},
|
||
{ dataIndex: 'enabled', title: '启用', width: 80, editor: { type: 'checkbox' } },
|
||
{
|
||
dataIndex: 'remark',
|
||
title: '备注',
|
||
editor: {
|
||
type: 'text',
|
||
// 备注非空:提供 warning 级校验(金额是 error,这里是 warning,两级都有样例)
|
||
validateWarning: (v: unknown) => String(v ?? '').length > 0 || '建议填写备注',
|
||
},
|
||
},
|
||
{ dataIndex: 'op', title: '操作', width: 140, resizable: false, actions: [
|
||
{ label: '编辑', onClick: () => {} },
|
||
{ label: '删除', type: 'danger', confirm: '确认删除该行?', onClick: () => {} },
|
||
] },
|
||
])
|
||
const tableData = ref([
|
||
{ id: 1, name: '合同 A', amount: 12000, status: 1, enabled: 1, remark: '已归档' },
|
||
{ id: 2, name: '合同 B', amount: 0, status: 0, enabled: 0, remark: '' },
|
||
{ id: 3, name: '合同 C', amount: 43000, status: 1, enabled: 1, remark: '待复核' },
|
||
])
|
||
const tableSelectedKeys = ref([])
|
||
|
||
// 大数据量:用于验证虚拟滚动真的开启
|
||
// 后两列带 editor:「编辑行被虚拟滚动滚出窗口 → 编辑器卸载 → 焦点掉到 body」这条
|
||
// 路径只能在这种「行数多到会虚拟化」的表上复现(见 e2e/table.spec.ts 的键盘跳格用例)
|
||
const bigTableColumns = [
|
||
{ dataIndex: 'id', title: 'ID', width: 80 },
|
||
{ dataIndex: 'name', title: '名称', width: 200, editor: { type: 'text' } },
|
||
{ dataIndex: 'value', title: '数值', width: 140, align: 'right', editor: { type: 'number' } },
|
||
]
|
||
const bigTableData = Array.from({ length: 5000 }, (_, i) => ({
|
||
id: i + 1,
|
||
name: `记录 ${i + 1}`,
|
||
value: (i + 1) * 7,
|
||
}))
|
||
|
||
/**
|
||
* 纯展示表(列上**都没有** `editor`)—— 「不可改灰底」门控的宿主。
|
||
*
|
||
* 灰底的语义是「这一格现在改不了」,只有**旁边存在可改的格子**时才是信息。
|
||
* 这类表若逐格铺灰,整张表看上去就是「禁用状态」(文档里 6/7 张 demo 正是纯展示表)。
|
||
* 判据在 G3Table 的 editableColumnsExist / shouldMarkReadonly,见那边注释。
|
||
*/
|
||
const plainColumns = [
|
||
{ dataIndex: 'code', title: '编号', width: 120 },
|
||
{ dataIndex: 'name', title: '名称', width: 200 },
|
||
{ dataIndex: 'remark', title: '备注', width: 200 },
|
||
]
|
||
const plainData = ref([
|
||
{ code: 'A-001', name: '纯展示行', remark: '这一列也不可编辑' },
|
||
{ code: 'A-002', name: '纯展示行 2', remark: '这一列也不可编辑' },
|
||
])
|
||
|
||
/* ---- Table:固定列 + 校验态(几何回归用例的宿主)---- */
|
||
/**
|
||
* 为什么单独造一份数据:
|
||
* 「校验未通过的格子刚好在固定列上」这个组合出过一次**只有真浏览器才量得出**的
|
||
* 位移 —— 校验态样式给 td 的 `position: relative` 覆盖了库的固定列定位,
|
||
* 内联的 `left: 200px` 于是从「sticky 吸附偏移」变成普通相对位移,
|
||
* 整格被推开「前面所有左固定列宽度之和」。happy-dom 没有布局引擎,锁不住这类问题。
|
||
*
|
||
* 两个刻意之处:
|
||
* · 第 2 行名称为空 → 固定列上必然出现一个 error 格;
|
||
* · 列宽总和(1680px)超过视口宽度 → 必须横向滚动,固定列才有「固定」可言。
|
||
*/
|
||
const fixedTableColumns = [
|
||
{ dataIndex: 'code', title: '编号(左固定)', width: 200, fixed: 'left' },
|
||
{
|
||
dataIndex: 'name',
|
||
title: '名称(校验列)',
|
||
width: 200,
|
||
fixed: 'left',
|
||
editor: {
|
||
type: 'text',
|
||
validate: (v: unknown) => String(v ?? '').length > 0 || '名称不能为空',
|
||
},
|
||
},
|
||
// 填充列:把总宽顶出视口,否则固定列与普通列长得一模一样
|
||
...Array.from({ length: 6 }, (_, i) => ({
|
||
dataIndex: `extra${i}`,
|
||
title: `填充列 ${i + 1}`,
|
||
width: 180,
|
||
})),
|
||
{ dataIndex: 'remark', title: '备注(右固定)', width: 200, fixed: 'right' },
|
||
]
|
||
const fixedRowFillers = (row: number) =>
|
||
Object.fromEntries(Array.from({ length: 6 }, (_, k) => [`extra${k}`, `填充值 ${k + 1}-${row}`]))
|
||
const fixedTableData = [
|
||
{ id: 1, code: 'A-001', name: '正常行', remark: '已归档', ...fixedRowFillers(1) },
|
||
// 空名称 → 这一格必然处于 error 态
|
||
{ id: 2, code: 'A-002', name: '', remark: '待复核', ...fixedRowFillers(2) },
|
||
]
|
||
/**
|
||
* 当前行按 fms 那套写法交给调用方管(`:active-row-key` + `@row-click`)。
|
||
*
|
||
* 这张表专门用来锁「受控调用方点格进入编辑 → 当前行也要高亮」——
|
||
* 曾经因为浏览器在「单元格 DOM 被替换」时不派发 click,`row-click` 永不触发,
|
||
* 调用方这个唯一写入者收不到消息,整行不高亮(见 G3Table 的 handleAreaSelectionChange)。
|
||
*/
|
||
const fixedActiveKey = ref<string | null>(null)
|
||
|
||
return {
|
||
rtl,
|
||
toggleRtl,
|
||
tableColumns,
|
||
tableData,
|
||
tableSelectedKeys,
|
||
bigTableColumns,
|
||
bigTableData,
|
||
plainColumns,
|
||
plainData,
|
||
fixedTableColumns,
|
||
fixedTableData,
|
||
fixedActiveKey,
|
||
model,
|
||
tab,
|
||
seg,
|
||
time,
|
||
modalOpen,
|
||
modalConfirmOpen,
|
||
drawerOpen,
|
||
drawerPlacement,
|
||
openDrawer,
|
||
popoverAOpen,
|
||
popoverBOpen,
|
||
color,
|
||
captchaPassed,
|
||
captchaDisabled,
|
||
richHtml,
|
||
flatTreeData,
|
||
treeMenuItems,
|
||
roleOptions: ROLE_OPTIONS,
|
||
treeData: TREE_DATA,
|
||
longText: LONG_TEXT,
|
||
toast,
|
||
notify,
|
||
openCommandConfirm,
|
||
}
|
||
},
|
||
template: `
|
||
<div class="page">
|
||
<div class="row" style="padding: 8px 0">
|
||
<G3Button size="small" data-testid="toggle-rtl" @click="toggleRtl">
|
||
dir: {{ rtl ? 'rtl' : 'ltr' }}
|
||
</G3Button>
|
||
</div>
|
||
|
||
<section id="s-basic">
|
||
<h2>基础:Button / Tag / Spin / Segmented / Space</h2>
|
||
<div class="row">
|
||
<G3Button data-testid="btn-primary">主要</G3Button>
|
||
<G3Button type="secondary">次要</G3Button>
|
||
<G3Button type="outline">描边</G3Button>
|
||
<G3Button type="ghost">幽灵</G3Button>
|
||
<G3Button type="danger">危险</G3Button>
|
||
<G3Button type="danger-outline">危险描边</G3Button>
|
||
<G3Button type="link">链接</G3Button>
|
||
<G3Button type="icon" aria-label="新增"><Plus /></G3Button>
|
||
<G3Button size="small">小</G3Button>
|
||
<G3Button size="large">大</G3Button>
|
||
<G3Button loading>加载中</G3Button>
|
||
<G3Button disabled>禁用</G3Button>
|
||
</div>
|
||
<div class="row">
|
||
<G3ButtonGroup data-testid="btn-group">
|
||
<G3Button type="outline">左</G3Button>
|
||
<G3ButtonGroupSeparator />
|
||
<G3Button type="outline">中</G3Button>
|
||
<G3ButtonGroupSeparator />
|
||
<G3Button type="outline">右</G3Button>
|
||
</G3ButtonGroup>
|
||
<!-- 无分隔线的连续按钮组:相邻边框重叠,hover 时被压住的那条必须可见 -->
|
||
<G3ButtonGroup data-testid="btn-group-plain">
|
||
<G3Button type="outline">第一</G3Button>
|
||
<G3Button type="outline">第二</G3Button>
|
||
<G3Button type="outline">第三</G3Button>
|
||
</G3ButtonGroup>
|
||
<G3ButtonGroup orientation="vertical" data-testid="btn-group-vertical">
|
||
<G3Button type="outline">上</G3Button>
|
||
<G3Button type="outline">中</G3Button>
|
||
<G3Button type="outline">下</G3Button>
|
||
</G3ButtonGroup>
|
||
<G3Tag variant="default">default</G3Tag>
|
||
<G3Tag variant="secondary">secondary</G3Tag>
|
||
<G3Tag variant="outline">outline</G3Tag>
|
||
<G3Tag variant="success">success</G3Tag>
|
||
<G3Tag variant="danger">danger</G3Tag>
|
||
<G3Tag variant="ghost">ghost</G3Tag>
|
||
<G3Tag variant="success" closable data-testid="tag-closable">可关闭</G3Tag>
|
||
<G3Spin data-testid="spin" />
|
||
</div>
|
||
<div class="row">
|
||
<G3Segmented v-model="seg" :options="['日', '周', '月']" data-testid="segmented" />
|
||
<G3Space :size="8"><G3Tag>间距</G3Tag><G3Tag>测试</G3Tag></G3Space>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="s-input">
|
||
<h2>Input(含 textarea 与拖拽手柄)</h2>
|
||
<div class="row">
|
||
<G3Input v-model="model.name" data-testid="input-basic" placeholder="默认" style="width: 180px" />
|
||
<G3Input v-model="model.name" type="password" data-testid="input-password" style="width: 180px" />
|
||
<G3Input v-model="model.level" type="number" data-testid="input-number" style="width: 120px" />
|
||
<G3Input v-model="model.name" clearable data-testid="input-clear" style="width: 180px" />
|
||
<G3Input v-model="model.name" show-count :maxlength="20" style="width: 180px" />
|
||
<G3Input v-model="model.name" status="error" style="width: 160px" />
|
||
<G3Input v-model="model.name" disabled style="width: 160px" />
|
||
<G3Input v-model="model.name" placeholder="带前缀">
|
||
<template #prefix><Search /></template>
|
||
</G3Input>
|
||
</div>
|
||
<div class="row">
|
||
<G3Input v-model="model.intro" type="textarea" :rows="3" data-testid="textarea-basic" style="width: 300px" />
|
||
<G3Input
|
||
v-model="model.intro"
|
||
type="textarea"
|
||
:rows="3"
|
||
show-count
|
||
:maxlength="100"
|
||
data-testid="textarea-count"
|
||
style="width: 300px"
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="s-color-picker">
|
||
<h2>ColorPicker</h2>
|
||
<div class="row">
|
||
<!-- 注意:不给 G3ColorPicker 挂 data-testid —— 它的根是 G3Popover(多根:
|
||
触发器 + Teleport),多根组件不做 attr 透传,testid 会被 Vue 丢弃。
|
||
E2E 用 .g3-color-picker 类名定位。 -->
|
||
<G3ColorPicker v-model="color" />
|
||
<G3ColorPicker v-model="color" :presets="false" />
|
||
<G3ColorPicker v-model="color" size="small" />
|
||
<G3ColorPicker v-model="color" disabled />
|
||
</div>
|
||
</section>
|
||
|
||
<section id="s-form">
|
||
<h2>Form / Select / Date / Switch / Radio / Checkbox</h2>
|
||
<G3Form :model="model" :label-width="90" data-testid="form">
|
||
<G3FormItem name="name" label="名称"><G3Input v-model="model.name" /></G3FormItem>
|
||
<G3FormItem name="role" label="角色">
|
||
<G3Select v-model="model.role" :options="roleOptions" data-testid="select-basic" placeholder="请选择" />
|
||
</G3FormItem>
|
||
<G3FormItem name="city" label="城市">
|
||
<G3Select
|
||
v-model="model.city"
|
||
:options="roleOptions"
|
||
multiple
|
||
data-testid="select-multiple"
|
||
placeholder="多选"
|
||
/>
|
||
</G3FormItem>
|
||
<G3FormItem name="date" label="日期时间">
|
||
<G3DatePicker v-model="model.date" format="YYYY-MM-DD HH:mm:ss" data-testid="date-picker" />
|
||
</G3FormItem>
|
||
<G3FormItem name="range" label="日期区间">
|
||
<G3RangePicker v-model="model.range" format="YYYY-MM-DD" data-testid="range-picker" />
|
||
</G3FormItem>
|
||
<G3FormItem name="intro" label="简介">
|
||
<G3Input v-model="model.intro" type="textarea" :rows="3" />
|
||
</G3FormItem>
|
||
<G3FormItem name="agree" label="开关"><G3Switch v-model="model.agree" data-testid="switch" /></G3FormItem>
|
||
<G3FormItem name="gender" label="单选">
|
||
<G3RadioGroup v-model="model.gender">
|
||
<G3Radio value="male">男</G3Radio>
|
||
<G3Radio value="female">女</G3Radio>
|
||
</G3RadioGroup>
|
||
</G3FormItem>
|
||
<G3FormItem name="level" label="复选">
|
||
<G3Checkbox :model-value="true">已选中</G3Checkbox>
|
||
<G3Checkbox :model-value="false">未选中</G3Checkbox>
|
||
<G3Checkbox :model-value="false" disabled>禁用</G3Checkbox>
|
||
</G3FormItem>
|
||
</G3Form>
|
||
<G3FormGroup title="分组(可折叠)" style="margin-top: 12px" data-testid="form-group">
|
||
<G3FormItem name="name" label="组内字段"><G3Input v-model="model.name" /></G3FormItem>
|
||
</G3FormGroup>
|
||
</section>
|
||
|
||
<section id="s-captcha">
|
||
<h2>SliderCaptcha 滑块验证码</h2>
|
||
<div class="row" style="align-items: flex-start">
|
||
<div style="width: 320px">
|
||
<G3SliderCaptcha v-model="captchaPassed" data-testid="slider-captcha" />
|
||
<div data-testid="slider-captcha-state" style="margin-top: 8px; font-size: 12px">
|
||
{{ captchaPassed ? 'passed' : 'idle' }}
|
||
</div>
|
||
</div>
|
||
<div style="width: 320px">
|
||
<G3SliderCaptcha v-model="captchaDisabled" disabled data-testid="slider-captcha-disabled" />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="s-data">
|
||
<h2>Tabs / Tree / Pagination / Splitter / Scrollbar / TimeSelect</h2>
|
||
<div class="row" style="align-items: flex-start">
|
||
<div style="width: 380px">
|
||
<!-- data-testid 挂在包裹层:G3Tabs / G3Tree 是多根或 Teleport 根节点,
|
||
直接挂在组件上会触发 Vue 的 attrs 继承警告(E2E 仍可用它定位) -->
|
||
<div data-testid="tabs">
|
||
<G3Tabs v-model="tab">
|
||
<G3TabPane tab-key="a" label="标签一">标签一的内容</G3TabPane>
|
||
<G3TabPane tab-key="b" label="标签二">标签二的内容</G3TabPane>
|
||
<G3TabPane tab-key="c" label="禁用" disabled>不可见</G3TabPane>
|
||
</G3Tabs>
|
||
</div>
|
||
<G3Tabs v-model="tab" placement="left" type="line">
|
||
<G3TabPane tab-key="a" label="左侧一">左侧标签页内容</G3TabPane>
|
||
<G3TabPane tab-key="b" label="左侧二">第二个面板</G3TabPane>
|
||
</G3Tabs>
|
||
<G3Tabs v-model="tab" placement="right" type="line">
|
||
<G3TabPane tab-key="a" label="右侧一">右侧标签页内容</G3TabPane>
|
||
<G3TabPane tab-key="b" label="右侧二">第二个面板</G3TabPane>
|
||
</G3Tabs>
|
||
</div>
|
||
<div style="width: 280px">
|
||
<div data-testid="tree">
|
||
<G3Tree :tree-data="treeData" checkable default-expand-all block-node />
|
||
</div>
|
||
<!-- 右键菜单:两个节点都是叶子,验证叶子保留(隐藏的)箭头占位列 -->
|
||
<G3Tree :tree-data="flatTreeData" :context-menu="treeMenuItems" />
|
||
<!-- 拖拽排序:长按 400ms 才武装,避免手抖 -->
|
||
<G3Tree :tree-data="treeData" draggable default-expand-all />
|
||
</div>
|
||
<div style="width: 240px">
|
||
<G3TimeSelect v-model="time" data-testid="time-select" />
|
||
</div>
|
||
</div>
|
||
<div style="margin-top: 12px">
|
||
<G3Pagination :total="126" show-total show-size-changer show-quick-jumper data-testid="pagination" />
|
||
</div>
|
||
<div style="margin-top: 12px; height: 120px; display: flex; gap: 12px">
|
||
<G3Splitter style="height: 120px; flex: 1" data-testid="splitter">
|
||
<G3SplitterPanel :default-size="'40%'"><div class="box">左</div></G3SplitterPanel>
|
||
<G3SplitterPanel><div class="box">右</div></G3SplitterPanel>
|
||
</G3Splitter>
|
||
<!-- auto-hide=never:滚动条常驻,E2E 才能稳定点到轨道 -->
|
||
<G3Scrollbar
|
||
style="height: 120px; width: 220px; border: 1px solid #e5e7eb"
|
||
auto-hide="never"
|
||
data-testid="scrollbar"
|
||
>
|
||
<pre style="margin: 0; padding: 8px">{{ longText }}</pre>
|
||
</G3Scrollbar>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="s-overlay">
|
||
<h2>Dropdown / Tooltip / Popover(首两个挂载即展开,用于层级回归)</h2>
|
||
<div class="row">
|
||
<G3Dropdown data-testid="dropdown">
|
||
<template #trigger><G3Button type="outline">下拉菜单</G3Button></template>
|
||
<G3DropdownItem item-key="edit">编辑</G3DropdownItem>
|
||
<G3DropdownItem item-key="copy">复制</G3DropdownItem>
|
||
<G3DropdownItem divider />
|
||
<G3DropdownItem item-key="del" danger>删除</G3DropdownItem>
|
||
</G3Dropdown>
|
||
<G3Tooltip content="这是一段提示文案">
|
||
<G3Button type="outline" data-testid="tooltip-trigger">悬停提示</G3Button>
|
||
</G3Tooltip>
|
||
<G3Popover v-model:open="popoverAOpen" trigger="manual" placement="bottom-start" data-testid="popover-a">
|
||
<template #trigger><G3Button type="outline">初始展开 A</G3Button></template>
|
||
<template #content><div style="width: 120px">浮层 A</div></template>
|
||
</G3Popover>
|
||
<G3Popover v-model:open="popoverBOpen" trigger="manual" placement="bottom-start" data-testid="popover-b">
|
||
<template #trigger><G3Button type="outline">初始展开 B</G3Button></template>
|
||
<template #content><div style="width: 120px">浮层 B</div></template>
|
||
</G3Popover>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="s-richtext">
|
||
<h2>RichText 富文本编辑器</h2>
|
||
<G3RichText v-model="richHtml" data-testid="rich-text" placeholder="请输入正文…" />
|
||
</section>
|
||
|
||
<section id="s-feedback">
|
||
<h2>Modal / Drawer / Message / Notification</h2>
|
||
<div class="row">
|
||
<G3Button data-testid="open-modal" @click="modalOpen = true">打开 Modal</G3Button>
|
||
<G3Button data-testid="open-drawer" @click="openDrawer('right')">右侧 Drawer</G3Button>
|
||
<G3Button data-testid="open-drawer-left" @click="openDrawer('left')">左侧 Drawer</G3Button>
|
||
<G3Button data-testid="open-drawer-top" @click="openDrawer('top')">上方 Drawer</G3Button>
|
||
<G3Button data-testid="open-drawer-bottom" @click="openDrawer('bottom')">下方 Drawer</G3Button>
|
||
<G3Button data-testid="open-confirm" @click="openCommandConfirm">Modal.confirm</G3Button>
|
||
<G3Button data-testid="toast-default" @click="toast()">Message 默认</G3Button>
|
||
<G3Button data-testid="toast-top-left" @click="toast('top-left')">Message 左上</G3Button>
|
||
<G3Button data-testid="toast-bottom-right" @click="toast('bottom-right')">Message 右下</G3Button>
|
||
<G3Button data-testid="notify" @click="notify">Notification</G3Button>
|
||
</div>
|
||
|
||
<G3Modal v-model:open="modalOpen" title="对话框标题" data-testid="modal">
|
||
<p>第一段内容。</p>
|
||
<G3Select v-model="model.role" :options="roleOptions" data-testid="modal-select" placeholder="弹窗内选择器" />
|
||
<p v-for="i in 20" :key="i">第 {{ i }} 段内容,用于验证缩放时内容区是否内部滚动。</p>
|
||
</G3Modal>
|
||
|
||
<G3Drawer v-model:open="drawerOpen" :placement="drawerPlacement" title="抽屉标题" data-testid="drawer">
|
||
<p>抽屉内容</p>
|
||
<G3Select v-model="model.role" :options="roleOptions" data-testid="drawer-select" placeholder="抽屉内选择" />
|
||
</G3Drawer>
|
||
</section>
|
||
|
||
<section id="s-table">
|
||
<h2>Table 表格</h2>
|
||
|
||
<!-- 小数据量(3 行):行数 ≤ 100,纵向虚拟滚动应当关闭 -->
|
||
<div data-testid="table-wrap" style="height: 320px">
|
||
<G3Table
|
||
data-testid="table"
|
||
v-model:columns="tableColumns"
|
||
:data-source="tableData"
|
||
:row-selection="{ selectedRowKeys: tableSelectedKeys, onChange: (keys) => (tableSelectedKeys = keys) }"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 大数据量(5000 行):纵向虚拟滚动应当开启;容器限高以产生滚动。
|
||
顺带开斑马纹:纵向虚拟时库改取 nth-child(odd)(DOM 首行≈偶数数据行),
|
||
冻结列的条纹叠色必须跟着同一套 nth-child 走(见 _overrides.scss 的 stripe 段)。 -->
|
||
<div data-testid="table-big-wrap" style="height: 360px; margin-top: 16px">
|
||
<G3Table
|
||
data-testid="table-big"
|
||
:columns="bigTableColumns"
|
||
:data-source="bigTableData"
|
||
:seq="false"
|
||
:row-height="30"
|
||
stripe
|
||
/>
|
||
</div>
|
||
|
||
<!-- 窄容器:验证列宽铺满与省略号 -->
|
||
<div data-testid="table-narrow-wrap" style="width: 480px; height: 240px; margin-top: 16px">
|
||
<G3Table
|
||
data-testid="table-narrow"
|
||
:columns="tableColumns"
|
||
:data-source="tableData"
|
||
:seq="false"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 固定列 + 校验态 + 受控当前行:几何与「点格进编辑要高亮」的回归宿主 -->
|
||
<div data-testid="table-fixed-wrap" style="height: 240px; margin-top: 16px">
|
||
<G3Table
|
||
data-testid="table-fixed"
|
||
:columns="fixedTableColumns"
|
||
:data-source="fixedTableData"
|
||
:active-row-key="fixedActiveKey"
|
||
fixed-col-shadow
|
||
@row-click="(_, row) => (fixedActiveKey = String(row.id))"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 纯展示表(列上都没有 editor):不该铺「不可改」灰底 -->
|
||
<div data-testid="table-plain-wrap" style="height: 200px; margin-top: 16px">
|
||
<G3Table data-testid="table-plain" :columns="plainColumns" :data-source="plainData" />
|
||
</div>
|
||
|
||
<!-- 关闭编辑(editable: false),但列上声明了 editor:同样不该铺灰底 -->
|
||
<div data-testid="table-noedit-wrap" style="height: 200px; margin-top: 16px">
|
||
<G3Table
|
||
data-testid="table-noedit"
|
||
:columns="tableColumns"
|
||
:data-source="tableData"
|
||
:editable="false"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 斑马纹(stripe):固定列(序号列)也要跟着上条纹。
|
||
用纯展示列(没有 editor)当宿主,避免「不可改灰底」盖住条纹干扰断言。 -->
|
||
<div data-testid="table-stripe-wrap" style="height: 200px; margin-top: 16px">
|
||
<G3Table data-testid="table-stripe" :columns="plainColumns" :data-source="plainData" stripe />
|
||
</div>
|
||
</section>
|
||
</div>
|
||
`,
|
||
}
|
||
|
||
createApp(App).mount('#app')
|