@_tc/template-core 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.skills/tc-component-usage-skills/SKILL.md +110 -0
- package/.skills/tc-component-usage-skills/reference/component-api.md +774 -0
- package/.skills/tc-component-usage-skills/reference/examples.md +425 -0
- package/.skills/tc-component-usage-skills/reference/patterns.md +357 -0
- package/README.md +12 -5
- package/cjs/bundler/utils.js +1 -1
- package/esm/bundler/utils.js +1 -0
- package/fe/frontend/src/hooks/useCurrentMenuData.js +1 -1
- package/fe/packages/ui/react/components/Menu/SubMenu.js +1 -1
- package/fe/packages/ui/react/components/testPage/index.js +114 -19
- package/package.json +1 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# Component Patterns
|
|
2
|
+
|
|
3
|
+
Recurring patterns and best practices for `@tc/ui/react`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Form Patterns
|
|
8
|
+
|
|
9
|
+
### SchemaForm: Data-Driven Forms
|
|
10
|
+
|
|
11
|
+
The standard way to build forms. Define schemas, not JSX:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
const [form] = useForm<UserForm>()
|
|
15
|
+
|
|
16
|
+
<SchemaForm<UserForm>
|
|
17
|
+
form={form}
|
|
18
|
+
schemas={[
|
|
19
|
+
{ key: 'username', label: 'Username', type: 'input', required: true },
|
|
20
|
+
{ key: 'email', label: 'Email', type: 'input',
|
|
21
|
+
rules: [{ type: 'email', message: 'Invalid email' }] },
|
|
22
|
+
{ key: 'role', label: 'Role', type: 'select',
|
|
23
|
+
fieldProps: { options: [{ label: 'Admin', value: 'admin' }, { label: 'User', value: 'user' }] } },
|
|
24
|
+
{ key: 'active', label: 'Active', type: 'switch' },
|
|
25
|
+
]}
|
|
26
|
+
onFinish={handleSubmit}
|
|
27
|
+
/>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### SchemaForm Layouts
|
|
31
|
+
|
|
32
|
+
- **`layout='vertical'`** (default): Label above field. Footer buttons auto-rendered.
|
|
33
|
+
- **`layout='horizontal'`**: Label beside field. **Footer buttons NOT auto-rendered** — provide via `footerButtons` prop or handle externally.
|
|
34
|
+
- **Per-field override**: Set `layout` on individual schema items via `fieldLayout`.
|
|
35
|
+
|
|
36
|
+
### Registering Custom SchemaForm Types
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { registerComponents } from '@tc/scalability/SchemaForm/data'
|
|
40
|
+
|
|
41
|
+
registerComponents({
|
|
42
|
+
richText: RichTextEditor,
|
|
43
|
+
userSelector: UserSelect,
|
|
44
|
+
})
|
|
45
|
+
// Now { type: 'richText', ... } works in schemas
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### FormItem: Render Function Pattern
|
|
49
|
+
|
|
50
|
+
When you need custom logic inside a form field:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<FormItem label="Tags" name="tags">
|
|
54
|
+
{(value, onChange) => (
|
|
55
|
+
<TagInput value={value} onChange={(tags) => onChange(tags)} />
|
|
56
|
+
)}
|
|
57
|
+
</FormItem>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### FormItem: Automatic Child Injection
|
|
61
|
+
|
|
62
|
+
`FormItem` auto-clones children to inject `value`/`onChange`. It handles Input's two-arg `onChange(value, event)` signature correctly.
|
|
63
|
+
|
|
64
|
+
### SubmitButton for Async Forms
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<SubmitButton
|
|
68
|
+
onClick={async () => {
|
|
69
|
+
await submitToApi(form.getFieldsValue())
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
Save
|
|
73
|
+
</SubmitButton>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Auto-shows loading spinner during async execution.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## DataTable Patterns
|
|
81
|
+
|
|
82
|
+
### Basic Column Definition
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
const columns: TableColumnDef<User>[] = [
|
|
86
|
+
{ key: 'id', title: 'ID', dataIndex: 'id', width: 80 },
|
|
87
|
+
{ key: 'name', title: 'Name', dataIndex: 'name' },
|
|
88
|
+
{ key: 'status', title: 'Status', dataIndex: 'status',
|
|
89
|
+
render: (value) => <Badge status={value} /> },
|
|
90
|
+
{ key: 'action', title: 'Actions', type: 'action',
|
|
91
|
+
actions: [
|
|
92
|
+
{ key: 'edit', label: 'Edit', onClick: (record) => openEdit(record) },
|
|
93
|
+
{ key: 'delete', label: 'Delete', danger: true, onClick: (record) => onDelete(record) },
|
|
94
|
+
] },
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Action Column Behavior
|
|
99
|
+
|
|
100
|
+
`ActionBtn` handles the action column rendering:
|
|
101
|
+
- **0 actions**: Shows `'-'`
|
|
102
|
+
- **1 action**: Single button
|
|
103
|
+
- **Multiple**: Uses overflow mode:
|
|
104
|
+
- `'auto'` (default): Uses ResizeObserver to detect fit; falls back to dropdown
|
|
105
|
+
- `'dropdown'`: Always shows "..." dropdown button
|
|
106
|
+
- `'inline'`: Shows all buttons inline (only for `DropdownBaseItem`)
|
|
107
|
+
|
|
108
|
+
### Pagination
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// Let DataTable handle pagination internally
|
|
112
|
+
<DataTable columns={columns} data={data} />
|
|
113
|
+
|
|
114
|
+
// Control pagination externally
|
|
115
|
+
<DataTable
|
|
116
|
+
columns={columns}
|
|
117
|
+
data={data}
|
|
118
|
+
pagination={{ current: 1, pageSize: 20, total: 100, onChange: fetchPage }}
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Row Selection
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<DataTable
|
|
126
|
+
columns={columns}
|
|
127
|
+
data={data}
|
|
128
|
+
rowSelection={{
|
|
129
|
+
selectedRowKeys: selectedIds,
|
|
130
|
+
onChange: (keys) => setSelectedIds(keys),
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Supports checkboxes, indeterminate (partial) state, page-level select-all.
|
|
136
|
+
|
|
137
|
+
### Sticky Columns
|
|
138
|
+
|
|
139
|
+
Action columns are auto-sticky right. Set `fixed: 'left'` or `fixed: 'right'` on other columns. IntersectionObserver-based shadow effects when scrolled.
|
|
140
|
+
|
|
141
|
+
### Loading State
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
<DataTable columns={columns} data={data} loading={isLoading} />
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Shows skeleton overlay in `tableloading` mode while `loading={true}`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## TableSearch Pattern
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<TableSearch<UserSearch>
|
|
155
|
+
schemas={[
|
|
156
|
+
{ key: 'name', label: 'Name', type: 'input', isPermanent: true },
|
|
157
|
+
{ key: 'status', label: 'Status', type: 'select',
|
|
158
|
+
fieldProps: { options: statusOptions }, isPermanent: true },
|
|
159
|
+
{ key: 'createdAt', label: 'Created', type: 'date' },
|
|
160
|
+
{ key: 'email', label: 'Email', type: 'input' },
|
|
161
|
+
]}
|
|
162
|
+
onSearch={(values) => fetchData(values)}
|
|
163
|
+
onReset={() => fetchData({})}
|
|
164
|
+
onExport={(type, searchData) => exportData(type, searchData)}
|
|
165
|
+
exportMenuItems={exportItems}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`isPermanent: true` fields always show. Others hidden when collapsed. `expandBoundaries` (default 4) controls permanent field threshold.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Overlay Patterns
|
|
174
|
+
|
|
175
|
+
### Modal with Form
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
const [open, setOpen] = useState(false)
|
|
179
|
+
const [form] = useForm()
|
|
180
|
+
|
|
181
|
+
<Modal open={open} onClose={() => setOpen(false)} title="Create User"
|
|
182
|
+
footer={
|
|
183
|
+
<div className="flex justify-end gap-2">
|
|
184
|
+
<Button onClick={() => setOpen(false)}>Cancel</Button>
|
|
185
|
+
<SubmitButton onClick={() => form.submit()}>Save</SubmitButton>
|
|
186
|
+
</div>
|
|
187
|
+
}
|
|
188
|
+
>
|
|
189
|
+
<SchemaForm form={form} schemas={schemas} footerButtons={false}
|
|
190
|
+
onFinish={async (values) => { await createUser(values); setOpen(false); }} />
|
|
191
|
+
</Modal>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### ConfirmDialog for Simple Confirmations
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
<ConfirmDialog
|
|
198
|
+
open={confirmOpen}
|
|
199
|
+
title="Delete User"
|
|
200
|
+
content="This action cannot be undone. Continue?"
|
|
201
|
+
onOk={async () => { await deleteUser(id); setConfirmOpen(false); }}
|
|
202
|
+
onCancel={() => setConfirmOpen(false)}
|
|
203
|
+
/>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Imperative Confirm (modalManager)
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
modalManager.confirm({
|
|
210
|
+
title: 'Delete',
|
|
211
|
+
content: 'Are you sure?',
|
|
212
|
+
onOk: async () => { await deleteUser(id); },
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Drawer for Side Panels
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
<Drawer open={open} onClose={() => setOpen(false)} title="User Details"
|
|
220
|
+
placement="right" width={480}
|
|
221
|
+
>
|
|
222
|
+
<UserDetailContent userId={id} />
|
|
223
|
+
</Drawer>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Menu Patterns
|
|
229
|
+
|
|
230
|
+
### Declarative via items
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
<Menu
|
|
234
|
+
mode="left"
|
|
235
|
+
items={[
|
|
236
|
+
{ key: 'dashboard', label: 'Dashboard', icon: <HomeIcon /> },
|
|
237
|
+
{ key: 'users', label: 'Users', icon: <UsersIcon />,
|
|
238
|
+
children: [
|
|
239
|
+
{ key: 'users-list', label: 'All Users' },
|
|
240
|
+
{ key: 'users-roles', label: 'Roles' },
|
|
241
|
+
] },
|
|
242
|
+
]}
|
|
243
|
+
selectedKey={currentKey}
|
|
244
|
+
onSelect={(key) => navigate(key)}
|
|
245
|
+
/>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Collapsible Sidebar
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
<Menu mode="left" collapsed={collapsed} collapsedWidth={64} collapsedLabelTooltip>
|
|
252
|
+
<Menu.CollapseToggle ref={toggleRef} />
|
|
253
|
+
{/* items */}
|
|
254
|
+
</Menu>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
When collapsed without icon: shows first character of label. With `collapsedLabelTooltip`: hover shows full label via Popup.
|
|
258
|
+
|
|
259
|
+
### Top Mode Menu
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
<Menu mode="top" expandTrigger="hover">
|
|
263
|
+
<Menu.Item eventKey="home">Home</Menu.Item>
|
|
264
|
+
<Menu.SubMenu eventKey="products" label="Products" popup>
|
|
265
|
+
<Menu.Item eventKey="prod-list">List</Menu.Item>
|
|
266
|
+
<Menu.Item eventKey="prod-create">Create</Menu.Item>
|
|
267
|
+
</Menu.SubMenu>
|
|
268
|
+
</Menu>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
In `mode='top'`, SubMenus always use popup dropdowns.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Notification & Message Patterns
|
|
276
|
+
|
|
277
|
+
### Toast Messages
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
import { message } from '@tc/ui/react'
|
|
281
|
+
|
|
282
|
+
message.success('User created successfully')
|
|
283
|
+
message.error('Failed to save')
|
|
284
|
+
message.warning('Session expiring soon')
|
|
285
|
+
message.info('New update available')
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Notifications (Corner)
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
import { Notification } from '@tc/ui/react'
|
|
292
|
+
|
|
293
|
+
// As component
|
|
294
|
+
<Notification type="error" title="Upload Failed" message="Network error. Retry?" />
|
|
295
|
+
|
|
296
|
+
// To manage manually, render conditionally
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Layout Pattern
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
<Layout
|
|
305
|
+
header={<Header />}
|
|
306
|
+
sidebar={<SidebarMenu />}
|
|
307
|
+
footer={<Footer />}
|
|
308
|
+
>
|
|
309
|
+
<main className="p-6">
|
|
310
|
+
<Outlet />
|
|
311
|
+
</main>
|
|
312
|
+
</Layout>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Compound Component Pattern
|
|
318
|
+
|
|
319
|
+
Three components use compound patterns with Context:
|
|
320
|
+
|
|
321
|
+
1. **Menu**: `<Menu>` + `<Menu.Item>` + `<Menu.SubMenu>` + `<Menu.CollapseToggle>`
|
|
322
|
+
2. **Radio**: `<Radio.Group>` + `<Radio>`
|
|
323
|
+
3. **Breadcrumb**: `<Breadcrumb>` + `<BreadcrumbList>` + `<BreadcrumbItem>` + ...
|
|
324
|
+
|
|
325
|
+
Children auto-detect parent via Context and inherit configuration.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Controlled/Uncontrolled Convention
|
|
330
|
+
|
|
331
|
+
All input-like components follow this pattern:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// Internal pattern used by every input component:
|
|
335
|
+
const isControlled = props.value !== undefined
|
|
336
|
+
// → Controlled: use props.value + props.onChange
|
|
337
|
+
// → Uncontrolled: use internal useState(defaultValue)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
This means both patterns work identically:
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
// Controlled
|
|
344
|
+
<Select value={val} onChange={setVal} options={opts} />
|
|
345
|
+
|
|
346
|
+
// Uncontrolled
|
|
347
|
+
<Select defaultValue="foo" options={opts} />
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## CSS Conventions
|
|
353
|
+
|
|
354
|
+
- All CSS classes are prefixed `tc-ui-<component-name>`
|
|
355
|
+
- Tailwind CSS used for styling
|
|
356
|
+
- Theme via CSS custom properties: `hsl(var(--theme-primary))`, `hsl(var(--theme-background))`, etc.
|
|
357
|
+
- Customize by setting CSS variables or overriding Tailwind theme
|
package/README.md
CHANGED
|
@@ -16,18 +16,24 @@ pnpm add @_tc/template-core
|
|
|
16
16
|
|
|
17
17
|
## 可选:AI Agent Skill
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
包内附带两份 Agent Skill,本质是可复用的 Agent 指令包,采用渐进式披露(SKILL.md + reference/)结构。
|
|
20
|
+
|
|
21
|
+
| Skill | 用途 |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `tc-generator` | 按 TemplateCore 约定生成项目、model 配置、Schema CRUD、controller 和 router。 |
|
|
24
|
+
| `tc-component-usage-skills` | 指导 Agent 选用和正确使用内置 React 组件库(Button、Form、DataTable、Modal 等)。 |
|
|
20
25
|
|
|
21
26
|
如果你的 Agent 支持本地 skills 目录,可以从已安装的 npm 包复制安装。以 Codex 为例:
|
|
22
27
|
|
|
23
28
|
```bash
|
|
24
|
-
mkdir -p
|
|
25
|
-
cp -R node_modules/@_tc/template-core/.skills/tc-generator
|
|
29
|
+
mkdir -p “${CODEX_HOME:-$HOME/.codex}/skills”
|
|
30
|
+
cp -R node_modules/@_tc/template-core/.skills/tc-generator “${CODEX_HOME:-$HOME/.codex}/skills/tc-generator”
|
|
31
|
+
cp -R node_modules/@_tc/template-core/.skills/tc-component-usage-skills “${CODEX_HOME:-$HOME/.codex}/skills/tc-component-usage-skills”
|
|
26
32
|
```
|
|
27
33
|
|
|
28
|
-
安装后重启对应 Agent,让新 skill
|
|
34
|
+
安装后重启对应 Agent,让新 skill 生效。使用时可以直接说”用 tc-generator 生成一个商品管理模块”或”用 tc-component-usage-skills 帮我写一个带搜索的分页表格”。
|
|
29
35
|
|
|
30
|
-
其他 Agent 如果不支持自动安装 skill,也可以直接读取 `.skills/tc-generator/SKILL.md
|
|
36
|
+
其他 Agent 如果不支持自动安装 skill,也可以直接读取 `.skills/tc-generator/SKILL.md` 或 `.skills/tc-component-usage-skills/SKILL.md`,再按需读取 `reference/` 下的参考文件。
|
|
31
37
|
|
|
32
38
|
## 快速使用
|
|
33
39
|
|
|
@@ -1328,3 +1334,4 @@ buildFE('prod', {
|
|
|
1328
1334
|
- `model` 数组合并依赖稳定 `key`。
|
|
1329
1335
|
- Dashboard 默认需要 URL 带 `?projk=项目key`。
|
|
1330
1336
|
- 前端入口必须命名为 `.entry.tsx`、`.entry.ts`、`.entry.jsx` 或 `.entry.js`。
|
|
1337
|
+
- **使用方不建议在 `/frontend/apps/` 下保留 `html` 文件。** Vite 构建时会根据模板文件(`app/view/entry.tpl`)自动生成 html,如果保留 html 文件,可能与模板不同步而导致内容过时。
|
package/cjs/bundler/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.js`),t=require(`../packages/utils/path.js`);require(`../packages/utils/index.js`);const n=require(`../packages/common/log/index.js`),r=require(`./state.js`);let i=require(`path`);i=e.__toESM(i);let a=require(`glob`);a=e.__toESM(a);let o=require(`fs`),s=require(`@tailwindcss/vite`);s=e.__toESM(s);let c=require(`module`),l=require(`url`),u=require(`vite`);var d=typeof __filename==`string`?__filename:(0,l.fileURLToPath)({}.url),f=i.default.dirname(d),p=process.cwd(),m=(0,c.createRequire)(d),h=i.default.resolve(f,`../../`),g=i.default.resolve(f,`../`),_=i.default.resolve(h,`./fe/frontend`),v=i.default.resolve(p,`.`),y=i.default.resolve(v,`./frontend`),b=e=>i.default.dirname(m.resolve(`${e}/package.json`));function x(){n.logWhite(`frameFEPath: `+_),n.logWhite(`runFEPath: `+y);let e=[_,y].reduce((e,n)=>{let r=[`js`,`jsx`,`ts`,`tsx`].map(e=>t.resolve(n,`/**`,`/*.`+e)),i=a.sync(r).filter(e=>e.includes(`.entry.`)).filter(e=>!e.includes(`.d.ts`));return[...e,...i]},[]),r={};if(!e.length)return r;let s;for(let n=0;n<e.length;n++){let c=e[n],l=i.default.resolve(c,`../`),u=a.sync(t.resolve(l,`*.html`))[0];if(u){let e=i.default.basename(u,`.html`);r[e]=i.default.resolve(p,u)}else{s||=(0,o.readFileSync)(i.default.resolve(g,`./app/view/entry.tpl`),`utf-8`);let e=c.split(i.default.sep).at(-1),t=s.replace(`<!-- {{InsertScript}} -->`,`<script type="module" src="./${e}"><\/script>`),n=l.split(i.default.sep).at(-1),a=i.default.join(l,n+`.html`);(0,o.writeFileSync)(a,t),r[n]=a}}return r}function S(){let e=r.buildFESharedState.options.output??`frame`;return e!==`frame`&&n.logRed(`Please pay attention to the build output.`),e===`run`?i.default.join(v,`./app/public/dist`):typeof e==`function`?e():i.default.join(g,`./app/public/dist`)}var C=async e=>{let t=(await import(`chokidar`)).watch(y,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),r=!1;t.on(`change`,async t=>{r||=(r=!0,n.logYellow(`changed: ${t}`),await e?.(),!1)}),n.logYellow(`watching ${y}/...`)};function w(e=`.html`){let t=[...new Set(Array.isArray(e)?e:[e])];return{name:`flatten-html`,apply:`build`,closeBundle(){let e=S();if(!(0,o.existsSync)(e))return;let r=a.sync(`**/*.html`,{cwd:e,dot:!0});for(let n of r){let r=i.default.join(e,n),a=i.default.basename(n,`.html`),s=`.entry`;a.indexOf(s)===-1&&(a+=s);for(let n of t){let t=a+n,s=i.default.join(e,t);r!==s&&(0,o.copyFileSync)(r,s)}t.some(t=>r===i.default.join(e,a+t))||(0,o.unlinkSync)(r)}let s=[...new Set(r.map(e=>i.default.dirname(e)))].sort().reverse(),c=e=>{if(!((0,o.existsSync)(e)?(0,o.readdirSync)(e):[1]).length){(0,o.rmdirSync)(e);let t=e.split(i.default.sep).slice(0,-1);t.length&&c(t.join(i.default.sep))}};for(let t of s)t&&t!==`.`&&c(i.default.join(e,t));let l=t.flatMap(t=>a.sync(`**/*`+t,{cwd:e,dot:!0}));for(let e of l)n.logYellow(`final file path -----> `+e)}}}function T(e){let t=[``,`.ts`,`.tsx`,`.js`,`.jsx`];return e.reduce((e,[n,r,a=y])=>{let s=t.map(e=>i.default.resolve(a,r+e)).find(e=>(0,o.existsSync)(e)),c=i.default.resolve(f,`./defaultAlias`);return s&&(c=s),e[n]=c,e},{})}var E=(...e)=>new RegExp(e.join(`[\\/]`)),D=e=>`_${e}_tcC`,O=async(e,t)=>{try{await(0,u.build)({configFile:!1,base:`/dist`,plugins:[(await import(`@vitejs/plugin-react`)).default(),w([`.tpl`,`.html`]),(0,s.default)()],build:{outDir:S(),emptyOutDir:!0,sourcemap:t===`dev`?`inline`:`hidden`,rollupOptions:{input:e,output:{codeSplitting:{groups:[{name:D(`__nm`),test:E(`node_modules`),maxSize:5e5,minShareCount:2,priority:1},{name:D(`r`),test:E(`node_modules`,`react`),minShareCount:2,maxSize:5e5,priority:5},{name:D(`r-d`),test:E(`node_modules`,`react-dom`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`r-r-d`),test:E(`node_modules`,`react-router-dom`),minShareCount:2,maxSize:5e5,priority:10},{name:D(`ajv`),test:E(`node_modules`,`ajv`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`ui-lib`),test:E(`components`),minShareCount:2,maxSize:2e5,priority:5},{name:D(`ui-f-lib`),test:E(`components`,`.*?[Ff]orm.*?`),minShareCount:2,maxSize:5e4,priority:10}]}}}},resolve:{alias:{react:b(`react`),"react-dom":b(`react-dom`),"@tc/ui-react":i.default.resolve(f,`../../fe/packages/ui/react/`),...T([[`@tc/scalability/SchemaForm/frameData`,`./extended/SchemaForm/data`,_],[`@tc/scalability/SchemaForm/data`,`./extended/SchemaForm/data`],[`@tc/scalability/SchemaPage/CallCom/data`,`./extended/SchemaPage/CallCom/data`],[`@tc/scalability/dash/customRoutes`,`./extended/dash/customRoutes`]])}}}),n.logJoinColorized(n.colorize(`builded`,`green`),n.colorize(` -> `,`white`),n.colorize(`time: `,`cyan`),n.colorize(new Date,`pink`))}catch(e){n.logRed(e)}};exports.VBuildFE=O,exports.entries=x,exports.outDir=S,exports.watchFiles=C;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.js`),t=require(`../packages/utils/path.js`);require(`../packages/utils/index.js`);const n=require(`../packages/common/log/index.js`),r=require(`./state.js`);let i=require(`path`);i=e.__toESM(i);let a=require(`glob`);a=e.__toESM(a);let o=require(`fs`),s=require(`@tailwindcss/vite`);s=e.__toESM(s);let c=require(`module`),l=require(`url`),u=require(`vite`);var d=typeof __filename==`string`?__filename:(0,l.fileURLToPath)({}.url),f=i.default.dirname(d),p=process.cwd(),m=(0,c.createRequire)(d),h=i.default.resolve(f,`../../`),g=i.default.resolve(f,`../`),_=i.default.resolve(h,`./fe/frontend`),v=i.default.resolve(p,`.`),y=i.default.resolve(v,`./frontend`),b=e=>i.default.dirname(m.resolve(`${e}/package.json`));function x(){n.logWhite(`frameFEPath: `+_),n.logWhite(`runFEPath: `+y);let e=[_,y].reduce((e,n)=>{let r=[`js`,`jsx`,`ts`,`tsx`].map(e=>t.resolve(n,`/**`,`/*.`+e)),i=a.sync(r).filter(e=>e.includes(`.entry.`)).filter(e=>!e.includes(`.d.ts`));return[...e,...i]},[]),r={};if(!e.length)return r;let s;for(let n=0;n<e.length;n++){let c=e[n],l=i.default.resolve(c,`../`),u=a.sync(t.resolve(l,`*.html`))[0];if(u){let e=i.default.basename(u,`.html`);r[e]=i.default.resolve(p,u)}else{s||=(0,o.readFileSync)(i.default.resolve(g,`./app/view/entry.tpl`),`utf-8`);let e=c.split(i.default.sep).at(-1),t=s.replace(`<!-- {{InsertScript}} -->`,`<script type="module" src="./${e}"><\/script>`),n=l.split(i.default.sep).at(-1),a=i.default.join(l,n+`.html`);(0,o.writeFileSync)(a,t),r[n]=a}}return r}function S(){let e=r.buildFESharedState.options.output??`frame`;return e!==`frame`&&n.logRed(`Please pay attention to the build output.`),e===`run`?i.default.join(v,`./app/public/dist`):typeof e==`function`?e():i.default.join(g,`./app/public/dist`)}var C=async e=>{let t=(await import(`chokidar`)).watch(y,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),r=!1;t.on(`change`,async t=>{r||=(r=!0,n.logYellow(`changed: ${t}`),await e?.(),!1)}),n.logYellow(`watching ${y}/...`)};function w(e=`.html`){let t=[...new Set(Array.isArray(e)?e:[e])];return{name:`flatten-html`,apply:`build`,closeBundle(){let e=S();if(!(0,o.existsSync)(e))return;let r=a.sync(`**/*.html`,{cwd:e,dot:!0});for(let n of r){let r=i.default.join(e,n),a=i.default.basename(n,`.html`),s=`.entry`;a.indexOf(s)===-1&&(a+=s);for(let n of t){let t=a+n,s=i.default.join(e,t);r!==s&&(0,o.copyFileSync)(r,s)}t.some(t=>r===i.default.join(e,a+t))||(0,o.unlinkSync)(r)}let s=[...new Set(r.map(e=>i.default.dirname(e)))].sort().reverse(),c=e=>{if(!((0,o.existsSync)(e)?(0,o.readdirSync)(e):[1]).length){(0,o.rmdirSync)(e);let t=e.split(i.default.sep).slice(0,-1);t.length&&c(t.join(i.default.sep))}};for(let t of s)t&&t!==`.`&&c(i.default.join(e,t));let l=t.flatMap(t=>a.sync(`**/*`+t,{cwd:e,dot:!0}));for(let e of l)n.logYellow(`final file path -----> `+e)}}}function T(e){let t=[``,`.ts`,`.tsx`,`.js`,`.jsx`];return e.reduce((e,[n,r,a=y])=>{let s=t.map(e=>i.default.resolve(a,r+e)).find(e=>(0,o.existsSync)(e)),c=i.default.resolve(f,`./defaultAlias`);return s&&(c=s),e[n]=c,e},{})}var E=(...e)=>new RegExp(e.join(`[\\/]`)),D=e=>`_${e}_tcC`,O=async(e,t)=>{try{await(0,u.build)({configFile:!1,base:`/dist`,plugins:[(await import(`@vitejs/plugin-react`)).default(),w([`.tpl`,`.html`]),(0,s.default)()],build:{outDir:S(),emptyOutDir:!0,sourcemap:t===`dev`?`inline`:`hidden`,rollupOptions:{input:e,output:{codeSplitting:{groups:[{name:D(`__nm`),test:E(`node_modules`),maxSize:5e5,minShareCount:2,priority:1},{name:D(`r`),test:E(`node_modules`,`react`),minShareCount:2,maxSize:5e5,priority:5},{name:D(`r-d`),test:E(`node_modules`,`react-dom`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`r-r-d`),test:E(`node_modules`,`react-router-dom`),minShareCount:2,maxSize:5e5,priority:10},{name:D(`ajv`),test:E(`node_modules`,`ajv`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`ui-lib`),test:E(`components`),minShareCount:2,maxSize:2e5,priority:5},{name:D(`ui-f-lib`),test:E(`components`,`.*?[Ff]orm.*?`),minShareCount:2,maxSize:5e4,priority:10}]}}}},resolve:{alias:{react:b(`react`),"react-dom":b(`react-dom`),vite:b(`vite`),"@tc/ui-react":i.default.resolve(f,`../../fe/packages/ui/react/`),...T([[`@tc/scalability/SchemaForm/frameData`,`./extended/SchemaForm/data`,_],[`@tc/scalability/SchemaForm/data`,`./extended/SchemaForm/data`],[`@tc/scalability/SchemaPage/CallCom/data`,`./extended/SchemaPage/CallCom/data`],[`@tc/scalability/dash/customRoutes`,`./extended/dash/customRoutes`]])}}}),n.logJoinColorized(n.colorize(`builded`,`green`),n.colorize(` -> `,`white`),n.colorize(`time: `,`cyan`),n.colorize(new Date,`pink`))}catch(e){n.logRed(e)}};exports.VBuildFE=O,exports.entries=x,exports.outDir=S,exports.watchFiles=C;
|
package/esm/bundler/utils.js
CHANGED
|
@@ -173,6 +173,7 @@ var F = (...e) => new RegExp(e.join("[\\/]")), I = (e) => `_${e}_tcC`, L = async
|
|
|
173
173
|
resolve: { alias: {
|
|
174
174
|
react: k("react"),
|
|
175
175
|
"react-dom": k("react-dom"),
|
|
176
|
+
vite: k("vite"),
|
|
176
177
|
"@tc/ui-react": s.resolve(x, "../../fe/packages/ui/react/"),
|
|
177
178
|
...P([
|
|
178
179
|
[
|
|
@@ -17,7 +17,7 @@ const useCurrentMenuData = (useSibe = false) => {
|
|
|
17
17
|
const isSide = params[QK.sk];
|
|
18
18
|
const menuTypeIsTop = !!params[QK.k];
|
|
19
19
|
const menu = findMenuItem(projectInfo?.menu, menuTypeIsTop ? params[QK.k] : params[QK.sk]);
|
|
20
|
-
if (isSide && useSibe && menuTypeIsTop) {
|
|
20
|
+
if (isSide && useSibe && menuTypeIsTop && menu) {
|
|
21
21
|
const sMenu = findMenuItem(menu.sidebarConfig?.menu ?? [], params[QK.sk]);
|
|
22
22
|
return sMenu;
|
|
23
23
|
}
|
|
@@ -154,7 +154,7 @@ export function SubMenu({ eventKey, label, icon, disabled, popup: forcePopup, pa
|
|
|
154
154
|
const hoverBridgePlacement = mode === "top" && level > 0 ? "horizontal" : "auto";
|
|
155
155
|
const subMenuContent = (_jsx("div", { className: cn(
|
|
156
156
|
// "min-w-[180px]",
|
|
157
|
-
"
|
|
157
|
+
"rounded-xl border border-border/80 bg-popover p-2 text-popover-foreground", "shadow-[0_20px_56px_hsl(var(--background)/0.7),0_8px_18px_hsl(var(--foreground)/0.16),inset_0_1px_0_hsl(var(--foreground)/0.06)]"), children: _jsx(SubMenuLevelContext.Provider, { value: level + 1, children: _jsx("div", { className: "flex flex-col gap-1", children: children }) }) }));
|
|
158
158
|
/**
|
|
159
159
|
*
|
|
160
160
|
* submenu 生成的节点是 1 menuItem
|
|
@@ -156,20 +156,20 @@ function DataTableDemo() {
|
|
|
156
156
|
const columns = [
|
|
157
157
|
{ key: 'id', title: 'ID', width: '60px' },
|
|
158
158
|
{ key: 'name', title: '姓名' },
|
|
159
|
-
{ key: '
|
|
160
|
-
{ key: '
|
|
161
|
-
{ key: '
|
|
162
|
-
{ key: '
|
|
163
|
-
{ key: '
|
|
164
|
-
{ key: '
|
|
165
|
-
{ key: '
|
|
166
|
-
{ key: '
|
|
167
|
-
{ key: '
|
|
168
|
-
{ key: '
|
|
169
|
-
{ key: '
|
|
170
|
-
{ key: '
|
|
171
|
-
{ key: '
|
|
172
|
-
{ key: '
|
|
159
|
+
{ key: 'role1', title: '角色1', dataIndex: 'role', fixed: 'left' },
|
|
160
|
+
{ key: 'status1', title: '状态1', dataIndex: 'status', fixed: 'left' },
|
|
161
|
+
{ key: 'role2', title: '角色2', dataIndex: 'role' },
|
|
162
|
+
{ key: 'status2', title: '状态2', dataIndex: 'status' },
|
|
163
|
+
{ key: 'role3', title: '角色3', dataIndex: 'role' },
|
|
164
|
+
{ key: 'status3', title: '状态3', dataIndex: 'status' },
|
|
165
|
+
{ key: 'role4', title: '角色4', dataIndex: 'role' },
|
|
166
|
+
{ key: 'status4', title: '状态4', dataIndex: 'status' },
|
|
167
|
+
{ key: 'role5', title: '角色5', dataIndex: 'role' },
|
|
168
|
+
{ key: 'status5', title: '状态5', dataIndex: 'status' },
|
|
169
|
+
{ key: 'role6', title: '角色6', dataIndex: 'role' },
|
|
170
|
+
{ key: 'status6', title: '状态6', dataIndex: 'status' },
|
|
171
|
+
{ key: 'role7', title: '角色', dataIndex: 'role', fixed: 'right' },
|
|
172
|
+
{ key: 'status7', title: '状态', dataIndex: 'status' },
|
|
173
173
|
{
|
|
174
174
|
key: 'action',
|
|
175
175
|
title: '操作',
|
|
@@ -183,11 +183,106 @@ function DataTableDemo() {
|
|
|
183
183
|
},
|
|
184
184
|
];
|
|
185
185
|
const data = [
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
186
|
+
{
|
|
187
|
+
id: 1,
|
|
188
|
+
name: '张三',
|
|
189
|
+
role: '管理员',
|
|
190
|
+
role7: '管理员',
|
|
191
|
+
role6: '管理员',
|
|
192
|
+
role5: '管理员',
|
|
193
|
+
role4: '管理员',
|
|
194
|
+
role3: '管理员',
|
|
195
|
+
role2: '管理员',
|
|
196
|
+
role1: '管理员',
|
|
197
|
+
status: '正常',
|
|
198
|
+
status7: '正常',
|
|
199
|
+
status6: '正常',
|
|
200
|
+
status5: '正常',
|
|
201
|
+
status4: '正常',
|
|
202
|
+
status3: '正常',
|
|
203
|
+
status2: '正常',
|
|
204
|
+
status1: '正常',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 2,
|
|
208
|
+
name: '李四',
|
|
209
|
+
role: '用户',
|
|
210
|
+
role7: '用户',
|
|
211
|
+
role6: '用户',
|
|
212
|
+
role5: '用户',
|
|
213
|
+
role4: '用户',
|
|
214
|
+
role3: '用户',
|
|
215
|
+
role2: '用户',
|
|
216
|
+
role1: '用户',
|
|
217
|
+
status: '禁用',
|
|
218
|
+
status7: '禁用',
|
|
219
|
+
status6: '禁用',
|
|
220
|
+
status5: '禁用',
|
|
221
|
+
status4: '禁用',
|
|
222
|
+
status3: '禁用',
|
|
223
|
+
status2: '禁用',
|
|
224
|
+
status1: '禁用',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: 3,
|
|
228
|
+
name: '王五',
|
|
229
|
+
role: '审计员',
|
|
230
|
+
role7: '审计员',
|
|
231
|
+
role6: '审计员',
|
|
232
|
+
role5: '审计员',
|
|
233
|
+
role4: '审计员',
|
|
234
|
+
role3: '审计员',
|
|
235
|
+
role2: '审计员',
|
|
236
|
+
role1: '审计员',
|
|
237
|
+
status: '正常',
|
|
238
|
+
status7: '正常',
|
|
239
|
+
status6: '正常',
|
|
240
|
+
status5: '正常',
|
|
241
|
+
status4: '正常',
|
|
242
|
+
status3: '正常',
|
|
243
|
+
status2: '正常',
|
|
244
|
+
status1: '正常',
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: 4,
|
|
248
|
+
name: '赵六',
|
|
249
|
+
role: '用户',
|
|
250
|
+
role7: '用户',
|
|
251
|
+
role6: '用户',
|
|
252
|
+
role5: '用户',
|
|
253
|
+
role4: '用户',
|
|
254
|
+
role3: '用户',
|
|
255
|
+
role2: '用户',
|
|
256
|
+
role1: '用户',
|
|
257
|
+
status: '正常',
|
|
258
|
+
status7: '正常',
|
|
259
|
+
status6: '正常',
|
|
260
|
+
status5: '正常',
|
|
261
|
+
status4: '正常',
|
|
262
|
+
status3: '正常',
|
|
263
|
+
status2: '正常',
|
|
264
|
+
status1: '正常',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
id: 5,
|
|
268
|
+
name: '孙七',
|
|
269
|
+
role: '用户',
|
|
270
|
+
role7: '用户',
|
|
271
|
+
role6: '用户',
|
|
272
|
+
role5: '用户',
|
|
273
|
+
role4: '用户',
|
|
274
|
+
role3: '用户',
|
|
275
|
+
role2: '用户',
|
|
276
|
+
role1: '用户',
|
|
277
|
+
status: '正常',
|
|
278
|
+
status7: '正常',
|
|
279
|
+
status6: '正常',
|
|
280
|
+
status5: '正常',
|
|
281
|
+
status4: '正常',
|
|
282
|
+
status3: '正常',
|
|
283
|
+
status2: '正常',
|
|
284
|
+
status1: '正常',
|
|
285
|
+
},
|
|
191
286
|
];
|
|
192
287
|
return (_jsx(Section, { title: "DataTable", children: _jsxs("div", { className: "w-full", children: [_jsxs("p", { className: "text-sm mb-2", children: ["\u5DF2\u9009: ", JSON.stringify(selectedKeys)] }), _jsx(DataTable, { columns: columns, data: data, rowKey: "id", rowSelection: {
|
|
193
288
|
selectedRowKeys: selectedKeys,
|