一个通过vue实现的练手小项目,数据保存和导出通过node进行处理

成品截图:

安装vue-cli,webpack:

cnpm install webpack -g

cnpm install vue-cli -g

通过vue-cli搭建项目:

需要使用vuex管理数据,添加store文件夹,最终目录结构:

----vue_notes

  |--components

  |--router

  |--store

编辑入口文件 main.js

//引入Vue
import Vue from 'vue'
//引入vuere-source,该组件为网络请求组件
import VueResource from "vue-resource"
//引入store,vuex对象
import store from './store'
//引入入口页面
import App from './App'
//使用vue-resource中间件挂载网络请求组件
Vue.use(VueResource);

/* 实例化vue项目 */
new Vue({
    el: '#app',
    store,
    ...App
})

vuex出口文件 store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'

//添加vuex中间件
Vue.use(Vuex);

//使用数据结构
const state = {
    isAllList: true,
    notes: [],
    activeNote: {},
}

export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters,
})

vuex方法声明 /store/mutation-types.js

//修改日记列表状态,是否查看全部
export const changeListStatus = "changeListStatus";
//新增日记
export const addNote = "addNote";
//编辑当前日记
export const editNote = "editNote";
//删除所选日记
export const deleteNote = "deleteNote";
//切换收藏状态
export const toggleFavorite = "toggleFavorite";
//切换当前日记
export const setActiveNote = "setActiveNote";
//初始化日记数据
export const initNotes = 'initNotes';
//编辑当前日记标题
export const eidtNoteTitle = 'eidtNoteTitle';

store/mutations.js 声明方法

import * as types from './mutation-types'

export default {
    [types.changeListStatus](state, bool) {
        state.isAllList = bool;
    },
    [types.addNote](state) {
        const newNote = {
            text: 'New note',
            title: 'New',
            favorite: !state.isAllList,
            _rm: Math.random(),
        }
        state.notes.push(newNote);
        state.activeNote = newNote;
    },
    [types.editNote](state, text) {
        state.activeNote.text = text;
    },
    [types.deleteNote](state) {
        let rm = state.activeNote['_rm'];
        let index = state.notes.findIndex(function(v, i) {
            if(rm == v['_rm']) return true;
            return false;
        });
        if(index >= 0) state.notes.splice(index, 1);
        state.activeNote = state.notes[0] || {};
    },
    [types.toggleFavorite](state) {
        state.activeNote['favorite'] = !state.activeNote['favorite']
    },
    [types.setActiveNote](state, note) {
        state.activeNote = note;
    },
    [types.initNotes](state, notes) {
        for(let i of notes.notes) {
            if(i._rm === notes.activeNote._rm) {
                notes.activeNote = i;
                break;
            }
        }
        state.isAllList = notes.isAllList;
        state.notes = notes.notes;
        state.activeNote = notes.activeNote;
        window.state = state;
    },
    [types.eidtNoteTitle](state, title) {
        state.activeNote.title = title;
    }
}

/store/actions.js 声明异步方法

import * as types from './mutation-types'

export default {
    [types.changeListStatus]({ commit }, { bool }) {
        commit('changeListStatus', bool);
    },
    [types.addNote]({ commit }) {
        commit('addNote');
    },
    [types.editNote]({ commit }, { text }) {
        commit('editNote', text);
    },
    [types.deleteNote]({ commit }) {
        commit('deleteNote');
    },
    [types.toggleFavorite]({ commit }) {
        commit('toggleFavorite');
    },
    [types.setActiveNote]({ commit }, { note }) {
        commit('setActiveNote', note);
    },
    [types.initNotes]({ commit }, { notes }) {
        commit('initNotes', notes);
    },
    [types.eidtNoteTitle]({ commit }, { title }) {
        commit('eidtNoteTitle', title);
    }
}

/store/getters.js 声明获取数据方法

export default {
    favoriteNotes: state => {
        return state.notes.filter((v, i) => v['favorite']);
    }
}

App.vue 外层组件

<template>
    <div id="app">
        <toolbar></toolbar>
        <notes-list></notes-list>
        <notes-editor></notes-editor>
    </div>
</template>

<script>
    import Vue from 'vue'
    import { mapActions, mapState } from 'vuex'

    import Toolbar from "./components/Toolbar.vue";
    import NotesList from "./components/NotesList.vue";
    import NotesEditor from "./components/NotesEditor.vue";

    export default {
        name: 'app',
        components: {
            Toolbar,
            NotesList,
            NotesEditor
        },
        computed: {
//引入vuex状态生成对应计算属性
            ...mapState({
                isAllList: state => state.isAllList,
                notes: state => state.notes,
                activeNote: state => state.activeNote,
            })
        },
//钩子方法,创建dom之前抓取数据进行初始化
        beforeCreate() {
            this.$http.get('/test.action').then(function(res) {
                return res.json();
            }).then((data) => this.initNotes({notes: data}));
        },
        methods: {
//引入vuex initNotes方法
            ...mapActions(['initNotes']),
            save() {
                this.$http.post('/save.action', this.$store.state).then((res) => res.json()).then((data) => console.log(data));
            }
        },
//监听数据变化,如果出现变化,重新保存到后台
        watch: {
            'isAllList': function() {
                return this.save;
            },
            'notes': function() {
                return this.save;
            },
            'activeNote': {
                handler: function() {
                    return this.save;
                },
                deep: true
            },
        }
    }
</script>

Toolbar.vue 操作日记按钮组件

