整理框架和新建基础模块

This commit is contained in:
linxd
2025-06-17 18:32:33 +08:00
parent 08424ecdb8
commit ebc4dfe1f9
51 changed files with 2200 additions and 2850 deletions

View File

@ -54,6 +54,48 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe616;</span>
<div class="name">帮助中心</div>
<div class="code-name">&amp;#xe616;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe62a;</span>
<div class="name">管理员</div>
<div class="code-name">&amp;#xe62a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe646;</span>
<div class="name">关于我们</div>
<div class="code-name">&amp;#xe646;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe61c;</span>
<div class="name">通知中心</div>
<div class="code-name">&amp;#xe61c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe76a;</span>
<div class="name">政策法规</div>
<div class="code-name">&amp;#xe76a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe657;</span>
<div class="name">Tab_下载中心</div>
<div class="code-name">&amp;#xe657;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe604;</span>
<div class="name">首页</div>
<div class="code-name">&amp;#xe604;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe60c;</span>
<div class="name">地址</div>
@ -90,10 +132,10 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1750055522436') format('woff2'),
url('iconfont.woff?t=1750055522436') format('woff'),
url('iconfont.ttf?t=1750055522436') format('truetype'),
url('iconfont.svg?t=1750055522436#iconfont') format('svg');
src: url('iconfont.woff2?t=1750146970222') format('woff2'),
url('iconfont.woff?t=1750146970222') format('woff'),
url('iconfont.ttf?t=1750146970222') format('truetype'),
url('iconfont.svg?t=1750146970222#iconfont') format('svg');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@ -119,6 +161,69 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-bangzhuzhongxin"></span>
<div class="name">
帮助中心
</div>
<div class="code-name">.icon-bangzhuzhongxin
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-guanliyuan"></span>
<div class="name">
管理员
</div>
<div class="code-name">.icon-guanliyuan
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-guanyuwomen"></span>
<div class="name">
关于我们
</div>
<div class="code-name">.icon-guanyuwomen
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-tongzhizhongxin"></span>
<div class="name">
通知中心
</div>
<div class="code-name">.icon-tongzhizhongxin
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-zhengcefagui"></span>
<div class="name">
政策法规
</div>
<div class="code-name">.icon-zhengcefagui
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-Tab_xiazaizhongxin"></span>
<div class="name">
Tab_下载中心
</div>
<div class="code-name">.icon-Tab_xiazaizhongxin
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shouye"></span>
<div class="name">
首页
</div>
<div class="code-name">.icon-shouye
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-dizhi"></span>
<div class="name">
@ -173,6 +278,62 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-bangzhuzhongxin"></use>
</svg>
<div class="name">帮助中心</div>
<div class="code-name">#icon-bangzhuzhongxin</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-guanliyuan"></use>
</svg>
<div class="name">管理员</div>
<div class="code-name">#icon-guanliyuan</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-guanyuwomen"></use>
</svg>
<div class="name">关于我们</div>
<div class="code-name">#icon-guanyuwomen</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tongzhizhongxin"></use>
</svg>
<div class="name">通知中心</div>
<div class="code-name">#icon-tongzhizhongxin</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zhengcefagui"></use>
</svg>
<div class="name">政策法规</div>
<div class="code-name">#icon-zhengcefagui</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-Tab_xiazaizhongxin"></use>
</svg>
<div class="name">Tab_下载中心</div>
<div class="code-name">#icon-Tab_xiazaizhongxin</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shouye"></use>
</svg>
<div class="name">首页</div>
<div class="code-name">#icon-shouye</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-dizhi"></use>

View File

