Vue.js

  • pqdong 
  • 用vuejs的webpack模板生成的项目中,在使用import导入包时@的作用:
import Hello from '@/components/Hello'

// 采用@替代路径(具体设置在webpack.config.js文件中),可以减少路径错误,而且便捷
webpack.base.conf.js

resolve: {
    modules: ['node_modules'],
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      '@oj': resolve('src/pages/oj'),
      '@admin': resolve('src/pages/admin'),
      '~': resolve('src/components')
    }
  },
  • var vm = new vue({}) 与 export default {} 的区别: export default 用来导出组件,相当于暴露了一个接口给外界,让你其他文件可以通过 import 来引入使用该组件,export可以暴露若干个。vm = new Vue({}) ->创建一个Vue的实例 就是相当于创建一个根组件(组件的实例)

demo

(可以看看青岛大学开源的oj web前端,尝试写两三个页面)

feedback.vue

<template>  //定义组件的模板
  <div class="feedback view">
    <Panel title="Feedback List">  //Panel是自定义的基础组件
      <el-table
        v-loading="loading"
        element-loading-text="loading"
        ref="table"
        :data="feedbackList"   //绑定model
        style="width: 100%">
        <el-table-column
          width="100"
          prop="id"    //model属性
          label="ID">   //显示
        </el-table-column>
        <el-table-column
          prop="display_id"
          label="题目_id">
        </el-table-column>
        <el-table-column
          prop="type"
          label="类别">
        </el-table-column>
        <el-table-column
          prop="description"
          label="描述">
        </el-table-column>
          <el-table-column
            prop="create_time"
            label="CreateTime">
            <template slot-scope="scope">  //插槽
              {{ scope.row.create_time | localtime }}
            </template>
          </el-table-column>
          <el-table-column
            prop="created_by"
            label="提出作者">
          </el-table-column>
          <el-table-column
            prop="notation"
            label="管理员批注">
          </el-table-column>

        <el-table-column
          width="100"
          prop="visible"
          label="Visible">
          <template slot-scope="scope">
            <el-switch v-model="scope.row.visible"
                       active-text=""
                       inactive-text=""
                       @change="handleVisibleSwitch(scope.row)">
            </el-switch>
          </template>
        </el-table-column>

          <el-table-column
            fixed="right"
            label="Option"
            width="200">
            <div slot-scope="scope">
              <icon-btn name="Edit" icon="edit" @click.native="openFeedbackDialog(scope.row.id)"></icon-btn>
              <icon-btn name="Delete" icon="trash" @click.native="deleteFeedback(scope.row.id)"></icon-btn>
            </div>
          </el-table-column>
        </el-table>
      <div class="panel-options">
        <el-pagination
          class="page"
          layout="prev, pager, next"
          @current-change="currentChange"   //触发事件
          :page-size="pageSize"
          :total="total">
        </el-pagination>
      </div>
    </Panel>
    <!--对话框-->
    <el-dialog :title="feedbackDialogTitle" :visible.sync="showEditFeedbackDialog"
               @open="onOpenEditDialog" :close-on-click-modal="false">
      <el-form label-position="top">
        <el-form-item :label="$t('管理员批注')" required>
          <el-input
            v-model="feedback.notation"
            :placeholder="$t('管理员批注')" class="title-input">
          </el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
          <cancel @click.native="showEditFeedbackDialog = false"></cancel>
          <save type="primary" @click.native="submitFeedback"></save>
        </span>
    </el-dialog>
  </div>
</template>

