第一章 本周导学


1-1 本章介绍

  • 组件平台
  • 组件预览
  • 组件README

第二章 组件平台架构设计和技术选型


2-1 组件平台架构设计

点击查看【processon】

2-2 组件平台技术选型和框架搭建

umi-component-test改项目代码提交至:https://github.com/liugezhou/umi-component-test

  1. 初始化UmiJS
    • mkdir umi-component-dev
    • cd umi-component-dev
    • yarn create @umijs/umi-app
    • yarn install
    • yarn start

2. 新建页面

npx umi g page detail

  1. 配置路由

    .umirc.ts新建页面路由 routes: [ { path:’/‘, component:’@/pages/index’ }, { path:’/nice’, component:’@/pages/detail’ }, ], yarn start启动项目后,访问 http://192.168.1.3:8000/nice即可看到最新的页面

4. 安装 Ant Design

yarn add antd

  1. 使用 ```html import { Button } from “antd”;

  1. <a name="mbeWU"></a>
  2. ###
  3. <a name="JWwJv"></a>
  4. ### 第三章 组件平台基础功能开发
  5. ---
  6. <a name="CmlG4"></a>
  7. #### <br />3-1 umi 项目全局入口文件+国际化开发
  8. > - 运行时配置:约定src/app.tsx为运行时配置,该配置文件下可以做一些全局性的操作
  9. > - 国际化:文档-插件-@umijs/plugin-locale
  10. > - **mkdir:**src/locales/zh-CN.ts | en-US.ts,配置WELCOME_TO_UMI_WORLD字段内容
  11. > - .umirc.ts中配置国际化[代码如下所示]
  12. > - src/app.tsx中引入国际化[代码如下所示]
  13. ```javascript
  14. // src/umirc.ts
  15. locale: {
  16. default: 'zh-CN',
  17. antd: false,
  18. title: false,
  19. baseNavigator: true,
  20. baseSeparator: '-',
  21. },
  22. // src/app.tsx
  23. import 'antd/dist/antd.css';
  24. import { getLocale,setLocale } from 'umi';
  25. import qs from 'qs';
  26. const { search } = window.location;
  27. const { locale = 'zh-CN' } = qs.parse(search, { ignoreQueryPrefix: true });
  28. setLocale(locale,false)
  29. // src/pages/index.tsx
  30. import { useIntl } from 'umi';
  31. export default function IndexPage() {
  32. const init = useIntl()
  33. const msg = init.formatMessage({
  34. id:'WELCOME_TO_UMI_WORLD',
  35. defaultMessage:'你好,牛逼的前端架构师!'
  36. },{
  37. name:'liugezhou'
  38. })
  39. console.log(msg)
  40. }

最后:yarn start启动文件,访问查看控制台:http://localhost:8000/?locale=en-US

3-2 组件平台功能展示 + 页头页脚开发

umijs支持layout引入,于是我们在开发页头页脚的时候,页面页头与页脚是在各个页面都存在的,于是我们可以将页面不同的地方以layout的形式注入

  1. // src/layouts/index.js
  2. import styles from './index.less'
  3. function BasicLayout(props){
  4. return (
  5. <div className={styles.normal}>
  6. <div className={styles.title}>{slogan}</div>
  7. {props.children}
  8. <div className={styles.footer}>{copyright}</div>
  9. </div>
  10. )
  11. }
  12. export default BasicLayout
  13. //src/layouts/index.less
  14. @import '~antd/dist/antd';
  15. .normal{
  16. text-align:center
  17. }
  18. .title{
  19. font-size:15px;
  20. font-weight: normal;
  21. background: @primary-color;
  22. padding: 10px 0;
  23. color: white;
  24. margin:0;
  25. }
  26. .footer {
  27. font-size: 12px;
  28. font-weight: normal;
  29. background: @primary-color;
  30. padding: 10px 0;
  31. color: white;
  32. margin: 0;
  33. }
  34. // .umijsrc.js
  35. import { defineConfig } from 'umi';
  36. export default defineConfig({
  37. nodeModulesTransform: {
  38. type: 'none',
  39. },
  40. routes: [
  41. {
  42. path: '/',
  43. component: '@/layouts/index',
  44. routes: [
  45. {
  46. path: '/',
  47. component: '@/pages/index'
  48. },
  49. {
  50. path: '/detail',
  51. component: '@/pages/detail'
  52. }
  53. ]
  54. }
  55. ],
  56. fastRefresh: {},
  57. locale: {
  58. default: 'zh-CN',
  59. antd: false,
  60. title: false,
  61. baseNavigator: true,
  62. baseSeparator: '-',
  63. }
  64. });

