feact: 消息通知模块
parent
c807eb7123
commit
ebb977a170
@ -0,0 +1,5 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 7.89008C8.6925 7.89008 8.4375 7.63508 8.4375 7.32758V4.83008C8.4375 4.52258 8.6925 4.26758 9 4.26758C9.3075 4.26758 9.5625 4.52258 9.5625 4.83008V7.32758C9.5625 7.64258 9.3075 7.89008 9 7.89008Z" fill="#333333"/>
|
||||
<path d="M9.01502 15.2622C7.08002 15.2622 5.15252 14.9547 3.31502 14.3397C2.63252 14.1147 2.11502 13.6272 1.89002 13.0122C1.66502 12.3972 1.74002 11.6922 2.10752 11.0772L3.06002 9.48719C3.27002 9.13469 3.45752 8.47469 3.45752 8.06219V6.48719C3.45752 3.41969 5.94752 0.929688 9.01502 0.929688C12.0825 0.929688 14.5725 3.41969 14.5725 6.48719V8.06219C14.5725 8.46719 14.76 9.13469 14.97 9.48719L15.9225 11.0772C16.275 11.6622 16.335 12.3597 16.1025 12.9972C15.87 13.6347 15.36 14.1222 14.715 14.3397C12.8775 14.9622 10.95 15.2622 9.01502 15.2622ZM9.01502 2.06219C6.57002 2.06219 4.58252 4.04969 4.58252 6.49469V8.06969C4.58252 8.67719 4.34252 9.55469 4.02752 10.0722L3.07502 11.6697C2.88002 11.9922 2.83502 12.3372 2.94752 12.6372C3.06002 12.9372 3.31502 13.1622 3.67502 13.2822C7.12502 14.4297 10.92 14.4297 14.37 13.2822C14.6925 13.1772 14.94 12.9372 15.0525 12.6222C15.1725 12.3072 15.135 11.9622 14.9625 11.6697L14.01 10.0797C13.695 9.56219 13.455 8.68469 13.455 8.07719V6.50219C13.4475 4.04969 11.46 2.06219 9.01502 2.06219Z" fill="#333333"/>
|
||||
<path d="M8.99994 17.1752C8.19744 17.1752 7.40994 16.8452 6.83994 16.2752C6.26994 15.7052 5.93994 14.9177 5.93994 14.1152H7.06494C7.06494 14.6252 7.27494 15.1202 7.63494 15.4802C7.99494 15.8402 8.48994 16.0502 8.99994 16.0502C10.0649 16.0502 10.9349 15.1802 10.9349 14.1152H12.0599C12.0599 15.8027 10.6874 17.1752 8.99994 17.1752Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,38 @@
|
||||
.notice {
|
||||
display: flex;
|
||||
// float: right;
|
||||
// padding-top: 10px;
|
||||
// margin-right: 28px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.topNotice {
|
||||
padding: 4px 8px 0px 8px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.bottomBar {
|
||||
text-align: center;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
border-radius: 0 0 2px 2px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.dobtn {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
padding: 12px 0;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.doLeftBtn {
|
||||
@extend .dobtn;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 0 12px;
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Tabs, Badge, Spin, List, Avatar, Tag, Popover, notification } from "antd";
|
||||
import { BellOutlined, LoadingOutlined } from "@ant-design/icons";
|
||||
// svg 转组件
|
||||
import { ReactComponent as NoticeSvg } from '../../../public/icons/notice.svg';
|
||||
import type { NotificationPlacement } from 'antd/es/notification/interface';
|
||||
|
||||
// import { getallnotices, getUidNotices } from "@/api/caravan/User";
|
||||
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
enum EventStatus {
|
||||
todo = "rgba(255,255,255,1)",
|
||||
urgent = "#f5222d",
|
||||
doing = "#faad14",
|
||||
processing = "#1890ff"
|
||||
}
|
||||
|
||||
const antIcon = <LoadingOutlined style={{ fontSize: 24 }} spin />;
|
||||
|
||||
const { TabPane} = Tabs;
|
||||
const Context = React.createContext({ name: 'Default' });
|
||||
|
||||
const Notice: React.FC = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [noticeList, setNoticeList] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const noticeCount = 10;
|
||||
const intervalHandle: any = useRef(null);
|
||||
const [noticeNum, setNoticeNum] = useState(0);
|
||||
|
||||
const noticeListFilter = <T extends any>(type: T) => {
|
||||
return noticeList.filter(notice => notice.type === type) as any[];
|
||||
};
|
||||
|
||||
const getNotice = async () => {
|
||||
//查看我是否有新消息 无论如何
|
||||
// setLoading(true);
|
||||
//无论如何,清掉定时器,使用了 ref,肯定能拿到指针。
|
||||
//并且启动一个定时器,定时刷新我的消息
|
||||
clearTimeout(intervalHandle.current);
|
||||
intervalHandle.current = setTimeout(() => {
|
||||
setNoticeNum(Math.ceil(Math.random() * 1000));
|
||||
}, 1000 * 60 * 60);
|
||||
// getUidNotices()
|
||||
// .then(result => {
|
||||
// setLoading(false);
|
||||
// if (result.status && result.data && Array.isArray(result.data.data)) {
|
||||
// setNoticeList(result.data.data);
|
||||
// }
|
||||
// })
|
||||
// .catch(() => {
|
||||
// setLoading(false);
|
||||
// });
|
||||
// getallnotices();
|
||||
};
|
||||
// const [api, contextHolder] = notification.useNotification();
|
||||
|
||||
// const openNotification = (placement: NotificationPlacement) => {
|
||||
// api.info({
|
||||
// message: `Notification ${placement}`,
|
||||
// description: <Context.Consumer>{({ name }) => `Hello, ${name}!`}</Context.Consumer>,
|
||||
// placement,
|
||||
// });
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
getNotice();
|
||||
}, [noticeNum]);
|
||||
// const ws = new WebSocket("ws://localhost:3000/");
|
||||
|
||||
// useEffect(() => {
|
||||
|
||||
// console.log(ws);
|
||||
// // ws.onopen = function () {
|
||||
// // setInterval(function () {
|
||||
// // ws.send("客户端消息");
|
||||
// // }, 2000);
|
||||
// // };
|
||||
|
||||
// ws.onmessage = function (e) {
|
||||
// console.log(e.data);
|
||||
// openNotification('topRight')
|
||||
// }
|
||||
// }, []);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimeout(intervalHandle.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onClear = () => {
|
||||
// ws.send("客户端消息");
|
||||
};
|
||||
const onViewMore = () => {};
|
||||
const tabsData = [
|
||||
{
|
||||
title: 'Ant Design Title 1',
|
||||
time: '刚刚'
|
||||
},
|
||||
{
|
||||
title: 'Ant Design Title 2Ant Design Title 2Ant Design Title 2Ant Design Title 2Ant Design Title 2Ant Design Title 2Ant Design Title 2',
|
||||
time: '刚刚'
|
||||
},
|
||||
{
|
||||
title: 'Ant Design Title 3',
|
||||
time: '刚刚'
|
||||
},
|
||||
{
|
||||
title: 'Ant Design Title 4',
|
||||
time: '刚刚'
|
||||
},
|
||||
];
|
||||
const tabs = (
|
||||
<div className={styles.topNotice}>
|
||||
<Spin tip="Loading..." indicator={antIcon} spinning={loading}>
|
||||
<List
|
||||
dataSource={tabsData}
|
||||
renderItem={item => (
|
||||
<List.Item>
|
||||
<List.Item.Meta
|
||||
description={
|
||||
<div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<span style={{ fontSize: "14px", color: "#333" }}>系统消息</span>
|
||||
<Tag bordered={false} color="error" style={{ marginInlineEnd: 0 }}>
|
||||
紧急
|
||||
</Tag>
|
||||
</div>
|
||||
<div style={{ fontSize: "14px", color: "#333" }}>
|
||||
{item.title}
|
||||
</div>
|
||||
<div style={{ color: "#999" }}>{item.time}</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div style={{display: 'flex'}}>
|
||||
{/* {contextHolder} */}
|
||||
<Popover
|
||||
content={tabs}
|
||||
overlayClassName="bg-2"
|
||||
placement="bottomRight"
|
||||
trigger={['click']}
|
||||
open={visible}
|
||||
onOpenChange={v => setVisible(v)}
|
||||
arrow={{pointAtCenter: true}}
|
||||
overlayStyle={{
|
||||
width: 396,
|
||||
}}
|
||||
>
|
||||
{/* <Tooltip
|
||||
title={'消息通知'}
|
||||
> */}
|
||||
<Badge count={noticeList.length || noticeCount} overflowCount={999} offset={[8, -2]}>
|
||||
<span>
|
||||
{/* <BellOutlined style={{ fontSize: "18px" }} /> */}
|
||||
<NoticeSvg style={{ fontSize: "18px" }} />
|
||||
</span>
|
||||
</Badge>
|
||||
{/* </Tooltip> */}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notice;
|
@ -0,0 +1,17 @@
|
||||
export const notice_list: { [key: string]: string } = {
|
||||
'notice.notice.table.list.id': 'ID',
|
||||
'notice.notice.table.list.type': '消息类型',
|
||||
'notice.notice.table.list.priority': '优先级',
|
||||
'notice.notice.table.list.content': '内容',
|
||||
'notice.notice.table.list.time': '时间',
|
||||
'notice.notice.table.list.isEnable': '是否启用',
|
||||
'notice.notice.table.list.remark': '备注',
|
||||
'notice.notice.table.list.createTime': '创建时间',
|
||||
'notice.notice.table.list.updateTime': '更新时间',
|
||||
'notice.notice.table.rule.required.name': '姓名为必填项',
|
||||
'notice.notice.table.rule.required.code': '项目代码为必填项',
|
||||
'notice.notice.table.rule.required.info': '项目简介为必填项',
|
||||
'notice.notice.table.list.add': '新建联系人',
|
||||
'notice.notice.table.list.update': '编辑联系人',
|
||||
'notice.notice.table.list.editor': '编辑',
|
||||
};
|
@ -0,0 +1,210 @@
|
||||
import IsBatchDelete from '@/components/BatchOperation/isBatchDelete';
|
||||
import TableActionCard from '@/components/TableActionCard';
|
||||
import IsDelete from '@/components/TableActionCard/isDelete';
|
||||
import {
|
||||
deleteProjectDeleteProject,
|
||||
deleteProjectDeleteProjectByIds,
|
||||
postProjectGetProjectList,
|
||||
} from '@/services/project/Project';
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||
import { PageContainer, ProTable } from '@ant-design/pro-components';
|
||||
import { FormattedMessage, useAccess, useIntl } from '@umijs/max';
|
||||
import { Tag, message } from 'antd';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { proTableCommonOptions, proTablePaginationOptions } from '../../../config/defaultTable';
|
||||
const ProjectList: React.FC = () => {
|
||||
/**
|
||||
* @en-US Pop-up window of new window
|
||||
* @zh-CN 新建窗口的弹窗
|
||||
* */
|
||||
const [createModalOpen, setCreateModalOpen] = useState<boolean>(false);
|
||||
/**
|
||||
* @en-US The pop-up window of the distribution update window
|
||||
* @zh-CN 分布更新窗口的弹窗
|
||||
* */
|
||||
const [updateModalOpen, setUpdateModalOpen] = useState<boolean>(false);
|
||||
const [showDetail, setShowDetail] = useState<boolean>(false);
|
||||
/**
|
||||
* @en-US International configuration
|
||||
* @zh-CN 国际化配置
|
||||
* */
|
||||
const intl = useIntl();
|
||||
const actionRef = useRef<ActionType>();
|
||||
// 动态设置每页数量
|
||||
const [currentPageSize, setCurrentPageSize] = useState<number>(10);
|
||||
const [currentRow, setCurrentRow] = useState<API.Project>();
|
||||
const [selectedRowsState, setSelectedRows] = useState<API.Project[]>([]);
|
||||
|
||||
const handleUpdateModal = () => {
|
||||
if (updateModalOpen) {
|
||||
setUpdateModalOpen(false);
|
||||
setCurrentRow(undefined);
|
||||
} else {
|
||||
setUpdateModalOpen(true);
|
||||
}
|
||||
};
|
||||
const handleCreateModal = () => {
|
||||
if (createModalOpen) {
|
||||
setCreateModalOpen(false);
|
||||
setCurrentRow(undefined);
|
||||
} else {
|
||||
setCreateModalOpen(true);
|
||||
}
|
||||
};
|
||||
const handleColumnDrawer = () => {
|
||||
if (showDetail) {
|
||||
setShowDetail(false);
|
||||
setCurrentRow(undefined);
|
||||
} else {
|
||||
setShowDetail(true);
|
||||
}
|
||||
};
|
||||
const handleDestroy = async (selectedRow: API.Project) => {
|
||||
deleteProjectDeleteProject({ id: selectedRow.id })
|
||||
.then(() => {
|
||||
message.success(intl.formatMessage({ id: 'common.success', defaultMessage: '$$$' }));
|
||||
actionRef.current?.reload();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error(intl.formatMessage({ id: 'common.failure', defaultMessage: '$$$' }));
|
||||
});
|
||||
};
|
||||
|
||||
const columns: ProColumns<API.Project>[] = [
|
||||
{
|
||||
title: <FormattedMessage id="notice.notice.table.list.type" defaultMessage="$$$" />,
|
||||
dataIndex: 'name',
|
||||
hideInSearch: true,
|
||||
key: 'fixedName',
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: <FormattedMessage id="notice.notice.table.list.priority" defaultMessage="$$$" />,
|
||||
dataIndex: 'code',
|
||||
// hideInSearch: true,
|
||||
render: (dom) => {
|
||||
return (
|
||||
<Tag bordered={false} color={dom === 'XM00002'? "success": "error"}>
|
||||
{dom}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: <FormattedMessage id="notice.notice.table.list.content" defaultMessage="$$$" />,
|
||||
dataIndex: 'info',
|
||||
hideInSearch: true,
|
||||
},
|
||||
|
||||
{
|
||||
title: <FormattedMessage id="notice.notice.table.list.time" defaultMessage="$$$" />,
|
||||
dataIndex: 'createTime',
|
||||
hideInSearch: false,
|
||||
valueType: 'dateRange',
|
||||
},
|
||||
|
||||
{
|
||||
title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="Operating" />,
|
||||
dataIndex: 'option',
|
||||
valueType: 'option',
|
||||
fixed: 'right',
|
||||
render: (_, record) => [
|
||||
<TableActionCard
|
||||
key="TableActionCardRef"
|
||||
renderActions={[
|
||||
{
|
||||
key: 'destroy',
|
||||
renderDom: (
|
||||
<IsDelete
|
||||
deleteApi={() => {
|
||||
handleDestroy(record).then(() => {});
|
||||
}}
|
||||
></IsDelete>
|
||||
),
|
||||
},
|
||||
]}
|
||||
></TableActionCard>,
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<API.Project>
|
||||
headerTitle={intl.formatMessage({
|
||||
id: 'pages.searchTable.title',
|
||||
defaultMessage: '$$$',
|
||||
})}
|
||||
scroll={{ y: proTableCommonOptions.commscrollY, x: proTableCommonOptions.commscrollX }}
|
||||
options={{ fullScreen: true, setting: true, density: true, reload: true }}
|
||||
actionRef={actionRef}
|
||||
rowKey="key"
|
||||
search={{
|
||||
labelWidth: proTableCommonOptions.searchLabelWidth,
|
||||
}}
|
||||
onDataSourceChange={() => {}}
|
||||
pagination={{
|
||||
...proTablePaginationOptions,
|
||||
pageSize: currentPageSize,
|
||||
onChange: (page, pageSize) => setCurrentPageSize(pageSize),
|
||||
}}
|
||||
columnsState={{
|
||||
persistenceKey: 'project_list',
|
||||
persistenceType: 'localStorage',
|
||||
}}
|
||||
tableAlertOptionRender={() => {
|
||||
return (
|
||||
<>
|
||||
{selectedRowsState?.length > 0 && (
|
||||
<IsBatchDelete
|
||||
deleteApi={() => {
|
||||
// TODO 需要;联调删除接口
|
||||
deleteProjectDeleteProjectByIds({
|
||||
ids: selectedRowsState.map((v: API.Project) => {
|
||||
return v.id as number;
|
||||
}),
|
||||
}).then(() => {
|
||||
actionRef.current?.reloadAndRest?.();
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
request={async (params = {}, sort) => {
|
||||
const { current, ...rest } = params;
|
||||
const reqParams = {
|
||||
page: current,
|
||||
desc: false,
|
||||
orderKey: '',
|
||||
...rest,
|
||||
};
|
||||
if (sort && Object.keys(sort).length) {
|
||||
reqParams.orderKey = Object.keys(sort)[0];
|
||||
let sort_select = sort[reqParams.orderKey];
|
||||
reqParams.desc = sort_select === 'descend';
|
||||
}
|
||||
let resp = await postProjectGetProjectList({ ...reqParams });
|
||||
return {
|
||||
data: resp.data.list.map((v: API.Project) => {
|
||||
return { ...v, key: v.id };
|
||||
}),
|
||||
success: resp.success,
|
||||
total: resp.data.total,
|
||||
current: resp.data.page,
|
||||
pageSize: resp.data.pageSize,
|
||||
};
|
||||
}}
|
||||
columns={columns}
|
||||
// rowSelection={{
|
||||
// onChange: (_, selectedRows) => {
|
||||
// setSelectedRows(selectedRows);
|
||||
// },
|
||||
// }}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectList;
|
Loading…
Reference in New Issue