---
description: module | plugins 模块、插件
globs: 
---
# 模块/插件开发

## 目录结构

在 `src/modules` 或 `src/plugins` 下添加一个目录 `demo`：

```js
demo
    ├──pages // 页面路由
    ├──views // 视图路由
    ├──hooks // 常用函数
    ├──components // 常用组件
    ├──directives // 指令
    ├──static // 静态文件目录
    ├──store // 状态管理
    ├──... // 其他自定义文件
    ├──config.ts // 配置文件
    └──index.ts // 入口文件
```

::: warning
约定的目录名称不可修改，但可自行添加或者删除。
:::

## pages、views

1. 页面参与权限控制，所以不主动注册目录下的路由，通过 `菜单列表` 中配置注册。或者在 `config.ts` 中手动配置：

```js
import { type ModuleConfig } from "/@/cool";

export default (): ModuleConfig => {
  return {
    views: [
      {
        path: "/demo",
        meta: {
          label: "测试",
        },
        component: () => import("./views/demo.vue"),
      },
    ],
    pages: [
      {
        path: "/demo2",
        meta: {
          label: "测试",
        },
        component: () => import("./pages/demo.vue"),
      },
    ],
  };
};
```

2. 使页面参与路由缓存，配置 `name` 参数

:::warning

`path` 与 `name` 的匹配规则：

- /demo/t1 = demo-t1
- /demo/t1-det = demo-t1-det

:::

方式 1：

```html
<script lang="ts" setup>
  defineOptions({
    name: "demo",
  });
</script>
```

方式 2：

```html
<script lang="ts">
  export default defineComponent({
    name: "demo",
  });
</script>
```

## components

目录下的组件，全局注册配置方法如下：

```js
import { ModuleConfig } from "/@/cool";

export default (): ModuleConfig => {
  return {
    components: [
      import("./components/demo.vue"),
      import("./components/demo1.vue"),
    ],
  };
};
```

## directives

`directives` 会以目录下的文件名分别注册指令

```ts
// demo/directives/test.ts
export default {
	created(el, binding) {},
	mounted() {},
	...
};
```

使用

```html
<div v-test></div>
```

## store

使用 `pinia` 的推荐写法：

```ts
import { defineStore } from "pinia";
import { ref } from "vue";

export const useTestStore = defineStore("test", function () {
  const count = ref(0);

  function add() {
    count.value += 1;
  }

  return {
    count,
    add,
  };
});
```

使用

```ts
import { useTestStore } from "/$/demo/store";

const test = useTestStore();

test.add();

console.log(test.count); // 1
```

::: tip
参考 `base` 模块下 `store` 的导出方式
:::

## config.ts

模块的配置，程序运行时会读取该文件。

- 全局组件、路由的导入

- 事件钩子

输入 `module-config` 关键字，`vscode` 中会自动生成：

```ts
import { ModuleConfig } from "/@/cool";
import { Vue } from "vue";

export default (): ModuleConfig => {
  return {
    // 是否启用
    enable: true,

    // 插件名称
    label: "插件名称",

    // 插件描述
    description: "插件描述",

    // 作者
    author: "作者",
    version: "1.0.0",
    updateTime: "2024-02-02",
    logo: "",

    // 忽略
    ignore: {
      // 忽略进度条的请求
      NProgress: [
        "/base/open/eps",
        "/base/comm/person",
        "/base/comm/permmenu",
        "/base/comm/upload",
        "/base/comm/uploadMode",
      ],

      // 忽略 token 的路由
      token: ["/login", "/401", "/403", "/404", "/500", "/502"],
    },

    // 排序
    order: 0,

    // 配置参数
    options: {
      name: "神仙",
    },

    // 示例页面
    demo: [
      {
        name: "基础用法",
        component: () => import("..."),
      },
    ],

    // 注册全局组件
    components: [],

    // 视图路由
    views: [],

    // 页面路由
    pages: [],

    // 顶部工具栏
    toolbar: {
      order: 1,
      pc: true, // 是否在 pc 端显示
      h5: true, // 是否在 h5 端显示
      component: import("./components/index.vue"),
    },

    // 注入全局组件
    index: {
      component: import("./components/index.vue"),
    },

    // 安装时触发
    install(app: Vue) {},

    // 加载时触发
    onLoad(events) {},
  };
};
```

