ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
**写在前面** 该系列文章是为具有开发能力的朋友写作的,目的是帮助他们在刮擦3.0的基础上开发一套完整的集刮擦3.0编程工具,用户社区和作品云端存储及分享,品牌集成于一体的刮擦编程平台。如果您不是开发者,但想要拥有自己的教育平台和品牌,也欢迎学习交流和洽谈合作。 所以如果您是想学习scratch少儿编程课程,那请忽略该系列的文章。 **前言** 前面我们将scratch-gui工程成功建造运行起来,并且成功植入了我们的品牌徽标,这让我们对整个项目有了初步的认识。 现在我们已经有了scratch编程工具,但是我们还剩下两个主要的后台,用户社区后台和GUI的存储后台。目前Scratch3.0团队还没有发现社区替换和GUI的存储部分是否有开源计划,考虑到Scratch2.0的重新初始化开源,3.0社区初始化开源的可能也不大。 scratch-www项目提供了用户社区的功能,但是需要通过接口去分析它的后台的数据存储的结构,我觉得比较麻烦,不如我们自己来开发一个,集成到我们的编程工具scratch-gui中。 所以接下来我们的工作是自己来提供相关的两个前提平台,并与GUI集成到一起。 **约会用户登录状态** 我们先一步一步来,先做一个比较简单的用户系统,再一步一步迭代。 这一章,我们先来改造一下前面的scratch-gui,约会用户登录状态的检测。 在进入项目时,检测用户是否登录,如果用户未登录,则在右上角显示登录按钮,否则显示用户头像和姓名等基本信息。 先在减少器目录中创建user-state.js文件,用作记录用户的信息。 添加如下内容: > import keyMirror from 'keymirror'; > > const UserState = keyMirror({ > >     NOT\_LOGINED: null, > >     LOGINED: null > > }); > > const UserStates = Object.keys(UserState) > > const initialState = { > >     error: null, > >     userData: null, > >     loginState: UserState.NOT\_LOGINED > > }; > > const getIsLogined = loginState => ( > >     loginState === UserState.LOGINED > > ); > > const reducer = function (state, action) { > >     if (typeof state === 'undefined') state = initialState; > > } 在减少器/gui.js中,作为项目的用户相关的初始化信息。 在reducer / gui.js中日期用户状态: > ​​​ > > ~~~ > import userStateReducer, {userStateInitialState} from './user-state'; > ``` > 加入到initialState中: > ``` > const guiInitialState = { >     alerts: alertsInitialState, >     assetDrag: assetDragInitialState, >     blockDrag: blockDragInitialState, >     cards: cardsInitialState, >     colorPicker: colorPickerInitialState, >     connectionModal: connectionModalInitialState, >     customProcedures: customProceduresInitialState, >     editorTab: editorTabInitialState, >     mode: modeInitialState, >     hoveredTarget: hoveredTargetInitialState, >     stageSize: stageSizeInitialState, >     menus: menuInitialState, >     micIndicator: micIndicatorInitialState, >     modals: modalsInitialState, >     monitors: monitorsInitialState, >     monitorLayout: monitorLayoutInitialState, >     projectChanged: projectChangedInitialState, >     projectState: projectStateInitialState, >     projectTitle: projectTitleInitialState, >     fontsLoaded: fontsLoadedInitialState, >     restoreDeletion: restoreDeletionInitialState, >     targets: targetsInitialState, >     timeout: timeoutInitialState, >     toolbox: toolboxInitialState, >     vm: vmInitialState, >     vmStatus: vmStatusInitialState, >     userState: userStateInitialState > }; > ``` > 将reducer加入到guiReducer中: > ``` > const guiReducer = combineReducers({ >     alerts: alertsReducer, >     assetDrag: assetDragReducer, >     blockDrag: blockDragReducer, >     cards: cardsReducer, >     colorPicker: colorPickerReducer, >     connectionModal: connectionModalReducer, >     customProcedures: customProceduresReducer, >     editorTab: editorTabReducer, >     mode: modeReducer, >     hoveredTarget: hoveredTargetReducer, >     stageSize: stageSizeReducer, >     menus: menuReducer, >     micIndicator: micIndicatorReducer, >     modals: modalReducer, >     monitors: monitorReducer, >     monitorLayout: monitorLayoutReducer, >     projectChanged: projectChangedReducer, >     projectState: projectStateReducer, >     projectTitle: projectTitleReducer, >     fontsLoaded: fontsLoadedReducer, >     restoreDeletion: restoreDeletionReducer, >     targets: targetReducer, >     timeout: timeoutReducer, >     toolbox: toolboxReducer, >     vm: vmReducer, >     vmStatus: vmStatusReducer, >     userState: userStateReducer > }); > ~~~ > > 下面去container / gui.jsx中为里面定义的GUI Component添加loginState这个道具,使用标识用户是否登录: > > > ~~~ > GUI.propTypes = { >     assetHost: PropTypes.string, >     children: PropTypes.node, >     cloudHost: PropTypes.string, >     error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), >     fetchingProject: PropTypes.bool, >     intl: intlShape, >     isError: PropTypes.bool, >     isLoading: PropTypes.bool, >     isScratchDesktop: PropTypes.bool, >     isShowingProject: PropTypes.bool, >     loadingStateVisible: PropTypes.bool, >     onProjectLoaded: PropTypes.func, >     onSeeCommunity: PropTypes.func, >     onStorageInit: PropTypes.func, >     onUpdateProjectId: PropTypes.func, >     onUpdateProjectTitle: PropTypes.func, >     onUpdateReduxProjectTitle: PropTypes.func, >     onVmInit: PropTypes.func, >     projectHost: PropTypes.string, >     projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), >     projectTitle: PropTypes.string, >     telemetryModalVisible: PropTypes.bool, >     vm: PropTypes.instanceOf(VM).isRequired, >     loginState: PropTypes.bool > }; > ~~~ > > 这个`loginState`道具的状态值来自于user-state.js中getIsLogined中检测当前的loginState(指状态中的)是否等于UserState.LOGINED: > > > ~~~ > const mapStateToProps = state => { >     const loadingState = state.scratchGui.projectState.loadingState; >     const loginState = state.scratchGui.userState.loginState; >     return { >         activeTabIndex: state.scratchGui.editorTab.activeTabIndex, >         alertsVisible: state.scratchGui.alerts.visible, >         backdropLibraryVisible: state.scratchGui.modals.backdropLibrary, >         blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX, >         cardsVisible: state.scratchGui.cards.visible, >         connectionModalVisible: state.scratchGui.modals.connectionModal, >         costumeLibraryVisible: state.scratchGui.modals.costumeLibrary, >         costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, >         error: state.scratchGui.projectState.error, >         isError: getIsError(loadingState), >         isFullScreen: state.scratchGui.mode.isFullScreen, >         isPlayerOnly: state.scratchGui.mode.isPlayerOnly, >         isRtl: state.locales.isRtl, >         isShowingProject: getIsShowingProject(loadingState), >         loadingStateVisible: state.scratchGui.modals.loadingProject, >         projectId: state.scratchGui.projectState.projectId, >         soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX, >         targetIsStage: ( >             state.scratchGui.targets.stage && >             state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget >         ), >         telemetryModalVisible: state.scratchGui.modals.telemetryModal, >         tipsLibraryVisible: state.scratchGui.modals.tipsLibrary, >         vm: state.scratchGui.vm, >         loginState: getIsLogined(loginState) >     }; > }; > ~~~ > > 现在container / gui.jsx中定义的Component GUI具有登录状态属性了,我们要把它传到menu-bar中,因为我们要在menu-bar中去控制右上角的显示状态。 在这个GUI组件中使用了components / gui / gui.jsx定义的GUIComponent这个组件,GUIComponent定义了整个项目的基本样式结构中,可以找到对MenuBar的使用。 首先,在GUIComponent的定义中日期之前定义的`loginState`: > > > ~~~ > const GUIComponent = props => { >     const { >         accountNavOpen, >         activeTabIndex, >         alertsVisible, >         authorId, >         authorThumbnailUrl, >         authorUsername, >         basePath, >         backdropLibraryVisible, >         backpackHost, >         backpackVisible, >         blocksTabVisible, >         cardsVisible, >         canCreateNew, >         canEditTitle, >         canRemix, >         canSave, >         canCreateCopy, >         canShare, >         canUseCloud, >         children, >         connectionModalVisible, >         costumeLibraryVisible, >         costumesTabVisible, >         enableCommunity, >         intl, >         isCreating, >         isFullScreen, >         isPlayerOnly, >         isRtl, >         isShared, >         loading, >         renderLogin, >         onClickAccountNav, >         onCloseAccountNav, >         onLogOut, >         onOpenRegistration, >         onToggleLoginOpen, >         onUpdateProjectTitle, >         onActivateCostumesTab, >         onActivateSoundsTab, >         onActivateTab, >         onClickLogo, >         onExtensionButtonClick, >         onProjectTelemetryEvent, >         onRequestCloseBackdropLibrary, >         onRequestCloseCostumeLibrary, >         onRequestCloseTelemetryModal, >         onSeeCommunity, >         onShare, >         onTelemetryModalCancel, >         onTelemetryModalOptIn, >         onTelemetryModalOptOut, >         showComingSoon, >         soundsTabVisible, >         stageSizeMode, >         targetIsStage, >         telemetryModalVisible, >         tipsLibraryVisible, >         vm, >         loginState, >         ...componentProps >     } = omit(props, 'dispatch'); >     ... > ~~~ > > 再在使用MenuBar的地方也为MenuBar定义`loginState`属性,它的值就是GUIComponent传进来的`loginState`的值: > > > 最后修改components / menu-bar.jsx中的MenuBar组件的显示,将右上角替换成: >                             {this.props.loginState ? ( > >                                             className={classNames( > >                                                 styles.menuBarItem, > >                                                 styles.hoverable, > >                                                 styles.mystuffButton > >                                             )} > >                                         > > >                                                 className={styles.mystuffIcon} > >                                                 src={mystuffIcon} > >                                             /> > >                                         id="account-nav" > >                                         place={this.props.isRtl ? 'right' : 'left'} > >                                     > > >                                             className={classNames( > >                                                 styles.menuBarItem, > >                                                 styles.hoverable, > >                                                 styles.accountNavMenu > >                                             )} > >                                         > > >                                                 className={styles.profileIcon} > >                                                 src={profileIcon} > >                                             /> > >                                                 {'scratch-cat'} > >                                                 className={styles.dropdownCaretIcon} > >                                                 src={dropdownCaret} > >                                             /> > >                             ) : Login} 如果用户已登录,就显示头像和姓名的样式(具体的用户信息需要跟后台打通,我们后面再实现): ![](https://img-blog.csdnimg.cn/20200127181021798.png) 否则显示登录按钮: ![](https://img-blog.csdnimg.cn/20200127181041268.png) 我们可以通过修改reducers / user-state.js中的loginState的初始值来查看效果: > loginState: UserState.NOT\_LOGINED > loginState: UserState.LOGINED 这个值我们会在后面根据用户登录的token去获取。 为了与项目整体风格一致,我们修改这个登录按钮的样式,在菜单栏目录中添加login-button.css和login-button.jsx文件,内容分别如下: > ​​ > > ~~~ > @import "../../css/colors.css"; > > .login-button { background: $data-primary;} > ~~~ > > > import classNames from 'classnames'; > > import {FormattedMessage} from 'react-intl'; > > import PropTypes from 'prop-types'; > > import React from 'react'; > > import Button from '../button/button.jsx'; > > import styles from './login-button.css'; > > const LoginButton = ({ > >     className, > >     onClick > > }) => ( > >         className={classNames( > >             className, > >             styles.loginButton > >         )} > >         onClick={onClick} > >     > > >             defaultMessage="Login" > >             description="Label for login" > >             id="gui.menuBar.login" > >         /> > > ); > > LoginButton.propTypes = { > >     className: PropTypes.string, > >     onClick: PropTypes.func > > }; > > LoginButton.defaultProps = { > >     onClick: () => {} > > }; > > export default LoginButton; 然后在menu-bar.jsx中如下使用: > 这样看起来就好看多了: ![](https://img-blog.csdnimg.cn/20200127181254620.png) 好了,这里接收完成后,我们接下来就可以实现一个后台系统,然后对接后台系统登录和获取用户信息了。