修改注册银行账户 地址以及 融合样式改动
@ -66,7 +66,7 @@
|
||||
"@umijs/route-utils": "1.0.37",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-react": "^1.0.6",
|
||||
"antd": "4.15.1",
|
||||
"antd": "latest-4",
|
||||
"array-move": "3.0.1",
|
||||
"axios": "0.21.1",
|
||||
"classnames": "2.3.1",
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 13 KiB |
BIN
src/assets/HarmonyOS_Sans_SC_Regular.ttf
Normal file
1
src/assets/arrow-left.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="14" height="14" viewBox="0 0 14 14"><defs><clipPath id="master_svg0_51_7400/51_7034/5_5361"><rect x="0" y="0" width="14" height="14" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_51_7400/51_7034/5_5361)"><g><path d="M8.326062556686402,2.0370083067779543C8.326062556686402,2.0370083067779543,3.8005605566864014,6.6990103067779545,3.8005605566864014,6.6990103067779545C3.7186017566864016,6.780970306777954,3.6776225566864014,6.878970306777954,3.6776225566864014,6.993010306777954C3.6776225566864014,7.107050306777954,3.7186017566864016,7.209570306777954,3.8005605566864014,7.300570306777954C3.8005605566864014,7.300570306777954,8.326062556686402,11.962570306777954,8.326062556686402,11.962570306777954C8.4080225566864,12.035470306777954,8.503682556686401,12.071970306777954,8.613062556686401,12.071970306777954C8.722432556686401,12.071970306777954,8.818102556686402,12.030970306777954,8.9000625566864,11.948970306777953C8.982022556686402,11.867050306777955,9.0230025566864,11.771380306777955,9.0230025566864,11.662010306777955C9.0230025566864,11.552630306777955,8.982022556686402,11.456970306777954,8.9000625566864,11.375010306777954C8.9000625566864,11.375010306777954,4.634435556686402,7.000010306777954,4.634435556686402,7.000010306777954C4.634435556686402,7.000010306777954,8.9000625566864,2.6250073067779542,8.9000625566864,2.6250073067779542C8.982022556686402,2.543049306777954,9.0230025566864,2.4473823067779543,9.0230025566864,2.3380073067779543C9.0230025566864,2.2286323067779543,8.982022556686402,2.1329663067779543,8.9000625566864,2.051008306777954C8.818102556686402,1.969049506777954,8.722432556686401,1.928070306777954,8.613062556686401,1.928070306777954C8.503682556686401,1.928070306777954,8.4080225566864,1.9645286067779542,8.326062556686402,2.037445306777954C8.326062556686402,2.037445306777954,8.326062556686402,2.0370083067779543,8.326062556686402,2.0370083067779543C8.326062556686402,2.0370083067779543,8.326062556686402,2.0370083067779543,8.326062556686402,2.0370083067779543Z" fill="#606266" fill-opacity="1"/></g></g></svg>
|
After Width: | Height: | Size: 2.1 KiB |
1
src/assets/arrow-right.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="14" height="14" viewBox="0 0 14 14"><defs><clipPath id="master_svg0_51_7400/51_7030/5_5361"><rect x="0" y="0" width="14" height="14" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_51_7400/51_7030/5_5361)"><g><path d="M4.6619995,2.0369868065948484C4.5800417,2.1282788065948486,4.5390625,2.2286118065948486,4.5390625,2.3379868065948486C4.5390625,2.4473618065948486,4.5800417,2.5430288065948488,4.6619995,2.6249868065948485C4.6619995,2.6249868065948485,8.9276225,6.999984806594848,8.9276225,6.999984806594848C8.9276225,6.999984806594848,4.6619995,11.37498480659485,4.6619995,11.37498480659485C4.5800417,11.456974806594848,4.5390625,11.552614806594848,4.5390625,11.661984806594848C4.5390625,11.771364806594848,4.5800417,11.86700480659485,4.6619995,11.94897480659485C4.7439585,12.030974806594848,4.8396255,12.071874806594849,4.9490005,12.071874806594849C5.0583755,12.071874806594849,5.1540425,12.035474806594848,5.2360005,11.962574806594848C5.2360005,11.962574806594848,9.761502499999999,7.300544806594849,9.761502499999999,7.300544806594849C9.843462500000001,7.218584806594849,9.884442499999999,7.118264806594849,9.884442499999999,6.999544806594849C9.884442499999999,6.880834806594849,9.843462500000001,6.780514806594849,9.761502499999999,6.698554806594848C9.761502499999999,6.698554806594848,5.2360005,2.0365498065948486,5.2360005,2.0365498065948486C5.1540425,1.9636332065948485,5.0583755,1.9271748065948486,4.9490005,1.9271748065948486C4.8396255,1.9271748065948486,4.7439585,1.9636332065948485,4.6619995,2.0365498065948486C4.6619995,2.0365498065948486,4.6619995,2.0369868065948484,4.6619995,2.0369868065948484C4.6619995,2.0369868065948484,4.6619995,2.0369868065948484,4.6619995,2.0369868065948484Z" fill="#606266" fill-opacity="1"/></g></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/divider.png
Normal file
After Width: | Height: | Size: 239 B |
1
src/assets/home.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="12" height="13.843500137329102" viewBox="0 0 12 13.843500137329102"><g><path d="M1,5.3125C1,5.3125,1,12.844,1,12.844C1,12.844,11,12.844,11,12.844C11,12.844,11,5.3125,11,5.3125C11,5.3125,6,1.1405,6,1.1405C6,1.1405,1,5.3125,1,5.3125C1,5.3125,1,5.3125,1,5.3125ZM0.1875,4.6875C0.1875,4.6875,5.6875,0.1095,5.6875,0.1095C5.78117,0.0365001,5.88533,0,6,0C6.11467,0,6.21883,0.0365001,6.3125,0.1095C6.3125,0.1095,11.8125,4.6875,11.8125,4.6875C11.9375,4.79183,12,4.922,12,5.078C12,5.078,12,13.3435,12,13.3435C12,13.4895,11.9532,13.6093,11.8595,13.703C11.7658,13.7967,11.646,13.8435,11.5,13.8435C11.5,13.8435,0.5,13.8435,0.5,13.8435C0.354,13.8435,0.234167,13.7967,0.1405,13.703C0.0468334,13.6093,-2.22045e-16,13.4895,0,13.3435C0,13.3435,0,5.078,0,5.078C0,4.92167,0.0625,4.7915,0.1875,4.6875C0.1875,4.6875,0.1875,4.6875,0.1875,4.6875Z" fill="#08080A" fill-opacity="0.9599999785423279"/></g></svg>
|
After Width: | Height: | Size: 999 B |
1
src/assets/logo.svg
Normal file
After Width: | Height: | Size: 13 KiB |
1
src/assets/shutdown.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="12.989924430847168" height="14.000499725341797" viewBox="0 0 12.989924430847168 14.000499725341797"><g><path d="M3.99396,1.5C3.99396,1.5,3.99396,2.594,3.99396,2.594C2.84796,3.20867,2.0173,4.0785,1.50196,5.2035C0.986629,6.3285,0.869462,7.52117,1.15046,8.7815C1.47346,10.0418,2.1193,11.0548,3.08796,11.8205C4.05663,12.5862,5.19213,12.9793,6.49446,13C7.79646,12.979,8.93196,12.5858,9.90096,11.8205C10.87,11.0552,11.5158,10.0422,11.8385,8.7815C12.1198,7.52117,12.0026,6.3285,11.487,5.2035C10.9713,4.0785,10.1406,3.20867,8.99496,2.594C8.99496,2.594,8.99496,1.5,8.99496,1.5C10.443,2.13533,11.5211,3.11717,12.2295,4.4455C12.9378,5.77383,13.1513,7.214,12.87,8.766C12.5263,10.318,11.7736,11.5707,10.612,12.524C9.4503,13.4773,8.07796,13.9695,6.49496,14.0005C4.91163,13.9692,3.53663,13.477,2.36996,12.524C1.2033,11.571,0.453296,10.3237,0.119962,8.782C-0.161371,7.21933,0.0521289,5.774,0.760462,4.446C1.4688,3.118,2.54696,2.13617,3.99496,1.5005C3.99496,1.5005,3.99396,1.5,3.99396,1.5C3.99396,1.5,3.99396,1.5,3.99396,1.5ZM6.49396,0C6.8273,0,6.99396,0.166667,6.99396,0.5C6.99396,0.5,6.99396,5.5,6.99396,5.5C6.99396,5.83333,6.8273,6,6.49396,6C6.16063,6,5.99396,5.83333,5.99396,5.5C5.99396,5.5,5.99396,0.5,5.99396,0.5C5.99396,0.166667,6.16063,0,6.49396,0C6.49396,0,6.49396,0,6.49396,0Z" fill="#000000" fill-opacity="1"/></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
1
src/assets/user.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1750316870480" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2449" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M512 74.666667C270.933333 74.666667 74.666667 270.933333 74.666667 512S270.933333 949.333333 512 949.333333 949.333333 753.066667 949.333333 512 753.066667 74.666667 512 74.666667zM288 810.666667c0-123.733333 100.266667-224 224-224S736 686.933333 736 810.666667c-61.866667 46.933333-140.8 74.666667-224 74.666666s-162.133333-27.733333-224-74.666666z m128-384c0-53.333333 42.666667-96 96-96s96 42.666667 96 96-42.666667 96-96 96-96-42.666667-96-96z m377.6 328.533333c-19.2-96-85.333333-174.933333-174.933333-211.2 32-29.866667 51.2-70.4 51.2-117.333333 0-87.466667-72.533333-160-160-160s-160 72.533333-160 160c0 46.933333 19.2 87.466667 51.2 117.333333-89.6 36.266667-155.733333 115.2-174.933334 211.2-55.466667-66.133333-91.733333-149.333333-91.733333-243.2 0-204.8 168.533333-373.333333 373.333333-373.333333S885.333333 307.2 885.333333 512c0 93.866667-34.133333 177.066667-91.733333 243.2z" fill="#666666" p-id="2450"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -5,6 +5,13 @@
|
||||
@main-text-color-2: #666;
|
||||
@main-danger-color: rgb(214, 0, 14);
|
||||
|
||||
@layout-background: #666;
|
||||
@layout-header-background: #666;
|
||||
@menu-list-bg: #666;
|
||||
@table-selected-row-bg: #666;
|
||||
@text-color-secondary: #666;
|
||||
@menu-item-selected-bg: #666;
|
||||
|
||||
//灰色
|
||||
|
||||
@gray: rgb(240, 240, 240);
|
||||
@ -82,3 +89,377 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// .ant-layout-header {
|
||||
// position: fixed;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// background-color: @layout-header-background;
|
||||
// height: 80px;
|
||||
// color: @primary-color;
|
||||
|
||||
|
||||
|
||||
// .ant-pro-global-header{
|
||||
// background-color: #fff;
|
||||
// height: 56px;
|
||||
// color: #333;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// .ant-pro-sider-logo {
|
||||
// height: 80px;
|
||||
// }
|
||||
// .ant-pro-sider-light .ant-pro-sider-logo h1 {
|
||||
// color: #ffffff;
|
||||
// }
|
||||
// .ant-menu {
|
||||
// margin-top: 6px;
|
||||
// padding-top: 6px !important
|
||||
// }
|
||||
// #root .ant-layout-sider {
|
||||
// z-index: 0;
|
||||
// }
|
||||
// // 通用页面布局样式
|
||||
// .layout-container {
|
||||
// background-color: @layout-background;
|
||||
// min-height: 100vh;
|
||||
// }
|
||||
|
||||
// #root {
|
||||
// .ant-table-tbody > tr.ant-table-row:hover > td,
|
||||
// .ant-table-tbody > tr > td.ant-table-cell-row-hover {
|
||||
// background-color: @table-selected-row-bg;
|
||||
// }
|
||||
// .ant-table {
|
||||
// color: @text-color-secondary;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 控制左侧菜单背景色
|
||||
// .ant-menu.ant-menu-sub.ant-menu-inline {
|
||||
// background-color: @menu-list-bg;
|
||||
|
||||
// .ant-menu-item-selected {
|
||||
// background-color: @menu-item-selected-bg;
|
||||
// }
|
||||
// .ant-menu-item-selected a {
|
||||
// color: @primary-color;
|
||||
// font-weight: bold;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 修改全局分页器样式
|
||||
// .pagination-container {
|
||||
// align-items: center;
|
||||
|
||||
// .ant-pagination-total-text {
|
||||
// margin-right: 16px;
|
||||
// height: 24px;
|
||||
// line-height: 24px;
|
||||
// }
|
||||
// .ant-pagination-prev,
|
||||
// .ant-pagination-next,
|
||||
// .ant-pagination-item {
|
||||
// min-width: 24px;
|
||||
// height: 24px;
|
||||
// margin: 0;
|
||||
// line-height: 24px;
|
||||
// }
|
||||
// // .ant-pagination-item {
|
||||
// // border: none;
|
||||
// // }
|
||||
// // .ant-pagination-item-active {
|
||||
// // color: @primary-color;
|
||||
// // }
|
||||
// .ant-pagination-options {
|
||||
// .ant-pagination-options-quick-jumper {
|
||||
// margin-left: 16px;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // .ant-menu-submenu-title {
|
||||
// // padding-left: 24px !important;
|
||||
// // }
|
||||
// // .ant-menu-submenu-arrow {
|
||||
// // right: 24px;
|
||||
// // }
|
||||
|
||||
// .ant-pro-basicLayout-content {
|
||||
// margin-top: 97px !important;
|
||||
// margin-left: 26px !important;
|
||||
// margin-bottom: 0px !important;
|
||||
// // padding-left: 224px;
|
||||
// // display: flex;
|
||||
// flex: auto;
|
||||
// flex-direction: row;
|
||||
|
||||
// // // 去除滚动条样式
|
||||
// // margin-right: 0 !important;
|
||||
// // padding-right: 10px;
|
||||
// // height: 100%;
|
||||
// // overflow: auto;
|
||||
// }
|
||||
// .bidContent {
|
||||
// flex: 1;
|
||||
// }
|
||||
|
||||
// .ant-card-head-title {
|
||||
// padding: 11px 0 10px 0;
|
||||
// }
|
||||
|
||||
// .ant-input {
|
||||
// border-radius: 3px;
|
||||
// }
|
||||
|
||||
// .ant-form-item {
|
||||
// margin-bottom: 8px;
|
||||
// }
|
||||
|
||||
// // .ant-tabs-content-holder {
|
||||
// // margin-top: 24px;
|
||||
// // }
|
||||
|
||||
// .ant-btn-dangerous.ant-btn-link {
|
||||
// color: #1890ff;
|
||||
// }
|
||||
|
||||
|
||||
// .ant-table-pagination.ant-pagination {
|
||||
// margin: 16px 0 24px 0;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// .ant-pagination-item {
|
||||
// border: 1px solid #999;
|
||||
// margin: 0 4px;
|
||||
// }
|
||||
|
||||
// .ant-pagination-item-active {
|
||||
// font-weight: 500;
|
||||
// background: #014f8f;
|
||||
// border-color: #fff;
|
||||
// }
|
||||
|
||||
// .ant-page-header .ant-page-header-heading-title {
|
||||
// font-size: 16px;
|
||||
// font-weight: normal;
|
||||
// color: #333;
|
||||
// }
|
||||
|
||||
// .ant-page-header-heading-sub-title {
|
||||
// color: #333;
|
||||
// font-size: 16px;
|
||||
// }
|
||||
|
||||
// .first-title {
|
||||
// font-size: 14px;
|
||||
// color: @primary-color;
|
||||
// line-height: 40px;
|
||||
// font-weight: 500;
|
||||
// position: relative;
|
||||
// padding-left: 11px;
|
||||
// background-color: @primary-color
|
||||
// }
|
||||
// .first-title::before{
|
||||
// content: "";
|
||||
// display: block;
|
||||
// position: absolute;
|
||||
// top: 13px;
|
||||
// left: 0;
|
||||
// width: 3px;
|
||||
// height: 14px;
|
||||
// background-color: #014f8f;
|
||||
// }
|
||||
|
||||
// .scd-title {
|
||||
// font-size: 14px;
|
||||
// color: #333;
|
||||
// line-height: 42px;
|
||||
// font-weight: 500;
|
||||
// position: relative;
|
||||
// padding-left: 34px;
|
||||
// }
|
||||
// .scd-title::before{
|
||||
// content: "";
|
||||
// display: block;
|
||||
// position: absolute;
|
||||
// top: 20px;
|
||||
// left: 0;
|
||||
// width: 24px;
|
||||
// height: 2px;
|
||||
// background-color: @primary-color;
|
||||
// }
|
||||
|
||||
// .ant-modal-footer {
|
||||
// overflow: hidden;
|
||||
// button {
|
||||
// float: right;
|
||||
// margin-left: 8px;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .MuiTabs-indicator {
|
||||
// background-color: #ffffff !important;
|
||||
// }
|
||||
|
||||
// .ant-pro-table td.ant-table-cell > a {
|
||||
// color: #1890ff;
|
||||
// padding-right: 8px;
|
||||
|
||||
// }
|
||||
|
||||
// .ant-pro-table-search {
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// .ant-pro-table-search {
|
||||
// padding: 10px 24px 6px 24px;
|
||||
// margin-bottom: 8px;
|
||||
// }
|
||||
|
||||
// .ant-pagination-item-active:focus a, .ant-pagination-item-active:hover a {
|
||||
// color: #ffffff;
|
||||
// }
|
||||
|
||||
// .ant-tabs-tab {
|
||||
// margin: 0px 48px 0 0 !important;
|
||||
// }
|
||||
|
||||
// .ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
|
||||
// box-shadow: none;
|
||||
// }
|
||||
// .ant-pro-sider-light {
|
||||
// height: auto;
|
||||
// box-shadow: none;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// .myselfContent .ant-menu-submenu-title {
|
||||
// padding-right: 34px;
|
||||
// text-align: center;
|
||||
// color: #014f8f;
|
||||
// border-top: 2px solid #014f8f;
|
||||
// //background: #014f8f;
|
||||
// }
|
||||
// .myselfContent .ant-menu-submenu-title i {
|
||||
// display: none;
|
||||
// }
|
||||
// .myselfContent .ant-menu-item {
|
||||
// text-align: center;
|
||||
// padding: 0 !important;
|
||||
// }
|
||||
// // .myselfContent .ant-menu .ant-menu-item-selected {
|
||||
// // background-color: rgba(225,225,225,.0);
|
||||
// // }
|
||||
|
||||
// // .myselfContent .ant-menu .ant-menu-submenu-selected {
|
||||
// // background: #014f8f;
|
||||
// // color: #fff !important;
|
||||
// // }
|
||||
// // .myselfContent .ant-menu .ant-menu-submenu-selected:hover {
|
||||
// // background: #014f8f;
|
||||
// // color: #fff !important;
|
||||
// // }
|
||||
|
||||
// .myselfContent .ant-menu-inline .ant-menu-item::after {
|
||||
// border-right: none;
|
||||
// }
|
||||
|
||||
// .ant-collapse {
|
||||
// border: none;
|
||||
// }
|
||||
|
||||
// .ant-page-header {
|
||||
// border-radius: 6px 6px 0 0;
|
||||
// }
|
||||
|
||||
// .ant-table-content .ant-table-cell .ant-btn-text {
|
||||
// color: @primary-color !important;
|
||||
// background: rgba(0,0,0,0);
|
||||
// padding-left: 4px;
|
||||
// padding-right: 4px;
|
||||
// }
|
||||
// .ant-table-content .ant-table-cell .ant-btn-text:hover {
|
||||
// color: @primary-color;
|
||||
// background: none;
|
||||
// }
|
||||
|
||||
// .frize-menu {
|
||||
// width: 224px;
|
||||
// height: 100%;
|
||||
// overflow: hidden;
|
||||
// flex: 0 0 224px;
|
||||
// max-width: 224px;
|
||||
// min-width: 224px;
|
||||
// background: #fff;
|
||||
// position: fixed;
|
||||
// min-height: 100%;
|
||||
// scrollbar-width: none;
|
||||
// z-index: 100;
|
||||
// left: 0;
|
||||
// top: 56px;
|
||||
// overflow-y: auto;
|
||||
// border: none;
|
||||
// // padding-top: 66px;
|
||||
// padding-bottom: 200px;
|
||||
// -ms-overflow-style: none; /* IE 10+ */
|
||||
// }
|
||||
// .frize-menu::-webkit-scrollbar {
|
||||
// display: none;
|
||||
// }
|
||||
// input::-webkit-outer-spin-button,
|
||||
// input::-webkit-inner-spin-button {
|
||||
// -webkit-appearance: none;
|
||||
// }
|
||||
// input[type="number"]{
|
||||
// -moz-appearance: textfield;
|
||||
// }
|
||||
|
||||
// .basicLayout-children{
|
||||
// margin-top:5px;
|
||||
// background-color:white;
|
||||
// border-radius:6px;
|
||||
// overflow:auto;
|
||||
// -ms-overflow-style: none;
|
||||
// }
|
||||
// .basicLayout-children::-webkit-scrollbar {
|
||||
// width:0px;
|
||||
// }
|
||||
|
||||
// // 修改全局界面上所有只读表单文字颜色改成黑色
|
||||
// .ant-input[disabled]{
|
||||
// color: #333;
|
||||
// }
|
||||
// .ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{
|
||||
// color: #333;
|
||||
// }
|
||||
// .ant-radio-disabled + span{
|
||||
// color: #333;
|
||||
// }
|
||||
// .ant-btn-primary[disabled], .ant-btn-primary[disabled]:hover, .ant-btn-primary[disabled]:focus, .ant-btn-primary[disabled]:active{
|
||||
// color: #333;
|
||||
// }
|
||||
// .ant-checkbox-disabled + span{
|
||||
// color: #333;
|
||||
// }
|
||||
// .ant-picker-input > input[disabled]{
|
||||
// color: #333;
|
||||
// }
|
||||
// .ant-btn[disabled], .ant-btn[disabled]:hover, .ant-btn[disabled]:focus, .ant-btn[disabled]:active{
|
||||
// color: #333;
|
||||
// }
|
||||
// //2022.2.18增加PageHeader全局样式 来源:ld_style.less
|
||||
// .ant-page-header{
|
||||
// background: #fff;
|
||||
// padding: 6px 24px;
|
||||
// border-bottom: 1px solid #ddd;
|
||||
// }
|
||||
|
||||
|
35
src/components/Authorized/Authorized.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Result } from 'antd';
|
||||
import check from './CheckPermissions';
|
||||
import type { IAuthorityType } from './CheckPermissions';
|
||||
import type AuthorizedRoute from './AuthorizedRoute';
|
||||
import type Secured from './Secured';
|
||||
|
||||
type AuthorizedProps = {
|
||||
authority: IAuthorityType;
|
||||
noMatch?: React.ReactNode;
|
||||
};
|
||||
|
||||
type IAuthorizedType = React.FunctionComponent<AuthorizedProps> & {
|
||||
Secured: typeof Secured;
|
||||
check: typeof check;
|
||||
AuthorizedRoute: typeof AuthorizedRoute;
|
||||
};
|
||||
|
||||
const Authorized: React.FunctionComponent<AuthorizedProps> = ({
|
||||
children,
|
||||
authority,
|
||||
noMatch = (
|
||||
<Result
|
||||
status="403"
|
||||
title="403"
|
||||
subTitle="Sorry, you are not authorized to access this page."
|
||||
/>
|
||||
),
|
||||
}) => {
|
||||
const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children;
|
||||
const dom = check(authority, childrenRender, noMatch);
|
||||
return <>{dom}</>;
|
||||
};
|
||||
|
||||
export default Authorized as IAuthorizedType;
|
33
src/components/Authorized/AuthorizedRoute.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Redirect, Route } from 'umi';
|
||||
|
||||
import React from 'react';
|
||||
import Authorized from './Authorized';
|
||||
import type { IAuthorityType } from './CheckPermissions';
|
||||
|
||||
type AuthorizedRouteProps = {
|
||||
currentAuthority: string;
|
||||
component: React.ComponentClass<any, any>;
|
||||
render: (props: any) => React.ReactNode;
|
||||
redirectPath: string;
|
||||
authority: IAuthorityType;
|
||||
};
|
||||
|
||||
const AuthorizedRoute: React.SFC<AuthorizedRouteProps> = ({
|
||||
component: Component,
|
||||
render,
|
||||
authority,
|
||||
redirectPath,
|
||||
...rest
|
||||
}) => (
|
||||
<Authorized
|
||||
authority={authority}
|
||||
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
|
||||
>
|
||||
<Route
|
||||
{...rest}
|
||||
render={(props: any) => (Component ? <Component {...props} /> : render(props))}
|
||||
/>
|
||||
</Authorized>
|
||||
);
|
||||
|
||||
export default AuthorizedRoute;
|
88
src/components/Authorized/CheckPermissions.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import { CURRENT } from './renderAuthorize';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import PromiseRender from './PromiseRender';
|
||||
|
||||
export type IAuthorityType =
|
||||
| undefined
|
||||
| string
|
||||
| string[]
|
||||
| Promise<boolean>
|
||||
| ((currentAuthority: string | string[]) => IAuthorityType);
|
||||
|
||||
/**
|
||||
* @en-US
|
||||
* General permission check method
|
||||
* Common check permissions method
|
||||
* @param {Permission judgment} authority
|
||||
* @param {Your permission | Your permission description} currentAuthority
|
||||
* @param {Passing components} target
|
||||
* @param {no pass components | no pass components} Exception
|
||||
* -------------------------------------------------------
|
||||
* @zh-CN
|
||||
* 通用权限检查方法 Common check permissions method
|
||||
*
|
||||
* @param { 权限判定 | Permission judgment } authority
|
||||
* @param { 你的权限 | Your permission description } currentAuthority
|
||||
* @param { 通过的组件 | Passing components } target
|
||||
* @param { 未通过的组件 | no pass components } Exception
|
||||
*/
|
||||
const checkPermissions = <T, K>(
|
||||
authority: IAuthorityType,
|
||||
currentAuthority: string | string[],
|
||||
target: T,
|
||||
Exception: K,
|
||||
): T | K | React.ReactNode => {
|
||||
// No judgment permission. View all by default
|
||||
// Retirement authority, return target;
|
||||
if (!authority) {
|
||||
return target;
|
||||
}
|
||||
// Array processing
|
||||
if (Array.isArray(authority)) {
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
if (currentAuthority.some((item) => authority.includes(item))) {
|
||||
return target;
|
||||
}
|
||||
} else if (authority.includes(currentAuthority)) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
// Deal with string
|
||||
if (typeof authority === 'string') {
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
if (currentAuthority.some((item) => authority === item)) {
|
||||
return target;
|
||||
}
|
||||
} else if (authority === currentAuthority) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
// Deal with promise
|
||||
if (authority instanceof Promise) {
|
||||
return <PromiseRender<T, K> ok={target} error={Exception} promise={authority} />;
|
||||
}
|
||||
// Deal with function
|
||||
if (typeof authority === 'function') {
|
||||
const bool = authority(currentAuthority);
|
||||
// The return value after the function is executed is Promise
|
||||
if (bool instanceof Promise) {
|
||||
return <PromiseRender<T, K> ok={target} error={Exception} promise={bool} />;
|
||||
}
|
||||
if (bool) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
throw new Error('unsupported parameters');
|
||||
};
|
||||
|
||||
export { checkPermissions };
|
||||
|
||||
function check<T, K>(authority: IAuthorityType, target: T, Exception: K): T | K | React.ReactNode {
|
||||
return checkPermissions<T, K>(authority, CURRENT, target, Exception);
|
||||
}
|
||||
|
||||
export default check;
|
96
src/components/Authorized/PromiseRender.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { isComponentClass } from './Secured';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
|
||||
type PromiseRenderProps<T, K> = {
|
||||
ok: T;
|
||||
error: K;
|
||||
promise: Promise<boolean>;
|
||||
};
|
||||
|
||||
type PromiseRenderState = {
|
||||
component: React.ComponentClass | React.FunctionComponent;
|
||||
};
|
||||
|
||||
export default class PromiseRender<T, K> extends React.Component<
|
||||
PromiseRenderProps<T, K>,
|
||||
PromiseRenderState
|
||||
> {
|
||||
state: PromiseRenderState = {
|
||||
component: () => null,
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.setRenderComponent(this.props);
|
||||
}
|
||||
|
||||
shouldComponentUpdate = (
|
||||
nextProps: PromiseRenderProps<T, K>,
|
||||
nextState: PromiseRenderState,
|
||||
): boolean => {
|
||||
const { component } = this.state;
|
||||
if (!isEqual(nextProps, this.props)) {
|
||||
this.setRenderComponent(nextProps);
|
||||
}
|
||||
if (nextState.component !== component) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
// set render Component : ok or error
|
||||
setRenderComponent(props: PromiseRenderProps<T, K>): void {
|
||||
const ok = this.checkIsInstantiation(props.ok);
|
||||
const error = this.checkIsInstantiation(props.error);
|
||||
props.promise
|
||||
.then(() => {
|
||||
this.setState({
|
||||
component: ok,
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
component: error,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
checkIsInstantiation = (
|
||||
target: React.ReactNode | React.ComponentClass,
|
||||
): React.FunctionComponent => {
|
||||
if (isComponentClass(target)) {
|
||||
const Target = target as React.ComponentClass;
|
||||
return (props: any) => <Target {...props} />;
|
||||
}
|
||||
if (React.isValidElement(target)) {
|
||||
return (props: any) => React.cloneElement(target, props);
|
||||
}
|
||||
return () => target as React.ReactNode & null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { component: Component } = this.state;
|
||||
const { ok, error, promise, ...rest } = this.props;
|
||||
|
||||
return Component ? (
|
||||
<Component {...rest} />
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
margin: 'auto',
|
||||
paddingTop: 50,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
80
src/components/Authorized/Secured.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import CheckPermissions from './CheckPermissions';
|
||||
|
||||
/**
|
||||
* @en-US No pages can be accessed by default,default is "NULL"
|
||||
* @zh-CN 默认不能访问任何页面 default is "NULL"
|
||||
* */
|
||||
const Exception403 = () => 403;
|
||||
|
||||
export const isComponentClass = (component: React.ComponentClass | React.ReactNode): boolean => {
|
||||
if (!component) return false;
|
||||
const proto = Object.getPrototypeOf(component);
|
||||
if (proto === React.Component || proto === Function.prototype) return true;
|
||||
return isComponentClass(proto);
|
||||
};
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
const checkIsInstantiation = (target: React.ComponentClass | React.ReactNode) => {
|
||||
if (isComponentClass(target)) {
|
||||
const Target = target as React.ComponentClass;
|
||||
return (props: any) => <Target {...props} />;
|
||||
}
|
||||
if (React.isValidElement(target)) {
|
||||
return (props: any) => React.cloneElement(target, props);
|
||||
}
|
||||
return () => target;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en-US
|
||||
* Used to determine whether you have permission to access this view permission
|
||||
* authority supports incoming string, () => boolean | Promise
|
||||
* e.g.'user' Only user user can access
|
||||
* e.g.'user,admin' user and admin can access
|
||||
* e.g. ()=>boolean return true to access, return false to not access
|
||||
* e.g. Promise then can be accessed, catch can not be accessed
|
||||
* e.g. authority support incoming string, () => boolean | Promise
|
||||
* e.g.'user' only user user can access
|
||||
* e.g.'user, admin' user and admin can access
|
||||
* e.g. () => boolean true to be able to visit, return false can not be accessed
|
||||
* e.g. Promise then can not access the visit to catch
|
||||
*-------------------------------------------------------------
|
||||
* @zh-CN
|
||||
* 用于判断是否拥有权限访问此 view 权限 authority 支持传入 string, () => boolean | Promise e.g. 'user' 只有 user 用户能访问
|
||||
* e.g. 'user,admin' user 和 admin 都能访问 e.g. ()=>boolean 返回true能访问,返回false不能访问 e.g. Promise then 能访问
|
||||
* catch不能访问 e.g. authority support incoming string, () => boolean | Promise e.g. 'user' only user
|
||||
* user can access e.g. 'user, admin' user and admin can access e.g. () => boolean true to be able
|
||||
* to visit, return false can not be accessed e.g. Promise then can not access the visit to catch
|
||||
*
|
||||
* @param {string | function | Promise} authority
|
||||
* @param {ReactNode} error non-required parameter
|
||||
*/
|
||||
const authorize = (authority: string, error?: React.ReactNode) => {
|
||||
/**
|
||||
* @en-US
|
||||
* conversion into a class
|
||||
* Prevent the staticContext from being found to cause an error when the string is passed in
|
||||
* String parameters can cause staticContext not found error
|
||||
*-------------------------------------------------------------
|
||||
* @zh-CN
|
||||
* Conversion into a class 防止传入字符串时找不到staticContext造成报错 String parameters can cause staticContext
|
||||
* not found error
|
||||
*/
|
||||
let classError: boolean | React.FunctionComponent = false;
|
||||
if (error) {
|
||||
classError = (() => error) as React.FunctionComponent;
|
||||
}
|
||||
if (!authority) {
|
||||
throw new Error('authority is required');
|
||||
}
|
||||
return function decideAuthority(target: React.ComponentClass | React.ReactNode) {
|
||||
const component = CheckPermissions(authority, target, classError || Exception403);
|
||||
return checkIsInstantiation(component);
|
||||
};
|
||||
};
|
||||
|
||||
export default authorize;
|
11
src/components/Authorized/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import Authorized from './Authorized';
|
||||
import Secured from './Secured';
|
||||
import check from './CheckPermissions';
|
||||
import renderAuthorize from './renderAuthorize';
|
||||
|
||||
Authorized.Secured = Secured;
|
||||
Authorized.check = check;
|
||||
|
||||
const RenderAuthorize = renderAuthorize(Authorized);
|
||||
|
||||
export default RenderAuthorize;
|
31
src/components/Authorized/renderAuthorize.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable import/no-mutable-exports */
|
||||
let CURRENT: string | string[] = 'NULL';
|
||||
|
||||
type CurrentAuthorityType = string | string[] | (() => typeof CURRENT);
|
||||
/**
|
||||
* Use authority or getAuthority
|
||||
*
|
||||
* @param {string|()=>String} currentAuthority
|
||||
*/
|
||||
const renderAuthorize = <T>(Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => (
|
||||
currentAuthority: CurrentAuthorityType,
|
||||
): T => {
|
||||
if (currentAuthority) {
|
||||
if (typeof currentAuthority === 'function') {
|
||||
CURRENT = currentAuthority();
|
||||
}
|
||||
if (
|
||||
Object.prototype.toString.call(currentAuthority) === '[object String]' ||
|
||||
Array.isArray(currentAuthority)
|
||||
) {
|
||||
CURRENT = currentAuthority as string[];
|
||||
}
|
||||
} else {
|
||||
CURRENT = 'NULL';
|
||||
}
|
||||
return Authorized;
|
||||
};
|
||||
|
||||
export { CURRENT };
|
||||
export default <T>(Authorized: T) => renderAuthorize<T>(Authorized);
|
@ -16,7 +16,7 @@ interface CompanyInfoProps {
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const CompanyInfo: React.FC<CompanyInfoProps> = (props) => {
|
||||
const { viewType = false, record = '' } = props;
|
||||
const { viewType = false, record = '999698' } = props;
|
||||
const intl = useIntl();
|
||||
// 切换tab
|
||||
const [subTab, setSubTab] = useState<string>('base');
|
||||
|
@ -5,7 +5,7 @@ import request from '@/utils/request';
|
||||
/**
|
||||
* 供应商基本信息
|
||||
*/
|
||||
export const coscoSupplierBase = (id: string) => request.get(`/coscoSupplierBase/${id}`);
|
||||
export const coscoSupplierBase = (id: string) => request.get(`/coscoSupplierBase/${id? id: '999698'}`);
|
||||
|
||||
/**
|
||||
* 资质分页列表
|
||||
|
88
src/components/GlobalHeader/AvatarDropdown.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Menu, Spin } from 'antd';
|
||||
import React from 'react';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { history, connect } from 'umi';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
import HeaderDropdown from '../HeaderDropdown';
|
||||
import styles from './index.less';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
currentUser?: CurrentUser;
|
||||
menu?: boolean;
|
||||
} & Partial<ConnectProps>;
|
||||
|
||||
class AvatarDropdown extends React.Component<GlobalHeaderRightProps> {
|
||||
onMenuClick = (event: { key: React.Key; keyPath: React.Key[]; item: React.ReactInstance }) => {
|
||||
const { key } = event;
|
||||
|
||||
if (key === 'logout') {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'login/logout',
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
history.push(`/account/${key}`);
|
||||
};
|
||||
|
||||
render(): React.ReactNode {
|
||||
const {
|
||||
currentUser = {
|
||||
avatar: '',
|
||||
name: '',
|
||||
},
|
||||
menu,
|
||||
} = this.props;
|
||||
const menuHeaderDropdown = (
|
||||
<Menu className={styles.menu} selectedKeys={[]} onClick={this.onMenuClick}>
|
||||
{menu && (
|
||||
<Menu.Item key="center">
|
||||
<UserOutlined />
|
||||
个人中心
|
||||
</Menu.Item>
|
||||
)}
|
||||
{menu && (
|
||||
<Menu.Item key="settings">
|
||||
<SettingOutlined />
|
||||
个人设置
|
||||
</Menu.Item>
|
||||
)}
|
||||
{menu && <Menu.Divider />}
|
||||
|
||||
<Menu.Item key="logout">
|
||||
<LogoutOutlined />
|
||||
退出登录
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
return currentUser && currentUser.name ? (
|
||||
<HeaderDropdown overlay={menuHeaderDropdown}>
|
||||
<span className={`${styles.action} ${styles.account}`}>
|
||||
<Avatar size="small" className={styles.avatar} src={currentUser.avatar} alt="avatar" />
|
||||
<span className={`${styles.name} anticon`}>{currentUser.name}</span>
|
||||
</span>
|
||||
</HeaderDropdown>
|
||||
) : (
|
||||
<span className={`${styles.action} ${styles.account}`}>
|
||||
<Spin
|
||||
size="small"
|
||||
style={{
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user }: ConnectState) => ({
|
||||
currentUser: user.currentUser,
|
||||
}))(AvatarDropdown);
|
168
src/components/GlobalHeader/NoticeIconView.tsx
Normal file
@ -0,0 +1,168 @@
|
||||
import { Component } from 'react';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import { Tag, message } from 'antd';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import moment from 'moment';
|
||||
import type { NoticeItem } from '@/models/global';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import NoticeIcon from '../NoticeIcon';
|
||||
import styles from './index.less';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
notices?: NoticeItem[];
|
||||
currentUser?: CurrentUser;
|
||||
fetchingNotices?: boolean;
|
||||
onNoticeVisibleChange?: (visible: boolean) => void;
|
||||
onNoticeClear?: (tabName?: string) => void;
|
||||
} & Partial<ConnectProps>;
|
||||
|
||||
class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/fetchNotices',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changeReadState = (clickedItem: NoticeItem): void => {
|
||||
const { id } = clickedItem;
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/changeNoticeReadState',
|
||||
payload: id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleNoticeClear = (title: string, key: string) => {
|
||||
const { dispatch } = this.props;
|
||||
message.success(`${'Emptied'} ${title}`);
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/clearNotices',
|
||||
payload: key,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getNoticeData = (): Record<string, NoticeItem[]> => {
|
||||
const { notices = [] } = this.props;
|
||||
|
||||
if (!notices || notices.length === 0 || !Array.isArray(notices)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const newNotices = notices.map((notice) => {
|
||||
const newNotice = { ...notice };
|
||||
|
||||
if (newNotice.datetime) {
|
||||
newNotice.datetime = moment(notice.datetime as string).fromNow();
|
||||
}
|
||||
|
||||
if (newNotice.id) {
|
||||
newNotice.key = newNotice.id;
|
||||
}
|
||||
|
||||
if (newNotice.extra && newNotice.status) {
|
||||
const color = {
|
||||
todo: '',
|
||||
processing: 'blue',
|
||||
urgent: 'red',
|
||||
doing: 'gold',
|
||||
}[newNotice.status];
|
||||
newNotice.extra = (
|
||||
<Tag
|
||||
color={color}
|
||||
style={{
|
||||
marginRight: 0,
|
||||
}}
|
||||
>
|
||||
{newNotice.extra}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
return newNotice;
|
||||
});
|
||||
return groupBy(newNotices, 'type');
|
||||
};
|
||||
|
||||
getUnreadData = (noticeData: Record<string, NoticeItem[]>) => {
|
||||
const unreadMsg: Record<string, number> = {};
|
||||
Object.keys(noticeData).forEach((key) => {
|
||||
const value = noticeData[key];
|
||||
|
||||
if (!unreadMsg[key]) {
|
||||
unreadMsg[key] = 0;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
unreadMsg[key] = value.filter((item) => !item.read).length;
|
||||
}
|
||||
});
|
||||
return unreadMsg;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { currentUser, fetchingNotices, onNoticeVisibleChange } = this.props;
|
||||
const noticeData = this.getNoticeData();
|
||||
const unreadMsg = this.getUnreadData(noticeData);
|
||||
return (
|
||||
<NoticeIcon
|
||||
className={styles.action}
|
||||
count={currentUser && currentUser.unreadCount}
|
||||
onItemClick={(item) => {
|
||||
this.changeReadState(item as NoticeItem);
|
||||
}}
|
||||
loading={fetchingNotices}
|
||||
clearText="Empty"
|
||||
viewMoreText="See more"
|
||||
onClear={this.handleNoticeClear}
|
||||
onPopupVisibleChange={onNoticeVisibleChange}
|
||||
onViewMore={() => message.info('Click on view more')}
|
||||
clearClose
|
||||
>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="notification"
|
||||
count={unreadMsg.notification}
|
||||
list={noticeData.notification}
|
||||
title="Notification"
|
||||
emptyText="You have viewed all notifications"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="message"
|
||||
count={unreadMsg.message}
|
||||
list={noticeData.message}
|
||||
title="Message"
|
||||
emptyText="You have read all messages"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="event"
|
||||
title="To do"
|
||||
emptyText="You have completed all to-dos"
|
||||
count={unreadMsg.event}
|
||||
list={noticeData.event}
|
||||
showViewMore
|
||||
/>
|
||||
</NoticeIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user, global, loading }: ConnectState) => ({
|
||||
currentUser: user.currentUser,
|
||||
collapsed: global.collapsed,
|
||||
fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
|
||||
fetchingNotices: loading.effects['global/fetchNotices'],
|
||||
notices: global.notices,
|
||||
}))(GlobalHeaderRight);
|
89
src/components/GlobalHeader/RightContent.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import { Tooltip, Tag } from 'antd';
|
||||
import type { Settings as ProSettings } from '@ant-design/pro-layout';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { connect, SelectLang } from 'umi';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import Avatar from './AvatarDropdown';
|
||||
import HeaderSearch from '../HeaderSearch';
|
||||
import styles from './index.less';
|
||||
|
||||
// import userIcon from '@/assets/user.svg';
|
||||
// import homeIcon from '@/assets/home.svg';
|
||||
// import shutdownIcon from '@/assets/shutdown.svg';
|
||||
// import divider from '@/assets/divider.png';
|
||||
// const theme = JSON.parse(PROJECT_THEME);
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
theme?: ProSettings['navTheme'] | 'realDark';
|
||||
} & Partial<ConnectProps> &
|
||||
Partial<ProSettings>;
|
||||
|
||||
const ENVTagColor = {
|
||||
dev: 'orange',
|
||||
test: 'green',
|
||||
pre: '#87d068',
|
||||
};
|
||||
|
||||
const GlobalHeaderRight: React.SFC<GlobalHeaderRightProps> = (props) => {
|
||||
const { theme, layout } = props;
|
||||
let className = styles.right;
|
||||
|
||||
if (theme === 'dark' && layout === 'top') {
|
||||
className = `${styles.right} ${styles.dark}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<HeaderSearch
|
||||
className={`${styles.action} ${styles.search}`}
|
||||
placeholder="Site Search"
|
||||
defaultValue="umi ui"
|
||||
options={[
|
||||
{ label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, value: 'umi ui' },
|
||||
{
|
||||
label: <a href="next.ant.design">Ant Design</a>,
|
||||
value: 'Ant Design',
|
||||
},
|
||||
{
|
||||
label: <a href="https://protable.ant.design/">Pro Table</a>,
|
||||
value: 'Pro Table',
|
||||
},
|
||||
{
|
||||
label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
|
||||
value: 'Pro Layout',
|
||||
},
|
||||
]}
|
||||
// onSearch={value => {
|
||||
// //console.log('input', value);
|
||||
// }}
|
||||
/>
|
||||
<Tooltip title="Use documentation">
|
||||
<a
|
||||
style={{
|
||||
color: 'inherit',
|
||||
}}
|
||||
target="_blank"
|
||||
href="https://pro.ant.design/docs/getting-started"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.action}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Avatar />
|
||||
{REACT_APP_ENV && (
|
||||
<span>
|
||||
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
|
||||
</span>
|
||||
)}
|
||||
<SelectLang className={styles.action} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect((state: ConnectState) => ({
|
||||
theme: state.settings?.navTheme,
|
||||
layout: state.settings?.layout,
|
||||
}))(GlobalHeaderRight);
|
96
src/components/GlobalHeader/index.less
Normal file
@ -0,0 +1,96 @@
|
||||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
@pro-header-hover-bg: rgba(0, 0, 0, 0.025);
|
||||
|
||||
.menu {
|
||||
:global(.anticon) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
:global(.ant-dropdown-menu-item) {
|
||||
min-width: 160px;
|
||||
}
|
||||
}
|
||||
.left-logo {
|
||||
top: 16px;
|
||||
|
||||
img {
|
||||
margin-left: 32px;
|
||||
margin-right: 12px;
|
||||
top: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.right-btns {
|
||||
color: inherit;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
float: right;
|
||||
height: 48px;
|
||||
margin-left: auto;
|
||||
overflow: hidden;
|
||||
.action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
> span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
&:hover {
|
||||
background: @pro-header-hover-bg;
|
||||
}
|
||||
&:global(.opened) {
|
||||
background: @pro-header-hover-bg;
|
||||
}
|
||||
}
|
||||
.search {
|
||||
padding: 0 12px;
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
.account {
|
||||
.avatar {
|
||||
margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
|
||||
margin-right: 8px;
|
||||
color: @primary-color;
|
||||
vertical-align: top;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.action {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
> span {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
&:hover,
|
||||
&:global(.opened) {
|
||||
background: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ant-pro-global-header) {
|
||||
.dark {
|
||||
.action {
|
||||
color: @text-color;
|
||||
> span {
|
||||
color: @text-color;
|
||||
}
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
> span {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
src/components/HeaderDropdown/index.less
Normal file
@ -0,0 +1,16 @@
|
||||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.container > * {
|
||||
background-color: @popover-bg;
|
||||
border-radius: 4px;
|
||||
box-shadow: @shadow-1-down;
|
||||
}
|
||||
|
||||
@media screen and (max-width: @screen-xs) {
|
||||
.container {
|
||||
width: 100% !important;
|
||||
}
|
||||
.container > * {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
}
|
17
src/components/HeaderDropdown/index.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import type { DropDownProps } from 'antd/es/dropdown';
|
||||
import { Dropdown } from 'antd';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './index.less';
|
||||
|
||||
export type HeaderDropdownProps = {
|
||||
overlayClassName?: string;
|
||||
overlay: React.ReactNode | (() => React.ReactNode) | any;
|
||||
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
|
||||
} & Omit<DropDownProps, 'overlay'>;
|
||||
|
||||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => (
|
||||
<Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
|
||||
);
|
||||
|
||||
export default HeaderDropdown;
|
30
src/components/HeaderSearch/index.less
Normal file
@ -0,0 +1,30 @@
|
||||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.headerSearch {
|
||||
.input {
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
transition: width 0.3s, margin-left 0.3s;
|
||||
:global(.ant-select-selection) {
|
||||
background: transparent;
|
||||
}
|
||||
input {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
border: 0;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-bottom: 1px solid @border-color-base;
|
||||
}
|
||||
&.show {
|
||||
width: 210px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
105
src/components/HeaderSearch/index.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { AutoComplete, Input } from 'antd';
|
||||
import useMergedState from 'rc-util/es/hooks/useMergedState';
|
||||
import type { AutoCompleteProps } from 'antd/es/auto-complete';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import styles from './index.less';
|
||||
|
||||
export type HeaderSearchProps = {
|
||||
onSearch?: (value?: string) => void;
|
||||
onChange?: (value?: string) => void;
|
||||
onVisibleChange?: (b: boolean) => void;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
options: AutoCompleteProps['options'];
|
||||
defaultOpen?: boolean;
|
||||
open?: boolean;
|
||||
defaultValue?: string;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
const HeaderSearch: React.FC<HeaderSearchProps> = (props) => {
|
||||
const {
|
||||
className,
|
||||
defaultValue,
|
||||
onVisibleChange,
|
||||
placeholder,
|
||||
open,
|
||||
defaultOpen,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const inputRef = useRef<Input | null>(null);
|
||||
|
||||
const [value, setValue] = useMergedState<string | undefined>(defaultValue, {
|
||||
value: props.value,
|
||||
onChange: props.onChange,
|
||||
});
|
||||
|
||||
const [searchMode, setSearchMode] = useMergedState(defaultOpen ?? false, {
|
||||
value: props.open,
|
||||
onChange: onVisibleChange,
|
||||
});
|
||||
|
||||
const inputClass = classNames(styles.input, {
|
||||
[styles.show]: searchMode,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(className, styles.headerSearch)}
|
||||
onClick={() => {
|
||||
setSearchMode(true);
|
||||
if (searchMode && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}}
|
||||
onTransitionEnd={({ propertyName }) => {
|
||||
if (propertyName === 'width' && !searchMode) {
|
||||
if (onVisibleChange) {
|
||||
onVisibleChange(searchMode);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SearchOutlined
|
||||
key="Icon"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
<AutoComplete
|
||||
key="AutoComplete"
|
||||
className={inputClass}
|
||||
value={value}
|
||||
style={{
|
||||
height: 28,
|
||||
marginTop: -6,
|
||||
}}
|
||||
options={restProps.options}
|
||||
onChange={setValue}
|
||||
>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
defaultValue={defaultValue}
|
||||
aria-label={placeholder}
|
||||
placeholder={placeholder}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (restProps.onSearch) {
|
||||
restProps.onSearch(value);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
setSearchMode(false);
|
||||
}}
|
||||
/>
|
||||
</AutoComplete>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderSearch;
|
@ -1,7 +1,15 @@
|
||||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
@import './baseStyle.less';
|
||||
// 页面公用样式
|
||||
@import './utils/componentStyle.less';
|
||||
@font-face {
|
||||
font-family: 'HarmonyOS Sans SC';
|
||||
src: url('./assets/HarmonyOS_Sans_SC_Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
@ -16,6 +24,7 @@ body {
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: 'HarmonyOS Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.ant-layout-header {
|
||||
@ -28,3 +37,41 @@ body {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-layout {
|
||||
// min-height: 100vh;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-xs) {
|
||||
.ant-table {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
&-thead > tr,
|
||||
&-tbody > tr {
|
||||
> th,
|
||||
> td {
|
||||
white-space: pre;
|
||||
> span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compatible with IE11
|
||||
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
|
||||
body .ant-design-pro > .ant-layout {
|
||||
// min-height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
|
10
src/layouts/BlankLayout.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Inspector } from 'react-dev-inspector';
|
||||
|
||||
const InspectorWrapper = process.env.NODE_ENV === 'development' ? Inspector : React.Fragment;
|
||||
|
||||
const Layout: React.FC = ({ children }) => {
|
||||
return <InspectorWrapper>{children}</InspectorWrapper>;
|
||||
};
|
||||
|
||||
export default Layout;
|
58
src/layouts/SecurityLayout.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { Redirect, connect } from 'umi';
|
||||
import { stringify } from 'querystring';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
|
||||
type SecurityLayoutProps = {
|
||||
loading?: boolean;
|
||||
currentUser?: CurrentUser;
|
||||
} & ConnectProps;
|
||||
|
||||
type SecurityLayoutState = {
|
||||
isReady: boolean;
|
||||
};
|
||||
|
||||
class SecurityLayout extends React.Component<SecurityLayoutProps, SecurityLayoutState> {
|
||||
state: SecurityLayoutState = {
|
||||
isReady: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
isReady: true,
|
||||
});
|
||||
const { dispatch } = this.props;
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'user/fetchCurrent',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isReady } = this.state;
|
||||
const { children, loading, currentUser } = this.props;
|
||||
// You can replace it to your authentication rule (such as check token exists)
|
||||
// You can replace it with your own login authentication rules (such as judging whether the token exists)
|
||||
const isLogin = currentUser && currentUser.userid;
|
||||
const queryString = stringify({
|
||||
redirect: window.location.href,
|
||||
});
|
||||
|
||||
if ((!isLogin && loading) || !isReady) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (!isLogin && window.location.pathname !== '/user/login') {
|
||||
return <Redirect to={`/user/login?${queryString}`} />;
|
||||
}
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user, loading }: ConnectState) => ({
|
||||
currentUser: user.currentUser,
|
||||
loading: loading.models.user,
|
||||
}))(SecurityLayout);
|
71
src/layouts/UserLayout.less
Normal file
@ -0,0 +1,71 @@
|
||||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
background: @layout-body-background;
|
||||
}
|
||||
|
||||
.lang {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 44px;
|
||||
text-align: right;
|
||||
:global(.ant-dropdown-trigger) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-md-min) {
|
||||
.container {
|
||||
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 110px;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32px 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 44px;
|
||||
margin-right: 16px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
color: @heading-color;
|
||||
font-weight: 600;
|
||||
font-size: 33px;
|
||||
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 40px;
|
||||
color: @text-color-secondary;
|
||||
font-size: @font-size-base;
|
||||
}
|
70
src/layouts/UserLayout.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import type { MenuDataItem } from '@ant-design/pro-layout';
|
||||
import { DefaultFooter, getMenuData, getPageTitle } from '@ant-design/pro-layout';
|
||||
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { Link, SelectLang, useIntl, connect, FormattedMessage } from 'umi';
|
||||
import React from 'react';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import logo from '../assets/logo.svg';
|
||||
import styles from './UserLayout.less';
|
||||
|
||||
export type UserLayoutProps = {
|
||||
breadcrumbNameMap: Record<string, MenuDataItem>;
|
||||
} & Partial<ConnectProps>;
|
||||
|
||||
const UserLayout: React.FC<UserLayoutProps> = (props) => {
|
||||
const {
|
||||
route = {
|
||||
routes: [],
|
||||
},
|
||||
} = props;
|
||||
const { routes = [] } = route;
|
||||
const {
|
||||
children,
|
||||
location = {
|
||||
pathname: '',
|
||||
},
|
||||
} = props;
|
||||
const { formatMessage } = useIntl();
|
||||
const { breadcrumb } = getMenuData(routes);
|
||||
const title = getPageTitle({
|
||||
pathname: location.pathname,
|
||||
formatMessage,
|
||||
breadcrumb,
|
||||
...props,
|
||||
});
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={title} />
|
||||
</Helmet>
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.lang}>
|
||||
<SelectLang />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.top}>
|
||||
<div className={styles.header}>
|
||||
<Link to="/">
|
||||
<img alt="logo" className={styles.logo} src={logo} />
|
||||
<span className={styles.title}>Ant Design</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.desc}>
|
||||
<FormattedMessage
|
||||
id="pages.layouts.userLayout.title"
|
||||
defaultMessage="Ant Design. The most influential Web design specification in Xihu District."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
<DefaultFooter />
|
||||
</div>
|
||||
</HelmetProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ settings }: ConnectState) => ({ ...settings }))(UserLayout);
|
@ -72,9 +72,9 @@ export default {
|
||||
'menu.categoryLibraryReview': '品类库建库审核管理',
|
||||
'menu.supplierEntryManage': '供应商入库管理',
|
||||
'menu.supplierEntryReview': '供应商入库审核管理',
|
||||
'menu.supplierBlacklist': '供应商黑灰名单管理',
|
||||
'menu.blacklistManage': '供应商黑灰名单管理',
|
||||
'menu.blacklistAudit': '供应商黑灰名单审核管理',
|
||||
'menu.supplierBlacklist': '供应商黑名单管理',
|
||||
'menu.blacklistManage': '供应商黑名单管理',
|
||||
'menu.blacklistAudit': '供应商黑名单审核管理',
|
||||
'menu.supplierExit': '供应商退出管理',
|
||||
'menu.supplierExitManage': '供应商退出管理',
|
||||
'menu.supplierExitAudit': '供应商退出审核管理',
|
||||
|
30
src/models/connect.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
import type { MenuDataItem, Settings as ProSettings } from '@ant-design/pro-layout';
|
||||
import { GlobalModelState } from './global';
|
||||
import { UserModelState } from './user';
|
||||
import type { StateType } from './login';
|
||||
|
||||
export { GlobalModelState, UserModelState };
|
||||
|
||||
export type Loading = {
|
||||
global: boolean;
|
||||
effects: Record<string, boolean | undefined>;
|
||||
models: {
|
||||
global?: boolean;
|
||||
menu?: boolean;
|
||||
setting?: boolean;
|
||||
user?: boolean;
|
||||
login?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type ConnectState = {
|
||||
global: GlobalModelState;
|
||||
loading: Loading;
|
||||
settings: ProSettings;
|
||||
user: UserModelState;
|
||||
login: StateType;
|
||||
};
|
||||
|
||||
export type Route = {
|
||||
routes?: Route[];
|
||||
} & MenuDataItem;
|
85
src/models/user.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import type { Effect, Reducer } from 'umi';
|
||||
|
||||
import { queryCurrent, query as queryUsers } from '@/servers/user';
|
||||
|
||||
export type CurrentUser = {
|
||||
avatar?: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
group?: string;
|
||||
signature?: string;
|
||||
tags?: {
|
||||
key: string;
|
||||
label: string;
|
||||
}[];
|
||||
userid?: string;
|
||||
unreadCount?: number;
|
||||
};
|
||||
|
||||
export type UserModelState = {
|
||||
currentUser?: CurrentUser;
|
||||
};
|
||||
|
||||
export type UserModelType = {
|
||||
namespace: 'user';
|
||||
state: UserModelState;
|
||||
effects: {
|
||||
fetch: Effect;
|
||||
fetchCurrent: Effect;
|
||||
};
|
||||
reducers: {
|
||||
saveCurrentUser: Reducer<UserModelState>;
|
||||
changeNotifyCount: Reducer<UserModelState>;
|
||||
};
|
||||
};
|
||||
|
||||
const UserModel: UserModelType = {
|
||||
namespace: 'user',
|
||||
|
||||
state: {
|
||||
currentUser: {},
|
||||
},
|
||||
|
||||
effects: {
|
||||
*fetch(_, { call, put }) {
|
||||
const response = yield call(queryUsers);
|
||||
yield put({
|
||||
type: 'save',
|
||||
payload: response,
|
||||
});
|
||||
},
|
||||
*fetchCurrent(_, { call, put }) {
|
||||
const response = yield call(queryCurrent);
|
||||
yield put({
|
||||
type: 'saveCurrentUser',
|
||||
payload: response,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
reducers: {
|
||||
saveCurrentUser(state, action) {
|
||||
return {
|
||||
...state,
|
||||
currentUser: action.payload || {},
|
||||
};
|
||||
},
|
||||
changeNotifyCount(
|
||||
state = {
|
||||
currentUser: {},
|
||||
},
|
||||
action,
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
currentUser: {
|
||||
...state.currentUser,
|
||||
notifyCount: action.payload.totalCount,
|
||||
unreadCount: action.payload.unreadCount,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default UserModel;
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 供应商注册表单通用部分
|
||||
* 封装了国内企业和境外企业注册表单相同的部分
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Form,
|
||||
Input,
|
||||
@ -20,55 +20,26 @@ import {
|
||||
import { UploadOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { message } from 'antd';
|
||||
import { validateFileSize } from '@/utils/utils';
|
||||
import { getRegionTree, getregionInternational } from '@/servers/api/register';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
// 中国省市区级联数据
|
||||
export const addressOptions = [
|
||||
{
|
||||
value: '330000',
|
||||
label: '浙江省',
|
||||
children: [
|
||||
{
|
||||
value: '330100',
|
||||
label: '杭州市',
|
||||
children: [
|
||||
{ value: '330102', label: '上城区' },
|
||||
{ value: '330103', label: '下城区' },
|
||||
{ value: '330104', label: '江干区' },
|
||||
{ value: '330105', label: '拱墅区' },
|
||||
{ value: '330106', label: '西湖区' },
|
||||
{ value: '330108', label: '滨江区' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: '310000',
|
||||
label: '上海市',
|
||||
children: [
|
||||
{
|
||||
value: '310100',
|
||||
label: '上海市',
|
||||
children: [
|
||||
{ value: '310101', label: '黄浦区' },
|
||||
{ value: '310104', label: '徐汇区' },
|
||||
{ value: '310105', label: '长宁区' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
interface CommonFormSectionsProps {
|
||||
form: any;
|
||||
supplierType?: string;
|
||||
}
|
||||
|
||||
// 扩展问卷部分的属性接口
|
||||
interface SurveySectionProps extends CommonFormSectionsProps {
|
||||
surveyQuestions?: API.SurveyQuestionResponse;
|
||||
}
|
||||
|
||||
function convertToCascaderOptions(data: any[]): any[] {
|
||||
return data.map(item => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
children: item.children && item.children.length > 0 ? convertToCascaderOptions(item.children) : undefined,
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* 资质信息表单部分
|
||||
* 包含资质证书类型、名称、编号、等级、发证机构、发证日期、有效期等
|
||||
@ -326,7 +297,21 @@ export const InvoiceSection: React.FC<CommonFormSectionsProps> = ({ form }) => {
|
||||
* 银行账户表单部分
|
||||
* 包含开户银行、账户名称、账号、所在地区等
|
||||
*/
|
||||
export const BankAccountSection: React.FC<CommonFormSectionsProps> = ({ form }) => {
|
||||
export const BankAccountSection: React.FC<CommonFormSectionsProps> = ({ form, supplierType }) => {
|
||||
// 地区
|
||||
const [addressOptions, setAddressOptions] = useState<API.RegionOption[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if(supplierType) {
|
||||
const submitInterface = supplierType === 'dvs'? getRegionTree : getregionInternational;
|
||||
submitInterface().then(res => {
|
||||
if (res.code === 200) {
|
||||
setAddressOptions(convertToCascaderOptions(res.data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}, [supplierType]);
|
||||
return (
|
||||
<>
|
||||
<div className="form-section-title">银行账户</div>
|
||||
@ -391,7 +376,7 @@ export const BankAccountSection: React.FC<CommonFormSectionsProps> = ({ form })
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '国家、省、市',
|
||||
title: '地址',
|
||||
dataIndex: 'location',
|
||||
render: (_, record) => (
|
||||
<Form.Item
|
||||
@ -401,7 +386,7 @@ export const BankAccountSection: React.FC<CommonFormSectionsProps> = ({ form })
|
||||
>
|
||||
<Cascader
|
||||
options={addressOptions}
|
||||
placeholder="请选择省市区"
|
||||
placeholder="请选择地址"
|
||||
showSearch={{
|
||||
filter: (inputValue, path) => {
|
||||
return path.some((option) => {
|
||||
|
@ -256,12 +256,12 @@ const DomesticForm: React.FC<DomesticFormProps> = ({
|
||||
{ required: true, message: '请输入电子邮箱' },
|
||||
]}
|
||||
>
|
||||
<Input prefix={<MailOutlined />} placeholder="请输入企业联系电话" />
|
||||
<Input prefix={<MailOutlined />} placeholder="XXX@XXX.com " />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="telephone" label="固定电话">
|
||||
<Input placeholder="XXX@XXX.com" />
|
||||
<Input placeholder="请输入企业联系电话" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -269,7 +269,7 @@ const DomesticForm: React.FC<DomesticFormProps> = ({
|
||||
{/* 使用通用表单组件 */}
|
||||
<QualificationSection form={form} />
|
||||
<InvoiceSection form={form} />
|
||||
<BankAccountSection form={form} />
|
||||
<BankAccountSection form={form} supplierType={'dvs'} />
|
||||
<SurveySection form={form} surveyQuestions={surveyQuestions} />
|
||||
<AttachmentSection form={form} />
|
||||
</>
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* 境外企业 表单项 */
|
||||
import React from 'react';
|
||||
import { Form, Input, Button, Select, Row, Col } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Form, Input, Button, Select, Row, Col, Cascader } from 'antd';
|
||||
import {
|
||||
MobileOutlined,
|
||||
MailOutlined,
|
||||
@ -17,6 +17,8 @@ import {
|
||||
AttachmentSection,
|
||||
} from './CommonFormSections';
|
||||
|
||||
import { getregionInternational } from '@/servers/api/register';
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
|
||||
@ -33,6 +35,16 @@ interface ForeignFormProps {
|
||||
* 其他部分使用通用表单组件
|
||||
*/
|
||||
const ForeignForm: React.FC<ForeignFormProps> = ({ form, countdown, handleGetCaptcha, surveyQuestions }) => {
|
||||
// 全球
|
||||
const [regionOptions, setRegionOptions] = useState<API.RegionOption[]>([]);
|
||||
useEffect(() => {
|
||||
getregionInternational().then(res => {
|
||||
if (res.code === 200) {
|
||||
setRegionOptions(res.data);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 境外企业特有的基本信息部分 */}
|
||||
@ -73,16 +85,11 @@ const ForeignForm: React.FC<ForeignFormProps> = ({ form, countdown, handleGetCap
|
||||
rules={[{ required: true, message: '请选择国家/地区' }]}
|
||||
>
|
||||
<Select placeholder="请选择国家/地区">
|
||||
<Option value="US">美国</Option>
|
||||
<Option value="UK">英国</Option>
|
||||
<Option value="JP">日本</Option>
|
||||
<Option value="DE">德国</Option>
|
||||
<Option value="FR">法国</Option>
|
||||
<Option value="AU">澳大利亚</Option>
|
||||
<Option value="CA">加拿大</Option>
|
||||
<Option value="SG">新加坡</Option>
|
||||
<Option value="HK">中国香港</Option>
|
||||
<Option value="OTHER">其他</Option>
|
||||
{
|
||||
regionOptions.map((item) => {
|
||||
return <Option value={item.id}>{item.name}</Option>
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
@ -307,7 +314,7 @@ const ForeignForm: React.FC<ForeignFormProps> = ({ form, countdown, handleGetCap
|
||||
{/* 使用通用表单组件 */}
|
||||
<QualificationSection form={form} />
|
||||
<InvoiceSection form={form} />
|
||||
<BankAccountSection form={form} />
|
||||
<BankAccountSection form={form} supplierType={'ovs'} />
|
||||
<SurveySection form={form} surveyQuestions={surveyQuestions} />
|
||||
<AttachmentSection form={form} />
|
||||
</>
|
||||
|
@ -80,8 +80,8 @@ const supplierNews: React.FC = () => {
|
||||
},
|
||||
{
|
||||
title: '业务类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
dataIndex: 'typeCn',
|
||||
key: 'typeCn',
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
|
@ -120,12 +120,7 @@ const SupplierAccessDetailModal: React.FC<SupplierAccessDetailModalProps> = ({ v
|
||||
key: "exitTime",
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: "进入黑名单时间",
|
||||
dataIndex: "blackTime",
|
||||
key: "blackTime",
|
||||
align: "center",
|
||||
},
|
||||
|
||||
];
|
||||
return (
|
||||
<Modal
|
||||
|
@ -115,12 +115,7 @@ const SupplierAccessDetailModal: React.FC<SupplierAccessDetailModalProps> = ({ v
|
||||
key: "exitTime",
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: "进入黑名单时间",
|
||||
dataIndex: "blackTime",
|
||||
key: "blackTime",
|
||||
align: "center",
|
||||
},
|
||||
|
||||
];
|
||||
return (
|
||||
<Modal
|
||||
|
@ -11,7 +11,7 @@ const messageTypeOptions = [
|
||||
{ label: '供应商评价', value: '供应商评价' },
|
||||
{ label: '供应商评审', value: '供应商评审' },
|
||||
{ label: '供应商退出', value: '供应商退出' },
|
||||
{ label: '供应商黑灰名单', value: '供应商黑灰名单' },
|
||||
{ label: '供应商黑名单', value: '供应商黑名单' },
|
||||
];
|
||||
|
||||
const SupplierMessage: React.FC = () => {
|
||||
|
@ -21,3 +21,17 @@ export async function fetchSurveyQuestions(): Promise<API.APIResponse<API.Survey
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
// 获取省市县
|
||||
export async function getRegionTree(){
|
||||
return request('/api/cosco/dictRegion/region', {
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
// 获取全球
|
||||
export async function getregionInternational(){
|
||||
return request('/api/cosco/dictRegion/regionInternational', {
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
8
src/servers/api/typings.d.ts
vendored
@ -5,6 +5,14 @@ declare namespace API {
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
// 省市区
|
||||
interface RegionOption {
|
||||
label: string;
|
||||
value: string;
|
||||
id: string;
|
||||
name: string;
|
||||
children?: RegionOption[];
|
||||
}
|
||||
|
||||
// 用户相关接口类型定义
|
||||
export interface UserListRequest {
|
||||
|
13
src/servers/user.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
export async function query(): Promise<any> {
|
||||
return request('/api/users');
|
||||
}
|
||||
|
||||
export async function queryCurrent(): Promise<any> {
|
||||
return request('/api/currentUser');
|
||||
}
|
||||
|
||||
export async function queryNotices(): Promise<any> {
|
||||
return request('/api/notices');
|
||||
}
|
16
src/utils/Authorized.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import RenderAuthorize from '@/components/Authorized';
|
||||
import { getAuthority } from './authority';
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable import/no-mutable-exports */
|
||||
let Authorized = RenderAuthorize(getAuthority());
|
||||
|
||||
// Reload the rights component
|
||||
const reloadAuthorized = (): void => {
|
||||
Authorized = RenderAuthorize(getAuthority());
|
||||
};
|
||||
|
||||
/** Hard code block need it。 */
|
||||
window.reloadAuthorized = reloadAuthorized;
|
||||
|
||||
export { reloadAuthorized };
|
||||
export default Authorized;
|
32
src/utils/authority.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { reloadAuthorized } from './Authorized';
|
||||
|
||||
// use localStorage to store the authority info, which might be sent from server in actual project.
|
||||
export function getAuthority(str?: string): string | string[] {
|
||||
const authorityString =
|
||||
typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str;
|
||||
// authorityString could be admin, "admin", ["admin"]
|
||||
let authority;
|
||||
try {
|
||||
if (authorityString) {
|
||||
authority = JSON.parse(authorityString);
|
||||
}
|
||||
} catch (e) {
|
||||
authority = authorityString;
|
||||
}
|
||||
if (typeof authority === 'string') {
|
||||
return [authority];
|
||||
}
|
||||
// preview.pro.ant.design only do not use in your production.
|
||||
// preview.pro.ant.design Dedicated environment variable, please do not use it in your project.
|
||||
if (!authority && ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
|
||||
return ['admin'];
|
||||
}
|
||||
return authority;
|
||||
}
|
||||
|
||||
export function setAuthority(authority: string | string[]): void {
|
||||
const proAuthority = typeof authority === 'string' ? [authority] : authority;
|
||||
localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority));
|
||||
// auto reload
|
||||
reloadAuthorized();
|
||||
}
|