代码标准
管理系统 - 最佳实践

管理系统最佳实践

前端管理系统代码编写最佳示例

内容目录

  1. 表单验证提交 标准代码
  2. 新增页面
  3. Table操作按钮样式
  4. 关于element 、vxe-table 组件属性
  5. el-select 组件增强功能
  6. 对象深拷贝
  7. 前端本地字典
  8. 关于接口调用
  9. 导入引用
  10. el-form 样式
  11. 关于 el-form 的 label 宽度
  12. 关于样式编写
  13. 关于路由配置

表单验证提交

  • 具体参考采购订单新增提交

注意: 先了解 Promise、async 和 await 对理解以下代码有帮助

  1. info-form.vue 中的 部分代码
<template>
  <el-form class="sht-info-form-5" :model="infoForm" :rules="rules" ref="ruleForm" label-width="$getConfig('EL_FORM_LABEL_WIDTH')">
    代码省略...
  </el-form>
</template>
 
<script>
import { getFormErrorMsg } from '@/util/prompt';
 
export default {
  methods: {
    submitForm() {
      return new Promise((resolve, reject) => {
        this.$refs.ruleForm.validate((valid, o) => {
          // 校验是否成功
          if (valid) {
            // 验证成功
            resolve();
          } else {
            // 验证失败  如果失败调用 getFormErrorMsg 方法传入 o 参数 (o -> callback 的第二个参数)
            // getFormErrorMsg 返回错误的验证消息
            reject(getFormErrorMsg(o));
          }
        });
      });
    },
  }
}
</script>
  1. info-goods.vue 中的 部分代码
<template>
  <vxe-table
    ref="xTable"
    show-footer
    show-overflow
    :max-height="getHeight()"
    :edit-rules="validRules"
    :loading="loadingTable"
    :footer-method="footerMethod"
    :data="infoForm.purchaseOrderInfoList"
    :edit-config="{ trigger: 'click', mode: 'row' }"
  >
    代码省略...
  </vxe-table>
</template>
 
<script>
import { getTableErrorMsg } from '@/util/prompt';
export default {
  methods: {
    // 提交验证
    async submitForm() {
      if (!this.infoForm.purchaseOrderInfoList.length) {
        return Promise.reject('请增加采购商品明细!');
      }
      // 校验是否成功
      const errMap = await this.$refs.xTable.fullValidate(true).catch((err) => err);
      if (errMap) {
        // 校验失败  调用 getTableErrorMsg 传入errMap 得到 错误的验证消息
        return Promise.reject(getTableErrorMsg(errMap));
      }
      // 校验成功
      return Promise.resolve();
    },
  }
}
</script>
  1. add.vue 中的 部分代码
<template>
  <el-button @click="handleCheck($getConfig('SUBMIT_TYPE'))" type="primary">新增提交</el-button>
  代码省略...
  <InfoForm :info-form="infoForm" ref="infoForm" />
  代码省略...
  <InfoGoods :getHeight="getHeight" :info-form="infoForm" ref="infoGoods" />
  代码省略...
</template>
 
<script>
import { promptMessage } from '@/util/prompt';
 
export default {
  methods: {
    // 提交
    async handleCheck(type) {
      try {
        // 校验订单info-form form必填项
        await this.$refs.infoForm.submitForm();
 
        // 校验订单info-goods table必填项
        await this.$refs.infoGoods.submitForm();
 
        /* 代码省略... */
      } catch (msg) {
        // msg 都是 reject 返回的错误信息  通过 调用promptMessage 进行提示
        promptMessage.call(this, msg);
      }
    }
  }
}
</script>

新增页面

  • 表单样式
    • 新开页面的表单提交中有多个输入框的(一般在15个以上) 使用以下样式 采用一行5个输入框排列
    • 弹出框的表单提交中有多个输入框的(一般在15个以上) 使用以下样式 采用一行4个输入框排列
    • el-form 一定要加上 label-width="$getConfig('EL_FORM_LABEL_WIDTH')" class="sht-info-form-5"
    • label-width 大部分是120px 如果标题超长则自定义
  • 新开页面表单样式 (采购订单新增页面)
  1. 代码片段
