nsbp-cli 0.1.0

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +150 -0
  3. package/bin/nsbp.js +145 -0
  4. package/package.json +43 -0
  5. package/scripts/sync-template.js +277 -0
  6. package/templates/basic/.prettierignore +5 -0
  7. package/templates/basic/.prettierrc +5 -0
  8. package/templates/basic/README.md +13 -0
  9. package/templates/basic/package.json +101 -0
  10. package/templates/basic/postcss.config.js +7 -0
  11. package/templates/basic/public/favicon.ico +0 -0
  12. package/templates/basic/public/images/test/0.jpg +0 -0
  13. package/templates/basic/public/images/test/1.jpg +0 -0
  14. package/templates/basic/public/images/test/2.jpg +0 -0
  15. package/templates/basic/public/images/test/3.jpg +0 -0
  16. package/templates/basic/public/images/test/4.jpg +0 -0
  17. package/templates/basic/public/images/test/5.jpg +0 -0
  18. package/templates/basic/scripts/start.js +3 -0
  19. package/templates/basic/src/Routers.tsx +40 -0
  20. package/templates/basic/src/client/index.tsx +37 -0
  21. package/templates/basic/src/component/Header.tsx +38 -0
  22. package/templates/basic/src/component/Layout.tsx +24 -0
  23. package/templates/basic/src/component/Loading.tsx +7 -0
  24. package/templates/basic/src/component/Theme.tsx +14 -0
  25. package/templates/basic/src/containers/Home.tsx +435 -0
  26. package/templates/basic/src/containers/Login.tsx +32 -0
  27. package/templates/basic/src/containers/Photo.tsx +162 -0
  28. package/templates/basic/src/css/test.css +13 -0
  29. package/templates/basic/src/css/test.less +8 -0
  30. package/templates/basic/src/css/test2.sass +7 -0
  31. package/templates/basic/src/css/test3.scss +8 -0
  32. package/templates/basic/src/externals/less.d.ts +4 -0
  33. package/templates/basic/src/externals/window.d.ts +3 -0
  34. package/templates/basic/src/reducers/home.ts +17 -0
  35. package/templates/basic/src/reducers/index.ts +26 -0
  36. package/templates/basic/src/reducers/photo.ts +23 -0
  37. package/templates/basic/src/server/index.ts +29 -0
  38. package/templates/basic/src/server/photo.ts +118 -0
  39. package/templates/basic/src/server/utils.tsx +158 -0
  40. package/templates/basic/src/services/home.ts +52 -0
  41. package/templates/basic/src/services/photo.ts +64 -0
  42. package/templates/basic/src/store/constants.ts +4 -0
  43. package/templates/basic/src/store/index.ts +14 -0
  44. package/templates/basic/src/styled/common.ts +26 -0
  45. package/templates/basic/src/styled/component/header.ts +166 -0
  46. package/templates/basic/src/styled/component/layout.ts +10 -0
  47. package/templates/basic/src/styled/home.ts +789 -0
  48. package/templates/basic/src/styled/photo.ts +44 -0
  49. package/templates/basic/src/styled/test.ts +14 -0
  50. package/templates/basic/src/utils/clientConfig.ts +2 -0
  51. package/templates/basic/src/utils/config.ts +7 -0
  52. package/templates/basic/src/utils/fetch.ts +26 -0
  53. package/templates/basic/src/utils/index.ts +45 -0
  54. package/templates/basic/tsconfig.json +18 -0
  55. package/templates/basic/webpack.base.js +262 -0
  56. package/templates/basic/webpack.client.js +26 -0
  57. package/templates/basic/webpack.server.js +33 -0
