优雅,太优雅了(项目中代码质量和手撸业务)

2,758 阅读4分钟

代码质量

env配置文件

env文件后端需要进行相应的转换或者变动,以适应正式环境的部署

  1. url:有关跳转路径或者回调路径,都需要放入env中,因为开发环境和正式环境的路径可能不同,后端需要转成正式环境的路径

  2. 令牌和密钥:令牌和密钥是由后端生成的,保证请求是由正确的用户发送的,而不是其他无关用户。并且可能由于一些原因令牌和密钥发生了变化,后端可以直接修改。

  3. axios的baseUrl:在开发、测试和生产环境中,API 的基本路径可能会有所不同。

枚举和常量

枚举

当代码中存在常量列表:

const obj = {google:100,Apple:102,Facebook:103,email:107}

可以放在constants文件中转化成枚举:(注意:枚举名称第一个字符要大写

export enmu Obj {
    Google = 100,
    Apple = 102,
    Facebook = 103,
    Email = 107
}

另一个🌰子 img_v3_0261_fdff733c-701a-4462-941c-26eaa5f5ba0g.jpg

可以替换成(可真是太优雅了)

export enum AttachType {
  propertys = 1,
  weapons = 2,
}
export const AttachMentType = {
  [AttachType.propertys]: {
    key: AttachType.propertys,
    type: "道具",
    common: null,
    allList: null,
  },
  [AttachType.weapons]: {
    key: AttachType.weapons,
    type: "武器",
    common: null,
    allList: null,
  },
};
<div class="select-item-tab">
    <a-menu-item v-for="item in AttachMentList" :key="`${item.key}`">
      {{ item.type }}
    </a-menu-item>
</div>

常量

如果有些常量使用较多或者有意义的字符串或者数字,仍然抽离出来放入constants中(常量是全部大写)

export const MESSAGE = "创作不易,点个👍呗"
export const SECONDS = 1;
export const MINUTES = 60;
export const HOURS = 3600;

函数的抽离

依稀记得有人说过函数中的代码超过多少多少行就需要抽离出一个函数,以保证代码的可读性

一些功能功能相同的代码也需要抽离出来,否则太抽象了

比如以下代码,关于find(map,reduce之类的同理)

img_v3_025f_504d1f6c-8f3b-4c73-b221-7cb0990642eg.jpg

可以抽离成下述代码

const trackData = (key: string) => {
  return infoData.value.find((item: InfoItem) => item.key === key);
};

布尔值的返回

有些时候代码可能写的太快,返回的布尔值没经过思考,直到被提醒才发觉

比如以下代码(大意了,属实没注意到)

img_v3_0261_2539a32d-ca6a-49e9-a059-d6ccb5ac0bbg.jpg

可以直接写出

return isObjextInArray;

还有一些例子

return a==b

样式的放置

样式不要写在style中,就算再少也不行,不要偷懒,这样做的目的是为了维护性和可读性(代码更优雅)

img_v3_0261_37c7675d-a7c1-4b34-998d-40039fa3bfeg.jpg

lodash中判断为空的内置函数

对于 null、undefined、空字符串、空数组以及空对象,函数都返回 true;而对于非空对象 { name: ‘John’ } 和非空数组 [1, 2, 3],函数返回 false。

例如

// 判断是否处于搜索状态
if (itemName.value !== "") {
    isShow.value = true;
} else {
    isShow.value = false;
}

可以转换成

import { isEmpty } from "lodash-es";
isShow.value = !isEmpty(itemName.value);

let / const的使用

let声明变量,const声明常量

但ref声明的值是个变量,虽然知道和理解,但用起来谁记得啊(reactive同理)

img_v3_0261_fcce59ca-5853-4fa0-a8ee-998dca45cb6g.jpg
const itemName = ref(1)

解构赋值

常用于解构网络请求的值(即拦截),和props里的值

和上述一样,知道理解,用起来忘记

img_v3_0261_94319ba5-580d-4c53-886c-e67b804caf2g.jpg
watch(
  () => props.ui.items,
  (newItems) => {
    const { common, items, weapons } = newItems.value;
    AttachMentList[AttachType.propertys].common = common;
    AttachMentList[AttachType.propertys].allList = items;
    AttachMentList[AttachType.weapons].allList = weapons;
  },
  { immediate: true }
);

路由守卫时拿参数从location中拿

场景:由于弹窗的实现需要携带app_id的参数才能为弹窗,否者只是单纯的页面,在做路由守卫跳转到维护通知弹窗,需要拿到app_id

img_v3_0269_fb71268e-fa72-4734-896d-f2086b7cde0g.jpg 原来的代码

const loginGuard = (to, from, next) => {
 getUser()
     .catch(error => {
         if (error == MAINTAIN) {
             next({
              path: Routes.MAINTAIN,
              query:{ app_id: 861896 }
            });
         }
     }
}

优化后的代码

const loginGuard = (to, from, next) => {
 getUser()
     .catch(error => {
         if (error == MAINTAIN) {
            // 获取当前页面的 URL
            const currentURL = new URL(window.location.href);
            // 获取 app_id 参数的值
            const app_id = currentURL.searchParams.get("app_id");
            next({
              path: Routes.MAINTAIN,
              // 保证了如果没有app_id跳转时不会由app_id的小尾巴,它是干净的
              query: app_id ? { app_id } : undefined 
            });
            return;
         }
     }
}

手撸业务

1. ant-design-vue中input-number组件中@blur事件无法触发?只能手撸一个blur事件了

场景:对于后台管理的每条列表数据的某个数值进行修改,当失焦时会触发保存事件,而不是手动的保存,也就是进行产品优化。团队使用的组件库是ant-design-vue,但在input-number组件中添加@blur却发现该事件无法触发,只能根据鼠标点击的位置来判断是否需要失焦

<template>
    <st v-for="item in datas">
       <template #count="{ row }">
          <template v-if="row.editing">
            <input-number ref="inputNumberRef" v-model="row.count"/>
            <SaveOutlined class="ml-sm" @click="save(row)" /> // 保存图标
          </template>
          <span v-else>
            {{ row.count }}
            <EditOutlined class="ml-sm" @click="edit(row)" /> // 编辑图标
          </span>
        </template>
    </st>
<template>
 const inputNumberRef = ref(null);
  // 定义一个 ref 来保存当前编辑的行
 const editingRow:any = ref(null);
 
 const isInitialClick = ref(true);
const handleClickOutside = (event: any) => {
     if (isInitialClick.value) {
       isInitialClick.value = false;
       return;
     }
     【1if (inputNumberRef.value && !inputNumberRef.value.$el.contains(event.target)) {
       // 点击输入框之外的区域,触发失焦操作
       if (editingRow.value) {
         // console.log(editingRow.value); // 输出当前编辑的行
         save(editingRow.value);
         editingRow.editing = false;
       }
     }
};
onMounted(() => {
 // 在组件的生命周期内添加点击事件监听器
 document.addEventListener('click', handleClickOutside);
});

onBeforeUnmount(() => {
 // 在组件的生命周期内移除点击事件监听器
 document.removeEventListener('click', handleClickOutside);
});

【1】先判断能不能拿到输入框的实例,$el指的是input-number这个组件的dom元素,event.target是当你触发点击事件时点击的元素。

从而可以进行判断,当你点击页面中的某个dom元素时,就会触发handleClickOutside事件,如果你点击的dom元素不在input-number这个antd组件里,说明你点击了input-number之外的dom元素,则表明失焦了,触发相应事件;如果input-numbe组件dom元素包含你点击的dom元素,则没有失焦。

   const save = (row: IItem) => {
     if (row.count <= 0 || !row.name) {
       message.warn("请完善附件");
       return;
     }
     row.id = items.value!.items.find((i) => i.value === row.id)!.value;
     row.editing = false;
     datas.value = [...datas.value];
     changeValue();
   };
    const edit = (row: IItem) => {
      if (editingRow.value) {
        // 如果已经有正在编辑的行,将其设为非编辑状态
        editingRow.value.editing = false;
      }
      isInitialClick.value = true; // 重置为true,以防止编辑图标的第一次点击触发handleClickOutside
      row.editing = true;
      editingRow.value = row;
    };    

2. ant-design-vue中的级联展示无法自定义,只能结合下拉菜单手撸一个了

antd中的级联

image.png

手撸成图

image.png image.png

输入框部分,用于存放选择后的值和点击显示弹窗

// trigger触发下拉的行为,visible(v-model)控制弹窗是否显示,placement菜单弹出位置
<a-dropdown :trigger="['click']" v-model:visible="visible" placement="topLeft">
    <div @click="show">
      <DownOutlined />
      <div>
        <a-input
          v-model:value="itemName"
          @focus="show"
          @blur="hide"
        />
      </div>
      <span :class="{ editing-label: visible, hide-label: itemName }">{{ label }}</span>
    </div>
</a-dropdown>

大致逻辑:input被点击或者聚焦时弹框展示,失焦时弹窗关闭。并且当label有值且弹窗展示时,label值的颜色为灰色,而在输入框中输入了itemName值时,label的值不展示。

const itemName = ref("");
const label = ref("");
// 弹窗默认关闭
const visible = ref(false);
// 弹窗关闭
const hide = () => {
  visible.value = false;
};
// 弹窗展示
const show = () => {
  visible.value = true;
};
.editing-label {
  color: #bfbfbf;
}
.hide-label {
  opacity: 0;
}

弹窗部分

 <template #overlay>
      <div>
        <a-menu
          // 选中时a-menu-item的高亮数组key值默认选中道具
          v-model:selectedKeys="selectArray"
          @click="handleMenuClick"
          // 呈现左右弹性布局
          style="display:flex"
        >
          <div>
            <a-menu-item key="1"> 道具 </a-menu-item>
            <a-menu-item key="2"> 武器 </a-menu-item>
          </div>
          <div
            :class="{ tabhidden: true,tabshow: kCode == 1 }"
          >
              <div
                v-for="item in itemsList"
                @click="changeValue(item)"
              >
                <span>{{ item.label }}</span>
                <span>{{ item.value }}</span>
              </div>
          </div>
          <div :class="{ tabhidden: true,tabshow: kCode == 2 }">
              // 上同,除了v-for遍历的列表不同
          </div>
        </a-menu>
      </div>
    </template>

大致逻辑:主要是点击道具或者武器时,获取到它的key值,然后根据点击key值展示相应的数据

// 级联高亮控制
const selectArray = ref(["1"]);

// 判断点击的是道具还是武器,然后展示相应的数据列表
const handleMenuClick = (event) => {
  kCode.value = event.key;
};

// 选择完后再次选择时,搜索栏为空,弹出默认为道具栏
const changeValue = (item) => {
  label.value = item.label;
  itemName.value = "";
  kCode.value = 1;
  selectArray.value = ["1"];
  hide();
};
.tabhidden {
  display: none;
}
.tabshow {
  display: block;
  padding: 0;
  margin: 0;
  width: 380px;
  height: 290px;
}

3. 网络拦截

getUser
    .then((resp) => {
        console.log(resp);
    })
    .catch((error) => {
        ElMessage.error(error);
    })

.then里的resp是拦截中resolve的值,比如resolve(data)

.catch里的error是reject里的值,比如reject(msg)

const instance = Axios.create();

instance.interceptors.response.use(()=> {
    const { code = -1, data = {}, msg = "" } = response.data;
    if (handleUnlogin(code)) {
      return Promise.reject(msg);
    }
    if (code === HttpCode.SUSPENSION) {
      redirectSuspension();
      return Promise.reject(msg);
    }
    if (code === HttpCode.Ok) {
      return Promise.resolve(data);
    }
    return Promise.reject(msg);
})