create-rue 0.0.14 → 0.0.15
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/bundle.js +4 -19
- package/package.json +2 -2
- package/template/base/app/pages/TodoApp.tsx +563 -198
- package/template/base/app/pages/components/Layout.tsx +75 -22
- package/template/base/package-lock.json +617 -432
- package/template/base/package.json +19 -17
- package/template/base/pnpm-lock.yaml +550 -480
- package/template/base/tsconfig.json +1 -2
|
@@ -80,7 +80,10 @@ const themeLabels: Record<string, string> = {
|
|
|
80
80
|
sunset: '日落',
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
const getStoredTheme = () =>
|
|
83
|
+
const getStoredTheme = () => {
|
|
84
|
+
const storedTheme = localStorage.getItem(themeStorageKey)
|
|
85
|
+
return storedTheme && themes.includes(storedTheme) ? storedTheme : 'light'
|
|
86
|
+
}
|
|
84
87
|
|
|
85
88
|
const getStoredHeaderHidden = () => localStorage.getItem(reportHeaderStorageKey) === '1'
|
|
86
89
|
|
|
@@ -91,20 +94,48 @@ const applyTheme = (theme: string) => {
|
|
|
91
94
|
const ThemePicker: FC = () => {
|
|
92
95
|
const theme = ref(getStoredTheme())
|
|
93
96
|
|
|
97
|
+
const applyThemeState = () => {
|
|
98
|
+
const currentTheme = theme.value
|
|
99
|
+
const themePicker = document.querySelector<HTMLSelectElement>('[data-base-theme-picker="true"]')
|
|
100
|
+
|
|
101
|
+
applyTheme(currentTheme)
|
|
102
|
+
|
|
103
|
+
if (themePicker && themePicker.value !== currentTheme) {
|
|
104
|
+
themePicker.value = currentTheme
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const syncStoredTheme = () => {
|
|
109
|
+
theme.value = getStoredTheme()
|
|
110
|
+
requestAnimationFrame(applyThemeState)
|
|
111
|
+
}
|
|
112
|
+
|
|
94
113
|
useEffect(() => {
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
localStorage.setItem(themeStorageKey, theme.value)
|
|
115
|
+
requestAnimationFrame(applyThemeState)
|
|
116
|
+
|
|
117
|
+
window.addEventListener('storage', syncStoredTheme)
|
|
118
|
+
window.addEventListener('focus', syncStoredTheme)
|
|
119
|
+
document.addEventListener('visibilitychange', syncStoredTheme)
|
|
120
|
+
|
|
121
|
+
return () => {
|
|
122
|
+
window.removeEventListener('storage', syncStoredTheme)
|
|
123
|
+
window.removeEventListener('focus', syncStoredTheme)
|
|
124
|
+
document.removeEventListener('visibilitychange', syncStoredTheme)
|
|
125
|
+
}
|
|
126
|
+
}, [])
|
|
97
127
|
|
|
98
128
|
watch(
|
|
99
129
|
() => theme.value,
|
|
100
130
|
() => {
|
|
101
|
-
applyTheme(theme.value)
|
|
102
131
|
localStorage.setItem(themeStorageKey, theme.value)
|
|
132
|
+
requestAnimationFrame(applyThemeState)
|
|
103
133
|
},
|
|
104
134
|
)
|
|
105
135
|
|
|
106
136
|
return (
|
|
107
137
|
<select
|
|
138
|
+
data-base-theme-picker="true"
|
|
108
139
|
aria-label="切换主题"
|
|
109
140
|
className="select select-bordered select-sm w-40"
|
|
110
141
|
onChange={(event: Event) => {
|
|
@@ -112,7 +143,7 @@ const ThemePicker: FC = () => {
|
|
|
112
143
|
}}
|
|
113
144
|
>
|
|
114
145
|
{themes.map((name) => (
|
|
115
|
-
<option key={name} value={name}
|
|
146
|
+
<option key={name} value={name}>
|
|
116
147
|
{themeLabels[name] || name}
|
|
117
148
|
</option>
|
|
118
149
|
))}
|
|
@@ -123,11 +154,34 @@ const ThemePicker: FC = () => {
|
|
|
123
154
|
const Header: FC = () => {
|
|
124
155
|
const route = useRoute()
|
|
125
156
|
const currentPath = computed(() => route.get()?.path || '/')
|
|
157
|
+
const navClass = (path: string) =>
|
|
158
|
+
`btn btn-sm ${currentPath.get() === path ? 'btn-primary' : 'btn-ghost'}`.trim()
|
|
159
|
+
|
|
160
|
+
const applyHeaderNavState = () => {
|
|
161
|
+
const path = currentPath.get()
|
|
162
|
+
const navLinks = document.querySelectorAll<HTMLElement>('[data-base-nav-path]')
|
|
163
|
+
|
|
164
|
+
navLinks.forEach((link) => {
|
|
165
|
+
const targetPath = link.dataset.baseNavPath || ''
|
|
166
|
+
link.className = `btn btn-sm ${path === targetPath ? 'btn-primary' : 'btn-ghost'}`.trim()
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
watch(
|
|
171
|
+
() => currentPath.get(),
|
|
172
|
+
() => {
|
|
173
|
+
requestAnimationFrame(applyHeaderNavState)
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
requestAnimationFrame(applyHeaderNavState)
|
|
179
|
+
}, [])
|
|
126
180
|
|
|
127
181
|
return (
|
|
128
|
-
<header className="sticky top-0 z-40 border-b border-base-300/70 bg-base-100/
|
|
129
|
-
<div className="content-width
|
|
130
|
-
<div className="
|
|
182
|
+
<header className="sticky top-0 z-40 border-b border-base-300/70 bg-base-100/90 backdrop-blur">
|
|
183
|
+
<div className="content-width flex flex-wrap items-center gap-3 py-3">
|
|
184
|
+
<div className="min-w-0 flex-1 md:flex-none">
|
|
131
185
|
<RouterLink to="/" className="flex min-w-0 items-center gap-3">
|
|
132
186
|
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-primary text-primary-content shadow-lg shadow-primary/20">
|
|
133
187
|
R
|
|
@@ -137,18 +191,19 @@ const Header: FC = () => {
|
|
|
137
191
|
</div>
|
|
138
192
|
</RouterLink>
|
|
139
193
|
</div>
|
|
140
|
-
<nav className="
|
|
194
|
+
<nav className="order-3 flex w-full flex-wrap gap-2 md:order-none md:w-auto md:flex-1 md:justify-center">
|
|
141
195
|
{navItems.map((item) => (
|
|
142
196
|
<RouterLink
|
|
143
197
|
key={item.to}
|
|
144
198
|
to={item.to}
|
|
145
|
-
|
|
199
|
+
data-base-nav-path={item.to}
|
|
200
|
+
className={navClass(item.to)}
|
|
146
201
|
>
|
|
147
|
-
{item.label}
|
|
202
|
+
<span>{item.label}</span>
|
|
148
203
|
</RouterLink>
|
|
149
204
|
))}
|
|
150
205
|
</nav>
|
|
151
|
-
<div className="
|
|
206
|
+
<div className="flex items-center gap-2 md:ml-auto">
|
|
152
207
|
<ThemePicker />
|
|
153
208
|
</div>
|
|
154
209
|
</div>
|
|
@@ -175,8 +230,8 @@ const HeaderToggleButton: FC<{ hidden: boolean; onToggle: () => void }> = (props
|
|
|
175
230
|
<button
|
|
176
231
|
type="button"
|
|
177
232
|
className="btn btn-circle btn-sm fixed left-3 top-3 z-50 border-base-300 bg-base-100/90 shadow-lg backdrop-blur"
|
|
178
|
-
aria-label={props.hidden ? '
|
|
179
|
-
title={props.hidden ? '
|
|
233
|
+
aria-label={props.hidden ? '显示头部和底部' : '隐藏头部和底部'}
|
|
234
|
+
title={props.hidden ? '显示头部和底部' : '隐藏头部和底部'}
|
|
180
235
|
onClick={props.onToggle}
|
|
181
236
|
>
|
|
182
237
|
{props.hidden ? '显' : '隐'}
|
|
@@ -185,10 +240,10 @@ const HeaderToggleButton: FC<{ hidden: boolean; onToggle: () => void }> = (props
|
|
|
185
240
|
|
|
186
241
|
const SiteLayout: FC = (props) => {
|
|
187
242
|
const route = useRoute()
|
|
188
|
-
const reportHeaderHidden = ref(getStoredHeaderHidden())
|
|
189
243
|
const currentPath = computed(() => route.get()?.path || '/')
|
|
190
|
-
const
|
|
191
|
-
const
|
|
244
|
+
const allowHeaderToggle = computed(() => currentPath.get() === '/report')
|
|
245
|
+
const reportHeaderHidden = ref(getStoredHeaderHidden())
|
|
246
|
+
const hideHeader = computed(() => allowHeaderToggle.get() && reportHeaderHidden.value)
|
|
192
247
|
|
|
193
248
|
watch(
|
|
194
249
|
() => reportHeaderHidden.value,
|
|
@@ -201,7 +256,7 @@ const SiteLayout: FC = (props) => {
|
|
|
201
256
|
<div className="app-shell">
|
|
202
257
|
<div className="app-surface">
|
|
203
258
|
{!hideHeader.get() && <Header />}
|
|
204
|
-
{
|
|
259
|
+
{allowHeaderToggle.get() && (
|
|
205
260
|
<HeaderToggleButton
|
|
206
261
|
hidden={hideHeader.get()}
|
|
207
262
|
onToggle={() => {
|
|
@@ -209,12 +264,10 @@ const SiteLayout: FC = (props) => {
|
|
|
209
264
|
}}
|
|
210
265
|
/>
|
|
211
266
|
)}
|
|
212
|
-
<main
|
|
213
|
-
className={`content-width pb-10 ${hideHeader.get() ? 'pt-6 sm:pt-8' : 'pt-6 sm:pt-8'}`}
|
|
214
|
-
>
|
|
267
|
+
<main className="content-width pb-10 pt-6 sm:pt-8">
|
|
215
268
|
{props.children}
|
|
216
269
|
</main>
|
|
217
|
-
<Footer />
|
|
270
|
+
{!hideHeader.get() && <Footer />}
|
|
218
271
|
</div>
|
|
219
272
|
</div>
|
|
220
273
|
)
|