<template>
  <el-form label-width="$getConfig('EL_FORM_LABEL_WIDTH')" class="sht-info-form-5">
    <!-- 此处省略部分代码 -->
    <el-form-item label="所属组织">
      <el-input v-model="infoForm.tenant" disabled placeholder="系统自动生成" />
    </el-form-item>
    <!-- 此处省略部分代码 -->
    <el-form-item label="备注" style="flex: 0 0 40%">
      <el-input type="textarea" v-model="infoForm.remark" />
    </el-form-item>
  </el-form>
</template>
  1. 图文片段 image
  • 弹出框表单样式 (采购退货单) *
  1. 代码片段
<template>
  <el-dialog title="新增退货单 "width="80%">
    <el-form ref="infoForm" :model="form" class="sht-info-form-4" label-width="$getConfig('EL_FORM_LABEL_WIDTH')">
      <!-- 此处省略部分代码 -->
      <el-form-item label="xxxxx" title="得捷-湿度敏感等级">
        <el-input v-model="form.dmsl" disabled />
      </el-form-item>
      <el-form-item label="xxxxx" style="flex: 0 0 40%">
        <el-input v-model="form.dataBookUrl" disabled type="textarea" />
      </el-form-item>
    </el-form>
    <!-- 此处省略部分代码 -->
  </el-dialog>
</template>
  1. 图文片段 image

操作按钮样式

注意: 操作按钮中的 button 大于2个 以上需要使用 OperateButtonGroup 组件 进行包裹

  • button 大于2个 标准写法与实际样式
  1. 代码片段
<template>
  <vxe-table
      ref="table"
      emptyRender
      :data="tableData"
      :max-height="getHeight()"
      @checkbox-change="select"
      @checkbox-all="handleSelectionChange"
    >
    <vxe-table-column title="操作" width="100px" align="center">
      <!-- 省略部分代码 -->
      <template #default="{ row }">
        <OperateButtonGroup>
          <el-button type="primary" @click="handleEdit(row)">编辑</el-button>
          <el-button type="primary" @click="handleRecordsOpen(row)">记录</el-button>
          <el-button size="mini" type="primary" @click="handleDetailsOpen(row)">详情</el-button>
          <el-button type="danger" v-auth="'modelbase_remove'" @click="handleDelete(row)">删除</el-button>
        </OperateButtonGroup>
      </template>
    </vxe-table-column>
  </vxe-table>
</template>
  1. 图文片段 image
  • button 小于等于2个 标准写法与实际样式
  1. 代码片段
<template>
  <vxe-table>
    <vxe-table-column title="操作" width="100px" align="center">
      <!-- 省略部分代码 -->
      <template #default="{ row }">
        <el-button type="primary" v-auth="'brandnormal_edit'" @click="handleClickEdit(row)">编辑</el-button>
        <el-button type="danger" v-auth="'brandnormal_remove'" @click="handleDelete(row)">删除</el-button>
      </template>
    </vxe-table-column>
  </vxe-table>
</template>
  1. 图文片段 image

组件属性

注意: 在 element-ui 源代码中修改了size属性为mini, vxe-table 中的在全局配置文件中设置了size属性为mini

  • element-ui 和 vxe-table 中的 所有组件不应该有 size="mini"
<template>
  <!-- 错误示例  -->
  <el-input size="mini" v-model="infoForm.bankCharge" />
  <!-- 正确示例  -->
  <el-input v-model="infoForm.bankCharge" />
</template>
 

按钮权限控制

  • 注意 如果button中有v-if 一定要写注释而且注释方式要同下面代码注释一致
  • v-auth="'权限'"
<template>
  <!--  审批状态 => 审核通过  -->
  <el-button type="primary" @click="handlePrint" v-auth="'purchase_order_print'" v-if="obj.approveStatus === 3">
    打印
  </el-button>
  <!--  订单状态 => 未完成 -->
  <!--  审批状态 => 待提交审核,审核驳回  -->
  <el-button
    type="primary"
    @click="handleDoWaste"
    v-auth="'purchase_order_cancel_order'"
    v-if="obj.status === 0 && (obj.approveStatus === 0 || obj.approveStatus === 2)"
  >
    订单作废
  </el-button>
  <!--  订单状态 => 未完成 -->
  <!--  审批状态 => 待提交审核,审核驳回  -->
  <el-button size="mini" type="primary" @click="handleEdit" v-auth="'purchase_order_edit'">编辑</el-button>
