Commit 31899580 authored by 张晨辰's avatar 张晨辰

feat: merge

parents d5e2284c 0e63f39f
......@@ -58,12 +58,12 @@ const data = {
{
"name": "bg.jpg",
uuid:"a1",
"url": "/bg.jpg"
"url": "http://0.0.0.0:4002/assets/bg.jpg"
},
{
"name": "btn-join.png",
uuid:"a2",
"url": "/btn-join.png"
"url": "http://0.0.0.0:4002/assets/btn-join.png"
}
]
};
......@@ -71,7 +71,7 @@ const data = {
const resp = {
"success": true,
"data": {
"id": "6e1c9eadf8e28",
"id": "6566c4a3f237",
"name": "测试",
"creator": "卞龙亭",
"operator": "卞龙亭",
......
{
"success": true
}
\ No newline at end of file
<template>
<div id="app" class="theme-light">
<router-view/>
<div class="invalid-route" v-if="invalidRoute">
<div class="invalid-route" v-if="ready && invalidRoute">
<p>无效的页面 {{cd}}秒后跳转</p>
</div>
</div>
......@@ -14,6 +14,7 @@
data() {
return {
cd: 5,
ready: false,
}
},
computed: {
......@@ -25,13 +26,11 @@
$route: {
handler: function(val, oldVal){
this.dealInvalidRoute();
this.ready = true;
},
deep: true
}
},
mounted() {
this.dealInvalidRoute();
},
methods: {
dealInvalidRoute(){
if (this.invalidRoute) {
......
......@@ -36,15 +36,15 @@ export async function deleteOne(id) {
})
}
export async function getData(id) {
export async function fetchOne(id) {
return await fetchApi('/api/project/query/data', {
params: {id},
method: 'get',
errMessage: 'Failed to get project',
errMessage: 'Failed to fetch project',
})
}
export async function updateOne(project) {
export async function saveOne(project) {
return await fetchApi('/api/project/update', {
params: project,
method: 'post',
......
......@@ -4,7 +4,8 @@
export const API_HOST = 'http://10.10.94.31:7777';
//export const API_HOST = 'http://localhost:3002';
export const ASSETS_BASE = 'http://0.0.0.0:4002/assets';
export const UPLOAD_FILE_URL = API_HOST + '/api/uploadFile';
//文件类型图标 t表示展示缩略图
export const fileTypeIcon = {
......
......@@ -3,6 +3,11 @@
"Confirm": "Confirm",
"Cancel": "Cancel",
"Save": "Save",
"Add": "Add",
"Delete": "Delete",
"Import": "Import",
"Export": "Export",
"Upload": "Upload",
"Failed to fetch": "Network error!",
"In processing": "In processing",
"Projects": "Projects",
......@@ -15,6 +20,7 @@
"Template": "Template",
"Preparing": "Preparing…",
"Deleting": "Deleting…",
"Saving": "Saving…",
"Create project": "Create project",
"Creating project": "Creating project…",
"Create project success": "Create project success",
......@@ -30,6 +36,12 @@
"Failed to delete project": "Failed to delete project",
"Failed to get project": "Failed to get project",
"Failed to save project": "Failed to save project",
"Save project successfully": "Save project successfully",
"Input view name": "Input view name",
"Invalid view name": "Invalid view name",
"Unsaved version found locally": "Unsaved version found locally, please select version to open",
"Local Version": "Local Version",
"Remote Version": "Remote Version",
"Confirm to exit the editor": "Confirm to exit the editor?",
"Confirm to publish": "Confirm to publish?",
"menu": {
......@@ -40,6 +52,12 @@
"data-mapping": "DataMapping",
"exit": "Exit"
},
"view_node_menu": {
"node": "Node",
"image": "Image",
"label": "Label",
"rect": "Rect"
},
"panes": {
"Assets": "Assets",
"Inspector": "Inspector",
......
......@@ -7,6 +7,7 @@ import Vuex from 'vuex'
import {envStore} from "./modules/env";
import {projectsStore} from "./modules/projects";
import {projectStore} from "./modules/project";
import SaveToLocalPlugin from "./save-to-local-plugin";
Vue.use(Vuex);
......@@ -19,5 +20,14 @@ export default new Vuex.Store({
projects: projectsStore,
project: projectStore,
},
plugins: [
SaveToLocalPlugin({
mutationTypes: [
'addNode',
'deleteNode',
'addAsset',
]
})
]
})
/**
* Created by rockyl on 2019-09-19.
*/
import Vue from "vue";
import { projectApi } from "../../api";
import { compoleteComponentData } from '../../utils/compoleteCmpData';
let testData = {
views: [{
name: '视图1', type: 'node', children: [{
name: 'image',
type: 'image',
properties: {
width: 100,
height: 100,
left: 1,
top: 1,
source: 'http://yun.duiba.com.cn/images/201909/ogzik0c3hk.png'
}
}, {
name: 'label',
type: 'label',
properties: {
width: 110,
height: 110,
left: 100,
top: 100,
text: 'textlabel',
color: '#fff',
size: 12,
align: 'left'
}
}, {
name: 'rect',
type: 'rect',
properties: {
fillColor: '#fff',
strokeColor: '#000',
strokeWidth: 1,
width: 120,
height: 120,
left: 200,
top: 200
},
children: [{
name: 'label2',
type: 'label',
properties: {
text: 'textlabel2',
color: '#fff',
size: 12,
align: 'left',
width: 130,
height: 130,
left: 300,
top: 300
}
}]
}]
}]
}
// let testData = {
// views: [{
// name: 'rect',
// type: 'rect',
// properties: {
// fillColor: '#fff',
// strokeColor: '#000',
// strokeWidth: 1
// }
// }]
// }
import generateUUID from "uuid/v4";
export const projectStore = {
state: {
......@@ -101,16 +36,14 @@ export const projectStore = {
const localData = state.data;
const { views, assets, dataMapping } = JSON.parse(data);
if (!localData.views || localData.views.length === 0) {
localData.views = views || [];
Vue.set(localData, 'views', views || []);
}
if (!localData.assets || localData.assets.length === 0) {
localData.assets = assets || [];
Vue.set(localData, 'assets', assets || []);
}
if (!localData.dataMapping || localData.dataMapping.length === 0) {
localData.dataMapping = dataMapping || [];
};
} else {
state.data.views = testData.views;
Vue.set(localData, 'dataMapping', dataMapping || []);
}
}
compoleteComponentData(state.data.views);
},
......@@ -133,9 +66,42 @@ export const projectStore = {
state.activeComponent = id;
state.activeIdList = [id];
console.log('mutations activeComponent', state);
},
addNode(state, { node, name, type }) {
const child = {
name,
type,
};
if (node) {
if (!node.children) {
Vue.set(node, 'children', []);
}
node.children.push(child);
} else {
state.data.views.push(child);
}
},
deleteNode(state, { node, parentNode }) {
const index = parentNode.children.indexOf(node);
parentNode.children.splice(index, 1);
},
addAsset(state, { url, file }) {
state.data.assets.push({
name: file.name,
url,
uuid: generateUUID(),
})
},
},
getters: {
project(state) {
const { id, name, creator, data } = state;
return {
id, name, creator,
data: JSON.stringify(data),
};
},
/**
* 当前激活的组件
*/
......@@ -167,6 +133,34 @@ export const projectStore = {
}
},
actions: {
saveToLocal({ getters }) {
const { project } = getters;
localStorage.setItem('project-' + project.id, JSON.stringify(project));
},
localVersionExist({ commit }, projectID) {
let json = localStorage.getItem('project-' + projectID);
return !!json;
},
loadFromLocal({ commit }, projectID) {
let json = localStorage.getItem('project-' + projectID);
if (json) {
const project = JSON.parse(json);
commit('updateProject', project);
}
},
deleteLocalVersion({ state }, projectID) {
localStorage.removeItem('project-' + projectID);
},
async loadFromRemote({ commit, dispatch }, projectID) {
const project = await projectApi.fetchOne(projectID);
dispatch('deleteLocalVersion', projectID);
commit('updateProject', project);
},
async saveToRemote({ state, dispatch, getters }) {
await projectApi.saveOne(getters.project);
dispatch('deleteLocalVersion', state.id);
},
async updateProject({ commit }, projectID) {
const project = await projectApi.getData(projectID);
commit('updateProject', project);
......@@ -180,6 +174,7 @@ export const projectStore = {
console.log('actions activeComponent', data);
commit('activeComponent', data);
}
},
},
};
/**
* Created by rockyl on 2019-09-25.
*/
export default function (options = {}) {
const {mutationTypes = []} = options;
return (store) => {
store.subscribe((mutation, state) => {
if (mutationTypes.includes(mutation.type)) {
store.dispatch('saveToLocal')
}
})
}
}
......@@ -35,7 +35,6 @@
display: flex;
justify-content: center;
align-items: center;
//background: gray;
.thumbnail {
max-width: 60px;
......@@ -50,6 +49,29 @@
.name {
text-align: center;
font-size: 12px;
width: 60px;
overflow: hidden;
}
}
.file-uploader {
width: 60px;
height: 60px;
margin: 5px 5px 5px;
border: 1px dashed $--color-text-secondary;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
.file-uploader-icon {
font-size: 28px;
color: $--color-text-regular;
width: 60px;
height: 60px;
line-height: 60px;
text-align: center;
}
}
}
......@@ -80,13 +102,22 @@
.name-bar {
color: $--color-white;
align-self: flex-start;
align-self: stretch;
.item{
display: flex;
.el-tag {
width: 40px;
text-align: right;
margin-right: 5px;
}
.value{
width: 0;
flex: 1;
overflow-wrap: break-word;
}
}
}
.big-image {
......
......@@ -22,5 +22,26 @@
width: 100%;
flex: 1;
}
.el-tree-node__content:hover{
& > .tree-node > .more-button{
display: block;
}
}
.tree-node{
flex: 1;
flex-direction: row;
display: flex;
padding-right: 10px;
.node-name{
flex: 1;
}
.more-button{
display: none;
}
}
}
}
\ No newline at end of file
......@@ -17,7 +17,7 @@
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { mapGetters, mapActions } from 'vuex';
import SplitPanes from 'splitpanes';
import ToolBar from './Editor/ToolBar';
import Inspector from './Editor/Inspector';
......@@ -46,15 +46,45 @@ export default {
panesConfig
};
},
mounted() {
computed: {
...mapGetters([])
},
async mounted() {
const { projectID } = this.$route.params;
if (await this.localVersionExist(projectID)) {
this.$confirm(this.$t('Unsaved version found locally'), this.$t('Alert'), {
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
confirmButtonText: this.$t('Local Version'),
cancelButtonText: this.$t('Remote Version'),
type: 'warning'
})
.then(() => {
this.loadLocalVersion(projectID);
})
.catch(e => {
if (e === 'cancel') {
this.loadRemoteVersion(projectID);
} else {
console.log(e);
}
});
} else {
this.loadRemoteVersion(projectID);
}
},
methods: {
loadLocalVersion(projectID) {
this.loadFromLocal(projectID);
},
loadRemoteVersion(projectID) {
if (projectID) {
playWaiting(this.updateProject(projectID), this.$t('Preparing')).catch(e => {});
playWaiting(this.loadFromRemote(projectID), this.$t('Preparing')).catch(e => {});
} else {
this.$router.push({ name: 'home' });
}
},
methods: {
getSize(id, side) {
let ratio = this.panesConfig[id];
return (side === 0 ? ratio : 1 - ratio) * 100;
......@@ -63,9 +93,17 @@ export default {
this.panesConfig[id] = configs[0].width / 100;
localStorage.panesConfig = JSON.stringify(this.panesConfig);
},
clickMenu(menuItem) {
console.log(menuItem);
async clickMenu(menuItem) {
switch (menuItem) {
case 'save':
try {
await playWaiting(this.saveToRemote(), this.$t('Saving'));
this.$message({
message: this.$t('Save project successfully'),
type: 'success'
});
} catch (e) {}
break;
case 'details':
this.$refs.projectDialogsDialog.show();
break;
......@@ -73,7 +111,7 @@ export default {
this.$refs.dataMappingDialog.show();
break;
case 'exit':
this,exitConfirm();
this, exitConfirm();
break;
case 'publish':
this.publishConfirm();
......@@ -91,7 +129,6 @@ export default {
});
// todo
// exit
console.log('exit confirm');
},
/**
* 发布
......@@ -103,10 +140,9 @@ export default {
type: 'warning'
});
// todo
// exit
console.log('exit confirm');
// publish
},
...mapActions(['updateProject'])
...mapActions(['localVersionExist', 'loadFromLocal', 'loadFromRemote', 'saveToLocal', 'saveToRemote'])
}
};
</script>
......
......@@ -2,10 +2,21 @@
<pane class="assets" icon="el-icon-s-shop" :title="$t('panes.Assets')">
<div class="container">
<div class="header-bar">
<el-link>上传</el-link>
<el-link>{{$t('Upload')}}</el-link>
</div>
<el-scrollbar class="assets-scrollbar" wrap-class="wrap-x-hidden">
<div class="file-list">
<el-upload
class="file-uploader"
:action="uploadFileUrl"
name="file"
multiple
:show-file-list="false"
:on-success="uploadFileSuccess"
:on-error="uploadFileError"
>
<i class="el-icon-plus file-uploader-icon"></i>
</el-upload>
<file-item v-for="file in assets" :data="file" :key="file.url" @show-file-details="showFileDetails"/>
</div>
</el-scrollbar>
......@@ -15,10 +26,11 @@
</template>
<script>
import {mapState, mapActions} from 'vuex'
import {mapState, mapMutations} from 'vuex'
import Pane from "../../components/Pane";
import FileItem from "./Assets/FileItem";
import AssetsShow from "./Assets/AssetsShow";
import {UPLOAD_FILE_URL} from "../../config";
export default {
name: "Assets",
......@@ -27,6 +39,9 @@
return {}
},
computed: {
uploadFileUrl(){
return UPLOAD_FILE_URL;
},
...mapState({
assets: state => state.project.data.assets
}),
......@@ -37,7 +52,24 @@
methods: {
showFileDetails(file){
this.$refs.assetsShow.show(file);
},
uploadFileSuccess(response, file){
if(response.success){
console.log('upload success', response, file);
this.addAsset({
url: response.url,
file,
})
}else{
this.uploadFileError();
}
},
uploadFileError(){
console.log('upload error')
},
...mapMutations([
'addAsset'
]),
}
}
</script>
......
......@@ -12,8 +12,14 @@
<div class="wrapper">
<el-button class="close-button" size="mini" circle icon="el-icon-close" @click="hide"></el-button>
<div class="name-bar">
<div><el-tag size="mini" type="success">name</el-tag>{{file.name}}</div>
<div><el-tag size="mini" type="success">url</el-tag>{{file.url}}</div>
<div class="item">
<el-tag size="mini" type="success">name</el-tag>
<span class="value">{{file.name}}</span>
</div>
<div class="item">
<el-tag size="mini" type="success">url</el-tag>
<span class="value">{{file.url}}</span>
</div>
</div>
<el-image class="big-image" :src="imageUrl" fit="contain"/>
<div class="operate-bar">
......@@ -28,7 +34,6 @@
</template>
<script>
import {ASSETS_BASE} from "../../../config";
export default {
name: "AssetsShow",
......@@ -40,7 +45,7 @@
},
computed: {
imageUrl(){
return ASSETS_BASE + this.file.url;
return this.file.url;
},
},
methods: {
......
......@@ -11,7 +11,7 @@
</template>
<script>
import {ASSETS_BASE, fileTypeIcon} from "../../../config";
import {fileTypeIcon} from "../../../config";
import path from "path";
export default {
......
......@@ -2,26 +2,48 @@
<pane class="views" icon="el-icon-s-grid" :title="$t('panes.Views')">
<div class="container">
<div class="header-bar">
<el-link>导入</el-link>
<el-link>导出</el-link>
<el-link @click="toAddView">{{$t('Add')}}</el-link>
<el-link @click="toImportView">{{$t('Import')}}</el-link>
<el-link @click="toExportView">{{$t('Export')}}</el-link>
</div>
<el-scrollbar class="tree-scrollbar" wrap-class="wrap-x-hidden">
<el-tree
:data="views"
:props="defaultProps"
:expand-on-click-node="false"
draggable
highlight-current
:default-expand-all="true"
@node-click="handleNodeClick"
empty-text=""
/>
>
<div slot-scope="{ node, data }" class="tree-node">
<div class="node-name">
<span>{{data.name}}</span>
</div>
<el-dropdown class="more-button" size="mini" trigger="click"
@command="(command)=>{onMoreMenu(command, data, node)}">
<el-link icon="el-icon-more" :underline="false"/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="delete">{{$t('Delete')}}</el-dropdown-item>
<el-dropdown-item v-for="(type, key, index) in $t('view_node_menu')"
:key="key"
:command="'add_node_' + key"
:divided="index===0">
{{$t('Add') + type}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-tree>
</el-scrollbar>
</div>
</pane>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { mapState, mapMutations } from 'vuex';
import Pane from '../../components/Pane';
export default {
......@@ -46,7 +68,41 @@ export default {
*/
handleNodeClick(data) {
this.$store.commit('activeComponent', data);
},
toAddView() {
this.$prompt(this.$t('Input view name'), this.$t('Alert'), {
confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'),
inputPattern: /^.{1,64}$/,
inputErrorMessage: this.$t('Invalid view name')
})
.then(({ value }) => {
this.addNode({
name: value,
type: 'node'
});
})
.catch(() => {});
},
toImportView() {},
toExportView() {},
selectNode(data, node, target) {},
onMoreMenu(command, data, node) {
if (command === 'delete') {
this.deleteNode({
node: data,
parentNode: node.parent.data
});
} else if (command.startsWith('add_node_')) {
const type = command.substr('add_node_'.length);
this.addNode({
node: data,
type,
name: 'node'
});
}
},
...mapMutations(['deleteNode', 'addNode'])
}
};
</script>
......
......@@ -7769,6 +7769,11 @@ uuid@^3.0.1, uuid@^3.3.2:
resolved "https://registry.npm.taobao.org/uuid/download/uuid-3.3.3.tgz?cache=0&sync_timestamp=1566221202613&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuuid%2Fdownload%2Fuuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha1-RWjwIW54dg7h2/Ok0s9T4iQRKGY=
uuid@^3.3.3:
version "3.3.3"
resolved "https://registry.npm.taobao.org/uuid/download/uuid-3.3.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuuid%2Fdownload%2Fuuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha1-RWjwIW54dg7h2/Ok0s9T4iQRKGY=
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.npm.taobao.org/validate-npm-package-license/download/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
......
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