太极ERP前端开发总结

一、项目目录(vue-cli2)

├── build
├── config
├── dist //打包后生成的静态页面文件
├── src
│ ├── assets //全局静态图片
│ ├── components //所有组件(项目核心部分)
│ │ ├── element //业务代码
│ │ ├── layout //布局代码(导航,头,和主体部分)
│ │ ├── login //登录代码
│ │ ├── public//公用功能组件
│ ├── filters //全局过滤器
│ ├── my-theme //全局样式表
│ ├── router //路由
│ ├── sever
│ │ ├── api.js //全局API
│ │ ├── method // API方法
│ │ ├── https.js // axios封装,路由请求响应拦截等
│ ├── store //状态管理
│ ├── utils //公用方法
│ │ ├── public_function //公用api增删改查审核作废等方法
│ │ ├── fetch.js //签名生成方法
│ │ ├── rules.js //封装验证方法
│ ├── App.vue //父组件
│ ├── main.js //入口文件
└── static //静态资源

项目目录是采用 vue-cli 自动生成的,如需添加依赖,"$ npm install 依赖名" 即可(如果仅用于开发阶段,则使用 $ npm install --save-dev )。

注:-save 和 save-dev 可以省掉你手动修改 package.json 文件的步骤。

npm install module-name -save 自动把模块和版本号添加到 dependencies 部分。

npm install module-name -save-dev 自动把模块和版本号添加到 devdependencies 部分。

​现已将 vue-cli2 升级到 vue-cli3 ,大大加快了项目编译及打包速度, 但 src 下的目录结构与业务代码几乎没有变动。

二、开发实践

(一) 权限

  1. 权限需求定位到每个页面的每个按钮(主要是列表页): 添加、修改、查看、审核、作废、反审核、打印等,如无权限直接屏蔽该按钮。
  2. 当路由改变时,我们需要动态的监听该页面是否具有权限,可在 router.afterEach 钩子函数中设置( router/index.js )
  3. 将获取到的权限存放到本地存储里,相应在页面里获取本地存取的权限值然后控制页面上按钮的显示状态。
  • 页面使用方法


    /**
    1. 在 created 或者 mounted 钩子函数初始化时获取
    2. 在template模板中给按钮绑定权限
    **/
    data(){
    return {
    authority: {},
    }
    },
    created() {
    this.authority = JSON.parse(localStorage.getItem("authority"));
    }

    <!-- template -->
    <el-button icon="el-icon-circle-plus-outline" @click="salesAddF" v-if="authority.b_add == 1">新增</el-button>
    <el-button icon="el-icon-delete" @click="deleteF" v-if="authority.b_del == 1">删除</el-button>

    // router/index.js
    import Vue from "vue";
    import axios from "@/sever/https";
    import Router from "vue-router";
    Vue.use(Router);
    const router = new Router({
    base: '/tomcat/',
    routes: [{
    path: "/login",
    name: "登录页面",
    component: login,
    },
    ...],
    ]
    });
    router.beforeEach((to, from, next) => {
    var objArr = str.split("/", 3);
    if (to.matched.some(r => r.meta.requireAuth)) {
    if (store.state.system_app.token) {
    //登录状态当监测到路由变化,则去请求进入页面的权限接口
    axios({
    method: "get",
    url: "xxxRuleApiUrl",
    params: { code: objArr[2] }
    }).then(res => {
    if (res.data.code == 200) {
    if (res.data.data.is_role == 'Y') {
    //如果该页面设有权限则将权限详情先存于本地
    window.localStorage.removeItem("authority")
    let authority = JSON.stringify(res.data.data.role_list);
    localStorage.authority = authority; next();
    } else {
    // 未有页面访问权限
    next({
    path: "/isRole",
    query: {
    redirect: to.fullPath
    }
    });
    }
    next();
    }
    }); } else {
    next({
    path: "/login",
    query: {
    redirect: to.fullPath
    }
    });
    }
    } else {
    next();
    }
    });
    export default router;

(二) 各组件间传递数据

  1. 点击 vuex官网
  2. 项目中引入了vuex,使组件间传值更加方便,避免使用中间件 bus 等
  3. 如果是简单的子父传参,建议使用 $parent / $children / $emit / $attrs / $refs.name 来减少代码量,并更易于读写,避免污染全局(灵活运用)
  4. 项目中大量运用到 vuex 的主要是解决列表页、新增页、修改页、查看页的来回跳转

