|
@@ -0,0 +1,241 @@
|
|
|
+import PageHeader from '../../components/PageHeader';
|
|
|
+import store from '../../store';
|
|
|
+import { fetchTodoList } from './todoSlice';
|
|
|
+import { useSelector } from 'react-redux';
|
|
|
+import {
|
|
|
+ Col,
|
|
|
+ Row,
|
|
|
+ Switch,
|
|
|
+ Card,
|
|
|
+ Typography,
|
|
|
+ Pagination,
|
|
|
+ Segmented,
|
|
|
+ Input,
|
|
|
+ Button,
|
|
|
+ Popconfirm,
|
|
|
+ Space,
|
|
|
+ message,
|
|
|
+} from 'antd';
|
|
|
+import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
|
|
+
|
|
|
+import { useDispatch } from 'react-redux';
|
|
|
+import { setPage, setFilter } from './todoSlice';
|
|
|
+
|
|
|
+import { useFetcher } from 'react-router-dom';
|
|
|
+import { deleteTodo, editTodo } from '../../api/todos';
|
|
|
+
|
|
|
+const { Meta } = Card;
|
|
|
+const { Paragraph } = Typography;
|
|
|
+
|
|
|
+function TodoList() {
|
|
|
+ const fetcher = useFetcher();
|
|
|
+ const submit = fetcher.submit;
|
|
|
+ const dispatch = useDispatch();
|
|
|
+ let filter = useSelector(({ todos }) => todos.filter);
|
|
|
+ let page = useSelector(({ todos }) => todos.page);
|
|
|
+ let list = useSelector(({ todos }) => todos.list);
|
|
|
+
|
|
|
+ function onPageChange(page, pageSize) {
|
|
|
+ dispatch(setPage({ current: page, count: pageSize }));
|
|
|
+ submit(null, { method: 'get' });
|
|
|
+ }
|
|
|
+
|
|
|
+ function onSelectChange(status) {
|
|
|
+ dispatch(setFilter({ status }));
|
|
|
+ submit(null, { method: 'get' });
|
|
|
+ }
|
|
|
+
|
|
|
+ function onTitleChange(e) {
|
|
|
+ dispatch(setFilter({ title: e.target.value }));
|
|
|
+ }
|
|
|
+
|
|
|
+ function onDeleteTodo(id) {
|
|
|
+ // console.log(id);
|
|
|
+ submit(null, { method: 'DELETE', action: `/todo/delete/${id}` });
|
|
|
+ }
|
|
|
+
|
|
|
+ function onStatusChange(status, todo) {
|
|
|
+ // console.log(todo);
|
|
|
+ let newTodo = { ...todo, status };
|
|
|
+ let formData = new FormData();
|
|
|
+
|
|
|
+ for (var name in newTodo) {
|
|
|
+ formData.append(name, newTodo[name]);
|
|
|
+ }
|
|
|
+
|
|
|
+ fetcher.submit(formData, { method: 'post', action: '/todo/change' });
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="todo-list">
|
|
|
+ <PageHeader addRoute={'/todo/add'}>代办列表</PageHeader>
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ backgroundColor: '#fff',
|
|
|
+ borderRadius: '12px',
|
|
|
+ height: '50px',
|
|
|
+ display: 'flex',
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Space>
|
|
|
+ <Input
|
|
|
+ placeholder="根据标题筛选..."
|
|
|
+ style={{ minWidth: '240px' }}
|
|
|
+ value={filter.title}
|
|
|
+ onChange={onTitleChange}
|
|
|
+ />
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ onClick={() => {
|
|
|
+ submit(null, { method: 'get' });
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 查询
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ </div>
|
|
|
+ <div className="list">
|
|
|
+ <div style={{ width: '70%', margin: '12px auto' }}>
|
|
|
+ <Segmented
|
|
|
+ value={filter.status}
|
|
|
+ onChange={onSelectChange}
|
|
|
+ block
|
|
|
+ options={[
|
|
|
+ {
|
|
|
+ label: (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ padding: 4,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div>所有</div>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ value: 'all',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ padding: 4,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div>进行中</div>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ value: 'active',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ padding: 4,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div>已完成</div>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ value: 'completed',
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <Row>
|
|
|
+ {list.map((todo) => (
|
|
|
+ <Col key={todo.id} span={8}>
|
|
|
+ <Card
|
|
|
+ hoverable
|
|
|
+ style={{
|
|
|
+ width: 300,
|
|
|
+ marginBottom: 12,
|
|
|
+ marginLeft: 'auto',
|
|
|
+ marginRight: 'auto',
|
|
|
+ }}
|
|
|
+ cover={
|
|
|
+ <img
|
|
|
+ alt="代办封面"
|
|
|
+ src={todo.cover}
|
|
|
+ style={{ width: '100%', height: '200px' }}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ actions={[
|
|
|
+ <EditOutlined key="edit" />,
|
|
|
+ <Popconfirm
|
|
|
+ title="删除"
|
|
|
+ description="您确定删除当前代办吗?"
|
|
|
+ onConfirm={() => {
|
|
|
+ onDeleteTodo(todo.id);
|
|
|
+ }}
|
|
|
+ okText="确定"
|
|
|
+ cancelText="取消"
|
|
|
+ >
|
|
|
+ <DeleteOutlined />
|
|
|
+ </Popconfirm>,
|
|
|
+ <Switch
|
|
|
+ checkedChildren="已完成"
|
|
|
+ unCheckedChildren="进行中"
|
|
|
+ checked={!!todo.status}
|
|
|
+ onChange={(status) => {
|
|
|
+ onStatusChange(status - 0, todo);
|
|
|
+ }}
|
|
|
+ />,
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <Meta
|
|
|
+ title={todo.title}
|
|
|
+ description={
|
|
|
+ <Paragraph
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ ellipsis={{
|
|
|
+ rows: 1,
|
|
|
+ tooltip: todo.description,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {todo.description}
|
|
|
+ </Paragraph>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ ))}
|
|
|
+ </Row>
|
|
|
+ {!!page.total && (
|
|
|
+ <Pagination
|
|
|
+ current={page.current}
|
|
|
+ total={page.total}
|
|
|
+ onChange={onPageChange}
|
|
|
+ pageSize={page.count}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+export default TodoList;
|
|
|
+
|
|
|
+export async function todoListLoader() {
|
|
|
+ store.dispatch(fetchTodoList());
|
|
|
+
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+export async function deleteTodoAction({ params }) {
|
|
|
+ let { code, msg } = await deleteTodo(params.id);
|
|
|
+
|
|
|
+ // . []
|
|
|
+ // code = 0 => message['success'] ; code=1 ==> message['error']
|
|
|
+ message[code ? 'error' : 'success'](msg);
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+export async function changeTodoAction({ request }) {
|
|
|
+ let todo = await request.formData();
|
|
|
+ todo = Object.fromEntries(todo);
|
|
|
+
|
|
|
+ await editTodo(todo);
|
|
|
+
|
|
|
+ return null;
|
|
|
+}
|