<script>
  import Simditor from '../../components/Simditor.vue'
  import api from '../../api.js'

  export default {
    name: 'Feedback',
    components: {
      Simditor
    },
    data () {
      return {
        showEditFeedbackDialog: false,
        feedbackList: [],
        // 一页显示的数
        pageSize: 15,
        // 总反馈数
        total: 0,
        // 当前id
        mode: 'edit',
        currentFeedbackId: null,
        // 公告 (new | edit) model
        feedback: {
          notation: '',
          visible: true
        },
        // 对话框标题
        feedbackDialogTitle: 'Edit Feedback',
        // 是否显示loading
        loading: true,
        // 当前页码
        currentPage: 0
      }
    },
    mounted () {
      this.init()
    },
    methods: {
      init () {
        this.getFeedbackList(1)
      },
      // 切换页码回调
      currentChange (page) {
        this.currentPage = page
        this.getFeedbackList(page)
      },
      getFeedbackList (page) {
        this.loading = true
        api.getFeedbackList((page - 1) * this.pageSize, this.pageSize).then(res => {
          this.loading = false
          this.total = res.data.data.total
          this.feedbackList = res.data.data.results
        }, res => {
          this.loading = false
        })
      },
      // 打开编辑对话框的回调
      onOpenEditDialog () {
        // todo 优化
        // 暂时解决 文本编辑器显示异常bug
        setTimeout(() => {
          if (document.createEvent) {
            let event = document.createEvent('HTMLEvents')
            event.initEvent('resize', true, true)
            window.dispatchEvent(event)
          } else if (document.createEventObject) {
            window.fireEvent('onresize')
          }
        }, 0)
      },
      // 提交编辑
      // 默认传入MouseEvent
      submitFeedback (data = undefined) {
        let funcName = 'updateFeedback'
        if (!data.id) {
          data = {
            id: this.currentFeedbackId,
            notation: this.feedback.notation
          }
        }
        api[funcName](data).then(res => {
          this.showEditFeedbackDialog = false
          this.init()
        }).catch()
      },
      updateFeedback (row) {
        let data = Object.assign({}, row)
        let funcName = 'updateFeedback'
        api[funcName](data).then(res => {
          this.InlineEditDialogVisible = true
          this.getFeedbackList(this.currentPage)
        }).catch(() => {
          this.InlineEditDialogVisible = true
        })
      },
      // 删除公告
      deleteFeedback (feedbackId) {
        this.$confirm('Are you sure you want to delete this announcement?', 'Warning', {
          confirmButtonText: 'Delete',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          this.loading = true
          let funcName = 'deleteFeedback'
          api[funcName](feedbackId).then(res => {
            this.loading = true
            this.init()
          })
        }).catch(() => {
          this.loading = true
        })
      },
      openFeedbackDialog (id) {
        this.showEditFeedbackDialog = true
        if (id !== null) {
          this.currentFeedbackId = id
          this.FeedbackDialogTitle = 'Edit Announcement'
          this.feedbackList.find(item => {
            if (item.id === this.currentFeedbackId) {
              this.feedback.notation = item.notation
              this.feedback.visible = item.visible
              this.mode = 'edit'
            }
          })
        }
      },
      handleVisibleSwitch (row) {
        this.mode = 'edit'
        this.submitFeedback({
          id: row.id,
          notation: row.notation,
          visible: row.visible
        })
      }
    },
    watch: {   //监视器
      $route () {
        this.init()
      }
    }
  }
</script>

Home.vue

<template>
  <div class="container">
    <div>
      <SideMenu></SideMenu>     //在这里使用了Feedback组件
    </div>
    <div id="header">
      <i class="el-icon-fa-font katex-editor" @click="katexVisible=true" ></i>
      <screen-full :width="14" :height="14" class="screen-full"></screen-full>
      <el-dropdown @command="handleCommand">
        <span>{{user.username}}<i class="el-icon-caret-bottom el-icon--right"></i></span>
        <!--<el-dropdown-menu slot="dropdown">-->
          <!--<el-dropdown-item command="logout">Logout</el-dropdown-item>-->
        <!--</el-dropdown-menu>-->
      </el-dropdown>
    </div>
    <div class="content-app">
      <transition name="fadeInUp" mode="out-in">
        <router-view></router-view>
      </transition>
      <div class="footer">
        Build Version: {{ version }}
      </div>
    </div>

    <el-dialog title="Latex Editor" :visible.sync="katexVisible">
      <KatexEditor></KatexEditor>
    </el-dialog>
  </div>
