import { createApp, reactive, ref } from 'vue'
import { Plus, Search, Settings } from '@lucide/vue'
import {
G3Attachment,
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,
G3TabPane,
G3Tabs,
G3Tag,
G3TimeSelect,
G3Tooltip,
G3Tree,
} from '@g3soft/ui'
import type { AttachmentCategory, AttachmentItem } 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: {
G3Attachment,
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,
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('
标题
这是加粗、斜体、上标、下标的正文。
')
// RTL 开关:直接翻 。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((resolve) => setTimeout(resolve, 600)),
})
}
/* ---- Attachment 附件管理器 ---- */
const attachmentFiles = ref([
{ id: 1, name: '2026年度框架合同.pdf', size: 512_000, categoryId: 'contract' },
{ id: 2, name: '供应商报价明细.xlsx', size: 86_000, categoryId: 'invoice' },
{ id: 3, name: '现场照片.jpg', size: 1_240_000, categoryId: 'photo' },
{ id: 4, name: '验收纪要.docx', size: 24_000, categoryId: 'contract' },
{ id: 5, name: '进度汇报.pptx', size: 640_000, categoryId: 'photo' },
{ id: 6, name: '现场记录.zip', size: 8_192_000, categoryId: 'invoice' },
])
const attachmentCategories: AttachmentCategory[] = [
{ id: 'contract', name: '合同文件' },
{ id: 'invoice', name: '发票凭证' },
{ id: 'photo', name: '现场照片' },
]
return {
attachmentFiles,
attachmentCategories,
rtl,
toggleRtl,
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: `
dir: {{ rtl ? 'rtl' : 'ltr' }}
基础:Button / Tag / Spin / Segmented / Space
主要
次要
描边
幽灵
危险
危险描边
链接
小
大
加载中
禁用
左
中
右
第一
第二
第三
上
中
下
default
secondary
outline
success
danger
ghost
可关闭
间距测试
SliderCaptcha 滑块验证码
{{ captchaPassed ? 'passed' : 'idle' }}
Tabs / Tree / Pagination / Splitter / Scrollbar / TimeSelect
标签一的内容
标签二的内容
不可见
左侧标签页内容
第二个面板
右侧标签页内容
第二个面板
Dropdown / Tooltip / Popover(首两个挂载即展开,用于层级回归)
下拉菜单
编辑
复制
删除
悬停提示
初始展开 A
浮层 A
初始展开 B
浮层 B
Modal / Drawer / Message / Notification
打开 Modal
右侧 Drawer
左侧 Drawer
上方 Drawer
下方 Drawer
Modal.confirm
Message 默认
Message 左上
Message 右下
Notification
第一段内容。
第 {{ i }} 段内容,用于验证缩放时内容区是否内部滚动。
抽屉内容
`,
}
createApp(App).mount('#app')