@_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.
@@ -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
- 包内附带 `tc-generator` skill,本质是一份可复用的 Agent 指令包,用来按 TemplateCore 约定生成项目、model 配置、Schema CRUD、controller 和 router。
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 "${CODEX_HOME:-$HOME/.codex}/skills"
25
- cp -R node_modules/@_tc/template-core/.skills/tc-generator "${CODEX_HOME:-$HOME/.codex}/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 生效。使用时可以直接说“用 tc-generator 生成一个商品管理模块”。
34
+ 安装后重启对应 Agent,让新 skill 生效。使用时可以直接说”用 tc-generator 生成一个商品管理模块”或”用 tc-component-usage-skills 帮我写一个带搜索的分页表格”。
29
35
 
30
- 其他 Agent 如果不支持自动安装 skill,也可以直接读取 `.skills/tc-generator/SKILL.md`,再按需读取 `.skills/tc-generator/reference/` 下的参考文件。
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 文件,可能与模板不同步而导致内容过时。
@@ -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;
@@ -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
- " rounded-xl bg-muted p-2 shadow-[0_18px_44px_rgba(15,23,42,0.14),0_4px_12px_rgba(15,23,42,0.08)]"), children: _jsx(SubMenuLevelContext.Provider, { value: level + 1, children: _jsx("div", { className: "flex flex-col gap-1", children: children }) }) }));
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: 'role', title: '角色1', fixed: 'left' },
160
- { key: 'status', title: '状态1', fixed: 'left' },
161
- { key: 'role', title: '角色2' },
162
- { key: 'status', title: '状态2' },
163
- { key: 'role', title: '角色3' },
164
- { key: 'status', title: '状态3' },
165
- { key: 'role', title: '角色4' },
166
- { key: 'status', title: '状态4' },
167
- { key: 'role', title: '角色5' },
168
- { key: 'status', title: '状态5' },
169
- { key: 'role', title: '角色6' },
170
- { key: 'status', title: '状态6' },
171
- { key: 'role', title: '角色', fixed: 'right' },
172
- { key: 'status', title: '状态' },
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
- { id: 1, name: '张三', role: '管理员', status: '正常' },
187
- { id: 2, name: '李四', role: '用户', status: '禁用' },
188
- { id: 3, name: '王五', role: '审计员', status: '正常' },
189
- { id: 4, name: '赵六', role: '用户', status: '正常' },
190
- { id: 5, name: '孙七', role: '用户', status: '正常' },
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_tc/template-core",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "A full-stack TypeScript admin framework powered by Koa, React, and Vite - monorepo root",
5
5
  "types": "./types/index.d.ts",
6
6
  "exports": {