421 lines
16 KiB
TypeScript
421 lines
16 KiB
TypeScript
import { createApp, reactive, ref } from 'vue'
|
|
import { Plus, Search, Settings } from '@lucide/vue'
|
|
import {
|
|
G3Button,
|
|
G3ButtonGroup,
|
|
G3ButtonGroupSeparator,
|
|
G3Checkbox,
|
|
G3Col,
|
|
G3DatePicker,
|
|
G3Drawer,
|
|
G3Dropdown,
|
|
G3DropdownItem,
|
|
G3Form,
|
|
G3FormGroup,
|
|
G3FormItem,
|
|
G3Input,
|
|
G3Message,
|
|
G3Modal,
|
|
G3Notification,
|
|
G3Pagination,
|
|
G3Popover,
|
|
G3Radio,
|
|
G3RadioGroup,
|
|
G3RangePicker,
|
|
G3Row,
|
|
G3Scrollbar,
|
|
G3Segmented,
|
|
G3Select,
|
|
G3Space,
|
|
G3Spin,
|
|
G3Splitter,
|
|
G3SplitterPanel,
|
|
G3Switch,
|
|
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,
|
|
G3DatePicker,
|
|
G3Drawer,
|
|
G3Dropdown,
|
|
G3DropdownItem,
|
|
G3Form,
|
|
G3FormGroup,
|
|
G3FormItem,
|
|
G3Input,
|
|
G3Modal,
|
|
G3Pagination,
|
|
G3Popover,
|
|
G3Radio,
|
|
G3RadioGroup,
|
|
G3RangePicker,
|
|
G3Row,
|
|
G3Scrollbar,
|
|
G3Segmented,
|
|
G3Select,
|
|
G3Space,
|
|
G3Spin,
|
|
G3Splitter,
|
|
G3SplitterPanel,
|
|
G3Switch,
|
|
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)
|
|
|
|
// 扁平树:两个节点都不是父节点,用来验「叶子不该留展开箭头占位」
|
|
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)),
|
|
})
|
|
}
|
|
|
|
return {
|
|
model,
|
|
tab,
|
|
seg,
|
|
time,
|
|
modalOpen,
|
|
modalConfirmOpen,
|
|
drawerOpen,
|
|
drawerPlacement,
|
|
openDrawer,
|
|
popoverAOpen,
|
|
popoverBOpen,
|
|
flatTreeData,
|
|
treeMenuItems,
|
|
roleOptions: ROLE_OPTIONS,
|
|
treeData: TREE_DATA,
|
|
longText: LONG_TEXT,
|
|
toast,
|
|
notify,
|
|
openCommandConfirm,
|
|
}
|
|
},
|
|
template: `
|
|
<div class="page">
|
|
<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" allow-clear 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-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-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-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>
|
|
</div>
|
|
`,
|
|
}
|
|
|
|
createApp(App).mount('#app')
|