@@ -0,0 +1,435 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { Link } from 'react-router-dom'
3
+ import Layout from '../component/Layout'
4
+ import { Helmet } from 'react-helmet'
5
+ import {
6
+ GlobalStyle,
7
+ PageWrapper,
8
+ HeroSection,
9
+ HeroContent,
10
+ HeroTitle,
11
+ HeroSubtitle,
12
+ HeroBadge,
13
+ HeroStats,
14
+ StatCard,
15
+ StatValue,
16
+ StatLabel,
17
+ TechSection,
18
+ SectionHeader,
19
+ SectionTitle,
20
+ SectionDescription,
21
+ FeatureGrid,
22
+ FeatureCard,
23
+ CardIcon,
24
+ CardTitle,
25
+ CardDescription,
26
+ CodeExample,
27
+ ComparisonSection,
28
+ ComparisonTable,
29
+ TableHeader,
30
+ TableRow,
31
+ TableCell,
32
+ TableHeaderCell,
33
+ NsbpJSBadge,
34
+ NextJSBadge,
35
+ PhotoSection,
36
+ PhotoGrid,
37
+ PhotoCard,
38
+ PhotoImageWrapper,
39
+ PhotoImage,
40
+ PhotoName,
41
+ PhotoTitle,
42
+ PhotoCount,
43
+ LoadingContainer,
44
+ LoadingSpinner,
45
+ LoadingText,
46
+ ErrorContainer,
47
+ ErrorTitle,
48
+ ErrorMessage,
49
+ QuickStartSection,
50
+ QuickStartGrid,
51
+ QuickStartCard,
52
+ QuickStartTitle,
53
+ QuickStartCode,
54
+ QuickStartDescription,
55
+ Footer
56
+ } from '../styled/home'
57
+
58
+ interface PhotoMenuItem {
59
+ name: string
60
+ cover?: string
61
+ count?: number
62
+ }
63
+
64
+ const Home: React.FC = () => {
65
+ const [menu, setMenu] = useState<PhotoMenuItem[]>([])
66
+ const [loading, setLoading] = useState(true)
67
+
68
+ useEffect(() => {
69
+ if (typeof window === 'undefined') return
70
+
71
+ setLoading(true)
72
+ // 先检查服务端是否已预取了图片菜单数据
73
+ const serverMenu = window?.context?.state?.photo?.menu || {}
74
+ const serverMenuArray = Array.isArray(serverMenu) ? serverMenu : []
75
+
76
+ if (serverMenuArray.length > 0) {
77
+ setMenu(serverMenuArray)
78
+ setLoading(false)
79
+ } else {
80
+ // 如果服务端没有预取,则在客户端获取
81
+ fetch('/getPhotoMenu')
82
+ .then(res => {
83
+ if (!res.ok) throw new Error(`Status ${res.status}`)
84
+ return res.json()
85
+ })
86
+ .then(data => {
87
+ setMenu(data?.data || [])
88
+ })
89
+ .catch(err => {
90
+ console.error('Failed to load menu:', err)
91
+ setMenu([])
92
+ })
93
+ .finally(() => setLoading(false))
94
+ }
95
+ }, [])
96
+
97
+ const [isLoaded, setIsLoaded] = useState(false)
98
+
99
+ useEffect(() => {
100
+ // 模拟页面加载
101
+ const timer = setTimeout(() => {
102
+ setIsLoaded(true)
103
+ }, 500)
104
+ return () => clearTimeout(timer)
105
+ }, [])
106
+
107
+ return (
108
+ <GlobalStyle>
109
+ {!isLoaded && (
110
+ <div className="page-loader" id="pageLoader">
111
+ <div className="loader-spinner"></div>
112
+ </div>
113
+ )}
114
+ <script dangerouslySetInnerHTML={{
115
+ __html: `
116
+ setTimeout(() => {
117
+ const loader = document.getElementById('pageLoader');
118
+ if (loader) {
119
+ loader.classList.add('fade-out');
120
+ setTimeout(() => loader.remove(), 500);
121
+ }
122
+ }, 800);
123
+ `
124
+ }} />
125
+ <Helmet>
126
+ <title>Nsbp.js - 轻量级 React SSR 框架</title>
127
+ <meta name="description" content="Nsbp.js - 一个轻量级 React SSR 框架,专为低资源部署与高度可定制场景而生。与 Next.js 相比,更节省资源,更灵活配置。" />
128
+ <meta name="keywords" content="Nsbp.js, React SSR, 轻量级, SSR, TypeScript, React 19" />
129
+ <meta property="og:title" content="Nsbp.js - 轻量级 React SSR 框架" />
130
+ <meta property="og:description" content="与 Next.js 相比,Nsbp.js 更轻量、更灵活、更可控。" />
131
+ </Helmet>
132
+
133
+ <Layout query={{}}>
134
+ <PageWrapper>
135
+
136
+ {/* ========================================
137
+ Hero Section - 首屏视觉冲击
138
+ ======================================== */}
139
+ <HeroSection className="fade-in">
140
+ <HeroContent>
141
+ <div className="hero-glow"></div>
142
+ <div className="hero-glow"></div>
143
+ <HeroBadge className="fade-in" style={{animationDelay: '0.1s'}}>🚀 轻量级 React SSR 框架</HeroBadge>
144
+ <HeroTitle className="fade-in" style={{animationDelay: '0.2s'}}>Nsbp.js</HeroTitle>
145
+ <HeroSubtitle className="fade-in" style={{animationDelay: '0.3s'}}>
146
+ 与 Next.js 相比,节省 60% 资源消耗
147
+ <br />
148
+ 完全掌控 Webpack 配置,无黑盒限制
149
+ </HeroSubtitle>
150
+
151
+ <HeroStats>
152
+ <StatCard>
153
+ <StatValue>~60%</StatValue>
154
+ <StatLabel>更少资源</StatLabel>
155
+ </StatCard>
156
+ <StatCard>
157
+ <StatValue>512MB</StatValue>
158
+ <StatLabel>最低内存</StatLabel>
159
+ </StatCard>
160
+ <StatCard>
161
+ <StatValue>100%</StatValue>
162
+ <StatLabel>可定制</StatLabel>
163
+ </StatCard>
164
+ <StatCard>
165
+ <StatValue>TS</StatValue>
166
+ <StatLabel>类型安全</StatLabel>
167
+ </StatCard>
168
+ </HeroStats>
169
+ </HeroContent>
170
+ </HeroSection>
171
+
172
+ {/* ========================================
173
+ 技术特性展示
174
+ ======================================== */}
175
+ <TechSection className="fade-in" style={{animationDelay: '0.4s'}}>
176
+ <SectionHeader>
177
+ <SectionTitle className="fade-in" style={{animationDelay: '0.5s'}}>核心特性</SectionTitle>
178
+ <SectionDescription className="fade-in" style={{animationDelay: '0.6s'}}>
179
+ 基于 React 19 + TypeScript,提供完整的 SSR 能力同时保持极致轻量
180
+ </SectionDescription>
181
+ </SectionHeader>
182
+
183
+ <FeatureGrid>
184
+ <FeatureCard>
185
+ <CardIcon>⚡</CardIcon>
186
+ <CardTitle>极速服务端渲染</CardTitle>
187
+ <CardDescription>
188
+ 服务端渲染 HTML,SEO 友好,首屏秒开
189
+ </CardDescription>
190
+ <CodeExample>{`// 路由 + 预取数据
191
+ // Routers.tsx
192
+ export default [
193
+ {
194
+ path: '/',
195
+ component: Home,
196
+ exact: true,
197
+ loadData: homeLoadData,
198
+ key: 'home'
199
+ }
200
+ ]`}</CodeExample>
201
+ </FeatureCard>
202
+
203
+ <FeatureCard>
204
+ <CardIcon>🔧</CardIcon>
205
+ <CardTitle>完全可控的 Webpack</CardTitle>
206
+ <CardDescription>
207
+ 无黑盒配置,自定义任何构建逻辑
208
+ </CardDescription>
209
+ <CodeExample>{`// 自定义 Webpack 配置
210
+ // webpack.server.js
211
+ module.exports = {
212
+ // 你的配置
213
+ }`}</CodeExample>
214
+ </FeatureCard>
215
+
216
+ <FeatureCard>
217
+ <CardIcon>📦</CardIcon>
218
+ <CardTitle>智能代码分割</CardTitle>
219
+ <CardDescription>
220
+ 基于 @loadable/component,按需加载
221
+ </CardDescription>
222
+ <CodeExample>{`// 组件懒加载
223
+ import loadable from '@loadable/component'
224
+
225
+ const Home = loadable(() => import('./containers/Home'))`}</CodeExample>
226
+ </FeatureCard>
227
+
228
+ <FeatureCard>
229
+ <CardIcon>🧩</CardIcon>
230
+ <CardTitle>React 19 原生支持</CardTitle>
231
+ <CardDescription>
232
+ 利用最新 React 特性,性能和开发体验提升
233
+ </CardDescription>
234
+ <CodeExample>{`// React 19 新特性
235
+ import { use, useTransition } from 'react'
236
+
237
+ // Server Actions
238
+ // Suspense 边界
239
+ // use Optimistic`}</CodeExample>
240
+ </FeatureCard>
241
+
242
+ <FeatureCard>
243
+ <CardIcon>📝</CardIcon>
244
+ <CardTitle>TypeScript 类型安全</CardTitle>
245
+ <CardDescription>
246
+ 完整的类型推断,编译时错误检查
247
+ </CardDescription>
248
+ <CodeExample>{`interface PhotoMenuItem {
249
+ name: string
250
+ cover?: string
251
+ count?: number
252
+ }`}</CodeExample>
253
+ </FeatureCard>
254
+
255
+ <FeatureCard>
256
+ <CardIcon>🖼️</CardIcon>
257
+ <CardTitle>内置图片服务</CardTitle>
258
+ <CardDescription>
259
+ 开箱即用的图片分类和管理接口
260
+ </CardDescription>
261
+ <CodeExample>{`// 图片服务
262
+ // src/server/photo.ts
263
+ export const getPhotoMenu = (req: any, res: any) => {
264
+ const photosDicPath = getPublicImagesPath()
265
+ const fileMenu = getFileMenu(photosDicPath)
266
+ res.json({ data: fileMenu })
267
+ }`}</CodeExample>
268
+ </FeatureCard>
269
+ </FeatureGrid>
270
+ </TechSection>
271
+
272
+ {/* ========================================
273
+ Nsbp.js vs Next.js 对比
274
+ ======================================== */}
275
+ <ComparisonSection className="fade-in" style={{animationDelay: '0.7s'}}>
276
+ <SectionHeader>
277
+ <SectionTitle className="fade-in" style={{animationDelay: '0.8s'}}>Nsbp.js vs Next.js</SectionTitle>
278
+ <SectionDescription className="fade-in" style={{animationDelay: '0.9s'}}>
279
+ 对比两个 SSR 框架的关键差异,帮助你做出正确选择
280
+ </SectionDescription>
281
+ </SectionHeader>
282
+
283
+ <ComparisonTable>
284
+ <TableHeader>
285
+ <TableRow>
286
+ <TableHeaderCell>特性</TableHeaderCell>
287
+ <TableHeaderCell><NsbpJSBadge>Nsbp.js</NsbpJSBadge></TableHeaderCell>
288
+ <TableHeaderCell><NextJSBadge>Next.js</NextJSBadge></TableHeaderCell>
289
+ </TableRow>
290
+ </TableHeader>
291
+ <tbody>
292
+ <TableRow>
293
+ <TableCell><strong>运行时体积</strong></TableCell>
294
+ <TableCell>~5MB</TableCell>
295
+ <TableCell>~20MB</TableCell>
296
+ </TableRow>
297
+ <TableRow>
298
+ <TableCell><strong>最低内存</strong></TableCell>
299
+ <TableCell>512MB</TableCell>
300
+ <TableCell>1GB+</TableCell>
301
+ </TableRow>
302
+ <TableRow>
303
+ <TableCell><strong>构建配置</strong></TableCell>
304
+ <TableCell>✅ 完全可控</TableCell>
305
+ <TableCell>❌ 黑盒封装</TableCell>
306
+ </TableRow>
307
+ <TableRow>
308
+ <TableCell><strong>代码分割</strong></TableCell>
309
+ <TableCell>✅ @loadable/component</TableCell>
310
+ <TableCell>✅ 自动(但有限制)</TableCell>
311
+ </TableRow>
312
+ <TableRow>
313
+ <TableCell><strong>SSR 渲染</strong></TableCell>
314
+ <TableCell>✅ 手动控制</TableCell>
315
+ <TableCell>✅ 自动(但可调)</TableCell>
316
+ </TableRow>
317
+ <TableRow>
318
+ <TableCell><strong>学习曲线</strong></TableCell>
319
+ <TableCell>🟡 中等</TableCell>
320
+ <TableCell>🟢 简单</TableCell>
321
+ </TableRow>
322
+ <TableRow>
323
+ <TableCell><strong>生态集成</strong></TableCell>
324
+ <TableCell>✅ 任意 React 库</TableCell>
325
+ <TableCell>⚠️ 需要官方方案</TableCell>
326
+ </TableRow>
327
+ <TableRow>
328
+ <TableCell><strong>适用场景</strong></TableCell>
329
+ <TableCell>博客、官网、教学</TableCell>
330
+ <TableCell>企业应用、电商</TableCell>
331
+ </TableRow>
332
+ </tbody>
333
+ </ComparisonTable>
334
+ </ComparisonSection>
335
+
336
+ {/* ========================================
337
+ 快速开始
338
+ ======================================== */}
339
+ <QuickStartSection className="fade-in" style={{animationDelay: '1.0s'}}>
340
+ <SectionHeader>
341
+ <SectionTitle className="fade-in" style={{animationDelay: '1.1s'}}>快速开始</SectionTitle>
342
+ <SectionDescription className="fade-in" style={{animationDelay: '1.2s'}}>
343
+ 三步启动你的第一个 SSR 项目
344
+ </SectionDescription>
345
+ </SectionHeader>
346
+
347
+ <QuickStartGrid>
348
+ <QuickStartCard>
349
+ <QuickStartTitle>1️⃣ 创建项目</QuickStartTitle>
350
+ <QuickStartCode>$ npx nsbp create my-app</QuickStartCode>
351
+ <QuickStartDescription>
352
+ 使用 CLI 工具创建新项目
353
+ </QuickStartDescription>
354
+ </QuickStartCard>
355
+
356
+ <QuickStartCard>
357
+ <QuickStartTitle>2️⃣ 启动开发</QuickStartTitle>
358
+ <QuickStartCode>$ npm run dev</QuickStartCode>
359
+ <QuickStartDescription>
360
+ 启动开发服务器,默认端口 3001
361
+ </QuickStartDescription>
362
+ </QuickStartCard>
363
+
364
+ <QuickStartCard>
365
+ <QuickStartTitle>3️⃣ 访问应用</QuickStartTitle>
366
+ <QuickStartCode>http://localhost:3001</QuickStartCode>
367
+ <QuickStartDescription>
368
+ 浏览器访问,开始开发
369
+ </QuickStartDescription>
370
+ </QuickStartCard>
371
+ </QuickStartGrid>
372
+ </QuickStartSection>
373
+
374
+ {/* ========================================
375
+ Photo Menu 示例
376
+ ======================================== */}
377
+ <PhotoSection className="fade-in" style={{animationDelay: '1.3s'}}>
378
+ <SectionHeader>
379
+ <SectionTitle className="fade-in" style={{animationDelay: '1.4s'}}>图片分类示例</SectionTitle>
380
+ <SectionDescription className="fade-in" style={{animationDelay: '1.5s'}}>
381
+ 基于 Nsbp.js 内置的图片服务接口,快速构建图库应用
382
+ </SectionDescription>
383
+ </SectionHeader>
384
+
385
+ {loading ? (
386
+ <LoadingContainer>
387
+ <LoadingSpinner />
388
+ <LoadingText>加载分类...</LoadingText>
389
+ </LoadingContainer>
390
+ ) : menu.length > 0 ? (
391
+ <PhotoGrid>
392
+ {menu.map(item => (
393
+ <Link key={item.name} to={`/photo?dic=${item.name}`}>
394
+ <PhotoCard>
395
+ <PhotoImageWrapper>
396
+ <PhotoImage
397
+ src={item.cover}
398
+ alt={item.name}
399
+ loading="lazy"
400
+ />
401
+ </PhotoImageWrapper>
402
+ <PhotoName>
403
+ <PhotoTitle>{item.name}</PhotoTitle>
404
+ {typeof item.count === 'number' && (
405
+ <PhotoCount>{item.count} 张</PhotoCount>
406
+ )}
407
+ </PhotoName>
408
+ </PhotoCard>
409
+ </Link>
410
+ ))}
411
+ </PhotoGrid>
412
+ ) : (
413
+ <ErrorContainer>
414
+ <ErrorTitle>❌ 暂无分类</ErrorTitle>
415
+ <ErrorMessage>
416
+ 请在 public/images 目录下创建图片文件夹
417
+ </ErrorMessage>
418
+ </ErrorContainer>
419
+ )}
420
+ </PhotoSection>
421
+
422
+ {/* ========================================
423
+ Footer
424
+ ======================================== */}
425
+ <Footer>
426
+ <p>© 2025 Nsbp.js. Built with React 19 + TypeScript.</p>
427
+ </Footer>
428
+
429
+ </PageWrapper>
430
+ </Layout>
431
+ </GlobalStyle>
432
+ )
433
+ }
434
+
435
+ export default Home
@@ -0,0 +1,32 @@
1
+ import React, { Fragment } from 'react'
2
+ import Header from '../component/Header'
3
+ import Layout from '../component/Layout'
4
+ import { Helmet } from 'react-helmet'
5
+ import '../css/test.css'
6
+ import '../css/test.less'
7
+ import '../css/test2.sass'
8
+ import '../css/test3.scss'
9
+ import { Container } from '../styled/test'
10
+
11
+ const Login = ({ query }: any) => {
12
+ return (
13
+ <Fragment>
14
+ <Helmet>
15
+ <title>Login</title>
16
+ <meta name="description" content="Login Description" />
17
+ </Helmet>
18
+ <Header />
19
+ <Layout query={query}>
20
+ <Container>
21
+ <p>login</p>
22
+ <div className="testBox"></div>
23
+ <div className="testBox1"></div>
24
+ <div className="testBox2"></div>
25
+ <div className="testBox3"></div>
26
+ </Container>
27
+ </Layout>
28
+ </Fragment>
29
+ )
30
+ }
31
+
32
+ export default Login
@@ -0,0 +1,162 @@
1
+ import React, { Fragment, useState, useEffect } from 'react'
2
+ import { connect } from 'react-redux'
3
+ import { Link, useLocation } from 'react-router-dom'
4
+ import Header from '../component/Header'
5
+ import Layout from '../component/Layout'
6
+ import { Helmet } from 'react-helmet'
7
+ import { Container, Row } from '../styled/photo'
8
+ import { motion } from 'framer-motion'
9
+ import { isSEO, getLocationParams } from '../utils'
10
+ import { useCurrentFlag } from '../utils/clientConfig'
11
+ import _ from 'lodash'
12
+ import { loadData } from '../services/photo'
13
+
14
+ const springSettings = { type: "spring", stiffness: 170, damping: 26 }
15
+ const NEXT = 'show-next'
16
+
17
+ const Photo = ({ query, data, menu, getPhotoMenu }: any) => {
18
+ const location = useLocation()
19
+ let { dic, from } = query
20
+ const photos = Array.isArray(data) ? data : []
21
+ const [currPhoto, setCurrPhoto] = useState(0)
22
+
23
+ const [currPhotoData, setCurrPhotoData] = useState(photos[0] || [0, 0, ''])
24
+
25
+ const [currWidth, currHeight] = currPhotoData
26
+
27
+ const widths = photos.map(([origW, origH]:any) => (currHeight / origH) * origW)
28
+
29
+ // 同步 currPhoto 和 currPhotoData
30
+ useEffect(() => {
31
+ if (photos[currPhoto]) {
32
+ setCurrPhotoData(photos[currPhoto])
33
+ }
34
+ }, [currPhoto, photos])
35
+
36
+ const leftStartCoords = widths
37
+ .slice(0, currPhoto)
38
+ .reduce((sum:any, width:any) => sum - width, 0)
39
+
40
+ // Calculate position for each photo
41
+ const photoPositions = photos.reduce((acc:any, [origW, origH]:any, i:any, arr:any) => {
42
+ const prevLeft = i === 0 ? leftStartCoords : acc[i-1].left + acc[i-1].width
43
+ acc.push({
44
+ left: prevLeft,
45
+ height: currHeight,
46
+ width: widths[i] || 0
47
+ })
48
+ return acc
49
+ }, [])
50
+
51
+ // console.log('photoPositions', photoPositions)
52
+
53
+ const handleChange = ({ target: { value } }: any) => {
54
+ setCurrPhoto(value)
55
+ }
56
+
57
+ const clickHandler = (btn: any) => {
58
+ let photoIndex = btn === NEXT ? currPhoto + 1 : currPhoto - 1
59
+
60
+ photoIndex = photoIndex >= 0 ? photoIndex : photos.length - 1
61
+ photoIndex = photoIndex >= photos.length ? 0 : photoIndex
62
+
63
+ setCurrPhoto(photoIndex)
64
+ }
65
+
66
+ useEffect(() => {
67
+ const currentDic = getLocationParams('dic')
68
+
69
+ const doGetPhotoMenu = () => {
70
+ getPhotoMenu(currentDic)
71
+ }
72
+
73
+ if (!isSEO()) {
74
+ doGetPhotoMenu()
75
+ } else {
76
+ if(from === 'link'){
77
+ doGetPhotoMenu()
78
+ }
79
+ }
80
+
81
+ // 重置到第一张
82
+ setCurrPhoto(0)
83
+ }, [location?.search])
84
+
85
+ return (
86
+ <Fragment>
87
+ <Helmet>
88
+ <title>Photo</title>
89
+ <meta name="description" content="Photo Description" />
90
+ </Helmet>
91
+ <Header />
92
+
93
+ <Layout query={query}>
94
+ <Container>
95
+ <Row>
96
+ {
97
+ _.map(menu, (item:any, index:number) => {
98
+ return (
99
+ <Link key={`menu${index}`} to={`/photo?dic=${item.name}`}>{item.name}</Link>
100
+ )
101
+ })
102
+ }
103
+ </Row>
104
+ <Row>Scroll Me</Row>
105
+ <Row>
106
+ <button onClick={() => clickHandler('')}>Previous</button>
107
+ <input
108
+ type="range"
109
+ min={0}
110
+ max={photos.length - 1}
111
+ value={currPhoto}
112
+ onChange={handleChange}
113
+ />
114
+ <button onClick={() => clickHandler(NEXT)}>Next</button>
115
+ </Row>
116
+ <div className="demo4">
117
+ <motion.div
118
+ className="demo4-inner"
119
+ animate={{ height: currHeight, width: currWidth }}
120
+ transition={springSettings}
121
+ >
122
+ {photoPositions.map((pos: any, i: any) => (
123
+ <motion.img
124
+ key={i}
125
+ className="demo4-photo"
126
+ src={useCurrentFlag ? `/images/${photos[i][2]}` : photos[i][2]}
127
+ initial={false}
128
+ animate={{
129
+ left: pos.left,
130
+ height: pos.height,
131
+ width: pos.width
132
+ }}
133
+ transition={springSettings}
134
+ style={{
135
+ position: 'absolute',
136
+ top: 0
137
+ }}
138
+ />
139
+ ))}
140
+ </motion.div>
141
+ </div>
142
+ </Container>
143
+ </Layout>
144
+ </Fragment>
145
+ )
146
+ }
147
+
148
+ const mapStateToProps = (state: any) => {
149
+ return {
150
+ query: state?.query,
151
+ menu: state?.photo?.menu,
152
+ data: state?.photo?.data
153
+ }
154
+ }
155
+
156
+ const mapDispatchToProps = (dispatch: any) => ({
157
+ getPhotoMenu(dic:any) {
158
+ dispatch(loadData(null, dic))
159
+ }
160
+ })
161
+
162
+ export default connect(mapStateToProps, mapDispatchToProps)(Photo)
@@ -0,0 +1,13 @@
1
+ .testBox {
2
+ width: 300px;
3
+ height: 200px;
4
+ background: blue;
5
+ transform: rotate(45deg);
6
+ }
7
+
8
+ #name {
9
+ width: 300px;
10
+ height: 200px;
11
+ background: blue;
12
+ transform: rotate(45deg);
13
+ }
@@ -0,0 +1,8 @@
1
+ @base: yellowgreen;
2
+
3
+ .testBox1 {
4
+ width: 300px;
5
+ height: 200px;
6
+ background: @base;
7
+ transform: rotate(45deg);
8
+ }
@@ -0,0 +1,7 @@
1
+ $base: red
2
+
3
+ .testBox2
4
+ width: 300px
5
+ height: 200px
6
+ background: $base
7
+ transform: rotate(45deg)
@@ -0,0 +1,8 @@
1
+ $base: black;
2
+
3
+ .testBox3 {
4
+ width: 300px;
5
+ height: 200px;
6
+ background: $base;
7
+ transform: rotate(45deg);
8
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*.less' {
2
+ const resource: { [key: string]: string }
3
+ export = resource
4
+ }
@@ -0,0 +1,3 @@
1
+ declare interface Window {
2
+ context: any
3
+ }