<template>
    <div id="toolbar">
        <i class="glyphicon glyphicon-plus" @click="addNote"></i>
        <i class="glyphicon glyphicon-star" :class="{starred: activeNote['favorite']}" @click="toggleFavorite"></i>
        <i class="glyphicon glyphicon-remove" @click="deleteNote"></i>
        <i class="glyphicon glyphicon-save" @click="down"></i>
    </div>
</template>

<script>
    import { mapState, mapActions } from "Vuex";

     export default {
        computed: {
            ...mapState({
                activeNote: state => state.activeNote,
            })
        },
        methods: {
            ...mapActions({
                addNote: 'addNote',
                toggleFavorite: 'toggleFavorite',
                deleteNote: 'deleteNote'
            }),
            down() {
                window.open('/down.action', '_blank');
            }
        }
    }
</script>

 NodeList.vue 日记列表组件

<template>
    <div id="notes-list">
        <div id="list-header">
            <h2>Notes | coligo</h2>
            <div class="btn-group btn-group-justified" role="group">
                <div class="btn-group" role="group">
                    <button type="button" class="btn btn-default" :class="{active:isAllList}" @click="changeStatus('isAll')"> All Notes </button>
                </div>
                <div class="btn-group" role="group">
                    <button type="button" class="btn btn-default" :class="{active:!isAllList}" @click="changeStatus('isFavorite')"> Favorites </button>
                </div>
            </div>
        </div>
        <div id="container">
            <div class="list-group">
                <a class="list-group-item" href="javascript:;" v-for="(v,k) in list" :class="{active: v['_rm']==activeNote['_rm']}" @click="setActiveNote({note:v})">
                    <h4 class="list-group-item-heading">{{ v['title'].length>10 ? v['title'].substring(0,10) + "..." : v['title'] }}</h4>
                </a>
            </div>
        </div>
    </div>
</template>

<script>
    import { mapState, mapGetters, mapActions } from "Vuex";

    export default {
        data() {
            return {
                list: [],
            }
        },
        computed: {
            ...mapState({
                isAllList: state => state.isAllList,
                notes: state => state.notes,
                activeNote: state => state.activeNote,
            }),
            ...mapGetters({
                favoriteNotes: 'favoriteNotes',
            }),
        },
        methods: {
            ...mapActions({
                setActiveNote: 'setActiveNote',
                changeListStatus: 'changeListStatus',
            }),
            changeStatus(s) {
                if(s == 'isAll') {
                    this.changeListStatus({ bool: true });
                } else if(s == 'isFavorite') {
                    this.changeListStatus({ bool: false });
                }
            },
            changeList() {
                if(this.isAllList) {
                    this.$data.list = this.notes;
                } else {
                    this.$data.list = this.favoriteNotes;
                }
            },
        },
        watch: {
            notes: function() {
                this.changeList();
            },
            isAllList: function() {
                this.changeList();
            },
        },
        mounted: function() {
//数据更新重新更新this.$data中数据再执行dom更新
            this.$nextTick(function() {
                this.$data.list = this.notes;
            });
        }
    }
</script>

<style>

</style>

NotesEditor 编辑框组件

<template>
    <div id="note-editor">
        <input type="text" v-model="textTitle" @change="eidtNoteTitle({title: textTitle})" />
        <textarea class="form-control" v-model="textVal" @change="editNote({text: textVal})"></textarea>
    </div>
</template>

<script>
    import { mapState, mapActions } from "Vuex";

    export default {
        data() {
            return {
                textVal: "",
                textTitle: ""
            }
        },
        computed: {
            ...mapState({
                activeNote: state => state.activeNote,
            })
        },
        methods: {
            ...mapActions({
                editNote: 'editNote',
                eidtNoteTitle: 'eidtNoteTitle'
            }),
        },
        watch: {
            activeNote: function() {
                this.$data.textVal = this.activeNote['text'];
                this.$data.textTitle = this.activeNote['title'];
            },
        }
    }
</script>

服务端功能,服务端文件build/dev-server.js

//引入文件操作模块
var fs = require('fs');
//post解码模块
var bodyParser = require('body-parser');
// 创建 application/x-www-form-urlencoded 编码解析
var urlencodedParser = bodyParser.urlencoded({ extended: false });
//使用中间件解码
app.use(bodyParser.json());
app.use(urlencodedParser);
//查询数据
app.all('/test.action', function(req, res) {
    fs.readFile('./static/data/data.json', 'utf-8', function(err, data) {
        res.json(JSON.parse(data));
    })
})
//保存数据
app.all('/save.action', function(req, res) {
    fs.writeFile('./static/data/data.json', JSON.stringify(req.body, null, '\t'), function(err) {
        if(err) {
            console.log(err);
            res.json({satus: 400});
        } else {
            res.json({satus: 200});
        }
    })
})
//保存功能,将json文件读取解析为txt文件,然后发送到前端下载
app.all('/down.action', function(req, res) {
    fs.readFile('./static/data/data.json', 'utf-8', function(err, data) {
        if(err) {
            res.json({status: 500});
        } else {
            let string = [];
            let jsonData = JSON.parse(data);
            for(let i of jsonData.notes) {
                string.push(`##${i.title}##\r\n${i.text}`)
            }
            fs.writeFile('./static/data/down.txt', string.join(`\r\n******************************************************\r\n`), function(err) {
                if(err) {
                    res.json({status: 500});
                } else {
                    res.download('./static/data/down.txt', 'notes.txt');
                }
            })
        }
    })
})

代码已上传至github