例,使用vuex存储变量,切换新增修改查看页面

  • 页面调用方法


    // 使用方法:调用 ACTION 的salesorderIndexACT方法,改变modulename为Index,
    // 切换组件到首页并更新视图
    // salesorderAdd.vue
    import { mapActions, mapGetters } from "vuex";
    methods:{
    ...mapActions([
    "salesorderAdd",
    "salesorderIndexACT"
    ]),
    goBack() {
    // 回到模块首页,使用 ACTION salesorderIndexACT
    this.salesorderIndexACT({
    moduleName: "Index"
    });
    }
    }
  • 初始化页面


    <!-- template 中默认绑定列表页 -->
    <template>
    <div>
    <component v-bind:is="commont"></component>
    </div>
    </template>
  • 页面中间件


    //组件中引入各模块
    <script>
    import Index from "@/components/element/sales/salesordermanage/salesorder/salesorderIndex";
    import salesorderAdd from "@/components/element/sales/salesordermanage/salesorder/salesorderAdd";
    import salesorderSee from "@/components/element/sales/salesordermanage/salesorder/salesorderSee";
    import { mapGetters } from "vuex";
    export default {
    data() {
    return {
    commont: "Index"
    };
    },
    components: {
    Index, //初始化首页
    salesorderAdd, //新增页
    },
    computed: {
    ...mapGetters([
    "salesorderIndexGet",
    "salesorderAddGet",
    "salesorderSeeGet"
    ])
    },
    watch: {
    salesorderIndexGet() {
    this.commont = this.salesorderIndexGet.moduleName;
    },
    salesorderAddGet() {
    this.commont = this.salesorderAddGet.moduleName;
    },
    salesorderSeeGet() {
    this.commont = this.salesorderSeeGet.moduleName;
    }
    }
    };
    </script>
  • 定义状态的方法


    //state.js
    export default {
    // 销售订单
    salesorderIndex: {},
    salesorderAdd: {},
    salesorderSee: {},
    ...
    };
    /**
    * 最好不要直接使用,state主要用于存取变量,例如:
    ...mapState({
    aa: state => state.system_app.user,
    })
    **/ // ============= 分割线 ============= // //getter.js
    export default {
    // 销售订单
    salesorderIndexGet(state) {
    return state.salesorderIndex;
    },
    salesorderAddGet(state) {
    return state.salesorderAdd;
    },
    salesorderSeeGet(state) {
    return state.salesorderSee;
    }
    }; /**
    * 用法:写到watch或者computed中,ShowCon类似于data中的一个对象,getShowCon是getters中的一个方法,例如:
    ...mapGetters({
    ShowCon:"getShowCon"
    })
    **/ // ============= 分割线 ============= // //mutations.js
    export default {
    // 销售订单
    salesorderIndex(state, info) {
    state.salesorderIndex = info
    },
    salesorderSee(state, info) {
    state.salesorderSee = info
    },
    salesorderAdd(state, info) {
    state.salesorderAdd = info
    }
    }
    /**
    * 获取方法,clickF是本地调用的方法,@click="clickF(...arg)",salesorderIndex是写到mutations里边的方法,例如:
    ...mapMutations({
    clickF:"salesorderIndex"
    })
    **/
    // ============= 分割线 ============= // //actions.js
    export default {
    salesorderIndexACT(context, data) {
    context.commit("salesorderIndex", data);
    },
    salesorderSee(context, data) {
    context.commit("salesorderSee", data);
    },
    salesorderAdd(context, data) {
    context.commit("salesorderAdd", data);
    }
    };
    /**
    * 执行或者多个mutations的方法
    **/

(三) 过滤器

  1. 项目中大量使用到了对数值等的格式化,如处理价格的千分位分隔符过滤器,对价格保留两位小数的过滤器,处理正负数的过滤器等。使用方法:
  • 页面上使用


    <template slot-scope="scope">
    {{scope.row.price | priceFormat}}
    </template>
  • 引入写好的过滤器模块,遍历所有过滤器并挂载到Vue实例上,组件里即可以直接使用


    // main.js
    import filters from '@/filters/index.js'
    Object.keys(filters).forEach(key => {
    Vue.filter(key, filters[key])
    });
  • 自定义过滤器

    //filter.js
    export default {
    //价格保留两位小数并使用千分位分隔符
    priceFormat(s, n) { //可以小于0
    if (!s && s !== 0) {
    return s
    }
    var numberS = Number(s)
    if (numberS) {
    n = n > 0 && n <= 20 ? n : 2;
    numberS = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + "";
    var l = numberS.split(".")[0].split("").reverse(),
    r = numberS.split(".")[1];
    var t = "";
    for (var i = 0; i < l.length; i++) {
    t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? "," : ""); }
    if (s < 0 && Math.abs(s) < 1000) {
    return s
    } else {
    return t.split("").reverse().join("") + "." + r;
    }
    } else {
    return s
    }
    },
    ...
    };

