Commit 01406053 authored by rockyl's avatar rockyl

版本冲突功能

parent 7e600024
......@@ -66,19 +66,24 @@ export async function fetchApi(uri, {host = '', params, method = 'get', auth = t
}
const response = await fetch(url, options);
if (response.status === 401) {
location.href = '/admin/permission';
const respText = await response.text();
if (response.status === 310) { //客户端重定向,用于跨域重定向
location.href = respText;
}
const jsonObj = await response.json();
//console.log(jsonObj);
try {
let jsonObj = JSON.parse(respText);
if (judgeSuccess) {
if (jsonObj.success) {
return jsonObj[dataField];
return jsonObj.data;
}
} else {
return jsonObj;
}
throw new ApiError('call api failed', jsonObj.code, errMessage, jsonObj.details);
return Promise.reject(new ApiError('call api failed', jsonObj.code, errMessage, jsonObj.details));
}catch (e) {
return Promise.reject(e);
}
}
......@@ -155,6 +155,7 @@
"Failed to get project": "获取项目失败",
"Failed to save project": "保存项目失败",
"Save project successfully": "保存项目成功",
"There are conflicts in the project": "项目有冲突,请先解决冲突",
"Input version remark": "输入版本备注",
"Input view name": "输入视图名",
"Invalid view name": "无效的视图名",
......@@ -226,6 +227,11 @@
"Operate": "操作",
"Computed": "计算属性",
"Are you sure to close?": "确定关闭吗?",
"Project conflict resolver": "项目冲突解决",
"Merge conflicts": "合并冲突",
"There are still unresolved conflicts": "还有冲突未解决,不能保存!",
"The format of the JSON document is wrong": "JSON文档格式有误,请先更正!",
"The conflict has been resolved": "冲突已解决,确定保存吗?",
"eventGroup": {
"in": "接收",
"out": "派发"
......
......@@ -91,6 +91,9 @@ export const projectStore = {
},
makeProjectDirty() {
},
updateProjectUpdateTime(state, updateTime) {
state.update_time = updateTime;
},
updateProject(state, project) {
const {id, name, creator, data, operators, update_time} = project;
......@@ -352,11 +355,11 @@ export const projectStore = {
copyNode(state, {node, parentNode, copyState}) {
let _node1 = node;
console.log(_node1)
copyNodeCatch =clonePureObj(_node1)
if(copyState==1){
copyNodeCatch = clonePureObj(_node1)
if (copyState == 1) {
//复制行为
copyNodeCatch.events
}else if(copyState==2){
} else if (copyState == 2) {
//不复制行为
delete copyNodeCatch.events
}
......@@ -453,17 +456,17 @@ export const projectStore = {
state.mockServeEnabled = enabled;
},
modifyViewStore(state, {view, store}){
modifyViewStore(state, {view, store}) {
view.store = store;
this.commit('makeProjectDirty');
},
addCmd(state, cmd){
addCmd(state, cmd) {
Vue.set(state.activeComponent.properties, cmd, '');
this.commit('makeProjectDirty');
},
deleteCmd(state, cmd){
deleteCmd(state, cmd) {
Vue.delete(state.activeComponent.properties, cmd);
this.commit('makeProjectDirty');
},
......@@ -566,7 +569,7 @@ export const projectStore = {
let index = entries.indexOf(behavior.meta);
if (index >= 0) {
let path = paths.splice(index, 1, null)[0];
if(!path){
if (!path) {
continue;
}
path[0] = {
......@@ -641,9 +644,19 @@ export const projectStore = {
throw new Error('Project does not exist')
}
},
async saveToRemote({state, dispatch, getters}, {remark}) {
await projectApi.saveOne(getters.project, remark);
async saveToRemote({state, dispatch, getters, commit}, {remark, data}) {
let project = Object.assign({}, getters.project);
if (data) {
project.data = data;
project.force = true;
}
let resp = await projectApi.saveOne(project, remark);
if (resp.result) {
commit('updateProjectUpdateTime', resp.project.update_time);
dispatch('deleteLocalVersion', state.id);
}
resp.localData = project.data;
return resp;
},
async updateProject({commit}, projectID) {
const project = await projectApi.getData(projectID);
......@@ -673,10 +686,10 @@ export const projectStore = {
}
}
if(_view.data === data.data){
if(!data.data.hasOwnProperty('$isRootView')){
if (_view.data === data.data) {
if (!data.data.hasOwnProperty('$isRootView')) {
Object.defineProperty(data.data, '$isRootView', {
get(){
get() {
return true;
}
})
......
......@@ -219,11 +219,13 @@
.tpl-editor {
flex: 1;
height: 0;
.el-form-item__content {
height: 100%;
.editor {
width: 100%;
height: 100%;
}
}
......@@ -269,6 +271,7 @@
}
.editor {
width: 100%;
height: 300px;
}
}
......@@ -322,11 +325,13 @@
.editor-form-item {
flex: 1;
height: 0;
.el-form-item__content {
height: 100%;
.editor {
width: 100%;
height: 100%;
}
}
......@@ -359,11 +364,13 @@
}
.editor {
width: 100%;
height: 100%;
}
.computed-editor{
.editor{
width: 100%;
height: 200px;
}
}
......@@ -519,3 +526,21 @@
.pack-failed-details{
width: 80vw !important;
}
.project-conflict-resolve-editor{
.wrapper{
height: 100%;
padding: 10px;
flex: 1;
display: flex;
flex-direction: column;
.container {
flex: 1;
.editor{
height: 100%;
}
}
}
}
......@@ -34,7 +34,8 @@ export const pxHostMapping = {
export const monacoEditorOptions = {
tabSize: 2,
insertSpaces: false
insertSpaces: false,
automaticLayout: true,
};
export const cmdPrefix = 'z-';
......@@ -424,3 +425,7 @@ export function deleteAssetsDepConfig(data) {
delete asset['dep'];
}
}
export function formatJson(source){
return JSON.stringify(JSON.parse(source), null, '\t')
}
......@@ -20,6 +20,7 @@
<px-skin-editor-dialog ref="pxSkinEditorDialog"/>
<process-search-dialog ref="processSearchDialog"/>
<behavior-editor-dialog @change="handleBehaviorsChange" ref="behaviorEditorDialog"></behavior-editor-dialog>
<project-conflict-resolve-dialog ref="projectConflictResolveDialog" @resolved="onConflictResolved"/>
</div>
</template>
......@@ -45,10 +46,12 @@
import PxSkinEditorDialog from "./Editor/dialogs/PxSkinEditorDialog";
import BehaviorEditorDialog from "./Editor/dialogs/BehaviorEditorDialog";
import ProcessSearchDialog from "./Editor/dialogs/ProcessSearchDialog";
import ProjectConflictResolveDialog from "./Editor/dialogs/ProjectConflictResolveDialog";
export default {
name: 'Editor',
components: {
ProjectConflictResolveDialog,
ProcessSearchDialog,
BehaviorEditorDialog,
PxSkinEditorDialog,
......@@ -190,7 +193,7 @@
this.panesConfig[id] = configs[0].width / 100;
localStorage.panesConfig = JSON.stringify(this.panesConfig);
},
async saveProject(closeLoading) {
async saveProject(closeLoading, data) {
let remark, cancel;
await this.$prompt(this.$t('Input version remark'), this.$t('Alert'), {
confirmButtonText: this.$t('Confirm'),
......@@ -204,15 +207,29 @@
cancel = true;
});
if (!cancel) {
await playWaiting(this.saveToRemote({remark}), this.$t('Saving'), closeLoading);
let resp = await playWaiting(this.saveToRemote({remark, data}), this.$t('Saving'), closeLoading);
if (resp.result) {
this.$message({
message: i18n.t('Save project successfully'), //因为message是异步出现,但是当路由回退的时候,this.i18n的实例已经置空,所以要用全局的i18n实例
type: 'success'
});
} else {
await this.$confirm(i18n.t('There are conflicts in the project'), i18n.t('Alert'), {
confirmButtonText: i18n.t('Confirm'),
cancelButtonText: i18n.t('Cancel'),
type: 'warning'
}).then(() => {
this.$refs.projectConflictResolveDialog.show(resp.remoteData, resp.localData);
}).catch((e) => {
});
}
}
return cancel;
},
async onConflictResolved(data) {
this.saveProject(true, data);
},
async clickMenu(menuItem) {
switch (menuItem) {
case 'save':
......@@ -306,7 +323,7 @@
type: 'error',
duration: 1000,
});
if(e.details){
if (e.details) {
let details = e.details.split('\n').join('\n');
this.$alert(`<p>Compile error</p><pre style="color:red;">${details}</pre>`, this.$t('Alert'), {
dangerouslyUseHTMLString: true,
......
<template>
<el-dialog :title="$t('Project conflict resolver')" width="70%" :visible.sync="visible" @opened="onOpen"
:close-on-click-modal="false"
:append-to-body="true"
fullscreen
custom-class="flex-dialog project-conflict-resolve-editor"
>
<div class="wrapper">
<el-steps :active="step" finish-status="success" align-center>
<el-step title="查看冲突"></el-step>
<el-step title="手动合并"></el-step>
<el-step title="完成"></el-step>
</el-steps>
<div class="container">
<monaco-editor
ref="editor"
class="editor"
v-model="localData"
language="json"
:options="monacoEditorOptions"
diff-editor
:original="remoteData"
/>
</div>
</div>
<div slot="footer" class="dialog-footer">
<div>
<el-button size="mini" @click="mergeConflicts" type="success" v-show="step===0">{{$t('Merge conflicts')}}
</el-button>
</div>
<div>
<el-button size="mini" @click="onClose">{{$t('Close')}}</el-button>
<el-button size="mini" @click="onSave" type="primary" :disabled="step===0">{{$t('Save')}}</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
import {mapMutations} from 'vuex';
import MonacoEditor from "vue-monaco";
import {formatJson, monacoEditorOptions} from "../../../utils";
import SplitPanes from 'splitpanes'
const conflictFlags = ['<<<<<<< REMOTE', '=======', '>>>>>>> LOCAL'];
export default {
name: "ProjectConflictResolveDialog",
components: {MonacoEditor, SplitPanes},
data() {
let monacoEditorOptionsSelf = Object.assign({
readOnly: true,
}, monacoEditorOptions);
return {
monacoEditorOptions: monacoEditorOptionsSelf,
visible: false,
step: 0,
remoteData: '',
localData: '',
}
},
mounted() {
},
methods: {
show(remoteData, localData) {
this.remoteData = '';
this.localData = '';
this.$nextTick(() => {
this.remoteData = this.formatJson(remoteData);
this.localData = this.formatJson(localData);
this.step = 0;
this.visible = true;
this.monacoEditorOptions.readOnly = true;
this.diff = null;
this.$nextTick(() => {
if (!this.inited) {
this.inited = true;
let modifiedEditor = this.$refs.editor.getModifiedEditor();
let editor = this.$refs.editor.getEditor();
editor.onDidUpdateDiff(() => {
if (!this.diff) {
this.diff = editor.getLineChanges();
}
});
modifiedEditor.onDidChangeModelDecorations(() => {
});
}
});
});
},
async onSave() {
let resolved = true;
for (let conflictFlag of conflictFlags) {
if (this.localData.includes(conflictFlag)) {
resolved = false;
break;
}
}
if (resolved) {
try {
JSON.parse(this.localData);
await this.$confirm(this.$t('The conflict has been resolved'), this.$t('Alert'), {
confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'),
type: 'warning'
}).then(() => {
this.visible = false;
this.$emit('resolved', this.localData);
}).catch((e) => {
});
} catch (e) {
this.$alert(this.$t('The format of the JSON document is wrong'), {
type: 'warning'
});
}
} else {
this.$alert(this.$t('There are still unresolved conflicts'), {
type: 'warning'
});
}
},
onClose() {
this.visible = false;
},
onOpen() {
},
...mapMutations([
'modifyProjectDetails',
]),
formatJson(source) {
return formatJson(source);
},
mergeConflicts() {
this.monacoEditorOptions.readOnly = false;
this.step++;
let remoteCodeLines = this.remoteData.split('\n');
let localCodeLines = this.localData.split('\n');
let mergedCodeLines = localCodeLines.concat();
//console.log(this.diff);
let offset = 0;
for (let {modifiedStartLineNumber, modifiedEndLineNumber, originalStartLineNumber, originalEndLineNumber} of this.diff) {
let remotePart = [];
for (let i = originalStartLineNumber, li = originalEndLineNumber; i <= li; i++) {
remotePart.push(remoteCodeLines[i - 1]);
}
let localPart = [];
for (let i = modifiedStartLineNumber, li = modifiedEndLineNumber; i <= li; i++) {
localPart.push(localCodeLines[i - 1]);
}
let merged = [];
merged.push(conflictFlags[0]);
merged.push(...remotePart);
merged.push(conflictFlags[1]);
merged.push(...localPart);
merged.push(conflictFlags[2]);
//console.log(merged);
mergedCodeLines.splice(modifiedStartLineNumber - 1 + offset, modifiedEndLineNumber - modifiedStartLineNumber + 1, ...merged);
offset += 3 + remotePart.length;
}
this.localData = mergedCodeLines.join('\n');
},
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment