重构协议模板功能,修复issue

This commit is contained in:
何童崇 2022-03-09 11:17:01 +08:00
parent ff0515281b
commit 5bbc3cace1
11 changed files with 179 additions and 257 deletions

View File

@ -62,18 +62,11 @@ class Index extends Component {
)}
></Route>
<Route
path="/explore/all"
path="/projects"
render={(props) => (
<ProjectIndex {...this.props} {...props} />
)}
></Route>
<Route
path="/explore"
render={(props) => (
<ProjectHome {...this.props} {...props} />
)}
></Route>
</Switch>
</div>
);

View File

@ -16,8 +16,11 @@ 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`)
window.open(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

@ -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>} */}
<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 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

@ -0,0 +1,87 @@
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 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 节点
.cloneNode(true);
const { wordStyles: styles } = this.props; // 文件样式
// 默认样式
const defaultStyle = '.text-div{ text-decoration: underline; margin-right: 15px; } .textarea-div{ text-decoration: underline; }';
const mHtmlBottom = '\n--NEXT.ITEM-BOUNDARY--';// 文件尾信息
// 替换模板里的内容
const fileContent = mHtml.top.replace('_html_', mHtml.head.replace('_styles_', defaultStyle + styles) + 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="editor-sany-word">
<span className={className} onClick={this.exportWord}>{title}</span>
</span>
);
}
}
ExportWord.propTypes = propTypes;
ExportWord.defaultProps = defaultProps;
export default ExportWord;

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

@ -1,39 +1,58 @@
import React, { useCallback, useEffect, useState } from "react";
import { Input, Button, Form, Select } from "antd";
import MDEditor from "../../../modules/tpm/challengesnew/tpm-md-editor";
import {getAgreement, agreementEdit } from "../api";
import {getAgreement, agreementAdd,agreementEdit } from "../api";
import "../index.scss";
import "./index.scss";
export default Form.create()(({ form, showNotification, match, history }) => {
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().then(res=>{
console.log(res);
getAgreement({title:'协议模板'}).then(res=>{
if(res.data){
setContent(res.data.content);
setId(res.data.id);
}
})
},[])
// 保存wiki文件包括新增和修改
// 保存,包括新增和修改
function saveFile() {
validateFields((err, values) => {
if (!err) {
let regEn = /[\[\]`\/:*?''<>|%-+_]/g;
if (regEn.test(values.name)) {
message.error("文件名不能有特殊字符");
return;
}
agreementEdit({
owner,
repo: projectsId,
projectId: project.id,
pagename: wikiName,
id?agreementEdit({
...values,
commit_message: "",
}).then((res) => dealRes(res));
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">
@ -42,28 +61,23 @@ export default Form.create()(({ form, showNotification, match, history }) => {
)}
{rightComponent}
</Form.Item>
),
[]
);
),[]);
return (
<div>
{/* {helper(
<div className="centerbox task-manage agreement-content">
{helper(
"协议名称",
"name",
[
{ required: true, message: "请输入协议名称" },
// { pattern: /[^`\[\]\/:*?''<>|%-+_]/g, message: '不允许部分特殊字符' }
],
"协议模板",
"title",
[{ required: true, message: "请输入协议名称" }],
<Input
placeholder={"请输入协议名称"}
className="contact-input"
maxLength={50}
/>
)} */}
disabled
/>,
"协议模板"
)}
{/* <Form.Item className="mb0">
<Form.Item className="mb0">
<MDEditor
placeholder={"请输入协议内容"}
height={500}
@ -76,7 +90,7 @@ export default Form.create()(({ form, showNotification, match, history }) => {
rules: [{ required: true, message: "请输入协议内容" }],
validateFirst: true,
})(<Input style={{ display: "none" }} />)}
</Form.Item> */}
</Form.Item>
<Button className="mt25" type="primary" onClick={saveFile}>
保存

View File

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

View File

@ -289,18 +289,20 @@ export function checkHavePaper(taskId) {
}
// 新增协议
export function agreementAdd() {
export function agreementAdd(data) {
return fetch({
url: '/api/paper/agreementSettings/',
method: 'post'
method: 'post',
data,
});
}
// 修改协议
export function agreementEdit() {
export function agreementEdit(data) {
return fetch({
url: '/api/paper/agreementSettings/',
method: 'put'
method: 'put',
data
});
}

View File

@ -1,7 +1,9 @@
import React, { useState, } from 'react';
import React, { useEffect, useState, } from 'react';
import { Modal, Form, Input, } from 'antd';
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 +12,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,7 +66,13 @@ 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>
<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}
@ -68,6 +85,7 @@ export default Form.create()(props => {
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
<div id="wordExpert" style={{display:'none'}} dangerouslySetInnerHTML={{__html:content}}></div>
</div>
</Modal>
)

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>