(四) 路由

  1. 官网:Vue Router

  2. 根据接口 system/menu/getLeftMenuList 命名文件

  3. 模块化路由文件,在 router/index.js 中导入各模块路由的路径

  4. 避免首次加载页面的时候加载整个模块代码(导致几秒白屏),加入了路由懒加载

    //router/index.js
    {
    path: "xx",
    name: "xx",
    component: resolve =>
    require([
    "@/components/element/xx/xx/xx.vue"
    ], resolve),//路由懒加载
    meta: {
    _meauname: '自定义标题'
    }
    }

(五) axios

  1. 可加入默认自定义参数

    // sever/https.js
    axios.defaults.timeout = 10000; //可等待时长10s
    axios.defaults.baseURL = "http://60.205.214.105"; //接口baseURL
    axios.defaults.headers.post["Content-Type"] =
    "application/x-www-form-urlencoded; charset=UTF-8";//请求默认头信息
  2. 可在axios拦截中自定义功能。

  3. 在请求拦截 axios.interceptors.request.use 中加入 loading 效果、 post 和 get 请求对参数进行格式化。

  4. 在响应拦截 axios.interceptors.response.use 里如果接口报错,根据状态码给出相应提示语和项目公用方法规范


    import axios from "axios";
    import {
    Message,
    Loading
    } from "element-ui"; // 请求拦截器
    axios.interceptors.request.use(
    config => {
    loading = Loading.service({
    fullscreen: true,
    lock: true,
    text: '正在加载,请稍等……'
    }); if (config.method == "post") {
    removePending(config); //在一个ajax发送前执行一下取消操作
    config.cancelToken = new cancelToken((c) => {
    // 这里的ajax标识我是用请求地址&请求方式拼接的字符串,当然你可以选择其他的一些方式
    pending.push({
    u: config.url + '&' + config.method,
    f: c
    });
    });
    let postData = config.data;
    config.data.sign = serverMoudle(postData);
    } else if (config.method == "get") {
    let getData = config.params;
    config.params = {
    sign: serverMoudle(getData),
    ...config.params
    };
    }
    if (store.state.system_app.token) {
    config.headers.Token = `${store.state.system_app.token}`;
    }
    // 每次请求添加sign
    return config;
    },
    err => {
    if (loading) { loading.close(); }
    Message({
    showClose: true,
    message: err.msg,
    type: 'error'
    })
    return Promise.reject(err);
    }
    ); // 响应拦截器
    axios.interceptors.response.use(
    response => {
    if (loading) { loading.close(); }
    const data = response.data;
    if (response.config.method == 'post') {
    removePending(response.config);
    }
    if (data.code == 401) {
    store.dispatch('LOGOUT')
    router.replace({
    path: "/login"
    });
    } else if (data.code == 400) {
    Message.error(data.msg) } else if (data.code == 503) {
    Message.error(data.msg)
    store.dispatch('LOGOUT')
    router.replace({
    path: "/login"
    });
    }
    return response;
    },
    error => {
    Message({
    message: "网络错误",
    type: "error",
    duration: 5 * 1000
    });
    return Promise.reject(error);
    }
    );
    export default axios;

(六) 请求列表数据(带分页)

查询、重置、切换页码、切换列表长度四个功能封装成为一个公用的方法。这四者有着密切的关系,又有各自的不同点(默认:列表30项,显示第一页),它们的传递值分别为:

