mirror of
https://github.com/YMFE/yapi.git
synced 2025-04-18 15:20:25 +08:00
feat: 增加防多人冲突的编辑检测机制
This commit is contained in:
parent
b2ffd72c86
commit
3460ce5e20
@ -1,18 +1,13 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Button, message, Checkbox } from 'antd';
|
||||
import { message } from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import axios from 'axios';
|
||||
import PropTypes from 'prop-types';
|
||||
import './index.scss';
|
||||
// deps for editor
|
||||
require('codemirror/lib/codemirror.css'); // codemirror
|
||||
require('tui-editor/dist/tui-editor.css'); // editor ui
|
||||
require('tui-editor/dist/tui-editor-contents.css'); // editor content
|
||||
require('highlight.js/styles/github.css'); // code block highlight
|
||||
// require('./editor.css');
|
||||
var Editor = require('tui-editor');
|
||||
import { timeago } from '../util.js';
|
||||
import { Link } from 'react-router-dom';
|
||||
import WikiView from './View.js';
|
||||
import WikiEditor from './Editor.js';
|
||||
|
||||
@connect(
|
||||
state => {
|
||||
@ -30,7 +25,11 @@ class WikiPage extends Component {
|
||||
isUpload: true,
|
||||
desc: '',
|
||||
markdown: '',
|
||||
notice: props.projectMsg.switch_notice
|
||||
notice: props.projectMsg.switch_notice,
|
||||
status: 'INIT',
|
||||
editUid: '',
|
||||
editName: '',
|
||||
curdata: null
|
||||
};
|
||||
}
|
||||
|
||||
@ -42,22 +41,100 @@ class WikiPage extends Component {
|
||||
async componentDidMount() {
|
||||
const currProjectId = this.props.match.params.id;
|
||||
await this.handleData({ project_id: currProjectId });
|
||||
|
||||
this.editor = new Editor({
|
||||
el: document.querySelector('#desc'),
|
||||
initialEditType: 'wysiwyg',
|
||||
height: '500px',
|
||||
initialValue: this.state.markdown || this.state.desc
|
||||
});
|
||||
}
|
||||
|
||||
// 点击编辑按钮
|
||||
onEditor = () => {
|
||||
this.setState({
|
||||
isEditor: !this.state.isEditor
|
||||
});
|
||||
componentWillUnmount() {
|
||||
// willUnmount
|
||||
this.closeWebSocket();
|
||||
}
|
||||
|
||||
this.editor.setHtml(this.state.desc);
|
||||
// 关闭websocket
|
||||
closeWebSocket = () => {
|
||||
try {
|
||||
if (this.state.status === 'CLOSE') {
|
||||
this.WebSocket.close();
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理多人编辑冲突问题
|
||||
handleConflict = () => {
|
||||
let self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
let domain = location.hostname + (location.port !== '' ? ':' + location.port : '');
|
||||
let s,
|
||||
initData = false;
|
||||
//因后端 node 仅支持 ws, 暂不支持 wss
|
||||
let wsProtocol = location.protocol === 'https' ? 'ws' : 'ws';
|
||||
|
||||
setTimeout(() => {
|
||||
if (initData === false) {
|
||||
self.setState({
|
||||
status: 'CLOSE'
|
||||
});
|
||||
initData = true;
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
s = new WebSocket(
|
||||
wsProtocol +
|
||||
'://' +
|
||||
domain +
|
||||
'/api/ws_plugin/wiki_desc/solve_conflict?id=' +
|
||||
this.props.match.params.id
|
||||
);
|
||||
s.onopen = () => {
|
||||
self.WebSocket = s;
|
||||
};
|
||||
|
||||
s.onmessage = e => {
|
||||
initData = true;
|
||||
let result = JSON.parse(e.data);
|
||||
if (result.errno === 0) {
|
||||
self.setState({
|
||||
curdata: result.data,
|
||||
status: 'CLOSE'
|
||||
});
|
||||
} else {
|
||||
self.setState({
|
||||
editUid: result.data.uid,
|
||||
editName: result.data.username,
|
||||
status: 'EDITOR'
|
||||
});
|
||||
}
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
s.onerror = () => {
|
||||
self.setState({
|
||||
// curdata: this.props.curdata,
|
||||
status: 'CLOSE'
|
||||
});
|
||||
console.warn('websocket 连接失败,将导致多人编辑同一个接口冲突。');
|
||||
reject('websocket 连接失败,将导致多人编辑同一个接口冲突。');
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 点击编辑按钮
|
||||
onEditor = async () => {
|
||||
this.setState({isEditor: !this.state.isEditor})
|
||||
// 多人冲突编辑判断
|
||||
await this.handleConflict();
|
||||
// 获取最新的编辑数据
|
||||
// let curDesc = this.state.curdata ? this.state.curdata.desc : this.state.desc;
|
||||
if(this.state.curdata) {
|
||||
this.setState({
|
||||
desc: this.state.curdata.desc,
|
||||
username: this.state.curdata.username,
|
||||
uid: this.state.curdata.uid,
|
||||
editorTime: timeago(this.state.curdata.up_time)
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// 获取数据
|
||||
@ -79,9 +156,8 @@ class WikiPage extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
onUpload = async () => {
|
||||
let desc = this.editor.getHtml();
|
||||
let markdown = this.editor.getMarkdown();
|
||||
onUpload = async (desc, markdown) => {
|
||||
|
||||
const currProjectId = this.props.match.params.id;
|
||||
let option = {
|
||||
project_id: currProjectId,
|
||||
@ -96,10 +172,12 @@ class WikiPage extends Component {
|
||||
} else {
|
||||
message.error(`更新失败: ${result.data.errmsg}`);
|
||||
}
|
||||
this.closeWebSocket();
|
||||
};
|
||||
// 取消编辑
|
||||
onCancel = () => {
|
||||
this.setState({ isEditor: false });
|
||||
this.setState({ isEditor: false, status: 'CLOSE' });
|
||||
this.closeWebSocket();
|
||||
};
|
||||
|
||||
// 邮件通知
|
||||
@ -110,51 +188,46 @@ class WikiPage extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isEditor, username, editorTime, notice, uid } = this.state;
|
||||
const { isEditor, username, editorTime, notice, uid, status, editUid, editName } = this.state;
|
||||
const editorEable =
|
||||
this.props.projectMsg.role === 'admin' ||
|
||||
this.props.projectMsg.role === 'owner' ||
|
||||
this.props.projectMsg.role === 'dev';
|
||||
const isConflict = status === 'EDITOR';
|
||||
|
||||
return (
|
||||
<div className="g-row">
|
||||
<div className="m-panel wiki-content">
|
||||
<div className="wiki-title">
|
||||
{!isEditor ? (
|
||||
<Button icon="edit" onClick={this.onEditor} disabled={!editorEable}>
|
||||
编辑
|
||||
</Button>
|
||||
) : (
|
||||
<div>
|
||||
<Button icon="upload" type="primary" className="upload-btn" onClick={this.onUpload}>
|
||||
更新
|
||||
</Button>
|
||||
<Button onClick={this.onCancel} className="upload-btn">
|
||||
取消
|
||||
</Button>
|
||||
<Checkbox checked={notice} onChange={this.onEmailNotice}>
|
||||
通知相关人员
|
||||
</Checkbox>
|
||||
{!isEditor ? (
|
||||
<WikiView
|
||||
editorEable={editorEable}
|
||||
onEditor={this.onEditor}
|
||||
uid={uid}
|
||||
username={username}
|
||||
editorTime={editorTime}
|
||||
desc={this.state.desc}
|
||||
/>
|
||||
) : (
|
||||
<WikiEditor
|
||||
isConflict={isConflict}
|
||||
onUpload={this.onUpload}
|
||||
onCancel={this.onCancel}
|
||||
notice={notice}
|
||||
onEmailNotice={this.onEmailNotice}
|
||||
desc={this.state.desc}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="wiki-content">
|
||||
{isConflict && (
|
||||
<div className="wiki-conflict">
|
||||
<Link to={`/user/profile/${editUid || uid}`}>
|
||||
<b>{editName || username}</b>
|
||||
</Link>
|
||||
<span>正在编辑该wiki,请稍后再试...</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isEditor &&
|
||||
username && (
|
||||
<div className="wiki-user">
|
||||
{/* 由 {username} */}
|
||||
由{' '}
|
||||
<Link className="user-name" to={`/user/profile/${uid || 11}`}>
|
||||
{/* <img src={'/api/user/avatar?uid=' + this.props.curData.uid} className="user-img" /> */}
|
||||
{username}
|
||||
</Link>{' '}
|
||||
修改于 {editorTime}
|
||||
</div>
|
||||
)}
|
||||
<div id="desc" className="wiki-editor" style={{ display: isEditor ? 'block' : 'none' }} />
|
||||
<div
|
||||
className="tui-editor-contents"
|
||||
style={{ display: isEditor ? 'none' : 'block' }}
|
||||
dangerouslySetInnerHTML={{ __html: this.state.desc }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -10,4 +10,14 @@
|
||||
.upload-btn {
|
||||
margin-right: 16px;
|
||||
}
|
||||
.wiki-conflict {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.wiki-up {
|
||||
text-align: right;
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import WikiPage from './WikiPage/index'
|
||||
import WikiPage from './wikiPage/index'
|
||||
// const WikiPage = require('./wikiPage/index')
|
||||
|
||||
module.exports = function(){
|
||||
this.bindHook('sub_nav', function(app){
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
const baseController = require('controllers/base.js');
|
||||
const wikiModel = require('./wikiModel.js');
|
||||
const projectModel = require('models/project.js');
|
||||
const userModel = require('models/user.js');
|
||||
const jsondiffpatch = require('jsondiffpatch');
|
||||
const formattersHtml = jsondiffpatch.formatters.html;
|
||||
const yapi = require('yapi.js');
|
||||
@ -14,7 +14,6 @@ class wikiController extends baseController {
|
||||
super(ctx);
|
||||
this.Model = yapi.getInst(wikiModel);
|
||||
this.projectModel = yapi.getInst(projectModel);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -29,10 +28,10 @@ class wikiController extends baseController {
|
||||
try {
|
||||
let project_id = ctx.request.query.project_id;
|
||||
if (!project_id) {
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '项目id不能为空');
|
||||
return (ctx.body = yapi.commons.resReturn(null, 400, '项目id不能为空'));
|
||||
}
|
||||
let result = await this.Model.get(project_id)
|
||||
return ctx.body = yapi.commons.resReturn(result);
|
||||
let result = await this.Model.get(project_id);
|
||||
return (ctx.body = yapi.commons.resReturn(result));
|
||||
} catch (err) {
|
||||
ctx.body = yapi.commons.resReturn(null, 400, err.message);
|
||||
}
|
||||
@ -55,14 +54,14 @@ class wikiController extends baseController {
|
||||
desc: 'string',
|
||||
markdown: 'string'
|
||||
});
|
||||
|
||||
|
||||
if (!params.project_id) {
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '项目id不能为空');
|
||||
return (ctx.body = yapi.commons.resReturn(null, 400, '项目id不能为空'));
|
||||
}
|
||||
if(!this.$tokenAuth){
|
||||
let auth = await this.checkAuth(params.project_id, 'project', 'edit')
|
||||
if (!this.$tokenAuth) {
|
||||
let auth = await this.checkAuth(params.project_id, 'project', 'edit');
|
||||
if (!auth) {
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '没有权限');
|
||||
return (ctx.body = yapi.commons.resReturn(null, 400, '没有权限'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,8 +71,8 @@ class wikiController extends baseController {
|
||||
const uid = this.getUid();
|
||||
|
||||
// 如果当前数据库里面没有数据
|
||||
let result = await this.Model.get(params.project_id)
|
||||
if(!result) {
|
||||
let result = await this.Model.get(params.project_id);
|
||||
if (!result) {
|
||||
let data = Object.assign(params, {
|
||||
username,
|
||||
uid,
|
||||
@ -82,33 +81,46 @@ class wikiController extends baseController {
|
||||
});
|
||||
|
||||
let res = await this.Model.save(data);
|
||||
return ctx.body = yapi.commons.resReturn(res);
|
||||
ctx.body = yapi.commons.resReturn(res);
|
||||
} else {
|
||||
let data = Object.assign(params, {
|
||||
username,
|
||||
uid,
|
||||
up_time: yapi.commons.time()
|
||||
});
|
||||
let upRes = await this.Model.up(result._id, data);
|
||||
ctx.body = yapi.commons.resReturn(upRes);
|
||||
}
|
||||
|
||||
// console.log('result', result);
|
||||
let data = Object.assign(params, {
|
||||
username,
|
||||
uid,
|
||||
up_time: yapi.commons.time()
|
||||
});
|
||||
let upRes = await this.Model.up(result._id, data);
|
||||
ctx.body = yapi.commons.resReturn(upRes)
|
||||
|
||||
let logData = {
|
||||
type: 'wiki',
|
||||
project_id: params.project_id,
|
||||
current: params.desc,
|
||||
old: result ? result.toObject().desc : ''
|
||||
}
|
||||
let wikiUrl = `http://${ctx.request.host}/project/${params.project_id}/wiki`
|
||||
};
|
||||
let wikiUrl = `http://${ctx.request.host}/project/${params.project_id}/wiki`;
|
||||
|
||||
if(notice) {
|
||||
let diffView = showDiffMsg(jsondiffpatch, formattersHtml, logData);
|
||||
|
||||
let annotatedCss = fs.readFileSync(path.resolve(yapi.WEBROOT, 'node_modules/jsondiffpatch/public/formatters-styles/annotated.css'), 'utf8');
|
||||
let htmlCss = fs.readFileSync(path.resolve(yapi.WEBROOT, 'node_modules/jsondiffpatch/public/formatters-styles/html.css'), 'utf8');
|
||||
if (notice) {
|
||||
let diffView = showDiffMsg(jsondiffpatch, formattersHtml, logData);
|
||||
|
||||
let annotatedCss = fs.readFileSync(
|
||||
path.resolve(
|
||||
yapi.WEBROOT,
|
||||
'node_modules/jsondiffpatch/public/formatters-styles/annotated.css'
|
||||
),
|
||||
'utf8'
|
||||
);
|
||||
let htmlCss = fs.readFileSync(
|
||||
path.resolve(
|
||||
yapi.WEBROOT,
|
||||
'node_modules/jsondiffpatch/public/formatters-styles/html.css'
|
||||
),
|
||||
'utf8'
|
||||
);
|
||||
let project = await this.projectModel.getBaseInfo(params.project_id);
|
||||
|
||||
|
||||
yapi.commons.sendNotice(params.project_id, {
|
||||
title: `${username} 更新了wiki说明`,
|
||||
content: `<html>
|
||||
@ -126,7 +138,7 @@ class wikiController extends baseController {
|
||||
<p>详细改动日志: ${this.diffHTML(diffView)}</p></div>
|
||||
</body>
|
||||
</html>`
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// 保存修改日志信息
|
||||
@ -143,21 +155,54 @@ class wikiController extends baseController {
|
||||
} catch (err) {
|
||||
ctx.body = yapi.commons.resReturn(null, 400, err.message);
|
||||
}
|
||||
|
||||
}
|
||||
diffHTML(html) {
|
||||
if (html.length === 0) {
|
||||
return `<span style="color: #555">没有改动,该操作未改动wiki数据</span>`
|
||||
return `<span style="color: #555">没有改动,该操作未改动wiki数据</span>`;
|
||||
}
|
||||
|
||||
return html.map(item => {
|
||||
return (`<div>
|
||||
return `<div>
|
||||
<h4 class="title">${item.title}</h4>
|
||||
<div>${item.content}</div>
|
||||
</div>`)
|
||||
})
|
||||
</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
// 处理编辑冲突
|
||||
async wikiConflict(ctx) {
|
||||
try {
|
||||
let id = parseInt(ctx.query.id, 10),
|
||||
result,
|
||||
userInst,
|
||||
userinfo,
|
||||
data;
|
||||
if (!id) return ctx.websocket.send('id 参数有误');
|
||||
result = await this.Model.get(id);
|
||||
|
||||
if (result.edit_uid !== 0 && result.edit_uid !== this.getUid()) {
|
||||
userInst = yapi.getInst(userModel);
|
||||
userinfo = await userInst.findById(result.edit_uid);
|
||||
data = {
|
||||
errno: result.edit_uid,
|
||||
data: { uid: result.edit_uid, username: userinfo.username }
|
||||
};
|
||||
} else {
|
||||
await this.Model.upEditUid(result._id, this.getUid());
|
||||
data = {
|
||||
errno: 0,
|
||||
data: result
|
||||
};
|
||||
}
|
||||
ctx.websocket.send(JSON.stringify(data));
|
||||
ctx.websocket.on('close', async () => {
|
||||
console.log('close');
|
||||
await this.Model.upEditUid(result._id, 0);
|
||||
});
|
||||
} catch (err) {
|
||||
yapi.commons.log(err, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = wikiController;
|
||||
|
@ -1,35 +1,39 @@
|
||||
|
||||
const yapi = require('yapi.js')
|
||||
const yapi = require('yapi.js');
|
||||
const mongoose = require('mongoose');
|
||||
const controller = require('./controller');
|
||||
|
||||
module.exports = function() {
|
||||
yapi.connect.then(function() {
|
||||
let Col = mongoose.connection.db.collection('wiki');
|
||||
Col.createIndex({
|
||||
project_id: 1
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = function () {
|
||||
|
||||
yapi.connect.then(function () {
|
||||
let Col = mongoose.connection.db.collection('wiki');
|
||||
Col.createIndex({
|
||||
project_id: 1
|
||||
})
|
||||
|
||||
this.bindHook('add_router', function(addRouter) {
|
||||
addRouter({
|
||||
// 获取wiki信息
|
||||
controller: controller,
|
||||
method: 'get',
|
||||
path: 'wiki_desc/get',
|
||||
action: 'getWikiDesc'
|
||||
});
|
||||
|
||||
this.bindHook('add_router', function (addRouter) {
|
||||
addRouter({
|
||||
// 获取wiki信息
|
||||
controller: controller,
|
||||
method: 'get',
|
||||
path: 'wiki_desc/get',
|
||||
action: 'getWikiDesc'
|
||||
})
|
||||
addRouter({
|
||||
// 更新wiki信息
|
||||
controller: controller,
|
||||
method: 'post',
|
||||
path: 'wiki_desc/up',
|
||||
action: 'uplodaWikiDesc'
|
||||
});
|
||||
});
|
||||
|
||||
addRouter({
|
||||
// 更新wiki信息
|
||||
controller: controller,
|
||||
method: 'post',
|
||||
path: 'wiki_desc/up',
|
||||
action: 'uplodaWikiDesc'
|
||||
})
|
||||
|
||||
})
|
||||
};
|
||||
this.bindHook('add_ws_router', function(wsRouter) {
|
||||
wsRouter({
|
||||
controller: controller,
|
||||
method: 'get',
|
||||
path: 'wiki_desc/solve_conflict',
|
||||
action: 'wikiConflict'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -10,7 +10,8 @@ class statisMockModel extends baseModel {
|
||||
return {
|
||||
project_id: { type: Number, required: true },
|
||||
username: String,
|
||||
uid: Number,
|
||||
uid: { type: Number, required: true },
|
||||
edit_uid: { type: Number, default: 0 },
|
||||
desc: String,
|
||||
markdown: String,
|
||||
add_time: Number,
|
||||
@ -44,6 +45,14 @@ class statisMockModel extends baseModel {
|
||||
{ runValidators: true }
|
||||
);
|
||||
}
|
||||
|
||||
upEditUid(id, uid) {
|
||||
return this.model.update({
|
||||
_id: id
|
||||
},
|
||||
{ edit_uid: uid },
|
||||
{ runValidators: true });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = statisMockModel;
|
||||
|
71
exts/yapi-plugin-wiki/wikiPage/Editor.js
Normal file
71
exts/yapi-plugin-wiki/wikiPage/Editor.js
Normal file
@ -0,0 +1,71 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Checkbox } from 'antd';
|
||||
// deps for editor
|
||||
require('codemirror/lib/codemirror.css'); // codemirror
|
||||
require('tui-editor/dist/tui-editor.css'); // editor ui
|
||||
require('tui-editor/dist/tui-editor-contents.css'); // editor content
|
||||
require('highlight.js/styles/github.css'); // code block highlight
|
||||
var Editor = require('tui-editor');
|
||||
|
||||
class WikiEditor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
isConflict: PropTypes.bool,
|
||||
onUpload: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
notice: PropTypes.bool,
|
||||
onEmailNotice: PropTypes.func,
|
||||
desc: PropTypes.string
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.editor = new Editor({
|
||||
el: document.querySelector('#desc'),
|
||||
initialEditType: 'wysiwyg',
|
||||
height: '500px',
|
||||
initialValue: this.props.desc
|
||||
});
|
||||
}
|
||||
|
||||
onUpload = () => {
|
||||
let desc = this.editor.getHtml();
|
||||
let markdown = this.editor.getMarkdown();
|
||||
this.props.onUpload(desc, markdown)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {isConflict, onCancel, notice, onEmailNotice } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
id="desc"
|
||||
className="wiki-editor"
|
||||
style={{ display: !isConflict ? 'block' : 'none' }}
|
||||
/>
|
||||
<div className="wiki-title wiki-up">
|
||||
<Button
|
||||
icon="upload"
|
||||
type="primary"
|
||||
className="upload-btn"
|
||||
disabled={isConflict}
|
||||
onClick={this.onUpload}
|
||||
>
|
||||
更新
|
||||
</Button>
|
||||
<Button onClick={onCancel} className="upload-btn">
|
||||
取消
|
||||
</Button>
|
||||
<Checkbox checked={notice} onChange={onEmailNotice}>
|
||||
通知相关人员
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WikiEditor;
|
41
exts/yapi-plugin-wiki/wikiPage/View.js
Normal file
41
exts/yapi-plugin-wiki/wikiPage/View.js
Normal file
@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const WikiView = props => {
|
||||
const { editorEable, onEditor, uid, username, editorTime, desc } = props;
|
||||
return (
|
||||
<div>
|
||||
<div className="wiki-title">
|
||||
<Button icon="edit" onClick={onEditor} disabled={!editorEable}>
|
||||
编辑
|
||||
</Button>
|
||||
{username && (
|
||||
<div className="wiki-user">
|
||||
由{' '}
|
||||
<Link className="user-name" to={`/user/profile/${uid || 11}`}>
|
||||
{username}
|
||||
</Link>{' '}
|
||||
修改于 {editorTime}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="tui-editor-contents"
|
||||
dangerouslySetInnerHTML={{ __html: desc }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
WikiView.propTypes = {
|
||||
editorEable: PropTypes.bool,
|
||||
onEditor: PropTypes.func,
|
||||
uid: PropTypes.number,
|
||||
username: PropTypes.string,
|
||||
editorTime: PropTypes.string,
|
||||
desc: PropTypes.string
|
||||
};
|
||||
|
||||
export default WikiView;
|
@ -679,7 +679,7 @@ class interfaceController extends baseController {
|
||||
ctx.body = yapi.commons.resReturn(null, 402, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理编辑冲突
|
||||
async solveConflict(ctx) {
|
||||
try {
|
||||
let id = parseInt(ctx.query.id, 10), result, userInst, userinfo, data;
|
||||
|
@ -20,11 +20,14 @@ function addPluginRouter(config) {
|
||||
pluginsRouterPath.push(routerPath);
|
||||
createAction(router, "/api", config.controller, config.action, routerPath, method, true);
|
||||
}
|
||||
|
||||
|
||||
function websocket(app) {
|
||||
createAction(router, "/api", interfaceController, "solveConflict", "/interface/solve_conflict", "get")
|
||||
|
||||
yapi.emitHookSync('add_ws_router', addPluginRouter);
|
||||
|
||||
|
||||
app.ws.use(router.routes())
|
||||
app.ws.use(router.allowedMethods());
|
||||
app.ws.use(function (ctx, next) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user