</template>

<script>
  import { types } from '@/store'
  import { mapGetters } from 'vuex'
  import SideMenu from '../components/SideMenu.vue'
  import ScreenFull from '@admin/components/ScreenFull.vue'
  import KatexEditor from '@admin/components/KatexEditor.vue'
  import api from '../api'

  export default {
    name: 'app',
    data () {
      return {
        version: process.env.VERSION,
        katexVisible: false
      }
    },
    components: {
      SideMenu,
      KatexEditor,
      ScreenFull
    },
    beforeRouteEnter (to, from, next) {
      api.getProfile().then(res => {
        if (!res.data.data) {
          // not login
          next({name: 'login'})
        } else {
          next(vm => {
            vm.$store.commit(types.CHANGE_PROFILE, {profile: res.data.data})
          })
        }
      })
    },
    methods: {
      handleCommand (command) {
        if (command === 'logout') {
          api.logout().then(() => {
            this.$router.push({name: 'login'})
          })
        }
      }
    },
    computed: {   //计算属性,属性侦听器
      ...mapGetters(['user'])   //vuex getter获取属性,这种方法要比method方法要好
    }
  }
</script>

<style lang="less">
  a {
    background-color: transparent;
  }

  a:active, a:hover {
    outline-width: 0
  }

  img {
    border-style: none
  }


</style>

api.js

import Vue from 'vue'
import router from './router'
import axios from 'axios'
import utils from '@/utils/utils'

Vue.prototype.$http = axios
axios.defaults.baseURL = '/api'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
axios.defaults.xsrfCookieName = 'csrftoken'

export default {
  // 登录
  login (username, password) {
    return ajax('login', 'post', {
      data: {
        username,
        password
      }
    })
  },
  // logout () {
  //   return ajax('logout', 'get')
  // },
  getProfile () {
    return ajax('profile', 'get')
  },
  // 获取公告列表
  getAnnouncementList (offset, limit) {
    return ajax('admin/announcement', 'get', {
      params: {
        paging: true,
        offset,
        limit
      }
    })
  },
  // 删除公告
  deleteAnnouncement (id) {
    return ajax('admin/announcement', 'delete', {
      params: {
        id
      }
    })
  },
  // 修改公告
  updateAnnouncement (data) {
    return ajax('admin/announcement', 'put', {
      data
    })
  },
  // 添加公告
  createAnnouncement (data) {
    return ajax('admin/announcement', 'post', {
      data
    })
  },

  // 编辑标签
  updateProblemTags (data) {
    return ajax('admin/tags', 'put', {
      data
    })
  },
  // 获取用户列表
  getUserList (offset, limit, keyword) {
    let params = {paging: true, offset, limit}
    if (keyword) {
      params.keyword = keyword
    }
    return ajax('admin/user', 'get', {
      params: params
    })
  },
  
  getJudgeServer () {
    return ajax('admin/judge_server', 'get')
  },
  deleteJudgeServer (hostname) {
    return ajax('admin/judge_server', 'delete', {
      params: {
        hostname: hostname
      }
    })
  },
  updateJudgeServer (data) {
    return ajax('admin/judge_server', 'put', {
      data
    })
  },
  getInvalidTestCaseList () {
    return ajax('admin/prune_test_case', 'get')
  },
  pruneTestCase (id) {
    return ajax('admin/prune_test_case', 'delete', {
      params: {
        id
      }
    })
  },
  createContest (data) {
    return ajax('admin/contest', 'post', {
      data
    })
  },
  getContest (id) {
    return ajax('admin/contest', 'get', {
      params: {
        id
      }
    })
  },
  editContest (data) {
    return ajax('admin/contest', 'put', {
      data
    })
  },
  getContestList (offset, limit, keyword) {
    let params = {paging: true, offset, limit}
    if (keyword) {
      params.keyword = keyword
    }
    return ajax('admin/contest', 'get', {
      params: params
    })
  },
  getContestAnnouncementList (contestID) {
    return ajax('admin/contest/announcement', 'get', {
      params: {
        contest_id: contestID
      }
    })
  },
  createContestAnnouncement (data) {
    return ajax('admin/contest/announcement', 'post', {
      data
    })
  },
  deleteContestAnnouncement (id) {
    return ajax('admin/contest/announcement', 'delete', {
      params: {
        id
      }
    })
  },
  getContestProblemList (params) {
    params = utils.filterEmptyValue(params)
    return ajax('admin/contest/problem', 'get', {
      params
    })
  },
  getContestProblem (id) {
    return ajax('admin/contest/problem', 'get', {
      params: {
        id
      }
    })
  },
  createContestProblem (data) {
    return ajax('admin/contest/problem', 'post', {
      data
    })
  },
  editContestProblem (data) {
    return ajax('admin/contest/problem', 'put', {
      data
    })
  },
  deleteContestProblem (id) {
    return ajax('admin/contest/problem', 'delete', {
      params: {
        id
      }
    })
  },
  makeContestProblemPublic (data) {
    return ajax('admin/contest_problem/make_public', 'post', {
      data
    })
  },
  addProblemFromPublic (data) {
    return ajax('admin/contest/add_problem_from_public', 'post', {
      data
    })
  },
  getReleaseNotes () {
    return ajax('admin/versions', 'get')
  },
  getFeedbackList (params) {
    params = utils.filterEmptyValue(params)
    return ajax('admin/feedback', 'get', {
      params
    })
  },
  updateFeedback (data) {
    return ajax('admin/feedback', 'put', {
      data
    })
  },
  deleteFeedback (id) {
    return ajax('admin/feedback', 'delete', {
      params: {
        id
      }
    })
  }
}