- order 模块加载顺序，值越大越先

- options 提供给外部使用的参数配置：

```ts
import { ModuleConfig } from "/@/cool";

export default (): ModuleConfig => {
  return {
    options: {
      // 尺寸
      size: 120,
      // 显示文案
      text: "选择文件",
      // 限制
      limit: {
        // 上传最大数量
        upload: 9,
        // 文件空间选择数
        select: 9,
        // 上传大小限制
        size: 100,
      },
    },
  };
};
```

获取方式：

```ts
import { module } from "/@/cool";

const config = module.config("模块名");
```

- components 提供全局的组件：

```ts
import type { ModuleConfig } from "/@/cool";

export default (): ModuleConfig => {
  return {
    components: [import("./components/test.vue")],
  };
};
```

批量导入可以使用 [import.meta.glob](mdc:https:/vitejs.dev/guide/features.html#glob-import) 方法：

```ts
import { ModuleConfig } from "/@/cool";

export default (): ModuleConfig => {
  return {
    components: Object.values(import.meta.glob("./components/**/*")),
  };
};
```

- views 全局注册的视图路由，存放在 `/` 中的子路由 `children`：

```ts
import { ModuleConfig } from "/@/cool";

export default (): ModuleConfig => {
  return {
    views: [
      {
        path: "/test",
        meta: {
          label: "测试中心",
        },
        component: () => import("./views/test.vue"),
      },
    ],
  };
};
```

- pages 全局注册的页面路由：

```ts
import { ModuleConfig } from "/@/cool";

export default (): ModuleConfig => {
  return {
    pages: [
      {
        path: "/test",
        meta: {
          label: "测试中心",
        },
        component: () => import("./views/test.vue"),
      },
    ],
  };
};
```

- install 模块安装时触发。用于预先处理：

```ts
import { ModuleConfig } from "/@/cool";
import { Vue } from "vue";

export default (): ModuleConfig => {
  return {
    install(app: Vue) {
      // 注册组件
      app.component("test", Test);

      // 注册指令
      app.directive("focus", {
        created(el, bind) {},
      });
    },
  };
};
```

- onLoad 模块安装时触发，预先加载数据，如菜单配置、用户信息：

  1.  使用 `await` 等待加载完成后往下执行

  2.  可往下模块导出某个方法和变量，如 `hasToken` 验证是否有登陆

```ts
import { ModuleConfig } from "/@/cool";
import { Vue } from "vue";

export default (): ModuleConfig => {
  return {
    async onLoad() {
      const { user, menu } = useStore();

      if (user.token) {
        // 获取用户信息
        user.get();
        // 获取菜单权限
        await menu.get();
      }

      return {
        async hasToken(cb: () => Promise<any> | void) {
          if (user.token) {
            if (cb) await cb();
          }
        },
      };
    },
  };
};
```

其他模块中接收 `hasToken` 方法：

```ts
import { ModuleConfig } from "/@/cool";
import { useDict } from "./index";

export default (): ModuleConfig => {
  return {
    onLoad({ hasToken }) {
      const { dict } = useDict();

      hasToken(() => {
        dict.refresh();
      });
    },
  };
};
```

## index.ts

该模块需要对外开放的变量及方法，方便于别人直接使用：

```ts
// modules/test/index.ts
import { useStore } from "./store";

export function useTest() {
  return {
    // 导出 pinia
    ...useStore(),

    // 自定义方法
    test() {},

    // 自定义变量
    data: {
      description: "数据描述",
    },
  };
}
```

导出命名规则 `useBase` `useDemo` `useDict` use + 模块名

使用：

```ts
import { useTest } from "/$/test";

const { data, test } = useTest();
```
