@talknote/create-talknote-h5 0.0.1
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/README.md +54 -0
- package/bin/create-talknote-h5.js +124 -0
- package/package.json +25 -0
- package/template/.editorconfig +16 -0
- package/template/.eslintignore +6 -0
- package/template/.eslintrc.js +6 -0
- package/template/.prettierignore +14 -0
- package/template/.prettierrc.js +5 -0
- package/template/.stylelintrc.js +3 -0
- package/template/README.md +153 -0
- package/template/_gitignore +42 -0
- package/template/_npmrc +1 -0
- package/template/config/config.ts +14 -0
- package/template/jest-setup.ts +1 -0
- package/template/jest.config.ts +15 -0
- package/template/logo.png +0 -0
- package/template/mock/app.ts +5 -0
- package/template/mock/user.ts +33 -0
- package/template/package.json +49 -0
- package/template/plugin.ts +10 -0
- package/template/src/app.tsx +76 -0
- package/template/src/assets/demoIcon/home.png +0 -0
- package/template/src/assets/demoIcon/home1.png +0 -0
- package/template/src/assets/demoIcon/list.png +0 -0
- package/template/src/assets/demoIcon/list1.png +0 -0
- package/template/src/assets/demoIcon/setting.png +0 -0
- package/template/src/assets/demoIcon/setting1.png +0 -0
- package/template/src/assets/yay.jpg +0 -0
- package/template/src/components/SearchBar/__tests__/__snapshots__/index.test.tsx.snap +143 -0
- package/template/src/components/SearchBar/__tests__/index.test.tsx +9 -0
- package/template/src/components/SearchBar/index.less +3 -0
- package/template/src/components/SearchBar/index.tsx +8 -0
- package/template/src/favicon.ico +0 -0
- package/template/src/global.css +9 -0
- package/template/src/models/index.ts +34 -0
- package/template/src/models/todo.ts +9 -0
- package/template/src/pages/index/index.css +20 -0
- package/template/src/pages/index/index.tsx +24 -0
- package/template/src/pages/list/index.less +4 -0
- package/template/src/pages/list/index.tsx +65 -0
- package/template/src/services/api.ts +5 -0
- package/template/tsconfig.json +3 -0
- package/template/typings.d.ts +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# create-talknote-h5
|
|
2
|
+
|
|
3
|
+
用于创建 Talknote Alita H5 项目的脚手架。
|
|
4
|
+
|
|
5
|
+
## 使用方式
|
|
6
|
+
|
|
7
|
+
发布到 npm 后执行:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn create talknote-h5 my-project
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
其中 `my-project` 是必填的项目名称。项目名称只允许小写字母、数字、点号、下划线和连字符,并且必须以小写字母或数字开头。
|
|
14
|
+
|
|
15
|
+
生成完成后执行:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd my-project
|
|
19
|
+
yarn
|
|
20
|
+
yarn start
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
脚手架不会自动安装依赖,也不会初始化 Git 仓库。
|
|
24
|
+
|
|
25
|
+
## 本地验证
|
|
26
|
+
|
|
27
|
+
在脚手架根目录执行:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm test
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
也可以在脚手架根目录直接执行命令入口:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node bin/create-talknote-h5.js my-project
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 模板维护
|
|
40
|
+
|
|
41
|
+
生成项目的全部内容位于 `template/`。修改模板时,不要把 `node_modules`、构建产物或 Git 数据放入该目录。
|
|
42
|
+
|
|
43
|
+
模板中的 `_gitignore` 和 `_npmrc` 会在生成项目时分别恢复为 `.gitignore` 和 `.npmrc`。
|
|
44
|
+
|
|
45
|
+
## 发布
|
|
46
|
+
|
|
47
|
+
确认包名和版本后,由维护者自行登录 npm 并发布:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm login
|
|
51
|
+
npm publish
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
发布完成后即可使用 `yarn create talknote-h5 my-project`。
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const USAGE = 'Usage: yarn create talknote-h5 <project-name>';
|
|
8
|
+
const PROJECT_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
9
|
+
const SPECIAL_FILE_NAMES = {
|
|
10
|
+
_gitignore: '.gitignore',
|
|
11
|
+
_npmrc: '.npmrc',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function printUsageError(message) {
|
|
15
|
+
process.stderr.write(`Error: ${message}\n${USAGE}\n`);
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function removeTree(targetPath) {
|
|
20
|
+
if (!fs.existsSync(targetPath)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const targetStat = fs.lstatSync(targetPath);
|
|
25
|
+
if (!targetStat.isDirectory() || targetStat.isSymbolicLink()) {
|
|
26
|
+
fs.unlinkSync(targetPath);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const entryName of fs.readdirSync(targetPath)) {
|
|
31
|
+
removeTree(path.join(targetPath, entryName));
|
|
32
|
+
}
|
|
33
|
+
fs.rmdirSync(targetPath);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function copyTemplateEntries(sourceDirectory, targetDirectory) {
|
|
37
|
+
for (const entry of fs.readdirSync(sourceDirectory, { withFileTypes: true })) {
|
|
38
|
+
const targetName = SPECIAL_FILE_NAMES[entry.name] || entry.name;
|
|
39
|
+
const sourcePath = path.join(sourceDirectory, entry.name);
|
|
40
|
+
const targetPath = path.join(targetDirectory, targetName);
|
|
41
|
+
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
fs.mkdirSync(targetPath);
|
|
44
|
+
copyTemplateEntries(sourcePath, targetPath);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (entry.isSymbolicLink()) {
|
|
49
|
+
fs.symlinkSync(fs.readlinkSync(sourcePath), targetPath);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function updateGeneratedPackageName(targetDirectory, projectName) {
|
|
58
|
+
const packagePath = path.join(targetDirectory, 'package.json');
|
|
59
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
60
|
+
packageJson.name = projectName;
|
|
61
|
+
fs.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function main(argumentsList) {
|
|
65
|
+
if (argumentsList.length !== 1) {
|
|
66
|
+
return printUsageError(
|
|
67
|
+
'Project name is required and must be the only argument.',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const projectName = argumentsList[0];
|
|
72
|
+
if (!PROJECT_NAME_PATTERN.test(projectName)) {
|
|
73
|
+
return printUsageError(
|
|
74
|
+
`Invalid project name "${projectName}". Use lowercase letters, numbers, dots, underscores, and hyphens.`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const targetDirectory = path.resolve(process.cwd(), projectName);
|
|
79
|
+
if (fs.existsSync(targetDirectory)) {
|
|
80
|
+
process.stderr.write(
|
|
81
|
+
`Error: Target directory already exists: ${targetDirectory}\n`,
|
|
82
|
+
);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const templateDirectory = path.resolve(__dirname, '..', 'template');
|
|
87
|
+
let targetCreated = false;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
fs.mkdirSync(targetDirectory);
|
|
91
|
+
targetCreated = true;
|
|
92
|
+
copyTemplateEntries(templateDirectory, targetDirectory);
|
|
93
|
+
updateGeneratedPackageName(targetDirectory, projectName);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (targetCreated) {
|
|
96
|
+
try {
|
|
97
|
+
removeTree(targetDirectory);
|
|
98
|
+
} catch (cleanupError) {
|
|
99
|
+
process.stderr.write(
|
|
100
|
+
`Error: Could not clean up ${targetDirectory}: ${cleanupError.message}\n`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
process.stderr.write(`Error: Could not create project: ${error.message}\n`);
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
process.stdout.write(
|
|
110
|
+
`✓ Created ${projectName}\n\nNext steps:\n cd ${projectName}\n yarn\n yarn start\n`,
|
|
111
|
+
);
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (require.main === module) {
|
|
116
|
+
process.exitCode = main(process.argv.slice(2));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = {
|
|
120
|
+
copyTemplateEntries,
|
|
121
|
+
main,
|
|
122
|
+
removeTree,
|
|
123
|
+
updateGeneratedPackageName,
|
|
124
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@talknote/create-talknote-h5",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public",
|
|
5
|
+
"registry": "https://registry.npmjs.org/"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.0.1",
|
|
8
|
+
"description": "Create a Talknote Alita H5 project.",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"private": false,
|
|
11
|
+
"bin": {
|
|
12
|
+
"create-talknote-h5": "bin/create-talknote-h5.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"template",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node tests/create-talknote-h5.test.js"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=14.0.1"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# http://editorconfig.org
|
|
2
|
+
root = true
|
|
3
|
+
|
|
4
|
+
[*]
|
|
5
|
+
indent_style = space
|
|
6
|
+
indent_size = 2
|
|
7
|
+
end_of_line = lf
|
|
8
|
+
charset = utf-8
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
insert_final_newline = true
|
|
11
|
+
|
|
12
|
+
[*.md]
|
|
13
|
+
trim_trailing_whitespace = false
|
|
14
|
+
|
|
15
|
+
[Makefile]
|
|
16
|
+
indent_style = tab
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<h1 align="center">Alita Pro</h1>
|
|
2
|
+
|
|
3
|
+
帮助你快速使用 alita
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
pnpm i
|
|
7
|
+
pnpm start
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### 首页
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
### 列表页
|
|
15
|
+
|
|
16
|
+
列表页支持滚动加载更多,固定头部,状态保持
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### 关于尺寸标注
|
|
22
|
+
|
|
23
|
+
100% 宽为 750px,可在蓝湖等工具中将标准改为 750px,这样可以直接使用标注的尺寸用于 caa class 中的尺寸,最终运行会根据手机适配,将 px 自动转为对应的 rem 值。
|
|
24
|
+
|
|
25
|
+
值得注意的是,写在内联样式 style 中的 px 值不会被转化,需要手动写明 rem 单位,直接将 px 值除于 100 即可,如 750px,在 style 中应该写为 7.5rem
|
|
26
|
+
|
|
27
|
+
### 关于路由
|
|
28
|
+
|
|
29
|
+
采用约定式路由,只有 `src/pages/**/index.tsx` 会被识别成路由,可使用 npx alita g pages name 来新增页面。
|
|
30
|
+
|
|
31
|
+
> Q: 为什么仅 index.tsx 会被识别成路由?如果我想要嵌套的路由,如 hello/hero 我该怎么办?
|
|
32
|
+
> A: 仅 index.tsx 被识别成路由,会让整个文件结构变得很干净,在移动端上不需要嵌套路由,一般短位链接优势更大
|
|
33
|
+
|
|
34
|
+
### 自带头部
|
|
35
|
+
|
|
36
|
+
可在 `src/app.tsx#L24` 中配置
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
const titleList: TitleListItem[] = [
|
|
40
|
+
{
|
|
41
|
+
pagePath: '/',
|
|
42
|
+
title: '首页',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
pagePath: '/list',
|
|
46
|
+
title: '列表',
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
也可以在页面中独立配置
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { setPageNavBar, useLocation } from 'alita';
|
|
55
|
+
import { LeftOutline } from 'antd-mobile-icons';
|
|
56
|
+
import { useEffect } from 'react';
|
|
57
|
+
|
|
58
|
+
const location = useLocation();
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setPageNavBar({
|
|
61
|
+
pagePath: location.pathname,
|
|
62
|
+
navBar: {
|
|
63
|
+
mode: 'dark',
|
|
64
|
+
pageTitle: '首页动态标题',
|
|
65
|
+
pageBackground: '#fff',
|
|
66
|
+
back: '返回',
|
|
67
|
+
icon: <LeftOutline />,
|
|
68
|
+
leftContent: <div>首页</div>,
|
|
69
|
+
rightContent: <div>详情</div>,
|
|
70
|
+
onLeftClick: (navigate) => {
|
|
71
|
+
navigate(-1);
|
|
72
|
+
},
|
|
73
|
+
hideNavBar: false,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}, []);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> 关闭默认 NavBar 上述 hideNavBar 配置改为 hideNavBar: true
|
|
80
|
+
|
|
81
|
+
### 底部 Tabs
|
|
82
|
+
|
|
83
|
+
可在 `src/app.tsx#L42` 中配置
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
const tabList: TabBarListItem[] = [
|
|
87
|
+
{
|
|
88
|
+
pagePath: '/',
|
|
89
|
+
text: '首页',
|
|
90
|
+
iconPath: HomeGary,
|
|
91
|
+
selectedIconPath: HomeBlue,
|
|
92
|
+
title: '首页',
|
|
93
|
+
iconSize: '',
|
|
94
|
+
badge: '',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
pagePath: '/list',
|
|
98
|
+
text: '列表',
|
|
99
|
+
iconPath: ListGary,
|
|
100
|
+
selectedIconPath: ListBlue,
|
|
101
|
+
title: '列表',
|
|
102
|
+
iconSize: '',
|
|
103
|
+
badge: '',
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
也可以在页面中独立配置
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { setTabBarList, useLocation } from 'alita';
|
|
112
|
+
import { useEffect } from 'react';
|
|
113
|
+
|
|
114
|
+
const location = useLocation();
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
setTabBarList({
|
|
117
|
+
pagePath: location.pathname,
|
|
118
|
+
text: '首页',
|
|
119
|
+
iconSize: '18px',
|
|
120
|
+
badge: '12',
|
|
121
|
+
onPress: () => {
|
|
122
|
+
navigate(location.pathname, { replace: true });
|
|
123
|
+
return '';
|
|
124
|
+
},
|
|
125
|
+
remove: false,
|
|
126
|
+
});
|
|
127
|
+
}, []);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
> 关闭 app.ts 中已经配置好的 Tab ,上述 remove 配置改为 remove: true
|
|
131
|
+
|
|
132
|
+
### 格式化
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
pnpm format
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 构建
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
pnpm build
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 调试工具
|
|
145
|
+
|
|
146
|
+
在 `config/config.ts` 中增加配置
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
aconsole: {
|
|
150
|
+
console: {},
|
|
151
|
+
inspx: {},
|
|
152
|
+
},
|
|
153
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
**/node_modules
|
|
5
|
+
|
|
6
|
+
# production
|
|
7
|
+
/dist
|
|
8
|
+
.mfsu
|
|
9
|
+
# misc
|
|
10
|
+
.DS_Store
|
|
11
|
+
npm-debug.log*
|
|
12
|
+
yarn-error.log
|
|
13
|
+
|
|
14
|
+
/coverage
|
|
15
|
+
.idea
|
|
16
|
+
yarn.lock
|
|
17
|
+
package-lock.json
|
|
18
|
+
pnpm-lock.yaml
|
|
19
|
+
*bak
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# visual studio code
|
|
23
|
+
.history
|
|
24
|
+
*.log
|
|
25
|
+
functions/*
|
|
26
|
+
.temp/**
|
|
27
|
+
|
|
28
|
+
# umi
|
|
29
|
+
.umi
|
|
30
|
+
.umi-production
|
|
31
|
+
/.env.local
|
|
32
|
+
/.umirc.local.ts
|
|
33
|
+
/.umirc.local.js
|
|
34
|
+
/config/config.local.ts
|
|
35
|
+
/config/config.local.js
|
|
36
|
+
|
|
37
|
+
# screenshot
|
|
38
|
+
screenshot
|
|
39
|
+
.firebase
|
|
40
|
+
.eslintcache
|
|
41
|
+
|
|
42
|
+
build
|
package/template/_npmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
registry=https://registry.npmjs.org/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Config, configUmiAlias, createConfig } from 'alita/test';
|
|
2
|
+
|
|
3
|
+
export default async () => {
|
|
4
|
+
return (await configUmiAlias({
|
|
5
|
+
...createConfig({
|
|
6
|
+
target: 'browser',
|
|
7
|
+
}),
|
|
8
|
+
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
|
|
9
|
+
// if you require some es-module npm package, please uncomment below line and insert your package name
|
|
10
|
+
// transformIgnorePatterns: ['node_modules/(?!.*(lodash-es|your-es-pkg-name)/)']
|
|
11
|
+
transformIgnorePatterns: [
|
|
12
|
+
'node_modules/(?!.*(@umijs/renderer-react|umi/client/client/|@umijs/plugins/libs|antd-mobile/es/))',
|
|
13
|
+
],
|
|
14
|
+
})) as Config.InitialOptions;
|
|
15
|
+
};
|
|
Binary file
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
|
|
3
|
+
const waitTime = (time: number = 100) => {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
setTimeout(() => {
|
|
6
|
+
resolve(true);
|
|
7
|
+
}, time);
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
'POST /api/login/account': async (req: Request, res: Response) => {
|
|
13
|
+
const { password, username } = req.body;
|
|
14
|
+
await waitTime(2000);
|
|
15
|
+
if (password === 'xiaohuoni' && username === 'antd.mobile') {
|
|
16
|
+
res.send({
|
|
17
|
+
message: 'ok',
|
|
18
|
+
success: true,
|
|
19
|
+
});
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
res.send({
|
|
23
|
+
message: 'login error',
|
|
24
|
+
success: false,
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
'POST /api/login/outLogin': (req: Request, res: Response) => {
|
|
28
|
+
res.send({ data: {}, success: true });
|
|
29
|
+
},
|
|
30
|
+
'POST /api/register': (req: Request, res: Response) => {
|
|
31
|
+
res.send({ status: 'ok', currentAuthority: 'user', success: true });
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"private": true,
|
|
4
|
+
"scripts": {
|
|
5
|
+
"build": "alita build",
|
|
6
|
+
"dev": "alita dev",
|
|
7
|
+
"format": "prettier --write .",
|
|
8
|
+
"lint-staged": "lint-staged",
|
|
9
|
+
"lint-staged:js": "eslint --ext .ts,.tsx",
|
|
10
|
+
"plugin": "alita plugin list",
|
|
11
|
+
"start": "alita dev",
|
|
12
|
+
"test": "jest"
|
|
13
|
+
},
|
|
14
|
+
"lint-staged": {
|
|
15
|
+
"**/*.less": "stylelint --syntax less",
|
|
16
|
+
"**/*.css": "stylelint --syntax css",
|
|
17
|
+
"**/*.{ts,tsx}": "npm run lint-staged:js",
|
|
18
|
+
"**/*.{ts,tsx,json,jsx,less}": [
|
|
19
|
+
"git add",
|
|
20
|
+
"prettier --write"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@alita/flow": "^3.1.1",
|
|
25
|
+
"alita": "^3.2.27",
|
|
26
|
+
"antd-mobile": "^5.15.1",
|
|
27
|
+
"react": "^18.2.0",
|
|
28
|
+
"react-dom": "^18.2.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@testing-library/jest-dom": "^5.16.4",
|
|
32
|
+
"@testing-library/react": "^13.4.0",
|
|
33
|
+
"@types/jest": "^27.5.2",
|
|
34
|
+
"@types/react": "^18.0.15",
|
|
35
|
+
"@types/react-dom": "^18.0.6",
|
|
36
|
+
"@types/testing-library__jest-dom": "^5.14.5",
|
|
37
|
+
"jest": "^27.5.1",
|
|
38
|
+
"prettier": "^2.8.8",
|
|
39
|
+
"ts-node": "^10.9.1",
|
|
40
|
+
"typescript": "^5.2.2"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=14.0.1"
|
|
44
|
+
},
|
|
45
|
+
"gitHooks": {
|
|
46
|
+
"pre-commit": "lint-staged",
|
|
47
|
+
"commit-msg": "alita verify-commit"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IApi } from 'alita';
|
|
2
|
+
|
|
3
|
+
export default (api: IApi) => {
|
|
4
|
+
api.onDevCompileDone((opts) => {
|
|
5
|
+
// console.log('> onDevCompileDone', opts.isFirstCompile);
|
|
6
|
+
});
|
|
7
|
+
api.onBuildComplete((opts) => {
|
|
8
|
+
// console.log('> onBuildComplete', opts.isFirstCompile);
|
|
9
|
+
});
|
|
10
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import HomeGary from '@/assets/demoIcon/home.png';
|
|
2
|
+
import HomeBlue from '@/assets/demoIcon/home1.png';
|
|
3
|
+
import ListGary from '@/assets/demoIcon/list.png';
|
|
4
|
+
import ListBlue from '@/assets/demoIcon/list1.png';
|
|
5
|
+
import type {
|
|
6
|
+
NavBarListItem,
|
|
7
|
+
NavBarProps,
|
|
8
|
+
ResponseError,
|
|
9
|
+
TabBarListItem,
|
|
10
|
+
TabBarProps,
|
|
11
|
+
TitleListItem,
|
|
12
|
+
} from 'alita';
|
|
13
|
+
|
|
14
|
+
export const request = {
|
|
15
|
+
prefix: '/api',
|
|
16
|
+
method: 'get',
|
|
17
|
+
errorHandler: (error: ResponseError) => {
|
|
18
|
+
// 集中处理错误
|
|
19
|
+
console.log(11111111);
|
|
20
|
+
console.log(error);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const titleList: TitleListItem[] = [
|
|
25
|
+
{
|
|
26
|
+
pagePath: '/',
|
|
27
|
+
title: '首页',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
pagePath: '/list',
|
|
31
|
+
title: '列表',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
const navList: NavBarListItem[] = [];
|
|
35
|
+
const navBar: NavBarProps = {
|
|
36
|
+
navList,
|
|
37
|
+
fixed: false,
|
|
38
|
+
onLeftClick: () => {
|
|
39
|
+
// router.goBack();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const tabList: TabBarListItem[] = [
|
|
43
|
+
{
|
|
44
|
+
pagePath: '/',
|
|
45
|
+
text: '首页',
|
|
46
|
+
iconPath: HomeGary,
|
|
47
|
+
selectedIconPath: HomeBlue,
|
|
48
|
+
title: '首页',
|
|
49
|
+
iconSize: '',
|
|
50
|
+
badge: '',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
pagePath: '/list',
|
|
54
|
+
text: '列表',
|
|
55
|
+
iconPath: ListGary,
|
|
56
|
+
selectedIconPath: ListBlue,
|
|
57
|
+
title: '列表',
|
|
58
|
+
iconSize: '',
|
|
59
|
+
badge: '',
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const tabBar: TabBarProps = {
|
|
64
|
+
color: `#999999`,
|
|
65
|
+
selectedColor: '#00A0FF',
|
|
66
|
+
borderStyle: 'white',
|
|
67
|
+
position: 'bottom',
|
|
68
|
+
list: tabList,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const mobileLayout = {
|
|
72
|
+
documentTitle: '默认标题',
|
|
73
|
+
navBar,
|
|
74
|
+
tabBar,
|
|
75
|
+
titleList,
|
|
76
|
+
};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[` 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="search-bar-wrap"
|
|
7
|
+
>
|
|
8
|
+
<div
|
|
9
|
+
class="adm-search-bar"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class="adm-search-bar-input-box"
|
|
13
|
+
>
|
|
14
|
+
<div
|
|
15
|
+
class="adm-search-bar-input-box-icon"
|
|
16
|
+
>
|
|
17
|
+
<svg
|
|
18
|
+
class="antd-mobile-icon"
|
|
19
|
+
height="1em"
|
|
20
|
+
style="vertical-align: -0.125em;"
|
|
21
|
+
viewBox="0 0 48 48"
|
|
22
|
+
width="1em"
|
|
23
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
24
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
25
|
+
>
|
|
26
|
+
<title>
|
|
27
|
+
1A7F0A7E-6023-4A8C-9C26-4DD5452D0C85@2x
|
|
28
|
+
</title>
|
|
29
|
+
<g
|
|
30
|
+
fill="none"
|
|
31
|
+
fill-rule="evenodd"
|
|
32
|
+
id="SearchOutline-SearchOutline"
|
|
33
|
+
stroke="none"
|
|
34
|
+
stroke-width="1"
|
|
35
|
+
>
|
|
36
|
+
<g
|
|
37
|
+
id="SearchOutline-编组"
|
|
38
|
+
>
|
|
39
|
+
<rect
|
|
40
|
+
fill="#FFFFFF"
|
|
41
|
+
height="48"
|
|
42
|
+
id="SearchOutline-矩形"
|
|
43
|
+
opacity="0"
|
|
44
|
+
width="48"
|
|
45
|
+
x="0"
|
|
46
|
+
y="0"
|
|
47
|
+
/>
|
|
48
|
+
<path
|
|
49
|
+
d="M10.2434135,10.1505371 C17.2346315,3.28315429 28.5696354,3.28315429 35.5608534,10.1505371 C42.3159331,16.7859644 42.5440954,27.4048667 36.2453405,34.3093889 L43.7095294,41.6422249 C43.8671196,41.7970419 43.8693677,42.0502979 43.7145508,42.2078881 C43.7128864,42.2095822 43.7112069,42.2112616 43.7095126,42.2129259 L42.1705322,43.7246464 C42.014915,43.8775072 41.7655181,43.8775006 41.6099089,43.7246316 L34.0775268,36.3248916 L34.0775268,36.3248916 C27.0485579,41.8551751 16.7593545,41.4200547 10.2434135,35.0195303 C3.25219551,28.1521474 3.25219551,17.0179199 10.2434135,10.1505371 Z M12.3532001,12.2229532 C6.52718516,17.9457722 6.52718516,27.2242951 12.3532001,32.9471142 C18.1792151,38.6699332 27.6250517,38.6699332 33.4510667,32.9471142 C39.2770817,27.2242951 39.2770817,17.9457722 33.4510667,12.2229532 C27.6250517,6.50013419 18.1792151,6.50013419 12.3532001,12.2229532 Z"
|
|
50
|
+
fill="currentColor"
|
|
51
|
+
fill-rule="nonzero"
|
|
52
|
+
id="SearchOutline-形状"
|
|
53
|
+
/>
|
|
54
|
+
</g>
|
|
55
|
+
</g>
|
|
56
|
+
</svg>
|
|
57
|
+
</div>
|
|
58
|
+
<div
|
|
59
|
+
class="adm-input adm-search-bar-input"
|
|
60
|
+
>
|
|
61
|
+
<input
|
|
62
|
+
class="adm-input-element"
|
|
63
|
+
enterkeyhint="search"
|
|
64
|
+
type="search"
|
|
65
|
+
value=""
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
exports[`SearchBar Render correctly 1`] = `
|
|
75
|
+
<div>
|
|
76
|
+
<div
|
|
77
|
+
class="search-bar-wrap"
|
|
78
|
+
>
|
|
79
|
+
<div
|
|
80
|
+
class="adm-search-bar"
|
|
81
|
+
>
|
|
82
|
+
<div
|
|
83
|
+
class="adm-search-bar-input-box"
|
|
84
|
+
>
|
|
85
|
+
<div
|
|
86
|
+
class="adm-search-bar-input-box-icon"
|
|
87
|
+
>
|
|
88
|
+
<svg
|
|
89
|
+
class="antd-mobile-icon"
|
|
90
|
+
height="1em"
|
|
91
|
+
style="vertical-align: -0.125em;"
|
|
92
|
+
viewBox="0 0 48 48"
|
|
93
|
+
width="1em"
|
|
94
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
95
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
96
|
+
>
|
|
97
|
+
<title>
|
|
98
|
+
1A7F0A7E-6023-4A8C-9C26-4DD5452D0C85@2x
|
|
99
|
+
</title>
|
|
100
|
+
<g
|
|
101
|
+
fill="none"
|
|
102
|
+
fill-rule="evenodd"
|
|
103
|
+
id="SearchOutline-SearchOutline"
|
|
104
|
+
stroke="none"
|
|
105
|
+
stroke-width="1"
|
|
106
|
+
>
|
|
107
|
+
<g
|
|
108
|
+
id="SearchOutline-编组"
|
|
109
|
+
>
|
|
110
|
+
<rect
|
|
111
|
+
fill="#FFFFFF"
|
|
112
|
+
height="48"
|
|
113
|
+
id="SearchOutline-矩形"
|
|
114
|
+
opacity="0"
|
|
115
|
+
width="48"
|
|
116
|
+
x="0"
|
|
117
|
+
y="0"
|
|
118
|
+
/>
|
|
119
|
+
<path
|
|
120
|
+
d="M10.2434135,10.1505371 C17.2346315,3.28315429 28.5696354,3.28315429 35.5608534,10.1505371 C42.3159331,16.7859644 42.5440954,27.4048667 36.2453405,34.3093889 L43.7095294,41.6422249 C43.8671196,41.7970419 43.8693677,42.0502979 43.7145508,42.2078881 C43.7128864,42.2095822 43.7112069,42.2112616 43.7095126,42.2129259 L42.1705322,43.7246464 C42.014915,43.8775072 41.7655181,43.8775006 41.6099089,43.7246316 L34.0775268,36.3248916 L34.0775268,36.3248916 C27.0485579,41.8551751 16.7593545,41.4200547 10.2434135,35.0195303 C3.25219551,28.1521474 3.25219551,17.0179199 10.2434135,10.1505371 Z M12.3532001,12.2229532 C6.52718516,17.9457722 6.52718516,27.2242951 12.3532001,32.9471142 C18.1792151,38.6699332 27.6250517,38.6699332 33.4510667,32.9471142 C39.2770817,27.2242951 39.2770817,17.9457722 33.4510667,12.2229532 C27.6250517,6.50013419 18.1792151,6.50013419 12.3532001,12.2229532 Z"
|
|
121
|
+
fill="currentColor"
|
|
122
|
+
fill-rule="nonzero"
|
|
123
|
+
id="SearchOutline-形状"
|
|
124
|
+
/>
|
|
125
|
+
</g>
|
|
126
|
+
</g>
|
|
127
|
+
</svg>
|
|
128
|
+
</div>
|
|
129
|
+
<div
|
|
130
|
+
class="adm-input adm-search-bar-input"
|
|
131
|
+
>
|
|
132
|
+
<input
|
|
133
|
+
class="adm-input-element"
|
|
134
|
+
enterkeyhint="search"
|
|
135
|
+
type="search"
|
|
136
|
+
value=""
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
`;
|
|
Binary file
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { query } from '@/services/api';
|
|
2
|
+
import type { DvaModel } from 'alita';
|
|
3
|
+
|
|
4
|
+
export interface IndexModelState {
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const IndexModel: DvaModel<IndexModelState> = {
|
|
9
|
+
namespace: 'index',
|
|
10
|
+
|
|
11
|
+
state: {
|
|
12
|
+
name: 'Hello Alita',
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
effects: {
|
|
16
|
+
*query({ payload }, { call, put }) {
|
|
17
|
+
const data = yield call(query, payload);
|
|
18
|
+
yield put({
|
|
19
|
+
type: 'save',
|
|
20
|
+
payload: { name: data.text },
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
reducers: {
|
|
25
|
+
save(state, action) {
|
|
26
|
+
return {
|
|
27
|
+
...state,
|
|
28
|
+
...action.payload,
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default IndexModel;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.normal {
|
|
2
|
+
font-family: Georgia, sans-serif;
|
|
3
|
+
margin-top: 4em;
|
|
4
|
+
text-align: center;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.welcome {
|
|
8
|
+
height: 512px;
|
|
9
|
+
background: url('~@/assets/yay.jpg') no-repeat center 0;
|
|
10
|
+
background-size: 512px 512px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.description {
|
|
14
|
+
font-size: 35px;
|
|
15
|
+
margin: 18px 0 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.description code {
|
|
19
|
+
background: #f7f7f7;
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useNavigate } from 'alita';
|
|
2
|
+
import { Button } from 'antd-mobile';
|
|
3
|
+
import styles from './index.css';
|
|
4
|
+
|
|
5
|
+
export default function ({}) {
|
|
6
|
+
const navigate = useNavigate();
|
|
7
|
+
return (
|
|
8
|
+
<div className={styles.normal}>
|
|
9
|
+
<div className={styles.welcome} />
|
|
10
|
+
<p className={styles.description}>
|
|
11
|
+
To get started, edit <code>src/pages/index.js</code> and save to reload.
|
|
12
|
+
</p>
|
|
13
|
+
<Button
|
|
14
|
+
size="large"
|
|
15
|
+
color="primary"
|
|
16
|
+
fill="solid"
|
|
17
|
+
block
|
|
18
|
+
onClick={() => navigate('/list')}
|
|
19
|
+
>
|
|
20
|
+
Go to List
|
|
21
|
+
</Button>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Content, Header, Page } from '@alita/flow';
|
|
2
|
+
import { useNavigate } from 'alita';
|
|
3
|
+
import { InfiniteScroll, List } from 'antd-mobile';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import SearchBar from '../../components/SearchBar';
|
|
6
|
+
|
|
7
|
+
let count = 0;
|
|
8
|
+
|
|
9
|
+
async function mockRequest() {
|
|
10
|
+
console.log('mockRequest');
|
|
11
|
+
if (count >= 5) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
count++;
|
|
15
|
+
return [
|
|
16
|
+
'A',
|
|
17
|
+
'B',
|
|
18
|
+
'C',
|
|
19
|
+
'D',
|
|
20
|
+
'E',
|
|
21
|
+
'F',
|
|
22
|
+
'G',
|
|
23
|
+
'H',
|
|
24
|
+
'I',
|
|
25
|
+
'J',
|
|
26
|
+
'K',
|
|
27
|
+
'L',
|
|
28
|
+
'M',
|
|
29
|
+
'N',
|
|
30
|
+
'O',
|
|
31
|
+
'P',
|
|
32
|
+
'Q',
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default () => {
|
|
37
|
+
const [data, setData] = useState<string[]>([]);
|
|
38
|
+
const [hasMore, setHasMore] = useState(true);
|
|
39
|
+
const navigate = useNavigate();
|
|
40
|
+
async function loadMore() {
|
|
41
|
+
const append = await mockRequest();
|
|
42
|
+
setData((val) => [...val, ...append]);
|
|
43
|
+
setHasMore(append.length > 0);
|
|
44
|
+
}
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
loadMore();
|
|
47
|
+
}, []);
|
|
48
|
+
return (
|
|
49
|
+
<Page>
|
|
50
|
+
<Header>
|
|
51
|
+
<SearchBar placeholder="请输入内容" />
|
|
52
|
+
</Header>
|
|
53
|
+
<Content>
|
|
54
|
+
<List>
|
|
55
|
+
{data.map((item, index) => (
|
|
56
|
+
<List.Item key={index} onClick={() => navigate(-1)}>
|
|
57
|
+
{item}
|
|
58
|
+
</List.Item>
|
|
59
|
+
))}
|
|
60
|
+
</List>
|
|
61
|
+
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
|
|
62
|
+
</Content>
|
|
63
|
+
</Page>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'alita/typings';
|