/**
 * @param url
 * @param method get|post|put|delete...
 * @param params like queryString. if a url is index?a=1&b=2, params = {a: '1', b: '2'}
 * @param data post data, use for method put|post
 * @returns {Promise}
 */
function ajax (url, method, options) {
  if (options !== undefined) {
    var {params = {}, data = {}} = options
  } else {
    params = data = {}
  }
  return new Promise((resolve, reject) => {
    axios({
      url,
      method,
      params,
      data
    }).then(res => {
      // API正常返回(status=20x), 是否错误通过有无error判断
      if (res.data.error !== null) {
        Vue.prototype.$error(res.data.data)
        reject(res)
        // // 若后端返回为登录,则为session失效,应退出当前登录用户
        if (res.data.data.startsWith('Please login')) {
          router.push({name: 'login'})
        }
      } else {
        resolve(res)
        if (method !== 'get') {
          Vue.prototype.$success('Succeeded')
        }
      }
    }, res => {
      // API请求异常,一般为Server error 或 network error
      reject(res)
      Vue.prototype.$error(res.data.data)
    })
  })
}

router.js

import Vue from 'vue'
import VueRouter from 'vue-router'
// 引入 view 组件
import { Announcement, Conf, Contest, ContestList, Home, JudgeServer, Login,
  Problem, ProblemList, User, PruneTestCase, Dashboard, ProblemImportOrExport, Tags, Feedback } from './views'
Vue.use(VueRouter)

export default new VueRouter({
  mode: 'history',
  base: '/admin/',
  scrollBehavior: () => ({y: 0}),
  routes: [
    {
      path: '/login',
      name: 'login',
      component: Login
    },
    {
      path: '/',
      component: Home,
      children: [
        {
          path: '',
          name: 'dashboard',
          component: Dashboard
        },
        {
          path: '/feedback',
          name: 'feedback',
          component: Feedback
        },
        {
          path: '/announcement',
          name: 'announcement',
          component: Announcement
        },
        {
          path: '/user',
          name: 'user',
          component: User
        },
        {
          path: '/contest/:contestId/problem/:problemId/edit',
          name: 'edit-contest-problem',
          component: Problem
        }
      ]
    },
    {
      path: '*', redirect: '/login'
    }
  ]
})

发表评论

电子邮件地址不会被公开。 必填项已用*标注