@yh-ui/yh-ui-skill 1.0.39 → 1.0.42
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/assets/llms-full.txt +2 -0
- package/assets/llms.txt +2 -0
- package/assets/metadata.json +1 -1
- package/assets/skills/yh-ui/README.md +35 -32
- package/assets/skills/yh-ui/README.zh-CN.md +55 -0
- package/assets/skills/yh-ui/SKILL.md +57 -2
- package/assets/skills/yh-ui/references/codegen-rubric.md +16 -12
- package/assets/skills/yh-ui/references/nuxt.md +49 -3
- package/assets/skills/yh-ui/references/recipes-ai.md +129 -37
- package/assets/skills/yh-ui/references/recipes-flow.md +143 -37
- package/assets/skills/yh-ui/references/recipes-form-schema.md +147 -42
- package/assets/skills/yh-ui/references/recipes-icons.md +126 -0
- package/assets/skills/yh-ui/references/recipes-table.md +98 -47
- package/assets/skills/yh-ui/references/recipes-theme.md +141 -0
- package/assets/skills/yh-ui/references/request.md +98 -19
- package/assets/skills/yh-ui/references/vue-component-practices.md +82 -19
- package/package.json +1 -1
|
@@ -1,83 +1,134 @@
|
|
|
1
1
|
# Deep Recipe: YhTable
|
|
2
2
|
|
|
3
|
-
Use this recipe when
|
|
3
|
+
Use this recipe when building complex data tables, admin list views, large datasets with virtualization, custom cell layouts, context menus, data export, or printing.
|
|
4
4
|
|
|
5
5
|
## Default Choice
|
|
6
6
|
|
|
7
|
-
Use `YhTable` with a
|
|
7
|
+
Use `YhTable` with a statically defined `columns` array. Let `YhTable` handle pagination internally through the `pagination` configuration property.
|
|
8
8
|
|
|
9
9
|
## Source-Aligned API Highlights
|
|
10
10
|
|
|
11
|
-
- Props
|
|
12
|
-
- Events
|
|
13
|
-
-
|
|
11
|
+
- **Props**: `data`, `columns`, `rowKey`, `loading`, `pagination`, `height`, `maxHeight`, `border`, `stripe`, `virtualConfig`, `toolbarConfig`, `rowConfig`, `expandConfig`, `selectionConfig`, `sortConfig`, `filterConfig`.
|
|
12
|
+
- **Events**: `sort-change`, `filter-change`, `page-change`, `selection-change`, `row-click`, `cell-click`, `scroll`, `update:data`.
|
|
13
|
+
- **Slots**: `default` (column definitions), `toolbar`, `toolbar-left`, `toolbar-right`, `toolbar-left-prefix`, `toolbar-right-suffix`.
|
|
14
|
+
- **Exposed APIs**: `refresh()`, `clearSelection()`, `getSelectionRows()`, `exportData(options)`, `importData(options)`, `print(options)`, `scrollTo(options)`, `doLayout()`, `setColumnVisible(prop, visible)`.
|
|
14
15
|
|
|
15
|
-
## Pattern:
|
|
16
|
+
## Pattern: Advanced Admin Table
|
|
17
|
+
|
|
18
|
+
This example demonstrates request-driven loading, cell custom slot rendering, toolbars, virtual scrolling for large datasets, printing, and Excel exporting:
|
|
16
19
|
|
|
17
20
|
```vue
|
|
18
21
|
<script setup lang="ts">
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
22
|
+
import { ref, computed } from 'vue'
|
|
23
|
+
import { YhTable, YhButton, YhTag, YhMessage, YhPopconfirm } from '@yh-ui/components'
|
|
21
24
|
import { createRequest, useRequest } from '@yh-ui/request'
|
|
22
25
|
|
|
23
|
-
interface
|
|
26
|
+
interface LogRow {
|
|
24
27
|
id: number
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
status:
|
|
28
|
+
username: string
|
|
29
|
+
action: string
|
|
30
|
+
status: 'success' | 'failed'
|
|
31
|
+
ip: string
|
|
32
|
+
createdTime: string
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
const api = createRequest({ baseURL: '/api' })
|
|
31
|
-
const
|
|
32
|
-
const page = ref({ currentPage: 1, pageSize:
|
|
36
|
+
const tableRef = ref<InstanceType<typeof YhTable> | null>(null)
|
|
37
|
+
const page = ref({ currentPage: 1, pageSize: 50, total: 0 })
|
|
33
38
|
|
|
39
|
+
// Static columns definition - keep outside template
|
|
34
40
|
const columns = [
|
|
35
|
-
{ prop: '
|
|
36
|
-
{ prop: '
|
|
37
|
-
{ prop: 'status', label: 'Status', width: 120 }
|
|
41
|
+
{ prop: 'username', label: 'User', width: 150, sortable: true },
|
|
42
|
+
{ prop: 'action', label: 'Action Description', minWidth: 200 },
|
|
43
|
+
{ prop: 'status', label: 'Status', width: 120, slot: 'status' },
|
|
44
|
+
{ prop: 'ip', label: 'IP Address', width: 140 },
|
|
45
|
+
{ prop: 'createdTime', label: 'Timestamp', width: 180 },
|
|
46
|
+
{ prop: 'operations', label: 'Operations', width: 120, slot: 'operations' }
|
|
38
47
|
]
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
// Request client using useRequest
|
|
50
|
+
const { data, loading, refresh } = useRequest(async () => {
|
|
51
|
+
const result = await api.get<{ list: LogRow[]; total: number }>('/audit/logs', {
|
|
52
|
+
params: { page: page.value.currentPage, limit: page.value.pageSize }
|
|
43
53
|
})
|
|
44
54
|
page.value.total = result.total
|
|
45
55
|
return result.list
|
|
46
56
|
})
|
|
47
57
|
|
|
48
58
|
const rows = computed(() => data.value || [])
|
|
59
|
+
|
|
60
|
+
// Native Excel Export using YhTable exposed method
|
|
61
|
+
function exportExcel() {
|
|
62
|
+
if (!tableRef.value) return
|
|
63
|
+
tableRef.value.exportData({
|
|
64
|
+
filename: 'audit_logs',
|
|
65
|
+
type: 'xlsx',
|
|
66
|
+
original: true
|
|
67
|
+
})
|
|
68
|
+
YhMessage.success('Exporting file...')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Native Print using YhTable exposed method
|
|
72
|
+
function printTable() {
|
|
73
|
+
if (!tableRef.value) return
|
|
74
|
+
tableRef.value.print({
|
|
75
|
+
title: 'Audit Activity Report',
|
|
76
|
+
showPageNumber: true
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function deleteRow(row: LogRow) {
|
|
81
|
+
YhMessage.success(`Deleted log: ${row.id}`)
|
|
82
|
+
refresh()
|
|
83
|
+
}
|
|
49
84
|
</script>
|
|
50
85
|
|
|
51
86
|
<template>
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
<div class="table-container">
|
|
88
|
+
<YhTable
|
|
89
|
+
ref="tableRef"
|
|
90
|
+
row-key="id"
|
|
91
|
+
:data="rows"
|
|
92
|
+
:columns="columns"
|
|
93
|
+
:loading="loading"
|
|
94
|
+
:pagination="page"
|
|
95
|
+
height="600px"
|
|
96
|
+
border
|
|
97
|
+
stripe
|
|
98
|
+
:virtual-config="{ enabled: true, itemHeight: 45 }"
|
|
99
|
+
@page-change="refresh"
|
|
100
|
+
>
|
|
101
|
+
<!-- Custom Left Toolbar Slot -->
|
|
102
|
+
<template #toolbar-left>
|
|
103
|
+
<YhButton type="primary" icon="mdi:refresh" @click="refresh">Refresh Logs</YhButton>
|
|
104
|
+
<YhButton type="success" icon="mdi:file-excel" @click="exportExcel"
|
|
105
|
+
>Export to Excel</YhButton
|
|
106
|
+
>
|
|
107
|
+
<YhButton icon="mdi:printer" @click="printTable">Print Report</YhButton>
|
|
108
|
+
</template>
|
|
109
|
+
|
|
110
|
+
<!-- Custom Cell Slots mapped via column.slot -->
|
|
111
|
+
<template #status="{ row }">
|
|
112
|
+
<YhTag :type="row.status === 'success' ? 'success' : 'danger'">
|
|
113
|
+
{{ row.status === 'success' ? 'Success' : 'Failed' }}
|
|
114
|
+
</YhTag>
|
|
115
|
+
</template>
|
|
116
|
+
|
|
117
|
+
<template #operations="{ row }">
|
|
118
|
+
<YhPopconfirm title="Are you sure you want to delete this log?" @confirm="deleteRow(row)">
|
|
119
|
+
<template #reference>
|
|
120
|
+
<YhButton type="text" danger icon="mdi:delete" />
|
|
121
|
+
</template>
|
|
122
|
+
</YhPopconfirm>
|
|
123
|
+
</template>
|
|
124
|
+
</YhTable>
|
|
125
|
+
</div>
|
|
73
126
|
</template>
|
|
74
127
|
```
|
|
75
128
|
|
|
76
129
|
## Agent Rules
|
|
77
130
|
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
- Use table events for remote sort/filter/page behavior.
|
|
83
|
-
- Do not invent `YhDataTable`; use `YhTable`.
|
|
131
|
+
- **Static Columns**: Always define the `columns` array inside `<script setup>` as a constant or a reactive `computed` array; never define arrays inline inside the template to prevent duplicate component re-renders.
|
|
132
|
+
- **Set Table Height**: When enabling `virtual-config` for large datasets, an explicit `height` or `max-height` (e.g. `600px` or `100%`) must be set on `YhTable` to calculate viewport slots correctly.
|
|
133
|
+
- **Row Identity**: Always pass a unique `row-key` prop (e.g. `id`, `uuid`) to help Vue track element identity during sort, select, and virtual updates.
|
|
134
|
+
- **Avoid wrapper components**: Do not invent a custom `YhDataTable` or wrapper component. Always use the native `YhTable` from `@yh-ui/components`.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Deep Recipe: YH-UI Theme System
|
|
2
|
+
|
|
3
|
+
Use this recipe when integrating or customising themes, dark mode, responsive scaling, compact density, or color-blind friendly palettes using `@yh-ui/theme`.
|
|
4
|
+
|
|
5
|
+
## Default Choice
|
|
6
|
+
|
|
7
|
+
Use `ThemePlugin` during app registration and access reactive theme state using `useTheme` or `useThemeVars`.
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createApp } from 'vue'
|
|
13
|
+
import { ThemePlugin } from '@yh-ui/theme'
|
|
14
|
+
import App from './App.vue'
|
|
15
|
+
|
|
16
|
+
const app = createApp(App)
|
|
17
|
+
|
|
18
|
+
app.use(ThemePlugin, {
|
|
19
|
+
preset: 'default', // 'default' | 'dark' | 'blue' | 'green' | 'purple' | 'orange' | 'rose' | 'amber' | 'teal' | 'indigo' | 'slate' | 'zinc'
|
|
20
|
+
dark: false, // Initial mode
|
|
21
|
+
persist: true, // Save theme choices to localStorage automatically
|
|
22
|
+
persistKey: 'yh-ui-theme',
|
|
23
|
+
followSystem: true, // Follow OS dark/light mode preference
|
|
24
|
+
radius: 'md', // 'none' | 'sm' | 'md' | 'lg' | 'full'
|
|
25
|
+
density: 'comfortable', // 'comfortable' | 'compact' | 'dense'
|
|
26
|
+
colorBlindMode: 'none', // 'none' | 'protanopia' | 'deuteranopia' | 'tritanopia' | 'achromatopsia'
|
|
27
|
+
algorithm: 'default' // 'default' | 'vibrant' | 'muted' | 'pastel'
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
app.mount('#app')
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Composition APIs
|
|
34
|
+
|
|
35
|
+
Use `useTheme` to get the ThemeManager instance or `useThemeVars` for Vue-friendly reactive properties:
|
|
36
|
+
|
|
37
|
+
```vue
|
|
38
|
+
<script setup lang="ts">
|
|
39
|
+
import { useTheme, useThemeVars } from '@yh-ui/theme'
|
|
40
|
+
|
|
41
|
+
// Option 1: Accessing the ThemeManager
|
|
42
|
+
const theme = useTheme()
|
|
43
|
+
|
|
44
|
+
function toggleDark() {
|
|
45
|
+
theme.toggleDarkMode()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function selectPreset(name: 'blue' | 'green' | 'purple') {
|
|
49
|
+
theme.setPreset(name)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function selectDensity(density: 'comfortable' | 'compact' | 'dense') {
|
|
53
|
+
theme.setDensity(density)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Option 2: Using reactive variables directly
|
|
57
|
+
const { dark, theme: currentPreset, density, colorBlindMode } = useThemeVars()
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<div class="settings">
|
|
62
|
+
<p>Current Theme: {{ currentPreset }} (Dark mode: {{ dark }})</p>
|
|
63
|
+
<p>Density Factor: {{ density }}</p>
|
|
64
|
+
<p>Accessibility Mode: {{ colorBlindMode }}</p>
|
|
65
|
+
|
|
66
|
+
<button @click="toggleDark">Toggle Dark Mode</button>
|
|
67
|
+
<button @click="selectPreset('blue')">Blue Preset</button>
|
|
68
|
+
<button @click="selectDensity('compact')">Compact Layout</button>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Advanced Theme Customization
|
|
74
|
+
|
|
75
|
+
### Smart Palette Generation
|
|
76
|
+
|
|
77
|
+
Generate complete secondary semantic status colors (success, warning, danger, info) automatically from a single primary brand color:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { useTheme } from '@yh-ui/theme'
|
|
81
|
+
|
|
82
|
+
const theme = useTheme()
|
|
83
|
+
// Generates and applies primary/success/warning/danger/info hex color scale
|
|
84
|
+
theme.applyFromPrimary('#722ed1')
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Color Contrast Checks (WCAG 2.1 AA/AAA)
|
|
88
|
+
|
|
89
|
+
Ensure generated colors meet user accessibility contrast guidelines:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { checkContrast, getTextColorForBackground } from '@yh-ui/theme'
|
|
93
|
+
|
|
94
|
+
// Returns true if contrast ratio is >= 4.5 (or >= 7 for AAA)
|
|
95
|
+
const isAccessible = checkContrast('#ffffff', '#1890ff', 'AA')
|
|
96
|
+
|
|
97
|
+
// Returns '#ffffff' or '#000000' based on contrast luminance
|
|
98
|
+
const suitableTextColor = getTextColorForBackground('#1890ff')
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Component-Level Custom Styles
|
|
102
|
+
|
|
103
|
+
You can override specific design tokens for a single component globally or dynamically:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { useTheme } from '@yh-ui/theme'
|
|
107
|
+
|
|
108
|
+
const theme = useTheme()
|
|
109
|
+
theme.apply({
|
|
110
|
+
components: {
|
|
111
|
+
YhButton: {
|
|
112
|
+
'--yh-button-font-weight': '600',
|
|
113
|
+
'--yh-button-border-radius': '8px'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## CSS Variable Usage
|
|
120
|
+
|
|
121
|
+
Instead of hardcoding color hexes, border-radii, spacing, or typography values, always leverage YH-UI's global theme tokens:
|
|
122
|
+
|
|
123
|
+
```vue
|
|
124
|
+
<style scoped>
|
|
125
|
+
.custom-card {
|
|
126
|
+
color: var(--yh-text-color-primary);
|
|
127
|
+
background-color: var(--yh-bg-color);
|
|
128
|
+
border: 1px solid var(--yh-border-color);
|
|
129
|
+
border-radius: var(--yh-border-radius-base);
|
|
130
|
+
padding: calc(var(--yh-spacing-unit) * 2);
|
|
131
|
+
|
|
132
|
+
/* Font size and line height scales dynamically with density setting */
|
|
133
|
+
font-size: var(--yh-font-size-base);
|
|
134
|
+
line-height: var(--yh-line-height-base);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.custom-card:hover {
|
|
138
|
+
border-color: var(--yh-color-primary-hover);
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
141
|
+
```
|
|
@@ -13,13 +13,41 @@ const api = createRequest({
|
|
|
13
13
|
retry: 2
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
-
api.interceptors.request.use(
|
|
17
|
-
config
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
api.interceptors.request.use(
|
|
17
|
+
(config) => {
|
|
18
|
+
const token = localStorage.getItem('token')
|
|
19
|
+
if (token) {
|
|
20
|
+
config.headers = {
|
|
21
|
+
...config.headers,
|
|
22
|
+
Authorization: `Bearer ${token}`
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return config
|
|
26
|
+
},
|
|
27
|
+
(error) => Promise.reject(error)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
api.interceptors.response.use(
|
|
31
|
+
(response) => response,
|
|
32
|
+
async (error) => {
|
|
33
|
+
const originalRequest = error.config
|
|
34
|
+
// Automatic JWT token refresh flow
|
|
35
|
+
if (error.response?.status === 401 && !originalRequest._retry) {
|
|
36
|
+
originalRequest._retry = true
|
|
37
|
+
try {
|
|
38
|
+
const { token } = await api.post('/auth/refresh')
|
|
39
|
+
localStorage.setItem('token', token)
|
|
40
|
+
originalRequest.headers['Authorization'] = `Bearer ${token}`
|
|
41
|
+
return api(originalRequest)
|
|
42
|
+
} catch (refreshError) {
|
|
43
|
+
// Redirect to login or clear token on fail
|
|
44
|
+
localStorage.removeItem('token')
|
|
45
|
+
return Promise.reject(refreshError)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Promise.reject(error)
|
|
20
49
|
}
|
|
21
|
-
|
|
22
|
-
})
|
|
50
|
+
)
|
|
23
51
|
```
|
|
24
52
|
|
|
25
53
|
## useRequest
|
|
@@ -27,6 +55,7 @@ api.interceptors.request.use((config) => {
|
|
|
27
55
|
```vue
|
|
28
56
|
<script setup lang="ts">
|
|
29
57
|
import { createRequest, useRequest } from '@yh-ui/request'
|
|
58
|
+
import { YhButton, YhMessage } from '@yh-ui/components'
|
|
30
59
|
|
|
31
60
|
interface User {
|
|
32
61
|
id: number
|
|
@@ -34,37 +63,87 @@ interface User {
|
|
|
34
63
|
}
|
|
35
64
|
|
|
36
65
|
const api = createRequest({ baseURL: '/api' })
|
|
37
|
-
|
|
66
|
+
|
|
67
|
+
// useRequest returns reactive state and trigger helpers
|
|
68
|
+
const { data, loading, error, refresh, run } = useRequest<User[]>(() => api.get<User[]>('/users'), {
|
|
69
|
+
immediate: true,
|
|
70
|
+
onSuccess: (data) => {
|
|
71
|
+
YhMessage.success(`Loaded ${data.length} users successfully`)
|
|
72
|
+
},
|
|
73
|
+
onError: (err) => {
|
|
74
|
+
YhMessage.error(`Failed to load users: ${err.message}`)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
38
77
|
</script>
|
|
39
78
|
|
|
40
79
|
<template>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
80
|
+
<div class="action-bar">
|
|
81
|
+
<YhButton :loading="loading" type="primary" @click="refresh">Refresh</YhButton>
|
|
82
|
+
<YhButton @click="run()">Force Run</YhButton>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div v-if="loading" class="loading-state">Loading users...</div>
|
|
86
|
+
<div v-else-if="error" class="error-state">{{ error.message }}</div>
|
|
87
|
+
<ul v-else class="user-list">
|
|
45
88
|
<li v-for="user in data" :key="user.id">{{ user.name }}</li>
|
|
46
89
|
</ul>
|
|
47
90
|
</template>
|
|
48
91
|
```
|
|
49
92
|
|
|
50
|
-
## SSE
|
|
93
|
+
## SSE (Server-Sent Events)
|
|
51
94
|
|
|
52
|
-
|
|
95
|
+
Use `useSSE` for real-time streaming notifications, logs, or updates. It handles connection lifecycles, retries, and errors automatically:
|
|
96
|
+
|
|
97
|
+
```vue
|
|
98
|
+
<script setup lang="ts">
|
|
99
|
+
import { ref, onUnmounted } from 'vue'
|
|
53
100
|
import { useSSE } from '@yh-ui/request'
|
|
54
101
|
|
|
102
|
+
interface EventPayload {
|
|
103
|
+
msg: string
|
|
104
|
+
time: string
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const events = ref<EventPayload[]>([])
|
|
108
|
+
|
|
55
109
|
const { isConnected, connect, disconnect, error } = useSSE('/api/events', {
|
|
56
110
|
reconnect: true,
|
|
57
111
|
reconnectInterval: 3000,
|
|
112
|
+
maxReconnectAttempts: 5,
|
|
58
113
|
onMessage: (event) => {
|
|
59
|
-
|
|
114
|
+
const payload = JSON.parse(event.data) as EventPayload
|
|
115
|
+
events.value.push(payload)
|
|
116
|
+
},
|
|
117
|
+
onError: (err) => {
|
|
118
|
+
console.error('SSE Error:', err)
|
|
60
119
|
}
|
|
61
120
|
})
|
|
121
|
+
|
|
122
|
+
// Always clean up connections when component is unmounted
|
|
123
|
+
onUnmounted(() => {
|
|
124
|
+
disconnect()
|
|
125
|
+
})
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<template>
|
|
129
|
+
<div>
|
|
130
|
+
<p
|
|
131
|
+
>Status:
|
|
132
|
+
<span :class="{ connected: isConnected }">{{
|
|
133
|
+
isConnected ? 'Connected' : 'Disconnected'
|
|
134
|
+
}}</span></p
|
|
135
|
+
>
|
|
136
|
+
<p v-if="error">Connection error: {{ error.message }}</p>
|
|
137
|
+
<ul>
|
|
138
|
+
<li v-for="item in events" :key="item.time">[{{ item.time }}] {{ item.msg }}</li>
|
|
139
|
+
</ul>
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
62
142
|
```
|
|
63
143
|
|
|
64
144
|
## Agent Rules
|
|
65
145
|
|
|
66
|
-
- Use `createRequest`, not old
|
|
67
|
-
-
|
|
68
|
-
- Use `useSSE`
|
|
69
|
-
-
|
|
70
|
-
- Keep auth token handling in request interceptors or server middleware.
|
|
146
|
+
- Use `createRequest`, not old/legacy API clients.
|
|
147
|
+
- Always prefer `useRequest` for fetching state in Vue components instead of manual `ref(true)` loading flags.
|
|
148
|
+
- Use `useSSE` for server streaming logs/updates, and make sure to clean up connection via `disconnect()` in `onUnmounted`.
|
|
149
|
+
- Place global request interception logic (like auth header additions) inside the central request client, not scattered around views.
|
|
@@ -4,20 +4,43 @@ Use these rules when generating or reviewing YH-UI Vue 3 code. They combine Vue
|
|
|
4
4
|
|
|
5
5
|
## SFC Structure
|
|
6
6
|
|
|
7
|
-
- Prefer `<script setup lang="ts">` for application
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
- Prefer `<script setup lang="ts">` for all application pages and component definitions.
|
|
8
|
+
- Always order SFC blocks as `<script>`, `<template>`, then `<style scoped>`.
|
|
9
|
+
- Internal setup block sequence:
|
|
10
|
+
1. Module/library imports (separate external dependencies from local `@yh-ui` packages).
|
|
11
|
+
2. TypeScript types & interfaces.
|
|
12
|
+
3. `defineProps`, `defineEmits`, and `defineModel`.
|
|
13
|
+
4. Reactive state & refs.
|
|
14
|
+
5. Computed properties.
|
|
15
|
+
6. Watchers (`watch` / `watchEffect`).
|
|
16
|
+
7. Lifecycle hooks (`onMounted`, `onUnmounted`, etc.).
|
|
17
|
+
8. Action functions / methods.
|
|
18
|
+
- Use `scoped` styles to prevent styling leakages. Never use raw global selector tags inside SFC styles.
|
|
19
|
+
- Keep templates clean: move calculations, complex conditions, or array mapping out of the template into computed variables.
|
|
12
20
|
|
|
13
21
|
## Props, Emits, And v-model
|
|
14
22
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
- Always type props with clear TypeScript interfaces. Use type-only imports (`import type { ... }`) for external type models.
|
|
24
|
+
- For **Vue 3.5+**, prefer reactive props destructuring:
|
|
25
|
+
```ts
|
|
26
|
+
const { size = 'medium', disabled = false, data = () => [] } = defineProps<Props>()
|
|
27
|
+
```
|
|
28
|
+
This is the official Vue 3.5+ best practice and avoids verbose `withDefaults`.
|
|
29
|
+
- For Vue < 3.5 or projects without destructure support, fallback to `withDefaults(defineProps<Props>(), defaults)`.
|
|
30
|
+
- Use typed `defineEmits` or `defineModel` for two-way bindings:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// Two-way binding model (Vue 3.4+)
|
|
34
|
+
const modelValue = defineModel<string>({ default: '' })
|
|
35
|
+
|
|
36
|
+
// Typed custom events
|
|
37
|
+
const emit = defineEmits<{
|
|
38
|
+
change: [value: string]
|
|
39
|
+
submit: []
|
|
40
|
+
}>()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- Never mutate props. If props need internal modifications, copy them to a local `ref` or compile them via `computed` getters/setters.
|
|
21
44
|
|
|
22
45
|
## Slots And Composition
|
|
23
46
|
|
|
@@ -26,14 +49,20 @@ Use these rules when generating or reviewing YH-UI Vue 3 code. They combine Vue
|
|
|
26
49
|
- In app pages, compose YH-UI components directly instead of wrapping every component in a thin local abstraction.
|
|
27
50
|
- Create composables only when stateful logic is reused or complex enough to deserve a named boundary.
|
|
28
51
|
|
|
29
|
-
## Reactivity
|
|
30
|
-
|
|
31
|
-
- Use `ref` for
|
|
32
|
-
- Use `reactive` for
|
|
33
|
-
- Use `computed` for derived values instead of maintaining
|
|
34
|
-
- Use `watch` for side
|
|
35
|
-
-
|
|
36
|
-
-
|
|
52
|
+
## Reactivity & Lifecycle Cleanups
|
|
53
|
+
|
|
54
|
+
- Use `ref` for primitive values, lists/arrays, or objects that will be completely overwritten.
|
|
55
|
+
- Use `reactive` only for complex nested states that require deep property mutation.
|
|
56
|
+
- Use `computed` for all derived values instead of writing sync watchers or manually maintaining double state variables.
|
|
57
|
+
- Use `watch` or `watchEffect` solely for side-effects (e.g. storage sync, async API triggers). Always specify `immediate: true` or `deep: true` only if required.
|
|
58
|
+
- Use `shallowRef` for heavy objects, DOM nodes, third-party library instances (ECharts, Monco Editor, Flow canvases), as deep reactivity on these will degrade performance.
|
|
59
|
+
- **Critical Lifecycle Cleanups**: Always clean up timeouts, intervals, ResizeObservers, WebSocket event listeners, and custom event listeners in `onUnmounted`:
|
|
60
|
+
```ts
|
|
61
|
+
const timer = setInterval(tick, 1000)
|
|
62
|
+
onUnmounted(() => {
|
|
63
|
+
clearInterval(timer)
|
|
64
|
+
})
|
|
65
|
+
```
|
|
37
66
|
|
|
38
67
|
## Accessibility
|
|
39
68
|
|
|
@@ -76,6 +105,40 @@ Use these rules when generating or reviewing YH-UI Vue 3 code. They combine Vue
|
|
|
76
105
|
- Use `useNamespace` for internal BEM-style component classes when editing package components.
|
|
77
106
|
- Use `useZIndex`, `useLocale`, `useId`/`useYhId`, and config hooks instead of local one-off implementations.
|
|
78
107
|
|
|
108
|
+
## YH-UI Prioritization & Extension Workflow
|
|
109
|
+
|
|
110
|
+
To ensure we prioritize existing UI framework logic and do not write duplicate custom HTML/CSS controls, follow this decision tree when implementing user interface requests:
|
|
111
|
+
|
|
112
|
+
1. **Verify Availability**: Search `references/source-truth.md` and `references/component-map.md`. If a component exists (e.g., `YhTable`, `YhDialog`, `YhConfigProvider`, `YhScrollbar`), you **must** use it. Do not write raw `<div class="custom-scroll">` or raw `<dialog>`.
|
|
113
|
+
2. **Handle Feature Gaps (Encapsulation/Extension)**: If a component is missing a sub-feature, do not abandon the component. Apply extension patterns in order of priority:
|
|
114
|
+
- **Slot Customization**: Use slots (e.g., `#toolbar`, `#empty`, `#default` custom cells) to inject the custom logic.
|
|
115
|
+
- **Props & Event Listeners**: Use component event bindings and dynamic property configs to override behavior.
|
|
116
|
+
- **CSS Variable Injection**: Inject style variables (e.g. style overrides like `--yh-button-font-weight`) to alter theme aesthetics without altering core element code.
|
|
117
|
+
- **Composite Patterns**: Group multiple YH-UI components together (e.g. wrap a `YhTable` and `YhPagination` or nesting slots) before trying to write raw elements.
|
|
118
|
+
3. **Raw Component Last Resort**: Only build a custom element from scratch if the functionality is entirely absent in the UI framework and cannot be composed/wrapped. You must document this reasoning in the code comments.
|
|
119
|
+
|
|
120
|
+
## TypeScript Strict Standards
|
|
121
|
+
|
|
122
|
+
To maintain clean typing boundaries and avoid runtime failures, follow these TS conventions:
|
|
123
|
+
|
|
124
|
+
- **Type-only imports**: When importing interfaces, schemas, or types from YH-UI packages, use the `import type` syntax:
|
|
125
|
+
```ts
|
|
126
|
+
import type { FormSchema } from '@yh-ui/components'
|
|
127
|
+
import type { FlowNode, FlowEdge } from '@yh-ui/flow'
|
|
128
|
+
import type { ThemeOptions } from '@yh-ui/theme'
|
|
129
|
+
```
|
|
130
|
+
- **Type Template Refs**: Never leave component template refs untyped (`const refVal = ref(null)`). Always use Vue's `InstanceType` utility to extract component exports:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { ref } from 'vue'
|
|
134
|
+
import { YhTable } from '@yh-ui/components'
|
|
135
|
+
|
|
136
|
+
const tableRef = ref<InstanceType<typeof YhTable> | null>(null)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- **Exposed APIs**: Access exposed methods on components strictly through these typed instances (e.g., `tableRef.value?.exportData(...)`, `formRef.value?.validate()`).
|
|
140
|
+
- **Data Models**: Strongly type response objects and row lists using `interface` declarations; never pass `any` into data bindings or request helper promises.
|
|
141
|
+
|
|
79
142
|
## Testing Expectations
|
|
80
143
|
|
|
81
144
|
- For reusable components, test props, emitted events, slots, keyboard/focus behavior, and important visual states.
|