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 ...@@ -66,19 +66,24 @@ export async function fetchApi(uri, {host = '', params, method = 'get', auth = t
} }
const response = await fetch(url, options); const response = await fetch(url, options);
if (response.status === 401) { const respText = await response.text();
location.href = '/admin/permission';
if (response.status === 310) { //客户端重定向,用于跨域重定向
location.href = respText;
} }
const jsonObj = await response.json();
//console.log(jsonObj);
if (judgeSuccess) { try {
if (jsonObj.success) { let jsonObj = JSON.parse(respText);
return jsonObj[dataField]; if (judgeSuccess) {
if (jsonObj.success) {
return jsonObj.data;
}
} else {
return jsonObj;
} }
} 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 @@ ...@@ -155,6 +155,7 @@
"Failed to get project": "获取项目失败", "Failed to get project": "获取项目失败",
"Failed to save project": "保存项目失败", "Failed to save project": "保存项目失败",
"Save project successfully": "保存项目成功", "Save project successfully": "保存项目成功",
"There are conflicts in the project": "项目有冲突,请先解决冲突",
"Input version remark": "输入版本备注", "Input version remark": "输入版本备注",
"Input view name": "输入视图名", "Input view name": "输入视图名",
"Invalid view name": "无效的视图名", "Invalid view name": "无效的视图名",
...@@ -226,6 +227,11 @@ ...@@ -226,6 +227,11 @@
"Operate": "操作", "Operate": "操作",
"Computed": "计算属性", "Computed": "计算属性",
"Are you sure to close?": "确定关闭吗?", "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": { "eventGroup": {
"in": "接收", "in": "接收",
"out": "派发" "out": "派发"
......
...@@ -91,6 +91,9 @@ export const projectStore = { ...@@ -91,6 +91,9 @@ export const projectStore = {
}, },
makeProjectDirty() { makeProjectDirty() {
},
updateProjectUpdateTime(state, updateTime) {
state.update_time = updateTime;
}, },
updateProject(state, project) { updateProject(state, project) {
const {id, name, creator, data, operators, update_time} = project; const {id, name, creator, data, operators, update_time} = project;
...@@ -352,11 +355,11 @@ export const projectStore = { ...@@ -352,11 +355,11 @@ export const projectStore = {
copyNode(state, {node, parentNode, copyState}) { copyNode(state, {node, parentNode, copyState}) {
let _node1 = node; let _node1 = node;
console.log(_node1) console.log(_node1)
copyNodeCatch =clonePureObj(_node1) copyNodeCatch = clonePureObj(_node1)
if(copyState==1){ if (copyState == 1) {
//复制行为 //复制行为
copyNodeCatch.events copyNodeCatch.events
}else if(copyState==2){ } else if (copyState == 2) {
//不复制行为 //不复制行为
delete copyNodeCatch.events delete copyNodeCatch.events
} }
...@@ -453,17 +456,17 @@ export const projectStore = { ...@@ -453,17 +456,17 @@ export const projectStore = {
state.mockServeEnabled = enabled; state.mockServeEnabled = enabled;
}, },
modifyViewStore(state, {view, store}){ modifyViewStore(state, {view, store}) {
view.store = store; view.store = store;
this.commit('makeProjectDirty'); this.commit('makeProjectDirty');
}, },
addCmd(state, cmd){ addCmd(state, cmd) {
Vue.set(state.activeComponent.properties, cmd, ''); Vue.set(state.activeComponent.properties, cmd, '');
this.commit('makeProjectDirty'); this.commit('makeProjectDirty');
}, },
deleteCmd(state, cmd){ deleteCmd(state, cmd) {
Vue.delete(state.activeComponent.properties, cmd); Vue.delete(state.activeComponent.properties, cmd);
this.commit('makeProjectDirty'); this.commit('makeProjectDirty');
}, },
...@@ -566,7 +569,7 @@ export const projectStore = { ...@@ -566,7 +569,7 @@ export const projectStore = {
let index = entries.indexOf(behavior.meta); let index = entries.indexOf(behavior.meta);
if (index >= 0) { if (index >= 0) {
let path = paths.splice(index, 1, null)[0]; let path = paths.splice(index, 1, null)[0];
if(!path){ if (!path) {
continue; continue;
} }
path[0] = { path[0] = {
...@@ -641,9 +644,19 @@ export const projectStore = { ...@@ -641,9 +644,19 @@ export const projectStore = {
throw new Error('Project does not exist') throw new Error('Project does not exist')
} }
}, },
async saveToRemote({state, dispatch, getters}, {remark}) { async saveToRemote({state, dispatch, getters, commit}, {remark, data}) {
await projectApi.saveOne(getters.project, remark); let project = Object.assign({}, getters.project);
dispatch('deleteLocalVersion', state.id); 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) { async updateProject({commit}, projectID) {
const project = await projectApi.getData(projectID); const project = await projectApi.getData(projectID);
...@@ -673,10 +686,10 @@ export const projectStore = { ...@@ -673,10 +686,10 @@ export const projectStore = {
} }
} }
if(_view.data === data.data){ if (_view.data === data.data) {
if(!data.data.hasOwnProperty('$isRootView')){ if (!data.data.hasOwnProperty('$isRootView')) {
Object.defineProperty(data.data, '$isRootView', { Object.defineProperty(data.data, '$isRootView', {
get(){ get() {
return true; return true;
} }
}) })
...@@ -810,7 +823,7 @@ export const projectStore = { ...@@ -810,7 +823,7 @@ export const projectStore = {
let debug = params.debug; let debug = params.debug;
let packedAssets; let packedAssets;
//if (!debug) { //if (!debug) {
packedAssets = await packAssets(state.data.assets); packedAssets = await packAssets(state.data.assets);
//} //}
const packResult = await projectApi.pack(state.id, debug, packedAssets); const packResult = await projectApi.pack(state.id, debug, packedAssets);
......
...@@ -219,11 +219,13 @@ ...@@ -219,11 +219,13 @@
.tpl-editor { .tpl-editor {
flex: 1; flex: 1;
height: 0;
.el-form-item__content { .el-form-item__content {
height: 100%; height: 100%;
.editor { .editor {
width: 100%;
height: 100%; height: 100%;
} }
} }
...@@ -269,6 +271,7 @@ ...@@ -269,6 +271,7 @@
} }
.editor { .editor {
width: 100%;
height: 300px; height: 300px;
} }
} }
...@@ -322,11 +325,13 @@ ...@@ -322,11 +325,13 @@
.editor-form-item { .editor-form-item {
flex: 1; flex: 1;
height: 0;
.el-form-item__content { .el-form-item__content {
height: 100%; height: 100%;
.editor { .editor {
width: 100%;
height: 100%; height: 100%;
} }
} }
...@@ -359,11 +364,13 @@ ...@@ -359,11 +364,13 @@
} }
.editor { .editor {
width: 100%;
height: 100%; height: 100%;
} }
.computed-editor{ .computed-editor{
.editor{ .editor{
width: 100%;
height: 200px; height: 200px;
} }
} }
...@@ -519,3 +526,21 @@ ...@@ -519,3 +526,21 @@
.pack-failed-details{ .pack-failed-details{
width: 80vw !important; 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 = { ...@@ -34,7 +34,8 @@ export const pxHostMapping = {
export const monacoEditorOptions = { export const monacoEditorOptions = {
tabSize: 2, tabSize: 2,
insertSpaces: false insertSpaces: false,
automaticLayout: true,
}; };
export const cmdPrefix = 'z-'; export const cmdPrefix = 'z-';
...@@ -424,3 +425,7 @@ export function deleteAssetsDepConfig(data) { ...@@ -424,3 +425,7 @@ export function deleteAssetsDepConfig(data) {
delete asset['dep']; delete asset['dep'];
} }
} }
export function formatJson(source){
return JSON.stringify(JSON.parse(source), null, '\t')
}
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
<px-skin-editor-dialog ref="pxSkinEditorDialog"/> <px-skin-editor-dialog ref="pxSkinEditorDialog"/>
<process-search-dialog ref="processSearchDialog"/> <process-search-dialog ref="processSearchDialog"/>
<behavior-editor-dialog @change="handleBehaviorsChange" ref="behaviorEditorDialog"></behavior-editor-dialog> <behavior-editor-dialog @change="handleBehaviorsChange" ref="behaviorEditorDialog"></behavior-editor-dialog>
<project-conflict-resolve-dialog ref="projectConflictResolveDialog" @resolved="onConflictResolved"/>
</div> </div>
</template> </template>
...@@ -45,10 +46,12 @@ ...@@ -45,10 +46,12 @@
import PxSkinEditorDialog from "./Editor/dialogs/PxSkinEditorDialog"; import PxSkinEditorDialog from "./Editor/dialogs/PxSkinEditorDialog";
import BehaviorEditorDialog from "./Editor/dialogs/BehaviorEditorDialog"; import BehaviorEditorDialog from "./Editor/dialogs/BehaviorEditorDialog";
import ProcessSearchDialog from "./Editor/dialogs/ProcessSearchDialog"; import ProcessSearchDialog from "./Editor/dialogs/ProcessSearchDialog";
import ProjectConflictResolveDialog from "./Editor/dialogs/ProjectConflictResolveDialog";
export default { export default {
name: 'Editor', name: 'Editor',
components: { components: {
ProjectConflictResolveDialog,
ProcessSearchDialog, ProcessSearchDialog,
BehaviorEditorDialog, BehaviorEditorDialog,
PxSkinEditorDialog, PxSkinEditorDialog,
...@@ -190,7 +193,7 @@ ...@@ -190,7 +193,7 @@
this.panesConfig[id] = configs[0].width / 100; this.panesConfig[id] = configs[0].width / 100;
localStorage.panesConfig = JSON.stringify(this.panesConfig); localStorage.panesConfig = JSON.stringify(this.panesConfig);
}, },
async saveProject(closeLoading) { async saveProject(closeLoading, data) {
let remark, cancel; let remark, cancel;
await this.$prompt(this.$t('Input version remark'), this.$t('Alert'), { await this.$prompt(this.$t('Input version remark'), this.$t('Alert'), {
confirmButtonText: this.$t('Confirm'), confirmButtonText: this.$t('Confirm'),
...@@ -204,15 +207,29 @@ ...@@ -204,15 +207,29 @@
cancel = true; cancel = true;
}); });
if (!cancel) { if (!cancel) {
await playWaiting(this.saveToRemote({remark}), this.$t('Saving'), closeLoading); let resp = await playWaiting(this.saveToRemote({remark, data}), this.$t('Saving'), closeLoading);
this.$message({ if (resp.result) {
message: i18n.t('Save project successfully'), //因为message是异步出现,但是当路由回退的时候,this.i18n的实例已经置空,所以要用全局的i18n实例 this.$message({
type: 'success' 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; return cancel;
}, },
async onConflictResolved(data) {
this.saveProject(true, data);
},
async clickMenu(menuItem) { async clickMenu(menuItem) {
switch (menuItem) { switch (menuItem) {
case 'save': case 'save':
...@@ -306,7 +323,7 @@ ...@@ -306,7 +323,7 @@
type: 'error', type: 'error',
duration: 1000, duration: 1000,
}); });
if(e.details){ if (e.details) {
let details = e.details.split('\n').join('\n'); let details = e.details.split('\n').join('\n');
this.$alert(`<p>Compile error</p><pre style="color:red;">${details}</pre>`, this.$t('Alert'), { this.$alert(`<p>Compile error</p><pre style="color:red;">${details}</pre>`, this.$t('Alert'), {
dangerouslyUseHTMLString: true, 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