诗号:六道同坠,魔劫万千,引渡如来。

/img/bdx/yiyeshu-001.jpg

该文记录着实际开发过程中使用到的 vue3 及其生态中相关的框架或库遇到的各种问题,和 其他收集。

vue-next

template ref 使用

runtime-core ref 属性解析&分析

模板: attrs 形式使用 ref 将会绑定 elForm 。

1
2
3
<templet>
  <el-form ref="elForm"/>
</templet>

setup:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
defineComponent({
  setup(props, ctx) {
    // 声明 element ref 变量
    const elForm = ref(null)

    // 使用
    onMounted(() => {
      elForm.value.validate(/* bala bala... */)
    })

    return { elForm }
  }
})

注意点:

  1. ref="elForm" 不能使用 v-bind:ref 只能是 attrs

  2. 使用时 elForm.value 因为它是个 Ref 类型

vuex

vuejs/vuex at 4.0

Installation | Vuex

使用

creation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { createStore } from "vuex";
import permission from "./modules/permission";

const store = createStore({
  modules: {
    permission,
  },
});

export default store;

模块: 权限菜单 (permission)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// './modules/permission'
// State
const state: PermissionState = {
  asyncMenus: [] as MenuItem[],
  minorRoutes: [] as RouteConfigRecord[],
  get mountedRoutes(): RouteConfigRecord[] {
    // TODO 根据当前用户从服务器获取权限菜单,跟新到 asyncMenus 中
    // 根据过滤后的结果进行渲染
    return toBeMounted;
  },
};

// mutation 不可直接调用的,必须通过 commit(SET_MENUS) 来调用
// 更新状态
const mutations = {
  [SET_MENUS](state: PermissionState, menus: MenuItem[]): void {
    state.asyncMenus = menus;
  },

  [SET_MINOR_ROUTES](state: PermissionState, routes: RouteConfigRecord[]) {
    state.minorRoutes = routes;
  },
};

// action 组件中可通过 store.dispatch('permission/getMenus') 来触发
// 对应的 action, 注意这里如果是模块化需要加上 'permission/'
const actions = {
  async getMenus({ commit }: ActionContext) {
    if (state.asyncMenus.length) {
      return state.asyncMenus;
    }

    const res = await getMenuList();
    commit(SET_MENUS, res.data);
    return res.data;
  },
};

// 最后将结果导出,形成一个 store 模块 permission
export default {
  state,
  mutations,
  actions,
  namespaced: true,
};

疑难杂症

state: get mountedRoutes 没有触发?

问题缘由:

  1. getMenus 从服务端请求权控菜单

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    const actions: ActionTree<PermissionState, RootState> = {
      async getMenus({ commit, state }: ActionContext<PermissionState, RootState>) {
        if (state.asyncMenus.length) {
          return state.asyncMenus;
        }
    
        const res = await getMenuList();
        commit(SET_MENUS, res.data);
        console.log(state.mountedRoutes, "xxx");
        return res.data;
      },
    };
    
  2. state: get mountedRoutes()

    然后,希望在之后取 mountedRoutes 的时候能从 asyncMenus 中过滤出特定权限的路由, 但是貌似这个 getter 怎么都没执行,因为里面的 console.log 并没有打印出来。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    const state = {
      get mountedRoutes(): RouteConfigRecord[] {
        console.log(this.asyncMenus, asyncRoutes, "0000");
        const toBeMounted = filterRoutesByMenu(asyncRoutes, this.asyncMenus);
    
        toBeMounted.forEach((r: RouteConfigRecord) => {
          if (r.children && r.children.length && r.meta!.showInMenu) {
            const menuRoutes = r.children.filter((child) => child.meta!.showInMenu);
            if (menuRoutes.length) {
              r.redirect = menuRoutes[0].path;
            }
          }
        });
        return toBeMounted;
      },
    };
    

❗ 在 vue3 中响应式通过 Proxy + Reflect 来实现的,去 get mountedRoutes 最后执行的是 Reflect.get(state, 'mountedRoutes', ...) 这底层估计不会去访问 getter 访问器, 才导致不生效。

解决方案(折中方案):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const state: PermissionState = {
  asyncMenus: [] as MenuItem[],
  minorRoutes: [] as RouteConfigRecord[],
  // ADD1 不使用 getter
  mountedRoutes: [] as RouteConfigRecord[]
}