@ -1,9 +1,9 @@
@font-face {
font-family: "iconfont"; /* Project id 4950533 */
src: url('iconfont.woff2?t=1750055522436') format('woff2'),
url('iconfont.woff?t=1750055522436') format('woff'),
url('iconfont.ttf?t=1750055522436') format('truetype'),
url('iconfont.svg?t=1750055522436#iconfont') format('svg');
src: url('iconfont.woff2?t=1750146970222') format('woff2'),
url('iconfont.woff?t=1750146970222') format('woff'),
url('iconfont.ttf?t=1750146970222') format('truetype'),
url('iconfont.svg?t=1750146970222#iconfont') format('svg');
}
.iconfont {
@ -14,6 +14,34 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-bangzhuzhongxin:before {
content: "\e616";
}
.icon-guanliyuan:before {
content: "\e62a";
}
.icon-guanyuwomen:before {
content: "\e646";
}
.icon-tongzhizhongxin:before {
content: "\e61c";
}
.icon-zhengcefagui:before {
content: "\e76a";
}
.icon-Tab_xiazaizhongxin:before {
content: "\e657";
}
.icon-shouye:before {
content: "\e604";
}
.icon-dizhi:before {
content: "\e60c";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,55 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "1376042",
"name": "帮助中心",
"font_class": "bangzhuzhongxin",
"unicode": "e616",
"unicode_decimal": 58902
},
{
"icon_id": "3590935",
"name": "管理员",
"font_class": "guanliyuan",
"unicode": "e62a",
"unicode_decimal": 58922
},
{
"icon_id": "7148747",
"name": "关于我们",
"font_class": "guanyuwomen",
"unicode": "e646",
"unicode_decimal": 58950
},
{
"icon_id": "8831225",
"name": "通知中心",
"font_class": "tongzhizhongxin",
"unicode": "e61c",
"unicode_decimal": 58908
},
{
"icon_id": "28872723",
"name": "政策法规",
"font_class": "zhengcefagui",
"unicode": "e76a",
"unicode_decimal": 59242
},
{
"icon_id": "31518606",
"name": "Tab_下载中心",
"font_class": "Tab_xiazaizhongxin",
"unicode": "e657",
"unicode_decimal": 58967
},
{
"icon_id": "44539919",
"name": "首页",
"font_class": "shouye",
"unicode": "e604",
"unicode_decimal": 58884
},
{
"icon_id": "5093357",
"name": "地址",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,61 +1,30 @@
@import '~antd/es/style/themes/default.less';
@import './baseStyle.less';
// 页面公用样式
@import './utils/componentStyle.less';
html,
body,
#root {
height: 100%;
}
.layout-content {
box-sizing: border-box;
height: calc(100% - 60px);
overflow: auto;
padding: 15px 0;
background: rgba(@gray, 0.3);
.layout-content-main {
width: @width;
margin: 0 auto;
// background: #fff;
// padding: 15px;
}
}
.colorWeak {
filter: invert(80%);
}
.ant-layout {
min-height: 100vh;
// // 去除滚动条样式
// height: 100vh;
// overflow-y: hidden;
}
body {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@media (max-width: @screen-xs) {
.ant-table {
width: 100%;
overflow-x: auto;
&-thead > tr,
&-tbody > tr {
> th,
> td {
white-space: pre;
> span {
display: block;
}
}
}
}
}
// 兼容IE11
@media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) {
body .ant-design-pro > .ant-layout {
min-height: 100vh;
.ant-layout-header {
background: #fff;
color: @main-text-color;
padding: 0;
}
.ant-btn {
&.ant-btn-link {
padding: 4px 8px;
}
}

View File

@ -1,16 +1,18 @@
import React from 'react';
//导入logo图片
import LogoImg from '@/assets/img/logo.png';
// 引入样式文件
import './Header.less';
//导入菜单组件
import HeaderMenu from './HeaderMenu';
const Header: React.FC = (props) => {
import Language from './Language';
import User from './User';
const HeaderComponent: React.FC = (props) => {
return (
<div className="header">
<img className='logo' src={LogoImg} alt="logo" />
<HeaderMenu />
<div className="headerComponent">
<img className="logo" src={LogoImg} alt="logo" />
<div className="flex">
<Language />
<User />
</div>
</div>
);
};
export default Header;
export default HeaderComponent;

View File

@ -1,13 +1,33 @@
import React from 'react';
import Header from './Header';
// import Header from './Header';
import { Layout, Breadcrumb } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
//导入logo图片
import HeaderComponent from './Header';
import SiderMenu from './SiderMenu';
import './layout.less';
const LayoutIndex: React.FC = (props) => {
const { children } = props;
return (
<>
<Header />
<div className="layout-content">
<div className='layout-content-main'>{children}</div>
</div>
<Layout>
<Header className="header">
<HeaderComponent />
</Header>
<Layout>
<Sider width={200} theme="light">
<SiderMenu />
</Sider>
<Layout className="layout-content">
<Breadcrumb style={{ margin: '10px 0' }}>
<Breadcrumb.Item>Home</Breadcrumb.Item>
<Breadcrumb.Item>List</Breadcrumb.Item>
<Breadcrumb.Item>App</Breadcrumb.Item>
</Breadcrumb>
<Content>{children}</Content>
</Layout>
</Layout>
</Layout>
</>
);
};

View File

@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import { Menu } from 'antd';
import Language from './Language';
import { useIntl, Link, useHistory } from 'umi';
import User from './User';
import IconFont from '@/components/IconFont/IconFont';
interface IMenuItem {
label: string;
key: string;
path: string;
icon: string;
}
// 引入样式文件 useIntl().formatMessage({ id: 'menu.首页' }),
const items: IMenuItem[] = [
@ -14,35 +14,47 @@ const items: IMenuItem[] = [
label: 'menu.首页',
key: 'index',
path: '/index',
icon: 'icon-shouye',
},
{
label: 'menu.公告公示',
key: 'announce',
path: '/announce',
label: 'menu.管理员管理',
key: 'userManage',
path: '/userManage',
icon: 'icon-guanliyuan',
},
{
label: 'menu.政策法规',
key: 'policy',
path: '/policy',
label: 'menu.下载中心管理',
key: 'downloadManage',
path: '/downloadManage',
icon: 'icon-Tab_xiazaizhongxin',
},
{
label: 'menu.通知中心',
key: 'notice',
path: '/notice',
label: 'menu.通知中心管理',
key: 'noticeManage',
path: '/noticeManage',
icon: 'icon-tongzhizhongxin',
},
{
label: 'menu.下载中心',
key: 'download',
path: '/download',
label: 'menu.政策法规管理',
key: 'policyManage',
path: '/policyManage',
icon: 'icon-zhengcefagui',
},
{
label: 'menu.关于我们',
key: 'about',
path: '/about',
label: 'menu.关于我们管理',
key: 'aboutManage',
path: '/aboutManage',
icon: 'icon-guanyuwomen',
},
{
label: 'menu.帮助中心管理',
key: 'helpManage',
path: '/helpManage',
icon: 'icon-bangzhuzhongxin',
},
];
const HeaderMenu: React.FC = (props) => {
const SiderMenu: React.FC = (props) => {
//当前激活菜单
const [current, setCurrent] = useState('index');
const intl = useIntl();
@ -53,7 +65,7 @@ const HeaderMenu: React.FC = (props) => {
const menu = items.find((item) => item.path === path);
if (menu) {
setCurrent(menu.key);
}else{
} else {
// 如果跳转的详情页面获取根级激活菜单
const rootActiveMenu = path.split('/')[1];
setCurrent(rootActiveMenu);
@ -61,16 +73,15 @@ const HeaderMenu: React.FC = (props) => {
}, [history.location.pathname]);
return (
<div className="header-menu">
<Menu selectedKeys={[current]} mode="horizontal">
<Menu selectedKeys={[current]} mode="vertical">
{items.map((item: IMenuItem) => (
<Menu.Item key={item.key}>
<IconFont type={item.icon} />
<Link to={item.path}>{intl.formatMessage({ id: item.label })}</Link>
</Menu.Item>
))}
</Menu>
<Language />
<User />
</div>
);
};
export default HeaderMenu;
export default SiderMenu;

View File

@ -1,27 +1,26 @@
@import '../baseStyle.less';
.header {
.headerComponent {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
width: @width;
margin: 0 auto;
padding: 0 15px;
.logo {
height: 45px;
}
}
.header-menu {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
.ant-menu-horizontal {
line-height: 58px;
border: none;
}
}
.user {
margin-left: 15px;
}
.flex {
display: flex;
align-items: center;
}
.layout-content {
background: rgb(245,246,250);
padding: 0 15px;
height: calc(100vh - 64px);
overflow: auto;
}

View File

@ -1,10 +1,11 @@
export default {
'menu.首页': 'Home',
'menu.公告公示': 'Public Announcement',
'menu.政策法规': 'Policies and Regulations',
'menu.通知中心': 'Notifications',
'menu.下载中心': 'Download Center',
'menu.关于我们': 'About Us',
'menu.管理员管理': 'Admin Management',
'menu.下载中心管理': 'Download Center Management',
'menu.通知中心管理': 'Notice Center Management',
'menu.政策法规管理': 'Policy Management',
'menu.关于我们管理': 'About Us Management',
'menu.帮助中心管理': 'Help Center Management',
// About page
'about.title': 'About Us',

View File

@ -1,10 +1,11 @@
export default {
'menu.首页': '首页',
'menu.公告公示': '公告公示',
'menu.政策法规': '政策法规',
'menu.通知中心': '通知中心',
'menu.下载中心': '下载中心',
'menu.关于我们': '关于我们',
'menu.管理员管理': '管理员管理',
'menu.下载中心管理': '下载中心管理',
'menu.通知中心管理': '通知中心管理',
'menu.政策法规管理': '政策法规管理',
'menu.关于我们管理': '关于我们管理',
'menu.帮助中心管理': '帮助中心管理',
// 关于我们页面
'about.title': '关于我们',

View File

@ -1,3 +0,0 @@
export default {
'menu.下载中心': '下载中心',
};

View File

@ -3,8 +3,6 @@
"short_name": "Ant Design Pro",
"display": "standalone",
"start_url": "./?utm_source=homescreen",
"theme_color": "#002140",
"background_color": "#001529",
"icons": [
{
"src": "icons/icon-192x192.png",

View File

@ -1,78 +0,0 @@
@import '~antd/es/style/themes/default.less';
.aboutContainer {
padding: 24px;
background-color: #fff;
}
.aboutContent {
max-width: 1000px;
margin: 0 auto;
}
.aboutTitle {
text-align: center;
margin-bottom: 40px;
}
.aboutSection {
margin-bottom: 48px;
}
.sectionTitle {
position: relative;
margin-bottom: 24px;
padding-left: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 4px;
height: 20px;
width: 4px;
background-color: #1890ff;
}
}
.sectionContent {
color: #333;
line-height: 2;
font-size: 15px;
}
.imageWrapper {
margin: 24px 0;
text-align: center;
}
.aboutImage {
max-width: 100%;
height: auto;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.companyInfo {
background-color: #f5f5f5;
padding: 24px;
border-radius: 4px;
margin-top: 24px;
}
.infoItem {
margin-bottom: 12px;
display: flex;
.label {
font-weight: 500;
margin-right: 8px;
min-width: 80px;
}
}
.aboutFooter {
text-align: center;
margin-top: 48px;
color: rgba(0, 0, 0, 0.45);
}

View File

@ -1,193 +0,0 @@
import React from 'react';
import { Typography, Divider, Row, Col } from 'antd';
import { useIntl } from 'umi';
import styles from './about.less';
const { Title, Paragraph } = Typography;
const AboutPage: React.FC = () => {
const intl = useIntl();
return (
<div className={styles.aboutContainer}>
<div className={styles.aboutContent}>
<div className={styles.aboutTitle}>
<Title level={2}>{intl.formatMessage({ id: 'about.title' })}</Title>
</div>
<div className={styles.aboutSection}>
<Title level={3} className={styles.sectionTitle}>
{intl.formatMessage({ id: 'about.company.title' })}
</Title>
<div className={styles.sectionContent}>
<Paragraph>
</Paragraph>
<div className={styles.imageWrapper}>
<img
src="/home_bg.png"
alt="公司总部"
className={styles.aboutImage}
/>
</div>
<Paragraph>
&ldquo;&rdquo;
</Paragraph>
</div>
</div>
<Divider />
<div className={styles.aboutSection}>
<Title level={3} className={styles.sectionTitle}>
{intl.formatMessage({ id: 'about.advantages.title' })}
</Title>
<div className={styles.sectionContent}>
<Row gutter={[24, 24]}>
<Col xs={24} md={8}>
<div>
<Title level={4}></Title>
<Paragraph>
线
</Paragraph>
</div>
</Col>
<Col xs={24} md={8}>
<div>
<Title level={4}></Title>
<Paragraph>
</Paragraph>
</div>
</Col>
<Col xs={24} md={8}>
<div>
<Title level={4}></Title>
<Paragraph>
</Paragraph>
</div>
</Col>
</Row>
<div className={styles.imageWrapper}>
<img
src="/home_bg.png"
alt="平台功能展示"
className={styles.aboutImage}
/>
</div>
<Row gutter={[24, 24]}>
<Col xs={24} md={8}>
<div>
<Title level={4}></Title>
<Paragraph>
</Paragraph>
</div>
</Col>
<Col xs={24} md={8}>
<div>
<Title level={4}></Title>
<Paragraph>
</Paragraph>
</div>
</Col>
<Col xs={24} md={8}>
<div>
<Title level={4}></Title>
<Paragraph>
</Paragraph>
</div>
</Col>
</Row>
</div>
</div>
<Divider />
<div className={styles.aboutSection}>
<Title level={3} className={styles.sectionTitle}>
{intl.formatMessage({ id: 'about.history.title' })}
</Title>
<div className={styles.sectionContent}>
<Paragraph>
<strong>2018</strong> -
</Paragraph>
<Paragraph>
<strong>2019</strong> - 线
</Paragraph>
<Paragraph>
<strong>2020</strong> -
</Paragraph>
<Paragraph>
<strong>2021</strong> -
</Paragraph>
<Paragraph>
<strong>2022</strong> - ERP系统的深度集成
</Paragraph>
<Paragraph>
<strong>2023</strong> -
</Paragraph>
<Paragraph>
<strong>2024</strong> - 3.0
</Paragraph>
</div>
</div>
<Divider />
<div className={styles.aboutSection}>
<Title level={3} className={styles.sectionTitle}>
{intl.formatMessage({ id: 'about.contact.title' })}
</Title>
<div className={styles.sectionContent}>
<div className={styles.companyInfo}>
<div className={styles.infoItem}>
<span className={styles.label}>{intl.formatMessage({ id: 'about.address' })}</span>
<span>666</span>
</div>
<div className={styles.infoItem}>
<span className={styles.label}>{intl.formatMessage({ id: 'about.phone' })}</span>
<span>021-65966666</span>
</div>
<div className={styles.infoItem}>
<span className={styles.label}>{intl.formatMessage({ id: 'about.hotline' })}</span>
<span>400-888-9999</span>
</div>
<div className={styles.infoItem}>
<span className={styles.label}>{intl.formatMessage({ id: 'about.email' })}</span>
<span>service@ebidding.cosco.com</span>
</div>
<div className={styles.infoItem}>
<span className={styles.label}>{intl.formatMessage({ id: 'about.worktime' })}</span>
<span> 9:00-17:30</span>
</div>
</div>
</div>
</div>
<div className={styles.aboutFooter}>
<Paragraph>{intl.formatMessage({ id: 'about.copyright' })}</Paragraph>
</div>
</div>
</div>
);
};
export default AboutPage;

View File

@ -0,0 +1,11 @@
.about-manage-container {
padding: 24px;
.about-manage-content {
background: #fff;
padding: 24px;
min-height: 500px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import { useIntl } from 'umi';
import './aboutManage.less';
const AboutManage: React.FC = () => {
const intl = useIntl();
return (
<div className="about-manage-container">
<h1>{intl.formatMessage({ id: 'menu.关于我们管理' })}</h1>
<div className="about-manage-content">
{/* 关于我们管理内容 */}
</div>
</div>
);
};
export default AboutManage;

View File

@ -1,207 +0,0 @@
.announcePage {
// padding: 20px;
// background-color: #f5f5f5;
}
.searchSection {
background-color: #fff;
padding: 20px;
// border-radius: 4px;
margin-bottom: 20px;
// box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.searchRow {
margin-bottom: 20px;
display: flex;
align-items: center;
}
.searchLabel {
font-size: 14px;
font-weight: 500;
color: #333;
margin-right: 20px;
min-width: 70px;
}
.filterRow {
margin-bottom: 15px;
display: flex;
align-items: flex-start;
}
.filterLabel {
font-size: 14px;
font-weight: 500;
color: #333;
margin-right: 20px;
min-width: 70px;
padding-top: 5px;
}
.filterOptions {
flex: 1;
}
.locationSelector {
display: flex;
flex-direction: column;
}
.provinceSelector {
margin-top: 10px;
width: 100%;
.provinceRow {
margin-bottom: 8px;
display: flex;
flex-wrap: wrap;
}
}
/* 覆盖antd样式 */
:global {
.ant-radio-button-wrapper {
margin-right: 5px;
margin-bottom: 8px;
border-radius: 0;
height: 32px;
line-height: 30px;
min-width: 60px;
text-align: center;
padding: 0 10px;
}
.ant-radio-button-wrapper:first-child {
border-radius: 0;
}
.ant-radio-button-wrapper:last-child {
border-radius: 0;
}
.ant-radio-button-wrapper-checked {
background-color: rgb(0, 79, 142);
border-color: rgb(0, 79, 142);
color: #fff;
&:hover {
background-color: rgb(0, 79, 142);
border-color: rgb(0, 79, 142);
color: #fff;
}
}
.ant-btn-primary {
background-color: rgb(0, 79, 142);
border-color: rgb(0, 79, 142);
border-radius: 0;
&:hover, &:focus {
background-color: rgba(0, 79, 142, 0.8);
border-color: rgba(0, 79, 142, 0.8);
}
}
.ant-select-selector {
border-radius: 0 !important;
}
.ant-input {
border-radius: 0;
}
.ant-input-affix-wrapper {
border-radius: 0;
}
.ant-list-pagination {
margin-top: 20px;
text-align: center;
}
.ant-pagination-item {
border-radius: 0;
&-active {
border-color: rgb(0, 79, 142);
a {
color: rgb(0, 79, 142);
}
}
}
.ant-pagination-item-link {
border-radius: 0;
}
.ant-pagination-options {
.ant-select-selector {
border-radius: 0;
}
}
.ant-tag {
margin-right: 0;
border-radius: 2px;
}
}
.announceList {
background-color: #fff;
padding: 10px;
}
.listItem {
padding: 16px 10px;
border-bottom: 1px solid #f0f0f0;
&:hover {
background-color: #f9f9f9;
}
}
.itemContent {
width: 100%;
}
.itemHeader {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.typeTag {
margin-right: 10px;
min-width: 90px;
text-align: center;
}
.itemTitle {
font-size: 16px;
font-weight: 500;
color: #333;
flex: 1;
&:hover {
color: rgb(0, 79, 142);
cursor: pointer;
}
}
.itemFooter {
display: flex;
justify-content: space-between;
color: #666;
font-size: 14px;
}
.company {
flex: 1;
}
.date {
min-width: 180px;
text-align: right;
}

View File

@ -1,369 +0,0 @@
import React, { useState } from 'react';
import { Input, Button, Select, Radio, DatePicker, List, Tag } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import { history } from 'umi';
import styles from './announce.less';
const { Option } = Select;
const { RangePicker } = DatePicker;
// 模拟公告数据
const mockAnnounceData = [
{
id: '1',
type: '招标采购公告',
title: '中远海运集团2023年度办公设备采购项目招标公告',
company: '中远海运集团有限公司',
publishDate: '2023-06-15',
},
{
id: '2',
type: '中标结果公示',
title: '中远海运物流有限公司信息系统建设项目中标结果公示',
company: '中远海运物流有限公司',
publishDate: '2023-06-10',
},
{
id: '3',
type: '采购需求公示',
title: '中远海运集装箱运输有限公司集装箱采购需求公示',
company: '中远海运集装箱运输有限公司',
publishDate: '2023-06-08',
},
{
id: '4',
type: '变更公告',
title: '关于"中远海运能源运输股份有限公司船舶维修项目"的变更公告',
company: '中远海运能源运输股份有限公司',
publishDate: '2023-06-05',
},
{
id: '5',
type: '招标采购公告',
title: '中远海运重工有限公司船舶设备采购项目招标公告',
company: '中远海运重工有限公司',
publishDate: '2023-06-01',
},
];
const AnnouncePage: React.FC = () => {
// 状态管理
const [searchKeyword, setSearchKeyword] = useState<string>('');
const [announceType, setAnnounceType] = useState<string>('全部');
const [projectUnit, setProjectUnit] = useState<string>('全选');
const [projectLocation, setProjectLocation] = useState<string>('中国');
const [publishTime, setPublishTime] = useState<string>('不限');
const [projectType, setProjectType] = useState<string>('全部');
const [currentProvince, setCurrentProvince] = useState<string>('全部');
// 分页状态
const [current, setCurrent] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [total, setTotal] = useState(100);
// 公告类型选项
const announceTypeOptions = [
{ label: '全部', value: '全部' },
{ label: '采购需求公示', value: '采购需求公示' },
{ label: '招标采购公告', value: '招标采购公告' },
{ label: '非招标采购公告', value: '非招标采购公告' },
{ label: '资格预审公告', value: '资格预审公告' },
{ label: '招募公告', value: '招募公告' },
{ label: '变更公告', value: '变更公告' },
{ label: '中标(中选)候选人公示', value: '中标(中选)候选人公示' },
{ label: '中标(中选)结果公示', value: '中标(中选)结果公示' },
{ label: '采购失败(流标)公告', value: '采购失败(流标)公告' },
];
// 省份选项 - 按照图片中的顺序排列
const provinceOptions = [
{ label: '全部', value: '全部' },
{ label: '北京', value: '北京' },
{ label: '天津', value: '天津' },
{ label: '河北', value: '河北' },
{ label: '山西', value: '山西' },
{ label: '内蒙古', value: '内蒙古' },
{ label: '辽宁', value: '辽宁' },
{ label: '吉林', value: '吉林' },
{ label: '黑龙江', value: '黑龙江' },
{ label: '上海', value: '上海' },
{ label: '江苏', value: '江苏' },
{ label: '浙江', value: '浙江' },
{ label: '安徽', value: '安徽' },
{ label: '福建', value: '福建' },
{ label: '江西', value: '江西' },
{ label: '山东', value: '山东' },
{ label: '河南', value: '河南' },
{ label: '湖北', value: '湖北' },
{ label: '湖南', value: '湖南' },
{ label: '广东', value: '广东' },
{ label: '广西', value: '广西' },
{ label: '海南', value: '海南' },
{ label: '重庆', value: '重庆' },
{ label: '四川', value: '四川' },
{ label: '贵州', value: '贵州' },
{ label: '云南', value: '云南' },
{ label: '西藏', value: '西藏' },
{ label: '陕西', value: '陕西' },
{ label: '甘肃', value: '甘肃' },
{ label: '青海', value: '青海' },
{ label: '宁夏', value: '宁夏' },
{ label: '新疆', value: '新疆' },
{ label: '香港', value: '香港' },
{ label: '澳门', value: '澳门' },
{ label: '台湾', value: '台湾' },
];
// 第一行省份
const firstRowProvinces = ['全部', '北京', '天津', '河北', '山西', '内蒙古', '辽宁', '吉林', '黑龙江', '上海', '江苏', '浙江', '安徽', '福建', '江西'];
// 第二行省份
const secondRowProvinces = ['山东', '河南', '湖北', '湖南', '广东', '广西', '海南', '重庆', '四川', '贵州', '云南', '西藏', '陕西'];
// 第三行省份
const thirdRowProvinces = ['甘肃', '青海', '宁夏', '新疆', '香港', '澳门', '台湾'];
// 发布时间选项
const publishTimeOptions = [
{ label: '不限', value: '不限' },
{ label: '今天', value: '今天' },
{ label: '最近三天', value: '最近三天' },
{ label: '最近一周', value: '最近一周' },
{ label: '最近一月', value: '最近一月' },
{ label: '自定义', value: '自定义' },
];
// 项目类型选项
const projectTypeOptions = [
{ label: '全部', value: '全部' },
{ label: '货物', value: '货物' },
{ label: '工程', value: '工程' },
{ label: '服务', value: '服务' },
{ label: '其他', value: '其他' },
];
// 处理搜索
const handleSearch = () => {
console.log('搜索条件:', {
searchKeyword,
announceType,
projectUnit,
projectLocation,
publishTime,
projectType,
});
// 这里添加搜索逻辑
};
// 处理分页变化
const handlePageChange = (page: number, pageSizeValue?: number) => {
setCurrent(page);
if (pageSizeValue) setPageSize(pageSizeValue);
};
// 获取公告类型对应的颜色
const getTypeColor = (type: string) => {
switch (type) {
case '招标采购公告':
return 'blue';
case '中标结果公示':
return 'green';
case '采购需求公示':
return 'orange';
case '变更公告':
return 'red';
default:
return 'default';
}
};
// 处理点击公告标题
const handleAnnounceClick = (id: string) => {
console.log('点击了公告ID:', id);
history.push({
pathname: '/announce/announceInfo',
query: {
id,
},
});
};
return (
<div className={styles.announcePage}>
<div className={styles.searchSection}>
{/* 公告搜索 */}
<div className={styles.searchRow}>
<div className={styles.searchLabel}></div>
<Input
placeholder="请输入公告关键词"
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
style={{ width: '80%', maxWidth: 1000 }}
suffix={
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
</Button>
}
/>
</div>
{/* 公告类型 */}
<div className={styles.filterRow}>
<div className={styles.filterLabel}></div>
<div className={styles.filterOptions}>
<Radio.Group
value={announceType}
onChange={(e) => setAnnounceType(e.target.value)}
buttonStyle="solid"
>
{announceTypeOptions.map((option) => (
<Radio.Button key={option.value} value={option.value}>
{option.label}
</Radio.Button>
))}
</Radio.Group>
</div>
</div>
{/* 项目所属单位 */}
<div className={styles.filterRow}>
<div className={styles.filterLabel}></div>
<Select
style={{ width: 300 }}
value={projectUnit}
onChange={(value) => setProjectUnit(value)}
>
<Option value="全选"></Option>
<Option value="中远海运集团"></Option>
<Option value="中远海运物流"></Option>
<Option value="中远海运集装箱"></Option>
</Select>
</div>
{/* 项目所在地 */}
<div className={styles.filterRow}>
<div className={styles.filterLabel}></div>
<div className={styles.locationSelector}>
<Select
style={{ width: 150, marginRight: 10 }}
value={projectLocation}
onChange={(value) => setProjectLocation(value)}
>
<Option value="中国"></Option>
<Option value="国外"></Option>
</Select>
<div className={styles.provinceSelector}>
<Radio.Group
value={currentProvince}
onChange={(e) => setCurrentProvince(e.target.value)}
buttonStyle="solid"
>
<div className={styles.provinceRow}>
{firstRowProvinces.map((province) => (
<Radio.Button key={province} value={province}>
{province}
</Radio.Button>
))}
</div>
<div className={styles.provinceRow}>
{secondRowProvinces.map((province) => (
<Radio.Button key={province} value={province}>
{province}
</Radio.Button>
))}
</div>
<div className={styles.provinceRow}>
{thirdRowProvinces.map((province) => (
<Radio.Button key={province} value={province}>
{province}
</Radio.Button>
))}
</div>
</Radio.Group>
</div>
</div>
</div>
{/* 发布时间 */}
<div className={styles.filterRow}>
<div className={styles.filterLabel}></div>
<div className={styles.filterOptions}>
<Radio.Group
value={publishTime}
onChange={(e) => setPublishTime(e.target.value)}
buttonStyle="solid"
>
{publishTimeOptions.map((option) => (
<Radio.Button key={option.value} value={option.value}>
{option.label}
</Radio.Button>
))}
</Radio.Group>
{publishTime === '自定义' && (
<RangePicker style={{ marginLeft: 10 }} />
)}
</div>
</div>
{/* 项目类型 */}
<div className={styles.filterRow}>
<div className={styles.filterLabel}></div>
<div className={styles.filterOptions}>
<Radio.Group
value={projectType}
onChange={(e) => setProjectType(e.target.value)}
buttonStyle="solid"
>
{projectTypeOptions.map((option) => (
<Radio.Button key={option.value} value={option.value}>
{option.label}
</Radio.Button>
))}
</Radio.Group>
</div>
</div>
</div>
{/* 公告列表部分 */}
<div className={styles.announceList}>
<List
itemLayout="horizontal"
dataSource={mockAnnounceData}
pagination={{
onChange: handlePageChange,
current,
pageSize,
total,
showTotal: (totalItems) => `${totalItems} 条记录`,
showSizeChanger: true,
showQuickJumper: true,
}}
renderItem={(item) => (
<List.Item
key={item.id}
className={styles.listItem}
>
<div className={styles.itemContent}>
<div className={styles.itemHeader}>
<Tag color={getTypeColor(item.type)} className={styles.typeTag}>
{item.type}
</Tag>
<div
className={styles.itemTitle}
onClick={() => handleAnnounceClick(item.id)}
>
{item.title}
</div>
</div>
<div className={styles.itemFooter}>
<div className={styles.company}>{item.company}</div>
<div className={styles.date}>{item.publishDate}</div>
</div>
</div>
</List.Item>
)}
/>
</div>
</div>
);
};
export default AnnouncePage;

View File

@ -1,144 +0,0 @@
.announceInfoContainer {
padding: 20px;
background-color: #fff;
}
.loadingContainer {
display: flex;
justify-content: center;
align-items: center;
}
.announceInfoHeader {
margin-bottom: 20px;
}
.backButton {
padding-left: 0;
font-size: 16px;
color: rgb(0, 79, 142);
&:hover, &:focus {
color: rgba(0, 79, 142, 0.8);
}
}
.announceInfoContent {
padding: 30px;
}
.titleContainer {
text-align: center;
margin-bottom: 30px;
}
.title {
font-weight: bold !important;
color: #333;
}
.metaInfo {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin-bottom: 20px;
@media (max-width: 768px) {
flex-direction: column;
> div {
margin-bottom: 10px;
}
}
}
.metaLeft {
flex: 1;
}
.metaCenter {
flex: 1;
text-align: center;
@media (max-width: 768px) {
text-align: left;
}
}
.metaRight {
flex: 1;
text-align: right;
@media (max-width: 768px) {
text-align: left;
}
}
.divider {
margin: 20px 0 !important;
border-top-color: #e8e8e8;
}
.contentBody {
font-size: 16px;
line-height: 1.8;
color: #333;
p {
margin-bottom: 16px;
}
}
.attachmentsSection {
margin-top: 40px;
}
.attachmentsTitle {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
}
.attachmentsList {
display: flex;
flex-wrap: wrap;
}
.attachmentButton {
margin-right: 15px;
margin-bottom: 15px;
}
:global {
.ant-btn-primary {
background-color: rgb(0, 79, 142);
border-color: rgb(0, 79, 142);
border-radius: 0;
&:hover, &:focus {
background-color: rgba(0, 79, 142, 0.8);
border-color: rgba(0, 79, 142, 0.8);
}
}
.ant-btn-primary[disabled] {
background-color: rgba(0, 0, 0, 0.04);
border-color: #d9d9d9;
color: rgba(0, 0, 0, 0.25);
}
.ant-btn-background-ghost.ant-btn-primary {
color: rgb(0, 79, 142);
border-color: rgb(0, 79, 142);
border-radius: 0;
&:hover, &:focus {
color: rgba(0, 79, 142, 0.8);
border-color: rgba(0, 79, 142, 0.8);
}
}
.ant-divider {
border-top-color: #e8e8e8;
}
}

View File

@ -1,186 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from 'umi';
import { Typography, Button, Space, Divider, Row, Col, Spin, message } from 'antd';
import { DownloadOutlined, ArrowLeftOutlined, FilePdfOutlined, FileWordOutlined, FileExcelOutlined } from '@ant-design/icons';
import styles from './announceInfo.less';
const { Title, Text } = Typography;
// 模拟公告详情数据
const mockAnnounceDetail = {
id: '1',
title: '中远海运集团2023年度办公设备采购项目招标公告',
type: '招标采购公告',
publishDate: '2023-06-15',
publisher: '中远海运集团采购中心',
content: `
<p>一、项目基本情况</p>
<p>1. 项目名称中远海运集团2023年度办公设备采购项目</p>
<p>2. 采购需求台式电脑300台、笔记本电脑150台、打印机50台、投影仪10台等办公设备</p>
<p>3. 项目预算人民币800万元</p>
<p>4. 最高限价人民币800万元</p>
<p>5. 采购方式:公开招标</p>
<p>6. 交付地点:中远海运集团总部及各分支机构</p>
<p>7. 交付期限合同签订后45个工作日内</p>
<p>8. 本项目是否接受联合体投标:否</p>
<p>二、申请人的资格要求</p>
<p>1. 满足《中华人民共和国政府采购法》第二十二条规定;</p>
<p>2. 落实政府采购政策需满足的资格要求:本项目支持小微企业,监狱企业,残疾人福利性单位发展等政府采购政策。</p>
<p>3. 本项目的特定资格要求:</p>
<p>1投标人须具有电子产品销售相关资质</p>
<p>2投标人须提供所投产品的厂家授权</p>
<p>3投标人须具有近三年同类项目业绩不少于3个。</p>
<p>三、获取招标文件</p>
<p>1. 时间2023年6月15日至2023年6月22日每天9:00至17:00北京时间法定节假日除外</p>
<p>2. 地点:中远海运集团采购中心官网</p>
<p>3. 方式:网上下载</p>
<p>4. 售价:免费</p>
<p>四、提交投标文件截止时间、开标时间和地点</p>
<p>1. 提交投标文件截止时间2023年7月6日14:30北京时间</p>
<p>2. 开标时间2023年7月6日15:00北京时间</p>
<p>3. 地点中远海运集团总部1号会议室</p>
<p>五、公告期限</p>
<p>自本公告发布之日起5个工作日。</p>
<p>六、其他补充事宜</p>
<p>1. 本项目需要落实的政府采购政策:节能环保、中小微企业扶持、促进残疾人就业等。</p>
<p>2. 投标保证金人民币10万元。</p>
<p>七、对本次招标提出询问,请按以下方式联系</p>
<p>1. 采购人信息</p>
<p>名称:中远海运集团有限公司</p>
<p>地址上海市东大名路xxx号</p>
<p>联系方式021-xxxxxxxx</p>
<p>2. 采购代理机构信息</p>
<p>名称上海xxx招标代理有限公司</p>
<p>地址上海市浦东新区xxx路xx号</p>
<p>联系方式021-xxxxxxxx</p>
<p>3. 项目联系方式</p>
<p>项目联系人:张先生</p>
<p>电话021-xxxxxxxx</p>
`,
attachments: [
{ name: '招标文件.pdf', url: '/files/招标文件.pdf', type: 'pdf' },
{ name: '投标须知.docx', url: '/files/投标须知.docx', type: 'word' },
{ name: '技术需求说明.xlsx', url: '/files/技术需求说明.xlsx', type: 'excel' }
]
};
const AnnounceInfo: React.FC = () => {
const location = useLocation();
const id = new URLSearchParams(location.search).get("id");
const [announceDetail, setAnnounceDetail] = useState<any>(null);
const [loading, setLoading] = useState<boolean>(true);
// 模拟获取公告详情数据
useEffect(() => {
// 实际项目中应该通过API获取数据
setTimeout(() => {
setAnnounceDetail(mockAnnounceDetail);
setLoading(false);
}, 500);
}, [id]);
// 处理返回列表
const handleBack = () => {
window.history.back();
};
// 处理下载附件
const handleDownload = (url: string, name: string) => {
message.success(`开始下载: ${name}`);
console.log('下载附件:', url, name);
// 实际项目中应该调用下载API
// window.open(url);
};
// 根据文件类型获取图标
const getFileIcon = (type: string) => {
switch (type) {
case 'pdf':
return <FilePdfOutlined />;
case 'word':
return <FileWordOutlined />;
case 'excel':
return <FileExcelOutlined />;
default:
return <DownloadOutlined />;
}
};
if (loading) {
return (
<div className={styles.loadingContainer}>
<Spin size="large" />
</div>
);
}
return (
<div className={styles.announceInfoContainer}>
<div className={styles.announceInfoHeader}>
<Button
type="link"
icon={<ArrowLeftOutlined />}
onClick={handleBack}
className={styles.backButton}
>
</Button>
</div>
<div className={styles.announceInfoContent}>
<div className={styles.titleContainer}>
<Title level={2} className={styles.title}>
{announceDetail.title}
</Title>
</div>
<div className={styles.metaInfo}>
<div className={styles.metaLeft}>
<Text type="secondary">: {announceDetail.publishDate}</Text>
</div>
<div className={styles.metaCenter}>
<Text type="secondary">: {announceDetail.publisher}</Text>
</div>
<div className={styles.metaRight}>
<Text type="secondary">: {announceDetail.type}</Text>
</div>
</div>
<Divider className={styles.divider} />
<div className={styles.contentBody}>
<div dangerouslySetInnerHTML={{ __html: announceDetail.content }} />
</div>
{announceDetail.attachments.length > 0 && (
<div className={styles.attachmentsSection}>
<Divider className={styles.divider} />
<div className={styles.attachmentsTitle}></div>
<div className={styles.attachmentsList}>
{announceDetail.attachments.map((attachment: any, index: number) => (
<Button
key={index}
type="primary"
ghost
icon={getFileIcon(attachment.type)}
onClick={() => handleDownload(attachment.url, attachment.name)}
className={styles.attachmentButton}
>
{attachment.name}
</Button>
))}
</div>
</div>
)}
</div>
</div>
);
};
export default AnnounceInfo;

View File

@ -1,68 +0,0 @@
@import '~antd/es/style/themes/default.less';
.downloadContainer {
padding: 24px;
background-color: #fff;
}
.downloadRow {
width: 100%;
}
.menuCol {
margin-bottom: 24px;
}
.downloadMenu {
border-right: none;
}
.downloadContent {
width: 100%;
}
.downloadCardList {
margin-top: 16px;
}
.downloadCard {
margin-bottom: 24px;
transition: all 0.3s;
display: flex;
flex-direction: column;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
}
.cardTitle {
color: #333;
font-weight: 500;
font-size: 16px;
line-height: 22px;
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex;
align-items: center;
}
.cardDate {
color: rgba(0, 0, 0, 0.45);
margin-bottom: 12px;
font-size: 12px;
}
.downloadIcon {
font-size: 24px;
margin-right: 8px;
color: #1890ff;
}
.downloadButton {
margin-top: 12px;
width: 100%;
}

View File

@ -1,240 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Card, Menu, Row, Col, Pagination, Button, Typography, Space, message } from 'antd';
import { FileOutlined, BookOutlined, DownloadOutlined } from '@ant-design/icons';
import styles from './download.less';
import { downloadFile } from './service';
const { Title, Text } = Typography;
// 模拟模板文件数据
const mockTemplateFiles = [
{
id: '1',
title: '公开招标操作手册',
description: '公开招标各角色操作手册',
publishDate: '2025年2月3日',
type: 'template',
icon: <FileOutlined />,
fileName: '公开招标操作手册.pdf',
},
{
id: '2',
title: '询价采购模板',
description: '标准询价采购流程文档模板',
publishDate: '2025年1月15日',
type: 'template',
icon: <FileOutlined />,
fileName: '询价采购模板.docx',
},
{
id: '3',
title: '竞争性谈判模板',
description: '竞争性谈判标准文档模板',
publishDate: '2024年12月20日',
type: 'template',
icon: <FileOutlined />,
fileName: '竞争性谈判模板.docx',
},
{
id: '4',
title: '单一来源采购模板',
description: '单一来源采购申请及实施模板',
publishDate: '2024年12月5日',
type: 'template',
icon: <FileOutlined />,
fileName: '单一来源采购模板.docx',
},
{
id: '5',
title: '供应商评估表格',
description: '供应商资质与能力评估标准表格',
publishDate: '2024年11月18日',
type: 'template',
icon: <FileOutlined />,
fileName: '供应商评估表格.xlsx',
},
{
id: '6',
title: '合同范本模板',
description: '标准采购合同范本',
publishDate: '2024年11月1日',
type: 'template',
icon: <FileOutlined />,
fileName: '合同范本模板.docx',
},
];
// 模拟操作手册数据
const mockManuals = [
{
id: '7',
title: '供应商注册指南',
description: '供应商平台注册及资质提交操作指南',
publishDate: '2025年1月25日',
type: 'manual',
icon: <BookOutlined />,
fileName: '供应商注册指南.pdf',
},
{
id: '8',
title: '投标操作指南',
description: '电子投标全流程操作手册',
publishDate: '2025年1月10日',
type: 'manual',
icon: <BookOutlined />,
fileName: '投标操作指南.pdf',
},
{
id: '9',
title: '在线开标指南',
description: '在线开标会议参与指南',
publishDate: '2024年12月15日',
type: 'manual',
icon: <BookOutlined />,
fileName: '在线开标指南.pdf',
},
{
id: '10',
title: '评标专家操作手册',
description: '评标专家系统使用指南',
publishDate: '2024年11月28日',
type: 'manual',
icon: <BookOutlined />,
fileName: '评标专家操作手册.pdf',
},
{
id: '11',
title: '合同签署指南',
description: '电子合同签署流程指南',
publishDate: '2024年11月10日',
type: 'manual',
icon: <BookOutlined />,
fileName: '合同签署指南.pdf',
},
];
const DownloadPage: React.FC = () => {
const [activeMenu, setActiveMenu] = useState<string>('template');
const [downloadData, setDownloadData] = useState<any[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 6,
total: 0,
});
// 根据当前选中的菜单加载对应的下载数据
useEffect(() => {
setLoading(true);
// 模拟API请求
setTimeout(() => {
const data = activeMenu === 'template' ? mockTemplateFiles : mockManuals;
setDownloadData(data);
setPagination((prevPagination) => ({
...prevPagination,
total: data.length,
current: 1,
}));
setLoading(false);
}, 500);
}, [activeMenu]);
// 处理菜单切换
const handleMenuClick = (e: any) => {
setActiveMenu(e.key);
};
// 处理分页变化
const handlePageChange = (page: number, pageSize?: number) => {
setPagination({
...pagination,
current: page,
pageSize: pageSize || pagination.pageSize,
});
};
// 处理下载按钮点击
const handleDownload = async (id: string, fileName: string) => {
try {
await downloadFile(id, fileName);
} catch (error) {
message.error('下载失败,请稍后重试');
}
};
// 计算当前页显示的数据
const getCurrentPageData = () => {
const { current, pageSize } = pagination;
const startIndex = (current - 1) * pageSize;
const endIndex = startIndex + pageSize;
return downloadData.slice(startIndex, endIndex);
};
return (
<div className={styles.downloadContainer}>
<Row gutter={16} className={styles.downloadRow}>
<Col span={4} className={styles.menuCol}>
<Menu
mode="vertical"
selectedKeys={[activeMenu]}
onClick={handleMenuClick}
className={styles.downloadMenu}
>
<Menu.Item key="template" icon={<FileOutlined />}>
</Menu.Item>
<Menu.Item key="manual" icon={<BookOutlined />}>
</Menu.Item>
</Menu>
</Col>
<Col span={20}>
<div className={styles.downloadContent}>
<Title level={4}>{activeMenu === 'template' ? '模板文件' : '操作手册'}</Title>
<Row gutter={[16, 16]} className={styles.downloadCardList}>
{getCurrentPageData().map((item) => (
<Col xs={24} sm={12} md={8} key={item.id}>
<Card
hoverable
className={styles.downloadCard}
loading={loading}
>
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<div className={styles.cardTitle}>
<span className={styles.downloadIcon}>{item.icon}</span>
{item.title}
</div>
<div className={styles.cardDate}>{item.publishDate}</div>
<Text type="secondary" ellipsis={{ tooltip: true }}>{item.description}</Text>
<Button
type="primary"
icon={<DownloadOutlined />}
className={styles.downloadButton}
onClick={() => handleDownload(item.id, item.fileName)}
>
</Button>
</Space>
</Card>
</Col>
))}
</Row>
<Pagination
current={pagination.current}
pageSize={pagination.pageSize}
total={pagination.total}
onChange={handlePageChange}
showTotal={(total) => `${total} 条记录`}
style={{ marginTop: 24, textAlign: 'right' }}
/>
</div>
</Col>
</Row>
</div>
);
};
export default DownloadPage;

View File

@ -1,75 +0,0 @@
import { message } from 'antd';
import request from '@/utils/request';
/**
* 获取下载中心文件列表
* @param params 查询参数
*/
export async function getDownloadList(params: {
type: string;
current: number;
pageSize: number;
}) {
try {
// 实际项目中应该通过API获取数据
// return request('/api/download/list', {
// method: 'GET',
// params,
// });
// 模拟API请求返回数据
return Promise.resolve({
success: true,
data: {
list: [],
total: 0,
},
});
} catch (error) {
message.error('获取下载列表失败');
return {
success: false,
data: {
list: [],
total: 0,
},
};
}
}
/**
* 下载文件
* @param fileId 文件ID
* @param fileName 文件名称
*/
export async function downloadFile(fileId: string, fileName: string) {
try {
// 实际项目中应该通过API下载文件
// const response = await request(`/api/download/file/${fileId}`, {
// method: 'GET',
// responseType: 'blob',
// });
// 创建下载链接
// const blob = new Blob([response]);
// const url = window.URL.createObjectURL(blob);
// const link = document.createElement('a');
// link.href = url;
// link.download = fileName;
// document.body.appendChild(link);
// link.click();
// window.URL.revokeObjectURL(url);
// document.body.removeChild(link);
// 模拟下载成功
message.success(`文件"${fileName}"开始下载`);
return {
success: true,
};
} catch (error) {
message.error(`下载文件"${fileName}"失败`);
return {
success: false,
};
}
}

View File

@ -0,0 +1,50 @@
.download-manage-container {
.upload-hint {
margin-top: 8px;
color: #999;
font-size: 12px;
}
.content-area {
background-color: #fff;
padding: 16px;
border-radius: 4px;
margin-top: 16px;
}
.filter-action-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
background-color: #fff;
padding: 16px;
border-radius: 4px;
.filter-form {
flex: 1;
}
.filter-btns {
margin-left: 8px;
display: flex;
gap: 8px;
}
.right-buttons {
display: flex;
gap: 8px;
align-items: center;
.selected-count {
margin-left: 8px;
color: #1890ff;
}
}
}
.ant-table-wrapper {
width: 100%;
}
}

View File

@ -0,0 +1,675 @@
import React, { useState } from 'react';
import { useIntl } from 'umi';
import { Button, Table, Space, Modal, message, Input, Select, Form, Tooltip, Switch, Upload, Tag } from 'antd';
import {
PlusOutlined,
DeleteOutlined,
ExclamationCircleOutlined,
SearchOutlined,
InboxOutlined,
} from '@ant-design/icons';
import { validateFileSize } from '@/utils/utils';
import './downloadManage.less';
import type { UploadFile } from 'antd/es/upload/interface';
const { Option } = Select;
const { Dragger } = Upload;
type DownloadType = API.DownloadRecord & {
key: string;
};
const DownloadManage: React.FC = () => {
const intl = useIntl();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm();
const [modalVisible, setModalVisible] = useState<boolean>(false);
const [isEdit, setIsEdit] = useState<boolean>(false);
const [currentId, setCurrentId] = useState<string>('');
const [modalForm] = Form.useForm();
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [thumbnailList, setThumbnailList] = useState<UploadFile[]>([]);
const [downloadData, setDownloadData] = useState<DownloadType[]>([
{
key: '1',
id: '1',
name: '采购申请模板',
category: '模板文件',
keyword: '采购,申请,模板',
createTime: '2023-06-15 10:30:00',
createBy: 'admin',
status: '1', // 已发布
thumbnail: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
key: '2',
id: '2',
name: '系统操作手册V1.0',
category: '操作手册',
keyword: '系统,操作,手册',
createTime: '2023-06-20 14:45:00',
createBy: 'admin',
status: '0', // 草稿
thumbnail: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
key: '3',
id: '3',
name: '发票申请指南',
category: '操作手册',
keyword: '发票,申请,指南',
createTime: '2023-06-25 09:15:00',
createBy: 'admin',
status: '1', // 已发布
thumbnail: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
]);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 3,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<API.DownloadSearchParams>({});
// 获取下载中心列表(模拟数据)
const fetchDownloadList = (current: number = 1, pageSize: number = 10) => {
setLoading(true);
// 模拟API请求延迟
setTimeout(() => {
setLoading(false);
}, 500);
};
// 处理编辑
const handleEdit = (record: DownloadType) => {
// 检查是否为已发布状态
if (record.status === '1') {
message.warning('已发布的文件不能编辑');
return;
}
setIsEdit(true);
setCurrentId(record.id);
setModalVisible(true);
// 填充表单数据
modalForm.setFieldsValue({
name: record.name,
category: record.category,
keyword: record.keyword,
});
// 重置文件列表和缩略图
setFileList([]);
if (record.thumbnail) {
setThumbnailList([
{
uid: '-1',
name: 'thumbnail.png',
status: 'done',
url: record.thumbnail,
type: 'image/png',
size: 0,
} as UploadFile
]);
} else {
setThumbnailList([]);
}
};
// 处理删除
const showDeleteConfirm = (record: DownloadType) => {
// 检查是否为已发布状态
if (record.status === '1') {
message.warning('已发布的文件不能删除');
return;
}
Modal.confirm({
title: '确定要删除该文件吗?',
icon: <ExclamationCircleOutlined />,
content: `名称: ${record.name}`,
okText: '确定',
okType: 'danger',
cancelText: '取消',
maskClosable: false,
onOk: async () => {
try {
// 模拟删除操作
const newData = downloadData.filter(item => item.id !== record.id);
setDownloadData(newData);
message.success('删除成功');
} catch (error) {
console.error('删除文件失败:', error);
message.error('删除失败');
}
},
});
};
// 处理发布/下架
const handlePublishStatus = async (record: DownloadType) => {
// 状态: 0-草稿1-已发布
const isPublished = record.status === '1';
const actionText = isPublished ? '下架' : '发布';
const newStatus = isPublished ? '0' : '1';
try {
// 模拟API请求
const newData = downloadData.map(item =>
item.id === record.id ? { ...item, status: newStatus } : item
);
setDownloadData(newData);
message.success(`${actionText}成功`);
} catch (error) {
console.error(`${actionText}失败:`, error);
message.error(`${actionText}失败`);
}
};
// 获取栏目标签
const getCategoryTag = (category: string) => {
switch (category) {
case '模板文件':
return <Tag color="blue"></Tag>;
case '操作手册':
return <Tag color="green"></Tag>;
default:
return <Tag color="default"></Tag>;
}
};
// 获取状态标签
const getStatusTag = (status: string) => {
switch (status) {
case '0':
return <Tag color="default">稿</Tag>;
case '1':
return <Tag color="green"></Tag>;
default:
return <Tag color="default"></Tag>;
}
};
// 处理表格分页变化
const handleTableChange = (newPagination: any) => {
fetchDownloadList(newPagination.current, newPagination.pageSize);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 80,
},
{
title: '缩略图',
dataIndex: 'thumbnail',
key: 'thumbnail',
render: (thumbnail: string) => (
thumbnail ? <img src={thumbnail} alt="缩略图" style={{ width: 50, height: 50, objectFit: 'cover' }} /> : '无'
),
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
ellipsis: {
showTitle: false,
},
render: (name: string) => (
<Tooltip placement="topLeft" title={name}>
{name}
</Tooltip>
),
},
{
title: '栏目',
dataIndex: 'category',
key: 'category',
render: (category: string) => getCategoryTag(category),
},
{
title: '关键词',
dataIndex: 'keyword',
key: 'keyword',
ellipsis: {
showTitle: false,
},
render: (keyword: string) => (
<Tooltip placement="topLeft" title={keyword}>
{keyword}
</Tooltip>
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => getStatusTag(status),
},
{
title: '上传时间',
dataIndex: 'createTime',
key: 'createTime',
},
{
title: '上传人',
dataIndex: 'createBy',
align: 'center' as const,
key: 'createBy',
},
{
title: '操作',
key: 'action',
width: 300,
align: 'center' as const,
render: (_: unknown, record: DownloadType) => (
<>
<Button type="link" onClick={() => window.open('#')}>
</Button>
{record.status === '1' ? (
<Button type="link" onClick={() => handlePublishStatus(record)}>
</Button>
) : (
<>
<Button type="link" onClick={() => handleEdit(record)}>
</Button>
<Button
type="link"
onClick={() => handlePublishStatus(record)}
>
</Button>
</>
)}
{record.status !== '1' && (
<Button
type="link"
danger
onClick={() => showDeleteConfirm(record)}
>
</Button>
)}
</>
),
},
];
// 行选择限制
const checkSelectable = (record: DownloadType) => {
return record.status !== '1'; // 已发布的不能选择
};
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
setSelectedRowKeys(newSelectedRowKeys);
};
const rowSelection = {
selectedRowKeys,
onChange: onSelectChange,
getCheckboxProps: (record: DownloadType) => ({
disabled: record.status === '1', // 已发布的不能选择
}),
};
const hasSelected = selectedRowKeys.length > 0;
// 处理添加
const handleAdd = () => {
setIsEdit(false);
setCurrentId('');
modalForm.resetFields();
setFileList([]);
setThumbnailList([]);
setModalVisible(true);
};
// 处理批量删除
const handleBatchDelete = () => {
Modal.confirm({
title: '确定要删除选中的文件吗?',
icon: <ExclamationCircleOutlined />,
content: '删除后无法恢复',
okText: '确定',
okType: 'danger',
cancelText: '取消',
maskClosable: false,
onOk: async () => {
setLoading(true);
try {
// 模拟批量删除,过滤掉已发布的文件
const deleteIds = selectedRowKeys.filter(key => {
const record = downloadData.find(item => item.key === key);
return record && record.status !== '1';
});
const newData = downloadData.filter(item => !deleteIds.includes(item.key));
setDownloadData(newData);
setSelectedRowKeys([]);
message.success('删除成功');
} catch (error) {
console.error('批量删除失败:', error);
message.error('批量删除失败');
} finally {
setLoading(false);
}
},
});
};
// 处理搜索
const handleSearch = (values: API.DownloadSearchParams) => {
console.log('搜索条件:', values);
setSearchParams(values);
// 模拟搜索实际项目中应调用API
const initialData = [
{
key: '1',
id: '1',
name: '采购申请模板',
category: '模板文件',
keyword: '采购,申请,模板',
createTime: '2023-06-15 10:30:00',
createBy: 'admin',
status: '1', // 已发布
thumbnail: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
key: '2',
id: '2',
name: '系统操作手册V1.0',
category: '操作手册',
keyword: '系统,操作,手册',
createTime: '2023-06-20 14:45:00',
createBy: 'admin',
status: '0', // 草稿
thumbnail: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
key: '3',
id: '3',
name: '发票申请指南',
category: '操作手册',
keyword: '发票,申请,指南',
createTime: '2023-06-25 09:15:00',
createBy: 'admin',
status: '1', // 已发布
thumbnail: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
];
if (values.name || values.category || values.status) {
const filteredData = initialData.filter(item => {
const nameMatch = values.name ? item.name.includes(values.name) : true;
const categoryMatch = values.category ? item.category === values.category : true;
const statusMatch = values.status ? item.status === values.status : true;
return nameMatch && categoryMatch && statusMatch;
});
setDownloadData(filteredData);
} else {
setDownloadData(initialData);
}
};
// 处理文件上传变化
const handleFileChange = (info: any) => {
setFileList(info.fileList.slice(-1)); // 只保留最后一个文件
};
// 处理缩略图上传变化
const handleThumbnailChange = (info: any) => {
setThumbnailList(info.fileList.slice(-1)); // 只保留最后一个文件
// 如果上传成功模拟获取URL
if (info.file.status === 'done') {
message.success(`${info.file.name} 上传成功`);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} 上传失败`);
}
};
// 处理模态框提交
const handleModalSubmit = () => {
modalForm.validateFields().then(values => {
try {
// 检查是否有文件上传
if (!isEdit && fileList.length === 0) {
message.error('请上传文件');
return;
}
const thumbnailUrl = thumbnailList.length > 0
? thumbnailList[0].url || 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'
: undefined;
const newItem: DownloadType = {
key: isEdit ? currentId : (downloadData.length + 1).toString(),
id: isEdit ? currentId : (downloadData.length + 1).toString(),
name: values.name,
category: values.category,
keyword: values.keyword,
createTime: new Date().toLocaleString(),
createBy: 'admin',
status: '0', // 默认为草稿状态
thumbnail: thumbnailUrl,
};
if (isEdit) {
// 编辑模式,更新数据
const newData = downloadData.map(item =>
item.id === currentId ? newItem : item
);
setDownloadData(newData);
message.success('更新成功');
} else {
// 新增模式,添加数据
setDownloadData([...downloadData, newItem]);
message.success('添加成功');
}
setModalVisible(false);
modalForm.resetFields();
setFileList([]);
setThumbnailList([]);
} catch (error) {
console.error(isEdit ? '更新文件失败:' : '添加文件失败:', error);
message.error(isEdit ? '更新失败' : '添加失败');
}
}).catch(info => {
console.log('校验失败:', info);
});
};
// 处理模态框取消
const handleModalCancel = () => {
setModalVisible(false);
modalForm.resetFields();
setFileList([]);
setThumbnailList([]);
};
return (
<div className="download-manage-container common-container">
<div className="filter-action-row">
<Form
form={form}
name="search"
onFinish={handleSearch}
layout="inline"
className="filter-form"
>
<Form.Item name="name" label="名称">
<Input placeholder="请输入名称关键词" allowClear />
</Form.Item>
<Form.Item name="category" label="栏目">
<Select placeholder="请选择栏目" allowClear>
<Option value="模板文件"></Option>
<Option value="操作手册"></Option>
</Select>
</Form.Item>
<Form.Item name="status" label="状态">
<Select placeholder="请选择状态" allowClear>
<Option value="0">稿</Option>
<Option value="1"></Option>
</Select>
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" htmlType="submit" icon={<SearchOutlined />}>
</Button>
<Button
type="primary"
danger
icon={<DeleteOutlined />}
onClick={() => {
form.resetFields();
setSearchParams({});
handleSearch({});
}}
>
</Button>
</Form.Item>
</Form>
<div className="right-buttons">
<Button
type="primary"
ghost
icon={<PlusOutlined />}
onClick={handleAdd}
>
</Button>
<Button
danger
icon={<DeleteOutlined />}
onClick={handleBatchDelete}
disabled={!hasSelected}
loading={loading}
>
</Button>
{hasSelected && (
<span className="selected-count">
{selectedRowKeys.length}
</span>
)}
</div>
</div>
<div className="content-area">
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={downloadData}
pagination={pagination}
loading={loading}
onChange={handleTableChange}
/>
</div>
{/* 新增/编辑模态框 */}
<Modal
title={isEdit ? "编辑文件" : "新增文件"}
visible={modalVisible}
onOk={handleModalSubmit}
onCancel={handleModalCancel}
width={600}
maskClosable={false}
destroyOnClose
>
<Form
form={modalForm}
layout="vertical"
name="downloadForm"
preserve={false}
>
<Form.Item
name="name"
label="名称"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input placeholder="请输入名称" />
</Form.Item>
<Form.Item
name="category"
label="栏目"
rules={[{ required: true, message: '请选择栏目' }]}
>
<Select placeholder="请选择栏目">
<Option value="模板文件"></Option>
<Option value="操作手册"></Option>
</Select>
</Form.Item>
<Form.Item
name="keyword"
label="关键词"
rules={[{ required: true, message: '请输入关键词' }]}
>
<Input placeholder="请输入关键词,多个关键词用逗号分隔" />
</Form.Item>
<Form.Item
name="file"
label="文件上传"
rules={[{ required: !isEdit, message: '请上传文件' }]}
>
<Dragger
name="file"
fileList={fileList}
beforeUpload={(file) => validateFileSize(file, 20, ['pdf', 'zip', 'rar', 'doc', 'docx'])}
onChange={handleFileChange}
action="/"
maxCount={1}
>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text"></p>
<p className="ant-upload-hint">.rar .zip .doc .docx .pdf20MB</p>
</Dragger>
</Form.Item>
<Form.Item
name="thumbnail"
label="缩略图"
extra="支持jpg、png、gif等图片格式大小不超过2MB"
>
<Upload
name="thumbnail"
listType="picture-card"
fileList={thumbnailList}
beforeUpload={(file) => validateFileSize(file, 2, ['jpg', 'png', 'gif'])}
onChange={handleThumbnailChange}
action="/"
maxCount={1}
>
{thumbnailList.length >= 1 ? null : (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}></div>
</div>
)}
</Upload>
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default DownloadManage;

View File

@ -0,0 +1,11 @@
.help-manage-container {
padding: 24px;
.help-manage-content {
background: #fff;
padding: 24px;
min-height: 500px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import { useIntl } from 'umi';
import './helpManage.less';
const HelpManage: React.FC = () => {
const intl = useIntl();
return (
<div className="help-manage-container">
<h1>{intl.formatMessage({ id: 'menu.帮助中心管理' })}</h1>
<div className="help-manage-content">
{/* 帮助中心管理内容 */}
</div>
</div>
);
};
export default HelpManage;

View File

@ -1,32 +0,0 @@
@import '~antd/es/style/themes/default.less';
.noticeContainer {
min-height: calc(100vh - 200px);
padding: 24px;
background-color: #fff;
}
.noticeRow {
width: 100%;
}
.menuCol {
margin-bottom: 24px;
}
.noticeMenu {
border-right: none;
}
.noticeContent {
width: 100%;
}
.noticeTitle {
color: #333;
transition: color 0.3s;
&:hover {
color: rgb(0, 79, 142);
}
}

View File

@ -1,191 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Table, Menu, Row, Col } from 'antd';
import { history } from 'umi';
import { BellOutlined, AppstoreOutlined } from '@ant-design/icons';
import styles from './notice.less';
// 模拟系统更新通知数据
const mockSystemNotices = [
{
id: '1',
title: '系统将于2023年9月15日进行版本升级',
publishDate: '2023-09-10',
type: 'system',
},
{
id: '2',
title: '招标模块功能优化更新通知',
publishDate: '2023-08-25',
type: 'system',
},
{
id: '3',
title: '系统安全性能升级维护通知',
publishDate: '2023-08-10',
type: 'system',
},
{
id: '4',
title: '移动端应用同步更新通知',
publishDate: '2023-07-28',
type: 'system',
},
{
id: '5',
title: '供应商管理模块功能升级通知',
publishDate: '2023-07-15',
type: 'system',
},
];
// 模拟其他通知数据
const mockOtherNotices = [
{
id: '6',
title: '关于开展2023年度供应商评估工作的通知',
publishDate: '2023-09-05',
type: 'other',
},
{
id: '7',
title: '关于调整采购审批流程的通知',
publishDate: '2023-08-20',
type: 'other',
},
{
id: '8',
title: '关于加强信息安全管理的通知',
publishDate: '2023-08-05',
type: 'other',
},
{
id: '9',
title: '关于开展采购人员培训的通知',
publishDate: '2023-07-25',
type: 'other',
},
{
id: '10',
title: '关于优化供应商准入机制的通知',
publishDate: '2023-07-10',
type: 'other',
},
];
const NoticePage: React.FC = () => {
const [activeMenu, setActiveMenu] = useState<string>('system');
const [noticeData, setNoticeData] = useState<any[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
// 根据当前选中的菜单加载对应的通知数据
useEffect(() => {
setLoading(true);
// 模拟API请求
setTimeout(() => {
const data = activeMenu === 'system' ? mockSystemNotices : mockOtherNotices;
setNoticeData(data);
setPagination((prevPagination) => ({
...prevPagination,
total: data.length,
}));
setLoading(false);
}, 500);
}, [activeMenu]);
// 处理菜单切换
const handleMenuClick = (e: any) => {
setActiveMenu(e.key);
setPagination({
...pagination,
current: 1,
});
};
// 处理表格分页变化
const handleTableChange = (newPagination: any) => {
setPagination(newPagination);
};
// 处理点击通知标题
const handleNoticeClick = (id: string) => {
history.push(`/notice/noticeInfo?id=${id}`);
};
// 定义表格列
const columns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
render: (_: any, __: any, index: number) => {
return (pagination.current - 1) * pagination.pageSize + index + 1;
},
},
{
title: '标题',
dataIndex: 'title',
key: 'title',
render: (text: string, record: any) => (
<a onClick={() => handleNoticeClick(record.id)} className={styles.noticeTitle}>
{text}
</a>
),
},
{
title: '发布时间',
dataIndex: 'publishDate',
key: 'publishDate',
width: 150,
align: 'center' as 'center',
},
];
return (
<div className={styles.noticeContainer}>
<Row gutter={10} className={styles.noticeRow}>
<Col span={3} className={styles.menuCol}>
<Menu
mode="vertical"
selectedKeys={[activeMenu]}
onClick={handleMenuClick}
className={styles.noticeMenu}
>
<Menu.Item key="system" icon={<AppstoreOutlined />}>
</Menu.Item>
<Menu.Item key="other" icon={<BellOutlined />}>
</Menu.Item>
</Menu>
</Col>
<Col span={21}>
<div className={styles.noticeContent}>
<Table
columns={columns}
dataSource={noticeData}
rowKey="id"
pagination={{
...pagination,
showTotal: (total) => `${total} 条记录`,
showSizeChanger: true,
showQuickJumper: true,
}}
loading={loading}
onChange={handleTableChange}
bordered
/>
</div>
</Col>
</Row>
</div>
);
};
export default NoticePage;

View File

@ -1,99 +0,0 @@
@import '~antd/es/style/themes/default.less';
.noticeInfoContainer {
padding: 24px;
background-color: #fff;
min-height: calc(100vh - 200px);
}
.noticeInfoHeader {
margin-bottom: 24px;
}
.backButton {
color: rgb(0, 79, 142);
padding: 0;
&:hover {
color: lighten(rgb(0, 79, 142), 10%);
}
}
.noticeInfoContent {
width: 100%;
max-width: 1000px;
margin: 0 auto;
}
.titleContainer {
text-align: center;
margin-bottom: 24px;
}
.title {
color: rgb(0, 79, 142);
font-weight: 500;
}
.metaInfo {
display: flex;
justify-content: space-between;
margin-bottom: 24px;
color: #999;
.metaLeft, .metaCenter, .metaRight {
flex: 1;
}
.metaCenter {
text-align: center;
}
.metaRight {
text-align: right;
}
}
.divider {
margin: 16px 0;
border-top: 1px solid #e8e8e8;
}
.contentBody {
font-size: 16px;
line-height: 1.8;
p {
margin-bottom: 16px;
}
strong {
font-weight: 500;
color: rgb(0, 79, 142);
}
ol, ul {
margin-bottom: 16px;
padding-left: 24px;
li {
margin-bottom: 8px;
}
}
}
.loadingContainer {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
.notFoundContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 400px;
gap: 24px;
}

View File

@ -1,247 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from 'umi';
import { Typography, Button, Divider, Spin, message } from 'antd';
import { ArrowLeftOutlined } from '@ant-design/icons';
import styles from './noticeInfo.less';
const { Title, Text } = Typography;
interface NoticeDetail {
id: string;
title: string;
content: string;
publishDate: string;
publisher: string;
type: string;
}
interface NoticeDetails {
[key: string]: NoticeDetail;
}
// 模拟通知详情数据
const mockNoticeDetails: NoticeDetails = {
'1': {
id: '1',
title: '系统将于2023年9月15日进行版本升级',
publishDate: '2023-09-10',
publisher: '系统管理员',
type: 'system',
content: `
<p>尊敬的用户:</p>
<p>为了提供更好的用户体验和系统功能我们计划于2023年9月15日晚上22:00至次日凌晨2:00进行系统版本升级。在此期间系统将暂停服务请您提前做好相关工作安排。</p>
<p><strong>本次升级内容:</strong></p>
<ol>
<li>优化招标采购流程,提高操作效率</li>
<li>增加供应商评价功能,完善评价体系</li>
<li>优化系统响应速度,提升用户体验</li>
<li>修复已知问题,提高系统稳定性</li>
<li>增强系统安全性,保障数据安全</li>
</ol>
<p><strong>升级注意事项:</strong></p>
<ol>
<li>请您在系统升级前保存并提交所有未完成的工作</li>
<li>系统升级后,建议清除浏览器缓存再登录系统</li>
<li>如遇到任何问题,请联系系统管理员</li>
</ol>
<p>感谢您的理解与支持!</p>
<p>系统管理团队</p>
<p>2023年9月10日</p>
`
},
'2': {
id: '2',
title: '招标模块功能优化更新通知',
publishDate: '2023-08-25',
publisher: '系统管理员',
type: 'system',
content: `
<p>尊敬的用户:</p>
<p>我们很高兴地通知您,招标模块已完成功能优化升级,现已上线。本次更新主要针对招标流程进行了优化,提高了操作效率和用户体验。</p>
<p><strong>主要更新内容:</strong></p>
<ol>
<li>简化招标发布流程,减少操作步骤</li>
<li>优化投标文件管理功能,支持批量上传和下载</li>
<li>增加招标项目进度跟踪功能,实时掌握项目状态</li>
<li>完善评标功能,支持多种评标方法</li>
<li>增加中标结果自动生成功能,提高工作效率</li>
</ol>
<p><strong>使用建议:</strong></p>
<ol>
<li>请参考系统内置的操作指南熟悉新功能</li>
<li>如有任何问题或建议,请通过系统反馈功能提交</li>
</ol>
<p>感谢您对我们工作的支持!</p>
<p>系统管理团队</p>
<p>2023年8月25日</p>
`
},
'6': {
id: '6',
title: '关于开展2023年度供应商评估工作的通知',
publishDate: '2023-09-05',
publisher: '采购管理部',
type: 'other',
content: `
<p>各相关部门:</p>
<p>为加强供应商管理提高采购质量根据《中远海运集团供应商管理实施细则》的要求现决定开展2023年度供应商评估工作具体事项通知如下</p>
<p><strong>一、评估对象</strong></p>
<p>2023年度与我集团有业务往来的所有供应商。</p>
<p><strong>二、评估时间</strong></p>
<p>2023年9月15日至10月15日。</p>
<p><strong>三、评估内容</strong></p>
<ol>
<li>供应商资质情况</li>
<li>产品质量及服务水平</li>
<li>价格合理性</li>
<li>交货及时性</li>
<li>售后服务响应速度</li>
<li>合同履约情况</li>
<li>社会责任履行情况</li>
</ol>
<p><strong>四、评估方法</strong></p>
<p>采用百分制评分根据评分结果将供应商分为A、B、C、D四个等级。</p>
<p><strong>五、工作要求</strong></p>
<ol>
<li>各部门应指定专人负责本部门供应商评估工作</li>
<li>评估过程应客观公正,实事求是</li>
<li>评估结果应于10月20日前报送采购管理部汇总</li>
</ol>
<p>特此通知。</p>
<p>采购管理部</p>
<p>2023年9月5日</p>
`
},
'7': {
id: '7',
title: '关于调整采购审批流程的通知',
publishDate: '2023-08-20',
publisher: '采购管理部',
type: 'other',
content: `
<p>各相关部门:</p>
<p>为提高采购效率,优化业务流程,经公司研究决定,对采购审批流程进行调整,现将有关事项通知如下:</p>
<p><strong>一、调整内容</strong></p>
<ol>
<li>采购金额50万元以下的项目由部门经理审批后直接提交采购部执行无需总经理审批</li>
<li>采购金额50万元至200万元的项目由部门经理和分管副总经理审批后提交采购部执行</li>
<li>采购金额200万元以上的项目仍按原流程执行需经总经理审批</li>
</ol>
<p><strong>二、实施时间</strong></p>
<p>本通知自2023年9月1日起实施。</p>
<p><strong>三、注意事项</strong></p>
<ol>
<li>各部门应严格按照新的审批流程执行,不得擅自变更</li>
<li>采购部应加强对采购项目的监督管理,确保采购质量</li>
<li>审批人应认真履行审批职责,对采购项目的必要性、合理性进行审核</li>
</ol>
<p>特此通知。</p>
<p>采购管理部</p>
<p>2023年8月20日</p>
`
}
};
const NoticeInfo: React.FC = () => {
const location = useLocation();
const id = new URLSearchParams(location.search).get("id");
const [noticeDetail, setNoticeDetail] = useState<NoticeDetail | null>(null);
const [loading, setLoading] = useState<boolean>(true);
// 模拟获取通知详情数据
useEffect(() => {
// 实际项目中应该通过API获取数据
setTimeout(() => {
if (id && mockNoticeDetails[id]) {
setNoticeDetail(mockNoticeDetails[id]);
} else {
// 处理ID不存在的情况
message.error('通知不存在');
}
setLoading(false);
}, 500);
}, [id]);
// 处理返回列表
const handleBack = () => {
window.history.back();
};
if (loading) {
return (
<div className={styles.loadingContainer}>
<Spin size="large" />
</div>
);
}
if (!noticeDetail) {
return (
<div className={styles.notFoundContainer}>
<Title level={4}></Title>
<Button type="primary" onClick={handleBack}></Button>
</div>
);
}
return (
<div className={styles.noticeInfoContainer}>
<div className={styles.noticeInfoHeader}>
<Button
type="link"
icon={<ArrowLeftOutlined />}
onClick={handleBack}
className={styles.backButton}
>
</Button>
</div>
<div className={styles.noticeInfoContent}>
<div className={styles.titleContainer}>
<Title level={2} className={styles.title}>
{noticeDetail.title}
</Title>
</div>
<div className={styles.metaInfo}>
<div className={styles.metaLeft}>
<Text type="secondary">: {noticeDetail.publishDate}</Text>
</div>
<div className={styles.metaCenter}>
<Text type="secondary">: {noticeDetail.publisher}</Text>
</div>
<div className={styles.metaRight}>
<Text type="secondary">
: {noticeDetail.type === 'system' ? '系统更新通知' : '其他通知'}
</Text>
</div>
</div>
<Divider className={styles.divider} />
<div className={styles.contentBody}>
<div dangerouslySetInnerHTML={{ __html: noticeDetail.content }} />
</div>
</div>
</div>
);
};
export default NoticeInfo;

View File

@ -0,0 +1,11 @@
.notice-manage-container {
padding: 24px;
.notice-manage-content {
background: #fff;
padding: 24px;
min-height: 500px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import { useIntl } from 'umi';
import './noticeManage.less';
const NoticeManage: React.FC = () => {
const intl = useIntl();
return (
<div className="notice-manage-container">
<h1>{intl.formatMessage({ id: 'menu.通知中心管理' })}</h1>
<div className="notice-manage-content">
{/* 通知中心管理内容 */}
</div>
</div>
);
};
export default NoticeManage;

View File

@ -1,28 +0,0 @@
@import '~antd/es/style/themes/default.less';
.policyContainer {
padding: 10px;
background-color: #fff;
// min-height: calc(100vh - 200px);
}
.policyHeader {
margin-bottom: 24px;
}
.pageTitle {
color: rgb(0, 79, 142);
font-weight: 500;
text-align: center;
margin-bottom: 24px;
}
.policyTitle {
color: #333;
transition: color 0.3s;
&:hover {
color: rgb(0, 79, 142);
}
}

View File

@ -1,146 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Table, Typography } from 'antd';
import { history } from 'umi';
import styles from './policy.less';
const { Title } = Typography;
// 模拟政策法规数据
const mockPolicyData = [
{
id: '1',
title: '关于进一步规范招标采购活动的管理办法',
publishDate: '2023-08-15',
},
{
id: '2',
title: '中远海运集团供应商管理实施细则(2023年修订版)',
publishDate: '2023-07-20',
},
{
id: '3',
title: '关于加强采购合同履约管理的通知',
publishDate: '2023-06-10',
},
{
id: '4',
title: '中远海运集团招标采购管理办法',
publishDate: '2023-05-25',
},
{
id: '5',
title: '关于优化招标采购流程提高采购效率的实施意见',
publishDate: '2023-04-18',
},
{
id: '6',
title: '中远海运集团电子招标采购平台操作指南',
publishDate: '2023-03-30',
},
{
id: '7',
title: '关于进一步加强采购风险防控的指导意见',
publishDate: '2023-02-15',
},
{
id: '8',
title: '中远海运集团采购评审专家管理办法',
publishDate: '2023-01-10',
},
{
id: '9',
title: '关于推进绿色采购的实施方案',
publishDate: '2022-12-20',
},
{
id: '10',
title: '中远海运集团采购人员职业道德规范',
publishDate: '2022-11-05',
},
];
const PolicyPage: React.FC = () => {
const [policyData, setPolicyData] = useState<any[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
// 模拟获取政策法规数据
useEffect(() => {
// 实际项目中应该通过API获取数据
setTimeout(() => {
setPolicyData(mockPolicyData);
setPagination((prevPagination) => ({
...prevPagination,
total: mockPolicyData.length,
}));
setLoading(false);
}, 500);
}, []);
// 处理表格分页变化
const handleTableChange = (newPagination: any) => {
setPagination(newPagination);
};
// 处理点击政策标题
const handlePolicyClick = (id: string) => {
history.push(`/policy/policyInfo?id=${id}`);
};
// 定义表格列
const columns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
render: (_: any, __: any, index: number) => {
return (pagination.current - 1) * pagination.pageSize + index + 1;
},
},
{
title: '标题',
dataIndex: 'title',
key: 'title',
render: (text: string, record: any) => (
<a onClick={() => handlePolicyClick(record.id)} className={styles.policyTitle}>
{text}
</a>
),
},
{
title: '发布时间',
dataIndex: 'publishDate',
key: 'publishDate',
width: 150,
align: 'center' as 'center',
},
];
return (
<div className={styles.policyContainer}>
<div className={styles.policyTable}>
<Table
columns={columns}
dataSource={policyData}
rowKey="id"
pagination={{
...pagination,
showTotal: (total) => `${total} 条记录`,
showSizeChanger: true,
showQuickJumper: true,
}}
loading={loading}
onChange={handleTableChange}
bordered
/>
</div>
</div>
);
};
export default PolicyPage;

View File

@ -1,107 +0,0 @@
@import '~antd/es/style/themes/default.less';
.policyInfoContainer {
padding: 24px;
background-color: #fff;
min-height: calc(100vh - 200px);
}
.policyInfoHeader {
margin-bottom: 24px;
}
.backButton {
color: rgb(0, 79, 142);
padding: 0;
&:hover {
color: lighten(rgb(0, 79, 142), 10%);
}
}
.policyInfoContent {
width: 100%;
max-width: 1000px;
margin: 0 auto;
}
.titleContainer {
text-align: center;
margin-bottom: 24px;
}
.title {
color: rgb(0, 79, 142);
font-weight: 500;
}
.metaInfo {
display: flex;
justify-content: space-between;
margin-bottom: 24px;
color: #999;
.metaLeft, .metaRight {
flex: 1;
}
.metaRight {
text-align: right;
}
}
.divider {
margin: 16px 0;
border-top: 1px solid #e8e8e8;
}
.contentBody {
font-size: 16px;
line-height: 1.8;
p {
margin-bottom: 16px;
}
strong {
font-weight: 500;
color: rgb(0, 79, 142);
}
}
.attachmentsSection {
margin-top: 40px;
}
.attachmentsTitle {
font-size: 18px;
font-weight: 500;
color: rgb(0, 79, 142);
margin-bottom: 16px;
}
.attachmentsList {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.attachmentButton {
margin-bottom: 12px;
}
.loadingContainer {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
.notFoundContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 400px;
gap: 24px;
}

View File

@ -1,293 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from 'umi';
import { Typography, Button, Space, Divider, Row, Col, Spin, message } from 'antd';
import { DownloadOutlined, ArrowLeftOutlined, FilePdfOutlined, FileWordOutlined, FileExcelOutlined } from '@ant-design/icons';
import styles from './policyInfo.less';
const { Title, Text } = Typography;
interface PolicyDetail {
id: string;
title: string;
publishDate: string;
publisher: string;
content: string;
attachments: {
name: string;
url: string;
type: string;
}[];
}
interface PolicyDetails {
[key: string]: PolicyDetail;
}
// 模拟政策法规详情数据
const mockPolicyDetails: PolicyDetails = {
'1': {
id: '1',
title: '关于进一步规范招标采购活动的管理办法',
publishDate: '2023-08-15',
publisher: '中远海运集团采购管理部',
content: `
<p><strong>第一章 总则</strong></p>
<p>第一条 为进一步规范集团招标采购活动,提高采购效率,降低采购成本,防范采购风险,根据《中华人民共和国招标投标法》《中华人民共和国招标投标法实施条例》等法律法规,结合集团实际情况,制定本办法。</p>
<p>第二条 本办法适用于集团总部及各下属单位的招标采购活动。</p>
<p>第三条 招标采购活动应当遵循公开、公平、公正和诚实信用的原则。</p>
<p><strong>第二章 招标采购组织机构及职责</strong></p>
<p>第四条 集团设立采购管理委员会,负责审议集团采购管理制度、采购策略、重大采购事项等。</p>
<p>第五条 集团采购管理部是集团采购管理的职能部门,负责组织实施集团采购管理工作。</p>
<p>第六条 各单位应当设立采购管理部门,负责本单位采购管理工作。</p>
<p><strong>第三章 招标采购方式</strong></p>
<p>第七条 招标采购方式包括公开招标、邀请招标、竞争性谈判、竞争性磋商、询价采购、单一来源采购等。</p>
<p>第八条 采购金额达到下列标准之一的,应当采用公开招标方式:</p>
<p>货物类项目采购金额在200万元以上</p>
<p>工程类项目采购金额在400万元以上</p>
<p>服务类项目采购金额在100万元以上。</p>
<p>第九条 符合下列情形之一的,可以采用邀请招标方式:</p>
<p>(一)技术复杂、有特殊要求或者受自然环境限制,只有少量供应商可供选择;</p>
<p>(二)采用公开招标方式的费用占采购项目总价值的比例过大。</p>
<p><strong>第四章 招标采购程序</strong></p>
<p>第十条 招标采购程序包括:采购需求提出、采购方式确定、采购文件编制、采购公告发布、投标文件接收与开标、评标、定标、合同签订等环节。</p>
<p>第十一条 采购需求部门应当提出书面采购申请,明确采购需求、技术规格、数量、交货期等要求。</p>
<p>第十二条 采购管理部门根据采购金额、采购内容等因素,确定采购方式。</p>
<p>第十三条 采购文件应当包括采购项目的技术要求、商务条件、评标方法与标准、合同主要条款等内容。</p>
<p><strong>第五章 评标与定标</strong></p>
<p>第十四条 评标委员会由采购人代表和评审专家组成成员人数为5人以上单数其中评审专家不得少于成员总数的三分之二。</p>
<p>第十五条 评标方法可以采用最低评标价法、综合评分法等。</p>
<p>第十六条 评标委员会按照招标文件确定的评标方法和标准,对投标文件进行评审和比较,提出书面评标报告。</p>
<p><strong>第六章 合同管理</strong></p>
<p>第十七条 采购合同应当明确双方的权利义务、技术要求、合同价格、履行期限、履行地点和方式、验收标准和方法、违约责任等内容。</p>
<p>第十八条 采购合同签订后,采购人应当加强对合同履行情况的监督检查,确保合同全面履行。</p>
<p><strong>第七章 供应商管理</strong></p>
<p>第十九条 集团应当建立供应商库,对供应商实行动态管理。</p>
<p>第二十条 对供应商的考核评价应当包括供应商资质、产品质量、价格水平、交货期、售后服务等方面。</p>
<p><strong>第八章 监督检查</strong></p>
<p>第二十一条 集团审计部门应当对招标采购活动进行监督检查。</p>
<p>第二十二条 任何单位和个人不得以任何方式干涉招标采购活动。</p>
<p><strong>第九章 附则</strong></p>
<p>第二十三条 本办法由集团采购管理部负责解释。</p>
<p>第二十四条 本办法自发布之日起施行。</p>
`,
attachments: [
{ name: '招标采购管理办法.pdf', url: '/files/招标采购管理办法.pdf', type: 'pdf' },
{ name: '招标采购流程图.docx', url: '/files/招标采购流程图.docx', type: 'word' }
]
},
'2': {
id: '2',
title: '中远海运集团供应商管理实施细则(2023年修订版)',
publishDate: '2023-07-20',
publisher: '中远海运集团采购管理部',
content: `
<p><strong>第一章 总则</strong></p>
<p>第一条 为规范集团供应商管理工作,建立健全供应商评价体系,提高采购质量和效率,根据《中远海运集团招标采购管理办法》,制定本细则。</p>
<p>第二条 本细则适用于集团总部及各下属单位的供应商管理工作。</p>
<p><strong>第二章 供应商分类</strong></p>
<p>第三条 供应商按照提供的产品或服务类型,分为货物类供应商、工程类供应商和服务类供应商。</p>
<p>第四条 供应商按照合作关系,分为战略供应商、核心供应商和一般供应商。</p>
<p><strong>第三章 供应商准入</strong></p>
<p>第五条 供应商准入应当符合以下基本条件:</p>
<p>(一)具有独立承担民事责任的能力;</p>
<p>(二)具有良好的商业信誉和健全的财务会计制度;</p>
<p>(三)具有履行合同所必需的设备和专业技术能力;</p>
<p>(四)有依法缴纳税收和社会保障资金的良好记录;</p>
<p>(五)参加采购活动前三年内,在经营活动中没有重大违法记录;</p>
<p>(六)法律、行政法规规定的其他条件。</p>
<p><strong>第四章 供应商评价</strong></p>
<p>第六条 供应商评价指标包括企业资质、财务状况、技术能力、质量保证、价格水平、交货期、售后服务、社会责任等。</p>
<p>第七条 供应商评价采用百分制评价结果分为A、B、C、D四个等级</p>
<p>A级90分以上优秀供应商</p>
<p>B级75-89分良好供应商</p>
<p>C级60-74分合格供应商</p>
<p>D级60分以下不合格供应商。</p>
<p><strong>第五章 供应商退出</strong></p>
<p>第八条 供应商有下列情形之一的,应当从供应商库中退出:</p>
<p>连续两次评价结果为D级</p>
<p>(二)提供虚假材料谋取中标、成交的;</p>
<p>(三)与采购人、其他供应商恶意串通的;</p>
<p>(四)向采购人、评标委员会成员行贿或者提供其他不正当利益的;</p>
<p>(五)在采购活动中有其他违法违规行为的。</p>
<p><strong>第六章 附则</strong></p>
<p>第九条 本细则由集团采购管理部负责解释。</p>
<p>第十条 本细则自发布之日起施行。</p>
`,
attachments: [
{ name: '供应商管理实施细则.pdf', url: '/files/供应商管理实施细则.pdf', type: 'pdf' },
{ name: '供应商评价表.xlsx', url: '/files/供应商评价表.xlsx', type: 'excel' }
]
},
'3': {
id: '3',
title: '关于加强采购合同履约管理的通知',
publishDate: '2023-06-10',
publisher: '中远海运集团采购管理部',
content: `
<p>各单位:</p>
<p>为进一步加强采购合同履约管理,防范合同风险,提高采购质量,经集团研究决定,现就加强采购合同履约管理有关事项通知如下:</p>
<p><strong>一、高度重视采购合同履约管理</strong></p>
<p>各单位要充分认识加强采购合同履约管理的重要性,将合同履约管理作为采购管理的重要环节,纳入采购全过程管理。要明确合同履约管理责任,建立健全合同履约管理制度,规范合同履约管理流程。</p>
<p><strong>二、严格合同签订管理</strong></p>
<p>各单位要严格执行合同审查制度,重点审查合同主体、标的、数量、质量、价款、履行期限、履行地点和方式、违约责任等内容。要加强合同条款设计,合理设置付款条件、验收标准、质保期、违约责任等条款,防范合同风险。</p>
<p><strong>三、加强合同履行监督</strong></p>
<p>各单位要建立合同履行台账,对合同履行情况进行动态跟踪。要加强对供应商履约情况的监督检查,重点关注产品质量、交货期、服务质量等方面。对发现的问题,要及时督促供应商整改。</p>
<p><strong>四、规范合同验收程序</strong></p>
<p>各单位要严格执行合同验收制度,按照合同约定的验收标准和方法进行验收。验收人员应当如实记录验收情况,出具验收报告。验收不合格的,要及时通知供应商整改或者退货。</p>
<p><strong>五、加强合同档案管理</strong></p>
<p>各单位要建立健全合同档案管理制度,对合同文本、履行记录、验收报告、付款凭证等资料进行归档管理。要加强合同信息化管理,提高合同管理效率。</p>
<p><strong>六、严肃合同履约考核</strong></p>
<p>各单位要将合同履约情况纳入供应商评价体系,对供应商履约情况进行考核评价。对履约情况良好的供应商,可以在同等条件下优先考虑;对履约情况不良的供应商,要依据合同约定追究违约责任,并在供应商评价中予以扣分或者降级。</p>
<p>特此通知。</p>
`,
attachments: [
{ name: '合同履约管理通知.pdf', url: '/files/合同履约管理通知.pdf', type: 'pdf' }
]
}
};
const PolicyInfo: React.FC = () => {
const location = useLocation();
const id = new URLSearchParams(location.search).get("id");
const [policyDetail, setPolicyDetail] = useState<PolicyDetail | null>(null);
const [loading, setLoading] = useState<boolean>(true);
// 模拟获取政策法规详情数据
useEffect(() => {
// 实际项目中应该通过API获取数据
setTimeout(() => {
if (id && mockPolicyDetails[id]) {
setPolicyDetail(mockPolicyDetails[id]);
} else {
// 处理ID不存在的情况
message.error('政策法规不存在');
}
setLoading(false);
}, 500);
}, [id]);
// 处理返回列表
const handleBack = () => {
window.history.back();
};
// 处理下载附件
const handleDownload = (url: string, name: string) => {
message.success(`开始下载: ${name}`);
console.log('下载附件:', url, name);
// 实际项目中应该调用下载API
// window.open(url);
};
// 根据文件类型获取图标
const getFileIcon = (type: string) => {
switch (type) {
case 'pdf':
return <FilePdfOutlined />;
case 'word':
return <FileWordOutlined />;
case 'excel':
return <FileExcelOutlined />;
default:
return <DownloadOutlined />;
}
};
if (loading) {
return (
<div className={styles.loadingContainer}>
<Spin size="large" />
</div>
);
}
if (!policyDetail) {
return (
<div className={styles.notFoundContainer}>
<Title level={4}></Title>
<Button type="primary" onClick={handleBack}></Button>
</div>
);
}
return (
<div className={styles.policyInfoContainer}>
<div className={styles.policyInfoHeader}>
<Button
type="link"
icon={<ArrowLeftOutlined />}
onClick={handleBack}
className={styles.backButton}
>
</Button>
</div>
<div className={styles.policyInfoContent}>
<div className={styles.titleContainer}>
<Title level={2} className={styles.title}>
{policyDetail.title}
</Title>
</div>
<div className={styles.metaInfo}>
<div className={styles.metaLeft}>
<Text type="secondary">: {policyDetail.publishDate}</Text>
</div>
<div className={styles.metaRight}>
<Text type="secondary">: {policyDetail.publisher}</Text>
</div>
</div>
<Divider className={styles.divider} />
<div className={styles.contentBody}>
<div dangerouslySetInnerHTML={{ __html: policyDetail.content }} />
</div>
{policyDetail.attachments && policyDetail.attachments.length > 0 && (
<div className={styles.attachmentsSection}>
<Divider className={styles.divider} />
<div className={styles.attachmentsTitle}></div>
<div className={styles.attachmentsList}>
{policyDetail.attachments.map((attachment: any, index: number) => (
<Button
key={index}
type="primary"
ghost
icon={getFileIcon(attachment.type)}
onClick={() => handleDownload(attachment.url, attachment.name)}
className={styles.attachmentButton}
>
{attachment.name}
</Button>
))}
</div>
</div>
)}
</div>
</div>
);
};
export default PolicyInfo;

View File

View File

@ -0,0 +1,527 @@
import React, { useState, useEffect } from 'react';
import { useIntl } from 'umi';
import { Button, Table, Space, Modal, message, Input, Select, Form, Tooltip, Switch, Tabs, Tag, TablePaginationConfig } from 'antd';
import {
PlusOutlined,
DeleteOutlined,
ExclamationCircleOutlined,
SearchOutlined,
} from '@ant-design/icons';
import { addPolicy, getPolicyPage, deletePolicy, publishPolicy, unpublishPolicy, getPolicyDetail, updatePolicy } from '@/servers/api/policy';
const { Option } = Select;
const { TabPane } = Tabs;
interface PolicyType {
key: string;
id: string;
title: string;
createTime: string;
status: string;
createBy: string;
isTop: string;
}
interface SearchParams {
title?: string;
status?: string;
}
const PolicyManage: React.FC = () => {
const intl = useIntl();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm();
const [modalVisible, setModalVisible] = useState<boolean>(false);
const [isEdit, setIsEdit] = useState<boolean>(false);
const [currentId, setCurrentId] = useState<string>('');
const [modalForm] = Form.useForm();
const [policyData, setPolicyData] = useState<PolicyType[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<SearchParams>({});
// 获取政策列表
const fetchPolicyList = async (current: number = 1, pageSize: number = 10) => {
setLoading(true);
try {
// 构造请求参数
const params: API.PageRequest = {
basePageRequest: {
pageNo: current,
pageSize: pageSize,
},
...searchParams,
};
const response = await getPolicyPage(params);
if (response && response.success) {
const { records, total, current: currentPage, size } = response.data;
// 转换数据
const formattedData = records.map((item: API.PolicyRecord) => ({
key: item.id,
id: item.id,
title: item.title,
createTime: item.createTime,
status: item.status,
createBy: item.createBy,
isTop: item.isTop,
}));
setPolicyData(formattedData);
setPagination({
...pagination,
current: currentPage,
pageSize: size,
total: total,
});
} else {
message.error(response.message || '获取数据失败');
}
} catch (error) {
console.error('获取政策列表失败:', error);
message.error('获取数据失败');
} finally {
setLoading(false);
}
};
// 首次加载获取数据
useEffect(() => {
fetchPolicyList(pagination.current as number, pagination.pageSize as number);
}, []);
// 处理编辑
const handleEdit = async (record: PolicyType) => {
setIsEdit(true);
setCurrentId(record.id);
setModalVisible(true);
try {
const response = await getPolicyDetail(record.id);
if (response && response.success) {
const detail = response.data;
modalForm.setFieldsValue({
isTop: detail.isTop === '1',
titleZh: detail.title,
titleEn: detail.titleEn,
contentZh: detail.content,
contentEn: detail.contentEn,
});
} else {
message.error(response.message || '获取详情失败');
}
} catch (error) {
console.error('获取政策详情失败:', error);
message.error('获取详情失败');
}
};
// 处理删除
const showDeleteConfirm = (record: PolicyType) => {
Modal.confirm({
title: '确定要删除该政策吗?',
icon: <ExclamationCircleOutlined />,
content: `标题: ${record.title}`,
okText: '确定',
okType: 'danger',
cancelText: '取消',
maskClosable: false,
onOk: async () => {
try {
const response = await deletePolicy(record.id);
if (response && response.success) {
message.success('删除成功');
fetchPolicyList(pagination.current as number, pagination.pageSize as number); // 重新加载数据
} else {
message.error(response.message || '删除失败');
}
} catch (error) {
console.error('删除政策失败:', error);
message.error('删除失败');
}
},
});
};
// 处理发布/下架
const handlePublishStatus = async (record: PolicyType) => {
// 状态: 0.草稿、1.上架、2.下架
const isPublished = record.status === '1';
const actionText = isPublished ? '下架' : '发布';
try {
let response;
if (isPublished) {
response = await unpublishPolicy(record.id);
} else {
response = await publishPolicy(record.id);
}
if (response && response.success) {
message.success(`${actionText}成功`);
fetchPolicyList(pagination.current as number, pagination.pageSize as number); // 重新加载数据
} else {
message.error(response.message || `${actionText}失败`);
}
} catch (error) {
console.error(`${actionText}失败:`, error);
message.error(`${actionText}失败`);
}
};
// 获取状态标签
const getStatusTag = (status: string) => {
switch (status) {
case '0':
return <Tag color="default">稿</Tag>;
case '1':
return <Tag color="green"></Tag>;
case '2':
return <Tag color="orange"></Tag>;
default:
return <Tag color="default"></Tag>;
}
};
// 处理表格分页变化
const handleTableChange = (newPagination: TablePaginationConfig) => {
fetchPolicyList(newPagination.current as number, newPagination.pageSize as number);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 80,
},
{
title: '标题',
dataIndex: 'title',
key: 'title',
width: 300,
ellipsis: {
showTitle: false,
},
render: (title: string, record: PolicyType) => (
<Tooltip placement="topLeft" title={title}>
<span>
{record.isTop === '1' && <Tag color="red" style={{ marginRight: 8 }}></Tag>}
{title}
</span>
</Tooltip>
),
},
{
title: '日期',
dataIndex: 'createTime',
key: 'createTime',
width: 160,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status: string) => getStatusTag(status),
},
{
title: '发布人',
dataIndex: 'createBy',
key: 'createBy',
width: 120,
},
{
title: '操作',
key: 'action',
width: 230,
align: 'center' as const,
render: (_: unknown, record: PolicyType) => (
<Space size="middle" className="action-space">
{record.status === '1' ? (
<Button type="link" onClick={() => handlePublishStatus(record)}>
</Button>
) : (
<Button type="link" onClick={() => handlePublishStatus(record)}>
</Button>
)}
<Button type="link" onClick={() => handleEdit(record)}>
</Button>
<Button
type="link"
danger
onClick={() => showDeleteConfirm(record)}
>
</Button>
</Space>
),
},
];
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
setSelectedRowKeys(newSelectedRowKeys);
};
const rowSelection = {
selectedRowKeys,
onChange: onSelectChange,
};
const hasSelected = selectedRowKeys.length > 0;
// 处理添加
const handleAdd = () => {
setIsEdit(false);
setCurrentId('');
modalForm.resetFields();
setModalVisible(true);
};
// 处理批量删除
const handleBatchDelete = () => {
Modal.confirm({
title: '确定要删除选中的政策吗?',
icon: <ExclamationCircleOutlined />,
content: '删除后无法恢复',
okText: '确定',
okType: 'danger',
cancelText: '取消',
maskClosable: false,
onOk: async () => {
setLoading(true);
try {
// 实际项目中应该使用批量删除API
const deletePromises = selectedRowKeys.map(id => deletePolicy(id.toString()));
const results = await Promise.all(deletePromises);
// 检查是否所有操作都成功
const allSuccess = results.every(res => res.success);
if (allSuccess) {
setSelectedRowKeys([]);
message.success('删除成功');
fetchPolicyList(pagination.current as number, pagination.pageSize as number); // 重新加载数据
} else {
message.error('部分删除失败,请刷新后重试');
}
} catch (error) {
console.error('批量删除失败:', error);
message.error('批量删除失败');
} finally {
setLoading(false);
}
},
});
};
// 处理搜索
const handleSearch = (values: SearchParams) => {
setSearchParams(values);
fetchPolicyList(1, pagination.pageSize as number); // 搜索时重置为第一页
};
// 处理模态框提交
const handleModalSubmit = () => {
modalForm.validateFields().then(async values => {
try {
// 转换表单数据为API需要的格式
const requestData: API.PolicyRequest = {
title: values.titleZh,
titleEn: values.titleEn || '',
content: values.contentZh,
contentEn: values.contentEn || '',
isTop: values.isTop ? '1' : '0',
settingEn: values.titleEn && values.contentEn ? 1 : 0,
};
let response;
if (isEdit) {
response = await updatePolicy(currentId, requestData);
} else {
response = await addPolicy(requestData);
}
if (response && response.success) {
message.success(isEdit ? '更新成功' : '添加成功');
setModalVisible(false);
modalForm.resetFields();
fetchPolicyList(1, pagination.pageSize as number); // 操作成功后返回第一页
} else {
message.error(response.message || (isEdit ? '更新失败' : '添加失败'));
}
} catch (error) {
console.error(isEdit ? '更新政策失败:' : '添加政策失败:', error);
message.error(isEdit ? '更新失败' : '添加失败');
}
}).catch(info => {
console.log('校验失败:', info);
});
};
// 处理模态框取消
const handleModalCancel = () => {
setModalVisible(false);
modalForm.resetFields();
};
return (
<div className="policy-manage-container common-container">
<div className="filter-action-row">
<Form
form={form}
name="search"
onFinish={handleSearch}
layout="inline"
className="filter-form"
>
<Form.Item name="title" label="标题">
<Input placeholder="请输入标题关键词" allowClear />
</Form.Item>
<Form.Item name="status" label="状态">
<Select placeholder="请选择状态" allowClear>
<Option value="0">稿</Option>
<Option value="1"></Option>
<Option value="2"></Option>
</Select>
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" htmlType="submit" icon={<SearchOutlined />}>
</Button>
<Button
type="primary"
danger
icon={<DeleteOutlined />}
onClick={() => {
form.resetFields();
setSearchParams({});
fetchPolicyList(1, pagination.pageSize as number);
}}
>
</Button>
</Form.Item>
</Form>
<div className="right-buttons">
<Button
type="primary"
ghost
icon={<PlusOutlined />}
onClick={handleAdd}
>
</Button>
<Button
danger
icon={<DeleteOutlined />}
onClick={handleBatchDelete}
disabled={!hasSelected}
loading={loading}
>
</Button>
{hasSelected && (
<span className="selected-count">
{selectedRowKeys.length}
</span>
)}
</div>
</div>
<div className="content-area">
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={policyData}
pagination={pagination}
loading={loading}
onChange={handleTableChange}
/>
</div>
{/* 新增/编辑模态框 */}
<Modal
title={isEdit ? "编辑政策法规" : "新增政策法规"}
visible={modalVisible}
onOk={handleModalSubmit}
onCancel={handleModalCancel}
width={800}
maskClosable={false}
destroyOnClose
>
<Form
form={modalForm}
layout="vertical"
name="policyForm"
preserve={false}
>
<Form.Item name="isTop" label="是否置顶" valuePropName="checked">
<Switch />
</Form.Item>
<Tabs defaultActiveKey="zh">
<TabPane tab="中文版" key="zh">
<Form.Item
name="titleZh"
label="标题(中文)"
rules={[{ required: true, message: '请输入中文标题' }]}
>
<Input placeholder="请输入中文标题" />
</Form.Item>
<Form.Item
name="contentZh"
label="正文内容(中文)"
rules={[{ required: true, message: '请输入中文正文' }]}
>
<Input.TextArea
placeholder="请输入中文正文"
rows={10}
style={{ minHeight: '200px' }}
/>
{/* 实际项目中应替换为富文本编辑器组件 */}
</Form.Item>
</TabPane>
<TabPane tab="英文版" key="en">
<Form.Item
name="titleEn"
label="标题(英文)"
rules={[{ required: false, message: 'Please enter title in English' }]}
>
<Input placeholder="Please enter title in English" />
</Form.Item>
<Form.Item
name="contentEn"
label="正文内容(英文)"
rules={[{ required: false, message: 'Please enter content in English' }]}
>
<Input.TextArea
placeholder="Please enter content in English"
rows={10}
style={{ minHeight: '200px' }}
/>
{/* 实际项目中应替换为富文本编辑器组件 */}
</Form.Item>
</TabPane>
</Tabs>
</Form>
</Modal>
</div>
);
};
export default PolicyManage;

View File

@ -0,0 +1,191 @@
import React, { useState } from 'react';
import { useIntl } from 'umi';
import { Button, Table, Space, Modal, message } from 'antd';
import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
interface UserType {
key: string;
username: string;
email: string;
role: string;
lastLoginTime: string;
status: 'active' | 'inactive';
}
const UserManage: React.FC = () => {
const intl = useIntl();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [loading, setLoading] = useState<boolean>(false);
// 模拟数据
const data: UserType[] = [
{
key: '1',
username: 'admin',
email: 'admin@example.com',
role: '超级管理员',
lastLoginTime: '2023-06-01 10:00:00',
status: 'active',
},
{
key: '2',
username: 'editor',
email: 'editor@example.com',
role: '内容编辑',
lastLoginTime: '2023-05-28 15:30:00',
status: 'active',
},
{
key: '3',
username: 'operator',
email: 'operator@example.com',
role: '运营人员',
lastLoginTime: '2023-05-25 09:15:00',
status: 'inactive',
},
];
// 处理编辑用户
const handleEdit = (record: UserType) => {
message.success(`编辑用户: ${record.username}`);
};
// 处理删除单个用户
const showDeleteConfirm = (record: UserType) => {
Modal.confirm({
title: '确定要删除该用户吗?',
icon: <ExclamationCircleOutlined />,
content: `用户名: ${record.username}`,
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk() {
// 在这里处理删除逻辑
message.success(`删除用户: ${record.username}`);
},
});
};
const columns = [
{
title: '用户名',
dataIndex: 'username',
key: 'username',
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
},
{
title: '所属角色',
dataIndex: 'role',
key: 'role',
},
{
title: '最后登录时间',
dataIndex: 'lastLoginTime',
key: 'lastLoginTime',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => (status === 'active' ? '启用' : '禁用'),
},
{
title: '操作',
key: 'action',
align: 'center' as const,
render: (_: any, record: UserType) => (
<Space size="middle" className="action-space">
<Button type="link" onClick={() => handleEdit(record)}>
</Button>
<Button type="link" onClick={() => showDeleteConfirm(record)}>
</Button>
</Space>
),
},
];
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
setSelectedRowKeys(newSelectedRowKeys);
};
const rowSelection = {
selectedRowKeys,
onChange: onSelectChange,
};
const hasSelected = selectedRowKeys.length > 0;
// 处理添加用户
const handleAdd = () => {
// 添加用户逻辑
message.success('点击了添加按钮');
};
// 处理批量删除
const handleBatchDelete = () => {
Modal.confirm({
title: '确定要删除选中的用户吗?',
icon: <ExclamationCircleOutlined />,
content: '删除后无法恢复',
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk() {
setLoading(true);
// 在这里处理批量删除逻辑
setTimeout(() => {
setSelectedRowKeys([]);
setLoading(false);
message.success('删除成功');
}, 1000);
},
});
};
return (
<div className="user-manage-container common-container">
<div className="filter-action-row">
<div className="right-buttons">
<Button
type="primary"
ghost
icon={<PlusOutlined />}
onClick={handleAdd}
>
</Button>
<Button
danger
icon={<DeleteOutlined />}
onClick={handleBatchDelete}
disabled={!hasSelected}
loading={loading}
>
</Button>
{hasSelected && (
<span className="selected-count">
{selectedRowKeys.length}
</span>
)}
</div>
</div>
<div className="content-area">
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={data}
pagination={{ pageSize: 10 }}
/>
</div>
</div>
);
};
export default UserManage;

64
src/servers/api/policy.ts Normal file
View File

@ -0,0 +1,64 @@
import request from '@/utils/request';
// 新增政策法规
export async function addPolicy(data: API.PolicyRequest): Promise<API.Response<string>> {
return request('/portals/regulations', {
method: 'POST',
data,
});
}
// 更新政策法规
export async function updatePolicy(id: string, data: API.PolicyRequest): Promise<API.Response<string>> {
return request(`/portals/regulations`, {
method: 'PUT',
data: {
...data,
id,
},
});
}
// 获取政策法规列表
export async function getPolicyList(params: Partial<API.PolicyRequest>): Promise<API.Response<API.PolicyRecord[]>> {
return request('/portals/regulations', {
method: 'GET',
params,
});
}
// 获取政策法规分页列表
export async function getPolicyPage(params: API.PageRequest): Promise<API.Response<API.PageResponse<API.PolicyRecord>>> {
return request('/portals/regulations/getPage', {
method: 'POST',
data: params,
});
}
// 获取政策法规详情
export async function getPolicyDetail(id: string): Promise<API.Response<API.PolicyRecord>> {
return request(`/portals/regulations/${id}`, {
method: 'GET',
});
}
// 删除政策法规
export async function deletePolicy(id: string): Promise<API.Response<string>> {
return request(`/portals/regulations/${id}`, {
method: 'DELETE',
});
}
// 发布(上架)政策法规
export async function publishPolicy(id: string): Promise<API.Response<string>> {
return request(`/portals/regulations/up/${id}`, {
method: 'POST',
});
}
// 下架政策法规
export async function unpublishPolicy(id: string): Promise<API.Response<string>> {
return request(`/portals/regulations/down/${id}`, {
method: 'POST',
});
}

View File

@ -6,6 +6,34 @@ declare namespace API {
data: T;
}
export type PolicyRequest = {
/**
* 内容
*/
content: string;
/**
* 内容_英文版
*/
contentEn: string;
/**
* 是否置顶(0.否、1.是)
*/
isTop: string;
/**
* 是否设置英文内容(0.否、1.是)
*/
settingEn: number;
/**
* 标题
*/
title: string;
/**
* 标题英文
*/
titleEn: string;
[property: string]: any;
}
export type RegisterRequest = {
coscoSupplierBank: CoscoSupplierBank[];
coscoSupplierBase: CoscoSupplierBase;
@ -112,4 +140,120 @@ declare namespace API {
optionName: string;
}[];
}[]
// 基础分页请求参数
export interface BasePageRequest {
pageNo: number;
pageSize: number;
[property: string]: unknown;
}
// 分页请求参数
export interface PageRequest {
basePageRequest: BasePageRequest;
title?: string;
status?: string;
[property: string]: unknown;
}
// 基础响应格式
export interface Response<T = unknown> {
code: number;
data: T;
message: string;
success: boolean;
[property: string]: unknown;
}
// 分页数据响应
export interface PageResponse<T> {
countId: null;
current: number;
hitCount: boolean;
maxLimit: null;
optimizeCountSql: boolean;
orders: string[];
pages: number;
records: T[];
searchCount: boolean;
size: number;
total: number;
[property: string]: unknown;
}
// 政策法规数据项
export interface PolicyRecord {
basePageRequest: null;
content: string;
contentEn: string;
createBy: string;
createDate: null;
createTime: string;
deleteFlag: null;
delFlag: string;
id: string;
isTop: string;
lastUpdateTime: null;
publishBy: string | null;
publishTime: string | null;
settingEn: string;
status: string;
tenantId: string | null;
tenantName: string | null;
title: string;
titleEn: string | null;
updateBy: string | null;
updateDate: string | null;
updateTime: string;
}
// 政策法规请求参数
export interface PolicyRequest {
id?: string;
title: string;
titleEn: string;
content: string;
contentEn: string;
isTop: string;
settingEn: number;
}
// 下载中心数据项
export interface DownloadRecord {
id: string;
name: string;
category: string;
keyword: string;
createTime: string;
createBy: string;
status: string; // 状态0-草稿1-已发布
thumbnail?: string; // 缩略图URL
fileUrl?: string; // 文件URL
}
// 下载中心请求参数
export interface DownloadRequest {
id?: string;
name: string;
category: string;
keyword: string;
fileId?: string;
thumbnailId?: string;
}
// 下载中心查询参数
export interface DownloadSearchParams {
name?: string;
category?: string;
status?: string;
}
// 文件上传响应
export interface UploadResponse {
id: string;
fileName: string;
fileSize: number;
fileType: string;
fileUrl: string;
}
}

View File

@ -0,0 +1,73 @@
// 通用容器样式
.common-container {
background: #fff;
padding: 15px;
// 按钮组样式
.action-buttons {
display: flex;
align-items: center;
margin-bottom: 15px;
}
// 内容区样式
.content-area {
min-height: 500px;
}
// 表格样式优化
.ant-table-wrapper {
.ant-table-thead > tr > th {
background-color: #fafafa;
font-weight: 500;
}
}
// 分页样式
.ant-pagination {
margin-top: 16px;
text-align: right;
}
// 按钮间距
.action-space {
button {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
}
}
// 筛选条件和操作按钮区域样式
.filter-action-row {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
.filter-form {
flex: 1;
.filter-btns {
button {
margin-right: 8px;
}
}
}
.right-buttons {
display: flex;
align-items: center;
button {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
.selected-count {
margin-left: 8px;
}
}
}

View File

@ -52,11 +52,12 @@ const codeMessage = {
* 配置request请求时的默认参数
*/
const request = extend({
prefix: '/api',
// errorHandler, // 默认错误处理
credentials: 'include' // 默认请求是否带上cookie
});
// request拦截器, 改变url 或 options.
request.interceptors.request.use(async (url, options) => {
request.interceptors.request.use((url, options) => {
if (
options.method === 'post' ||
options.method === 'put' ||
@ -72,7 +73,7 @@ request.interceptors.request.use(async (url, options) => {
// options.headers = headers;
}
return {
url,
url: url,
options: { ...options },
};
});