</template>

el-select

只要在 el-option 增加 item属性 就能在 el-select的@change 方法的回调函数中从第二个参数获取到

<template>
  <el-select
    @change="handleSupplierChange"
  >
    <el-option
      :item="item"
      :key="item.id"
      :value="item.id"
      :label="item.supplierName"
      v-for="item in supplierOptions"
    />
  </el-select>
</template>
 
<script>
export default {
  methods: {
    // id 是 item.id, item 当前数据对象
    handleSupplierChange(id, item) {}
  }
}
</script>

对象深拷贝

  • 禁止使用 JSON.parse(JSON.stringify(xxx));
  • 使用 cloneDeep 方法 使用方法 'lodash/cloneDeep'
import { cloneDeep } from  'lodash';
const b = { b1: 10, a1: 20 };
const a = cloneDeep(b);
console.log(a === b); // false
console.log(a !== b); // true

前端本地字典

  • 本地字典统一放在 /config/data-keys.js 文件中
  • 需要在 index.d.ts 加上类型定义
  • 统一使用 $getDataKeys 方法获取 $getDataKeys是定义在Vue原型上的方法
<!-- 使用的地方  src\views\purchase\order\details.vue -->
<el-form-item label="结算期">
  <el-input v-model="$getDataKeys('settlementPeriodOptions')[infoForm.settlementPeriod]" disabled />
</el-form-item>
// 字典存放的文件位置 src\config\data-keys.js
export const settlementPeriodOptions = {
  1: '0天',
  2: '5天',
  3: '7天',
  4: '10天',
  5: '15天',
  6: '30天',
  7: '45天',
  8: '60天',
  9: '90天',
  10: '150天',
};
// 类型定义的位置 src\index.d.ts
type DataKeysType = {
  settlementPeriodOptions: {
    '1': string
    '2': string
    '3': string
    '4': string
    '5': string
    '6': string
    '7': string
    '8': string
    '9': string
    '10': string
  }
}
 

关于接口调用

注意 不要在 非 /api/目录下 引用 import request from '@/util/http'; 不要写重复接口, 定义接口前 应当全文搜索 url 确保没有重复接口

  1. 关于请求方法的书写
// good
export const getCaptcha = () =>
  request({
    url: '/api/blade-auth/captcha',
    method: 'get',
  });
 
// bad
// 不要在请求体中对数据做更改
export const getPurchaseApplyForList = async (params) => {
  const { data } = await request({
    url: '/api/oksht-drp/purchaserequestorder/list/purchase',
    method: 'get',
    params,
  });
  return data.data;
};
 
// bad
// 不要在请求体中做逻辑处理
export const dispatchOutDoWaste = async (data) =>
  new Promise((resolve) => {
    request({
      url: '/api/oksht-drp/purchaserequestorder/invalid',
      method: 'post',
      data,
    })
      .then(() => {
        resolve(true);
      })
      .catch(() => {
        resolve(false);
      });
  });
  1. 对接口返回做异常处理
// good
// 保证在接口异常的情况下 js能正常往下执行
const res = await getMenu(id).catch(() => null);
// 通过可选链的方式 处理数据异常
if (res?.data?.success) {
    this.form = res.data.data;
    this.parentName = res.data.data?.parentName;
}
 
// bad
const res = await getMenu(id);
if (res) {
    this.form = res.data.data;
    this.parentName = res.data.data?.parentName;
}

导入引用

1、第三方库排第一位 绝对路径排第二位 相对路径 排第三位

// good
import day from 'dayjs';                                 // 第三方库
import { listMixin } from '@/common/mixin/list';         // 绝对路径
import QueueTable from './component/queueTable';         // 相对路径
 
 
// bad
import QueueTable from './component/queueTable';         // 相对路径
import day from 'dayjs';                                 // 第三方库
import { listMixin } from '@/common/mixin/list';         // 绝对路径

