# 开发规范

# 新增 view

@/views(opens new window) 文件下 创建对应的文件夹,一般性一个路由对应一个文件, 该模块下的功能就建议在本文件夹下创建一个新文件夹,各个功能模块维护自己的utils或components组件。

# 新增 api

@/api(opens new window) 文件夹下创建本模块对应的 api 服务。

# 新增组件

在全局的 @/components(opens new window) 写一些全局的组件,如富文本,各种搜索组件,封装的分页组件等等能被公用的组件。 每个页面或者模块特定的业务组件则会写在当前 @/views(opens new window) 下面。 如:@/views/articlenews/components/xxx.vue。这样拆分大大减轻了维护成本。

请记住拆分组件最大的好处不是公用而是可维护性!

# 组件名

在注册一个组件的时候,我们始终需要给它一个名字。比如在全局注册的时候我们已经看到了

Vue.component('my-component-name', { /* ... */ })

定义组件名的方式有两种:

使用 kebab-case

Vue.component('my-component-name', { /* ... */ })

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如<my-component-name>

使用 PascalCase

Vue.component('MyComponentName', { /* ... */ })

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说<my-component-name><MyComponentName>都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的

# 全局注册

@/main.js(opens new window) 文件下注册组件。

import countTo from 'vue-count-to'
Vue.component('countTo', countTo)
<template>
  <count-to :startVal='startVal' :endVal='endVal' :duration='3000'></count-to>
</template>

# 局部注册

在对应页使用components注册组件。

<template>
  <div class="app-container">
    <el-card>
      <article-detail />
    </el-card>
  </div>
</template>
<script>
import ArticleDetail from './components/ArticleDetail.vue'
export default {
  name: 'CreateArticle',
  components: {
    ArticleDetail
  },
}
</script>

# 新增样式

页面的样式和组件是一个道理,全局的 @/style (opens new window)放置一下全局公用的样式,每一个页面的样式就写在当前 views下面,请记住加上scoped 就只会作用在当前组件内了,避免造成全局的样式污染。

<style>
/* global styles */
</style>

<style scoped>
/* local styles */
.xxx-container{
  /* name scoped */
  xxx
}
</style>

# 请求流程

# 交互流程

一个完整的前端 UI 交互到服务端处理流程是这样的:

UI 组件交互操作;

调用统一管理的 api service 请求函数;

使用封装的 request.js 发送请求;

获取服务端返回;

更新 data;

为了方便管理维护,统一的请求处理都放在 @/src/api 文件夹中,并且一般按照 model 纬度进行拆分文件,如:

api/
  system/
    user.js
    role.js
  monitor/
    operlog.js
	logininfor.js
  ...

提示

其中,@/src/utils/request.js(opens new window) 是基于 axios 的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。 它封装了全局 request拦截器、response拦截器、统一的错误处理、统一做了超时处理、baseURL设置等。

# 请求示例

import http from '@/utils/request'
import defaultSettings from '@/settings'

/**
   * 分页查询
   * @param {查询条件} data
   */
export function GetAllCategoryTreeTable(data) {
  return http.request({
    url: 'Articlecategory/GetAllCategoryTreeTable',
    method: 'get',
    params: data,
    baseURL: defaultSettings.apiCMSUrl // 直接通过覆盖的方式
  })
}/**
   * 获取所有可用的
   */
export function getAllArticlecategoryList() {
  return http.request({
    url: 'Articlecategory/GetAllEnable',
    method: 'get',
    baseURL: defaultSettings.apiCMSUrl // 直接通过覆盖的方式
  })
}

页面调用


import { GetAllCategoryTreeTable, getArticlecategoryDetail,
  saveArticlecategory, setArticlecategoryEnable, deleteSoftArticlecategory,
  deleteArticlecategory } from '@/api/cms/articlecategory'