const mutations: MutationTree<PermissionState> = {
  // ADD2 增加一个 mutation 去更新路由列表
  [SET_MOUNTED_ROUTES](state: PermissionState) {
    const menus: MenuItem[] = state.asyncMenus
    const toBeMounted = filterRoutesByMenu(asyncRoutes, menus)
    toBeMounted.forEach((r: RouteConfigRecord) => {
      if (r.children?.length && r.meta?.showInMenu) {
        const menuRoutes = r.children.filter((child) => child.meta!.showInMenu)
        if (menuRoutes.length) {
          r.redirect = menuRoutes[0].path
        }
      }
    })
    state.mountedRoutes = toBeMounted
  }
}

const actions: ActionTree<PermissionState, RootState> = {
  async getMenus({ commit, state }: ActionContext<PermissionState, RootState>) {
    if (state.asyncMenus.length) {
      return state.asyncMenus
    }

    const res = await getMenuList()
    commit(SET_MENUS, res.data)
    // ADD3 这里当更新菜单的时候同步过滤出有效路由
    commit(SET_MOUNTED_ROUTES)
    return res.data
  },
}

结果:

1
2
3
4
[[Target]]: Object
asyncMenus: (4) [{…}, {…}, {…}, {…}]
minorRoutes: []
mountedRoutes: (4) [{…}, {…}, {…}, {…}]

vue-i18n-next

intlify/vue-i18n-next: Vue I18n for Vue 3

Docs: Installation | Vue I18n

Usage: Vue i18n: Building a multi-lingual app - Lokalise Blog

  1. vue-i18n esm-bundler 警告

    You are running the esm-bundler build of vue-i18n. It is recommended to configure your bundler to explicitly replace feature flag globals with boolean literals to get proper tree-shaking in the final bundle.

    fix:解决vue-i18n在开发环境下的告警 · xiaoxian521/vue-pure-admin@f2db3ac

    1
    2
    3
    4
    
    alias: {
      "@": path.resolve(__dirname, "./src"),
    +  "vue-i18n": "vue-i18n/dist/vue-i18n.cjs.js",
    };
    

element-plus

el-upload

使用(不自动上传):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export function GlUpload(props, { slots }) {
  return h(
    E.ElUpload,
    _.extend(
      {
        class: "upload",
        action: "#",
        "list-type": "picture-card",
        "auto-upload": false,
        onChange(file, fileList) {
          console.log(file, fileList, "xx");
        },
      },
      props
    ),
    {
      default: () =>
        h("i", {
          class: "el-icon-plus",
        }),
      ...slots,
    }
  );
}

TIP

不使用自动上传功能的时候,如果要向服务器发送数据,需要自己将数据变成表单 (FormData)数据,有点绕。

这里有个插件可以使用,将 json 转成 FormData: hyperatom/json-form-data: A library to convert javascript objects into form data.

看了下源码(140l) 实现原理中就是深度遍历 json 数据,需要注意的是对象类型的转换。

1
2
3
4
5
6
7
8
9
如:{a:{b:1},{c:{d:2}}}
-> a[b]: 1
-> a[c][d]:2

如: {a: [1,2,3,4]}
-> a[0]:1
-> a[1]:2
-> a[2]:3
-> a[3]:4

实例:

pageId: 17
name: test5
status: 1
isDefault: 0
startTime: 2021-11-10 10:10
endTime: 2021-11-11 10:10
isPermanent: 0
targetType: 1
templateType: epg21
target[0]: 1001
target[1]: 1002
target[2]: 1003
target[3]: 1004
target[4]: 1005
target[5]: 1006
target[6]: 1007
target[7]: 1010
target[8]: 121
target[9]: 188
target[10]: 2001
target[11]: 2308
target[12]: 666
target[13]: 833
target[14]: 8513
hasBgPic: 1
hasBgMedia: 0
hasLogo: 0
hasSmallPic: 0
hasPicList: 1
hasWifi: 0
hasWeather: 0
hasWelcomeText: 1
welcomeText: []
IsPermanent: 0

即需要进行扁平化处理,将所有的嵌套转成路径方式 a[b][c][d][e]:1 等于是:

{ a: { b: { c: { d: { e:1 } } } } }