样式

注意 sht-info-form 会被移除, 请使用 sht-info-form-x (x 代表 1-5的其中一个数字) 代码示例会省略 3、4列排版

  1. sht-info-form-1 ( 1列排版 )
<template>
    <el-form
      class="sht-info-form-1"
      :label-width="$getConfig('EL_FORM_LABEL_WIDTH')"
    >
     <!-- 伪代码 -->
     <el-form-item />
     <el-form-item />
     <el-form-item />
     <el-form-item />
    </el-form>
</template>
image
  1. sht-info-form-2 ( 2列排版 )
<template>
    <el-form
      class="sht-info-form-2"
      :label-width="$getConfig('EL_FORM_LABEL_WIDTH')"
    >
     <!-- 伪代码 -->
     <el-form-item />
     <el-form-item />
     <el-form-item />
     <el-form-item />
     <!-- 独占一行 -->
     <el-form-item style="flex: 0 0 100%" />
     <!-- 独占一行 -->
     <el-form-item style="flex: 0 0 100%" />
    </el-form>
</template>
image
  1. sht-info-form-5 ( 5列排版 )
<template>
    <el-form
      class="sht-info-form-5"
      :label-width="$getConfig('EL_FORM_LABEL_WIDTH')"
    >
     <!-- 伪代码 -->
     <el-form-item />
     <el-form-item />
     <el-form-item />
     <el-form-item />
     <!-- 独占两列 -->
     <el-form-item style="flex: 0 0 40%" />
    </el-form>
</template>
image

el-form的label宽度

注意 如果label的宽度是120 使用 $getConfig('EL_FORM_LABEL_WIDTH') 特殊情况可以不使用

<template>
  <el-form :label-width="$getConfig('EL_FORM_LABEL_WIDTH')">
    <el-form-item />
  </el-form>
</template>

样式编写

  1. 颜色、字体大小 必须使用使用变量
  2. 类名穿透使用 ::v-deep
/* Color 颜色      ----------------------- */
$--color-primary: #BF2722;    // 主色
$--color-white: #FFFFFF;
$--color-black: #000000;
 
$--color-E7E7E7: #E7E7E7;
$--color-DFDFDF: #DFDFDF;
$--color-CCCCCC: #CCCCCC;
$--color-F0F0F0: #F0F0F0;
$--color-FFDEDD: #FFDEDD;
 
$--color-success: #21CA3D;   // 成功色
$--color-warning: #FF8C19;   // 警告色
$--color-danger: #FF5151;    // 失败色
$--color-info: #0090FF;      // 提醒色
 
/* Typography 字体  ----------------------- */
$--size-large: 30px;
$--font-size-extra-large: 20px;
$--font-size-large: 18px;
$--font-size-medium: 16px;
$--font-size-base: 14px;
$--font-size-small: 13px;
$--font-size-extra-small: 12px;
$--font-weight-primary: 500;
$--font-weight-secondary: 100;

书写参考

@import '~@/assets/styles/var.scss';
 
.download-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
 
  .text {
    margin-right: 20px;
    padding: 6px;
 
    color: $--color-info;
    font-size: $--font-size-base;
  }
 
  .link {
    font-size: $--font-size-base;
  }
}

错误示例

<!-- 严禁 字体、颜色相关属性 在标签中直接使用 -->
<!-- 在标签中不要书写过多的样式  > 2组样式属性 应当使用 class -->
<template>
  <p style="font-size: 12px; color: red">{{ v.createTime }}</p>
</template>

关于路由配置

  1. 正常情况 文件路径 src/views/purchase/order/index.vue 那路由path就是 /purchase/order/index
  2. 为了减少path长度 在配置菜单时按照下面图片方式进行配置 /purchase/order 系统会自动索引到 src/views/purchase/order/index.vue 文件
  3. 系统在配置后缀选择是的情况 会在查找文件时会把 路由path 与 index.vue 拼接
image