pull 代码

This commit is contained in:
谢思 2022-03-16 14:37:33 +08:00
commit da41f9582e
38 changed files with 1849 additions and 361 deletions

View File

@ -61,6 +61,12 @@ class Index extends Component {
<ProjectNew {...this.props} {...props} />
)}
></Route>
<Route
path="/projects"
render={(props) => (
<ProjectIndex {...this.props} {...props} />
)}
></Route>
<Route
path="/explore/all"
render={(props) => (

View File

@ -10,12 +10,12 @@ import img_parise from '../Images/parise.png';
import SpecialModal from './SpecialModal';
class IndexItem extends Component {
constructor(props){
constructor(props) {
super(props);
this.state={
visible:false,
user_apply_signatures:[],
project_id:undefined
this.state = {
visible: false,
user_apply_signatures: [],
project_id: undefined
}
}
TurnToDetail = (login, url) => {
@ -31,31 +31,31 @@ class IndexItem extends Component {
* is_secret:是否是特殊开源许可证项目
* id创建者login
* is_member:是否是项目成员(如果是项目成员可以直接进入项目)
* */
projectHref=(link , user_apply_signatures,project_id,is_secret , id,is_member)=>{
const { user , showLoginDialog } = this.props;
if(is_secret && (!user || (user && !user.login))){
* */
projectHref = (link, user_apply_signatures, project_id, is_secret, id, is_member) => {
const { user, showLoginDialog } = this.props;
if (is_secret && (!user || (user && !user.login))) {
showLoginDialog();
return;
}
let signa = user_apply_signatures && user_apply_signatures[0];
if((is_secret && !is_member && (!signa || (signa && signa.status !== "passed"))) && user.login !== id ){
if ((is_secret && !is_member && (!signa || (signa && signa.status !== "passed"))) && user.login !== id) {
this.setState({
visible:true,
user_apply_signatures:user_apply_signatures.length>0 ? user_apply_signatures[0] : undefined,
visible: true,
user_apply_signatures: user_apply_signatures.length > 0 ? user_apply_signatures[0] : undefined,
project_id
})
}else{
} else {
this.props.history.push(link);
}
}
hideModal=()=>{
hideModal = () => {
this.setState({
visible:false
visible: false
})
}
sureModal=()=>{
sureModal = () => {
this.hideModal();
const { getListData } = this.props;
getListData && getListData(1);
@ -63,26 +63,26 @@ class IndexItem extends Component {
render() {
const { projects } = this.props;
const { visible , user_apply_signatures , project_id } = this.state;
const { visible, user_apply_signatures, project_id } = this.state;
return (
<div className="project-list minH-670" style={{padding:"0px 20px"}}>
<div className="project-list minH-670" style={{ padding: "0px 20px" }}>
<SpecialModal {...this.props} visible={visible} hideModal={this.hideModal} user_apply_signatures={user_apply_signatures} project_id={project_id} sureModal={this.sureModal}></SpecialModal>
{ projects && projects.length > 0 ? projects.map((item, key) => {
{projects && projects.length > 0 ? projects.map((item, key) => {
return (
<div className="p-r-Item" key={key}>
{
item.platform === "educoder" ?
<a href="javascript:void(0)" style={{cursor:"default"}} className="show-user-link">
<img className="p-r-photo" alt="" src={item.author && item.author.image_url} ></img>
</a>
:
<Link to={`/${item.author && item.author.login}`} className="show-user-link">
<img className="p-r-photo" alt="" src={getImageUrl(`/${item.author && item.author.image_url}`)} ></img>
</Link>
<a href="javascript:void(0)" style={{ cursor: "default" }} className="show-user-link">
<img className="p-r-photo" alt="" src={item.author && item.author.image_url} ></img>
</a>
:
<Link to={`/${item.author && item.author.login}`} className="show-user-link">
<img className="p-r-photo" alt="" src={getImageUrl(`/${item.author && item.author.image_url}`)} ></img>
</Link>
}
<div className="p-r-Infos">
<div className="p-r-name">
<AlignCenter>
{/* <AlignCenter>
<Link to={`/${item.author.login}/${item.identifier}`} title={`${item.author.name}/${item.name}`} className="color-grey-3 font-18 task-hide " style={{maxWidth: 470 }}>
{item.author.name}/{item.name}
</Link>
@ -100,7 +100,27 @@ class IndexItem extends Component {
<i className="iconfont icon-banbenku font-18 color-green" />
</Tooltip>:""
}
</AlignCenter>
</AlignCenter> */}
<a onClick={() => this.projectHref(`/${item.author.login}/${item.identifier}`, item.user_apply_signatures, item.id, item.is_secret, item.author.login, item.is_member)} className="hide-1 color-grey-3 font-18 task-hide fwt-500 " style={{ whiteSpace: "wrap", display: 'flex', width: 400 }}>
{item.author.name}/{item.name}
{
item.forked_from_project_id ?
<span className="ml5">
<i className="iconfont icon-fork font-18 color-orange" />
</span>
: ""
}
{
item.type && item.type !== 0 ?
item.type === 2 ?
<Tooltip title="该项目是一个镜像" className="ml5">
<i className="iconfont icon-banbenku font-18 color-green" />
</Tooltip> :
<span className="ml5">
<i className="iconfont icon-jingxiang font-18 color-green" />
</span> : ""
}
</a>
<span className="p-r-tags">
<span className="pariseTag">
<img src={img_parise} alt="" className="pariseImg" /> {item.praises_count}
@ -110,7 +130,7 @@ class IndexItem extends Component {
</span>
</span>
</div>
<p className="break_word task-hide-2 mt10" style={{ maxHeight: "44px",lineHeight:"22px" }}>{item.description}</p>
<p className="break_word task-hide-2 mt10" style={{ maxHeight: "44px", lineHeight: "22px" }}>{item.description}</p>
<div className="p-r-about">
<span className="p-r-detail">
@ -122,7 +142,7 @@ class IndexItem extends Component {
</div>
)
}) : <Nodata _html="暂无数据~"></Nodata>
}
}
</div>
)
}

View File

@ -16,8 +16,10 @@ function SpecialModal({ visible , hideModal , sureModal , showNotification , use
function sure(){
if(!user_apply_signatures || (user_apply_signatures && user_apply_signatures.status !== "waiting")){
if(!id || (id && id.length === 0)){
// showNotification("");
getUrl(`/api/apply_signatures/template_file`)
const a = document.createElement('a');
a.href = getUrl(`/api/apply_signatures/template_file`);
a.click(); //
showNotification("请先提交文件进行审核!");
return;
}
const url = `/apply_signatures.json`;

View File

@ -50,7 +50,7 @@ function SubBanner() {
return(
<div className="banners">
<div className="bannersCenter">
<p className="bTitle"><span>GitLink 确实开源</span></p>
<p className="bTitle"><span>osredm 红山开源</span></p>
<p className="bSubTitle">新一代开发创新服务平台 让你的创意在这里释放</p>
<div className="bannerBox">
{

View File

@ -96,15 +96,15 @@ function Index(props){
<li>消息通知</li>
<li className={(pathname.indexOf("/settings/notice")>-1 && pathname.indexOf("/settings/notice/config") == -1) || pathname.indexOf("/settings/notice/privateLetter")>-1 ?"active":""}><Link to={"/settings/notice"}><i className="iconfont icon-wodetongzhi"></i><span className="text-shodow-bold">我的通知</span></Link></li>
<li className={pathname.indexOf("/settings/notice/config")>-1 ?"active":""}><Link to={'/settings/notice/config'}><i className="iconfont icon-tongzhiguanli"></i><span className="text-shodow-bold">通知管理</span></Link></li>
</ul>}
<ul className="securityUl">
<li>安全设置</li>
<li className={pathname.indexOf("/settings/SSH")>-1 ?"active":""}><Link to={`/settings/SSH`}><i className="iconfont icon-xuanzhongssh_icon mr5 font-14"></i><span className="text-shodow-bold">SSH密钥</span></Link></li>
</ul> */}
<ul className="securityUl ul-border-buttom">
</ul>} */}
{pathname.indexOf("/settings/notice")>-1 && <ul className="securityUl ul-border-buttom">
<li>消息通知</li>
<li className={(pathname.indexOf("/settings/notice")>-1 && pathname.indexOf("/settings/notice/config") == -1) || pathname.indexOf("/settings/notice/privateLetter")>-1 ?"active":""}><Link to={"/settings/notice"}><i className="iconfont icon-wodetongzhi"></i><span className="text-shodow-bold">我的通知</span></Link></li>
</ul>
</ul>}
{pathname.indexOf("/settings/SSH")>-1 &&<ul className="securityUl">
<li>安全设置</li>
<li className={pathname.indexOf("/settings/SSH")>-1 ?"active":""}><Link to={`/settings/SSH`}><i className="iconfont icon-xuanzhongssh_icon mr5 font-14"></i><span className="text-shodow-bold">SSH密钥</span></Link></li>
</ul>}
</div>
<LongWidth>
<Gap>

View File

@ -113,7 +113,7 @@ class Index extends Component {
</p>
</li>
{
false && projectDetail && projectDetail.permission && (projectDetail.permission === "Owner" || projectDetail.permission === "Admin") ?
projectDetail && projectDetail.permission && (projectDetail.permission === "Owner" || projectDetail.permission === "Admin") ?
<li
className={pathname.indexOf("settings/special") > -1 ? "active" : ""}
>

View File

@ -0,0 +1,152 @@
import React, { useEffect } from 'react';
import * as echarts from 'echarts';
let fontSizeText = 16;
let clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
fontSizeText = 16
} else if (clientWidth >= 1000 && clientWidth <= 2000) {
fontSizeText = 14 + (clientWidth - 1000) / 500;
} else if (clientWidth > 2000) {
fontSizeText = 16 + (clientWidth - 2000) / 500;
} else if (clientWidth > 3000) {
fontSizeText = 18 + (clientWidth - 3000) / 2000;
}
let fontSizeTitle = 1.25 * fontSizeText;
export default ({ id,className, title, xData, yData }) => {
useEffect(() => {
let newEchartBar = document.getElementById(id) && echarts.init(document.getElementById(id));
let textColor = "#7988a5";
let normalColor = "#e8e8ed";
let option = {
grid: {
left: "3%",
top: "15%",
right: "8%",
bottom: 0,
containLabel: true
},
title: {
text: title,
fontSize: fontSizeTitle,
position: 'inside',
textStyle: {
color: textColor
}
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
textStyle: {
color: "#fff"
}
},
},
xAxis: [{
type: "category",
data: xData,
axisPointer: {
type: "shadow"
},
axisLabel: {
textStyle: {
color: textColor,
fontSize: .75 * fontSizeText
},
interval: 0,
rotate: 40
},
axisLine: {
lineStyle: {
color: normalColor
}
},
axisTick: {
show: false
},
splitLine: {
show: false
}
}],
yAxis: [{
type: "value",
minInterval: 1,
nameTextStyle: {
color: textColor,
fontSize: fontSizeText
},
axisLabel: {
formatter: "{value}",
textStyle: {
color: textColor,
fontSize: fontSizeText
}
},
axisLine: {
lineStyle: {
color: normalColor
}
},
axisTick: {
show: false
},
splitLine: {
lineStyle: {
color: normalColor
}
},
}],
series: [{
type: "bar",
data: yData,
barWidth: "60%",
itemStyle: {
normal: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: "#deeefe"
},
{
offset: 1,
color: "#2c8bff"
}
],
globalCoord: false
}
}
},
label: {
show: true,
// position: ['-20%', -1.2 * fontSizeText],
position: "top",
// color: "#2c8bff",
color: '#555',
fontSize: fontSizeText,
formatter: function (params) {
// var percent = 0;
// percent = ((params.value / allStaff.num) * 100).toFixed(0);
return params.value || ''
// + '\n' + percent + '%';
},
}
}]
};
newEchartBar && newEchartBar.setOption(option);
}, [id, title, xData, yData])
return (
<div id={id} key={id} className={className} style={{minHeight:"20vh"}}>
</div>
)
}

View File

@ -0,0 +1,198 @@
import React, { useEffect, useState } from 'react';
import * as echarts from 'echarts';
let fontSizeText = 16;
let clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
fontSizeText = 16
} else if (clientWidth >= 1000 && clientWidth <= 2000) {
fontSizeText = 14 + (clientWidth - 1000) / 500;
} else if (clientWidth > 2000) {
fontSizeText = 16 + (clientWidth - 2000) / 500;
} else if (clientWidth > 3000) {
fontSizeText = 18 + (clientWidth - 3000) / 2000;
}
const colorList = ["#27e8b4", '#77b4fd', '#ffd55f', '#ac90ef', '#9E87FF'];
export default ({ id = "uid", className, title, xData, seriesArr }) => {
useEffect(() => {
let newEchartBar = document.getElementById(id) && echarts.init(document.getElementById(id));
let option = {
backgroundColor: '#fff',
legend: {
icon: 'circle',
top: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#556677'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: "#DCE2E8"
}
},
backgroundColor: '#fff',
textStyle: {
color: '#5c6c7c'
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)'
},
grid: {
top: '15%'
},
xAxis: [{
type: 'category',
data: xData,
axisLine: {
lineStyle: {
color: '#DCE2E8'
}
},
axisTick: {
show: false
},
axisLabel: {
// interval: 0,
textStyle: {
color: '#556677'
},
// x
fontSize: 0.75*fontSizeText,
// margin:x
margin: fontSizeText,
rotate: 40
},
axisPointer: {
label: {
padding: [0, 0, 10, 0],
/*
除了padding[0]建议必须是0之外其他三项可随意设置
和CSSpadding相同[]
如果需要下边线超出文字设左右padding即可左右padding最好相同
padding[2]的10:
10 = 文字距下边线的距离 + 下边线的宽度
UI图中文字距下边线距离为7 下边线宽度为2
则padding: [0, 0, 9, 0]
*/
// marginaxisLabelmargin!
margin: fontSizeText,
//
fontSize: 0.75*fontSizeText,
backgroundColor: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: '#fff' // 0%
}, {
// offset: 0.9,
offset: 0.86,
//0.86 = + 线/ + 线 + 线
color: '#fff' // 0%
}, {
offset: 0.86,
color: '#33c0cd' // 0%
}, {
offset: 1,
color: '#33c0cd' // 100%
}],
global: false // false
}
}
},
boundaryGap: false
}],
yAxis: [{
name: "(个)",
minInterval: 1,
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: false,
lineStyle: {
color: '#556677'
}
},
axisLabel: {
textStyle: {
color: '#556677'
}
},
splitLine: {
lineStyle: {
color: '#e8e8ed'
}
}
}],
series: [],
};
for (let i = 0; i < seriesArr.length; i++) {
if (seriesArr[i].data) {
option.series.push({
name: seriesArr[i].name,
type: 'line',
data: seriesArr[i].data,
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
itemStyle: {
normal: {
color: colorList[i],
borderColor: colorList[i]
}
}
});
}
if(seriesArr[i].name=='新增代码数'&&seriesArr[i].data){
option.yAxis.push({
name: "(行)",
type: 'value',
position: 'right',
axisTick: {
show: false
},
axisLabel: {
textStyle: {
color: '#556677'
},
formatter: '{value}'
},
axisLine: {
show: false,
lineStyle: {
color: '#556677'
}
},
splitLine: {
show: false
}
});
}
}
newEchartBar && newEchartBar.setOption(option);
}, [xData,seriesArr])
return (
<div id={id} key={id} className={className} style={{ minHeight: "400px" }}>
</div>
)
}

View File

@ -10,6 +10,49 @@
*/
export default [
{
key: "statistics",
title: "概览",
location: "",
icon: "icon-yonghuguanli",
target: "_self",
urltype: "self",
children: [
{
key: "global",
title: "全局统计",
location: "/managements/statistics/global",
menustatus: "Y",
parentKey: "statistics",
icon: "",
target: "",
urltype: "self",
children: [],
},
// {
// key: "member",
// title: "成员工作统计",
// location: "/managements/statistics/member",
// menustatus: "Y",
// parentKey: "statistics",
// icon: "",
// target: "",
// urltype: "self",
// children: [],
// },
// {
// key: "activity",
// title: "项目活跃度统计",
// location: "/managements/statistics/activity",
// menustatus: "Y",
// parentKey: "statistics",
// icon: "",
// target: "",
// urltype: "self",
// children: [],
// },
],
},
{
key: "task",
title: "创客空间",
@ -37,7 +80,7 @@ export default [
parentKey: "task",
icon: "",
target: "_self",
urltype: "",
urltype: "self",
children: [
{
key: "delayManage",
@ -71,7 +114,7 @@ export default [
parentKey: "task",
icon: "",
target: "_self",
urltype: "",
urltype: "self",
children: [
{
key: "overall",
@ -172,7 +215,7 @@ export default [
// parentKey: "task",
// icon: "",
// target: "_self",
// urltype: "",
// urltype: "self",
// children: [
// {
// key: "categories",
@ -396,4 +439,26 @@ export default [
},
],
},
{
key: "settings",
title: "网站配置",
location: "",
icon: "icon-yonghuguanli",
target: "",
urltype: "current_main_site_url",
children: [
{
key: "platform_communicates",
title: "社区动态管理",
location: "/managements/admins/platform_communicates",
menustatus: "Y",
parentKey: "settings",
icon: "",
target: "",
urltype: "current_main_site_url",
params:'layout=none',
children: [],
},
],
},
];

View File

@ -8,10 +8,14 @@ const { SubMenu } = Menu;
const titleObj = {};
const locationObj = {};
const parentKeyObj = {};
const allRouter = [];
const rootSubmenuKeys = [];
function titleFun(menus) {
menus.forEach(i => {
titleObj[i.key] = i.title;
locationObj[i.location] = i;
i.location && allRouter.push(i.location);
!i.parentKey && rootSubmenuKeys.push(i.key);
if (Array.isArray(i.children) && i.children.length > 0) {
titleFun(i.children);
i.parentKey && (parentKeyObj[i.key] = i.parentKey);
@ -21,15 +25,24 @@ function titleFun(menus) {
titleFun(urlConfig);
export default (props) => {
const { current_user, children,location:{pathname} } = props;
const { current_user, children, history, location: { pathname } } = props;
const setting = (localStorage.chromesetting && JSON.parse(localStorage.chromesetting)) || {};
useEffect(() => {
sessionStorage.setItem("current_user", JSON.stringify(current_user));
}, [current_user.login]);
const [current, setCurrent] = useState([locationObj[pathname].key]);
const [keyPath, setKeyPath] = useState([locationObj[pathname].parentKey,locationObj[pathname].key]);
const [acitve, setActive] = useState(locationObj[pathname]);
let initCurrent = locationObj[pathname] ? [locationObj[pathname].key] : [];
let initKeyPath = locationObj[pathname] ? [locationObj[pathname].parentKey, locationObj[pathname].key] : [];
const defaultOpenKeys = [];
const myKey = pathname && locationObj[pathname] && locationObj[pathname].parentKey;
defaultOpenKeys.push(myKey ? myKey : "task");
parentKeyObj[myKey] && defaultOpenKeys.push(parentKeyObj[myKey]);
const [openKeys, setOpenKeys] = useState(defaultOpenKeys);
const [current, setCurrent] = useState(initCurrent);
const [keyPath, setKeyPath] = useState(initKeyPath);
const [acitve, setActive] = useState(locationObj[pathname] || {});
function handleClick(e) {
console.log(e.item);
@ -71,35 +84,55 @@ export default (props) => {
})
}
function iframeLoad(){
try{
document.getElementById("iframe").height=document.getElementById("iframe").contentWindow.top.document.documentElement.scrollHeight;
}catch(err){
function iframeLoad() {
try {
document.getElementById("iframe").height = document.getElementById("iframe").contentWindow.top.document.documentElement.scrollHeight;
} catch (err) {
console.error(err);
}
}
const defaultOpenKeys = [];
const myKey = locationObj && pathname && locationObj[pathname].parentKey;
defaultOpenKeys.push(myKey ? myKey: "task") ;
defaultOpenKeys.push(parentKeyObj[myKey]);
function onOpenChange(newOpenKeys) {
const latestOpenKey = newOpenKeys.find(key => openKeys.indexOf(key) === -1);
if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
setOpenKeys(newOpenKeys);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
}
console.log(newOpenKeys);
}
useEffect(() => {
//
history.listen(historyLocation => {
//
if (allRouter.includes(historyLocation.pathname)) {
setCurrent([locationObj[historyLocation.pathname].key]);
setKeyPath([locationObj[pathname].parentKey, locationObj[pathname].key])
}
})
}, [history]);
console.log(acitve.urltype);
return (
<Fragment>
<div className="layouts">
<Menu
mode="inline"
defaultOpenKeys={defaultOpenKeys}
selectedKeys={current}
onClick={handleClick}
onOpenChange={onOpenChange}
openKeys={openKeys}
>
{getMenuList(urlConfig)}
</Menu>
</div>
<div className="managements">
<div className="head-title">{head(keyPath)}</div>
{acitve.urltype == "self" ? children :
<iframe id="iframe" className="iframe-item" src={`${setting[acitve.urltype] + acitve.location}?${acitve.params}`} onLoad={iframeLoad}></iframe>
{acitve.urltype && acitve.urltype !== 'self' ?
<iframe id="iframe" className="iframe-item" src={`${setting[acitve.urltype]}${acitve.location && acitve.location.startsWith('/managements') ? acitve.location.replace(/\/managements/, "") : acitve.location}?${acitve.params}`} onLoad={iframeLoad}></iframe>
: children
}
</div>
</Fragment>

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
import { Route, Switch } from "react-router-dom";
import { withRouter } from "react-router";
import { SnackbarHOC } from "educoder";
@ -11,6 +10,12 @@ import { ImageLayerOfCommentHOC } from "../modules/page/layers/ImageLayerOfComme
import Layouts from "./components/layouts";
import './index.scss';
// 概览
const Statistics = Loadable({
loader: () => import("./statistics/"),
loading: Loading,
});
{/* 任务管理审核 */ }
const TaskManage = Loadable({
loader: () => import("../military/task/taskManage"),
@ -93,23 +98,8 @@ const CompetitionList = Loadable({
});
// 公告管理
const NoticeList = Loadable({
loader: () => import("./notice/noticeList"),
loading: Loading,
});
// 公告管理详情
const NoticeDetail = Loadable({
loader: () => import("./notice/noticeDetail"),
loading: Loading,
});
// 公告管理编辑
const NoticeEdit = Loadable({
loader: () => import("./notice/noticeEdit"),
loading: Loading,
});
// 公告reader
const NoticeReader = Loadable({
loader: () => import("./notice/noticeReader"),
const NoticeManage = Loadable({
loader: () => import("./notice"),
loading: Loading,
});
@ -136,6 +126,15 @@ const Managements = (propsF) => {
<div className="newMain clearfix">
<Layouts {...propsF} >
<Switch {...propsF}>
{/* 概览 */}
<Route
path="/managements/statistics"
render={(props) => (
<Statistics {...propsF} {...props} />
)}
></Route>
{/* 任务管理审核 */}
<Route
path="/managements/task/taskManage/:publishMode"
@ -208,8 +207,6 @@ const Managements = (propsF) => {
)}
></Route>
{/* 专家审核 */}
<Route
path="/managements/expert/register"
@ -274,39 +271,11 @@ const Managements = (propsF) => {
)}
></Route>
{/* 公告列表 */}
{/* 公告管理 */}
<Route
path="/managements/notice/list/:isChecked"
path="/managements/notice"
render={(props) => (
<NoticeList {...propsF} {...props} />
)}
></Route>
{/* 公告详情 */}
<Route
path="/managements/notice/detail/:noticeId"
render={(props) => (
<NoticeDetail {...propsF} {...props} />
)}
></Route>
{/* 公告编辑 */}
<Route
path="/managements/notice/edit/:noticeId"
render={(props) => (
<NoticeEdit {...propsF} {...props} />
)}
></Route>
{/* 公告新增 */}
<Route
path="/managements/notice/edit"
render={(props) => (
<NoticeEdit {...propsF} {...props} />
)}
></Route>
{/* 公告预览 */}
<Route
path="/managements/notice/reader/:noticeId"
render={(props) => (
<NoticeReader {...propsF} {...props} />
<NoticeManage {...propsF} {...props} />
)}
></Route>
</Switch>

View File

@ -3,6 +3,9 @@
padding:0 1.875rem 1.6rem;
background: #f6f9fe;
min-height: 80vh;
.content{
margin-top:1rem;
}
}
.management-content-head{
background-color: #fff;

View File

@ -27,3 +27,16 @@
.encrypt-item .ant-form-item-label{
visibility: hidden;
}
.ant-radio-checked .ant-radio-inner{
border-color: #4154f1;
}
.ant-radio-inner::after{
background-color: #4154f1;
}
.ant-radio-wrapper:hover .ant-radio, .ant-radio:hover .ant-radio-inner, .ant-radio-input:focus + .ant-radio-inner{
border-color: #4154f1;
}
.ant-radio-checked::after{
border: 1px solid #4154f1;
}

View File

@ -0,0 +1,67 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import Loadable from "react-loadable";
import Loading from "../../Loading";
//
const NoticeList = Loadable({
loader: () => import("./noticeList"),
loading: Loading,
});
//
const NoticeDetail = Loadable({
loader: () => import("./noticeDetail"),
loading: Loading,
});
//
const NoticeEdit = Loadable({
loader: () => import("./noticeEdit"),
loading: Loading,
});
// reader
const NoticeReader = Loadable({
loader: () => import("./noticeReader"),
loading: Loading,
});
export default (propsF)=>{
return (
<Switch {...propsF}>
{/* 公告列表 */}
<Route
path="/managements/notice/list/:isChecked"
render={(props) => (
<NoticeList {...propsF} {...props} />
)}
></Route>
{/* 公告详情 */}
<Route
path="/managements/notice/detail/:noticeId"
render={(props) => (
<NoticeDetail {...propsF} {...props} />
)}
></Route>
{/* 公告编辑 */}
<Route
path="/managements/notice/edit/:noticeId"
render={(props) => (
<NoticeEdit {...propsF} {...props} />
)}
></Route>
{/* 公告新增 */}
<Route
path="/managements/notice/edit"
render={(props) => (
<NoticeEdit {...propsF} {...props} />
)}
></Route>
{/* 公告预览 */}
<Route
path="/managements/notice/reader/:noticeId"
render={(props) => (
<NoticeReader {...propsF} {...props} />
)}
></Route>
</Switch>
)
}

View File

@ -42,7 +42,7 @@ const IndexPage = ({ match, history, current_user: { admin } }) => {
}
function download(url) {
if (location.href.indexOf('localhost') > -1) {
if (window.location.href.indexOf('localhost') > -1) {
url = 'http://106.75.31.211:58088' + url;
}
window.open(url);

View File

@ -1,5 +1,5 @@
import React, { forwardRef, useEffect, useState, useCallback } from 'react';
import { Table, Pagination, Button, Form, Modal, Input, Select, Radio, } from 'antd';
import { Button, Form, Modal, Input, Select, Radio, } from 'antd';
import PaginationTable from "../../../components/pagination-table";
import { noticeType } from '../../common/static';
import DelModal from '../../components/DelModal';
@ -27,6 +27,7 @@ const NoticeList = Form.create()(forwardRef(({ form, match, history, current_use
const [type, setType] = useState(undefined);
const [title, setTitle] = useState(undefined);
const [pageSize, setPageSize] = useState(10);
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [orderBy, setOrderBy] = useState('');
@ -64,7 +65,7 @@ const NoticeList = Form.create()(forwardRef(({ form, match, history, current_use
orderBy,
curPage,
isChecked,
pageSize: 10,
pageSize,
status,
type,
title,
@ -79,7 +80,7 @@ const NoticeList = Form.create()(forwardRef(({ form, match, history, current_use
setDataList([]);
}
});
}, [curPage, type, title, isChecked, reload]);
}, [curPage, type, title, isChecked, reload,pageSize]);
const columns = [

View File

@ -1,5 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Button, Descriptions,Pagination, Icon, Table } from 'antd';
import { Button, Descriptions, Pagination, Icon, Table } from 'antd';
import PaginationTable from "../../../components/pagination-table";
import { noticeType, noticeChecked } from '../../common/static';
import { getNoticeDetail, getNoticeReader } from '../api';
import '../index.css';
@ -7,11 +8,11 @@ import './index.scss';
const noticeTypeArr = [];
for (const item of noticeType) {
noticeTypeArr[item.code]=(item.name);
noticeTypeArr[item.code] = (item.name);
}
const noticeCheckedArr = [];
for (const item of noticeChecked) {
noticeCheckedArr[item.code]=(item.name);
noticeCheckedArr[item.code] = (item.name);
}
const NoticeReader = ({ match, history }) => {
@ -19,6 +20,7 @@ const NoticeReader = ({ match, history }) => {
const [noticeData, setNoticeData] = useState({});
const [loading, setLoading] = useState(false);
const [dataList, setDataList] = useState([]);
const [pageSize, setPageSize] = useState(10);
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
@ -31,9 +33,9 @@ const NoticeReader = ({ match, history }) => {
useEffect(() => {
setLoading(true);
id && getNoticeReader({
currentPage:curPage,
annId:id,
pageSize:10
currentPage: curPage,
annId: id,
pageSize,
}).then(data => {
setLoading(false);
if (data) {
@ -44,63 +46,56 @@ const NoticeReader = ({ match, history }) => {
setDataList([]);
}
})
}, [id,curPage]);
function download(url) {
if (location.href.indexOf('localhost') > -1) {
url = 'http://106.75.31.211:58088' + url;
}
window.open(url);
}
}, [id, curPage, pageSize]);
const columns = [
{
title: '用户姓名',
key: 'readerName',
dataIndex: 'readerName',
width:'20%',
width: '20%',
},
{
title: '公司名称',
key: 'companyName',
dataIndex: 'companyName',
width:'40%',
width: '40%',
},
{
title: '联系方式',
key: 'contactInfo',
dataIndex: 'contactInfo',
width:'40%',
width: '40%',
},
];
function onShowSizeChange(current, pageSize) {
setCurPage(current);
setPageSize(pageSize);
}
return (
<div className="notice-content">
<h4 className="notice-title"><span className='backList' onClick={() => { history.go(-1) }}><Icon type="left" />返回</span>浏览过该公告加密内容的用户详情</h4>
<Descriptions className='itemContent' column={1}>
<Descriptions.Item label="公告标题">{noticeData.title}</Descriptions.Item>
<Descriptions.Item label="加密信息被浏览数">{total}</Descriptions.Item>
<Descriptions.Item label="浏览过该公告加密内容的用户信息"></Descriptions.Item>
<Descriptions.Item label="公告标题">{noticeData.title}</Descriptions.Item>
<Descriptions.Item label="加密信息被浏览数">{total}</Descriptions.Item>
<Descriptions.Item label="浏览过该公告加密内容的用户信息"></Descriptions.Item>
</Descriptions>
<div className="table-detail">
<Table
<PaginationTable
loading={loading}
rowKey={(row) => row.id}
dataSource={dataList}
columns={columns}
pagination={false}
total={total}
setCurPage={setCurPage}
current={curPage}
onShowSizeChange={onShowSizeChange}
showSizeChanger
/>
{dataList.length > 0 &&
<Pagination
onChange={(page) => { setCurPage(page) }}
current={curPage}
total={total}
/>}
</div>
</div>
</div>
)

View File

@ -0,0 +1,175 @@
import React, { useEffect, useState } from 'react';
import { Table, Pagination, notification, Input } from 'antd';
import { paramToUrl } from '../../common/utils';
import fetch from 'military/notice/fetch';
const { Search } = Input;
export default function Activity(props) {
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [orderBy, setOrderBy] = useState('');
const [dataList, setDataList] = useState([]);
const [loading, setLoading] = useState(false);
const [projectName, setProjectName] = useState('');
useEffect(() => {
// table
let params = {
orderBy,
curPage,
projectName,
pageSize: 10,
};
let url = encodeURI(paramToUrl('/api/ProjectActivityData/', params));
setLoading(true);
fetch({
url,
method: 'get',
}).then(res => {
if (res.code === '1') {
setDataList(res.data.rows);
setTotal(res.data.total);
} else {
setDataList([]);
setTotal(0);
notification.open({
message: "错误",
description: res.message,
});
}
setLoading(false);
});
}, [curPage, orderBy, projectName]);
function handleTableChange(pagination, filters, sorter) {
if (sorter.order) {
if (sorter.order === 'ascend') {
setOrderBy(sorter.field + 'Asc');
} else {
setOrderBy(sorter.field + 'Desc');
}
} else {
setOrderBy('');
}
}
function onCell(record) {
return {
onClick: event => { onCellClick(record) }, //
}
}
function onCellClick(record) {
let url = encodeURI(paramToUrl('/api/ProjectActivityData/getUrl', { projectId: record.projectId }));
fetch({
url,
method: 'get',
}).then(res => {
if (res.code === '1') {
window.open(res.data);
} else {
notification.open({
message: "错误",
description: res.message,
});
}
});
}
const columns = [
{
title: '项目名',
dataIndex: 'name',
key: 'name',
className: 'link',
onCell,
},
{
title: '活跃度',
key: 'activityScore',
dataIndex: 'activityScore',
sorter: true,
},
{
title: '浏览数',
key: 'visits',
dataIndex: 'visits',
sorter: true,
},
{
title: '点赞数',
key: 'praisesCount',
dataIndex: 'praisesCount',
sorter: true,
},
{
title: '关注数',
key: 'watchersCount',
dataIndex: 'watchersCount',
sorter: true,
},
{
title: '任务数',
key: 'issuesCount',
dataIndex: 'issuesCount',
sorter: true,
},
{
title: 'PR数',
key: 'pullRequestsCount',
dataIndex: 'pullRequestsCount',
sorter: true,
},
{
title: '版本数',
key: 'versionsCount',
dataIndex: 'versionsCount',
sorter: true,
},
];
function searchFun(val) {
setProjectName(val);
setCurPage(1);
}
return (
<div className="content">
<Search
style={{ width: '300px', margin: '1em' }}
placeholder="请输入项目关键字"
enterButton="搜索"
size="large"
onSearch={searchFun}
className="global-search"
maxLength={20}
/>
<div className="table-detail">
<Table
loading={loading}
rowKey={(row) => row.id}
dataSource={dataList}
columns={columns}
pagination={false}
onChange={handleTableChange} //
/>
{dataList.length > 0 &&
<Pagination
onChange={(page) => { setCurPage(page) }}
current={curPage}
total={total}
/>}
</div>
</div>
);
}

View File

@ -0,0 +1,134 @@
import React, { useEffect, useState } from 'react';
import { Icon, Col, Row, Tabs, notification } from 'antd';
import EchartBar from '../../components/EchartBar';
import fetch from 'military/notice/fetch';
import './index.scss';
const { TabPane } = Tabs;
let length = 13;
let nowMonth = new Date().getMonth() + 1;
let year = new Date().getFullYear() - 1;
let monthArr = [];
if (nowMonth === 13) {
nowMonth = 1
}
for (let i = 0; i < length; i++) {
monthArr.push(year + '.' + nowMonth);
nowMonth++;
if (nowMonth === 13) {
nowMonth = 1;
year++;
}
}
export default function Global(props){
const [type, setType] = useState(1);
const [totalProjects, setTotalProjects] = useState(0);
const [totalMembers, setTotalMembers] = useState(0);
const [newAddedProjects, setNewAddedProjects] = useState([]);
const [newAddedMembers, setNewAddedMembers] = useState([]);
const [newCodeSubmissionTimes, setNewCodeSubmissionTimes] = useState([]);
const [taskCompletion, setTaskCompletion] = useState([]);
useEffect(() => {
fetch({
url: '/api/GlobalResourcesData/',
method: 'get',
}).then(res => {
if (res.code === '1') {
const data = res.data;
setTotalProjects(data.totalProjects);
setTotalMembers(data.totalMembers);
setNewAddedProjects(data.newAddedProjects);
setNewAddedMembers(data.newAddedMembers);
setNewCodeSubmissionTimes(data.newCodeSubmissionTimes);
setTaskCompletion(data.taskCompletion);
} else {
notification.open({
message: "错误",
description: res.message,
});
}
});
}, []);
return (
<div className="content">
<Row>
<Col className="box-item" xs={12} lg={6}>
<Icon type="folder" />
<div>
<p className="box-item-tit">项目总数</p>
<p className="box-item-num">{totalProjects}</p>
<p className="box-item-describe">当前项目总数</p>
</div>
</Col>
<Col className="box-item" xs={12} lg={6}>
<Icon type="user" />
<div>
<p className="box-item-tit">用户总数</p>
<p className="box-item-num">{totalMembers}</p>
<p className="box-item-describe">当前用户总数</p>
</div>
</Col>
</Row>
<Tabs defaultActiveKey="1" onChange={(key) => { setType(key) }}>
<TabPane tab="开源统计" key="1">
<Row>
<Col xs={24} lg={12}>
<EchartBar
id="newAddedProjects"
className="echart-box"
title="新项目统计"
xData={monthArr}
yData={newAddedProjects}
/>
</Col>
<Col xs={24} lg={12}>
<EchartBar
id="newAddedMembers"
className="echart-box"
title="新用户统计"
xData={monthArr}
yData={newAddedMembers}
/>
</Col>
<Col xs={24} lg={12}>
<EchartBar
id="newCodeSubmissionTimes"
className="echart-box"
title="代码提交次数"
xData={monthArr}
yData={newCodeSubmissionTimes}
/>
</Col>
<Col xs={24} lg={12}>
<EchartBar
id="taskCompletion"
className="echart-box"
title="任务完成量"
xData={monthArr}
yData={taskCompletion}
/>
</Col>
</Row>
</TabPane>
{/* <TabPane tab="" key="2">
</TabPane>
<TabPane tab="其他" key="4">
</TabPane> */}
</Tabs>
</div>
);
}

View File

@ -0,0 +1,50 @@
.box-item{
display: flex;
max-width: 300px;
justify-content: space-around;
margin: .75em;
padding: .5em;
background: #fff;
border-radius: .5em;
box-shadow: 0 1px 1px #d9d9d9;
.anticon{
font-size: 4em;
color: #1890ff;
}
.box-item-num{
font-size: 2em;
margin-bottom: 0;
}
.box-item-describe{
font-size: .9em;
color: #999;
}
}
.ant-tabs-bar{
border: 0;
}
.ant-tabs-tabpane {
min-height: 25vh;
}
.ant-tabs-tab-active {
color: #000;
font-weight: 600;
}
.ant-tabs-nav .ant-tabs-tab:hover {
color: #000;
font-weight: 600;
}
.echart-box{
height: 300px;
margin: .75em;
padding: 1em;
border-radius: .5em;
background: #fff;
border:1px solid #d9d9d9;
}

View File

@ -0,0 +1,49 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import Loadable from "react-loadable";
import Loading from "../../Loading";
//
const Global = Loadable({
loader: () => import("./global"),
loading: Loading,
});
//
const Member = Loadable({
loader: () => import("./member"),
loading: Loading,
});
//
const Activity = Loadable({
loader: () => import("./activity"),
loading: Loading,
});
export default (propsF)=>{
return (
<Switch {...propsF}>
{/* 全局统计 */}
<Route
path="/managements/statistics/global"
render={(props) => (
<Global {...propsF} {...props} />
)}
></Route>
{/* 成员工作统计 */}
<Route
path="/managements/statistics/member"
render={(props) => (
<Member {...propsF} {...props} />
)}
></Route>
{/* 活跃度统计 */}
<Route
path="/managements/statistics/activity"
render={(props) => (
<Activity {...propsF} {...props} />
)}
></Route>
</Switch>
)
}

View File

@ -0,0 +1,280 @@
import React, { useEffect, useState } from 'react';
import { Col, Row, Select, Button, DatePicker, notification } from 'antd';
import moment from 'moment';
import EchartLine from '../../components/EchartLine';
import { beforeDayArr, paramToUrl } from '../../common/utils';
import TableDetail from './table-detail';
import fetch from 'military/notice/fetch';
import './index.scss';
const { Option } = Select;
const ButtonGroup = Button.Group;
const { RangePicker } = DatePicker;
const monthArr = beforeDayArr();
const nowDate = moment(new Date()).format('YYYY-MM-DD');
const weekAgo = moment(new Date().setDate(new Date().getDate() - 6)).format('YYYY-MM-DD');
console.log(weekAgo);
const monthAgo = moment(new Date().setDate(new Date().getDate() - 30)).format('YYYY-MM-DD');
const threeMonthAgo = moment(new Date().setDate(new Date().getDate() - 90)).format('YYYY-MM-DD');
export default function Member(props) {
const [member, setMember] = useState(undefined);
const [memberList, setMemberList] = useState([]);
const [timeType, setTimeType] = useState(2);
const [dayNum, setDayNum] = useState(30);
const [timeArr, setTimeArr] = useState(monthArr);
const [startTime, setStartTime] = useState(monthAgo); //
const [endTime, setEndTime] = useState(nowDate);
const [newTask, setNewTask] = useState(null);
const [completedTask, setCompletedTask] = useState(null);
const [newPrNum, setNewPrNum] = useState(null);
const [newCommitNum, setNewCommitNum] = useState(null);
const [newCodeNum, setNewCodeNum] = useState(null);
// 线
function getAllDataLine() {
let params = {
startTime,
endTime,
}
let url = encodeURI(paramToUrl('/api/DeveloperData/total', params));
fetch({
url,
method: 'get',
}).then(res => {
if (res.code === '1') {
const data = res.data;
setNewTask(data.newTask);
setCompletedTask(data.completedTask);
setNewPrNum(data.newPrNum);
setNewCommitNum(data.newCommitNum);
setNewCodeNum(data.newCodeNum);
}
});
}
//
function getPersonData() {
let params = {
startTime,
endTime,
curPage: 1,
pageSize: 31,
userId: member.key,
userName:member.login,
};
if (timeType == 1) {
params.pageSize = 7;
} else if (timeType == 3) {
params.pageSize = 91;
} else if (timeType == 0) {
params.pageSize = dayNum;
}
let url = encodeURI(paramToUrl('/api/DeveloperData/detail/search', params));
fetch({
url,
method: 'get',
}).then(res => {
let newTask = [];
let completedTask = [];
let newPrNum = [];
let newCommitNum = [];
let newCodeNum = [];
if (res.code === '1' && res.data.rows) {
if (res.data.rows.length) {
for (const item of res.data.rows) {
newTask.unshift(item.newTask);
completedTask.unshift(item.completedTask);
newPrNum.unshift(item.newPrNum);
newCommitNum.unshift(item.newCommitNum);
newCodeNum.unshift(item.newCodeNum);
}
} else {
newCodeNum.fill(0, 0,);
}
} else {
notification.open({
message: "错误",
description: res.message,
});
}
setNewTask(newTask);
setCompletedTask(completedTask);
setNewPrNum(newPrNum);
setNewCommitNum(newCommitNum);
setNewCodeNum(newCodeNum);
});
}
function onSearch(value) {
if (value) {
getMemberList(value);
}
}
//
let timeout;
function getMemberList(value) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
function fake() {
fetch({
url: '/api/DeveloperData/detail/searchInput?userName=' + value,
method: 'get',
}).then(res => {
if (res.code === '1') {
setMemberList(res.data);
} else {
notification.open({
message: "错误",
description: res.message,
});
}
});
}
timeout = setTimeout(fake, 1000);
}
function searchInTime(timeType) {
setTimeType(timeType);
if (timeType === 1) {
setTimeArr(beforeDayArr(7));
setStartTime(weekAgo);
setEndTime(nowDate);
} else if (timeType === 2) {
setTimeArr(monthArr);
setStartTime(monthAgo);
setEndTime(nowDate);
} else if (timeType === 3) {
setTimeArr(beforeDayArr(91));
setStartTime(threeMonthAgo);
setEndTime(nowDate);
}
}
function changeDate(dates, dateStrings) {
if (dateStrings && dateStrings[0]) {
setStartTime(dateStrings[0]);
setEndTime(dateStrings[1]);
let dayNum = (dates[1] - dates[0]) / 86400000 + 1;
setTimeArr(beforeDayArr(dayNum, new Date(dates[1])));
setTimeType(0);
setDayNum(dayNum);
} else {
setTimeType(2);
setTimeArr(monthArr);
setStartTime(monthAgo);
setEndTime(nowDate);
}
}
useEffect(() => {
if (member) {
getPersonData();
} else {
getAllDataLine();
}
}, [startTime, endTime, member]);
function changeMember(val){
if(val){
for(const item of memberList){
if(val.key==item.id){
val.login=item.login
}
}
}
setMember(val);
}
return (
<div className="content">
<Row className="search-box">
<Col xs={24} sm={12} lg={10} >
<Select
showSearch
value={member}
placeholder={'请选择成员或输入成员查询'}
defaultActiveFirstOption={false}
filterOption={false}
onSearch={onSearch}
onChange={(val) => { changeMember(val) }}
notFoundContent={null}
allowClear={true}
labelInValue
>
{
memberList.map(d => <Option key={d.id}>{d.nickname || d.login}</Option>)
}
</Select>
</Col>
<Col xs={24} sm={12} lg={14} className="choose-time">
<ButtonGroup>
<Button type={timeType === 1 ? "primary" : ""} onClick={() => { searchInTime(1) }}>
近1周
</Button>
<Button type={timeType === 2 ? "primary" : ""} onClick={() => { searchInTime(2) }}>
近1月
</Button>
<Button type={timeType === 3 ? "primary" : ""} onClick={() => { searchInTime(3) }}>
近3月
</Button>
</ButtonGroup>
<div className="custom-time">
<div className="time-label">自定义时间段</div>
<RangePicker
placeholder={['--/--', '--/--']}
onChange={changeDate}
/>
</div>
</Col>
</Row>
<EchartLine
id="memberStatistics"
xData={timeArr}
seriesArr={
[{
name: '新增任务数',
data: newTask
}, {
name: '完成任务数',
data: completedTask
}, {
name: '新增PR数',
data: newPrNum
}, {
name: '新增提交数',
data: newCommitNum
}, {
name: '新增代码数',
data: newCodeNum
}]
}
/>
<h4 className="mt20">详情列表</h4>
<TableDetail
member={member}
startTime={startTime}
endTime={endTime}
/>
</div>
);
}

View File

@ -0,0 +1,44 @@
.search-box {
margin-bottom: .75rem;
.ant-select {
min-width: 280px;
}
.ant-btn:active, .ant-btn.active{
color: #fff;
background-color: #096dd9;
border-color: #096dd9;
}
.choose-time{
display: flex;
justify-content: flex-end;
}
.ant-btn-group{
margin-right: 10px;
}
.custom-time{
display: flex;
justify-content: flex-end;
align-items: center;
}
.ant-calendar-picker{
max-width: 250px;
}
.time-label{
background: #fafbfc;
line-height: 30px;
padding: 0 10px;
border: 1px solid #d9d9d9;
border-right: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.ant-calendar-picker-input{
text-align: left;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
}

View File

@ -0,0 +1,175 @@
import React, { useEffect, useState } from 'react';
import { Table, Pagination, notification } from 'antd';
import { paramToUrl } from '../../common/utils';
import fetch from 'military/notice/fetch';
export default ({ member, startTime, endTime }) => {
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [orderBy, setOrderBy] = useState('');
const [dataList, setDataList] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
getTableData();
}, [member, startTime, endTime, orderBy, curPage]);
// table
function getTableData() {
let params = {
startTime,
endTime,
orderBy,
curPage,
pageSize: 10,
};
setLoading(true);
let url;
if (member) {
//
params = {
...params,
userId: member.key,
userName: member.login,
};
url = encodeURI(paramToUrl('/api/DeveloperData/detail/search', params));
} else {
//
url = encodeURI(paramToUrl('/api/DeveloperData/detail', params));
}
fetch({
url,
method: 'get',
data: params
}).then(res => {
if (res.code === '1') {
setDataList(res.data.rows);
setTotal(res.data.total);
} else {
setDataList([]);
setTotal(0);
notification.open({
message: "错误",
description: res.message,
});
}
setLoading(false);
});
}
function handleTableChange(pagination, filters, sorter) {
if (sorter.order) {
if (sorter.order === 'ascend') {
setOrderBy(sorter.field + 'Asc');
setCurPage(1);
} else {
setOrderBy(sorter.field + 'Desc');
setCurPage(1);
}
} else {
setOrderBy('');
setCurPage(1);
}
}
const columns = [
{
title: '成员姓名',
dataIndex: 'nickName',
key: 'nickName',
width: '20%',
className: 'link',
onCell,
render: (text, record) => {
return (text || record.userName)
}
},
{
title: '新增任务数',
key: 'newTask',
dataIndex: 'newTask',
sorter: true,
},
{
title: '完成任务数',
key: 'completedTask',
dataIndex: 'completedTask',
sorter: true,
},
{
title: '新增PR数',
key: 'newPrNum',
dataIndex: 'newPrNum',
sorter: true,
},
{
title: '新增提交数',
key: 'newCommitNum',
dataIndex: 'newCommitNum',
sorter: true,
},
// {
// title: '',
// key: 'newCodeNum',
// dataIndex: 'newCodeNum',
// },
];
function onCell(record) {
return {
onClick: event => { onCellClick(record) }, //
}
}
function onCellClick(record) {
let url = encodeURI(paramToUrl('/api/DeveloperData/getUrl', { login: record.userName }));
fetch({
url,
method: 'get',
}).then(res => {
if (res.code === '1') {
window.open(res.data);
} else {
notification.open({
message: "错误",
description: res.message,
});
}
});
}
if (member) {
columns.splice(0, 1, {
title: '日期',
dataIndex: 'date',
key: 'date',
width: '20%',
})
}
return (
<div className="table-detail">
<Table
loading={loading}
rowKey={(row, i) => row.id || i}
dataSource={dataList}
columns={columns}
pagination={false}
onChange={handleTableChange}
/>
{dataList.length > 0 &&
<Pagination
onChange={(page) => { setCurPage(page) }}
current={curPage}
total={total}
loading={loading}
/>}
</div>
)
}

View File

@ -0,0 +1,97 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
// todo 支持图表例如echart
const propTypes = {
wordId: PropTypes.string, // wordID
fileName: PropTypes.string, // 导出文件的名字
title: PropTypes.element, // 名称标题
styles: PropTypes.string, // word中的样式
};
const defaultProps = {
wordId: '',
className:'',
fileName: 'filename',
title: '导出Word',
styles: 'table{width:100%} ',
success:()=>{},
};
class ExportWord extends Component {
exportWord = () => {
const { wordId, fileName } = this.props;
this.getBlob(wordId, fileName);
};
getBlob = (wordId, fileName) => {
// IE10 以下
if (typeof window === 'undefined' || (typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent))) {
return;
}
const location =window.location;
const mHtml = {
top: `Mime-Version: 1.0\nContent-Base: ${location.href}\nContent-Type: Multipart/related; boundary="NEXT.ITEM-BOUNDARY";type="text/html"\n\n--NEXT.ITEM-BOUNDARY\nContent-Type: text/html; charset="utf-8"\nContent-Location: ${location.href}\n\n<!DOCTYPE html>\n<html>\n_html_</html>`,
head: '<head>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n<style>\n_styles_\n</style>\n</head>\n',
body: '<body>_body_</body>',
};
const activeDoc = document.getElementById(wordId); // 获取 dom 节点
if(activeDoc.querySelector(".markdown-body")){
let imgNode =activeDoc.querySelectorAll('.markdown-body img');
if(imgNode.length){
for(const item of imgNode){
let itemSrc=item.getAttribute('src');
if(itemSrc.indexOf('http')===-1){
item.setAttribute('src',location.origin+itemSrc);
}
}
}
}
// 默认样式
const defaultStyle = '.text-div{ text-decoration: underline; margin-right: 15px; } .textarea-div{ text-decoration: underline; } table td,table th{padding: 6px 13px;border: 1px solid #ddd;} table{border-collapse: collapse;}';
const mHtmlBottom = '\n--NEXT.ITEM-BOUNDARY--';// 文件尾信息
// 替换模板里的内容
const fileContent = mHtml.top.replace('_html_', mHtml.head.replace('_styles_', defaultStyle ) + mHtml.body.replace('_body_', activeDoc.innerHTML)) + mHtmlBottom;
// 创建包含文件内容的blob
const blob = new Blob([fileContent], { type: 'application/msword;charset=utf-8' });
// 下载word文件
this.saveAs(blob, `${fileName}.doc`);
// 下载成功后执行回调方法
this.props.success();
};
saveAs = (blob, name) => { // 实现下载操作
// IE 10+ (native saveAs)
if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) {
return navigator.msSaveOrOpenBlob(blob, name);
}
const urlObj = window.URL || window.webkitURL || window;
const objectUrl = urlObj.createObjectURL(blob);
const a = document.createElement('a');
a.href = objectUrl;
// 模拟点击事件
a.download = name;
a.click(); // 下载
};
render() {
const { title,className } = this.props;
return (
<span className="link">
<span className={className} onClick={this.exportWord}>{title}</span>
</span>
);
}
}
ExportWord.propTypes = propTypes;
ExportWord.defaultProps = defaultProps;
export default ExportWord;

View File

@ -38,7 +38,7 @@ export default props => {
<Menu.Item><a href={`${main_web_site_url}/admin/industries/list`}>行业信息</a></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/placements/list`}>职位信息</a></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/task_templates/list`}>需求导入模板</a></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/agreement_setting`}>签订协议内容</a></Menu.Item>
<Menu.Item><Link to="/managements/task/agreement/content">协议签订模板</Link></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/sign_agreement_setting`}>应征投稿协议内容</a></Menu.Item>
</SubMenu>
<SubMenu title="代办事项" >

View File

@ -1,200 +0,0 @@
import React, {Component} from 'react';
import {Button} from 'antd';
import PropTypes from "prop-types";
const propTypes = {
tableClass: PropTypes.string, // tableClass
fileName: PropTypes.string, // 导出文件的名字
worksheet: PropTypes.string, //导出工作簿名字
// colors: PropTypes.string, // button 样式
// size: PropTypes.string, //按钮大小(lg xg sm md)
// style: PropTypes.string, // 按钮样式
// exportIcon: PropTypes.element, // 自定义导出图标
title: PropTypes.element, //名称标题
filterElement: PropTypes.array, //过滤元素
};
const defaultProps = {
tableClass: '',
fileName: "filename",
worksheet: 'worksheet',
// colors: "primary",
// size: 'sm',
// style: '',
exportIcon: null,
title: '导出',
filterElement: ['button']
};
class ExportExcel extends Component {
exportExcel = () => {
const {tableClass, fileName, worksheet} = this.props;
if (this.getExplorer() === 'ie') {
//创建AX对象excel
const curTbl = document.querySelector(tableClass).cloneNode(true);
// eslint-disable-next-line no-undef
let oXL = new ActiveXObject("Excel.Application");
let oWB = oXL.Workbooks.Add(); //获取workbook对象
let xlSheet = oWB.Worksheets(1); //激活当前sheet
let sel = document.body.createTextRange(); //把表格中的内容移到TextRange中
sel.moveToElementText(curTbl);
sel.select;//全选TextRange中内容
sel.execCommand("Copy");//复制TextRange中内容
xlSheet.Paste(); //粘贴到活动的EXCEL中
oXL.Visible = true; //设置excel可见属性
let fName = null;
try {
fName = oXL.Application.GetSaveAsFilename("Excel.xls", "Excel Spreadsheets (*.xls), *.xls");
} catch (e) {
} finally {
oWB.SaveAs(fName);
// oWB.Close(savechanges = false);
oXL.Quit();
oXL = null;
// 下面代码用于解决IE call Excel的一个BUG, MSDN中提供的方法:
// setTimeout(CollectGarbage, 1);
// 由于不能清除(或同步)网页的受信任状态, 所以将导致SaveAs()等方法在
// 下次调用时无效.
window.location.reload();
}
} else {
this.tableToExcel(tableClass, fileName, worksheet)
}
}
traverseNodes = (node, newTd) => {
if (node.hasChildNodes) {
const sonNodes = node.childNodes;
const {filterElement} = this.props;
for (let sonNode of sonNodes) {
if (!filterElement.includes(sonNode.nodeName.toLowerCase())) { // 对不必要对element过滤
this.traverseNodes(sonNode, newTd);
}
}
}
return this.display(node, newTd);
}
display = (node, newTd) => {
const {nodeName, nodeValue} = node;
let newSpan = document.createElement("span");
newSpan.innerText = nodeValue;
if (nodeName === 'INPUT' || nodeName === 'TEXTAREA') { // 对 input 处理
const {type, checked, value} = node;
newSpan.innerText = value;
if (type === 'radio' || type === 'checkbox') {
console.log("type", type)
newSpan.innerText = type === 'radio' ? (checked ? "●" : "○") : (checked ? "■" : "□");
newSpan.style.fontSize = '16px';
newSpan.style.paddingLeft = '15px';
}
}
if (node.nodeName === 'IMG') {
const {width, height} = node;
newTd.appendChild(node);
newTd.style.height = height + "px";
newTd.style.width = width + "px";
}
if (newSpan.innerText.trim()) {
newTd.appendChild(newSpan);
}
return newTd
}
tableToExcel = (table, fileName, worksheet) => {
const uri = 'data:application/vnd.ms-excel;base64,';
// 定义文档的类型
const template = '<html><head><meta charset="UTF-8"></head><body><table border="1">{table}</table></body></html>';
const base64 = function (s) {
return window.btoa(unescape(encodeURIComponent(s)))
};
// 将template中的变量替换为页面内容ctx获取到的值
const format = function (s, c) {
return s.replace(/{(\w+)}/g, function (m, p) {
return c[p];
})
}
if (!table.nodeType) {
table = document.querySelector(table).cloneNode(true);
}
let newTable = document.createElement("table");
const trArray = table.getElementsByTagName('tr');
for (let trItem of trArray) {
let newTr = document.createElement("tr");
const thArray = trItem.getElementsByTagName('th');
const tdArray = trItem.getElementsByTagName('td');
for (let thItem of thArray) {
let newTh = document.createElement("th");
const {rowSpan = 1, colSpan = 1, style} = thItem;
this.traverseNodes(thItem, newTh);
newTh.rowSpan = rowSpan; //跨行
newTh.colSpan = colSpan; //跨列
newTh.style = style; // 样式
newTr.appendChild(newTh);
}
for (let tdItem of tdArray) {
let newTd = document.createElement("td");
const {rowSpan = 1, colSpan = 1, style} = tdItem;
this.traverseNodes(tdItem, newTd);
newTd.rowSpan = rowSpan; //跨行
newTd.colSpan = colSpan; //跨列
newTd.style = style; // 样式
newTr.appendChild(newTd);
}
if (newTr.childNodes.length > 1) {
newTable.appendChild(newTr);
}
}
const ctx = {worksheet, table: newTable.innerHTML}; // 获取表单的名字和表单查询的内容
const a = document.createElement("a"); // 虚拟一个a 标签
// format()函数:通过格式操作使任意类型的数据转换成一个字符串
// base64():进行编码
a.href = uri + base64(format(template, ctx));
a.download = fileName + ".xls";//设置文件的名字
a.click();// 下载
}
// 获取当前浏览器
getExplorer = () => {
const explorer = window.navigator.userAgent;
if (explorer.indexOf("MSIE") >= 0) { //ie
return 'ie';
}
else if (explorer.indexOf("Firefox") >= 0) { //firefox
return 'Firefox';
}
else if (explorer.indexOf("Chrome") >= 0) { //Chrome
return 'Chrome';
}
else if (explorer.indexOf("Opera") >= 0) { //Opera
return 'Opera';
}
else if (explorer.indexOf("Safari") >= 0) { //Safari
return 'Safari';
}
}
render() {
const {colors, exportIcon, size, title} = this.props;
return (
<Button
// colors={colors}
// size={size}
onClick={this.exportExcel}
>
{/*{exportIcon}{title}*/}
导出
</Button>
)
}
}
ExportExcel.propTypes = propTypes;
ExportExcel.defaultProps = defaultProps;
export default ExportExcel;

View File

@ -44,6 +44,12 @@ const AgreementManage = Loadable({
loading: Loading,
});
// 协议签订模板
const AgreementContent = Loadable({
loader: () => import("./task/agreementContent"),
loading: Loading,
});
const PayProof = Loadable({
loader: () => import("./task/payProof"),
loading: Loading,
@ -146,6 +152,14 @@ const Managements = (propsF) => {
)}
></Route>
{/* 协议内容 */}
<Route
path="/managements/task/agreement/content"
render={(props) => (
<AgreementContent {...propsF} {...props} />
)}
></Route>
{/* 管理员上传支付凭证 */}
<Route
path="/managements/task/payProof"

View File

@ -0,0 +1,100 @@
import React, { useCallback, useEffect, useState } from "react";
import { Input, Button, Form, message } from "antd";
import MDEditor from "../../../modules/tpm/challengesnew/tpm-md-editor";
import {getAgreement, agreementAdd,agreementEdit } from "../api";
import "../index.scss";
import "./index.scss";
export default Form.create()(({ form,current_user, showNotification, match, history }) => {
const { getFieldDecorator, validateFields, setFieldsValue } = form;
const [id,setId]=useState(0);
const [content,setContent]=useState(null);
useEffect(()=>{
getAgreement({title:'协议模板'}).then(res=>{
if(res.data){
setContent(res.data.content);
setId(res.data.id);
}
})
},[])
// 保存,包括新增和修改
function saveFile() {
validateFields((err, values) => {
if (!err) {
id?agreementEdit({
...values,
userId:current_user.user_id,
id,
}).then((res) => dealRes(res)):
agreementAdd({
...values,
userId:current_user.user_id,
id:0,
}).then((res) => dealRes(res))
}
})
}
function dealRes(res) {
if (res && res.message === "success") {
showNotification("操作成功");
} else {
message.error(res&&res.message||'操作失败');
}
}
function onContentChange(value) {
setContent(value);
setFieldsValue({
content: value
});
};
const helper = useCallback(
(label, name, rules, widget, initialValue, rightComponent) => (
<Form.Item label={label} className="mb0">
{getFieldDecorator(name, { rules, initialValue, validateFirst: true })(
widget
)}
{rightComponent}
</Form.Item>
),[]);
return (
<div className="centerbox task-manage agreement-content">
{helper(
"协议名称",
"title",
[{ required: true, message: "请输入协议名称" }],
<Input
placeholder={"请输入协议名称"}
maxLength={50}
disabled
/>,
"协议模板"
)}
<Form.Item className="mb0">
<MDEditor
placeholder={"请输入协议内容"}
height={500}
mdID={"order-new-description"}
initValue={content}
onChange={onContentChange}
className="mt20"
></MDEditor>
{getFieldDecorator("content", {
rules: [{ required: true, message: "请输入协议内容" }],
validateFirst: true,
})(<Input style={{ display: "none" }} />)}
</Form.Item>
<Button className="mt25" type="primary" onClick={saveFile}>
保存
</Button>
</div>
);
});

View File

@ -0,0 +1,5 @@
.agreement-content{
.agreement-content-title{
display: inline-block;
}
}

View File

@ -288,12 +288,30 @@ export function checkHavePaper(taskId) {
});
}
// 新增协议
export function agreementAdd(data) {
return fetch({
url: '/api/paper/agreementSettings/',
method: 'post',
data,
});
}
// 修改协议
export function agreementEdit(data) {
return fetch({
url: '/api/paper/agreementSettings/',
method: 'put',
data
});
}
// 获取协议
export function getAgreement() {
export function getAgreement(params) {
return fetch({
url: '/api/paper/agreementSettings/1',
method: 'get'
url: `/api/paper/agreementSettings/${params==1?params:''}`,
method: 'get',
params:params!=1?params:null
});
}

View File

@ -1,7 +1,10 @@
import React, { useState, } from 'react';
import React, { useEffect, useState, } from 'react';
import { Modal, Form, Input, } from 'antd';
import RenderHtml from 'src/components/render-html';
import Upload from 'military/components/Upload';
import { uploadAgreePaper } from "../../api";
import ExportWord from 'military/components/ExportWord';
import { uploadAgreePaper,getAgreement } from "../../api";
import '../../index.scss';
@ -10,6 +13,15 @@ export default Form.create()(props => {
const { getFieldDecorator, validateFields, setFieldsValue, } = form;
const [fileList, setFileList] = useState([]);
const [content,setContent]=useState('');
useEffect(()=>{
visible && getAgreement({title:'协议模板'}).then(res=>{
if(res.data){
setContent(res.data.content);
}
})
},visible)
//
@ -55,8 +67,14 @@ export default Form.create()(props => {
<div className="task-popup-content">
{/* paperAuditing */}
{checkedItem.paperAuditing && <p className=" mb10 color-orange task_tip">审核意见{checkedItem.paperAuditing.message}</p>}
<a href="https://task.osredm.com/busiAttachments/download/121" className="icon icon-attachment font-13 color-blue" length="32">协议样板.word</a>
<Form.Item className="upload-form" label="附件上传" required={true}>
<ExportWord
className="icon icon-attachment font-13 color-blue"
title="协议模板.doc"
fileName="协议模板"
wordId="wordExpert"
/>
<Form.Item className="upload-form" label="协议上传" required={true}>
<Upload
load={uploadFunc}
size={50}
@ -68,6 +86,9 @@ export default Form.create()(props => {
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
<div id="wordExpert" style={{display:'none'}} >
<RenderHtml className="break_word_comments imageLayerParent" value={content} />
</div>
</div>
</Modal>
)

View File

@ -87,6 +87,8 @@ span.list-gray {
.center-content .ant-pagination {
margin: 2rem 1.5rem;
text-align: right;
margin: 2rem auto !important;
text-align: center;
.ant-pagination-item:focus, .ant-pagination-item:hover, .ant-pagination-item-active, .ant-pagination-prev:hover a, .ant-pagination-next:hover a, .ant-pagination-options-quick-jumper input:focus, .ant-pagination-options-quick-jumper input:hover{
border-color: #4154f1;
}

View File

@ -95,16 +95,16 @@ export default Form.create()(({ form, showNotification, history}) => {
const columns = useMemo(() => {
return [
{
title: '号',
dataIndex: 'index',
render: (text, record, index) => {
return index + 1
title: '成果编号',
dataIndex: 'paperNumber',
render: (text, record) => {
return text ? <Link className="line_1 color-grey3" to={`/task/taskDetail/${record.taskId}`}>{text}</Link> : '--'
}
},
{
title: '申诉内容',
dataIndex: 'content',
width: "30%",
width: "26%",
},
{
title: '申诉文件',

View File

@ -90,7 +90,7 @@ export default Form.create()(({ current_user, form, showNotification, match, his
setReload(Math.random());
setVisible(false);
setFieldsValue({
pass: '',
pass: 2,
message: '',
})
}

View File

@ -114,7 +114,7 @@ export default Form.create()(
//
useEffect(() => {
applyModal && getAgreement().then(res => {
applyModal && getAgreement(1).then(res => {
if (res && res.data) {
setApplyContent({
title: res.data.title,

View File

@ -134,7 +134,7 @@ export default Form.create()(({ form, showNotification, match, history }) => {
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" type="" onClick={clearSearch}>清除</Button>
<Button className="mr10" type="primary" onClick={() => { window.open(`${main_web_site_url}/admin/tasks/activity_managed_tasks.zip?review_status=reviewing&amp;status_value=0`) }}>导出</Button>
{/* <Button className="mr10" type="primary" onClick={() => { window.open(`${main_web_site_url}/admin/tasks/activity_managed_tasks.zip?review_status=reviewing&amp;status_value=0`) }}>导出</Button> */}
</div>
</div>