import elDragDialog from '@/directive/el-drag-dialog' // base on element-ui
export default {
  name: 'Articlecategory',
  directives: { elDragDialog },
  data() {
    return {
      searchform: {
        keywords: ''
      },
      loadBtnFunc: [],
      tableData: [],
      tableloading: true,
      pagination: {
        currentPage: 1,
        pagesize: 20,
        pageTotal: 0
      },
      sortableData: {
        order: 'desc',
        sort: 'CreatorTime'
      },
      dialogEditFormVisible: false,
      editFormTitle: '',
      editFrom: {},
      rules: {
        Title: [
          { required: true, message: '请输入名称', trigger: 'blur' },
          { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
        ]
      },
      formLabelWidth: '120px',
      currentId: '', // 当前操作对象的ID值,主要用于修改
      currentSelected: [],
      selectedCategoryOptions: '',
      selectCategory: []
    }
  },
  created() {
    this.pagination.currentPage = 1
    this.InitDictItem()
    this.loadTableData()
    this.loadBtnFunc = JSON.parse(localStorage.getItem('yueboncurrentfuns'))
  },
  methods: {
    /**
     * 初始化数据
     */
    InitDictItem() {
    },
    /**
     * 加载页面table数据
     */
    loadTableData: function() {
      this.tableloading = true
      var seachdata = {
        keyword: this.searchform.keywords
      }
      GetAllCategoryTreeTable(seachdata).then(res => {
        this.tableData = res.ResData
        this.selectCategory = res.ResData
        this.tableloading = false
      })
    },
    /**
     * 点击查询
     */
    handleSearch: function() {
      this.pagination.currentPage = 1
      this.loadTableData()
    },
  }
}

# 引入依赖

除了 element-ui 组件以及脚手架内置的业务组件,有时我们还需要引入其他外部组件,这里以引入 vue-count-to (opens new window)为例进行介绍。

在终端输入下面的命令完成安装:

$ npm install vue-count-to --save

加上--save参数会自动添加依赖到package.json中去。

# 权限使用

封装了一个指令权限,能简单快速的实现按钮级别的权限判断。v-permission(opens new window)

<!-- 存在权限字符串才能看到-->
<el-button v-hasPermi="['Menu/Add']" type="primary" icon="el-icon-plus" size="mini" @click="ShowMenuEditOrViewDialog()">新增</el-button>

# 页签缓存

由于目前 keep-alive 和 router-view 是强耦合的,而且查看文档和源码不难发现 keep-alive 的 include (opens new window)默认是优先匹配组件的 name ,所以在编写路由 router 和路由对应的 view component 的时候一定要确保 两者的 name 是完全一致的。(切记 name 命名时候尽量保证唯一性 切记不要和某些组件的命名重复了,不然会递归引用最后内存溢出等问题)

//router 路由声明
{
  path: 'LogLogin',
  component: ()=>import('@/views/syslog/login'),
  name: 'LogLogin',
  meta: { title: '登录日志', icon: 'log' }
}
//路由对应的view  /syslog/login
export default {
  name: 'LogLogin'
}

一定要保证两着的名字相同,切记写重或者写错。默认如果不写 name 就不会被缓存

提示

在系统管理-菜单管理-可以配置菜单页签是否缓存,默认为缓存

# 使用图标

全局Svg Icon图标组件。

默认在 @/icons/index.js(opens new window) 中注册到全局中,可以在项目中任意地方使用。所以图标均可在 @/icons/svg(opens new window) 。可自行添加或者删除图标,所以图标都会被自动导入,无需手动操作。

# 使用方式

<!-- icon-class 为 icon 的名字; class-name 为 icon 自定义 class-->
<svg-icon icon-class="password"  class-name='custom-class' />

# 改变颜色

svg-icon 默认会读取其父级的 color fill: currentColor;

你可以改变父级的color或者直接改变fill的颜色即可。

提示

如果你是从 iconfont (opens new window)下载的图标,记得使用如 Sketch 等工具规范一下图标的大小问题,不然可能会造成项目中的图标大小尺寸不统一的问题。 本项目中使用的图标都是 128*128 大小规格的。

如遇到问题到Issues(opens new window) 反馈