功能 params(搜索参数) page(页码) per_page(分页长度)
查询 已填搜索项 1 已选的分页长度
重置 搜索项置空(根据需求) 1 30
切换页码 已填搜索项 已选的页码 已选的分页长度
切换分页长度 已填搜索项 已选的页码 已选的分页长度
  • 使用方法


    import { getData } from "@/utils/public_function/commonUtils";//初始化getData方法参数
    this.$set(this, "params", { defaultParams: "xx" });
    this.$set(this, "searchParams", {
    params: this.params, //默认参数
    TableData: this.TableData, //列表数据
    API: "getList", //列表接口名
    vm: this //Vue
    });
    getData(this.searchParams); //获取列表 //在实现相应功能时,可以直接使用组合方法改变this.searParams.params的值,即可。
    onSearch(){
    Object.assign(this.searchParams.params, {
    per_page: val,//分页长度
    page: 1//第一页
    });
    getData(this.searchParams); //获取列表
    }
  • getData 方法


    //commonUtils.js
    export function getData(arg, type) {
    /*
    * @Date: 2018-07-06 10:23:10
    * 初始化 搜索,重置,切换页码,切换页码长度 都需要调取该方法重新初始化列表
    * arg格式:
    * {
    * params:{},//请求参数
    TableData: this.TableData, //列表数据
    API: "getxxx", //接口名
    vm: this //传递的this
    }
    */ if (arg["params"]) {
    arg["vm"]["goPage"] = "page" in arg["params"] ? arg["params"]["page"] : 1; //改变当前页
    arg["vm"]["pageSize"] =
    "per_page" in arg["params"] ? arg["params"]["per_page"] : 30; //改变当前页size
    } arg.vm.loading = true;
    axios({
    method: "get",
    url: api[arg.API],
    params: arg.params
    }).then(res => {
    if ([200, 304].includes(res.data.code)) {
    arg.TableData.tableData = res.data.data.data;
    arg.TableData.total = Number(res.data.data.total);
    arg.TableData.allList = res.data.data.desc;
    }
    arg.vm.loading = false;
    });
    }

(七) 组织部门业务员三级联动

需求:

  1. 选择上一级需要清除下级绑定的数据和列表
  2. 页面默认赋值当前登陆者的信息
    //使用方法
import {
getCompanyListCommon,
getDepartmentListCommon,
getEmployeeListCommon
} from "@/utils/public_function/commonUtils"; //引入 获取组织、部门、业务员 方法 getCompanyListCommon(this); //即可获取组织 其他同理。其中三者的联动可在各自的回调完成之后再调取下一级数据

(八) 优化性能,手动绑定下拉框数据

  • 使用方法

    import { bindIdsF } from "@/utils/public_function/commonUtils";
    //在详情接口回调中,绑定
    this.$axios({
    method: "get",
    url: xxx,
    params: { order_id: xxx }
    }).then(({ data }) => {
    if (data.code == 200) {
    /**
    * data.data:下拉绑定的字段可能和详情接口返回的字段不一样,需要在这里用assign做下处理,多数情况下不需要
    * assign方法如下:
    */
    Object.assign(data.data, {
    employee_id: data.data.business_id,
    employee_name: data.data.business_name
    });
    // 列表下有 options 则传入,没有可不传入
    bindIdsF(this, data.data, [
    ["customer_id", "customer_name", "customerList", "options"],
    ["department_id", "department_name", "departmentList", "options"],
    ["employee_id", "employee_name", "employeeList", "options"]
    ]);
    }
    });
  • 绑定下拉框数据方法


    // commonUtils.js
    // 初始化绑定返回的id 下拉
    export function bindIdsF(self, oParent, arrList) {
    if (Array.isArray(arrList)) {
    for (const item of arrList) {
    let arr = [];
    if (item[3]) {
    arr = [{
    [item[0]]: Number(oParent[item[0]]),
    [item[1]]: oParent[item[1]],
    }];
    self.$set(self[item[2]], item[3], arr)
    } else {
    arr = [{
    [item[0]]: Number(oParent[item[0]]),
    [item[1]]: oParent[item[1]],
    }];
    self.$set(self, item[2], arr)
    }
    }
    }
    }

注意:遇到特殊情况(如选客户等带出其他下拉字段(业务员等)),需在相应接口回调里继续使用 bindIdsF 去初始化下拉数据

(九) 验证

