├── public ├── favicon.ico └── index.html ├── src ├── views │ ├── Admin │ │ ├── setting │ │ │ ├── index.vue │ │ │ ├── reset.vue │ │ │ └── profile.vue │ │ ├── analyse │ │ │ ├── chart.vue │ │ │ └── table.vue │ │ ├── dashboard.vue │ │ ├── manageMoment.vue │ │ └── index.vue │ ├── Login.vue │ ├── Home.vue │ └── Init.vue ├── scss │ └── _viewport.scss ├── utils │ └── index.js ├── assets │ ├── css │ │ └── loading.css │ ├── 2.svg │ ├── 0.svg │ └── 1.svg ├── api │ ├── upload.js │ ├── options.js │ ├── master.js │ └── moment.js ├── components │ ├── Home │ │ ├── overlay.vue │ │ ├── form │ │ │ ├── bubbleNotice.vue │ │ │ └── form-item.vue │ │ ├── skill-item.vue │ │ ├── responsive │ │ │ └── swiper-page.vue │ │ ├── showImg.vue │ │ ├── slide.vue │ │ ├── information.vue │ │ ├── swiper.vue │ │ ├── headerNav.vue │ │ ├── showContent.vue │ │ └── postDialog.vue │ └── Admin │ │ ├── card.vue │ │ ├── layout.vue │ │ ├── sidebar │ │ └── item.vue │ │ ├── Table │ │ └── table.vue │ │ └── item-card.vue ├── store │ ├── getters.js │ ├── store.js │ └── modules │ │ ├── post.js │ │ ├── options.js │ │ ├── moments.js │ │ └── user.js ├── plugins │ ├── axios.js │ └── bubble.vue ├── main.js ├── App.vue └── router.js ├── .prettierrc ├── postcss.config.js ├── vue.config.js ├── babel.config.js ├── .browserslistrc ├── .gitignore ├── .eslintrc.js ├── LICENSE ├── package.json ├── deploy.sh ├── README.md └── DEV.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Innei/Moment/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/views/Admin/setting/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/scss/_viewport.scss: -------------------------------------------------------------------------------- 1 | $small: 700px; 2 | $medium: 800px; 3 | $desktop-small: 1024px; 4 | $desktop-big: 1440px; -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "endOfLine": "lf", 5 | "tabWidth": 2 6 | } 7 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: { 4 | grid: true, 5 | 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: '/', 3 | outputDir: '/home/wwwroot/www', 4 | productionSourceMap: false 5 | } 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'], 3 | plugins: ['@babel/plugin-syntax-dynamic-import'] 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 提取某个对象中的指定属性 3 | * @param {Object} obj 被提取的对象 4 | * @param {Array} arr 要提取的属性 5 | * @return {Object} 6 | */ 7 | 8 | export const pick = (obj, arr) => 9 | arr.reduce((iter, val) => (val in obj && (iter[val] = obj[val]), iter), {}) 10 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | [production] 2 | chrome >= 70 3 | last 10 firefox version 4 | last 2 Safari version 5 | 6 | [modern] 7 | chrome > 75 8 | last 5 firefox version 9 | last 1 Safari version 10 | 11 | [development] 12 | chrome > 75 13 | last 5 firefox version 14 | Safari >= 12 15 | 16 | [ssr] 17 | node 12 18 | -------------------------------------------------------------------------------- /src/assets/css/loading.css: -------------------------------------------------------------------------------- 1 | #loader-wrapper { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | z-index: 99; 8 | display: flex; 9 | align-items: center; 10 | } 11 | #loader-wrapper .loader-container { 12 | text-align: center; 13 | width: 100%; 14 | } 15 | -------------------------------------------------------------------------------- /src/api/upload.js: -------------------------------------------------------------------------------- 1 | import http from '@/plugins/axios' 2 | 3 | const baseUrl = 'upload' 4 | 5 | const api = {} 6 | 7 | api.upload = file => 8 | http.post(baseUrl, file, { 9 | headers: { 10 | 'Content-Type': 'multipart/form-data;charset=UTF-8' 11 | } 12 | }) 13 | 14 | export default api 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/components/Home/overlay.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | user: state => state.user.user, 3 | token: state => state.user.token, 4 | moments: state => state.moments.moments, 5 | total: state => state.moments.total, 6 | isPost: state => state.post.isPost, 7 | isLogged: state => state.user.isLogged, 8 | sidebar: state => state.options.sidebar 9 | } 10 | export default getters -------------------------------------------------------------------------------- /src/api/options.js: -------------------------------------------------------------------------------- 1 | import http from '../plugins/axios.js' 2 | 3 | const baseUrl = 'options' 4 | 5 | export const getAccessData = ({ from, to, page, size } = {}) => 6 | http.get(`${baseUrl}/access`, { 7 | params: { from, to, size, page } 8 | }) 9 | 10 | export const getAnalytics = () => http.get(`${baseUrl}/analytics`) 11 | 12 | export default { getAccessData, getAnalytics } 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import user from './modules/user' 4 | import moments from './modules/moments' 5 | import post from './modules/post' 6 | import options from './modules/options' 7 | import getters from './getters' 8 | Vue.use(Vuex) 9 | 10 | const store = new Vuex.Store({ 11 | modules: { 12 | user, 13 | moments, 14 | post, 15 | options 16 | }, 17 | getters 18 | }) 19 | 20 | export default store 21 | -------------------------------------------------------------------------------- /src/store/modules/post.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | isPost: false 3 | } 4 | 5 | const actions = { 6 | togglePost({ commit }) { 7 | commit('toggleState') 8 | }, 9 | resetPost({ commit }) { 10 | commit('resetState') 11 | } 12 | } 13 | 14 | const mutations = { 15 | toggleState(state) { 16 | state.isPost = !state.isPost 17 | }, 18 | resetState(state) { 19 | state.isPost = false 20 | } 21 | } 22 | 23 | export default { 24 | state, 25 | actions, 26 | mutations 27 | } 28 | -------------------------------------------------------------------------------- /src/store/modules/options.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | sidebar: false 3 | } 4 | 5 | const actions = { 6 | toggleSidebar({ commit }) { 7 | commit('toggleSidebar') 8 | }, 9 | closeSidebar({ commit }) { 10 | commit('closeSidebar') 11 | } 12 | } 13 | 14 | const mutations = { 15 | toggleSidebar (state) { 16 | state.sidebar = !state.sidebar 17 | }, 18 | closeSidebar (state) { 19 | state.sidebar = false 20 | } 21 | } 22 | 23 | export default { 24 | state, 25 | actions, 26 | mutations 27 | } 28 | -------------------------------------------------------------------------------- /src/store/modules/moments.js: -------------------------------------------------------------------------------- 1 | import momentApi from '@/api/moment' 2 | 3 | const state = { 4 | moments: {}, 5 | total: 0 6 | } 7 | 8 | const actions = { 9 | loadRecentlyMoments({ commit }) { 10 | return new Promise(async (r, j) => { 11 | try { 12 | const data = (await momentApi.getRecentlyMoment({ size: 5 })).data 13 | if (data.ok === 1) { 14 | commit('storeMoments', data.data) 15 | commit('storeTotalNum', data.pageOptions.total) 16 | } 17 | r(data) 18 | } catch (e) { 19 | j(e) 20 | } 21 | }) 22 | } 23 | } 24 | 25 | const mutations = { 26 | storeMoments(state, moments) { 27 | state.moments = moments 28 | }, 29 | storeTotalNum (state, num) { 30 | state.total = num 31 | } 32 | } 33 | 34 | export default { 35 | state, 36 | actions, 37 | mutations 38 | } 39 | -------------------------------------------------------------------------------- /src/plugins/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import Vue from 'vue' 3 | import router from '../router' 4 | const http = axios.create({ 5 | baseURL: 6 | process.env.NODE_ENV === 'development' 7 | ? 'http://localhost:3000/api' 8 | : '/api', 9 | withCredentials: true 10 | }) 11 | 12 | http.interceptors.request.use( 13 | config => { 14 | if (localStorage.token) { 15 | config.headers.Authorization = localStorage.token 16 | } 17 | return config 18 | }, 19 | err => { 20 | console.log(err) 21 | } 22 | ) 23 | 24 | http.interceptors.response.use( 25 | res => { 26 | return res 27 | }, 28 | err => { 29 | if (err.response.data.msg) { 30 | Vue.prototype.$msg({ 31 | msg: err.response.data.msg, 32 | type: 'error' 33 | }) 34 | } 35 | 36 | if (err.response.status === 401) { 37 | router.push('/login') 38 | } 39 | return Promise.reject(err) 40 | } 41 | ) 42 | export default http 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Innei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store/store.js' 5 | import msg from './plugins/bubble.vue' 6 | import { PerfectScrollbar } from 'vue2-perfect-scrollbar' 7 | import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css' 8 | Vue.component('ps', PerfectScrollbar) 9 | Vue.use(msg) 10 | 11 | // https://www.jianshu.com/p/1191388be882 12 | import { library } from '@fortawesome/fontawesome-svg-core' 13 | import { fas } from '@fortawesome/free-solid-svg-icons' 14 | import { far } from '@fortawesome/free-regular-svg-icons' 15 | import { fab } from '@fortawesome/free-brands-svg-icons' 16 | 17 | import { 18 | FontAwesomeIcon, 19 | FontAwesomeLayers, 20 | FontAwesomeLayersText 21 | } from '@fortawesome/vue-fontawesome' 22 | 23 | library.add(fas, far, fab) 24 | 25 | Vue.component('font-awesome-icon', FontAwesomeIcon) 26 | Vue.component('font-awesome-layers', FontAwesomeLayers) 27 | Vue.component('font-awesome-layers-text', FontAwesomeLayersText) 28 | 29 | Vue.config.productionTip = false 30 | 31 | const app = new Vue({ 32 | router, 33 | store, 34 | render: h => h(App) 35 | }).$mount('#app') 36 | 37 | export default app -------------------------------------------------------------------------------- /src/api/master.js: -------------------------------------------------------------------------------- 1 | import http from '@/plugins/axios' 2 | 3 | const baseUrl = 'master' 4 | 5 | export const getUserInfo = () => { 6 | return http.get(`${baseUrl}`) 7 | } 8 | 9 | export const getUserIntroduce = () => http.get(`${baseUrl}/introduce`) 10 | 11 | export const checkPass = password => 12 | http.post(`${baseUrl}/check_pass`, { 13 | password 14 | }) 15 | 16 | export const completeInit = master => 17 | http.post(`${baseUrl}/init`, { ...master }) 18 | 19 | export const checkInit = () => http.get(`${baseUrl}/init`) 20 | 21 | export const login = master => http.post(`${baseUrl}/login`, { ...master }) 22 | 23 | export const checkLogged = () => http.get(`${baseUrl}/check_logged`) 24 | 25 | export const checkToken = () => http.post(`${baseUrl}/check_token`, { 26 | token: localStorage.token 27 | }) 28 | 29 | export const signOut = () => http.get(`${baseUrl}/sign_out`) 30 | 31 | export const resetPass = ({ password, oldPassword }) => 32 | http.post(`${baseUrl}/reset_password`, { 33 | password, 34 | oldPassword 35 | }) 36 | 37 | export const modifyIntro = data => http.put(`${baseUrl}/introduce`, data) 38 | 39 | export default { 40 | getUserInfo, 41 | getUserIntroduce, 42 | checkPass, 43 | login, 44 | checkLogged, 45 | signOut, 46 | completeInit, 47 | checkInit, 48 | checkToken 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moment", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "^1.2.28", 12 | "@fortawesome/free-brands-svg-icons": "^5.11.2", 13 | "@fortawesome/free-regular-svg-icons": "^5.11.2", 14 | "@fortawesome/free-solid-svg-icons": "^5.11.2", 15 | "@fortawesome/vue-fontawesome": "^0.1.7", 16 | "axios": "^0.19.2", 17 | "core-js": "^3.1.2", 18 | "echarts": "^4.7.0", 19 | "moment": "^2.24.0", 20 | "v-charts": "^1.19.0", 21 | "vue": "^2.6.11", 22 | "vue-avatar-cropper": "^1.0.6", 23 | "vue-awesome-swiper": "^3.1.3", 24 | "vue-router": "^3.1.6", 25 | "vue2-perfect-scrollbar": "^1.2.4", 26 | "vuex": "^3.1.1" 27 | }, 28 | "devDependencies": { 29 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 30 | "@vue/cli-plugin-babel": "^4.1.2", 31 | "@vue/cli-plugin-eslint": "^4.1.2", 32 | "@vue/cli-service": "^4.3.1", 33 | "babel-eslint": "^10.0.1", 34 | "eslint": "^5.16.0", 35 | "eslint-plugin-vue": "^5.0.0", 36 | "sass": "^1.26.3", 37 | "sass-loader": "^8.0.2", 38 | "vue-template-compiler": "^2.6.11" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Home/form/bubbleNotice.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/Home/skill-item.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 26 | 27 | -------------------------------------------------------------------------------- /src/api/moment.js: -------------------------------------------------------------------------------- 1 | import http from '../plugins/axios.js' 2 | 3 | const baseUrl = 'moments' 4 | 5 | export const getRecentlyMoment = function({ size, page }) { 6 | return http.get(baseUrl, { 7 | params: { 8 | size, 9 | page 10 | } 11 | }) 12 | } 13 | 14 | export const postNewMoment = momentData => 15 | http.post(baseUrl, { 16 | ...pickContent(momentData) 17 | }) 18 | 19 | export const deleteOneMoment = id => http.delete(`${baseUrl}/${id}`) 20 | 21 | export const modifyOneMoment = (id, data) => 22 | http.put(`${baseUrl}/${id}`, { 23 | ...pickContent(data) 24 | }) 25 | 26 | export default { 27 | getRecentlyMoment, 28 | postNewMoment, 29 | deleteOneMoment, 30 | modifyOneMoment 31 | } 32 | 33 | function pickContent(momentData) { 34 | let data 35 | const { title, body, mood, weather, source, src, comment } = momentData 36 | switch (momentData.type) { 37 | case 'moment': 38 | data = { 39 | title, 40 | body, 41 | mood, 42 | weather 43 | } 44 | break 45 | case 'hitokoto': 46 | data = { 47 | source, 48 | body 49 | } 50 | break 51 | case 'idea': 52 | data = { 53 | body 54 | } 55 | break 56 | case 'picture': 57 | data = { 58 | comment, 59 | src 60 | } 61 | 62 | break 63 | default: 64 | break 65 | } 66 | return Object.assign({}, { content: { ...data } }, { type: momentData.type }) 67 | } 68 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | 26 | 89 | -------------------------------------------------------------------------------- /src/components/Admin/card.vue: -------------------------------------------------------------------------------- 1 | 15 | 24 | -------------------------------------------------------------------------------- /src/components/Home/form/form-item.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | # For Ubuntu 2 | curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - 3 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - 4 | echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list 5 | sudo apt-get install gnupg -y 6 | wget -qO - https://www.mongodb.org/static/pgp/server-4.2.asc | sudo apt-key add - 7 | echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.2.list 8 | sudo apt update 9 | sudo apt-get install -y nodejs yarn redis mongodb-org git nginx screen 10 | 11 | cd ~ 12 | git clone https://github.com/Innei/moment.git 13 | git clone https://github.com/Innei/moment-server.git 14 | 15 | mkdir -p /home/wwwroot/www 16 | cd moment 17 | yarn 18 | yarn build --modern 19 | cd ../moment-server 20 | yarn 21 | cp .env.example .env 22 | yarn global add pm2 23 | sudo echo "server { 24 | 25 | server_name _; #在这里输入你绑定的域名 26 | root /home/wwwroot/www; 27 | 28 | location / { 29 | try_files $uri $uri/ /index.html; 30 | } 31 | 32 | # 反代 33 | location /api { 34 | proxy_set_header Host $host; 35 | proxy_set_header X-Real-IP $remote_addr; 36 | proxy_set_header REMOTE-HOST $remote_addr; 37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 38 | proxy_pass http://127.0.0.1:3000; 39 | } 40 | 41 | 42 | # gzip 43 | gzip on; 44 | gzip_vary on; 45 | gzip_proxied any; 46 | gzip_comp_level 6; 47 | gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; 48 | 49 | }" >> /etc/nginx/sites-enabled/moment.conf 50 | 51 | nginx -s reload 52 | 53 | pm2 start ecosystem.config.js --env production -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import masterApi from '@/api/master' 2 | const state = { 3 | user: { 4 | username: null, 5 | avatar: null, 6 | userId: null, 7 | nickname: null, 8 | githubUrl: null, 9 | userUrl: null 10 | }, 11 | token: localStorage.token || null, 12 | isLogged: false 13 | } 14 | 15 | const actions = { 16 | loadUser({ commit }) { 17 | return new Promise(async (r, j) => { 18 | try { 19 | const { data } = await masterApi.getUserInfo() 20 | commit('storeData', data) 21 | r(data) 22 | } catch (e) { 23 | j(e) 24 | } 25 | }) 26 | }, 27 | async checkLogged({ commit, state }) { 28 | // return new Promise(async (r, j) => { 29 | // try { 30 | // // const { data } = await masterApi.checkLogged() 31 | // // commit('storeLogged', data.ok ? true : false) 32 | // // r(data) 33 | 34 | // } catch (e) { 35 | // j(e) 36 | // } 37 | // }) 38 | if (state.token) { 39 | if ((await masterApi.checkToken()).data.ok === 1) { 40 | commit('storeLogged', true) 41 | return true 42 | } else { 43 | delete localStorage.token 44 | state.token = null 45 | return false 46 | } 47 | } else { 48 | commit('storeLogged', false) 49 | return false 50 | } 51 | }, 52 | setLogged({ commit }, status) { 53 | commit('storeLogged', status) 54 | }, 55 | setToken({ commit }, token) { 56 | commit('SET_TOKEN', token) 57 | } 58 | } 59 | 60 | const mutations = { 61 | storeData(state, user) { 62 | state.user = user 63 | }, 64 | SET_TOKEN(state, token) { 65 | state.token = token 66 | }, 67 | storeLogged(state, status) { 68 | state.isLogged = status 69 | } 70 | } 71 | 72 | export default { 73 | state, 74 | actions, 75 | mutations 76 | } 77 | -------------------------------------------------------------------------------- /src/components/Home/responsive/swiper-page.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 76 | 77 | -------------------------------------------------------------------------------- /src/components/Home/showImg.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 29 | 30 | -------------------------------------------------------------------------------- /src/views/Admin/analyse/chart.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 77 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 24 | Moment 25 | 26 | 27 | 33 |
34 |
35 |
36 | 45 | 46 | 55 | 56 | 57 |
数据正在加载中...
58 |
59 |
60 | 61 |
62 |
63 | 64 | 65 | -------------------------------------------------------------------------------- /src/plugins/bubble.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 一瞬 2 | 3 | 一瞬 (Moment),目的显而易见,就是希望它能够记录下生活中的美好瞬间。 4 | 5 | 后端正在测试和完善,咕咕咕咕 6 | 7 | 后端使用 Express + MongoDB 开发。 8 | 9 | 后端仓库地址: 10 | 11 | 即刻体验: 12 | 13 | 后台: 14 | 15 | 用户名: `Innei` 16 | 17 | 密码: `qaz123..qqaa` 18 | 19 | 20 | preview: 21 | 22 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191020211642.png) 23 | 24 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119152019.png) 25 | 26 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119152039.png) 27 | 28 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119152106.png) 29 | 30 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119152131.png) 31 | 32 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119153624.png) 33 | 34 | ## 快速构建 35 | 36 | 以 `yarn` 为例 37 | 38 | ```bash 39 | cd 40 | # 构建前端 41 | git clone https://github.com/Innei/moment.git 42 | cd moment 43 | yarn 44 | 45 | # 构建后端 46 | # 注意 您已正确安装了 redis 和 MongoDB 47 | cd 48 | git clone http://github.com/Innei/moment-server.git 49 | cd moment-server 50 | yarn 51 | 52 | ``` 53 | 54 | 经过以上的步骤,已经正确的搭建完了主要的环境。接下你可以配合 nginx 等把该项目托管到网站上。 55 | 56 | ## 托管生产环境 57 | 58 | 接下来,我会以 nginx 为例,简要的说明如何构建。 59 | 60 | ```bash 61 | cd ~/moment 62 | mkdir -p /home/wwwroot/www 63 | yarn build --modern 64 | cd ~/moment-server 65 | cp .env.example .env 66 | yarn prod 67 | ``` 68 | 69 | 通过以上的步骤已经把前端编译成静态页面,后端监听在本地 3000 端口上。使用 `yarn build --modern` 可以编译一个速度更快,体积更小的仅支持现代浏览器的版本,你也可以去掉 `--modern` 参数来编译一个兼容更多浏览器的版本。 70 | 71 | 接下来我们使用 `nginx` 来启用反向代理,将 `/api` 反向代理到本地的 3000 端口。 72 | 73 | ```bash 74 | sudo vim /etc/nginx/sites-enabled/moment.conf 75 | ``` 76 | 77 | 在该文件中写入 78 | 79 | ``` 80 | 81 | 82 | server { 83 | 84 | server_name example.com; #在这里输入你绑定的域名 85 | root /home/wwwroot/www; 86 | 87 | location / { 88 | try_files $uri $uri/ /index.html; 89 | } 90 | 91 | # 反代 92 | location /api { 93 | proxy_set_header Host $host; 94 | proxy_set_header X-Real-IP $remote_addr; 95 | proxy_set_header REMOTE-HOST $remote_addr; 96 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 97 | proxy_pass http://127.0.0.1:3000; 98 | } 99 | 100 | 101 | # gzip 102 | gzip on; 103 | gzip_vary on; 104 | gzip_proxied any; 105 | gzip_comp_level 6; 106 | gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; 107 | 108 | } 109 | ``` 110 | 111 | ``` 112 | nginx -s reload 113 | ``` 114 | 115 | 即刻访问你的绑定的网址,开始初始化。 116 | 117 | ## 浏览器兼容性 118 | 119 | 此项目使用 ES6 以及最新 CSS 规范编写,顾不支持旧版浏览器。 120 | 121 | ``` 122 | Chrome >= 76 123 | Firefox >= 70 124 | Safari >= 12 125 | etc. 126 | ``` 127 | 128 | ## 做出贡献 129 | 130 | 接下来,我们来构建开发环境。 131 | 132 | ```bash 133 | cd ~/moment 134 | yarn serve . 135 | cd ~/moment-server 136 | yarn start 137 | ``` 138 | 139 | ## 环境变量 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/views/Admin/analyse/table.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 109 | 110 | -------------------------------------------------------------------------------- /src/components/Home/slide.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 48 | 49 | -------------------------------------------------------------------------------- /src/views/Admin/dashboard.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 88 | 89 | -------------------------------------------------------------------------------- /src/views/Admin/setting/reset.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 67 | 68 | -------------------------------------------------------------------------------- /DEV.md: -------------------------------------------------------------------------------- 1 | # 一瞬 2 | 3 | 一瞬 (Moment), 是我接下来要做的一个项目,目的显而易见,就是希望它能够记录下生活中的美好瞬间。 4 | 5 | 这个项目是一个未成品,它仅仅是一个前端,所有的数据全部来自于 Mock.js 的接口模拟。但是它将作为一瞬的前端。 6 | 7 | 后端开始开发。开发中..... 咕咕咕咕 8 | 9 | 后端使用 Express + MongoDB 开发。存档地址: 10 | 11 | preview: (前端+后台) 12 | 13 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191020211642.png) 14 | 15 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119152019.png) 16 | 17 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119152039.png) 18 | 19 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119152106.png) 20 | 21 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119152131.png) 22 | 23 | ![](https://raw.githubusercontent.com/Innei/img-bed/master/20191119153624.png) 24 | 25 | Demo 地址: (Mock.js模拟,或页面已过期,请手动构建) 26 | 27 | [Moment](https://preview.shizuri.net/moment) 28 | 29 | ## 快速开始 30 | 31 | 即刻构建此项目,你只需要 32 | 33 | ```bash 34 | git clone https://github.com/Innei/moment.git 35 | cd moment 36 | yarn 37 | yarn serve 38 | ``` 39 | 40 | 打开访问 `http://localhost:8080` 41 | 42 | 要使用开发模式,请 clone 后端配合使用。 43 | 44 | [Moment-server](https://github.com/Innei/Moment-server) 45 | 46 | ## 接口格式 47 | 48 | 所有接口格式位于 `mock` 目录中,更为详细的 `json` 位于 `@api` 49 | 50 | 接口格式可能有更新!! 51 | 52 | 所有接口前缀统一为 `api`, 接口地址为 `api/` 加上 `baseUrl` 53 | 54 | 55 | 56 | **以下接口在后端中已全部实现,更多接口正在开发** 57 | 58 | 59 | 60 | ### 获取主人信息 61 | 62 | 基本信息: 63 | 64 | baseUrl: `/master` 65 | 66 | ```jsonc 67 | // get / 68 | { 69 | "username": "Innei", // 主人名 70 | "userId": "1", // id 71 | "avatar": "http://q1.qlogo.cn/g?b=qq&nk=1003521738&s=640", // 头像地址 72 | "nickname": "Moment", // 别名 73 | "githubUrl": "https://github.com/Innei" // 可选 GitHub 地址 74 | } 75 | ``` 76 | 77 | 首页介绍: 78 | 79 | ```jsonc 80 | // get /introduce 81 | { 82 | "ok": 1, // 状态 83 | "userId": 1, // uid 84 | "introduce": "我是一个练习时长两年半的个人练习生, xxx", // 介绍 85 | "skill": { // 技能树 (0 - 100) 86 | "Java": 70, 87 | "JavaScript": 50, 88 | "Vue": 90 89 | } 90 | } 91 | ``` 92 | 93 | ### 获取瞬间列表 94 | 95 | baseUrl: `/moments` 96 | 97 | 请求方式为 `GET` 98 | 99 | 携带参数可选 `size` `page` 100 | 101 | ```jsonc 102 | { 103 | "ok": 1, // 状态 104 | "pageOptions": { 105 | "size": 10, // 页大小 106 | "currentPage": 1, // 当前页 107 | "totalPage": 1, // 总页 108 | "hasNextPage": false, // 是否有下一页 109 | "hasPrevPage": false, // 是否有上一页 110 | }, 111 | "data": [ // 数据 112 | { 113 | "_id": 1, // MongoDB objectID 或者 其他 ID 114 | "createdTime": 1571299457065, // 13位时间截 创建时间 115 | "modifiedTime": 1571299777065, 116 | "type": "moment", // 类型 moment picture idea hitokoto 4选1, content 有所不同 117 | "content": { 118 | "title": "这是一条瞬间", 119 | "body": "今天的天气真好", 120 | "mood": "开心", 121 | "weather": "Sunny" 122 | } 123 | }, 124 | { 125 | "_id": 2, 126 | "createdTime": 1571399457065, 127 | "modifiedTime": 1571399777065, 128 | "type": "hitokoto", 129 | "content": { 130 | "source": "yiny", 131 | "body": "今天的天气真好" 132 | } 133 | }, 134 | { 135 | "_id": 3, 136 | "createdTime": 1571299457065, 137 | "modifiedTime": 1571299777065, 138 | "type": "idea", 139 | "content": { 140 | "body": "今天的天气真好" 141 | } 142 | }, 143 | { 144 | "_id": 4, 145 | "createdTime": 1571299457065, 146 | "modifiedTime": 1571299777065, 147 | "type": "picture", 148 | "content": { 149 | "src": "https://i.loli.net/2019/08/18/vGNB4oOepVA6lPQ.jpg" 150 | } 151 | }] 152 | } 153 | 154 | ``` 155 | 156 | 欢迎大佬们,能参与到一瞬的开发中去。 157 | -------------------------------------------------------------------------------- /src/components/Admin/layout.vue: -------------------------------------------------------------------------------- 1 | 25 | 47 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from './views/Home.vue' 4 | import master from '@/api/master' 5 | 6 | Vue.use(Router) 7 | 8 | const router = new Router({ 9 | routes: [ 10 | { 11 | name: 'home', 12 | path: '/', 13 | component: Home, 14 | meta: { title: 'Moment' } 15 | }, 16 | { 17 | name: 'init', 18 | path: '/init', 19 | component: () => 20 | import(/* webpackChunkName: "init" */ '@/views/Init.vue'), 21 | meta: { once: true, title: 'おかえりなさいご主人様 -- Moment' } 22 | }, 23 | { 24 | name: 'login', 25 | path: '/login', 26 | component: () => 27 | import(/* webpackChunkName: "login" */ '@/views/Login.vue'), 28 | meta: { title: '登陆 -- Moment' } 29 | }, 30 | { 31 | name: 'admin', 32 | path: '/master', 33 | meta: { private: true, title: ' -- Moment' }, 34 | component: () => 35 | import(/* webpackChunkName: "master" */ '@/views/Admin/index.vue'), 36 | redirect: '/master/dashboard', 37 | children: [ 38 | { 39 | path: 'dashboard', 40 | component: () => 41 | import( 42 | /* webpackChunkName: "dashboard" */ '@/views/Admin/dashboard.vue' 43 | ), 44 | meta: { title: '仪表盘' } 45 | }, 46 | { 47 | path: 'moments', 48 | component: () => 49 | import( 50 | /* webpackChunkName: "manageMoment" */ '@/views/Admin/manageMoment.vue' 51 | ), 52 | meta: { title: '管理瞬间' } 53 | }, 54 | { 55 | path: 'analytics', 56 | component: () => 57 | import( 58 | /* webpackChunkName: "analyse" */ '@/views/Admin/setting/index.vue' 59 | ), 60 | redirect: '/master/analytics/chart', 61 | children: [ 62 | { 63 | path: 'chart', 64 | meta: { title: '图表' }, 65 | component: () => 66 | import( 67 | /* webpackChunkName: "chart" */ '@/views/Admin/analyse/chart.vue' 68 | ) 69 | }, 70 | { 71 | path: 'table', 72 | meta: { title: '访客数据' }, 73 | component: () => 74 | import( 75 | /* webpackChunkName: "table" */ '@/views/Admin/analyse/table.vue' 76 | ) 77 | } 78 | ] 79 | }, 80 | { 81 | path: 'setting', 82 | component: () => 83 | import( 84 | /* webpackChunkName: "setting" */ '@/views/Admin/setting/index.vue' 85 | ), 86 | redirect: '/master/setting/profile', 87 | children: [ 88 | { 89 | path: 'profile', 90 | meta: { title: '主人信息' }, 91 | component: () => 92 | import( 93 | /* webpackChunkName: "profile" */ '@/views/Admin/setting/profile.vue' 94 | ) 95 | }, 96 | { 97 | path: 'reset', 98 | meta: { title: '修改密码' }, 99 | component: () => 100 | import( 101 | /* webpackChunkName: "reset" */ '@/views/Admin/setting/reset.vue' 102 | ) 103 | } 104 | ] 105 | } 106 | ] 107 | } 108 | ] 109 | }) 110 | 111 | router.beforeEach(async (to, from, next) => { 112 | // 用这种方式去获取父路由的 meta 信息 113 | if (to.matched.some(r => r.meta.private)) { 114 | const { data } = await master.checkLogged() 115 | if (data.ok === 1) { 116 | return next() 117 | } else { 118 | return next({ name: 'login' }) 119 | } 120 | } 121 | next() 122 | }) 123 | 124 | router.afterEach(to => { 125 | const titleArr = [] 126 | to.matched.map(i => titleArr.unshift(i.meta.title)) 127 | 128 | if (to.meta.title) { 129 | // document.title = to.meta.title 130 | document.title = titleArr.join('') 131 | } 132 | }) 133 | export default router 134 | -------------------------------------------------------------------------------- /src/components/Home/information.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 62 | 63 | -------------------------------------------------------------------------------- /src/components/Home/swiper.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 135 | 136 | 155 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 78 | 79 | 80 | 179 | -------------------------------------------------------------------------------- /src/views/Admin/manageMoment.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 165 | 166 | 171 | -------------------------------------------------------------------------------- /src/components/Admin/sidebar/item.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 121 | 122 | -------------------------------------------------------------------------------- /src/components/Home/headerNav.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 100 | 101 | -------------------------------------------------------------------------------- /src/components/Admin/Table/table.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 141 | 142 | -------------------------------------------------------------------------------- /src/components/Admin/item-card.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 123 | -------------------------------------------------------------------------------- /src/components/Home/showContent.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 127 | 128 | -------------------------------------------------------------------------------- /src/components/Home/postDialog.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 168 | 169 | 263 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 108 | 109 | 263 | 264 | 271 | -------------------------------------------------------------------------------- /src/assets/2.svg: -------------------------------------------------------------------------------- 1 | calendar -------------------------------------------------------------------------------- /src/views/Init.vue: -------------------------------------------------------------------------------- 1 | 175 | 176 | 177 | 178 | 318 | 319 | 576 | 577 | -------------------------------------------------------------------------------- /src/assets/1.svg: -------------------------------------------------------------------------------- 1 | target --------------------------------------------------------------------------------