Commit b46226a6 authored by zhuwj's avatar zhuwj

init

parents
Pipeline #234 failed with stages
# Created by .ignore support plugin (hsz.mobi)
# IntelliJ project files
.idea
*.iml
out
gen
.DS_Store
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea/
\ No newline at end of file
## install
```bash
# 安装
npm run install
# 修改 src/config 下配置
# dev
npm run start
```
\ No newline at end of file
const {
override,
fixBabelImports
} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
{
"name": "demo-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/jest": "24.0.15",
"@types/node": "12.0.8",
"@types/react": "16.8.22",
"@types/react-dom": "16.8.4",
"antd": "^3.19.6",
"babel-plugin-import": "^1.12.0",
"customize-cra": "^0.2.13",
"parse": "^2.4.0",
"react": "^16.8.6",
"react-app-rewired": "^2.1.3",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1",
"typescript": "3.4.5"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject",
"tailwind:build": "tailwind build src/tailwind.src.css -c tailwind.js -o src/tailwind.css"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/parse": "^2.2.5",
"@types/react-router-dom": "^4.3.4",
"@types/react-test-renderer": "^16.8.2",
"tailwindcss": "^1.0.4"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
import React, { FC } from 'react';
import BasicLayout from './layouts'
import { HashRouter as Router, Redirect, Route, Switch } from 'react-router-dom';
import Chat from './pages/chat';
import DocumentDemo from './pages/document';
import Curd from './pages/curd';
const Index = () => (<div>index</div>);
const NoMatch = () => (<div>not match </div>);
const App: FC = () => {
return (
<div>
<BasicLayout>
<Router>
<Switch>
<Route exact path="/home" component={Index} />
<Route exact path="/document" component={DocumentDemo} />
<Route exact path="/curd" component={Curd} />
<Route exact path="/chat" component={Chat} />
<Redirect from='*' to='/chat' />
<Route component={NoMatch} />
</Switch>
</Router>
</BasicLayout>
</div>
);
};
export default App;
import Parse from '../utils/Parse'
import { IMessage } from '../pages/chat';
// 添加消息
export const createMessage = (data: any) => {
const Message = Parse.Object.extend('Message');
const message = new Message(data);
return message.save()
};
// 获取所有消息
export const getMessages = (): Promise<Array<IMessage>> => {
const Message = Parse.Object.extend('Message');
const query = new Parse.Query<any>(Message);
query.limit(1000);
return query.find();
};
// 清空所有消息
export const clearAllMessages = async () => {
const Messages = Parse.Object.extend('Message');
const query = new Parse.Query<any>(Messages);
query.limit(1000);
return Parse.Object.destroyAll(await query.find())
};
// 监听消息变化
export const listenMessages = () => {
const messages = Parse.Object.extend('Message');
const query = new Parse.Query(messages);
return query.subscribe();
};
export const getTodos = () => {
const Message = Parse.Object.extend('Todo');
const query = new Parse.Query<any>(Message);
query.limit(1000);
return query.find();
};
export const getTodo = (id: any) => {
const Message = Parse.Object.extend('Todo');
const query = new Parse.Query<any>(Message);
query.equalTo('objectId', id);
return query.first();
};
//
export const createTodo = (data: any) => {
const Todo = Parse.Object.extend('Todo');
const todo = new Todo(data);
return todo.save();
};
export const deleteTodo = async (id: any) => {
const Todo = Parse.Object.extend('Todo');
const query = new Parse.Query(Todo);
query.equalTo('objectId', id);
return Parse.Object.destroyAll(await query.find());
};
\ No newline at end of file
export default {
parseServerURL: 'http://localhost:1337/parse',
parseServerAppId: 'APPLICATION_ID',
parseServerKey: 'MASTER_KEY',
}
\ No newline at end of file
@import "tailwind.css"
/* body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
} */
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
import React, { PureComponent } from "react";
import { Menu, Icon } from 'antd';
import SubMenu from 'antd/lib/menu/SubMenu';
import { createHashHistory } from "history";
interface IMenu {
title: string
key: string
icon: string
children?: IMenu
}
const menus: IMenu[] = [
{ title: 'document操作', key: 'document', icon: 'user' },
{ title: 'curd', key: 'curd', icon: 'menu' },
{ title: '聊天', key: 'chat', icon: 'menu' },
{ title: '聊天(redux版)', key: 'chat-redux', icon: 'menu' },
];
class Menus extends PureComponent {
render() {
return (
<>
<div className="m-2 h-10 bg-black-200" />
<Menu
theme="dark"
mode="inline"
onClick={({ key }) => createHashHistory().push(`/${key}`)}
>
{menus.map(item => (
item.children ?
<SubMenu
key={item.key}
title={(
<>
<Icon type={item.icon} />
<span>{item.title}</span>
</>
)}
>
{/*{*/}
{/*item.children.map((item, index) => (*/}
{/*<Menu.Item key={item.key}>{item.title}</Menu.Item>*/}
{/*))*/}
{/*}*/}
</SubMenu>
:
<Menu.Item key={item.key}>
<Icon type={item.icon} />
<span>{item.title}</span>
</Menu.Item>
))}
</Menu>
</>
)
}
}
export default Menus
import 'jest';
import BasicLayout from '..';
import React from 'react';
import renderer, { ReactTestInstance, ReactTestRenderer } from 'react-test-renderer';
describe('Layout: BasicLayout', () => {
it('Render correctly', () => {
const wrapper: ReactTestRenderer = renderer.create(<BasicLayout />);
expect(wrapper.root.children.length).toBe(1);
const outerLayer = wrapper.root.children[0] as ReactTestInstance;
expect(outerLayer.type).toBe('div');
const title = outerLayer.children[0] as ReactTestInstance;
expect(title.type).toBe('h1');
expect(title.children[0]).toBe('Yay! Welcome to umi!');
});
});
.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color .3s;
}
.trigger:hover {
color: #1890ff;
}
.logo {
height: 32px;
background: rgba(255,255,255,.2);
margin: 16px;
}
import React, { PureComponent } from 'react';
import { Layout, Icon } from 'antd';
import './index.less';
import Menus from './Menus';
const { Header, Sider, Content } = Layout;
export interface BasicLayoutProps extends React.Props<any> {
history?: History;
location?: Location;
}
class BasicLayout extends PureComponent<BasicLayoutProps> {
state = {
collapsed: false,
};
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
};
render() {
return (
<Layout>
<Sider
trigger={null}
collapsible={true}
collapsed={this.state.collapsed}
className='min-h-screen'
>
<Menus/>
</Sider>
<Layout>
<Header style={{ background: '#fff', padding: 0 }}>
<Icon
className="trigger px-6"
type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'}
onClick={this.toggle}
/>
</Header>
<Content>
{this.props.children}
</Content>
</Layout>
</Layout>
);
}
};
export default BasicLayout;
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>
import React, { FC } from 'react';
import avatar from '../../../assets/images/avatar.jpeg';
import { IMessage } from '../index';
const ReceiveChat: FC<IMessage> = (props: IMessage) => (
<div className='flex w-32'>
<img src={avatar} alt="" className='w-10 h-10 rounded-full mr-2' />
<div>
<div className="mb-1 text-xs text-gray-600">{props.username}</div>
<div className="p-2 rounded-lg border-2 border-solid border-gray-200 bg-blue-200">{props.content}</div>
</div>
</div>
);
export default ReceiveChat
\ No newline at end of file
import React, { FC } from 'react';
import avatar from '../../../assets/images/avatar.jpeg';
import { IMessage } from '../index';
const SendChat: FC<IMessage> = (props: IMessage) => (
<div className='flex w-full justify-end'>
<div>
<div className="mb-1 text-right text-xs text-gray-600">{props.username}</div>
<div className="p-2 rounded-lg border-2 border-solid border-gray-200 bg-blue-200 max-w-sm text-left">{props.content}</div>
</div>
<img src={avatar} alt="" className='w-10 h-10 rounded-full ml-2' />
</div>
);
export default SendChat
\ No newline at end of file
import React, { PureComponent } from "react";
import { Card, Button, Input, message } from "antd";
import SendChat from './components/SendChat';
import ReceiveChat from './components/ReceiveChat';
import { clearAllMessages, createMessage, getMessages, listenMessages } from '../../api';
import { IConsumerInfo } from '../../types/consumer';
// 定义接口, 接口可继承
export interface IMessage {
username: string
content: string
createdAt?: Date // 加?号表示可以为空
updatedAt?: Date
}
interface IProps {
}
interface IState {
messages: IMessage[] // 数组类型有两种写法, Array<Type>, Type[], 只要 idea 不 warn 用哪种都行
value: string
canInput: boolean
consumerInfo: IConsumerInfo
username: string
}
// class 组件, 通过泛型限制 props, state 的类型
// PureComponent 是经过优化的, 性能会好一些
class Chat extends PureComponent<IProps, IState> {
private subscription: any;
messagesEnd: any;
// state 属性存储组件内部的状态, 不可以直接改变其值, 要通过 this.setState 方法
// setState 方法是异步的, 是不会马上更新的, 只是因为比较快, 所以感觉是同步的而已
state: IState = {
messages: [],
value: '',
canInput: false,
consumerInfo: {} as IConsumerInfo,
username: '',
};
// 组件生命周期, 到此组件已渲染完成, 可以写业务逻辑了
async componentDidMount() {
this.scrollToBottom();
this.initMessages();
this.listenMessage();
}
componentDidUpdate() {
this.scrollToBottom();
}
// 组件方法尽量用箭头函数, 箭头函数会帮你纠正 this 的指向, 让 this 的指向符合你的直觉
// 不用箭头函数需要手动 bind this 的指向, 要写比较多的代码, 而且有时候不注意出 bug 查半天
// 获取消息
initMessages = async (): Promise<void> => {
// 对变量进行类型限制, 实在没类型就用 any
const messages: IMessage[] = (await getMessages()).map((data: any) => ({
username: data.get('username'),
content: data.get('content'),
}));
// 键值一样可以简写, 如下面写全是 message: message, 这是对象字面量的属性值简写
this.setState({ messages })
};
// 监听消息变化
listenMessage = async () => {
// async/await 异步函数, 只要返回的是 Promise 对象就可以用异步函数
// 在函数上声明 async, 在调用的异步函数前加 await, 调用多个异步函数每个都加 await, await func1(); await func2();
// listenMessages 函数返回的是 Promise对象, 这个返回值是不能直接用的, 加了异步函数声明就可以像用同步函数那样用, 可以接收到返回值
this.subscription = await listenMessages();
// 箭头函数简写, 单行不用加{}, 相当于 function() { return this.setState({ canInput: true }) }
// 如果在单行箭头函数里返回值不用加 return, 直接 () => 'return_string'
// () => {}, () => (), () => 'string' 试试这几种写法的区别
this.subscription.on('open', () => this.setState({ canInput: true }));
this.subscription.on('create', (data: any) => {
this.setState({
// 用扩展运算符实现数组合并
messages: [...this.state.messages, { username: data.get('username'), content: data.get('content') }],
}, () => this.scrollToBottom())
});
this.subscription.on('close', () => this.setState({ canInput: false }))
};
handleUsernameChange = ({ target: { value } }: any) => {
this.setState({ username: value })
};
handleLogin = () => {
this.setState({ consumerInfo: { username: this.state.username } })
};
// 处理输入信息, 参数也可以解构赋值
handleMessageChange = ({ target: { value } }: any) => {
this.setState({ value })
};
// 处理发送按钮事件
handleSendMessage = async () => {
await createMessage({ username: this.state.username, content: this.state.value });
this.setState({ value: '' })
};
clearAll = () => {
// Promise 写法
clearAllMessages().then(() => {
message.success('删除成功');
this.initMessages();
})
};
scrollToBottom = () => {
this.messagesEnd && this.messagesEnd.scrollIntoView({ behavior: "smooth" });
};
// 页面销毁时关闭 socket 连接
componentWillUnmount(): void {
setTimeout(() => this.subscription.unsubscribe(), 1000)
}
render(): React.ReactNode {
// 解构赋值
const { value, messages, canInput, consumerInfo, username } = this.state;
// 用 if 判断{}也是 true, 要想其他办法判断对象是否为空
if (Object.keys(consumerInfo).length === 0) {
return (
<div className='m-4 bg-white-200 shadow rounded flex justify-center' style={{ height: '80vh' }}>
<div className='w-1/3 bg-white p-8 text-center mt-32 h-64'>
<div className="my-6 text-xl font-bold">登录</div>
<Input
className='mb-4'
placeholder='请输入用户名'
value={username}
onChange={this.handleUsernameChange}
onKeyUp={({ keyCode }) => keyCode === 13 && this.handleLogin()}
autoFocus={true}
/>
<Button
type='primary'
block={true}
onClick={this.handleLogin}
disabled={!canInput || username.length === 0}
>确定</Button>
</div>
</div>
)
}
return (
// render返回的 jsx 要有一个根标签, 可以用Fragments代替
// jsx 的 class 要写成 className
<div className='m-4'>
<Card
// 如果传入组件的属性是静态字符串可以直接这样写, 不是就要用{}
title='聊天'
extra={
// Fragments简写<></>, 可以减少过多无用的标签
<>
<Button icon='logout' className='mr-1'
onClick={() => this.setState({ consumerInfo: {} as IConsumerInfo, username: '' })}>退出</Button>
<Button icon='delete' onClick={this.clearAll}>清空聊天记录</Button>
</>
}
>
<div className="flex flex-col p-4">
<div
className="mb-4"
style={{ height: '70vh', overflow: 'scroll' }}
>
{/* 渲染列表, 通过 map 方法, 注意 map 里面的方法用的是() */}
{/* 列表里的根标签要尽量有个 key, 且尽量不要用数组下标 */}
{messages.map((msg: IMessage, i) => (
<div
key={i}
className='p-2'
>
{/* 通过扩展运算符把属性批量传进子组件: {...message} */}
{msg.username === username ? <SendChat {...msg} /> : <ReceiveChat {...msg} />}
</div>
))}
{/* 滚动到底部, ref */}
<div ref={(el) => this.messagesEnd = el} />
</div>
<div className='flex justify-between'>
{/* 监听事件,事件都是以 on 开头的属性 */}
<Input
value={value}
onChange={this.handleMessageChange}
onKeyUp={({ keyCode }) => keyCode === 13 && this.handleSendMessage()}
autoFocus={true}
/>
<Button
type='primary'
className='ml-2'
onClick={this.handleSendMessage}
disabled={!canInput || value.length === 0}
>发送</Button>
</div>
</div>
</Card>
</div>
)
}
}
// export导出模块, 在其他模块就可以 import 来导入使用
// 加 default 有什么区别?
export default Chat
\ No newline at end of file
import React, { PureComponent } from 'react';
import { Input, Modal } from 'antd';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface IProps extends RouteComponentProps {
data?: any
onSubmit: (value: any) => void
}
interface IState {
visible: boolean
formValue: {
title: string
content: string
}
}
class EditCurd extends PureComponent<IProps, IState> {
state = {
visible: false,
formValue: {
title: '',
content: '',
}
};
componentDidMount(): void {
console.log(this.props.data)
}
show = () => {
this.setState({ visible: true })
};
hide = () => {
this.setState({ visible: false })
};
handleTitleChange = ({ target: { value } }: any) => {
this.setState({ formValue: { ...this.state.formValue, title: value } })
};
handleContentChange = ({ target: { value } }: any) => {
this.setState({ formValue: { ...this.state.formValue, content: value } })
};
handleOk = () => {
const { onSubmit } = this.props;
onSubmit && onSubmit(this.state.formValue);
this.setState({
formValue: {
title: '',
content: '',
}
});
this.hide()
};
render(): React.ReactNode {
const { data = { title: '', content: '' } } = this.props;
const { visible, formValue } = this.state;
return (
<>
<span onClick={this.show}>{this.props.children}</span>
<Modal title='编辑' visible={visible} onCancel={this.hide} onOk={this.handleOk}>
<Input
value={formValue.title || data.title}
onChange={this.handleTitleChange}
placeholder='请输入标题'
className='mb-2'
/>
<Input.TextArea
value={formValue.content || data.content}
onChange={this.handleContentChange}
placeholder='请输入内容'
/>
</Modal>
</>
)
}
}
export default withRouter(EditCurd);
\ No newline at end of file
import React, { PureComponent } from 'react';
import { Button, Card, message, Popconfirm, Table } from 'antd';
import { ColumnProps } from 'antd/lib/table';
import EditCurd from './edit';
import { createTodo, deleteTodo, getTodo, getTodos } from '../../api';
class Curd extends PureComponent {
columns: ColumnProps<any>[] = [
{
title: '标题',
key: 'title',
dataIndex: 'title',
},
{
title: '内容',
key: 'content',
dataIndex: 'title',
},
{
title: '创建时间',
key: 'createdAt',
dataIndex: 'createdAt',
},
{
title: '操作',
key: 'actions',
render: (_, record) => (
<>
{/*<EditCurd onSubmit={this.handleSubmit}>*/}
{/*/!* 传递参数, 要绑定 this *!/*/}
{/*<Button type='link' size='small' onClick={this.edit.bind(this, record.id)}>编辑</Button>*/}
{/*</EditCurd>*/}
<Popconfirm
placement="rightBottom"
title='确认删除?'
onConfirm={this.del.bind(this, record.id)}
okText="Yes"
cancelText="No"
>
<Button type="link" size="small">删除</Button>
</Popconfirm> </>
)
}
];
state = {
list: [],
formData: {},
};
componentDidMount(): void {
this.initTodos()
}
initTodos = async () => {
const list = (await getTodos()).map((data: any) => ({
id: data.id,
title: data.get('title'),
content: data.get('content'),
createdAt: data.get('createdAt').toLocaleDateString(),
}));
this.setState({ list })
};
handleSubmit = async (value: any) => {
await createTodo(value);
message.success('编辑成功');
this.initTodos()
};
edit = async (id: any) => {
const data = await getTodo(id);
this.setState({
formData: {
title: data.get('title'),
content: data.get('content'),
}
})
};
del = async (id: any) => {
await deleteTodo(id);
message.success('删除成功');
this.initTodos();
};
render(): React.ReactNode {
const { list, formData } = this.state;
return (
<div className='m-4'>
<Card
title='curd'
extra={<EditCurd data={formData} onSubmit={this.handleSubmit}><Button type='primary'>添加</Button></EditCurd>}
>
<Table rowKey='id' columns={this.columns} dataSource={list} />
</Card>
</div>
)
}
}
export default Curd
\ No newline at end of file
import React, { PureComponent } from 'react';
import { Card } from 'antd';
class DocumentDemo extends PureComponent {
componentDidMount(): void {
const demo: HTMLElement = document.getElementById('demo') as HTMLElement;
if (demo) {
let fontSize = 10;
let top = 10;
let left = 10;
let time = 1;
let timer: any = null;
timer = setInterval(() => {
fontSize++;
top++;
left++;
time++;
demo.style.color = 'red';
demo.style.height = '800px';
demo.style.position = 'relative';
demo.style.fontSize = fontSize + 'px';
demo.style.top = top + 'px';
demo.style.left = left + 'px';
if (time >= 50) {
clearInterval(timer)
}
}, 100)
}
}
render(): React.ReactNode {
return (
<div className='m-4'>
<Card
title='document 对象'
>
<div id="demo">demo</div>
</Card>
</div>
);
}
}
export default DocumentDemo
\ No newline at end of file
/// <reference types="react-scripts" />
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
This diff is collapsed.
/*@tailwind base;*/
@tailwind components;
@tailwind utilities;
export interface IConsumerInfo {
avatar?: string
username: string
}
\ No newline at end of file
import Parse from 'parse'
import config from '../config'
Parse.initialize(config.parseServerAppId, '', config.parseServerKey);
Parse.serverURL = config.parseServerURL;
export default Parse
\ No newline at end of file
module.exports = {
theme: {
extend: {}
},
important: true,
variants: {},
plugins: []
}
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": [
"src"
]
}
This diff is collapsed.
# Created by .ignore support plugin (hsz.mobi)
# IntelliJ project files
.idea
*.iml
out
gen
node_modules
yarn-error.log
logs
\ No newline at end of file
## mongodb和 parse-server 安装
https://github.com/parse-community/Parse-Server#getting-started
## install
```bash
# 安装
npm run install
# 修改 src/config 下配置
# dev
npm run start
```
\ No newline at end of file
// declare module 'parse-server';
\ No newline at end of file
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "ts-node app.ts",
"watch-server": "nodemon --watch 'src/**/*' -e ts,tsx --exec 'ts-node' ./src/app.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/parse": "^2.2.5",
"express": "^4.17.1",
"koa": "^2.7.0",
"koa-router": "^7.4.0",
"parse": "^2.4.0",
"parse-server": "^3.4.4"
},
"devDependencies": {
"@types/express": "^4.17.0",
"@types/koa": "^2.0.48",
"@types/koa-router": "^7.0.41",
"nodemon": "^1.19.1",
"ts-node": "^8.3.0",
"typescript": "^3.5.2"
}
}
import * as express from 'express'
import config from './config'
const ParseServer = require('parse-server').ParseServer;
const app = express();
const api = new ParseServer({
databaseURI: config.mongoDbUrl,
cloud: __dirname + '/cloud/index.ts',
appId: config.parseServerAppId,
masterKey: config.parseServerKey,
serverURL: config.parseServerURL,
// push: { ... }, // See the Push wiki page
// filesAdapter: ...,
liveQuery: {
classNames: ["Message"] // List of classes to support for query subscriptions
}
});
// router.get('/', async (ctx) => {
// ctx.body = 'Hello World!';
// });
app.use('/parse', api);
const port = config.port;
const httpServer = require('http').createServer(app);
httpServer.listen(port, () => {
console.log('parse-server running on port ' + port + '.');
});
ParseServer.createLiveQueryServer(httpServer);
\ No newline at end of file
import * as Parse from 'parse/node'
Parse.Cloud.define('hello', async (ctx) =>
// ctx.body = 'Hello World!'
console.log('hello')
)
\ No newline at end of file
export default {
port: 1337,
parseServerURL: 'http://localhost:1337/parse',
parseServerAppId: 'APPLICATION_ID',
parseServerKey: 'MASTER_KEY',
// monggodb://{账号}:{密码}@{地址}/{数据库名}
mongoDbUrl: 'mongodb://demo_app:123456@localhost/demo_app',
}
\ No newline at end of file
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist", // TS文件编译后会放入到此文件夹内
"baseUrl": "./src",
"paths": {
"*": [
"node_modules/*",
"src/types/*"
]
},
"typeRoots": [
"node_modules/@types",
"global.d.ts"
]
},
"include": [
"src/**/*"
]
}
\ No newline at end of file
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment