Commit 0e63f39f authored by rockyl's avatar rockyl

更新mock数据

更改api接口
增加素材列表功能
增加视图编辑功能
增加无效页面自动跳转功能
增加本地版本记录
增加本地版本和服务器版本的选择
删除素材地址前缀改用全路径
parent d64a4c0f
......@@ -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",
"menu": {
"save": "Save",
"details": "Details",
......@@ -38,6 +50,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',
]
})
]
})
......@@ -2,7 +2,9 @@
* Created by rockyl on 2019-09-19.
*/
import Vue from "vue";
import {projectApi} from "../../api";
import generateUUID from "uuid/v4";
export const projectStore = {
state: {
......@@ -16,32 +18,91 @@ export const projectStore = {
}
},
mutations: {
updateProject(state, project) {
const {id, name, creator, data} = project;
state.id = id;
state.name = name;
state.creator = creator;
if(data){
if (data) {
const localData = state.data;
const {views, assets, dataMapping} = JSON.parse(data);
if(!localData.views || localData.views.length === 0){
localData.views = views || [];
if (!localData.views || localData.views.length === 0) {
Vue.set(localData, 'views', views || []);
}
if(!localData.assets || localData.assets.length === 0){
localData.assets = assets || [];
if (!localData.assets || localData.assets.length === 0) {
Vue.set(localData, 'assets', assets || []);
}
if (!localData.dataMapping || localData.dataMapping.length === 0) {
Vue.set(localData, 'dataMapping', dataMapping || []);
}
if(!localData.dataMapping || localData.dataMapping.length === 0){
localData.dataMapping = dataMapping || [];
};
}
},
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),
};
}
},
actions: {
async updateProject({commit}, projectID) {
const project = await projectApi.getData(projectID);
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);
},
},
};
/**
* 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;
.el-tag {
width: 40px;
text-align: right;
margin-right: 5px;
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,44 @@
panesConfig,
}
},
mounted() {
computed: {
...mapGetters([]),
},
async mounted() {
const {projectID} = this.$route.params;
if(projectID){
playWaiting(this.updateProject(projectID), this.$t('Preparing')).catch(e => {});
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.$router.push({name: 'home'})
this.loadRemoteVersion(projectID);
}
},
methods: {
loadLocalVersion(projectID) {
this.loadFromLocal(projectID)
},
loadRemoteVersion(projectID) {
if (projectID) {
playWaiting(this.loadFromRemote(projectID), this.$t('Preparing')).catch(e => {
});
} else {
this.$router.push({name: 'home'})
}
},
getSize(id, side) {
let ratio = this.panesConfig[id];
return (side === 0 ? ratio : 1 - ratio) * 100
......@@ -63,9 +92,17 @@
this.panesConfig[id] = configs[0].width / 100;
localStorage.panesConfig = JSON.stringify(this.panesConfig);
},
clickMenu(menuItem){
console.log(menuItem);
switch(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;
......@@ -75,7 +112,11 @@
}
},
...mapActions([
'updateProject'
'localVersionExist',
'loadFromLocal',
'loadFromRemote',
"saveToLocal",
"saveToRemote",
])
}
}
......
......@@ -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 {
......@@ -38,7 +38,7 @@
return fileTypeIcon[this.ext] === 't';
},
thumbnailUrl(){
return ASSETS_BASE + this.data.url;
return this.data.url;
},
},
methods: {
......
......@@ -2,24 +2,47 @@
<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
empty-text=""
/>
@node-click="selectNode"
>
<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 {
......@@ -37,6 +60,50 @@
...mapState({
views: state => state.project.data.views
}),
},
methods: {
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