From 141e8d9818b722a3017fc6ec59578b1b4d5671b4 Mon Sep 17 00:00:00 2001 From: jiangh277 Date: Tue, 5 Aug 2025 19:02:14 +0800 Subject: [PATCH] =?UTF-8?q?Story=E6=8E=92=E7=89=88=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes.ts | 66 +--- src/components/Hooks/useFetchImageUrl.ts | 4 +- src/components/TimelineImage/index.tsx | 5 +- src/locales/zh-CN/pages.ts | 9 + .../components/TimelineItem/TimelineItem.tsx | 133 ------- src/pages/list/basic-list/detail.tsx | 246 ------------- src/pages/list/card-list/_mock.ts | 119 ------- src/pages/list/card-list/data.d.ts | 29 -- src/pages/list/card-list/index.tsx | 104 ------ src/pages/list/card-list/service.ts | 10 - src/pages/list/card-list/style.style.ts | 89 ----- src/pages/list/card-list/utils/utils.style.ts | 6 - src/pages/list/mock/index.ts | 6 - src/pages/list/search/applications/_mock.ts | 118 ------- .../components/StandardFormRow/index.style.ts | 60 ---- .../components/StandardFormRow/index.tsx | 37 -- .../components/TagSelect/index.style.ts | 35 -- .../components/TagSelect/index.tsx | 136 -------- src/pages/list/search/applications/data.d.ts | 33 -- src/pages/list/search/applications/index.tsx | 214 ------------ src/pages/list/search/applications/service.ts | 10 - .../list/search/applications/style.style.ts | 42 --- .../search/applications/utils/utils.style.ts | 6 - src/pages/list/search/articles/_mock.ts | 118 ------- .../ArticleListContent/index.style.ts | 29 -- .../components/ArticleListContent/index.tsx | 29 -- .../components/StandardFormRow/index.style.ts | 62 ---- .../components/StandardFormRow/index.tsx | 37 -- .../components/TagSelect/index.style.ts | 35 -- .../articles/components/TagSelect/index.tsx | 136 -------- src/pages/list/search/articles/data.d.ts | 32 -- src/pages/list/search/articles/index.tsx | 242 ------------- src/pages/list/search/articles/service.ts | 10 - src/pages/list/search/articles/style.style.ts | 30 -- src/pages/list/search/index.tsx | 81 ----- src/pages/list/search/projects/_mock.ts | 118 ------- .../components/AvatarList/index.style.ts | 41 --- .../projects/components/AvatarList/index.tsx | 79 ----- .../components/StandardFormRow/index.style.ts | 62 ---- .../components/StandardFormRow/index.tsx | 37 -- .../components/TagSelect/index.style.ts | 35 -- .../projects/components/TagSelect/index.tsx | 136 -------- src/pages/list/search/projects/data.d.ts | 32 -- src/pages/list/search/projects/index.tsx | 162 --------- src/pages/list/search/projects/service.ts | 10 - src/pages/list/search/projects/style.style.ts | 49 --- src/pages/list/table-list/_mock.ts | 176 ---------- .../list/table-list/components/CreateForm.tsx | 26 -- .../list/table-list/components/UpdateForm.tsx | 159 --------- src/pages/list/table-list/data.d.ts | 36 -- src/pages/list/table-list/index.tsx | 324 ------------------ src/pages/list/table-list/service.ts | 56 --- src/pages/{list/basic-list => story}/_mock.ts | 0 .../components/AddSubTimeLineItemModal.tsx | 0 .../components/AddTimeLineItemModal.tsx | 26 +- .../components/OperationModal.tsx | 0 .../components/SubTimeLineItemModal.tsx | 0 .../components/TimelineItem/TimelineItem.tsx | 182 ++++++++++ .../components/TimelineItem/index.css | 0 .../components/TimelineItem/index.style.ts | 90 +++++ .../components/TimelineItemDrawer.tsx | 14 +- .../{list/basic-list => story}/data.d.ts | 4 +- src/pages/story/detail.tsx | 129 +++++++ .../{list/basic-list => story}/index.css | 0 .../{list/basic-list => story}/index.tsx | 0 .../{list/basic-list => story}/service.ts | 23 +- .../{list/basic-list => story}/style.style.ts | 74 ++++ .../basic-list => story}/utils/utils.style.ts | 0 68 files changed, 536 insertions(+), 3902 deletions(-) delete mode 100644 src/pages/list/basic-list/components/TimelineItem/TimelineItem.tsx delete mode 100644 src/pages/list/basic-list/detail.tsx delete mode 100644 src/pages/list/card-list/_mock.ts delete mode 100644 src/pages/list/card-list/data.d.ts delete mode 100644 src/pages/list/card-list/index.tsx delete mode 100644 src/pages/list/card-list/service.ts delete mode 100644 src/pages/list/card-list/style.style.ts delete mode 100644 src/pages/list/card-list/utils/utils.style.ts delete mode 100644 src/pages/list/mock/index.ts delete mode 100644 src/pages/list/search/applications/_mock.ts delete mode 100644 src/pages/list/search/applications/components/StandardFormRow/index.style.ts delete mode 100644 src/pages/list/search/applications/components/StandardFormRow/index.tsx delete mode 100644 src/pages/list/search/applications/components/TagSelect/index.style.ts delete mode 100644 src/pages/list/search/applications/components/TagSelect/index.tsx delete mode 100644 src/pages/list/search/applications/data.d.ts delete mode 100644 src/pages/list/search/applications/index.tsx delete mode 100644 src/pages/list/search/applications/service.ts delete mode 100644 src/pages/list/search/applications/style.style.ts delete mode 100644 src/pages/list/search/applications/utils/utils.style.ts delete mode 100644 src/pages/list/search/articles/_mock.ts delete mode 100644 src/pages/list/search/articles/components/ArticleListContent/index.style.ts delete mode 100644 src/pages/list/search/articles/components/ArticleListContent/index.tsx delete mode 100644 src/pages/list/search/articles/components/StandardFormRow/index.style.ts delete mode 100644 src/pages/list/search/articles/components/StandardFormRow/index.tsx delete mode 100644 src/pages/list/search/articles/components/TagSelect/index.style.ts delete mode 100644 src/pages/list/search/articles/components/TagSelect/index.tsx delete mode 100644 src/pages/list/search/articles/data.d.ts delete mode 100644 src/pages/list/search/articles/index.tsx delete mode 100644 src/pages/list/search/articles/service.ts delete mode 100644 src/pages/list/search/articles/style.style.ts delete mode 100644 src/pages/list/search/index.tsx delete mode 100644 src/pages/list/search/projects/_mock.ts delete mode 100644 src/pages/list/search/projects/components/AvatarList/index.style.ts delete mode 100644 src/pages/list/search/projects/components/AvatarList/index.tsx delete mode 100644 src/pages/list/search/projects/components/StandardFormRow/index.style.ts delete mode 100644 src/pages/list/search/projects/components/StandardFormRow/index.tsx delete mode 100644 src/pages/list/search/projects/components/TagSelect/index.style.ts delete mode 100644 src/pages/list/search/projects/components/TagSelect/index.tsx delete mode 100644 src/pages/list/search/projects/data.d.ts delete mode 100644 src/pages/list/search/projects/index.tsx delete mode 100644 src/pages/list/search/projects/service.ts delete mode 100644 src/pages/list/search/projects/style.style.ts delete mode 100644 src/pages/list/table-list/_mock.ts delete mode 100644 src/pages/list/table-list/components/CreateForm.tsx delete mode 100644 src/pages/list/table-list/components/UpdateForm.tsx delete mode 100644 src/pages/list/table-list/data.d.ts delete mode 100644 src/pages/list/table-list/index.tsx delete mode 100644 src/pages/list/table-list/service.ts rename src/pages/{list/basic-list => story}/_mock.ts (100%) rename src/pages/{list/basic-list => story}/components/AddSubTimeLineItemModal.tsx (100%) rename src/pages/{list/basic-list => story}/components/AddTimeLineItemModal.tsx (90%) rename src/pages/{list/basic-list => story}/components/OperationModal.tsx (100%) rename src/pages/{list/basic-list => story}/components/SubTimeLineItemModal.tsx (100%) create mode 100644 src/pages/story/components/TimelineItem/TimelineItem.tsx rename src/pages/{list/basic-list => story}/components/TimelineItem/index.css (100%) create mode 100644 src/pages/story/components/TimelineItem/index.style.ts rename src/pages/{list/basic-list => story}/components/TimelineItemDrawer.tsx (90%) rename src/pages/{list/basic-list => story}/data.d.ts (96%) create mode 100644 src/pages/story/detail.tsx rename src/pages/{list/basic-list => story}/index.css (100%) rename src/pages/{list/basic-list => story}/index.tsx (100%) rename src/pages/{list/basic-list => story}/service.ts (72%) rename src/pages/{list/basic-list => story}/style.style.ts (72%) rename src/pages/{list/basic-list => story}/utils/utils.style.ts (100%) diff --git a/config/routes.ts b/config/routes.ts index 6a237ef..9736529 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -73,10 +73,10 @@ export default [ ], }, { - name: 'timeline', + name: '故事', icon: 'smile', - path: '/timeline', - component: './list/basic-list', + path: '/story', + component: './story', }, { name: '图库', @@ -86,7 +86,7 @@ export default [ }, { path: '/timeline/:id', - component: './list/basic-list/detail', + component: './story/detail', }, { path: '/', @@ -122,64 +122,6 @@ export default [ }, ], }, - { - path: '/list', - icon: 'table', - name: 'list', - routes: [ - { - path: '/list/search', - name: 'search-list', - component: './list/search', - routes: [ - { - path: '/list/search', - redirect: '/list/search/articles', - }, - { - name: 'articles', - icon: 'smile', - path: '/list/search/articles', - component: './list/search/articles', - }, - { - name: 'projects', - icon: 'smile', - path: '/list/search/projects', - component: './list/search/projects', - }, - { - name: 'applications', - icon: 'smile', - path: '/list/search/applications', - component: './list/search/applications', - }, - ], - }, - { - path: '/list', - redirect: '/list/table-list', - }, - { - name: 'table-list', - icon: 'smile', - path: '/list/table-list', - component: './table-list', - }, - { - name: 'basic-list', - icon: 'smile', - path: '/list/basic-list', - component: './list/basic-list', - }, - { - name: 'card-list', - icon: 'smile', - path: '/list/card-list', - component: './list/card-list', - }, - ], - }, { path: '/profile', name: 'profile', diff --git a/src/components/Hooks/useFetchImageUrl.ts b/src/components/Hooks/useFetchImageUrl.ts index 39bedb6..3f8aa4d 100644 --- a/src/components/Hooks/useFetchImageUrl.ts +++ b/src/components/Hooks/useFetchImageUrl.ts @@ -1,4 +1,4 @@ -import { fetchImage } from '@/pages/list/basic-list/service'; +import { fetchImage } from '@/pages/story/service'; import { useRequest } from '@umijs/max'; import { useEffect, useState } from 'react'; @@ -18,6 +18,8 @@ const useFetchImageUrl = (imageInstanceId: string) => { useEffect(() => { if (response) { setImageUrl(URL.createObjectURL(response)); + } else { + setImageUrl("error"); } }, [response]); useEffect(() => { diff --git a/src/components/TimelineImage/index.tsx b/src/components/TimelineImage/index.tsx index b24a48b..33db07c 100644 --- a/src/components/TimelineImage/index.tsx +++ b/src/components/TimelineImage/index.tsx @@ -35,12 +35,11 @@ const TimelineImage: React.FC = (props) => { const [previewVisible, setPreviewVisible] = useState(false); // 构建预览列表 - const previewList = imageList.map(item => ({ + imageList.map(item => ({ src: item.instanceId, title: item.imageName })); - - // 预览配置 +// 预览配置 const previewConfig = { visible: previewVisible, onVisibleChange: (visible: boolean) => setPreviewVisible(visible), diff --git a/src/locales/zh-CN/pages.ts b/src/locales/zh-CN/pages.ts index a266bc6..55a2147 100644 --- a/src/locales/zh-CN/pages.ts +++ b/src/locales/zh-CN/pages.ts @@ -64,4 +64,13 @@ export default { 'pages.searchTable.tenThousand': '万', 'pages.searchTable.batchDeletion': '批量删除', 'pages.searchTable.batchApproval': '批量审批', + "story.deleteSuccess": "删除成功", + "story.deleteFailed": "删除失败", + "story.deleteConfirm": "确认删除", + "story.deleteConfirmDescription": "确定要删除这个故事项吗?此操作不可撤销。", + "story.yes": "是", + "story.no": "否", + "story.edit": "编辑", + "story.addSubItem": "添加子项", + "story.delete": "删除" }; diff --git a/src/pages/list/basic-list/components/TimelineItem/TimelineItem.tsx b/src/pages/list/basic-list/components/TimelineItem/TimelineItem.tsx deleted file mode 100644 index e857cc9..0000000 --- a/src/pages/list/basic-list/components/TimelineItem/TimelineItem.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { Badge, Col, Row, Timeline } from 'antd'; -import React from 'react'; - -import TimelineImage from '@/components/TimelineImage'; -import TimelineItemDrawer from '@/pages/list/basic-list/components/TimelineItemDrawer'; -import { StoryItem, TimelineEvent } from '@/pages/list/basic-list/data'; -import './index.css'; - -interface TimelineItemProps { - event: StoryItem; - onUpdate?: (updatedEvent: TimelineEvent) => void; // 数据更新回调 -} - -const TimelineItem = ({ event: initialEvent, onUpdate }: TimelineItemProps) => { - const [openMainDrawer, setOpenMainDrawer] = React.useState(false); - const [expanded, setExpanded] = React.useState(false); // 控制子项展开状态 - - const showMainDrawer = () => { - setOpenMainDrawer(true); - }; - - return ( -
- {/* 主时间线容器 */} -
-
- - - - - -
-

{initialEvent.title}

-

{initialEvent.description}

-
- 故事时间:{initialEvent.storyItemTime} -
- 创建时间:{initialEvent.createTime} -
- 更新时间:{initialEvent.updateTime} -
-
- -
- - {/* 子时间点徽章 */} - {initialEvent.subItems && initialEvent.subItems.length > 0 && ( - { - e.stopPropagation(); - setExpanded(!expanded); - }} - /> - )} -
- - {/* 子时间线列表 */} - {initialEvent.subItems && initialEvent.subItems.length > 0 && expanded && ( -
- ({ - children: ( -
- - - - - -
-

{sub.title}

-

{sub.description}

-
- 故事时间:{sub.storyItemTime} -
- 创建时间:{sub.createTime} -
- 更新时间:{sub.updateTime} -
-
- -
-
- ), - }))} - /> -
- )} - - {/* 主时间点详情抽屉 */} - - -
-
- ); -}; - -export default TimelineItem; diff --git a/src/pages/list/basic-list/detail.tsx b/src/pages/list/basic-list/detail.tsx deleted file mode 100644 index d68d940..0000000 --- a/src/pages/list/basic-list/detail.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import AddTimeLineItemModal from '@/pages/list/basic-list/components/AddTimeLineItemModal'; -import TimelineItem from '@/pages/list/basic-list/components/TimelineItem/TimelineItem'; -import {StoryItem, TimelineEvent} from '@/pages/list/basic-list/data'; -import {queryStoryItem} from '@/pages/list/basic-list/service'; -import {PageContainer} from '@ant-design/pro-components'; -import {useRequest} from '@umijs/max'; -import {FloatButton, Spin, Timeline} from 'antd'; -import {debounce} from 'lodash'; -import React, {useEffect, useRef, useState} from 'react'; -import {useParams} from 'react-router'; -import './index.css'; -import {handleStoryItemList} from "@/utils"; - -interface TimelineItemProps { - children: React.ReactNode; // 修正:使用 ReactNode 更通用 - // label: string -} - -const MAX = 5; -const Index = () => { - const {id: lineId} = useParams<{ id: string }>(); - const containerRef = useRef(null); - const [items, setItems] = useState([]); - const lastScrollTopRef = useRef(0); - const isScrollingUpRef = useRef(false); - const isScrollingDownRef = useRef(false); // 是否向下滚动 - const isFetchingRef = useRef(false); // 防止重复请求 - const [loading, setLoading] = useState(false); - const [hasMoreNew, setHasMoreNew] = useState(true); - const [hasMoreOld, setHasMoreOld] = useState(true); - const [loadCount, setLoadCount] = useState(0); - const [openAddItemModal, setOpenAddItemModal] = useState(false); - - const {data: storyItemList, run} = useRequest(() => { - return queryStoryItem(lineId ?? ''); - }, { - manual: true, - }); -// 初始化加载数据 - useEffect(() => { - run(); - }, [lineId]); - useEffect(() => { - if (!storyItemList?.length) return; - let timelineItems = handleStoryItemList(storyItemList); - // 转换为 Timeline 组件需要的格式 - const formattedItems = timelineItems.map((item: StoryItem) => ({ - children: , - })); - - setItems(formattedItems); - }, [storyItemList]); - const handleScroll = () => { - const container = containerRef.current; - if (!container) return; - - const {scrollTop} = container; - - // 判断滚动方向 - if (scrollTop < lastScrollTopRef.current) { - isScrollingUpRef.current = true; - isScrollingDownRef.current = false; - loadMoreOldData(); - } else if (scrollTop > lastScrollTopRef.current) { - isScrollingUpRef.current = false; - isScrollingDownRef.current = true; - loadMoreNewData(); - } - - lastScrollTopRef.current = scrollTop; - }; - - const loadMoreOldData = async () => { - if (loadCount >= MAX) { - setHasMoreOld(false); - return; - } - if (loading || isFetchingRef.current) return; - isFetchingRef.current = true; - setLoading(true); - console.log('loadMoreData'); - - const container = containerRef.current; - - let scrollTopBeforeLoad = 0; - let beforeScrollHeight = 0; - - if (isScrollingUpRef.current && container) { - scrollTopBeforeLoad = container.scrollTop; - beforeScrollHeight = container.scrollHeight; - } - - setTimeout(() => { - const newItems = [ - { - title: 'New Event Top 1', - description: 'Description from top', - date: new Date().toDateString(), - }, - { - title: 'New Event Top 2', - description: 'Description from top', - date: new Date().toDateString(), - }, - ]; - - const newTimelineItems = newItems.map((item, index) => ({ - children: , - })); - - setItems((prev) => [...newTimelineItems, ...prev]); - setLoadCount((prev) => prev + 1); - - requestAnimationFrame(() => { - if (container && isScrollingUpRef.current) { - const afterScrollHeight = container.scrollHeight; - const newContentHeight = afterScrollHeight - beforeScrollHeight; - - container.scrollTop = scrollTopBeforeLoad + newContentHeight; - } - - isFetchingRef.current = false; - setLoading(false); - }); - }, 500); - }; - const loadMoreNewData = async () => { - if (loadCount >= MAX) { - setHasMoreNew(false); - return; - } - if (loading || isFetchingRef.current) return; - isFetchingRef.current = true; - setLoading(true); - - const container = containerRef.current; - - let scrollTopBeforeLoad = 0; - - if (isScrollingDownRef.current && container) { - scrollTopBeforeLoad = container.scrollTop; - } - - setTimeout(() => { - const newItems = [ - { - title: 'New Event Bottom 1', - description: 'New data from bottom', - date: new Date().toDateString(), - subItems: [ - { - title: 'New sub1 Event Bottom 1.1', - description: 'New data from bottom', - date: new Date().toDateString(), - }, - { - title: 'New Event Bottom 1.1', - description: 'New data from bottom', - date: new Date().toDateString(), - }, - ], - }, - { - title: 'New Event Bottom 2', - description: 'New data from bottom', - date: new Date().toDateString(), - }, - ]; - - const newTimelineItems = newItems.map((item, index) => ({ - children: , - })); - - setItems((prev) => [...prev, ...newTimelineItems]); - setLoadCount((prev) => prev + 1); - - requestAnimationFrame(() => { - if (container && isScrollingDownRef.current) { - // 不需要调整位置,因为是追加在最后 - } - - isFetchingRef.current = false; - setLoading(false); - }); - }, 500); - }; - - const debouncedHandleScroll = debounce(handleScroll, 500); - - // useEffect(() => { - // const container = containerRef.current; - // if (container) { - // container.addEventListener('scroll', debouncedHandleScroll); - // } - // - // return () => { - // if (container) { - // container.removeEventListener('scroll', debouncedHandleScroll); - // } - // }; - // }, []); - - /*useEffect(() => { - const container = containerRef.current; - if (container) { - container.scrollTop = container.scrollHeight; - } - }, [items]); -*/ - const updateTimelineItem = (updatedItem: TimelineEvent) => { - const storageKey = `timelineItems_${updatedItem.id}`; - localStorage.setItem(storageKey, JSON.stringify(updatedItem)); - - // 刷新整个时间线 - const storedItems = localStorage.getItem(`timelineItems_${lineId}`); - const items = storedItems ? JSON.parse(storedItems) : []; - - setItems(items.map((item) => (item.id === updatedItem.id ? updatedItem : item))); - }; - return ( - -
- {!hasMoreOld &&
没有更老的内容
} - {loading && } - - {loading && } - {!hasMoreNew &&
没有更新的内容
} -
- setOpenAddItemModal(true)}/> - { - setOpenAddItemModal(false); - }} - onOk={() => { - setOpenAddItemModal(false); - run(); - }} - lineId={lineId} - isRoot={true} - /> -
- ); -}; - -export default Index; diff --git a/src/pages/list/card-list/_mock.ts b/src/pages/list/card-list/_mock.ts deleted file mode 100644 index fb5c2f7..0000000 --- a/src/pages/list/card-list/_mock.ts +++ /dev/null @@ -1,119 +0,0 @@ -import type { Request, Response } from 'express'; -import type { CardListItemDataType } from './data.d'; - -const titles = [ - 'Alipay', - 'Angular', - 'Ant Design', - 'Ant Design Pro', - 'Bootstrap', - 'React', - 'Vue', - 'Webpack', -]; -const avatars = [ - 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay - 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular - 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design - 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro - 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap - 'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React - 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue - 'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack -]; - -const covers = [ - 'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png', - 'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png', - 'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png', - 'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png', -]; -const desc = [ - '那是一种内在的东西, 他们到达不了,也无法触及的', - '希望是一个好东西,也许是最好的,好东西是不会消亡的', - '生命就像一盒巧克力,结果往往出人意料', - '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', - '那时候我只会想自己想要什么,从不想自己拥有什么', -]; - -const user = [ - '付小小', - '曲丽丽', - '林东东', - '周星星', - '吴加好', - '朱偏右', - '鱼酱', - '乐哥', - '谭小仪', - '仲尼', -]; - -function fakeList(count: number): CardListItemDataType[] { - const list = []; - for (let i = 0; i < count; i += 1) { - list.push({ - id: `fake-list-${i}`, - owner: user[i % 10], - title: titles[i % 8], - avatar: avatars[i % 8], - cover: parseInt(`${i / 4}`, 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)], - status: ['active', 'exception', 'normal'][i % 3] as - | 'normal' - | 'exception' - | 'active' - | 'success', - percent: Math.ceil(Math.random() * 50) + 50, - logo: avatars[i % 8], - href: 'https://ant.design', - updatedAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i).getTime(), - createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i).getTime(), - subDescription: desc[i % 5], - description: - '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。', - activeUser: Math.ceil(Math.random() * 100000) + 100000, - newUser: Math.ceil(Math.random() * 1000) + 1000, - star: Math.ceil(Math.random() * 100) + 100, - like: Math.ceil(Math.random() * 100) + 100, - message: Math.ceil(Math.random() * 10) + 10, - content: - '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', - members: [ - { - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', - name: '曲丽丽', - id: 'member1', - }, - { - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', - name: '王昭君', - id: 'member2', - }, - { - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', - name: '董娜娜', - id: 'member3', - }, - ], - }); - } - - return list; -} - -function getFakeList(req: Request, res: Response) { - const params = req.query as any; - - const count = Number(params.count) * 1 || 20; - - const result = fakeList(count); - return res.json({ - data: { - list: result, - }, - }); -} - -export default { - 'GET /api/card_fake_list': getFakeList, -}; diff --git a/src/pages/list/card-list/data.d.ts b/src/pages/list/card-list/data.d.ts deleted file mode 100644 index c7e663f..0000000 --- a/src/pages/list/card-list/data.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -export type Member = { - avatar: string; - name: string; - id: string; -}; - -export type CardListItemDataType = { - id: string; - owner: string; - title: string; - avatar: string; - cover: string; - status: 'normal' | 'exception' | 'active' | 'success'; - percent: number; - logo: string; - href: string; - body?: any; - updatedAt: number; - createdAt: number; - subDescription: string; - description: string; - activeUser: number; - newUser: number; - star: number; - like: number; - message: number; - content: string; - members: Member[]; -}; diff --git a/src/pages/list/card-list/index.tsx b/src/pages/list/card-list/index.tsx deleted file mode 100644 index 92fc658..0000000 --- a/src/pages/list/card-list/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { PlusOutlined } from '@ant-design/icons'; -import { PageContainer } from '@ant-design/pro-components'; -import { useRequest } from '@umijs/max'; -import { Button, Card, List, Typography } from 'antd'; -import type { CardListItemDataType } from './data.d'; -import { queryFakeList } from './service'; -import useStyles from './style.style'; -const { Paragraph } = Typography; -const CardList = () => { - const { styles } = useStyles(); - const { data, loading } = useRequest(() => { - return queryFakeList({ - count: 8, - }); - }); - const list = data?.list || []; - const content = ( -
-

- 段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态, - 提供跨越设计与开发的体验解决方案。 -

- -
- ); - const extraContent = ( -
- 这是一个标题 -
- ); - const nullData: Partial = {}; - return ( - -
- > - rowKey="id" - loading={loading} - grid={{ - gutter: 16, - xs: 1, - sm: 2, - md: 3, - lg: 3, - xl: 4, - xxl: 4, - }} - dataSource={[nullData, ...list]} - renderItem={(item) => { - if (item && item.id) { - return ( - - 操作一, 操作二]} - > - } - title={{item.title}} - description={ - - {item.description} - - } - /> - - - ); - } - return ( - - - - ); - }} - /> -
-
- ); -}; -export default CardList; diff --git a/src/pages/list/card-list/service.ts b/src/pages/list/card-list/service.ts deleted file mode 100644 index 8095b5f..0000000 --- a/src/pages/list/card-list/service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { request } from '@umijs/max'; -import type { CardListItemDataType } from './data.d'; - -export async function queryFakeList(params: { - count: number; -}): Promise<{ data: { list: CardListItemDataType[] } }> { - return request('/api/card_fake_list', { - params, - }); -} diff --git a/src/pages/list/card-list/style.style.ts b/src/pages/list/card-list/style.style.ts deleted file mode 100644 index 69bbff4..0000000 --- a/src/pages/list/card-list/style.style.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { createStyles } from 'antd-style'; - -const useStyles = createStyles(({ token }) => { - return { - card: { - '.ant-card-meta-title': { - marginBottom: '12px', - '& > a': { - display: 'inline-block', - maxWidth: '100%', - color: token.colorTextHeading, - }, - }, - '.ant-card-body:hover': { - '.ant-card-meta-title > a': { - color: token.colorPrimary, - }, - }, - }, - item: { - height: '64px', - }, - cardList: { - '.ant-list .ant-list-item-content-single': { maxWidth: '100%' }, - }, - extraImg: { - width: '155px', - marginTop: '-20px', - textAlign: 'center', - img: { width: '100%' }, - [`@media screen and (max-width: ${token.screenMD}px)`]: { - display: 'none', - }, - }, - newButton: { - width: '100%', - height: '201px', - color: token.colorTextSecondary, - backgroundColor: token.colorBgContainer, - borderColor: token.colorBorder, - }, - cardAvatar: { - width: '48px', - height: '48px', - borderRadius: '48px', - }, - cardDescription: { - overflow: 'hidden', - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - wordBreak: 'break-all', - }, - pageHeaderContent: { - position: 'relative', - [`@media screen and (max-width: ${token.screenSM}px)`]: { - paddingBottom: '30px', - }, - }, - contentLink: { - marginTop: '16px', - a: { - marginRight: '32px', - img: { - width: '24px', - }, - }, - img: { marginRight: '8px', verticalAlign: 'middle' }, - [`@media screen and (max-width: ${token.screenLG}px)`]: { - a: { - marginRight: '16px', - }, - }, - [`@media screen and (max-width: ${token.screenSM}px)`]: { - position: 'absolute', - bottom: '-4px', - left: '0', - width: '1000px', - a: { - marginRight: '16px', - }, - img: { - marginRight: '4px', - }, - }, - }, - }; -}); - -export default useStyles; diff --git a/src/pages/list/card-list/utils/utils.style.ts b/src/pages/list/card-list/utils/utils.style.ts deleted file mode 100644 index 0ad5e64..0000000 --- a/src/pages/list/card-list/utils/utils.style.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createStyles } from 'antd-style'; - -const useStyles = createStyles(() => { - return {}; -}); -export default useStyles; diff --git a/src/pages/list/mock/index.ts b/src/pages/list/mock/index.ts deleted file mode 100644 index 733e9a0..0000000 --- a/src/pages/list/mock/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { DefaultOptionType } from 'antd/es/select'; - -export const categoryOptions: DefaultOptionType[] = Array.from({ length: 12 }).map((_, index) => ({ - value: `cat${index + 1}`, - label: `类目${index + 1}`, -})); diff --git a/src/pages/list/search/applications/_mock.ts b/src/pages/list/search/applications/_mock.ts deleted file mode 100644 index 4f9f4a3..0000000 --- a/src/pages/list/search/applications/_mock.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { Request, Response } from 'express'; -import type { ListItemDataType } from './data.d'; - -const titles = [ - 'Alipay', - 'Angular', - 'Ant Design', - 'Ant Design Pro', - 'Bootstrap', - 'React', - 'Vue', - 'Webpack', -]; -const avatars = [ - 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay - 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular - 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design - 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro - 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap - 'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React - 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue - 'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack -]; - -const covers = [ - 'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png', - 'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png', - 'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png', - 'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png', -]; -const desc = [ - '那是一种内在的东西, 他们到达不了,也无法触及的', - '希望是一个好东西,也许是最好的,好东西是不会消亡的', - '生命就像一盒巧克力,结果往往出人意料', - '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', - '那时候我只会想自己想要什么,从不想自己拥有什么', -]; -const user = [ - '付小小', - '曲丽丽', - '林东东', - '周星星', - '吴加好', - '朱偏右', - '鱼酱', - '乐哥', - '谭小仪', - '仲尼', -]; - -function fakeList(count: number): ListItemDataType[] { - const list = []; - for (let i = 0; i < count; i += 1) { - list.push({ - id: `fake-list-${i}`, - owner: user[i % 10], - title: titles[i % 8], - avatar: avatars[i % 8], - cover: parseInt(`${i / 4}`, 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)], - status: ['active', 'exception', 'normal'][i % 3] as - | 'normal' - | 'exception' - | 'active' - | 'success', - percent: Math.ceil(Math.random() * 50) + 50, - logo: avatars[i % 8], - href: 'https://ant.design', - updatedAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i).getTime(), - createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i).getTime(), - subDescription: desc[i % 5], - description: - '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。', - activeUser: Math.ceil(Math.random() * 100000) + 100000, - newUser: Math.ceil(Math.random() * 1000) + 1000, - star: Math.ceil(Math.random() * 100) + 100, - like: Math.ceil(Math.random() * 100) + 100, - message: Math.ceil(Math.random() * 10) + 10, - content: - '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', - members: [ - { - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', - name: '曲丽丽', - id: 'member1', - }, - { - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', - name: '王昭君', - id: 'member2', - }, - { - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', - name: '董娜娜', - id: 'member3', - }, - ], - }); - } - - return list; -} - -function getFakeList(req: Request, res: Response) { - const params: any = req.query; - - const count = params.count * 1 || 20; - - const result = fakeList(count); - return res.json({ - data: { - list: result, - }, - }); -} - -export default { - 'GET /api/fake_list': getFakeList, -}; diff --git a/src/pages/list/search/applications/components/StandardFormRow/index.style.ts b/src/pages/list/search/applications/components/StandardFormRow/index.style.ts deleted file mode 100644 index 8915609..0000000 --- a/src/pages/list/search/applications/components/StandardFormRow/index.style.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { createStyles } from 'antd-style'; - -const useStyles = createStyles(({ token }) => { - return { - standardFormRow: { - display: 'flex', - marginBottom: '16px', - paddingBottom: '16px', - borderBottom: `1px dashed ${token.colorSplit}`, - '.ant-form-item, .ant-legacy-form-item': { marginRight: '24px' }, - '.ant-form-item-label, .ant-legacy-form-item-label': { - label: { - marginRight: '0', - color: token.colorText, - }, - }, - '.ant-form-item-label, .ant-legacy-form-item-label, .ant-form-item-control, .ant-legacy-form-item-control': - { padding: '0', lineHeight: '32px' }, - }, - label: { - flex: '0 0 auto', - marginRight: '24px', - color: token.colorTextHeading, - fontSize: token.fontSize, - textAlign: 'right', - '& > span': { - display: 'inline-block', - height: '32px', - lineHeight: '32px', - '&::after': { - content: "':'", - }, - }, - }, - content: { - flex: '1 1 0', - '.ant-form-item, .ant-legacy-form-item': { - '&:last-child': { - marginRight: '0', - }, - }, - }, - standardFormRowLast: { - marginBottom: '0', - paddingBottom: '0', - border: 'none', - }, - standardFormRowBlock: { - '.ant-form-item, .ant-legacy-form-item, div.ant-form-item-control-wrapper, div.ant-legacy-form-item-control-wrapper': - { display: 'block' }, - }, - standardFormRowGrid: { - '.ant-form-item, .ant-legacy-form-item, div.ant-form-item-control-wrapper, div.ant-legacy-form-item-control-wrapper': - { display: 'block' }, - '.ant-form-item-label, .ant-legacy-form-item-label': { float: 'left' }, - }, - }; -}); - -export default useStyles; diff --git a/src/pages/list/search/applications/components/StandardFormRow/index.tsx b/src/pages/list/search/applications/components/StandardFormRow/index.tsx deleted file mode 100644 index 669a2c4..0000000 --- a/src/pages/list/search/applications/components/StandardFormRow/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import useStyles from './index.style'; -type StandardFormRowProps = { - title?: string; - last?: boolean; - block?: boolean; - grid?: boolean; - style?: React.CSSProperties; - children?: React.ReactNode; -}; -const StandardFormRow: React.FC = ({ - title, - children, - last, - block, - grid, - ...rest -}) => { - const { styles } = useStyles(); - const cls = classNames(styles.standardFormRow, { - [styles.standardFormRowBlock]: block, - [styles.standardFormRowLast]: last, - [styles.standardFormRowGrid]: grid, - }); - return ( -
- {title && ( -
- {title} -
- )} -
{children}
-
- ); -}; -export default StandardFormRow; diff --git a/src/pages/list/search/applications/components/TagSelect/index.style.ts b/src/pages/list/search/applications/components/TagSelect/index.style.ts deleted file mode 100644 index d198ab7..0000000 --- a/src/pages/list/search/applications/components/TagSelect/index.style.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { createStyles } from 'antd-style'; - -const useStyles = createStyles(({ token }) => { - return { - tagSelect: { - position: 'relative', - maxHeight: '32px', - marginLeft: '-8px', - overflow: 'hidden', - lineHeight: '32px', - transition: 'all 0.3s', - userSelect: 'none', - '.ant-tag': { - marginRight: '24px', - padding: '0 8px', - fontSize: token.fontSize, - }, - }, - trigger: { - position: 'absolute', - top: '0', - right: '0', - 'span.anticon': { fontSize: '12px' }, - }, - expanded: { - maxHeight: '200px', - transition: 'all 0.3s', - }, - hasExpandTag: { - paddingRight: '50px', - }, - }; -}); - -export default useStyles; diff --git a/src/pages/list/search/applications/components/TagSelect/index.tsx b/src/pages/list/search/applications/components/TagSelect/index.tsx deleted file mode 100644 index a6610a9..0000000 --- a/src/pages/list/search/applications/components/TagSelect/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { DownOutlined, UpOutlined } from '@ant-design/icons'; -import { Tag } from 'antd'; -import classNames from 'classnames'; -import { useMergedState } from 'rc-util'; -import React, { FC, useState } from 'react'; -import useStyles from './index.style'; -const { CheckableTag } = Tag; -export interface TagSelectOptionProps { - value: string | number; - style?: React.CSSProperties; - checked?: boolean; - onChange?: (value: string | number, state: boolean) => void; - children?: React.ReactNode; -} -const TagSelectOption: React.FC & { - isTagSelectOption: boolean; -} = ({ children, checked, onChange, value }) => ( - onChange && onChange(value, state)} - > - {children} - -); - -TagSelectOption.isTagSelectOption = true; - -type TagSelectOptionElement = React.ReactElement; - -export interface TagSelectProps { - onChange?: (value: (string | number)[]) => void; - expandable?: boolean; - value?: (string | number)[]; - defaultValue?: (string | number)[]; - style?: React.CSSProperties; - hideCheckAll?: boolean; - actionsText?: { - expandText?: React.ReactNode; - collapseText?: React.ReactNode; - selectAllText?: React.ReactNode; - }; - className?: string; - Option?: TagSelectOptionProps; - children?: TagSelectOptionElement | TagSelectOptionElement[]; -} -const TagSelect: FC & { - Option: typeof TagSelectOption; -} = (props) => { - const { styles } = useStyles(); - const { children, hideCheckAll = false, className, style, expandable, actionsText = {} } = props; - const [expand, setExpand] = useState(false); - - const [value, setValue] = useMergedState<(string | number)[]>(props.defaultValue || [], { - value: props.value, - defaultValue: props.defaultValue, - onChange: props.onChange, - }); - - const isTagSelectOption = (node: TagSelectOptionElement) => - node && - node.type && - (node.type.isTagSelectOption || node.type.displayName === 'TagSelectOption'); - const getAllTags = () => { - const childrenArray = React.Children.toArray(children) as TagSelectOptionElement[]; - const checkedTags = childrenArray - .filter((child) => isTagSelectOption(child)) - .map((child) => child.props.value); - return checkedTags || []; - }; - const onSelectAll = (checked: boolean) => { - let checkedTags: (string | number)[] = []; - if (checked) { - checkedTags = getAllTags(); - } - setValue(checkedTags); - }; - const handleTagChange = (tag: string | number, checked: boolean) => { - const checkedTags: (string | number)[] = [...(value || [])]; - const index = checkedTags.indexOf(tag); - if (checked && index === -1) { - checkedTags.push(tag); - } else if (!checked && index > -1) { - checkedTags.splice(index, 1); - } - setValue(checkedTags); - }; - const checkedAll = getAllTags().length === value?.length; - const { expandText = '展开', collapseText = '收起', selectAllText = '全部' } = actionsText; - const cls = classNames(styles.tagSelect, className, { - [styles.hasExpandTag]: expandable, - [styles.expanded]: expand, - }); - return ( -
- {hideCheckAll ? null : ( - - {selectAllText} - - )} - {children && - React.Children.map(children, (child: TagSelectOptionElement) => { - if (isTagSelectOption(child)) { - return React.cloneElement(child, { - key: `tag-select-${child.props.value}`, - value: child.props.value, - checked: value && value.indexOf(child.props.value) > -1, - onChange: handleTagChange, - }); - } - return child; - })} - {expandable && ( - { - setExpand(!expand); - }} - > - {expand ? ( - <> - {collapseText} - - ) : ( - <> - {expandText} - - - )} - - )} -
- ); -}; -TagSelect.Option = TagSelectOption; -export default TagSelect; diff --git a/src/pages/list/search/applications/data.d.ts b/src/pages/list/search/applications/data.d.ts deleted file mode 100644 index f39b0a6..0000000 --- a/src/pages/list/search/applications/data.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -export type Member = { - avatar: string; - name: string; - id: string; -}; - -export interface Params { - count: number; -} - -export interface ListItemDataType { - id: string; - owner: string; - title: string; - avatar: string; - cover: string; - status: 'normal' | 'exception' | 'active' | 'success'; - percent: number; - logo: string; - href: string; - body?: any; - updatedAt: number; - createdAt: number; - subDescription: string; - description: string; - activeUser: number; - newUser: number; - star: number; - like: number; - message: number; - content: string; - members: Member[]; -} diff --git a/src/pages/list/search/applications/index.tsx b/src/pages/list/search/applications/index.tsx deleted file mode 100644 index 42da94a..0000000 --- a/src/pages/list/search/applications/index.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import { - DownloadOutlined, - EditOutlined, - EllipsisOutlined, - ShareAltOutlined, -} from '@ant-design/icons'; -import { useRequest } from '@umijs/max'; -import { Avatar, Card, Col, Dropdown, Form, List, Row, Select, Tooltip } from 'antd'; -import numeral from 'numeral'; -import type { FC } from 'react'; -import React from 'react'; -import { categoryOptions } from '../../mock'; -import StandardFormRow from './components/StandardFormRow'; -import TagSelect from './components/TagSelect'; -import type { ListItemDataType } from './data.d'; -import { queryFakeList } from './service'; -import useStyles from './style.style'; -export function formatWan(val: number) { - const v = val * 1; - if (!v || Number.isNaN(v)) return ''; - let result: React.ReactNode = val; - if (val > 10000) { - result = ( - - {Math.floor(val / 10000)} - - 万 - - - ); - } - return result; -} -const formItemLayout = { - wrapperCol: { - xs: { - span: 24, - }, - sm: { - span: 16, - }, - }, -}; -const CardInfo: React.FC<{ - activeUser: React.ReactNode; - newUser: React.ReactNode; -}> = ({ activeUser, newUser }) => { - const { styles } = useStyles(); - return ( -
-
-

活跃用户

-

{activeUser}

-
-
-

新增用户

-

{newUser}

-
-
- ); -}; -export const Applications: FC> = () => { - const { styles } = useStyles(); - const { data, loading, run } = useRequest((values: any) => { - console.log('form data', values); - return queryFakeList({ - count: 8, - }); - }); - - const list = data?.list || []; - - return ( -
- -
{ - run(values); - }} - > - - - - {categoryOptions.map((category) => ( - - {category.label} - - ))} - - - - - - - - - - - - -
-
-
- - rowKey="id" - grid={{ - gutter: 16, - xs: 1, - sm: 2, - md: 3, - lg: 3, - xl: 4, - xxl: 4, - }} - loading={loading} - dataSource={list} - renderItem={(item) => ( - - - - , - - - , - - - , - - - , - ]} - > - } title={item.title} /> -
- -
-
-
- )} - /> -
- ); -}; -export default Applications; diff --git a/src/pages/list/search/applications/service.ts b/src/pages/list/search/applications/service.ts deleted file mode 100644 index c90e68c..0000000 --- a/src/pages/list/search/applications/service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { request } from '@umijs/max'; -import type { ListItemDataType, Params } from './data.d'; - -export async function queryFakeList( - params: Params, -): Promise<{ data: { list: ListItemDataType[] } }> { - return request('/api/fake_list', { - params, - }); -} diff --git a/src/pages/list/search/applications/style.style.ts b/src/pages/list/search/applications/style.style.ts deleted file mode 100644 index f304d85..0000000 --- a/src/pages/list/search/applications/style.style.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createStyles } from 'antd-style'; - -const useStyles = createStyles(({ token }) => { - return { - filterCardList: { - '.ant-card-meta-content': { marginTop: '0' }, - '.ant-card-meta-avatar': { fontSize: '0' }, - '.ant-list .ant-list-item-content-single': { maxWidth: '100%' }, - }, - cardInfo: { - marginTop: '16px', - marginLeft: '40px', - zoom: '1', - '&::before, &::after': { display: 'table', content: "' '" }, - '&::after': { - clear: 'both', - height: '0', - fontSize: '0', - visibility: 'hidden', - }, - '& > div': { - position: 'relative', - float: 'left', - width: '50%', - textAlign: 'left', - p: { - margin: '0', - fontSize: '24px', - lineHeight: '32px', - }, - 'p:first-child': { - marginBottom: '4px', - color: token.colorTextSecondary, - fontSize: '12px', - lineHeight: '20px', - }, - }, - }, - }; -}); - -export default useStyles; diff --git a/src/pages/list/search/applications/utils/utils.style.ts b/src/pages/list/search/applications/utils/utils.style.ts deleted file mode 100644 index 0ad5e64..0000000 --- a/src/pages/list/search/applications/utils/utils.style.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createStyles } from 'antd-style'; - -const useStyles = createStyles(() => { - return {}; -}); -export default useStyles; diff --git a/src/pages/list/search/articles/_mock.ts b/src/pages/list/search/articles/_mock.ts deleted file mode 100644 index fd58835..0000000 --- a/src/pages/list/search/articles/_mock.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { Request, Response } from 'express'; -import type { ListItemDataType } from './data.d'; - -const titles = [ - 'Alipay', - 'Angular', - 'Ant Design', - 'Ant Design Pro', - 'Bootstrap', - 'React', - 'Vue', - 'Webpack', -]; -const avatars = [ - 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay - 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular - 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design - 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro - 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap - 'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React - 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue - 'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack -]; - -const covers = [ - 'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png', - 'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png', - 'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png', - 'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png', -]; -const desc = [ - '那是一种内在的东西, 他们到达不了,也无法触及的', - '希望是一个好东西,也许是最好的,好东西是不会消亡的', - '生命就像一盒巧克力,结果往往出人意料', - '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', - '那时候我只会想自己想要什么,从不想自己拥有什么', -]; -const user = [ - '付小小', - '曲丽丽', - '林东东', - '周星星', - '吴加好', - '朱偏右', - '鱼酱', - '乐哥', - '谭小仪', - '仲尼', -]; - -function fakeList(count: number): ListItemDataType[] { - const list = []; - for (let i = 0; i < count; i += 1) { - list.push({ - id: `fake-list-${Math.random().toString(36).slice(2, 6)}${i}`, - owner: user[i % 10], - title: titles[i % 8], - avatar: avatars[i % 8], - cover: parseInt(`${i / 4}`, 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)], - status: ['active', 'exception', 'normal'][i % 3] as - | 'normal' - | 'exception' - | 'active' - | 'success', - percent: Math.ceil(Math.random() * 50) + 50, - logo: avatars[i % 8], - href: 'https://ant.design', - updatedAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i).getTime(), - createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i).getTime(), - subDescription: desc[i % 5], - description: - '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。', - activeUser: Math.ceil(Math.random() * 100000) + 100000, - newUser: Math.ceil(Math.random() * 1000) + 1000, - star: Math.ceil(Math.random() * 100) + 100, - like: Math.ceil(Math.random() * 100) + 100, - message: Math.ceil(Math.random() * 10) + 10, - content: - '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', - members: [ - { - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', - name: '曲丽丽', - id: 'member1', - }, - { - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', - name: '王昭君', - id: 'member2', - }, - { - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', - name: '董娜娜', - id: 'member3', - }, - ], - }); - } - - return list; -} - -function getFakeList(req: Request, res: Response) { - const params: any = req.query; - - const count = params.count * 1 || 20; - - const result = fakeList(count); - return res.json({ - data: { - list: result, - }, - }); -} - -export default { - 'GET /api/fake_list': getFakeList, -}; diff --git a/src/pages/list/search/articles/components/ArticleListContent/index.style.ts b/src/pages/list/search/articles/components/ArticleListContent/index.style.ts deleted file mode 100644 index c993635..0000000 --- a/src/pages/list/search/articles/components/ArticleListContent/index.style.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { createStyles } from 'antd-style'; - -const useStyles = createStyles(({ token }) => { - return { - description: { - maxWidth: '720px', - lineHeight: '22px', - }, - extra: { - marginTop: '16px', - color: token.colorTextSecondary, - lineHeight: '22px', - '& > em': { - marginLeft: '16px', - color: token.colorTextDisabled, - fontStyle: 'normal', - }, - [`@media screen and (max-width: ${token.screenXS}px)`]: { - '& > em': { - display: 'block', - marginTop: '8px', - marginLeft: '0', - }, - }, - }, - }; -}); - -export default useStyles; diff --git a/src/pages/list/search/articles/components/ArticleListContent/index.tsx b/src/pages/list/search/articles/components/ArticleListContent/index.tsx deleted file mode 100644 index 5e5fde8..0000000 --- a/src/pages/list/search/articles/components/ArticleListContent/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Avatar } from 'antd'; -import dayjs from 'dayjs'; -import React from 'react'; -import useStyles from './index.style'; -type ArticleListContentProps = { - data: { - content: React.ReactNode; - updatedAt: number; - avatar: string; - owner: string; - href: string; - }; -}; -const ArticleListContent: React.FC = ({ - data: { content, updatedAt, avatar, owner, href }, -}) => { - const { styles } = useStyles(); - return ( -
-
{content}
-
- - {owner} 发布在 {href} - {dayjs(updatedAt).format('YYYY-MM-DD HH:mm')} -
-
- ); -}; -export default ArticleListContent; diff --git a/src/pages/list/search/articles/components/StandardFormRow/index.style.ts b/src/pages/list/search/articles/components/StandardFormRow/index.style.ts deleted file mode 100644 index 7b5f569..0000000 --- a/src/pages/list/search/articles/components/StandardFormRow/index.style.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { createStyles } from 'antd-style'; - -const useStyles = createStyles(({ token }) => { - return { - standardFormRow: { - display: 'flex', - width: '100%', - marginBottom: '16px', - paddingBottom: '16px', - borderBottom: `1px dashed ${token.colorSplit}`, - '.ant-form-item, .ant-legacy-form-item': { marginRight: '24px' }, - '.ant-form-item-label, .ant-legacy-form-item-label': { - label: { - marginRight: '0', - color: token.colorText, - }, - }, - '.ant-form-item-label, .ant-legacy-form-item-label, .ant-form-item-control, .ant-legacy-form-item-control': - { padding: '0', lineHeight: '32px' }, - }, - label: { - flex: '0 0 auto', - marginRight: '24px', - color: token.colorTextHeading, - fontSize: token.fontSize, - textAlign: 'right', - '& > span': { - display: 'inline-block', - height: '32px', - lineHeight: '32px', - '&::after': { - content: "':'", - }, - }, - }, - content: { - flex: '1 1 0', - '.ant-form-item, .ant-legacy-form-item': { - '&:last-child': { - display: 'block', - marginRight: '0', - }, - }, - }, - standardFormRowLast: { - marginBottom: '0', - paddingBottom: '0', - border: 'none', - }, - standardFormRowBlock: { - '.ant-form-item, .ant-legacy-form-item, div.ant-form-item-control-wrapper, div.ant-legacy-form-item-control-wrapper': - { display: 'block' }, - }, - standardFormRowGrid: { - '.ant-form-item, .ant-legacy-form-item, div.ant-form-item-control-wrapper, div.ant-legacy-form-item-control-wrapper': - { display: 'block' }, - '.ant-form-item-label, .ant-legacy-form-item-label': { float: 'left' }, - }, - }; -}); - -export default useStyles; diff --git a/src/pages/list/search/articles/components/StandardFormRow/index.tsx b/src/pages/list/search/articles/components/StandardFormRow/index.tsx deleted file mode 100644 index be8c9c7..0000000 --- a/src/pages/list/search/articles/components/StandardFormRow/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import useStyles from './index.style'; -type StandardFormRowProps = { - title?: string; - last?: boolean; - block?: boolean; - grid?: boolean; - children?: React.ReactNode; - style?: React.CSSProperties; -}; -const StandardFormRow: React.FC = ({ - title, - children, - last, - block, - grid, - ...rest -}) => { - const { styles } = useStyles(); - const cls = classNames(styles.standardFormRow, { - [styles.standardFormRowBlock]: block, - [styles.standardFormRowLast]: last, - [styles.standardFormRowGrid]: grid, - }); - return ( -
- {title && ( -
- {title} -
- )} -
{children}
-
- ); -}; -export default StandardFormRow; diff --git a/src/pages/list/search/articles/components/TagSelect/index.style.ts b/src/pages/list/search/articles/components/TagSelect/index.style.ts deleted file mode 100644 index d198ab7..0000000 --- a/src/pages/list/search/articles/components/TagSelect/index.style.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { createStyles } from 'antd-style'; - -const useStyles = createStyles(({ token }) => { - return { - tagSelect: { - position: 'relative', - maxHeight: '32px', - marginLeft: '-8px', - overflow: 'hidden', - lineHeight: '32px', - transition: 'all 0.3s', - userSelect: 'none', - '.ant-tag': { - marginRight: '24px', - padding: '0 8px', - fontSize: token.fontSize, - }, - }, - trigger: { - position: 'absolute', - top: '0', - right: '0', - 'span.anticon': { fontSize: '12px' }, - }, - expanded: { - maxHeight: '200px', - transition: 'all 0.3s', - }, - hasExpandTag: { - paddingRight: '50px', - }, - }; -}); - -export default useStyles; diff --git a/src/pages/list/search/articles/components/TagSelect/index.tsx b/src/pages/list/search/articles/components/TagSelect/index.tsx deleted file mode 100644 index a6610a9..0000000 --- a/src/pages/list/search/articles/components/TagSelect/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { DownOutlined, UpOutlined } from '@ant-design/icons'; -import { Tag } from 'antd'; -import classNames from 'classnames'; -import { useMergedState } from 'rc-util'; -import React, { FC, useState } from 'react'; -import useStyles from './index.style'; -const { CheckableTag } = Tag; -export interface TagSelectOptionProps { - value: string | number; - style?: React.CSSProperties; - checked?: boolean; - onChange?: (value: string | number, state: boolean) => void; - children?: React.ReactNode; -} -const TagSelectOption: React.FC & { - isTagSelectOption: boolean; -} = ({ children, checked, onChange, value }) => ( - onChange && onChange(value, state)} - > - {children} - -); - -TagSelectOption.isTagSelectOption = true; - -type TagSelectOptionElement = React.ReactElement; - -export interface TagSelectProps { - onChange?: (value: (string | number)[]) => void; - expandable?: boolean; - value?: (string | number)[]; - defaultValue?: (string | number)[]; - style?: React.CSSProperties; - hideCheckAll?: boolean; - actionsText?: { - expandText?: React.ReactNode; - collapseText?: React.ReactNode; - selectAllText?: React.ReactNode; - }; - className?: string; - Option?: TagSelectOptionProps; - children?: TagSelectOptionElement | TagSelectOptionElement[]; -} -const TagSelect: FC & { - Option: typeof TagSelectOption; -} = (props) => { - const { styles } = useStyles(); - const { children, hideCheckAll = false, className, style, expandable, actionsText = {} } = props; - const [expand, setExpand] = useState(false); - - const [value, setValue] = useMergedState<(string | number)[]>(props.defaultValue || [], { - value: props.value, - defaultValue: props.defaultValue, - onChange: props.onChange, - }); - - const isTagSelectOption = (node: TagSelectOptionElement) => - node && - node.type && - (node.type.isTagSelectOption || node.type.displayName === 'TagSelectOption'); - const getAllTags = () => { - const childrenArray = React.Children.toArray(children) as TagSelectOptionElement[]; - const checkedTags = childrenArray - .filter((child) => isTagSelectOption(child)) - .map((child) => child.props.value); - return checkedTags || []; - }; - const onSelectAll = (checked: boolean) => { - let checkedTags: (string | number)[] = []; - if (checked) { - checkedTags = getAllTags(); - } - setValue(checkedTags); - }; - const handleTagChange = (tag: string | number, checked: boolean) => { - const checkedTags: (string | number)[] = [...(value || [])]; - const index = checkedTags.indexOf(tag); - if (checked && index === -1) { - checkedTags.push(tag); - } else if (!checked && index > -1) { - checkedTags.splice(index, 1); - } - setValue(checkedTags); - }; - const checkedAll = getAllTags().length === value?.length; - const { expandText = '展开', collapseText = '收起', selectAllText = '全部' } = actionsText; - const cls = classNames(styles.tagSelect, className, { - [styles.hasExpandTag]: expandable, - [styles.expanded]: expand, - }); - return ( -
- {hideCheckAll ? null : ( - - {selectAllText} - - )} - {children && - React.Children.map(children, (child: TagSelectOptionElement) => { - if (isTagSelectOption(child)) { - return React.cloneElement(child, { - key: `tag-select-${child.props.value}`, - value: child.props.value, - checked: value && value.indexOf(child.props.value) > -1, - onChange: handleTagChange, - }); - } - return child; - })} - {expandable && ( - { - setExpand(!expand); - }} - > - {expand ? ( - <> - {collapseText} - - ) : ( - <> - {expandText} - - - )} - - )} -
- ); -}; -TagSelect.Option = TagSelectOption; -export default TagSelect; diff --git a/src/pages/list/search/articles/data.d.ts b/src/pages/list/search/articles/data.d.ts deleted file mode 100644 index 0a4597c..0000000 --- a/src/pages/list/search/articles/data.d.ts +++ /dev/null @@ -1,32 +0,0 @@ -export type Member = { - avatar: string; - name: string; - id: string; -}; - -export interface Params { - count: number; -} -export interface ListItemDataType { - id: string; - owner: string; - title: string; - avatar: string; - cover: string; - status: 'normal' | 'exception' | 'active' | 'success'; - percent: number; - logo: string; - href: string; - body?: any; - updatedAt: number; - createdAt: number; - subDescription: string; - description: string; - activeUser: number; - newUser: number; - star: number; - like: number; - message: number; - content: string; - members: Member[]; -} diff --git a/src/pages/list/search/articles/index.tsx b/src/pages/list/search/articles/index.tsx deleted file mode 100644 index 17f90fd..0000000 --- a/src/pages/list/search/articles/index.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import { LikeOutlined, LoadingOutlined, MessageOutlined, StarOutlined } from '@ant-design/icons'; -import { useRequest } from '@umijs/max'; -import { Button, Card, Col, Form, List, Row, Select, Tag } from 'antd'; -import { DefaultOptionType } from 'antd/es/select'; -import type { FC } from 'react'; -import React, { useMemo } from 'react'; -import { categoryOptions } from '../../mock'; -import ArticleListContent from './components/ArticleListContent'; -import StandardFormRow from './components/StandardFormRow'; -import TagSelect from './components/TagSelect'; -import type { ListItemDataType } from './data.d'; -import { queryFakeList } from './service'; -import useStyles from './style.style'; - -const FormItem = Form.Item; - -const pageSize = 5; - -const Articles: FC = () => { - const [form] = Form.useForm(); - - const { styles } = useStyles(); - - const { data, reload, loading, loadMore, loadingMore } = useRequest( - () => { - return queryFakeList({ - count: pageSize, - }); - }, - { - loadMore: true, - }, - ); - - const list = data?.list || []; - - const setOwner = () => { - form.setFieldsValue({ - owner: ['wzj'], - }); - }; - - const owners = [ - { - id: 'wzj', - name: '我自己', - }, - { - id: 'wjh', - name: '吴家豪', - }, - { - id: 'zxx', - name: '周星星', - }, - { - id: 'zly', - name: '赵丽颖', - }, - { - id: 'ym', - name: '姚明', - }, - ]; - - const IconText: React.FC<{ - type: string; - text: React.ReactNode; - }> = ({ type, text }) => { - switch (type) { - case 'star-o': - return ( - - - {text} - - ); - case 'like-o': - return ( - - - {text} - - ); - case 'message': - return ( - - - {text} - - ); - default: - return null; - } - }; - - const formItemLayout = { - wrapperCol: { - xs: { span: 24 }, - sm: { span: 24 }, - md: { span: 12 }, - }, - }; - - const loadMoreDom = list.length > 0 && ( -
- -
- ); - - const ownerOptions = useMemo( - () => - owners.map((item) => ({ - label: item.name, - value: item.id, - })), - [owners], - ); - - return ( - <> - -
- - - - {categoryOptions.map((category) => ( - - {category.label} - - ))} - - - - - - - - - - - - - - - - ; - } - - return defaultRender(item); - }, - }, - { - title: '操作', - dataIndex: 'option', - valueType: 'option', - render: (_, record) => [ - { - handleUpdateModalVisible(true); - setCurrentRow(record); - }} - > - 配置 - , - - 订阅警报 - , - ], - }, - ]; - - return ( - - - headerTitle="查询表格" - actionRef={actionRef} - rowKey="key" - search={{ - labelWidth: 120, - }} - toolBarRender={() => [ - , - ]} - request={rule} - columns={columns} - rowSelection={{ - onChange: (_, selectedRows) => { - setSelectedRows(selectedRows); - }, - }} - /> - {selectedRowsState?.length > 0 && ( - - 已选择{' '} - - {selectedRowsState.length} - {' '} - 项    - - 服务调用次数总计 {selectedRowsState.reduce((pre, item) => pre + item.callNo!, 0)} 万 - - - } - > - - - - )} - { - const success = await handleAdd(value as TableListItem); - if (success) { - handleModalVisible(false); - if (actionRef.current) { - actionRef.current.reload(); - } - } - }} - > - - - - { - const success = await handleUpdate(value, currentRow); - - if (success) { - handleUpdateModalVisible(false); - setCurrentRow(undefined); - - if (actionRef.current) { - actionRef.current.reload(); - } - } - }} - onCancel={() => { - handleUpdateModalVisible(false); - setCurrentRow(undefined); - }} - updateModalVisible={updateModalVisible} - values={currentRow || {}} - /> - - { - setCurrentRow(undefined); - setShowDetail(false); - }} - closable={false} - > - {currentRow?.name && ( - - column={2} - title={currentRow?.name} - request={async () => ({ - data: currentRow || {}, - })} - params={{ - id: currentRow?.name, - }} - columns={columns as ProDescriptionsItemProps[]} - /> - )} - - - ); -}; - -export default TableList; diff --git a/src/pages/list/table-list/service.ts b/src/pages/list/table-list/service.ts deleted file mode 100644 index d4a6095..0000000 --- a/src/pages/list/table-list/service.ts +++ /dev/null @@ -1,56 +0,0 @@ -// @ts-ignore -/* eslint-disable */ -import { request } from '@umijs/max'; -import { TableListItem } from './data'; - -/** 获取规则列表 GET /api/rule */ -export async function rule( - params: { - // query - /** 当前的页码 */ - current?: number; - /** 页面的容量 */ - pageSize?: number; - }, - options?: { [key: string]: any }, -) { - return request<{ - data: TableListItem[]; - /** 列表的内容总数 */ - total?: number; - success?: boolean; - }>('/api/rule', { - method: 'GET', - params: { - ...params, - }, - ...(options || {}), - }); -} - -/** 新建规则 PUT /api/rule */ -export async function updateRule(data: { [key: string]: any }, options?: { [key: string]: any }) { - return request('/api/rule', { - data, - method: 'PUT', - ...(options || {}), - }); -} - -/** 新建规则 POST /api/rule */ -export async function addRule(data: { [key: string]: any }, options?: { [key: string]: any }) { - return request('/api/rule', { - data, - method: 'POST', - ...(options || {}), - }); -} - -/** 删除规则 DELETE /api/rule */ -export async function removeRule(data: { key: number[] }, options?: { [key: string]: any }) { - return request>('/api/rule', { - data, - method: 'DELETE', - ...(options || {}), - }); -} diff --git a/src/pages/list/basic-list/_mock.ts b/src/pages/story/_mock.ts similarity index 100% rename from src/pages/list/basic-list/_mock.ts rename to src/pages/story/_mock.ts diff --git a/src/pages/list/basic-list/components/AddSubTimeLineItemModal.tsx b/src/pages/story/components/AddSubTimeLineItemModal.tsx similarity index 100% rename from src/pages/list/basic-list/components/AddSubTimeLineItemModal.tsx rename to src/pages/story/components/AddSubTimeLineItemModal.tsx diff --git a/src/pages/list/basic-list/components/AddTimeLineItemModal.tsx b/src/pages/story/components/AddTimeLineItemModal.tsx similarity index 90% rename from src/pages/list/basic-list/components/AddTimeLineItemModal.tsx rename to src/pages/story/components/AddTimeLineItemModal.tsx index 2c2e183..108a56e 100644 --- a/src/pages/list/basic-list/components/AddTimeLineItemModal.tsx +++ b/src/pages/story/components/AddTimeLineItemModal.tsx @@ -1,6 +1,6 @@ // src/pages/list/basic-list/components/AddTimeLineItemModal.tsx -import { addStoryItem } from '@/pages/list/basic-list/service'; import chinaRegion, { code2Location } from '@/commonConstant/chinaRegion'; +import { addStoryItem } from '@/pages/story/service'; import { UploadOutlined } from '@ant-design/icons'; import { useRequest } from '@umijs/max'; import { Button, Cascader, DatePicker, Form, Input, message, Modal, Upload } from 'antd'; @@ -13,24 +13,26 @@ interface ModalProps { visible: boolean; onCancel: () => void; onOk: () => void; - lineId: string | number | undefined; + storyId: string | number | undefined; initialValues?: any; - isRoot: boolean; // 是否根节点 + storyItemId?: string; // 是否根节点 + option: 'add' | 'edit' | 'addSubItem' | 'editSubItem'; } const AddTimeLineItemModal: React.FC = ({ visible, onCancel, onOk, - lineId, + storyId, initialValues, - isRoot = true, + storyItemId, + option, }) => { const [form] = Form.useForm(); const [fileList, setFileList] = useState([]); const [imageList, setImageList] = useState(initialValues?.images || []); useEffect(() => { - if (initialValues) { + if (initialValues && option === 'edit') { form.setFieldsValue({ title: initialValues.title, storyItemTime: initialValues.date ? moment(initialValues.date) : undefined, @@ -40,7 +42,7 @@ const AddTimeLineItemModal: React.FC = ({ images: initialValues.images?.map((url) => ({ url })) || [], }); } - }, [initialValues]); + }, [initialValues, option]); const { run: submitItem, loading } = useRequest((newItem) => addStoryItem(newItem), { manual: true, onSuccess: (data) => { @@ -64,9 +66,9 @@ const AddTimeLineItemModal: React.FC = ({ ...values, id: initialValues?.id || Date.now(), storyItemTime: dayjs(values.date).format('YYYY-MM-DDTHH:mm:ss'), - masterItemId: lineId, + masterItemId: initialValues.masterItemId, subItems: initialValues?.subItems || [], - isRoot: isRoot ? 1 : 0, + storyInstanceId: storyId, location, }; delete newItem.cover; @@ -151,7 +153,7 @@ const AddTimeLineItemModal: React.FC = ({ ]} > - {!isRoot && {lineId}} + {['editSubItem', 'addSubItem'].includes( option) && {storyItemId}} @@ -185,7 +187,9 @@ const AddTimeLineItemModal: React.FC = ({ {/* 新增:时刻图库 */} - + diff --git a/src/pages/list/basic-list/components/OperationModal.tsx b/src/pages/story/components/OperationModal.tsx similarity index 100% rename from src/pages/list/basic-list/components/OperationModal.tsx rename to src/pages/story/components/OperationModal.tsx diff --git a/src/pages/list/basic-list/components/SubTimeLineItemModal.tsx b/src/pages/story/components/SubTimeLineItemModal.tsx similarity index 100% rename from src/pages/list/basic-list/components/SubTimeLineItemModal.tsx rename to src/pages/story/components/SubTimeLineItemModal.tsx diff --git a/src/pages/story/components/TimelineItem/TimelineItem.tsx b/src/pages/story/components/TimelineItem/TimelineItem.tsx new file mode 100644 index 0000000..cdc9dd6 --- /dev/null +++ b/src/pages/story/components/TimelineItem/TimelineItem.tsx @@ -0,0 +1,182 @@ +import {DeleteOutlined, DownOutlined, EditOutlined, PlusOutlined, UpOutlined} from '@ant-design/icons'; +import {useIntl, useRequest} from '@umijs/max'; +import { Button, Card, Popconfirm, message } from 'antd'; +import React, {useState} from 'react'; +import {queryStoryItemImages, removeStoryItem} from '../../service'; +import useStyles from './index.style'; +import {StoryItem} from "@/pages/story/data"; +import TimelineImage from "@/components/TimelineImage"; +import TimelineItemDrawer from '../TimelineItemDrawer'; + +const TimelineItem: React.FC<{ + item: StoryItem; + handleOption: (item: StoryItem, option: 'add' | 'edit' | 'addSubItem' | 'editSubItem') => void; + refresh: () => void; +}> = ({ item, handleOption, refresh }) => { + const { styles } = useStyles(); + const intl = useIntl(); + const [expanded, setExpanded] = useState(false); + const [showActions, setShowActions] = useState(false); + const [subItemsExpanded, setSubItemsExpanded] = useState(false); + const [openDetail, setOpenDetail] = useState(false) + const { data: imagesList } = useRequest( + async () => { + return await queryStoryItemImages(item.instanceId); + }, + ); + const handleDelete = async () => { + try { + if (!item.instanceId) return; + const response = await removeStoryItem(item.instanceId); + if (response.code === 200) { + message.success(intl.formatMessage({ id: 'story.deleteSuccess' })); + refresh(); + } else { + message.error(intl.formatMessage({ id: 'story.deleteFailed' })); + } + } catch (error) { + message.error(intl.formatMessage({ id: 'story.deleteFailed' })); + } + }; + const toggleDescription = () => { + setExpanded(!expanded); + }; + + const toggleSubItems = () => { + setSubItemsExpanded(!subItemsExpanded); + }; + + const displayedDescription = expanded + ? item.description + : item.description?.substring(0, 100) + (item.description && item.description.length > 100 ? '...' : ''); + + return ( + setShowActions(true)} + onMouseLeave={() => setShowActions(false)} + onClick={() => setOpenDetail(true)} + extra={ +
+ {showActions && ( + <> +
+ } + // onClick={() => onDetail(item)} + hoverable + > +
+
+ {item.storyItemTime} {item.location ? `创建于${item.location}` : ''} +
+
+ {displayedDescription} + {item.description && item.description.length > 100 && ( + + )} +
+ {imagesList && imagesList.length > 0 && ( + <> +
+ {imagesList.map((imageInstanceId, index) => ( + + ))} +
+ + )} + {item.subItems && item.subItems.length > 0 && ( +
+
{ + e.stopPropagation(); + toggleSubItems(); + }} + > + + {intl.formatMessage({ id: 'story.subItems' })} ({item.subItems.length}) + + {subItemsExpanded ? : } +
+ {subItemsExpanded && ( +
+ {item.subItems.map((subItem) => ( +
+
+ {item.storyItemTime} {item.location ? `创建于${item.location}` : ''} +
+
+ {subItem.description} +
+
+ ))} +
+ )} +
+ )} +
+ +
+ ); +}; + +export default TimelineItem; diff --git a/src/pages/list/basic-list/components/TimelineItem/index.css b/src/pages/story/components/TimelineItem/index.css similarity index 100% rename from src/pages/list/basic-list/components/TimelineItem/index.css rename to src/pages/story/components/TimelineItem/index.css diff --git a/src/pages/story/components/TimelineItem/index.style.ts b/src/pages/story/components/TimelineItem/index.style.ts new file mode 100644 index 0000000..769d289 --- /dev/null +++ b/src/pages/story/components/TimelineItem/index.style.ts @@ -0,0 +1,90 @@ +import { createStyles } from 'antd-style'; + +const useStyles = createStyles(({ token }) => { + return { + timelineItem: { + marginBottom: '20px', + boxShadow: token.boxShadow, + borderRadius: token.borderRadius, + transition: 'all 0.3s', + cursor: 'pointer', + '&:hover': { + boxShadow: token.boxShadowSecondary, + }, + }, + actions: { + display: 'flex', + gap: '8px', + alignItems: 'center', + height: '24px', + width: '120px', + }, + content: { + padding: '10px 0', + }, + cover: { + width: '100%', + height: '200px', + overflow: 'hidden', + borderRadius: '4px', + marginBottom: '15px', + img: { + width: '100%', + height: '100%', + objectFit: 'cover', + }, + }, + date: { + fontSize: '14px', + color: token.colorTextSecondary, + marginBottom: '10px', + fontWeight: 'bold', + }, + description: { + fontSize: '16px', + lineHeight: '1.6', + color: token.colorText, + marginBottom: '15px', + '.ant-btn-link': { + padding: '0 4px', + }, + }, + subItems: { + borderTop: `1px dashed ${token.colorBorder}`, + paddingTop: '15px', + marginTop: '15px', + }, + subItemsHeader: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + cursor: 'pointer', + fontWeight: 'bold', + color: token.colorTextHeading, + marginBottom: '10px', + padding: '5px 0', + }, + subItemsList: { + maxHeight: '300px', + overflowY: 'auto', + }, + subItem: { + display: 'flex', + marginBottom: '10px', + '&:last-child': { + marginBottom: 0, + }, + }, + subItemDate: { + fontWeight: 'bold', + minWidth: '100px', + color: token.colorTextSecondary, + }, + subItemContent: { + flex: 1, + color: token.colorText, + }, + }; +}); + +export default useStyles; diff --git a/src/pages/list/basic-list/components/TimelineItemDrawer.tsx b/src/pages/story/components/TimelineItemDrawer.tsx similarity index 90% rename from src/pages/list/basic-list/components/TimelineItemDrawer.tsx rename to src/pages/story/components/TimelineItemDrawer.tsx index e309151..e81227d 100644 --- a/src/pages/list/basic-list/components/TimelineItemDrawer.tsx +++ b/src/pages/story/components/TimelineItemDrawer.tsx @@ -1,8 +1,8 @@ import TimelineImage from '@/components/TimelineImage'; -import AddTimeLineItemModal from '@/pages/list/basic-list/components/AddTimeLineItemModal'; -import SubTimeLineItemModal from '@/pages/list/basic-list/components/SubTimeLineItemModal'; -import { StoryItem } from '@/pages/list/basic-list/data'; -import { queryStoryItemImages } from '@/pages/list/basic-list/service'; +import AddTimeLineItemModal from '@/pages/story/components/AddTimeLineItemModal'; +import SubTimeLineItemModal from '@/pages/story/components/SubTimeLineItemModal'; +import { StoryItem } from '@/pages/story/data'; +import { queryStoryItemImages } from '@/pages/story/service'; import { EditOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { useRequest } from '@umijs/max'; import { Button, Drawer, Space } from 'antd'; @@ -30,7 +30,7 @@ const TimelineItemDrawer = (props: Props) => { useEffect(() => { if (open) { console.log(storyItem); - run(storyItem.itemId); + run(storyItem.instanceId); } }, [open]); const closeDrawer = () => { @@ -120,8 +120,8 @@ const TimelineItemDrawer = (props: Props) => { visible={openAddSubItemModal} onOk={handleAddSubItem} onCancel={() => setOpenAddSubItemModal(false)} - lineId={storyItem.itemId} - isRoot={false} + lineId={storyItem.storyInstanceId} + storyItemId={storyItem.instanceId} /> {/* 编辑主时间点模态框 */} diff --git a/src/pages/list/basic-list/data.d.ts b/src/pages/story/data.d.ts similarity index 96% rename from src/pages/list/basic-list/data.d.ts rename to src/pages/story/data.d.ts index 03114e6..a253f82 100644 --- a/src/pages/list/basic-list/data.d.ts +++ b/src/pages/story/data.d.ts @@ -50,14 +50,14 @@ export interface ErrorResponse extends BaseResponse { } export interface StoryItem { id?: number; - itemId?: string; + instanceId: string; title: string; + storyInstanceId: string; masterItemId?: string; description: string; storyItemTime: string; // YYYY-MM-DD createTime: string; // YYYY-MM-DD updateTime: string; // YYYY-MM-DD - time?: string; // HH:mm (可选) location?: string; coverInstanceId?: string; // 封面图 images?: string[]; // 多张图片 diff --git a/src/pages/story/detail.tsx b/src/pages/story/detail.tsx new file mode 100644 index 0000000..f05b10c --- /dev/null +++ b/src/pages/story/detail.tsx @@ -0,0 +1,129 @@ +import AddTimeLineItemModal from '@/pages/story/components/AddTimeLineItemModal'; +import TimelineItem from '@/pages/story/components/TimelineItem/TimelineItem'; +import { StoryItem } from '@/pages/story/data'; +import { countStoryItem, queryStoryDetail, queryStoryItem } from '@/pages/story/service'; +import { PageContainer } from '@ant-design/pro-components'; +import { history, useIntl, useRequest } from '@umijs/max'; +import { FloatButton, Spin, Timeline } from 'antd'; +import React, { useEffect, useRef, useState } from 'react'; +import { useParams } from 'react-router'; +import './index.css'; +import useStyles from './style.style'; + +interface TimelineItemProps { + children: React.ReactNode; // 修正:使用 ReactNode 更通用 + // label: string +} + +const Index = () => { + const { id: lineId } = useParams<{ id: string }>(); + const { styles } = useStyles(); + const intl = useIntl(); + const containerRef = useRef(null); + const [items, setItems] = useState([]); + + const [loading, setLoading] = useState(false); + const [hasMoreNew, setHasMoreNew] = useState(true); + const [hasMoreOld, setHasMoreOld] = useState(true); + const [openAddItemModal, setOpenAddItemModal] = useState(false); + const [currentItem, setCurrentItem] = useState(); + const [currentOption, setCurrentOption] = useState<'add' | 'edit' | 'addSubItem' | 'editSubItem'>(); + + const { data: storyItemList, run } = useRequest( + () => { + return queryStoryItem(lineId ?? ''); + }, + { + manual: true, + }, + ); + const { + data: detail, + run: queryDetail, + loading: queryDetailLoading, + } = useRequest(() => { + return queryStoryDetail(lineId ?? ''); + }); + const { data: count, run: queryCount } = useRequest(() => { + return countStoryItem(lineId ?? ''); + }); + // 初始化加载数据 + useEffect(() => { + run(); + }, [lineId]); + useEffect(() => { + if (!storyItemList?.length) return; + console.log(storyItemList); + let timelineItems = storyItemList; //handleStoryItemList(storyItemList); + // 转换为 Timeline 组件需要的格式 + const formattedItems = timelineItems.map((item: StoryItem) => ({ + children: ( + { + setCurrentItem(item); + setCurrentOption(option) + setOpenAddItemModal(true); + }} + refresh={() => { + run(); + queryCount(); + queryDetail(); + }} + /> + ), + })); + + setItems(formattedItems); + }, [storyItemList]); + + return ( + history.push('/story')} + title={queryDetailLoading ? '加载中' : `${detail?.title} ${count ? `共${count}个时刻` : ``}`} + > +
+ {!hasMoreOld &&
没有更老的内容
} + {loading && } + + {loading && } + {!hasMoreNew &&
没有更新的内容
} +
+ { + setCurrentOption('add'); + setCurrentItem({ + coverInstanceId: "", + createTime: "", + description: "", + id: 0, + images: [], + instanceId: "", + isRoot: 0, + location: "", + masterItemId: "", + storyInstanceId: "", + storyItemTime: "", + subItems: [], + title: "", + updateTime: "" + }); + setOpenAddItemModal(true); + }} /> + { + setOpenAddItemModal(false); + }} + onOk={() => { + setOpenAddItemModal(false); + run(); + }} + storyId={lineId} + /> +
+ ); +}; + +export default Index; diff --git a/src/pages/list/basic-list/index.css b/src/pages/story/index.css similarity index 100% rename from src/pages/list/basic-list/index.css rename to src/pages/story/index.css diff --git a/src/pages/list/basic-list/index.tsx b/src/pages/story/index.tsx similarity index 100% rename from src/pages/list/basic-list/index.tsx rename to src/pages/story/index.tsx diff --git a/src/pages/list/basic-list/service.ts b/src/pages/story/service.ts similarity index 72% rename from src/pages/list/basic-list/service.ts rename to src/pages/story/service.ts index 6efb162..7fb1091 100644 --- a/src/pages/list/basic-list/service.ts +++ b/src/pages/story/service.ts @@ -1,5 +1,6 @@ import { request } from '@umijs/max'; import { StoryItem, StoryType } from './data.d'; +import {CommonResponse} from "@/types/common"; type ParamsType = { count?: number; @@ -40,7 +41,11 @@ export async function updateStory(params: ParamsType): Promise<{ data: { list: S }, }); } - +export async function queryStoryDetail(itemId: string): Promise<{ data: StoryType }> { + return request(`/story/${itemId}`, { + method: 'GET', + }); +} export async function addStoryItem(params: FormData): Promise { return request(`/story/item`, { method: 'POST', @@ -50,26 +55,36 @@ export async function addStoryItem(params: FormData): Promise { }); } -export async function queryStoryItem(masterItemId: string): Promise<{ data: StoryItem[] }> { +export async function queryStoryItem(storyInstanceId: string): Promise<{ data: StoryItem[] }> { return request(`/story/item/list`, { method: 'GET', params: { - masterItemId, + storyInstanceId, }, }); } -export async function queryStoryItemDetail(itemId: string): Promise<{ data: StoryItem[] }> { +export async function queryStoryItemDetail(itemId: string): Promise<{ data: StoryItem }> { return request(`/story/item/${itemId}`, { method: 'GET', }); } +export async function countStoryItem(storyInstanceId: string): Promise<{ data: StoryItem }> { + return request(`/story/item/count/${storyInstanceId}`, { + method: 'GET', + }); +} export async function queryStoryItemImages(itemId: string): Promise<{ data: string[] }> { return request(`/story/item/images/${itemId}`, { method: 'GET', }); } +export async function removeStoryItem(itemId: string): Promise> { + return request(`/story/item/${itemId}`, { + method: 'DELETE', + }); +} export async function fetchImage(imageInstanceId: string): Promise { return request(`/file/image/${imageInstanceId}`, { diff --git a/src/pages/list/basic-list/style.style.ts b/src/pages/story/style.style.ts similarity index 72% rename from src/pages/list/basic-list/style.style.ts rename to src/pages/story/style.style.ts index ba119e1..4031f77 100644 --- a/src/pages/list/basic-list/style.style.ts +++ b/src/pages/story/style.style.ts @@ -148,6 +148,80 @@ const useStyles = createStyles(({ token }) => { gap: 8, marginBottom: 16, }, + storyList: { + padding: '24px', + }, + toolbar: { + marginBottom: '24px', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, + storyDetail: { + padding: '12px', + }, + pageHeader: { + padding: 0, + marginBottom: '24px', + }, + card: { + maxWidth: '800px', + margin: '0 auto', + }, + date: { + fontSize: '16px', + color: token.colorTextSecondary, + marginBottom: '20px', + fontWeight: 'bold', + }, + cover: { + width: '100%', + height: '300px', + overflow: 'hidden', + borderRadius: '4px', + marginBottom: '20px', + img: { + width: '100%', + height: '100%', + objectFit: 'cover', + }, + }, + description: { + fontSize: '16px', + lineHeight: '1.8', + color: token.colorText, + whiteSpace: 'pre-wrap', + }, + subItems: { + marginTop: '20px', + }, + subItem: { + display: 'flex', + marginBottom: '15px', + padding: '10px', + backgroundColor: token.colorFillAlter, + borderRadius: '4px', + '&:last-child': { + marginBottom: 0, + }, + }, + subItemDate: { + fontWeight: 'bold', + minWidth: '120px', + color: token.colorTextSecondary, + }, + subItemContent: { + flex: 1, + color: token.colorText, + }, + timelineItem: { + cursor: 'pointer', + transition: 'all 0.3s', + '&:hover': { + transform: 'translateY(-4px)', + boxShadow: token.boxShadow, + }, + }, }; }); diff --git a/src/pages/list/basic-list/utils/utils.style.ts b/src/pages/story/utils/utils.style.ts similarity index 100% rename from src/pages/list/basic-list/utils/utils.style.ts rename to src/pages/story/utils/utils.style.ts