{"id":20563470,"url":"https://github.com/thundersdata-frontend/spa-template","last_synced_at":"2025-10-27T08:06:54.532Z","repository":{"id":35325474,"uuid":"217045984","full_name":"thundersdata-frontend/spa-template","owner":"thundersdata-frontend","description":"雷数前端SPA模板","archived":false,"fork":false,"pushed_at":"2023-01-27T04:56:48.000Z","size":5156,"stargazers_count":12,"open_issues_count":20,"forks_count":12,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T03:41:21.441Z","etag":null,"topics":["hooks","spa","typescript","umijs"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thundersdata-frontend.png","metadata":{"files":{"readme":"readMe.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-23T11:58:00.000Z","updated_at":"2023-09-27T03:01:06.000Z","dependencies_parsed_at":"2023-02-15T05:46:53.838Z","dependency_job_id":null,"html_url":"https://github.com/thundersdata-frontend/spa-template","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thundersdata-frontend%2Fspa-template","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thundersdata-frontend%2Fspa-template/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thundersdata-frontend%2Fspa-template/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thundersdata-frontend%2Fspa-template/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thundersdata-frontend","download_url":"https://codeload.github.com/thundersdata-frontend/spa-template/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248898684,"owners_count":21179822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hooks","spa","typescript","umijs"],"created_at":"2024-11-16T04:19:01.276Z","updated_at":"2025-10-27T08:06:49.500Z","avatar_url":"https://github.com/thundersdata-frontend.png","language":"TypeScript","readme":"## 使用 plugin-initial-state、plugin-model、plugin-access 来配置登录用户路由、菜单和权限\n\n### 1. plugin-initial-state 使用\n\n在`src/app.ts`中，导出`getInitialState`方法，需要配合`plugin-model`一起使用。\n\n```tsx\n// app.ts\nexport async function getInitialState() {\n  const data = await fetchXXX(); // 请求后端接口，比如拿到菜单、路由、权限、个人信息等数据\n  return data;\n}\n\n// 其他页面根据useModel('@@initialState')拿到初始值\nexport default () =\u003e {\n  const { initialState, loading, error, refresh, setInitialState } = useModel('@@initialState');\n\n  // 参数释义参见：https://umijs.org/zh-CN/plugins/plugin-initial-state#initialstate\n};\n```\n\n### 2. plugin-access 使用\n\n在`src/access.ts`里面默认导出一个方法，该方法返回一个对象，对象的每一个值就对应一条权限。\n\n```tsx\n// src/access.ts\nexport default initialState =\u003e {\n  // initialState是通过上面的`plugin-initial-state`返回的数据\n  const { userId, role } = initialState;\n\n  return {\n    canRead: true,\n    canUpdate: role === 'admin',\n    canDelete: foo =\u003e foo.ownerId === userId,\n    // ...\n  };\n};\n// 在其他页面，可以通过`umi`内置的`userAccess`来获取权限相关信息\nexport default () =\u003e {\n  const access = useAccess();\n\n  if (access.canRead) {\n    // do something\n  }\n\n  return \u003c\u003exxx\u003c/\u003e;\n};\n```\n\n同时，`umi`还给我们暴露了一个`\u003cAccess\u003e`组件，我们可以用它来对应用进行细粒度的权限控制，比如可以精确地控制某个按钮、某个菜单、某个超链接是否显示。 `\u003cAccess\u003e`组件的属性有：\n\n- `accessible`: 是否有权限，通常通过`useAccess`获取后传进来\n- `fallback`: 无权限时的显示，默认无权限不显示任何内容\n- `children`: 有权限时显示的内容\n\n```tsx\nimport React from 'react';\nimport { useAccess, Access } from 'umi';\n\nfunction IndexPage() {\n  const access = useAccess();\n\n  if (access.canRead) {\n    // Do something\n  }\n\n  return (\n    \u003cdiv\u003e\n      \u003cAccess accessible={access.canRead} fallback={\u003cdiv\u003eCannot read content\u003c/div\u003e}\u003e\n        Read Content\n      \u003c/Access\u003e\n      \u003cAccess accessible={access.canUpdate} fallback={\u003cdiv\u003eCannot update content\u003c/div\u003e}\u003e\n        Update Content\n      \u003c/Access\u003e\n      \u003cAccess accessible={access.canDelete} fallback={\u003cdiv\u003eCannot delete content\u003c/div\u003e}\u003e\n        Delete Content\n      \u003c/Access\u003e\n    \u003c/div\u003e\n  );\n}\n\nreturn IndexPage;\n```\n\n## pont 生成工具与 umi 的 useRequest 结合使用\n\n### pont 配置\n\n1. 在 pont-config.json 文件中配置 origins，一个 swagger 地址对应一个 name 和 originUrl，name 的命名没有约束，但是会在接口调用的时候用到，例如 authorization（权限中心），originUrl 就是 api-docs 的地址，例如'http://xxxx/v2/api-docs'，可以支持配置多个swagger地址，但是注意：除了origins之外的配置项不要改动\n2. server.config.js 文件是用于从 pont-config.json 文件中读取后端接口地址的，不需要进行改动（如果随意的更改可能会引起调用接口的时候 nameSpace 对应不上）\n3. pontTemplate.ts 文件是定义生成代码的模板文件，不需要进行改动\n4. 在项目的入口文件，即 src 目录下的 global.ts 文件中加入一句'import '@/services';'，把 API 引入进来\n\n### pont 使用\n\n1. 在 vscode 中安装 vscode 插件 td-pont，使用方法参考'https://github.com/nefe/vscode-pont'\n2. 当 vscode-pont 检测到项目中含有合法的 pont-config.json 之后，插件会马上启动生成 services 文件夹\n3. 如果后端接口发生了更新，那么需要手动的点击 VS code 左下方的 sync 按钮，这样才会去比较线上和线下的差异实现和服务端同步变更，但是这个变更是存在于内存中的，all/mod/bo 都是把对应内容更新到 api.lock 中，generate 是根据 lock 生成最后的代码\n4. 当重新打开项目时，会自动调用一次 sync，获取和服务端的差异\n5. 目前 API 已经配置为全局变量，当需要调用接口时，我们不需要再进行 import 操作，只需要 API.[nameSpace].[mod].[方法的文件名].fetch()，nameSpace 即在 pont-config.json 文件中配置的 origins 的 name，mod 即是 module，例如：API.authorization.role.resourceSave.fetch()\n6. 更多细节：'https://github.com/alibaba/pont'\n\n### pont 的 mock 功能\n\n我们自己基于 pont 的代码进行了改动，实现了多数据源 mock 以及生成的 mock 文件与 umi 的 request 的 mock 功能相结合，具体使用方式如下：\n\n1. 卸载 VS code 中的 pont 插件，并搜索 td-pont，进行安装，使用方式相同\n2. 在 pont-config.json 文件中的 mocks 字段中配置 containDataSources，格式为 string[]，注意：containDataSources 中的字段名必须是 origins 中的 name 字段，否则识别不到哪些数据源要走 mock 功能\n3. homepage 中有调用的例子，不管是否走 mock 功能，调用方式不变，都为 API.xxx.xxx.xxx.fetch 的形式，就是说只有在 containDataSources 中配置的数据源名称才是走 mock，否则就是正常接口请求\n\n### pont 最佳实践\n\n基于我们自定义的 pontTemplate，pont 已经帮我们生成了我们需要的 TypeScript 类型声明文件，以及对应的调用后端接口的胶水代码，同时也为我们生成好了初始值。那么我们使用 pont 的最佳实践应该是什么样的呢？我在这里大致总结一下：\n\n1. 不要自己定义初始值，直接使用 pont 生成的 init 值作为 useState 或者 store 里面的初始值。例如：\n\n```typescript\nconst [detail, setDetail] = useState\u003cdefs.gazelle.CompanyFinancialIndicatorDTO\u003e(\n  API.gazelle.companyFinancialIndicator.getById.init,\n);\n```\n\n2. 与 umi 的 useRequest 结合使用\n\n我们自定义生成的请求方法的格式如下：当接口请求的 success 为 false 时，我们会把错误 throw 出去，在 useRequest 的 onError 中进行处理，此时返回的 data 是接口数据的默认值\n\n```typescript\nexport async function fetch(params = {}) {\n  const result = await request.get(backEndUrl + '/interview/getInterviewerDetail', {\n    headers: {\n      'Content-Type': 'application/json',\n    },\n    params,\n  });\n  if (!result.success) throw new Error(result.message);\n  return result.data || new defs.recruitment.HrmInterviewDTO();\n}\n```\n\n```typescript\nuseRequest(() =\u003e API.recruitment.dict.getAllDict.fetch(), {\n  onSuccess: data =\u003e {\n    setEnums(data);\n  },\n  onError: error =\u003e {\n    console.log(error.message);\n  },\n});\n```\n\n3. 如果前端需要的数据格式和后端返回的格式有区别（最常见的就是日期和文件），那么你需要自己构造一个类型来对这些特殊属性进行处理。这个时候最好是使用 typescript 提供的`Utility Types(工具类型)`来尽可能复用已有的类型。例如：\n\n```typescript\nexport type PolicyDetailDTO = Pick\u003c\n  defs.gazelle.PolicyDTO,\n  | 'policyId'\n  | 'policyType'\n  | 'title'\n  | 'indexCode'\n  | 'issueNumber'\n  | 'issueOrg'\n  | 'subjectType'\n  | 'subjectWord'\n  | 'tenantCode'\n\u003e \u0026 {\n  issueDate: moment.Moment;\n  finalDate: moment.Moment;\n  attachment?: UploadFile[];\n};\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthundersdata-frontend%2Fspa-template","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthundersdata-frontend%2Fspa-template","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthundersdata-frontend%2Fspa-template/lists"}