3-3 组件平台动态配置 API 开发

我们的页面与页脚内容需要从接口获取,因此,本节内容为在 cloudscope-cli-server服务中去编写接口代码。 本周相关代码提交至:cloudscope-cli/server/lesson33

3-3-1 添加路由

  1. // app/route.js
  2. 'use strict';
  3. module.exports = app => {
  4. const { router, controller } = app;
  5. router.resources('componentSite', '/api/v1/componentSite', controller.v1.componentSite);
  6. };

3-3-2 API编写

  1. // app/controller/v1/ComponentSite.js
  2. 'use strict'
  3. const Controller = require('egg').Controller;
  4. const mongo = require('../../utils/mongo')
  5. class ComponentSiteController extends Controller{
  6. asynx index(){
  7. const { ctx } = this;
  8. const data = await mongo.query('componentSite);
  9. if(data && data.length>0){
  10. ctx.body = data[0]
  11. }else{
  12. ctx.body = []
  13. }
  14. }
  15. }
  • 需要在mongoDB中新建集合 componentSite,添加页面页脚数据
  • 启动项目,浏览器输入地址,测试访问结果

3-4 前端动态配置 API 接入

代码无分支提交至:umi-component-test

上一节我们开发完毕api接口后,在前端请求该接口

  • 首先需要install axios
  • 接着需要封装request请求。
  1. // src/layouts/util/reques.js
  2. import axios from 'axios';
  3. const BASE_URl = 'http://liugezhou.com:7001'
  4. const service = axios.create({
  5. baseURl:BASE_URL,
  6. timeout:5000
  7. })
  8. service.interceptor.request.use({
  9. config => {
  10. return config;
  11. }
  12. error => {
  13. return new Promise.reject(error)
  14. }
  15. service.interceptor.response.use({
  16. response => {
  17. return new response.data;
  18. }
  19. error => {
  20. return new Promise.reject(error)
  21. }
  22. })
  23. export default request;
  24. //src/layouts/utils/service.js
  25. import request from './request'
  26. export function getSiteInfo(){
  27. return request({
  28. url:'/api/v1/componentSite'
  29. })
  30. }
  31. export default {}
  • 最后,在首页中去调用该接口,且赋值为页头与页脚
  1. // src/layouts/index.js
  2. import styles from './index.less';
  3. import {getSiteInfo } from '../utils/service';
  4. import { useState, useEffect } from 'react'';
  5. function BasicLayout(props){
  6. const [init,setInit] = useState(false);
  7. const [slogan,setSlogan] = useState('');
  8. const [copyright,setCopyright ] = useState('');
  9. useEffect( () =>{
  10. if (!init) {
  11. setInit(true);
  12. getSiteInfo().then(data => {
  13. setSlogan(data.slogan);
  14. setCopyright(data.copyright);
  15. }).catch(err => {
  16. console.log(err);
  17. setSlogan('');
  18. setCopyright('');
  19. });
  20. }
  21. },[init])
  22. return (
  23. <div className={styles.normal}>
  24. <div className={styles.title}>{slogan}</div>
  25. {props.children}
  26. <div className={styles.footer}>{copyright}</div>
  27. </div>
  28. )
  29. }
  30. export default BasiLayout;

第四章 组件平台组件列表页面开发


4-1 组件列表 API 开发

本周相关代码提交至:cloudscope-cli/server/lesson33

  1. // app/controller/v1/Components.js
  2. async index(){
  3. const { ctx, app } = this;
  4. const { name } = ctx.query;
  5. const andWhere = name ? `AND c.name LIKE '%${name}%'` : '';
  6. const sql = `SELECT c.id, c.name, c.classname, c.description, c.npm_name, c.npm_version, c.git_type, c.git_remote, c.git_owner, c.git_login, c.create_dt, c.update_dt, v.version, v.build_path, v.example_path, v.example_list
  7. FROM component AS c
  8. LEFT JOIN version AS v ON c.id = v.component_id
  9. WHERE c.status = 1 AND v.status = 1 ${andWhere}
  10. ORDER BY c.create_dt, v.version DESC`;
  11. const result = await app.mysql.query(sql);
  12. const components = [];
  13. result.forEach(component => {
  14. let hasComponent = components.find(item => item.id === component.id);
  15. if (!hasComponent) {
  16. hasComponent = {
  17. ...component,
  18. };
  19. delete hasComponent.version;
  20. delete hasComponent.build_path;
  21. delete hasComponent.example_path;
  22. delete hasComponent.example_list;
  23. hasComponent.versions = [];
  24. components.push(hasComponent);
  25. hasComponent.versions.push({
  26. version: component.version,
  27. build_path: component.build_path,
  28. example_path: component.example_path,
  29. example_list: component.example_list,
  30. });
  31. } else {
  32. hasComponent.versions.push({
  33. version: component.version,
  34. build_path: component.build_path,
  35. example_path: component.example_path,
  36. example_list: component.example_list,
  37. });
  38. }
  39. });
  40. ctx.body = components;
  41. }

4-2 测试组件数据创建

  • 上一节我们获取的组件列表数据为一条,本节首先再去创建几条测试数据。
  • 在此之前,先去更改之前的组件模版 cloudscope-cli-components,packahe.json的配置:publishConfig为access:true 和 build:demo

    • 外层package.json 增加一个版本号
    • template内package.json 配置 publishConfig
    • template内package.json 配置‘build:demo’:”npm install && npm run build && cd examples && npm install && npm run build”
    • npm publish
    • mongodb数据库中将版本号升级
  • mkdir cloudscope-cli_component-test3

  • cd cloudscope-cli_component-test3
  • cloudscope-cli init -tp /Users/liumingzhou/Documents/imoocCourse/Web前端架构师/cloudscope-cli/commands/init/ 【这里需要注意的是,由于我本读安装且默认设置了node的版本为14,而之前开发的本地脚手架为12.16,因此支持以上代码需要更换node版本】
    • @cloudscope-cli/components-test2
    • 1.0.0
    • 通用的Vue3组件库模版
    • components test2
  • code .
  • npm run build:demo
  • cloudscope-cli publish -tp /Users/liumingzhou/Documents/imoocCourse/Web前端架构师/cloudscope-cli/commands/publish/ [不加prod属性,不用关心OSS与NPM发布]

检查:

  • git remote -v 【查看远程仓库信息】
  • 查看mysql数据库插入信息

  • 如果添加了 —prod属性

  • 查看npm发布组件信息
  • 查看云OSS上传信息

4-3 组件列表页面开发

4-4 组件卡片面板开发

4-5 搜索框组件开发+模糊搜索API开发

这三节内容为组件首页列表的umi项目代码开发,包括布局、请求、点击事件等功能,代码分类为:国际化配置、工具类、业务代码,其中核心内容为业务代码,主要是使用UI库ant-design-react和umi以及react的一些用法。

  • 国际化配置
  1. // src/locales/en-US.js
  2. export default {
  3. WELCOME_TO_UMI_WORLD: "{name}, welcome to umi's world",
  4. INDEX_SEARCH: 'Search',
  5. INDEX_PLACEHOLDER: 'Component Name',
  6. };
  7. // src/locales/zh-CN.js
  8. export default {
  9. WELCOME_TO_UMI_WORLD: '{name},欢迎光临umi的世界',
  10. INDEX_SEARCH: '搜索',
  11. INDEX_PLACEHOLDER: '组件名称',
  12. };
  • 工具类
  1. // src/utils/index.js
  2. export function formatName(name) {
  3. let _name = name;
  4. if (_name && _name.startsWith('@') && _name.indexOf('/') > 0) {
  5. // @cloudscope-cli/component-test ->
  6. // @cloudscope-cli_component-test
  7. const nameArray = _name.split('/');
  8. _name = nameArray.join('_').replace('@', '');
  9. }
  10. return _name;
  11. }
  12. export default {};
  13. // src/utils/request.js
  14. import axios from 'axios'
  15. const BASE_URL = 'http://liugezhou.com:7001'
  16. const service = axios.create({
  17. baseURL: BASE_URL,
  18. timeout: 5000
  19. })
  20. service.interceptors.request.use(
  21. config => {
  22. return config
  23. },
  24. error => {
  25. return Promise.reject(error)
  26. }
  27. )
  28. service.interceptors.response.use(
  29. response => {
  30. return response.data
  31. },
  32. error => {
  33. return Promise.reject(error)
  34. }
  35. );
  36. export default service;
  37. //src/utils/service.js
  38. import request from './request';
  39. export function getSiteInfo() {
  40. return request({
  41. url: '/api/v1/componentSite',
  42. });
  43. }
  44. export function getComponentList(params) {
  45. return request({
  46. url: '/api/v1/components',
  47. params,
  48. });
  49. }
  50. export default {};
  51. //src/utils/git.js
  52. import { formatName } from '../utils';
  53. export function getGitUrl(item) {
  54. let name = item.classname;
  55. if (name.startsWith('@') && name.indexOf('/') > 0) {
  56. const nameArray = name.split('/');
  57. name = nameArray.join('_').replace('@', '');
  58. }
  59. return `https://${item.git_type}.com/${item.git_login}/${name}`;
  60. }
  61. /**
  62. * 获取组件预览链接
  63. * @param name 组件名称
  64. * @param version 组件版本
  65. * @param path 组件预览文件路径
  66. * @param file 组件预览文件名称
  67. */
  68. export function getPreviewUrl({ name, version, path, file }) {
  69. // 上传至OSS再来配置
  70. return `https:// /${formatName(name)}@${version}/${path}/${file}`;
  71. }
  72. export default {};
  • 业务代码 ✨✨✨✨✨
  1. // src/pages/index.jsx
  2. import styles from './index.less';
  3. import { Divider, Row, Col, Card, Input } from 'antd';
  4. import { EditOutlined, EllipsisOutlined, EyeOutlined } from '@ant-design/icons';
  5. import { useIntl,history } from 'umi';
  6. import { getGitUrl,getPreviewUrl } from "../utils/git";
  7. import { getComponentList } from '../utils/service';
  8. import { useState, useEffect } from "react";
  9. const { Meta } = Card;
  10. const { Search } = Input;
  11. export default function IndexPage() {
  12. const [init, setInit] = useState(false);
  13. const [list, setList] = useState([]);
  14. const [name, setName] = useState(null);
  15. const intl = useIntl();
  16. useEffect(() => {
  17. if (!init) {
  18. setInit(true);
  19. getComponentList({ name }).then(data => {
  20. console.log(data);
  21. setList(data);
  22. }).catch(err => {
  23. console.error(err);
  24. setList([]);
  25. });
  26. }
  27. }, [init, name]);
  28. function getAvatar(item) {
  29. if (item.git_type === 'gitee') {
  30. return {
  31. img: 'https://gitee.com/static/images/logo-black.svg',
  32. style: { height: '20px', cursor: 'pointer' },
  33. };
  34. } else {
  35. return {
  36. img: 'https://www.youbaobao.xyz/arch/img/github.jpeg',
  37. style: { height: '40px', cursor: 'pointer' },
  38. };
  39. }
  40. }
  41. function getLastPreviewUrl(item) {
  42. const lastVersion = item.versions[0];
  43. const examplePath = lastVersion.example_path;
  44. const exampleFile = JSON.parse(lastVersion.example_list)[0];
  45. return getPreviewUrl({
  46. name: item.classname,
  47. version: lastVersion.version,
  48. path: examplePath,
  49. file: exampleFile,
  50. });
  51. }
  52. return (
  53. <div className={styles.container}>
  54. <div className={styles.search}>
  55. <Search
  56. style={{ width: '50%' }}
  57. placeholder={intl.formatMessage({ id: 'INDEX_PLACEHOLDER' })}
  58. allowClear
  59. enterButton={intl.formatMessage({ id: 'INDEX_SEARCH' })}
  60. size='large'
  61. onSearch={value => {
  62. setName(value);
  63. setInit(false);
  64. }}
  65. />
  66. </div>
  67. <Divider orientation='right'>共{list.length}个组件</Divider>
  68. <Row gutter={16} justify='space-around'>
  69. {
  70. list.map(item => (
  71. <Col span={6} key={item.id}>
  72. <Card
  73. actions={[
  74. <EyeOutlined key='setting' onClick={() => {
  75. window.open(getLastPreviewUrl(item));
  76. }}
  77. />,
  78. <EditOutlined key='edit' onClick={() => {
  79. history.push({
  80. pathname: '/detail',
  81. query: {
  82. id: item.id,
  83. },
  84. });
  85. }}
  86. />,
  87. <EllipsisOutlined key='ellipsis' />,
  88. ]}
  89. >
  90. <Meta
  91. avatar={<img
  92. alt={item.name}
  93. src={getAvatar(item).img}
  94. style={getAvatar(item).style}
  95. onClick={() => window.open(getGitUrl(item))}
  96. />}
  97. title={item.name}
  98. description={item.description}
  99. />
  100. </Card>
  101. </Col>
  102. ))
  103. }
  104. </Row>
  105. </div>
  106. );
  107. }
  108. // src/pages/index.less
  109. .containter {
  110. padding: 20px;
  111. }
  112. .search {
  113. padding:30px;
  114. }

第五章 组件平台组件详情页面开发


5-1 组件详情获取API开发

首先在umi-component-dev项目下写details页面

  1. // src/pages/details.jsx
  2. import { useState, useEffect } from 'react';
  3. import styles from './detail.css';
  4. import { getComponentItem } from "../utils/service";
  5. export default function Page(props) {
  6. const [init, setInit] = useState(false)
  7. const [data, setData] = useState(null)
  8. useEffect(() => {
  9. const query = props.location.query
  10. if(!init && query.id){
  11. setInit(true);
  12. getComponentItem(query.id).then( data=> {
  13. console.log(data)
  14. })
  15. }
  16. })
  17. return (
  18. <div>
  19. </div>
  20. );
  21. }
  22. // src/utils/service.js
  23. export function getComponentItem(id) {
  24. return request({
  25. url: `/api/v1/components/${id}`,
  26. });
  27. }

接着,重点就是去开发接口获取组件的具体信息了

  1. // app/controller/v1/Components.js
  2. // api/v1/components/:id
  3. async show() {
  4. const { ctx, app } = this;
  5. const id = ctx.params.id
  6. const results = await app.mysql.select('component', {
  7. where: { id }
  8. })
  9. if (results && results.length > 0) {
  10. const component = results[0]
  11. component.versions = await app.mysql.select('version', {
  12. where: { component_id: id },
  13. orders: [['version', 'desc']]
  14. })
  15. // gitee GET https://gitee.com/api/v5/repos/{owner}/{repo}/contents(/{path})
  16. // git GET https://api.github.com/repos/{owner}/{repo}/{path})
  17. let readmeUrl;
  18. let _name = component.classname;
  19. if (_name && _name.startsWith('@') && _name.indexOf('/') > 0) {
  20. const nameArray = _name.split('/');
  21. _name = nameArray.join('_').replace('@', '');
  22. }
  23. if (component.git_type === 'gitee') {
  24. readmeUrl = `https://gitee.com/api/v5/repos/${component.git_login}/${_name}/contents/README.md`
  25. } else {
  26. readmeUrl = `https://api.github.com/repos/${component.git_login}/${_name}/README.md`
  27. }
  28. const readme = await axios.get(readmeUrl);
  29. let content = readme.data && readme.data.content;
  30. if (content) {
  31. content = decode(content)
  32. if (content) {
  33. component.readme = content
  34. }
  35. }
  36. ctx.body = component
  37. } else {
  38. ctx.body = {}
  39. }
  40. }

5-2 组件基本信息样式开发

5-3 组件代码+预览样式开发

5-4 组件安装样式和复制命令功能开发

5-5 组件多预览文件上传工作

5-6 组件多预览文件上传开发