验证模块(utils/rules.js)中定义了组件上所有用到的验证,然后将rules 挂载的Vue原型上即可。

  • 使用方法


    /**
    * 组件中直接调用rules下的对应方法即可
    * 如果仅加星号,不做限制,只用prop属性即可
    **/
    <el-form :model="param" ref="paramsRef">
    <el-form-item label="库存组织" prop="address" :rules="rules.address">
    <el-input v-model="params.address"></el-input>
    </el-form-item>
    </el-form>
  • 将验证方法挂载到vue实例上


    // main.js
    import rules from './utils/rules' //全局定义表单验证规则
    Vue.prototype.rules = rules;
  • rules.js 页面定义方法,常用的有:


    // 自定义方法,作为 validator 的属性值
    // 第一位不能为0,保留两位小数
    const checkNumPot2 = (rule, value, callback) => {
    const reg = /(^[0-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/;
    if (!value && value !== 0) {
    return callback(new Error("请填写数字"));
    } else if (!reg.test(value)) {
    return callback(new Error("请填写数字,最多2位小数"));
    } else {
    callback();
    }
    };
    // 验证身份证
    const checkIdNum = (rule, value, callback) => {
    const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
    if (!reg.test(value)) {
    return callback(new Error("证件号码不正确"));
    } else {
    callback();
    }
    }; // 验证整数
    const checkInterNum = (rule, value, callback) => {
    const reg = /^[0-9]*[1-9][0-9]*$/;
    if (!value) {
    return callback(new Error("请填写整数"));
    } else if (!reg.test(value)) {
    return callback(new Error("请输入整数"));
    } else {
    callback();
    }
    };
    //验证座机
    const telephone = (rule, value, callback) => {
    const reg = /^([0-9]{3,4}-)?[0-9]{7,8}$/;
    if (!reg.test(value) && value != undefined && value != "") {
    return callback(new Error('请正确输入固定电话,格式为"区号-座机号码"'));
    } else {
    callback();
    }
    };
    //输入不可为空
    const empty = (rule, value, callback) => {
    if (value == "") {
    return callback(new Error("输入不能为空"));
    } else {
    callback();
    }
    };
    //邮箱验证
    const emailNew = (rule, value, callback) => {
    const reg = /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,5}$/;
    if (!reg.test(value)) {
    return callback(new Error('4~18个字符,可使用字母、数字、下划线,需以字母开头@xxx.com/cn'));
    } else {
    callback();
    }
    }; /**
    * requited: 是否可为空
    * pattern:自定义正则
    * validator:验证方法
    * message:验证不通过消息
    * trigger:触发方式
    *
    * pattern 和 validator 写一个即可
    **/
    export default {
    validatePhone: [{
    required: true,
    pattern:: /^1[0123456789]\d{9}$/,
    message: "目前只支持中国大陆的手机号码",
    trigger: "blur"
    }],
    validateEmpty: [{
    required: true,
    validator: empty,
    trigger: "blur"
    }], //不可输入为空
    numPot2: [{
    required: true,
    validator: checkNumPot2,
    trigger: "blur"
    }], // 第一位不能为0,保留两位小数
    interNum: [{
    required: true,
    validator: checkInterNum,
    trigger: "blur"
    }], // 整数
    telephone: [{
    required: false,
    validator: telephone,
    trigger: "blur"
    }], // 验证座机
    emailNew: [{
    required: false,
    validator:emailNew,
    message: "请输入正确的邮箱格式:xxx@xxx.com/cn",
    trigger: "blur"
    }],
    company_id: [{
    required: true,
    message: "请选择组织",
    trigger: "change"
    }],
    ...
    };

(十) 打印

自定义打印模板后,以组件的形式引入并调用其打印方法即可。

打印官网

  • 使用方法


    // template
    <el-button type="text" @click="printF(scope, scope.row)"> 打印拣货单 </el-button> <!-- 打印组件 -->
    <div>
    <printPage ref="printRef"></printPage>
    </div> <script>
    import printPage from "@/components/public/pickingList.vue"; //打印组件
    // 触发打印组件
    methods:{
    printF(scope, row) {
    this.$nextTick(function() {
    this.$refs.printRef.printPdf(row.outstock_id);
    });
    },
    }
    </script>
  • 打印内容页面(自定义的格式)


    // 引用打印插件:@/utils/LodopFuncs.js // pickingList.vue
    <template>
    <div v-show="false">
    <div v-if="params!=null">
    <div id="print-header">
    <table class="header" cellpadding="0" cellspacing="0" style="width: 100%;">
    <tr>
    <td>
    表头
    </td>
    </tr>
    </table>
    </div>
    <div id="print-body">
    <table class="body" cellpadding="0" cellspacing="0" style="width: 100%;">
    <tr v-for="item in tableData" :key="item.id">
    <td>表体</td>
    </tr>
    </table>
    </div>
    <div id="print-footer">
    <table class="footer" cellpadding="0" cellspacing="0" style="width: 100%;">
    <tr>
    <td>
    页脚
    </td>
    </tr>
    </table>
    </div>
    <div id="print-pages">
    <p style='text-align:center;font-size:9pt;'>
    页码:
    <span tdata='pageNO'>第 ## 页</span> /
    <span tdata='pageCount'>共 ## 页</span>
    </p>
    </div>
    </div>
    </div>
    </template> <script>
    import { getLodop } from "@/utils/LodopFuncs";
    let LODOP = getLodop;
    export default {
    data() {
    return {
    params: null,
    tableData: null
    };
    },
    methods: {
    list(val) {
    let vm = this;
    this.$axios({
    method: "get",
    url: this.$api.printPick,
    params: { outstock_id: val }
    }).then(res => {
    if (res.data.code == 200) {
    vm.params = res.data.data;
    vm.tableData = res.data.data.goods_info;
    setTimeout(function() {
    vm.CreatePrintPages();
    }, 0);
    }
    });
    },
    printPdf(val) {
    this.list(val);
    },
    CreatePrintPages() {
    var LODOP = getLodop();
    if (!LODOP) {
    return false;
    }
    var strStyle = LODOP.strStyle;
    LODOP.PRINT_INIT("");
    LODOP.SET_PRINT_PAGESIZE(1, "205mm", "93.3mm", "CreateCustomPage"); // 0 操作者自行决定或打印机缺省设置 1 纵向打印,固定纸张;2 横向打印,固定纸张
    LODOP.SET_PREVIEW_WINDOW(1, 0, 0, 1000, 600, ""); // 初始预览窗口大小
    LODOP.SET_SHOW_MODE("LANDSCAPE_DEFROTATED", 1); // 横向打印时正向显示
    LODOP.SET_SHOW_MODE("HIDE_PAPER_BOARD", 1); // 去除背景滚动线
    LODOP.SET_PRINT_MODE("AUTO_CLOSE_PREWINDOW", 1); // 打印后自动关闭预览
    LODOP.SET_PRINT_MODE("CUSTOM_TASK_NAME", "拣货单"); // 打印队列中的文档名
    // 追加打印头部
    LODOP.ADD_PRINT_TABLE(
    "2mm",
    "10mm",
    "185mm",
    "30mm",
    strStyle + document.getElementById("print-header").innerHTML
    );
    LODOP.SET_PRINT_STYLEA(0, "ItemType", 1); // 页眉页脚项
    // 追加打印主体:分页、循环表格
    LODOP.ADD_PRINT_TABLE(
    "28mm",
    "10mm",
    "185mm",
    "20mm",
    strStyle + document.getElementById("print-body").innerHTML
    );
    // 追加打印底部
    LODOP.ADD_PRINT_TABLE(
    "63mm",
    "10mm",
    "185mm",
    "30mm",
    strStyle + document.getElementById("print-footer").innerHTML
    );
    LODOP.SET_PRINT_STYLEA(0, "ItemType", 1); // 页眉页脚项
    // 追加页码
    LODOP.ADD_PRINT_HTM(
    "80mm",
    "10mm",
    "185mm",
    "5mm",
    document.getElementById("print-pages").innerHTML
    );
    LODOP.SET_PRINT_STYLEA(0, "ItemType", 1);
    LODOP.PREVIEW();
    }
    }
    };
    </script>

(十一) 签名

签名算法前后台要保持一致

  • 使用


    //签名使用方法 在 axios 请求拦截中,给发送的数据用签名算法格式化
    config.data.sign = serverMoudle(config.data) //post请求
    config.params.sign = serverMoudle(config.params) //get请求
  • 签名算法


    import md5 from 'js-md5';
    import Qs from "qs";
    const unchangeable = 'xxxxxxx'; //根据该字符串加密
    import { objKeySort } from '@/utils/public_function/commonUtils.js'; //将接口参数排序并将null或undefined的数据设为空
    export function objKeySort(obj) {
    var newkey = Object.keys(obj).sort();
    var newObj = {};
    for (var i = 0; i < newkey.length; i++) {
    //过滤参数中null或者undefined的值,并使之默认为空,否则报错
    if ([null, undefined].includes(obj[newkey[i]])) obj[newkey[i]] = '';
    newObj[newkey[i]] = obj[newkey[i]];
    }
    return newObj;
    } export function serverMoudle(params) {
    let newUrl = ''
    if (params == undefined) {
    newUrl = "key=" + unchangeable;
    return md5(newUrl)
    } else {
    if (Qs.stringify(objKeySort(params)) != '') {
    newUrl = decodeURIComponent(Qs.stringify(objKeySort(params))) + "&key=" + unchangeable;
    } else {
    newUrl = "key=" + unchangeable;
    }
    return md5(newUrl)
    }
    }

三、代码规范

(一) JS规范

1.直接只用对象、数组字面量初始化变量,不用new它的实例(官方推荐,简单明了)

let arr = [] , obj = {};

2.尽量用全等判断是否相等(===);

null === false; //false

3.用变量本身的布尔类型去做判断,必须转布尔值时可以使用双感叹号转布尔类型。

!!null === false //true

4.用其他代码优雅替换if else语句(三目运算符、switch、逻辑判断等)

5.写有意义的注释,了解一些特殊标记,e.g.

TODO: 有功能待实现。

FIXME: 该处代码运行没问题,但可能由于时间赶或者其他原因,需要修正。

HACK: 为修正某些问题而写的不太好或者使用了某些诡异手段的代码。

XXX: 该处存在陷阱。

6.避免代码冗余,能循环则不去单个操作变量

(二) Vue规范

1. 过滤器:

  1. v-for 要配合key使用,标注唯一性,这将会让 Vue 知道如何使行为更容易预测。

    ② 避免 v-if 和 v-for 同时用在同一个元素上。

2. 父子组件相互传递数据:

  1. 在父组件传子组件数据上,prop 起着重要的作用,其中prop的定义应该尽量详细,至少需要指定其类型,同时可指定该数据的默认值(避免控制台报错),注意,在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case
  2. 组件间传递数据要灵活使用,如使用vue自带的实例属性( $parent/$children/$emit/$attrs/$refs.name)
  3. 如果涉及不相关组件间的传值,可使用状态管理 vuex官网 点击

3. 样式:

  1. 组件里的 <style> 要避免污染全局,请设置属性 scope

4. data

  1. 每个组件对应的 data ,要用 function 来 return 出去,这样每个 data 的属性就不是挂载到 vue 的原型上,而是有它自己的上下文,当 data 的值是一个对象时,它会在这个组件的所有实例之间共享

5. $set

  1. 因为 Vue 无法探测普通的新增属性,所以向响应式对象添加属性需用$set赋值

6. JSX语法

如需渲染较为复杂的 DOM ,可使用Jsx语法简化代码 GITHUB 点击

7. 尽量使用Vue自带API

  1. 使用 vue 自带的 API 替代 js 原生方法 如 $remove Vue API 点击

8. 性能优化:

  1. 组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法( computed )(如表格中的数据计算或格式化等)

(三) 命名规范

  1. 小驼峰式命名(格式:myVariale

    • 组件名规范:

      新增页面:xxxAdd.vue / 修改页面:xxxModify.vue / 主页面:xxxIndex.vue / 查看页面:xxxSee.vue

    • 方法名规范:

      查询:onSearch / 重置:onReset / 修改某个功能:changeXxx / 新增:addF / 修改:modifyF / 查看:seeF

    • 变量名规范:

      列表数据:xxxList / 表格数据:TableData.tableData / 表头参数:params / 默认值:defaultXxx

    • 状态管理变量名规范:

      states:xxxIndex

      mutations:xxxIndex

      getters:xxxIndexGet

      actions:xxxIndexACT

  2. 良好的命名也能导致维护性更强,可读性更好,同时更大程度避免变量重复导致的bug

(四)文本编辑器规范

  • 缩进:两个空格

  • 去除eslint语法检测,虽然能使代码更规范,但是及其影响工作效率,不推荐。

  • 建议使用编辑器vscode或sublime Text,学会利用编辑器的有用插件。

    插件推荐

    ① 提高效率:HTML Snippets(html snippets)、Javascript (ES6) Code Snippets(ES6 snippets)、Auto Close Tag(自动补全)、Emmet(css snippets)、Vue2 Snippets

    ② 功能增强:Color Highlight(识别代码的颜色)、Bracket Pair

    Colorizer(识别代码中的括号,标记不同的颜色)

    ③ 设置编辑器自带的自动保存、自动格式化代码功能

    ④ 等等

  1. 运用好各编辑器的快捷键,工作效率会事半功倍
  2. 学会使用控制台调试代码

四、优化内容

该项目还在不断的完善当中,避免不了存在着一些不足,以下是目前规划的开发计划。

  • [x] 缓存功能
  • [ ] 完善项目细节
  • [x] 优化性能,减少接口调用次数
  • [x] 升级Vue3.0,极大提升编译与打包速度

erp前端项目总结的更多相关文章

  1. 用gulp构建你的前端项目

    前言 前端技术发展日新月异,随着模块化.组件化的提出,前端变得越来越复杂,静态资源越来越多,那么对静态资源的处理,如压缩,合并,去掉调试信息.. 如果还是人工去处理,效率非常之低且还容易出错,于是自动 ...

  2. 前后端分离之前端项目构建(grunt+require+angular)

    前言 前段时间做了一个项目,前端开发页面,然后把代码给到后端同学,后端同学通过vm再来渲染页面.后来才发现,这种方式简直是太low了,因为前端代码在服务端同学那里,每次前端需要更改的时候都需要去到服务 ...

  3. 前端项目构建工具---Grunt

    什么是Grunt? grunt是javascript项目构建工具,在grunt流行之前,前端项目的构建打包大多数使用ant.(ant具体使用 可以google),但ant对于前端而言,存在不友好,执行 ...

  4. [front]有效开展一个前端项目

    今天的前端如果没有用到 npm,效率是比较低的:所以要从使用的工具来讲. 1. 一切都依赖于 nodejs: 下载一个 linux 的源码包就可以开始安装了. $ wget https://nodej ...

  5. gulp 配置前端项目打包

    项目发布时,需要对项目js文件进行压缩,混淆,连接等操作以减小项目http请求,加快访问. gulpjs.com中有很多插件可以用来配置打包部署. 需要用的常用插件有: gulp-jsmin  压缩j ...

  6. 使用gulp来构建一个前端项目

    什么是gulp? gulp是一个前端项目构建工具,是自动化项目的构建利器,它不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成.你可以使用gulp及其插件对你的项目代码 ...

  7. gulp + webpack 构建多页面前端项目

    修改增加了demo地址 gulp-webpack-demo 之前在使用gulp和webpack对项目进行构建的时候遇到了一些问题,最终算是搭建了一套比较完整的解决方案,接下来这篇文章以一个实际项目为例 ...

  8. 从一个前端项目实践 Git flow 的流程与参考

    Git flow 出自 A successful Git branching model,这里使用了一个前端项目配合本文稿实施了 git flow 并记录流程作出示例和参考,对 hotfix 与持续部 ...

  9. 前端项目部署之Grunt

    如果你的前端项目很小或都者项目不需要通过专门的运维同学走流水线上线部署的话,那么可以略过以下的繁文. ok,Let's go! 我们看看如何使用grunt来部署上线项目? 前端项目一般分为两种类型:T ...

随机推荐

  1. Support for SSL/TLS protocols on Windows

    https://blogs.msdn.microsoft.com/kaushal/2011/10/02/support-for-ssltls-protocols-on-windows/ Support ...

  2. delphi手动创建dataset并插入值

    unit Unit1; interface uses  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, Syste ...

  3. 通过域名访问部署在服务器上的javaweb项目

    因为对域名访问什么也不了解,遇到问题就有种不知道从哪里下手的茫然,也就更不知道错在哪里,前前后后一共折腾了一天多,最后问了阿里客服才成功弄出来,因此记录一下. 关于服务器的购买.配置,及域名的备案解析 ...

  4. [AT697]フィボナッチ

    题目大意:给你$n,k(n\leqslant10^9,k\leqslant10^3)$,求$f_n$.$f$数组满足$f_1=f_2=\cdots=f_k=1$,$f_n=\sum\limits_{i ...

  5. 【BZOJ3105】【CQOI2013】新Nim游戏

    Description 传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同).两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴.可以只拿一根,也可以拿走整堆火柴 ...

  6. 【hdu3555】 Bomb

    http://acm.hdu.edu.cn/showproblem.php?pid=3555 (题目链接) 题意 求区间${[1,n]}$含有49的数的个数. Solution 数位dp,先求出不含4 ...

  7. JVM 垃圾回收机制和常见算法

    垃圾回收机制:释放那些不再持有引用的对象的内存. 如何判断对象是否需要回收? 引用计数:对象,内存,磁盘空间等被引用次数保存起来,次数为0时将其进行释放. 对象引用遍历:对象应用遍历从一组对象开始,沿 ...

  8. 使用Dom4解析xml

    XML是一种通用的数据交换格式,它的平台无关性.语言无关性.系统无关性.给数据集成与交互带来了极大的方便. XML在不同的语言环境中解析方式都是一样的,只不过实现的语法不同而已. XML的解析方式分为 ...

  9. 【bzoj2938】【Poi2000】病毒

    题解: 对病毒串建立ac自动机: 有一个无限长的串等价于可以一直在自动机上匹配,等价于自动机上的转移有环: 当然前提是去掉病毒节点的fail子树: 写一个dfs记录是否在栈中,来过没有找到就不必再来了 ...

  10. 树状数组+二分答案查询第k大的数 (团体程序设计天梯赛 L3-002. 堆栈)

    前提是数的范围较小 1 数据范围:O(n) 2 查第k大的数i:log(n)(树状数组查询小于等于i的数目)*log(n)(二分找到i) 3 添加:log(n) (树状数组) 4 删除:log(n) ...