├── static
└── .gitkeep
├── config
├── prod.env.js
├── test.env.js
├── dev.env.js
└── index.js
├── clipscreen
├── 0.jpg
├── 1.jpg
├── 10.jpg
├── 2.jpg
├── 3.jpg
├── 4.jpg
├── 5.jpg
├── 6.jpg
├── 7.jpg
├── 8.jpg
└── 9.jpg
├── src
├── assets
│ ├── logo.png
│ ├── menu.png
│ └── vue.png
├── App.vue
├── constants
│ └── mutationTypes.js
├── components
│ ├── loading.vue
│ ├── backTop.vue
│ ├── header.vue
│ ├── reply.vue
│ ├── userInfo.vue
│ └── menu.vue
├── main.js
├── style
│ ├── login.less
│ ├── login.css
│ ├── loading.less
│ ├── loading.css
│ ├── menu.less
│ ├── menu.css
│ ├── iconfont
│ │ └── iconfont.css
│ ├── publishTopic.less
│ ├── header.less
│ ├── publishTopic.css
│ ├── header.css
│ ├── index.less
│ ├── message.css
│ ├── index.css
│ ├── message.less
│ ├── topic.less
│ ├── user.less
│ ├── user.css
│ └── topic.css
├── router
│ └── index.js
├── api
│ ├── publicApi.js
│ └── index.js
├── utils
│ └── filter.js
├── page
│ ├── publishTopic.vue
│ ├── login.vue
│ ├── message.vue
│ ├── index.vue
│ ├── user.vue
│ └── topic.vue
└── vuex
│ └── store.js
├── test
├── unit
│ ├── .eslintrc
│ ├── specs
│ │ └── Hello.spec.js
│ ├── index.js
│ └── karma.conf.js
└── e2e
│ ├── specs
│ └── test.js
│ ├── custom-assertions
│ └── elementCount.js
│ ├── runner.js
│ └── nightwatch.conf.js
├── .gitignore
├── .editorconfig
├── .postcssrc.js
├── .babelrc
├── index.html
├── README.md
└── package.json
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/clipscreen/0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/0.jpg
--------------------------------------------------------------------------------
/clipscreen/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/1.jpg
--------------------------------------------------------------------------------
/clipscreen/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/10.jpg
--------------------------------------------------------------------------------
/clipscreen/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/2.jpg
--------------------------------------------------------------------------------
/clipscreen/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/3.jpg
--------------------------------------------------------------------------------
/clipscreen/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/4.jpg
--------------------------------------------------------------------------------
/clipscreen/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/5.jpg
--------------------------------------------------------------------------------
/clipscreen/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/6.jpg
--------------------------------------------------------------------------------
/clipscreen/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/7.jpg
--------------------------------------------------------------------------------
/clipscreen/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/8.jpg
--------------------------------------------------------------------------------
/clipscreen/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/clipscreen/9.jpg
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/src/assets/menu.png
--------------------------------------------------------------------------------
/src/assets/vue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandisen/cnode-vue/HEAD/src/assets/vue.png
--------------------------------------------------------------------------------
/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/config/test.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var devEnv = require('./dev.env')
3 |
4 | module.exports = merge(devEnv, {
5 | NODE_ENV: '"testing"'
6 | })
7 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var prodEnv = require('./prod.env')
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"'
6 | })
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | test/unit/coverage
8 | test/e2e/reports
9 | selenium-debug.log
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | // to edit target browsers: use "browserlist" field in package.json
6 | "autoprefixer": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", { "modules": false }],
4 | "stage-2"
5 | ],
6 | "plugins": ["transform-runtime"],
7 | "comments": false,
8 | "env": {
9 | "test": {
10 | "presets": ["env", "stage-2"],
11 | "plugins": [ "istanbul" ]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
16 |
--------------------------------------------------------------------------------
/src/constants/mutationTypes.js:
--------------------------------------------------------------------------------
1 | export const GET_TOPIC_LIST = 'GET_TOPIC_LIST';
2 | export const UPDATE_TOPIC_LIST = 'UPDATE_TOPIC_LIST';
3 | export const GET_TOPIC_INFO = 'GET_TOPIC_INFO';
4 | export const LOGIN = 'LOGIN';
5 | export const LOGIN_OUT = 'LOGIN_OUT';
6 | export const REPLY = 'REPLY';
7 | export const TOOGLE_LOAD = 'TOOGLE_LOAD';
8 | export const TOOGLE_LIST_LOAD = 'TOOGLE_LIST_LOAD';
9 |
10 |
--------------------------------------------------------------------------------
/test/unit/specs/Hello.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Hello from '@/components/Hello'
3 |
4 | describe('Hello.vue', () => {
5 | it('should render correct contents', () => {
6 | const Constructor = Vue.extend(Hello)
7 | const vm = new Constructor().$mount()
8 | expect(vm.$el.querySelector('.hello h1').textContent)
9 | .to.equal('Welcome to Your Vue.js App')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/src/components/loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 | import $ from 'webpack-zepto'
7 | import filter from './utils/filter.js';
8 | //注册全局组件
9 | Vue.prototype.$filter = filter;
10 | Vue.config.productionTip = false
11 |
12 | /* eslint-disable no-new */
13 | new Vue({
14 | el: '#app',
15 | router,
16 | template: '',
17 | components: { App }
18 | })
19 |
--------------------------------------------------------------------------------
/test/unit/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | Vue.config.productionTip = false
4 |
5 | // require all test files (files that ends with .spec.js)
6 | const testsContext = require.context('./specs', true, /\.spec$/)
7 | testsContext.keys().forEach(testsContext)
8 |
9 | // require all src files except main.js for coverage.
10 | // you can also change this to match only the subset of files that
11 | // you want coverage for.
12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
13 | srcContext.keys().forEach(srcContext)
14 |
--------------------------------------------------------------------------------
/src/style/login.less:
--------------------------------------------------------------------------------
1 | .login{
2 | padding-top:40px;
3 | .login_token{
4 | padding:0 15px;
5 | margin-top:50px;
6 | input{
7 | padding:12px 0;
8 | border-bottom:1px solid #80bd01;
9 | background-color: transparent;
10 | font-size: 1.4rem;
11 | color:#313131;
12 | width:100%;
13 | }
14 | .btn_login{
15 | width:100%;
16 | border-bottom: 2px solid #3aa373;
17 | background-color: #80bd01;
18 | font-size: 1.6rem;
19 | margin:15px 0;
20 | color:#fff;
21 | padding:10px;
22 | border-radius: 3px;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/style/login.css:
--------------------------------------------------------------------------------
1 | .login {
2 | padding-top: 40px;
3 | }
4 | .login .login_token {
5 | padding: 0 15px;
6 | margin-top: 50px;
7 | }
8 | .login .login_token input {
9 | padding: 12px 0;
10 | border-bottom: 1px solid #80bd01;
11 | background-color: transparent;
12 | font-size: 1.4rem;
13 | color: #313131;
14 | width: 100%;
15 | }
16 | .login .login_token .btn_login {
17 | width: 100%;
18 | border-bottom: 2px solid #3aa373;
19 | background-color: #80bd01;
20 | font-size: 1.6rem;
21 | margin: 15px 0;
22 | color: #fff;
23 | padding: 10px;
24 | border-radius: 3px;
25 | }
26 |
--------------------------------------------------------------------------------
/test/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // For authoring Nightwatch tests, see
2 | // http://nightwatchjs.org/guide#usage
3 |
4 | module.exports = {
5 | 'default e2e tests': function (browser) {
6 | // automatically uses dev Server port from /config.index.js
7 | // default: http://localhost:8080
8 | // see nightwatch.conf.js
9 | const devServer = browser.globals.devServerURL
10 |
11 | browser
12 | .url(devServer)
13 | .waitForElementVisible('#app', 5000)
14 | .assert.elementPresent('.hello')
15 | .assert.containsText('h1', 'Welcome to Your Vue.js App')
16 | .assert.elementCount('img', 1)
17 | .end()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/style/loading.less:
--------------------------------------------------------------------------------
1 | .loading{
2 | width:120px;
3 | margin:5px auto;
4 | text-align: center;
5 | .icon-loading{
6 | color:#ccc;
7 | display: inline-block;
8 | font-size: 5rem;
9 | -webkit-animation: loading 1s infinite linear;
10 | animation: loding 1s infinite linear;
11 | }
12 | @keyframes loading{
13 | 0% {
14 | -webkit-transform:rotate(0deg);
15 | transform: rotate(0deg);
16 | }
17 | 100%{
18 | -webkit-transform:rotate(360deg);
19 | transform: rotate(360deg);
20 | }
21 | }
22 | @-webkit-keyframes loading{
23 | 0% {
24 | -webkit-transform:rotate(0deg);
25 | transform: rotate(0deg);
26 | }
27 | 100%{
28 | -webkit-transform:rotate(360deg);
29 | transform: rotate(360deg);
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/style/loading.css:
--------------------------------------------------------------------------------
1 | .loading {
2 | width: 120px;
3 | margin: 5px auto;
4 | text-align: center;
5 | }
6 | .loading .icon-loading {
7 | color: #ccc;
8 | display: inline-block;
9 | font-size: 5rem;
10 | -webkit-animation: loading 1s infinite linear;
11 | animation: loding 1s infinite linear;
12 | }
13 | @keyframes loading {
14 | 0% {
15 | -webkit-transform: rotate(0deg);
16 | transform: rotate(0deg);
17 | }
18 | 100% {
19 | -webkit-transform: rotate(360deg);
20 | transform: rotate(360deg);
21 | }
22 | }
23 | @-webkit-keyframes loading {
24 | 0% {
25 | -webkit-transform: rotate(0deg);
26 | transform: rotate(0deg);
27 | }
28 | 100% {
29 | -webkit-transform: rotate(360deg);
30 | transform: rotate(360deg);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/style/menu.less:
--------------------------------------------------------------------------------
1 | nav.menu_bar{
2 | position:fixed;
3 | top:40px;
4 | bottom:0;
5 | left:-180px;
6 | width:180px;
7 | background-color: #fff;
8 | transition: all .3s ease;
9 | z-index:10;
10 | &.show{
11 | transform:translateX(180px);
12 | }
13 | .menu_list{
14 | borer-top:1px solid #80bd01;
15 | li.menu_item{
16 | a:hover{
17 | background-color: #80bd01;
18 | color:#fff;
19 | }
20 | a{
21 | display: block;
22 | padding:14px 24px;
23 | font-size: 1.4rem;
24 | text-decoration: none;
25 | i.iconfont{
26 | margin-right:30px;
27 | color:#333;
28 | }
29 | .message_count {
30 | position: absolute;
31 | color: red;
32 | left: 40px;
33 | top: 15px;
34 | }
35 | }
36 | &:nth-of-type(6){
37 | border-top:1px solid #80bd01;
38 | }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/test/e2e/custom-assertions/elementCount.js:
--------------------------------------------------------------------------------
1 | // A custom Nightwatch assertion.
2 | // the name of the method is the filename.
3 | // can be used in tests like this:
4 | //
5 | // browser.assert.elementCount(selector, count)
6 | //
7 | // for how to write custom assertions see
8 | // http://nightwatchjs.org/guide#writing-custom-assertions
9 | exports.assertion = function (selector, count) {
10 | this.message = 'Testing if element <' + selector + '> has count: ' + count
11 | this.expected = count
12 | this.pass = function (val) {
13 | return val === this.expected
14 | }
15 | this.value = function (res) {
16 | return res.value
17 | }
18 | this.command = function (cb) {
19 | var self = this
20 | return this.api.execute(function (selector) {
21 | return document.querySelectorAll(selector).length
22 | }, [selector], function (res) {
23 | cb.call(self, res)
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/style/menu.css:
--------------------------------------------------------------------------------
1 | nav.menu_bar {
2 | position: fixed;
3 | top: 40px;
4 | bottom: 0;
5 | left: -180px;
6 | width: 180px;
7 | background-color: #fff;
8 | transition: all .3s ease;
9 | z-index: 10;
10 | }
11 | nav.menu_bar.show {
12 | transform: translateX(180px);
13 | }
14 | nav.menu_bar .menu_list {
15 | borer-top: 1px solid #80bd01;
16 | }
17 | nav.menu_bar .menu_list li.menu_item a:hover {
18 | background-color: #80bd01;
19 | color: #fff;
20 | }
21 | nav.menu_bar .menu_list li.menu_item a {
22 | display: block;
23 | padding: 14px 24px;
24 | font-size: 1.4rem;
25 | text-decoration: none;
26 | }
27 | nav.menu_bar .menu_list li.menu_item a i.iconfont {
28 | margin-right: 30px;
29 | color: #333;
30 | }
31 | nav.menu_bar .menu_list li.menu_item a .message_count {
32 | position: absolute;
33 | color: red;
34 | left: 40px;
35 | top: 15px;
36 | }
37 | nav.menu_bar .menu_list li.menu_item:nth-of-type(6) {
38 | border-top: 1px solid #80bd01;
39 | }
40 |
--------------------------------------------------------------------------------
/src/style/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('//at.alicdn.com/t/font_flullthqh62vgqfr.eot?t=1478750580998'); /* IE9*/
4 | src: url('//at.alicdn.com/t/font_flullthqh62vgqfr.eot?t=1478750580998#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('//at.alicdn.com/t/font_flullthqh62vgqfr.woff?t=1478750580998') format('woff'), /* chrome, firefox */
6 | url('//at.alicdn.com/t/font_flullthqh62vgqfr.ttf?t=1478750580998') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('//at.alicdn.com/t/font_flullthqh62vgqfr.svg?t=1478750580998#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -webkit-text-stroke-width: 0.2px;
16 | -moz-osx-font-smoothing: grayscale;
17 | }
18 |
19 | .icon-dianzan:before { content: "\e659"; }
20 |
21 | .icon-menu:before { content: "\e628"; }
22 |
--------------------------------------------------------------------------------
/src/style/publishTopic.less:
--------------------------------------------------------------------------------
1 | .topic_create{
2 | padding-top:40px;
3 | .category{
4 | border-bottom: 1px solid #d4d4d4;
5 | padding:15px 20px;
6 | span{
7 | display: inline-block;
8 | }
9 | select{
10 | width:100px;
11 | border:1px solid rgb(169,169,169);
12 | height:30px;
13 | border-radius: 3px;
14 | font-size: 1.6rem;
15 | padding:3px;
16 | }
17 | }
18 | .title{
19 | padding:15px 20px;
20 | border-bottom:1px solid #d4d4d4;
21 | input{
22 | width:100%;
23 | height:30px;
24 | border-radius: 5px;
25 | box-sizing: border-box;
26 | box-shadow: 0 0 2px rgba(60,60,60,.5);
27 | font-size: 1.4rem;
28 | padding:5px;
29 | }
30 | }
31 | .content{
32 | padding:15px 20px;
33 | textarea{
34 | width:100%;
35 | border:1px solid rgb(60,60,60);
36 | border-radius: 3px;
37 | box-sizing: border-box;
38 | padding:5px;
39 | font-size: 16px;
40 | }
41 | }
42 | button{
43 | display: block;
44 | margin:0 20px;
45 | background-color: #80bd01;
46 | padding:8px 15px;
47 | border-radius: 5px;
48 | color:#fff;
49 | }
50 | }
--------------------------------------------------------------------------------
/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // http://karma-runner.github.io/0.13/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | var webpackConfig = require('../../build/webpack.test.conf')
7 |
8 | module.exports = function (config) {
9 | config.set({
10 | // to run in additional browsers:
11 | // 1. install corresponding karma launcher
12 | // http://karma-runner.github.io/0.13/config/browsers.html
13 | // 2. add it to the `browsers` array below.
14 | browsers: ['PhantomJS'],
15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
16 | reporters: ['spec', 'coverage'],
17 | files: ['./index.js'],
18 | preprocessors: {
19 | './index.js': ['webpack', 'sourcemap']
20 | },
21 | webpack: webpackConfig,
22 | webpackMiddleware: {
23 | noInfo: true
24 | },
25 | coverageReporter: {
26 | dir: './coverage',
27 | reporters: [
28 | { type: 'lcov', subdir: '.' },
29 | { type: 'text-summary' }
30 | ]
31 | }
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/src/style/header.less:
--------------------------------------------------------------------------------
1 | .page_cover{
2 | position: fixed;
3 | top:40px;
4 | left:0;
5 | right:0;
6 | bottom:0;
7 | background-color: rgba(0,0,0,.4);
8 | z-index:7;
9 | }
10 | .header_bar{
11 | position:fixed;
12 | top:0;
13 | left:0;
14 | width:100%;
15 | height:40px;
16 | line-height: 40px;
17 | box-shadow: 0 0 4px rgba(0,0,0,.25);
18 | z-index:6;
19 | background-color: rgba(255,255,255,1);
20 | transition:all .3s ease;
21 | display: flex;
22 | display: -webkit-flex;
23 | align-items: center;
24 | .menu_btn{
25 | width:44px;
26 | height:40px;
27 | background: url("../assets/menu.png") center center no-repeat;
28 | background-size: 24px;
29 | }
30 | .header_title{
31 | flex:1;
32 | text-align:center;
33 | display: flex;
34 | display: -webkit-flex;
35 | align-items: center;
36 | justify-content: center;
37 | font-size: 1.6rem;
38 | .vue_logo{
39 | width:40px;
40 | height:40px;
41 | background: url("../assets/vue.png") center center no-repeat;
42 | background-size: 24px;
43 | }
44 | }
45 | a.publish_btn{
46 | color:#42b983;
47 | height:40px;
48 | line-height: 40px;
49 | padding:0 15px;
50 | display: block;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | import Index from '@/page/index'
5 | import Topic from '@/page/topic'
6 | import PublishTopic from '@/page/publishTopic'
7 | import Login from '@/page/login'
8 | import User from '@/page/user'
9 | import Message from '@/page/message'
10 | Vue.use(Router)
11 |
12 | export default new Router({
13 | routes: [
14 | {
15 | path: '/',
16 | redirect: {name: 'index'}
17 | },
18 | {
19 | path: '/',
20 | name: 'index',
21 | component: Index
22 | },
23 | {
24 | path: '/topic/:id',
25 | name: 'topic',
26 | component: Topic
27 | },
28 | {
29 | path: '/create',
30 | name: 'create',
31 | component: PublishTopic,
32 | meta: { requiresAuth: true }
33 | },
34 | {
35 | path: '/login',
36 | name: 'login',
37 | component: Login
38 | },
39 | {
40 | path: '/user/:loginname',
41 | name: 'user',
42 | component: User
43 | },
44 | {
45 | path: '/message',
46 | name: 'message',
47 | component: Message,
48 | meta: { requiresAuth: true }
49 | }
50 | ]
51 | })
52 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vuedemo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/style/publishTopic.css:
--------------------------------------------------------------------------------
1 | .topic_create {
2 | padding-top: 40px;
3 | }
4 | .topic_create .category {
5 | border-bottom: 1px solid #d4d4d4;
6 | padding: 15px 20px;
7 | }
8 | .topic_create .category span {
9 | display: inline-block;
10 | }
11 | .topic_create .category select {
12 | width: 100px;
13 | border: 1px solid #a9a9a9;
14 | height: 30px;
15 | border-radius: 3px;
16 | font-size: 1.6rem;
17 | padding: 3px;
18 | }
19 | .topic_create .title {
20 | padding: 15px 20px;
21 | border-bottom: 1px solid #d4d4d4;
22 | }
23 | .topic_create .title input {
24 | width: 100%;
25 | height: 30px;
26 | border-radius: 5px;
27 | box-sizing: border-box;
28 | box-shadow: 0 0 2px rgba(60, 60, 60, 0.5);
29 | font-size: 1.4rem;
30 | padding: 5px;
31 | }
32 | .topic_create .content {
33 | padding: 15px 20px;
34 | }
35 | .topic_create .content textarea {
36 | width: 100%;
37 | border: 1px solid #3c3c3c;
38 | border-radius: 3px;
39 | box-sizing: border-box;
40 | padding: 5px;
41 | font-size: 16px;
42 | }
43 | .topic_create button {
44 | display: block;
45 | margin: 0 20px;
46 | background-color: #80bd01;
47 | padding: 8px 15px;
48 | border-radius: 5px;
49 | color: #fff;
50 | }
51 |
--------------------------------------------------------------------------------
/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | // 1. start the dev server using production config
2 | process.env.NODE_ENV = 'testing'
3 | var server = require('../../build/dev-server.js')
4 |
5 | server.ready.then(() => {
6 | // 2. run the nightwatch test suite against it
7 | // to run in additional browsers:
8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
9 | // 2. add it to the --env flag below
10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
11 | // For more information on Nightwatch's config file, see
12 | // http://nightwatchjs.org/guide#settings-file
13 | var opts = process.argv.slice(2)
14 | if (opts.indexOf('--config') === -1) {
15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
16 | }
17 | if (opts.indexOf('--env') === -1) {
18 | opts = opts.concat(['--env', 'chrome'])
19 | }
20 |
21 | var spawn = require('cross-spawn')
22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
23 |
24 | runner.on('exit', function (code) {
25 | server.close()
26 | process.exit(code)
27 | })
28 |
29 | runner.on('error', function (err) {
30 | server.close()
31 | throw err
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/test/e2e/nightwatch.conf.js:
--------------------------------------------------------------------------------
1 | require('babel-register')
2 | var config = require('../../config')
3 |
4 | // http://nightwatchjs.org/gettingstarted#settings-file
5 | module.exports = {
6 | src_folders: ['test/e2e/specs'],
7 | output_folder: 'test/e2e/reports',
8 | custom_assertions_path: ['test/e2e/custom-assertions'],
9 |
10 | selenium: {
11 | start_process: true,
12 | server_path: require('selenium-server').path,
13 | host: '127.0.0.1',
14 | port: 4444,
15 | cli_args: {
16 | 'webdriver.chrome.driver': require('chromedriver').path
17 | }
18 | },
19 |
20 | test_settings: {
21 | default: {
22 | selenium_port: 4444,
23 | selenium_host: 'localhost',
24 | silent: true,
25 | globals: {
26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
27 | }
28 | },
29 |
30 | chrome: {
31 | desiredCapabilities: {
32 | browserName: 'chrome',
33 | javascriptEnabled: true,
34 | acceptSslCerts: true
35 | }
36 | },
37 |
38 | firefox: {
39 | desiredCapabilities: {
40 | browserName: 'firefox',
41 | javascriptEnabled: true,
42 | acceptSslCerts: true
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vuedemo
2 |
3 | > vue2.0+vue-router2.0+webpack+npm+es6
4 |
5 | 
6 |
7 | 知识点:
8 | * vue2
9 | * vue-router2
10 | * 移动端开发
11 | * es6
12 | * less
13 |
14 | 项目还在持续更新中,以后计划:
15 | 1. 优化css
16 | 2. 优化router
17 | 3. 使用vuex状态管理器
18 | 4. 加入transition效果
19 |
20 | ## Build Setup
21 |
22 | ``` bash
23 | # install dependencies
24 | npm install
25 |
26 | # serve with hot reload at localhost:8080
27 | npm run dev
28 |
29 | # build for production with minification
30 | npm run build
31 |
32 | # build for production and view the bundle analyzer report
33 | npm run build --report
34 |
35 | # run unit tests
36 | npm run unit
37 |
38 | # run e2e tests
39 | npm run e2e
40 |
41 | # run all tests
42 | npm test
43 | ```
44 |
45 | 访问 localhost:8090
46 |
47 | 
48 | 
49 | 
50 | 
51 | 
52 | 
53 | 
54 | 
55 | 
56 | 
57 |
58 | # vue-cnode
59 |
60 | 学习vue.js前后端分离开发,熟悉并掌握vue2.0+vue-router2.0+webpack+npm+es6快速搭建项目框架,利用cnode社区提供的api实现话题列表,话题详情,发布话题,发表评论,点赞点踩等诸多功能。本人初涉vue,代码拙劣,大神请略过。如果对您有帮助,就给个star鼓励一下吧。
61 |
--------------------------------------------------------------------------------
/src/style/header.css:
--------------------------------------------------------------------------------
1 | .page_cover {
2 | position: fixed;
3 | top: 40px;
4 | left: 0;
5 | right: 0;
6 | bottom: 0;
7 | background-color: rgba(0, 0, 0, 0.4);
8 | z-index: 7;
9 | }
10 | .header_bar {
11 | position: fixed;
12 | top: 0;
13 | left: 0;
14 | width: 100%;
15 | height: 40px;
16 | line-height: 40px;
17 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.25);
18 | z-index: 6;
19 | background-color: #ffffff;
20 | transition: all .3s ease;
21 | display: flex;
22 | display: -webkit-flex;
23 | align-items: center;
24 | }
25 | .header_bar .menu_btn {
26 | width: 44px;
27 | height: 40px;
28 | background: url("../assets/menu.png") center center no-repeat;
29 | background-size: 24px;
30 | }
31 | .header_bar .header_title {
32 | flex: 1;
33 | text-align: center;
34 | display: flex;
35 | display: -webkit-flex;
36 | align-items: center;
37 | justify-content: center;
38 | font-size: 1.6rem;
39 | }
40 | .header_bar .header_title .vue_logo {
41 | width: 40px;
42 | height: 40px;
43 | background: url("../assets/vue.png") center center no-repeat;
44 | background-size: 24px;
45 | }
46 | .header_bar a.publish_btn {
47 | color: #42b983;
48 | height: 40px;
49 | line-height: 40px;
50 | padding: 0 15px;
51 | display: block;
52 | }
53 |
--------------------------------------------------------------------------------
/src/api/publicApi.js:
--------------------------------------------------------------------------------
1 | /****ES6语法尚不熟悉,所以暂不使用,留作以后优化*******/
2 |
3 | import fetchApi from './index';
4 |
5 | export const topicList = (data) => {
6 | return fetchApi({
7 | url: '/v1/topics',
8 | body: data
9 | })
10 | }
11 |
12 | export const topicInfo = (id) => {
13 | return fetchApi({
14 | url: '/v1/topic/' + id
15 | })
16 | }
17 |
18 | export const login = (data) => {
19 | return fetchApi({
20 | url: 'v1/accesstoken',
21 | method: 'post',
22 | body: data
23 | })
24 | }
25 |
26 | export const reply = (data, id) => {
27 | return fetchApi({
28 | url: 'v1/topic/${id}/replies',
29 | method: 'post',
30 | body: data
31 | })
32 | }
33 |
34 | export const messageCount = (data) => {
35 | return fetchApi({
36 | url: 'v1/message/count',
37 | body: data
38 | })
39 | }
40 |
41 | export const messages = (data) => {
42 | return fetchApi({
43 | url: 'v1/messages',
44 | body: data
45 | })
46 | }
47 |
48 | export const upReply = (data, id) => {
49 | return fetchApi({
50 | url: 'v1/reply/${id}/ups',
51 | method: 'post',
52 | body: data
53 | })
54 | }
55 |
56 | export const addTopic = (data) => {
57 | return fetchApi({
58 | url: 'v1/topics',
59 | method: 'post',
60 | body: data
61 | })
62 | }
63 |
64 | export const getUserInfo = (loginname) => {
65 | return fetchApi({
66 | url: 'v1/user/${loginname}'
67 | })
68 | }
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | var path = require('path')
3 |
4 | module.exports = {
5 | build: {
6 | env: require('./prod.env'),
7 | index: path.resolve(__dirname, '../dist/index.html'),
8 | assetsRoot: path.resolve(__dirname, '../dist'),
9 | assetsSubDirectory: 'static',
10 | assetsPublicPath: '/',
11 | productionSourceMap: true,
12 | // Gzip off by default as many popular static hosts such as
13 | // Surge or Netlify already gzip all static assets for you.
14 | // Before setting to `true`, make sure to:
15 | // npm install --save-dev compression-webpack-plugin
16 | productionGzip: false,
17 | productionGzipExtensions: ['js', 'css'],
18 | // Run the build command with an extra argument to
19 | // View the bundle analyzer report after build finishes:
20 | // `npm run build --report`
21 | // Set to `true` or `false` to always turn it on or off
22 | bundleAnalyzerReport: process.env.npm_config_report
23 | },
24 | dev: {
25 | env: require('./dev.env'),
26 | port: 8090,
27 | autoOpenBrowser: true,
28 | assetsSubDirectory: 'static',
29 | assetsPublicPath: '/',
30 | proxyTable: {},
31 | // CSS Sourcemaps off by default because relative paths are "buggy"
32 | // with this option, according to the CSS-Loader README
33 | // (https://github.com/webpack/css-loader#sourcemaps)
34 | // In our experience, they generally work as expected,
35 | // just be aware of this issue when enabling this option.
36 | cssSourceMap: false
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/backTop.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
--------------------------------------------------------------------------------
/src/utils/filter.js:
--------------------------------------------------------------------------------
1 | function formatTime(date) {
2 | var year = date.getFullYear()
3 | var month = date.getMonth() + 1
4 | var day = date.getDate()
5 |
6 | var hour = date.getHours()
7 | var minute = date.getMinutes()
8 | var second = date.getSeconds()
9 |
10 |
11 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
12 | }
13 |
14 | function formatNumber(n) {
15 | n = n.toString()
16 | return n[1] ? n : '0' + n
17 | }
18 |
19 | function getDateDiff(dateTimeStamp){
20 | var result="";
21 | var minute = 1000 * 60;
22 | var hour = minute * 60;
23 | var day = hour * 24;
24 | var halfmonth = day * 15;
25 | var month = day * 30;
26 | var year = day * 365;
27 | var now = new Date().getTime();
28 | var diffValue= now -dateTimeStamp;
29 | if(diffValue < 0){
30 | return '数据错误';
31 | }
32 | var yearC =diffValue / year;
33 | var monthC =diffValue / month;
34 | var weekC = diffValue / (7 * day);
35 | var dayC = diffValue / day;
36 | var hourC =diffValue / hour;
37 | var minC = diffValue /minute;
38 | if(yearC >= 1){
39 | result = parseInt(yearC) + '年以前';
40 | }else if(monthC >= 1){
41 | result = parseInt(monthC) + '个月前';
42 | }else if(weekC >= 1){
43 | result = parseInt(weekC) + '星期前';
44 | }else if(dayC >= 1){
45 | result = parseInt(dayC) + '天前';
46 | }else if(hourC >= 1){
47 | result = parseInt(hourC) + '小时前';
48 | }else if(minC >= 5){
49 | result = parseInt(minC) + '分钟前';
50 | }else{
51 | result = '刚刚发表';
52 | }
53 | return result;
54 | }
55 | export default {
56 | formatTime: formatTime,
57 | getDateDiff:getDateDiff
58 | }
--------------------------------------------------------------------------------
/src/components/header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
22 |
23 |
24 |
25 |
29 |
30 |
--------------------------------------------------------------------------------
/src/style/index.less:
--------------------------------------------------------------------------------
1 | body,html{
2 | font-family:"Microsoft YaHei",Source Sans Pro, Helvetica Neue,Roboto, Lato, sans-serif;
3 | font-size:10px;
4 | }
5 | body,html,ul,ol,li,dl,dd,dt,h1,h2,h3,h4,h5,h6,p,span,button,a,input,textarea,select{
6 | margin:0;
7 | padding:0;
8 | list-style-type:none;
9 | text-decoration: none;
10 | color:#000;
11 | border:none;
12 | -webkit-appearance:none;
13 | -webkit-tap-highlight-color: rgba(0,0,0,0);
14 | outline: none;
15 | }
16 | .logo{
17 | width:100%;
18 | background-color: #444;
19 | display: block;//消除img的缝隙
20 | }
21 | .tabbar{
22 | height:30px;
23 | line-height: 30px;
24 | font-size: 1.6rem;
25 | background-color: #444;
26 | .tabbar_item{
27 | width:20%;
28 | display: inline-block;
29 | text-align: center;
30 | color:#80bd01;
31 | }
32 | }
33 | .topic{
34 | font-size:1.4rem;
35 | margin-top:40px;
36 | .topic_list{
37 | padding:10px;
38 | border-bottom: 1px solid #80bd01;
39 | overflow: hidden;
40 | .topic_author{
41 | float:left;
42 | width:50px;
43 | height:50px;
44 | border-radius: 50%;
45 | display: inline-block;
46 | vertical-align: top;
47 | margin-right:10px;
48 | }
49 | .topic_msg{
50 | color:#778087;
51 | overflow: hidden;
52 | margin:5px 0;
53 | .topic_create_at{
54 | float: right;
55 | }
56 | }
57 | }
58 | .topic_title{
59 | .topic_tab_top,.topic_tab{
60 | font-size:1rem;
61 | float:left;
62 | border:1px solid red;
63 | color:red;
64 | display: inline-block;
65 | padding:2px 5px;
66 | margin-right:5px;
67 | border-radius: 3px;
68 | }
69 | .topic_tab{
70 | border:1px solid #80bd01;
71 | color:#80bd01;
72 | }
73 | .list_item{
74 | line-height: 25px;
75 | margin-top:10px;
76 | font-size: 1.5rem;
77 | font-weight: bold;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/page/publishTopic.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 选择版块:
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
--------------------------------------------------------------------------------
/src/components/reply.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
32 |
--------------------------------------------------------------------------------
/src/style/message.css:
--------------------------------------------------------------------------------
1 | .message {
2 | padding-top: 40px;
3 | }
4 | .message .tab {
5 | display: flex;
6 | display: -webkit-flex;
7 | border-bottom: 1px solid #d4d4d4;
8 | overflow: hidden;
9 | }
10 | .message .tab .tab_item {
11 | flex: 1;
12 | border-right: 1px solid #d4d4d4;
13 | text-align: center;
14 | font-weight: 700;
15 | font-size: 1.6rem;
16 | padding: 10px 0;
17 | }
18 | .message .tab .tab_item.active {
19 | color: #80bd01;
20 | border-bottom: 2px solid #80bd01;
21 | }
22 | .message .tab .tab_item:last-child {
23 | border-right: none;
24 | }
25 | .message .message_content {
26 | padding: 10px 0;
27 | border-bottom: 1px solid #d4d4d4;
28 | }
29 | .message .message_content .author_info {
30 | display: -webkit-flex;
31 | display: flex;
32 | padding: 0 10px;
33 | margin: 10px 0;
34 | }
35 | .message .message_content .author_info .head {
36 | width: 40px;
37 | height: 40px;
38 | margin-right: 15px;
39 | }
40 | .message .message_content .author_info .info {
41 | flex: 1;
42 | display: -webkit-flex;
43 | display: flex;
44 | }
45 | .message .message_content .author_info .info .left {
46 | flex: 1;
47 | color: #626262;
48 | font-size: 1.6rem;
49 | }
50 | .message .message_content .author_info .info .right {
51 | color: #80bd01;
52 | font-size: 1.2rem;
53 | }
54 | .message .message_content .reply_content {
55 | padding: 0 15px;
56 | }
57 | .message .message_content a {
58 | display: block;
59 | margin: 0 15px;
60 | }
61 | .message .message_content a .topic_title {
62 | padding: 5px;
63 | font-size: 1.8rem;
64 | color: #2c3e50;
65 | background-color: #f0f0f0;
66 | border-radius: 5px;
67 | }
68 | .message .no_data {
69 | height: -webkit-calc(100vh - 90px);
70 | height: calc(100vh - 90px);
71 | text-align: center;
72 | color: #d4d4d4;
73 | font-size: 1.8rem;
74 | display: flex;
75 | justify-content: center;
76 | flex-direction: column;
77 | }
78 | .message .no_data .icon-empty {
79 | display: block;
80 | font-size: 12.5rem;
81 | color: #d4d4d4;
82 | }
83 |
--------------------------------------------------------------------------------
/src/page/login.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
16 |
17 |
69 |
--------------------------------------------------------------------------------
/src/components/userInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 登录
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 退出
15 |
16 |
17 |
18 |
19 |
53 |
--------------------------------------------------------------------------------
/src/components/menu.vue:
--------------------------------------------------------------------------------
1 |
2 |
38 |
39 |
42 |
--------------------------------------------------------------------------------
/src/style/index.css:
--------------------------------------------------------------------------------
1 | body,
2 | html {
3 | font-family: "Microsoft YaHei", Source Sans Pro, Helvetica Neue, Roboto, Lato, sans-serif;
4 | font-size: 10px;
5 | }
6 | body,
7 | html,
8 | ul,
9 | ol,
10 | li,
11 | dl,
12 | dd,
13 | dt,
14 | h1,
15 | h2,
16 | h3,
17 | h4,
18 | h5,
19 | h6,
20 | p,
21 | span,
22 | button,
23 | a,
24 | input,
25 | textarea,
26 | select {
27 | margin: 0;
28 | padding: 0;
29 | list-style-type: none;
30 | text-decoration: none;
31 | color: #000;
32 | border: none;
33 | -webkit-appearance: none;
34 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
35 | outline: none;
36 | }
37 | .logo {
38 | width: 100%;
39 | background-color: #444;
40 | display: block;
41 | }
42 | .tabbar {
43 | height: 30px;
44 | line-height: 30px;
45 | font-size: 1.6rem;
46 | background-color: #444;
47 | }
48 | .tabbar .tabbar_item {
49 | width: 20%;
50 | display: inline-block;
51 | text-align: center;
52 | color: #80bd01;
53 | }
54 | .topic {
55 | font-size: 1.4rem;
56 | margin-top: 40px;
57 | }
58 | .topic .topic_list {
59 | padding: 10px;
60 | border-bottom: 1px solid #80bd01;
61 | overflow: hidden;
62 | }
63 | .topic .topic_list .topic_author {
64 | float: left;
65 | width: 50px;
66 | height: 50px;
67 | border-radius: 50%;
68 | display: inline-block;
69 | vertical-align: top;
70 | margin-right: 10px;
71 | }
72 | .topic .topic_list .topic_msg {
73 | color: #778087;
74 | overflow: hidden;
75 | margin: 5px 0;
76 | }
77 | .topic .topic_list .topic_msg .topic_create_at {
78 | float: right;
79 | }
80 | .topic .topic_title .topic_tab_top,
81 | .topic .topic_title .topic_tab {
82 | font-size: 1rem;
83 | float: left;
84 | border: 1px solid red;
85 | color: red;
86 | display: inline-block;
87 | padding: 2px 5px;
88 | margin-right: 5px;
89 | border-radius: 3px;
90 | }
91 | .topic .topic_title .topic_tab {
92 | border: 1px solid #80bd01;
93 | color: #80bd01;
94 | }
95 | .topic .topic_title .list_item {
96 | line-height: 25px;
97 | margin-top: 10px;
98 | font-size: 1.5rem;
99 | font-weight: bold;
100 | }
101 |
--------------------------------------------------------------------------------
/src/api/index.js:
--------------------------------------------------------------------------------
1 | /****ES6语法尚不熟悉,所以暂不使用,留作以后优化*******/
2 |
3 | require('es6-promise').polyfill();
4 | require('isomorphic-fetch');
5 |
6 | const baseOpts = {
7 | method: 'get',
8 | headers: {
9 | 'Accept': 'application/json',
10 | 'Content-Type': 'application/json'
11 | },
12 | // credentials: 'include',
13 | mode: 'cors'
14 | }
15 |
16 | const isObj = (obj) => {
17 | return obj && Object.prototype.toString.call(obj) === '[object Object]';
18 | }
19 |
20 | const isFormData = (obj) => {
21 | return obj && Object.prototype.toString.call(obj) === '[object FormData]';
22 | }
23 |
24 |
25 |
26 | const fetchApi = (cfg) => {
27 | let opts = Object.assign({}, baseOpts, cfg);
28 | const url = opts.url;
29 | delete opts.url;
30 |
31 |
32 |
33 | let fetchUrl = '/api'
34 | if (/^\//.test(url)) {
35 | fetchUrl += url
36 | } else {
37 | fetchUrl += '/' + url
38 | }
39 |
40 | if (opts.method.toLowerCase() !== 'get' && isObj(opts.body) && opts.headers['Content-Type'].indexOf('application/json') > -1) {
41 | opts.body = JSON.stringify(opts.body)
42 | }
43 |
44 | if (opts.method.toLowerCase() === 'get' && isObj(opts.body)) {
45 | fetchUrl += '?';
46 | for (let key in opts.body) {
47 | let value = opts.body[key];
48 |
49 | if (value instanceof Array) {
50 | value = JSON.stringify(value);
51 | }
52 | fetchUrl += key + '=' + value + '&';
53 | }
54 | fetchUrl = fetchUrl.slice(0, -1);
55 | delete opts.body;
56 | }
57 |
58 | // fetchUrl = 'https://cnodejs.org' + fetchUrl;
59 |
60 |
61 | return new Promise((resolve, reject) => {
62 | fetch(fetchUrl, opts).then((res) => {
63 | const isSuccess = res.ok || res.status >= 200 && res.status < 300;
64 | if (isSuccess) {
65 | const data = res.headers.get('content-type') && res.headers.get('content-type').indexOf('json') >= 0 ? res.json() : res.text();
66 | resolve(data);
67 | } else {
68 | throw res
69 | }
70 | }).catch((err) => {
71 | reject();
72 | })
73 | })
74 | }
75 |
76 | export default fetchApi
--------------------------------------------------------------------------------
/src/style/message.less:
--------------------------------------------------------------------------------
1 | .message {
2 | padding-top: 40px;
3 | .tab{
4 | display: flex;
5 | display: -webkit-flex;
6 | border-bottom: 1px solid #d4d4d4;
7 | overflow: hidden;
8 | .tab_item{
9 | flex:1;
10 | border-right:1px solid #d4d4d4;
11 | text-align: center;
12 | font-weight:700;
13 | font-size: 1.6rem;
14 | padding:10px 0;
15 | &.active{
16 | color:#80bd01;
17 | border-bottom: 2px solid #80bd01;
18 | }
19 | &:last-child{
20 | border-right:none;
21 | }
22 | }
23 | }
24 | .message_content {
25 | padding: 10px 0;
26 | border-bottom: 1px solid #d4d4d4;
27 | .author_info {
28 | display: -webkit-flex;
29 | display: flex;
30 | padding: 0 10px;
31 | margin: 10px 0;
32 | .head {
33 | width: 40px;
34 | height: 40px;
35 | margin-right: 15px;
36 | }
37 | .info {
38 | flex: 1;
39 | display: -webkit-flex;
40 | display: flex;
41 | .left {
42 | flex: 1;
43 | color: #626262;
44 | font-size: 1.6rem;
45 | }
46 | .right {
47 | color: #80bd01;
48 | font-size: 1.2rem;
49 | }
50 | }
51 | }
52 | .reply_content {
53 | padding: 0 15px;
54 | }
55 | a {
56 | display: block;
57 | margin: 0 15px;
58 | .topic_title {
59 | padding: 5px;
60 | font-size: 1.8rem;
61 | color: #2c3e50;
62 | background-color: #f0f0f0;
63 | border-radius: 5px;
64 | }
65 | }
66 | }
67 | .no_data {
68 | height: -webkit-calc(~'100vh - 90px');
69 | height: calc(~'100vh - 90px');
70 | text-align: center;
71 | color:#d4d4d4;
72 | font-size: 1.8rem;
73 | display: flex;
74 | justify-content: center;
75 | flex-direction: column;
76 | .icon-empty{
77 | display: block;
78 | font-size: 12.5rem;
79 | color:#d4d4d4;
80 | }
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/style/topic.less:
--------------------------------------------------------------------------------
1 | .topic_detail{
2 | padding-top:40px;
3 | .topic_title{
4 | padding:5px;
5 | margin:15px 10px;
6 | font-size: 1.8rem;
7 | color:#2c3e50;
8 | line-height: 1.5;
9 | background-color: #f0f0f0;
10 | border-radius: 5px;
11 | border-bottom: 2px solid #ddd;
12 | }
13 | .author_info{
14 | display: -webkit-flex;
15 | display: flex;
16 | align-items: center;
17 | font-size: 1.2rem;
18 | color:#34495e;
19 | padding:0 10px;
20 | }
21 | img.avatar{
22 | width:40px;
23 | height:40px;
24 | border-radius: 50%;
25 | margin-right:15px;
26 | }
27 | .center{
28 | flex:1;
29 | .author,.info{
30 | display: block;
31 | padding: 5px 0;
32 | }
33 | }
34 | .right{
35 | .tag{
36 | color:#fff;
37 | padding:5px 6px;
38 | font-size: 1.2rem;
39 | border-radius: 4px;
40 | text-align: center;
41 | display: block;
42 | background-color: #80bd01;
43 | }
44 | .name{
45 | padding:5px 0;
46 | display:block;
47 | }
48 | }
49 | .topic_content{
50 | padding:10px;
51 | margin-top:15px;
52 | border-bottom: 1px solid #d4d4d4;
53 | }
54 | .topic_reply{
55 | .topic_total{
56 | padding:10px;
57 | border-bottom: 1px solid #d4d4d4;
58 | font-size:1.6rem;
59 | strong{
60 | color:#80bd01;
61 | }
62 | }
63 | .reply_list{
64 | li{
65 | padding:10px;
66 | border-bottom:1px solid #d4d4d4;
67 | .user{
68 | display: -webkit-flex;
69 | display: flex;
70 | .head{
71 | width:40px;
72 | height:40px;
73 | border-radius: 50%;
74 | margin-right:10px;
75 | display: inline-block;
76 | }
77 | .info{
78 | font-size: 1.3rem;
79 | flex:1;
80 | display: -webkit-flex;
81 | display: flex;
82 | align-items: center;
83 | .left{
84 | flex:1;
85 | display: -webkit-flex;
86 | display: flex;
87 | line-height: 20px;
88 | .name{
89 | margin-right:10px;
90 | }
91 | }
92 | .right{
93 | display: flex;
94 | display: -webkit-flex;
95 | line-height:20px;
96 | .uped{
97 | color:#80bd01;
98 | }
99 | }
100 | .iconfont{
101 | font-size: 18px;
102 | color:#333;
103 | }
104 | }
105 | }
106 | .reply_content{
107 | margin-top:10px;
108 | img{
109 | max-width: 100%;
110 | border:0;
111 | vertical-align: middle;
112 | }
113 | }
114 | }
115 | &:last-child{
116 | border-bottom: none;
117 | }
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/src/style/user.less:
--------------------------------------------------------------------------------
1 | .user_page{
2 | padding-top:40px;
3 | .info{
4 | background-color: #e7e7e7;
5 | padding:15px 0;
6 | img{
7 | border-radius: 50%;
8 | width:100px;
9 | height:100px;
10 | border:3px solid #80bd01;
11 | display: block;
12 | margin:0 auto;
13 | }
14 | span.name{
15 | font-size: 1.4rem;
16 | text-align: center;
17 | margin-top:5px;
18 | display: block;
19 | }
20 | .bottom{
21 | margin-top: 10px;
22 | display: -webkit-flex;
23 | display: flex;
24 | .time{
25 | text-align: center;
26 | flex:1;
27 | }
28 | .score{
29 | text-align: center;
30 | color:#80bd01;
31 | flex:1;
32 | }
33 | }
34 | }
35 | .user_active{
36 | .tab{
37 | display: flex;
38 | display: -webkit-flex;
39 | border-bottom: 1px solid #d4d4d4;
40 | overflow: hidden;
41 | .tab_item{
42 | flex:1;
43 | border-right:1px solid #d4d4d4;
44 | text-align: center;
45 | font-weight:700;
46 | font-size: 1.6rem;
47 | padding:10px 0;
48 | &.active{
49 | color:#80bd01;
50 | border-bottom: 2px solid #80bd01;
51 | }
52 | &:last-child{
53 | border-right:none;
54 | }
55 | }
56 | }
57 | .active_content{
58 | display: -webkit-flex;
59 | display: flex;
60 | padding:10px;
61 | border-bottom: 1px solid #f0f0f0;
62 | .head{
63 | img{
64 | width:40px;
65 | height:40px;
66 | border-radius: 50%;
67 | margin-right:15px;
68 | border:2px solid #80bd01;
69 | }
70 | }
71 | .right{
72 | flex:1;
73 | overflow: hidden;
74 | .topic_title{
75 | overflow: hidden;
76 | text-overflow: ellipsis;
77 | white-space: nowrap;
78 | display: block;
79 | font-weight:700;
80 | font-size: 1.6rem;
81 | color:#333;
82 | }
83 | .topic_bottom{
84 | display: -webkit-flex;
85 | display: flex;
86 | margin-top:5px;
87 | .name{
88 | color:#626262;
89 | flex:1;
90 | }
91 | .time{
92 | color:#80bd01;
93 | font-size: 1.2rem;
94 | }
95 | }
96 | }
97 | }
98 | .no_data {
99 | height: -webkit-calc(~'100vh - 90px');
100 | height: calc(~'100vh - 90px');
101 | text-align: center;
102 | color:#d4d4d4;
103 | font-size: 1.8rem;
104 | display: flex;
105 | justify-content: center;
106 | flex-direction: column;
107 | .icon-empty{
108 | display: block;
109 | font-size: 12.5rem;
110 | color:#d4d4d4;
111 | }
112 | }
113 | }
114 | }
--------------------------------------------------------------------------------
/src/style/user.css:
--------------------------------------------------------------------------------
1 | .user_page {
2 | padding-top: 40px;
3 | }
4 | .user_page .info {
5 | background-color: #e7e7e7;
6 | padding: 15px 0;
7 | }
8 | .user_page .info img {
9 | border-radius: 50%;
10 | width: 100px;
11 | height: 100px;
12 | border: 3px solid #80bd01;
13 | display: block;
14 | margin: 0 auto;
15 | }
16 | .user_page .info span.name {
17 | font-size: 1.4rem;
18 | text-align: center;
19 | margin-top: 5px;
20 | display: block;
21 | }
22 | .user_page .info .bottom {
23 | margin-top: 10px;
24 | display: -webkit-flex;
25 | display: flex;
26 | }
27 | .user_page .info .bottom .time {
28 | text-align: center;
29 | flex: 1;
30 | }
31 | .user_page .info .bottom .score {
32 | text-align: center;
33 | color: #80bd01;
34 | flex: 1;
35 | }
36 | .user_page .user_active .tab {
37 | display: flex;
38 | display: -webkit-flex;
39 | border-bottom: 1px solid #d4d4d4;
40 | overflow: hidden;
41 | }
42 | .user_page .user_active .tab .tab_item {
43 | flex: 1;
44 | border-right: 1px solid #d4d4d4;
45 | text-align: center;
46 | font-weight: 700;
47 | font-size: 1.6rem;
48 | padding: 10px 0;
49 | }
50 | .user_page .user_active .tab .tab_item.active {
51 | color: #80bd01;
52 | border-bottom: 2px solid #80bd01;
53 | }
54 | .user_page .user_active .tab .tab_item:last-child {
55 | border-right: none;
56 | }
57 | .user_page .user_active .active_content {
58 | display: -webkit-flex;
59 | display: flex;
60 | padding: 10px;
61 | border-bottom: 1px solid #f0f0f0;
62 | }
63 | .user_page .user_active .active_content .head img {
64 | width: 40px;
65 | height: 40px;
66 | border-radius: 50%;
67 | margin-right: 15px;
68 | border: 2px solid #80bd01;
69 | }
70 | .user_page .user_active .active_content .right {
71 | flex: 1;
72 | overflow: hidden;
73 | }
74 | .user_page .user_active .active_content .right .topic_title {
75 | overflow: hidden;
76 | text-overflow: ellipsis;
77 | white-space: nowrap;
78 | display: block;
79 | font-weight: 700;
80 | font-size: 1.6rem;
81 | color: #333;
82 | }
83 | .user_page .user_active .active_content .right .topic_bottom {
84 | display: -webkit-flex;
85 | display: flex;
86 | margin-top: 5px;
87 | }
88 | .user_page .user_active .active_content .right .topic_bottom .name {
89 | color: #626262;
90 | flex: 1;
91 | }
92 | .user_page .user_active .active_content .right .topic_bottom .time {
93 | color: #80bd01;
94 | font-size: 1.2rem;
95 | }
96 | .user_page .user_active .no_data {
97 | height: -webkit-calc(100vh - 90px);
98 | height: calc(100vh - 90px);
99 | text-align: center;
100 | color: #d4d4d4;
101 | font-size: 1.8rem;
102 | display: flex;
103 | justify-content: center;
104 | flex-direction: column;
105 | }
106 | .user_page .user_active .no_data .icon-empty {
107 | display: block;
108 | font-size: 12.5rem;
109 | color: #d4d4d4;
110 | }
111 |
--------------------------------------------------------------------------------
/src/vuex/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | Vue.use(Vuex);
4 |
5 | import {GET_TOPIC_LIST, UPDATE_TOPIC_LIST, GET_TOPIC_INFO, LOGIN, LOGIN_OUT, REPLY, TOOGLE_LOAD, TOOGLE_LIST_LOAD} from '../constants/mutationTypes';
6 |
7 | const store = new Vuex.Store({
8 | state:{
9 | topics:[],
10 | topicInfo:{},
11 | userInfo:{},
12 | showLoad:false, //页面加载时的效果
13 | showListLoad:false //滑动到底部加载时的效果
14 | },
15 | mutations: {
16 | [GET_TOPIC_LIST](state,data) {
17 | state.topics = data;
18 | },
19 | [UPDATE_TOPIC_LIST](state,data) {
20 | state.topics = [...state.topics, ...data];
21 | },
22 | [GET_TOPIC_INFO](state,data) {
23 | state.topicInfo = data;
24 | },
25 | [LOGIN](state,data) {
26 | state.userInfo = data;
27 | },
28 | [LOGIN_OUT](state) {
29 | state.userInfo = {};
30 | localStorage.removeItem('userInfo');
31 | },
32 | [TOOGLE_LOAD](state,data) {
33 | if(data){
34 | state.showLoad = data;
35 | } else {
36 | state.showLoad = !state.showLoad;
37 | }
38 | },
39 | [TOOGLE_LIST_LOAD](state,data) {
40 | if(data){
41 | state.showListLoad = data;
42 | } else {
43 | state.showListLoad = !state.showListLoad;
44 | }
45 | }
46 | },
47 | actions: {
48 | [GET_TOPIC_LIST]({commit},data) {
49 | commit(TOOGLE_LOAD,true);
50 | return topicList(data).then((res) => {
51 | if(res.success){
52 | commit(TOOGLE_LOAD,false);
53 | commit(GET_TOPIC_LIST, res.data);
54 | }
55 | })
56 | },
57 |
58 | [UPDATE_TOPIC_LIST]({commit},data) {
59 | commit(TOOGLE_LIST_LOAD,true);
60 | return topicList(data).then((res) => {
61 | if(res.success){
62 | commit(TOOGLE_LIST_LOAD, false);
63 | commit(UPDATE_TOPIC_LIST, res.data);
64 | }
65 | })
66 | },
67 |
68 | [GET_TOPIC_INFO]({commit}, data) {
69 | commit(TOOGLE_LOAD, true);
70 | topicInfo(data).then((res) => {
71 | if (res.success) {
72 | commit(TOOGLE_LOAD, false);
73 | commit(GET_TOPIC_INFO, res.data)
74 | }
75 | })
76 | },
77 |
78 | [LOGIN]({commit}, data) {
79 | return login(data).then((res) => {
80 | if (res.success) {
81 | const user = {
82 | loginname: res.loginname,
83 | id: res.id,
84 | avatar_url: res.avatar_url,
85 | accesstoken: data.accesstoken
86 | }
87 | localStorage.setItem('userInfo', JSON.stringify(user));
88 | commit(LOGIN, user);
89 | }
90 | })
91 | },
92 |
93 | [REPLY]({commit, dispatch}, data) {
94 | const topicId = data.topicId;
95 | delete data.topicId;
96 | reply(data, topicId).then((res) => {
97 | if (res.success) {
98 | dispatch(GET_TOPIC_INFO, topicId);
99 | }
100 | })
101 | }
102 | }
103 | })
--------------------------------------------------------------------------------
/src/style/topic.css:
--------------------------------------------------------------------------------
1 | .topic_detail {
2 | padding-top: 40px;
3 | }
4 | .topic_detail .topic_title {
5 | padding: 5px;
6 | margin: 15px 10px;
7 | font-size: 1.8rem;
8 | color: #2c3e50;
9 | line-height: 1.5;
10 | background-color: #f0f0f0;
11 | border-radius: 5px;
12 | border-bottom: 2px solid #ddd;
13 | }
14 | .topic_detail .author_info {
15 | display: -webkit-flex;
16 | display: flex;
17 | align-items: center;
18 | font-size: 1.2rem;
19 | color: #34495e;
20 | padding: 0 10px;
21 | }
22 | .topic_detail img.avatar {
23 | width: 40px;
24 | height: 40px;
25 | border-radius: 50%;
26 | margin-right: 15px;
27 | }
28 | .topic_detail .center {
29 | flex: 1;
30 | }
31 | .topic_detail .center .author,
32 | .topic_detail .center .info {
33 | display: block;
34 | padding: 5px 0;
35 | }
36 | .topic_detail .right .tag {
37 | color: #fff;
38 | padding: 5px 6px;
39 | font-size: 1.2rem;
40 | border-radius: 4px;
41 | text-align: center;
42 | display: block;
43 | background-color: #80bd01;
44 | }
45 | .topic_detail .right .name {
46 | padding: 5px 0;
47 | display: block;
48 | }
49 | .topic_detail .topic_content {
50 | padding: 10px;
51 | margin-top: 15px;
52 | border-bottom: 1px solid #d4d4d4;
53 | }
54 | .topic_detail .topic_reply .topic_total {
55 | padding: 10px;
56 | border-bottom: 1px solid #d4d4d4;
57 | font-size: 1.6rem;
58 | }
59 | .topic_detail .topic_reply .topic_total strong {
60 | color: #80bd01;
61 | }
62 | .topic_detail .topic_reply .reply_list li {
63 | padding: 10px;
64 | border-bottom: 1px solid #d4d4d4;
65 | }
66 | .topic_detail .topic_reply .reply_list li .user {
67 | display: -webkit-flex;
68 | display: flex;
69 | }
70 | .topic_detail .topic_reply .reply_list li .user .head {
71 | width: 40px;
72 | height: 40px;
73 | border-radius: 50%;
74 | margin-right: 10px;
75 | display: inline-block;
76 | }
77 | .topic_detail .topic_reply .reply_list li .user .info {
78 | font-size: 1.3rem;
79 | flex: 1;
80 | display: -webkit-flex;
81 | display: flex;
82 | align-items: center;
83 | }
84 | .topic_detail .topic_reply .reply_list li .user .info .left {
85 | flex: 1;
86 | display: -webkit-flex;
87 | display: flex;
88 | line-height: 20px;
89 | }
90 | .topic_detail .topic_reply .reply_list li .user .info .left .name {
91 | margin-right: 10px;
92 | }
93 | .topic_detail .topic_reply .reply_list li .user .info .right {
94 | display: flex;
95 | display: -webkit-flex;
96 | line-height: 20px;
97 | }
98 | .topic_detail .topic_reply .reply_list li .user .info .right .uped {
99 | color: #80bd01;
100 | }
101 | .topic_detail .topic_reply .reply_list li .user .info .iconfont {
102 | font-size: 18px;
103 | color: #333;
104 | }
105 | .topic_detail .topic_reply .reply_list li .reply_content {
106 | margin-top: 10px;
107 | }
108 | .topic_detail .topic_reply .reply_list li .reply_content img {
109 | max-width: 100%;
110 | border: 0;
111 | vertical-align: middle;
112 | }
113 | .topic_detail .topic_reply .reply_list:last-child {
114 | border-bottom: none;
115 | }
116 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuedemo",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "sandisen ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "start": "node build/dev-server.js",
10 | "build": "node build/build.js",
11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
12 | "e2e": "node test/e2e/runner.js",
13 | "test": "npm run unit && npm run e2e"
14 | },
15 | "dependencies": {
16 | "es6-promise": "^4.1.0",
17 | "github-markdown-css": "^2.6.0",
18 | "isomorphic-fetch": "^2.2.1",
19 | "vue": "^2.2.2",
20 | "vue-mui": "^2.0.0",
21 | "vue-resource": "^1.3.1",
22 | "vue-router": "^2.3.0",
23 | "vuex": "^2.2.1",
24 | "webpack-zepto": "^0.0.1"
25 | },
26 | "devDependencies": {
27 | "autoprefixer": "^6.7.2",
28 | "babel-core": "^6.22.1",
29 | "babel-loader": "^6.2.10",
30 | "babel-plugin-istanbul": "^3.1.2",
31 | "babel-plugin-transform-runtime": "^6.22.0",
32 | "babel-preset-env": "^1.2.1",
33 | "babel-preset-stage-2": "^6.22.0",
34 | "babel-register": "^6.22.0",
35 | "chai": "^3.5.0",
36 | "chalk": "^1.1.3",
37 | "chromedriver": "^2.27.2",
38 | "connect-history-api-fallback": "^1.3.0",
39 | "copy-webpack-plugin": "^4.0.1",
40 | "cross-env": "^3.1.4",
41 | "cross-spawn": "^5.0.1",
42 | "css-loader": "^0.26.1",
43 | "eventsource-polyfill": "^0.9.6",
44 | "express": "^4.14.1",
45 | "extract-text-webpack-plugin": "^2.0.0",
46 | "file-loader": "^0.10.0",
47 | "friendly-errors-webpack-plugin": "^1.1.3",
48 | "html-webpack-plugin": "^2.28.0",
49 | "http-proxy-middleware": "^0.17.3",
50 | "inject-loader": "^2.0.1",
51 | "karma": "^1.4.1",
52 | "karma-coverage": "^1.1.1",
53 | "karma-mocha": "^1.3.0",
54 | "karma-phantomjs-launcher": "^1.0.2",
55 | "karma-phantomjs-shim": "^1.4.0",
56 | "karma-sinon-chai": "^1.2.4",
57 | "karma-sourcemap-loader": "^0.3.7",
58 | "karma-spec-reporter": "0.0.26",
59 | "karma-webpack": "^2.0.2",
60 | "less": "^2.7.2",
61 | "less-loader": "^4.0.3",
62 | "lolex": "^1.5.2",
63 | "mocha": "^3.2.0",
64 | "nightwatch": "^0.9.12",
65 | "node-less": "^1.0.0",
66 | "opn": "^4.0.2",
67 | "optimize-css-assets-webpack-plugin": "^1.3.0",
68 | "ora": "^1.1.0",
69 | "phantomjs-prebuilt": "^2.1.14",
70 | "rimraf": "^2.6.0",
71 | "selenium-server": "^3.0.1",
72 | "semver": "^5.3.0",
73 | "shelljs": "^0.7.6",
74 | "sinon": "^1.17.7",
75 | "sinon-chai": "^2.8.0",
76 | "style-loader": "^0.16.1",
77 | "superagent": "^3.5.2",
78 | "url-loader": "^0.5.7",
79 | "vue-loader": "^11.1.4",
80 | "vue-style-loader": "^2.0.0",
81 | "vue-template-compiler": "^2.2.4",
82 | "webpack": "^2.2.1",
83 | "webpack-bundle-analyzer": "^2.2.1",
84 | "webpack-dev-middleware": "^1.10.0",
85 | "webpack-hot-middleware": "^2.16.1",
86 | "webpack-merge": "^2.6.1"
87 | },
88 | "engines": {
89 | "node": ">= 4.0.0",
90 | "npm": ">= 3.0.0"
91 | },
92 | "browserslist": [
93 | "> 1%",
94 | "last 2 versions",
95 | "not ie <= 8"
96 | ]
97 | }
98 |
--------------------------------------------------------------------------------
/src/page/message.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 | {{item.author.loginname}}
15 | 在回复中@了您
16 | 回复了您的话题
17 |
18 |
19 | {{item.reply.create_at}}
20 |
21 |
22 |
23 |
24 |
25 | 话题:{{item.topic.title}}
26 |
27 |
28 |
29 |
30 | 暂无数据!
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
--------------------------------------------------------------------------------
/src/page/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{item.author.loginname}}
10 | {{item.reply_count}}/{{item.visit_count}}
11 |
12 |
13 | {{item.create_at}}
14 | {{item.last_reply_at}}
15 |
16 |
17 |
置顶
18 |
精华
19 |
分享
20 |
问答
21 |
招聘
22 |
{{item.title}}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
--------------------------------------------------------------------------------
/src/page/user.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
![头像]()
7 |
8 |
9 | {{userInfo.create_at}}
10 | 积分:{{userInfo.score}}
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 | {{item.title}}
24 |
25 |
26 | {{item.last_reply_at}}
27 |
28 |
29 |
30 |
31 | 暂无数据!
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
43 |
44 |
--------------------------------------------------------------------------------
/src/page/topic.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 置顶
16 | 精华
17 | 分享
18 | 问答
19 | 招聘
20 | {{topic.visit_count}}次浏览
21 |
22 |
23 |
24 |
25 |
26 | {{topic.reply_count}}条回复
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
发布于:{{item.create_at}}
38 |
39 |
40 |
42 | {{item.ups.length}}
43 |
44 |
45 |
46 |
47 |
48 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
66 |
--------------------------------------------------------------------------------