关于我们

左小皓
2020-09-30
1193
						

2013年开始投入微信营销功能开发,主要面向公众号,以及后来的小程序,网站主要提供SAAS服务,平台内现有300多种稳定的功能程序可供选择,从建站开始至今,已经服务过近两千名客户,我们可以做到按需要定制,有专业的开发团队,可以根据客户的活动需求做出优质活动方案。价格人性化,根据不同需求,不同流量,提供长短期的租用服务。

左小皓 M3U8下载合并工具-1.0.19含授权码

左小皓
2020-03-02
1945
						

左小皓m3u8下载合并工具是一款好用的m3u8视频流获取和合并工具,现在的直播以及视频播放采用的就是m3u8形式的,类似于一小段一小段的文件,有了这款工具就可以对整个视频进行下载和合并了,支持自定义合并名称,欢迎下载。

左小皓原创工具合集:https://lanzoui.com/b00nc1ltc

82__f6f99dd1516947fa76cb34d6e1b1211c_e9c3a883a975885552919e72c13d075e.png


主要特色

1、一键解析M3U8地址

2、自定义下载名称

3、自定义文件的保存目录

4、下载速度快

使用教程

1.获取m3u8文件网址

我习惯使用猫抓工具来获取

左小皓m3u8下载合并工具 v1.0.10免费版

2.获取到m3u8网址后,点击复制

打开软件,将网址粘贴到地址中,点击解析

左小皓m3u8下载合并工具 v1.0.10免费版

3.解析成功后,可以自定义合并后视频保存的文件名,以及保存目录

设置完成后,点击下载,开始下载视频文件

左小皓m3u8下载合并工具 v1.0.10免费版

4.全部下载完成后,软件自动合并保存视频文件

由于每个网站的m3u8文件规则不尽相同,会存在视频文件解析不完整的情况,遇到这种情况,可以将m3u8文件的网址回复上来,增加兼容性。

左小皓m3u8下载合并工具 v1.0.10免费版


下载地址

左小皓原创工具合集:https://lanzoui.com/b00nc1ltc

WPS2019专业版终身激活码

左小皓
2020-03-20
1182
						

694BF-YUDBG-EAR69-BPRGB-ATQXH


专业版下载地址:

https://ep.wps.cn/download


http://wdl1.cache.wps.cn/wps/download/ep/WPS2019/WPSPro_11.8.2.8721.exe


WPS官网下载地址:https://ep.wps.cn/download


安装后如果没有激活也可使用下面序列号

需要安装VBA,在解压后的根目录有WPSVBA.exe


某政府版激活码:THUV2-32HH7-6NMHN-PTX7Y-QQCTH


某人社局激活码:R7AKQ-KLBXV-RNX3F-BPACQ-NQDGE


企业版序列号1:R8R8P-MTT6F-KLRPM-J7CAB-PJM8C


企业版序列号2:A4XV7-QP9JN-E7FCB-VQFRD-4NLKC


企业版序列号3:694BF-YUDBG-EAR69-BPRGB-ATQXH


企业版序列号4:7LR67-WTXPA-KLUHV-GEK2E-QW4CK


企业版序列号5:EUYTH-3KWKL-PJMX7-XBCPW-9U2DD


企业版序列号6:U2PWU-H7D9H-69T3B-JEYC2-3R2NG


企业版序列号7:7G2HE-JR8KL-ABB9D-Y7789-GLNFL


企业版序列号8:9DP6T-9AGWG-KWV33-9MPC8-JDCV


云喇叭功能演示云支付播报云收款音箱

左小皓
2020-04-15
1186
						

云喇叭功能演示云支付播报云收款音箱

产品提供API接口,用于对接有开发权限的应用,非市面蓝牙类产品

产品仅供有开发能力的个人及公司对接使用,用于给自己的产品做配套的支付播报硬件工具,

二代喇叭支持以下几个自定义播报内容:

1.开机欢迎语,每个喇叭可独立设置。(非API接口控制)

2.自定义播报前缀(非API接口控制)

3.播报金额

4.播报后缀(非API接口控制)

gh_0b253cd86c0e_258.jpg

【云喇叭小助手】


精准解决中小商户二维码收款的痛点:

              ■  二维码收款到账信息不能及时确认,造成通过历史支付结果截图、或伪造收款二维码的支付诈骗隐患

              ■  老板不在收银现场,收银人员无法及时确认与收款二维码绑定的老板的账户是否已收到款项

              ■  老板虽然在收银现场,但收款App不在启动状态,或者手机播报声音小,无法及时获知到账信息


一、WIFI版(自带锂电池)

mmexport1554440382718.jpg

01.jpg



00-2.jpg

00-1.jpg

00-3.jpg

02.jpg


二、GPRS-2G流量版(自带一年流量)

mmexport1554440426063.jpg

mmexport1554440420092.jpg

mmexport1554440434265.jpg

mmexport1554440444878.jpg

mmexport1554440431595.jpg


支付播报业务平台(SaaS),是整个支付播报业务场景的重要组成环节,

参见如下的场景流程:

111.png


【左小皓】定时备份文件并发送到邮箱

左小皓
2019-12-01
1436
						

下载地址:

https://www.lanzoui.com/b00nc1ltc

天津博瑞皓科群聊二维码.jpg


使用方法很简单】1.软件运行后,添加好需要备份的文件或文件夹,
2.按类型设置好邮箱账号信息,设置好点击保存配置,
3.再点击开始运行,
完成以上三步,即可自动按设置的备份周期将备份文件发送到指定邮箱,达到定时备份的效果


一波三折啊,经过论坛@Hmily的严格检测,帖子终于被解除屏蔽了,洗白了,这也是好事,通过Hmily对待论坛原创软件的严谨态度,可以保证论坛的用户们下载到安全的原创软件。
大家放心使用,此软件安全,没有任何后门。

效果图

下载地址:

https://www.lanzoui.com/b00nc1ltc


BarTender 2019 R5 免费版下载安装教程(仅供测试,办公使用请购买正版)

左小皓
2019-12-18
1281
						

专业的标签、条形码打印软件,快速便捷设计专业高质量标签、RFID、证卡&条形码。


官方网站:https://www.bartendersoftware.com

版本对比:https://www.bartendersoftware.com.cn/software/compare/


01.png

02.png

03.png

链接:https://pan.baidu.com/s/14O5L1gMBwiG3x_T-3JmKWg 

提取码:okh2 

复制这段内容后打开百度网盘手机App,操作更方便哦


【scratch二次开发教程 第7章】scratch-gui中集成自定义用户系统(四)

管理员
2019-12-14
1551
						

写在前面该系列文章是为具有开发能力的朋友写作的,目的是帮助他们在scratch 3.0的基础上开发一套完整的集scratch 3.0编程工具、用户社区和作品云端存储及、品牌集成于一体的scratch编程平台。如果您不是开发者,但想要拥有自己的教育平台和品牌,也欢迎学习交流和洽谈合作。所以如果您是想学习scratch少儿编程课程,那请忽略该系列的文章。前言前面我们完成了登录弹窗按钮组件,现在我们开始对接登录接口。开始实现我们先到reducers / user - state.js中添加相关states和actions。

const DONE_USER_LOGIN = 'DONE_USER_LOGIN'; // 标识登录过程成功完成const ERROR_USER_LOGIN = 'ERROR_USER_LOGIN'; // 标识登录过程失败

const initialState = {

error: '',

// 登录错误信息    userData: null, // 用户初始信息    loginState: UserState.NOT_LOGINED,    token: '' // 登录获取的token};登录的actions:

const getUserSuccess = data = & gt;

({

type: DONE_USER_LOGIN,

data: data

});

const getUserError = () = & gt;

({

type: ERROR_USER_LOGIN

});

reducer处理登录成功: const reducer = function(state, action) {

if (typeof state === 'undefined') state = initialState;

switch (action.type) {

case DONE_USER_LOGIN:

return object.assign({}, state, {

loginState: UserState.LOGINED,

token: action.data.token,

userData: action.data.user

});

default:

return state;

}

};我们还有一个需要export给提交按钮先来SubmitLoginButton调用的方法,在用户提交时,获取参数然后向后台获取用户信息:

const loginFromServer = (dispatch, account, password) = & gt; {从后台获取用户数据.then(({

data

}) = & gt; {

console.log({

data

});

dispatch(getUserSuccess(data.login));

}).

catch (() = & gt; dispatch(getUserError()));

};从后台获取数据我们稍后实现。记得在最后export这个方法: export {

reducer as

default, initialState as userStateInitialState, UserState, UserStates, getIsLogined, loginFromServer

};再来SubmitLoginButton中,实现我们之前预留的onclick()方法,提交用户输入的信息。

SubmitLoginButton在被时,需要从账号和密码输入框获取用户输入提交。所以它的onclick()方法需要取得LoginModal中的其他表单元素的信息,很显然我们需要将这个处理过程定义到LoginModal中,然后将这一处理过程作为SubmitLoginButton的onclick属性值赋给它。这里我们把之前的LoginModal组件进行改造: const LoginModal = props = & gt;

( & lt; Modal className = {

styles.modalContent

}

contentLabel = {

props.title

}

id = & quot; loginModal & quot; onRequestClose = {

props.onCancel

} & gt; & lt; Box & gt; & lt; input className = {

styles.minInput

}

name = & quot; account & quot; placeholder = & quot;账号 & quot; type = & quot; text & quot;

/><br / & gt; & lt; input className = {

styles.minInput

}

name = & quot; password & quot; placeholder = & quot;密码 & quot; type = & quot; password & quot;

/><br / & gt; & lt; SubmitLoginButton className = {

styles.btnSubmit

}

/>        </Box & gt; & lt;

/Modal>);之前我们是以方法定义的方式实现它的,如上面。

现在我们用`extends Reactponent`的方式将它改造成受控组件来完成表单内容的接收和提交:

class LoginModal extends React.Component {

constructor(props) {

super(props);

bindAll(this, ['handleAccountChange', 'handlePasswordChange', 'handleSubmit']);

this.state = {

account: '',

password: ''

};

}

handleAccountChange(e) {

this.setState({

account: e.target.value

});

}

handlePasswordChange(e) {

this.setState({

password: e.target.value

});

}

handleSubmit() {

if (this.state.account.trim() === '' || this.state.password.trim() === '') {

alert('账号和密码不能为空');

return;

}

this.props.onsubmit(this.state.account, this.state.password);

}

render() {

return ( & lt; Modal className = {

styles.modalContent

}

contentLabel = {

this.props.title

}

id = & quot; loginModal & quot; onRequestClose = {

this.props.onCancel

} & gt; & lt; Box & gt; & lt; input className = {

styles.minInput

}

name = & quot; account & quot; placeholder = & quot;账号 & quot; type = & quot; text & quot; value = {

this.state.account

}

onchange = {

e = & gt;

this.handleAccountChange(e)

}

/ & gt; & lt; br / & gt; & lt; input className = {

styles.minInput

}

name = & quot; password & quot; placeholder = & quot;密码 & quot; type = & quot; password & quot; value = {

this.state.password

}

onchange = {

e = & gt;

this.handlePasswordChange(e)

}

/><br / & gt; & lt; SubmitLoginButton className = {

styles.btnSubmit

}

onclick = {

this.handleSubmit

}

/>                </Box & gt; & lt;

/Modal>        );    }}LoginModal.propTypes = {    onCancel: PropTypes.func.isRequired,    onsubmit: PropTypes.func.isRequired,    title: PropTypes.string.isRequired};const mapDispatchToProps = dispatch => ({    onCancel: () => dispatch(closeLoginModal()),    onsubmit: (account, password) => loginFromServer(dispatch, account, password)});export default connect(    null,    mapDispatchToProps)(LoginModal);上面是我们改造后的LoginModal组件。

里面定义两个state的值用来记录输入的账号和密码。三个方法handleAccountChange,handlePasswordChange和handleSubmit分别处理账号密码的输入和提交请求。

props中的onsubmit映射到刚才我们定义的longFromServer方法,将账号和密码传过去,然后与后台服务进行交互。

现在来实现与后台的交互过程。

我们使用apollo client来完成graphql接口与后台的交互。

react-apollo库有完善的使用graphql与后台进行交互并控制组件更新的功能,还包括缓存控制等等,非常强大。但是完整地使用它的话会涉及到对我们scratch里已有的组件的数据绑定和使用的修改,这个工作量不小,而且还可能引入意想不到的问题,所以我们这里只通过apollo client获取数据,更新state来控制我们组件的更新,就像传统的react-redux的工作方式一样。这可能让我们失去使用graphql的一些优势,但是综合考虑成本,我们还是需要适当取舍,毕竟我们是在二次开发别人的项目。

先安装相关的npm包:

npm install graphql apollo - client apollo - cache - inmemory apollo - link - http graphql - tag在lib目录下新建一个apollo.js文件定义apollo client: import {

ApolloClient

}

from 'apollo-client';

import {

InMemoryCache

}

from 'apollo-cache-inmemory';

import {

createHttplink

}

from 'apollo-link-http';

const client = new ApolloClient({

link: createHttplink({

uri: 'localhost:8602/graphql'

}),

cache: new InMemoryCache()

});

export

default client;

localhost:

8602 / graphql就是我们之前实现的后台用户社区服务的graphql接口地址,先暂时把它写死在代码里面。在lib中新建文件夹queries用于存放我们所有的graphql请求,现在在queries下新建目前user用户归类用户相关的请求,在user下新建login.jsx文件,定义我们的登录请求的graphql的query:

import gql from '

graphql - tag ';const LOGIN = gql`query login($account: String!, $password: String!) {  login(accountaccount, passwordpassword){    user{      id      name      account      avatar    }    token  }}`;export default LOGIN;现在回到user-state.js中,完善loginFromServer请求后台数据的过程:

const loginFromServer = (dispatch, account, password) => {    client.query({        query: LOGIN,        variables: {            account: account,            password: password        }    },    ).then(({data}) => {        console.log({data});        dispatch(getUserSuccess(data.login));        dispatch(closeLoginModal());    }).catch(() => dispatch(getUserError()));};这里的LOGIN就是前面我们定义的graphql query,要注意对照我们之前实现的后台接口返回的数据的结构来获取相应的数据:

{  "data": {    "login": {      "user": {        "id": "3",        "name": "馬沙拉",        "account": "mashala"        "avatar": "https://scratch-store.oss-cn-qingdao.aliyuncs/default-avatar.png"      },      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjQwNDgzMDMsImlhdCI6MTU2NDA0NDcwMywiaWQiOjIsInVzZXJuYW1lIjoibWFkYXJvdSJ9._rDpgQbOdoI95K7kCdN6K-taHwUF18XTIIn724grIPY"    }  }}现在我们开启后台服务,重新编译运行scratch-gui,进入浏览器(打开浏览器控制台),登录按钮,输入我们事先通过接口注册的账号和密码,提交,控制台中可以看到成功发送了请求并获取到结果:

MenuBar的右上角的登录按钮消失,出现了默认的scratch-cat的头像和姓名: 

这说明我们的基本的登录流程已经成功打通了。只是我们还没有将用户信息应用到MenuBar上面来。

现在我们就来完成这个过程。

前面我们已经将获取到的用户信息记录在了state中的userData中。现在我们要像使用`loginState`一样在gui.jsx中使用它。

在containers/gui.jsx中像之前`loginState`一样映射userData state到组件属性:

loginState: getIsLogined(loginState),userData: state.scratchGui.userState.userData然后在components/gui/gui.jsx中定义的GUIComponentz组件中使用它:

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,        showLoginModal,        soundsTabVisible,        stageSizeMode,        targetIsStage,        telemetryModalVisible,        tipsLibraryVisible,        vm,        loginState,        userData,        ..ponentProps    } = omit(props, '

dispatch ');在GUIComponent的Menubar中使用它:

<MenuBar                    accountNavOpen={accountNavOpen}                    authorId={authorId}                    authorThumbnailUrl={authorThumbnailUrl}                    authorUsername={authorUsername}                    canCreateCopy={canCreateCopy}                    canCreateNew={canCreateNew}                    canEditTitle={canEditTitle}                    canRemix={canRemix}                    canSave={canSave}                    canShare={canShare}                    className={styles.menuBarPosition}                    enableCommunity={enableCommunity}                    isShared={isShared}                    loginState={loginState}                    renderLogin={renderLogin}                    showComingSoon={showComingSoon}                    userData={userData}                    onclickAccountNav={onclickAccountNav}                    onclickLogo={onclickLogo}                    onCloseAccountNav={onCloseAccountNav}                    onLogOut={onLogOut}                    onOpenRegistration={onOpenRegistration}                    onProjectTelemetryEvent={onProjectTelemetryEvent}                    onSeeCommunity={onSeeCommunity}                    onShare={onShare}                    onToggleLoginOpen={onToggleLoginOpen}                    onUpdateProjectTitle={onUpdateProjectTitle}                />再为components/menu-bar/menu-bar.jsx中定义的MenuBar组件添加属性userData:

userData: PropTypes.shape({        id: PropTypes.string.isRequired,        name: PropTypes.string.isRequired,        account: PropTypes.string.isRequired,        avatar: PropTypes.string.isRequired    }),最后在之前根据loginState的值展示头像和姓名的地方,将默认头像和名字scratch-cat,替换成userData中的avatar和name:

好,现在我们来整理运行看看效果,输入账号密码,提交登录成功,登录框消失,出现了登录用户的名字和头像(这里我使用了scratch-gui提供的小猫作为没有设置头像的用户的默认头像):

整个登录的流程就按计划打通了。

我们最后再来完善一下登录的错误处理。

如果用户登录失败,登录窗不消失,在登录窗显示登录失败的错误信息。

在前面我们已经定义了登录失败的action:

const getUserError = () => ({    type: ERROR_USER_LOGIN});现在需要在reducer中处理它。

先修改`loginFromServer`的代码,增加后台请求错误的处理:

const loginFromServer = (dispatch, account, password) => {    client.query({        query: LOGIN,        variables: {            account: account,            password: password        }    },    ).then(({data}) => {        console.log(`login success${data}`);        dispatch(getUserSuccess(data.login));        dispatch(closeLoginModal());    })        .catch(error => {            console.log(`login error${error}`);            if (error.graphQLErrors.length > 0) {                dispatch(getUserError(error.graphQLErrors[0].message));            } else {                dispatch(getUserError(error.message));            }        });};其中分别处理了graphql的错误,这种错误是业务层面的,比如我们的用户名密码错误这些;另外还有一类更宽泛的错误,如网络出错等。

action `getUserError`现在需要接收错误消息:

const getUserError = data => ({    type: ERROR_USER_LOGIN,    data: data});reducer处理错误的情况:

const reducer = function (state, action) {    if (typeof state === '

undefined ') state = initialState;    switch (action.type) {    case DONE_USER_LOGIN:        return object.assign({}, state, {            loginState: UserState.LOGINED,            token: action.data.token,            userData: action.data.user        });    case ERROR_USER_LOGIN:        return object.assign({}, state, {            loginState: UserState.NOT_LOGINED,            token: '',            userData: null,            error: action.data        });    default:        return state;    }};现在我们需要在登录弹窗展示错误信息。

回到我们的LoginModal组件,增加一个展示错误信息的label:

同时我们定义loginError属性和映射它的值到state中的error:

LoginModal.propTypes = {    loginError: PropTypes.string.isRequired,    onCancel: PropTypes.func.isRequired,    onsubmit: PropTypes.func.isRequired,    title: PropTypes.string.isRequired};const mapStateToProps = state => ({    loginError: state.scratchGui.userState.error});下面我们重新运行程序,输入错误的用户名密码登录,看到出现了错误信息提示:

我们再输入正确的账号密码,成功登录。

好了,我们总算按计划对接完成了整个登录流程。

但是还有一个地方需要我们去完善,为了保证用户刷新页面或者在token过期前重新打开新的页面也能成功获取用户信息,避免不必要的重复登录,我们需要将token存到浏览器本地,并且在页面加载时尝试自动获取用户信息。

我们留到下一章来完成这一过程。


【scratch二次开发教程 第1章】基于scratch 3.0搭建自己的少儿编程平台

管理员
2019-12-14
1044
						

【scratch二次开发教程 第1章】基于scratch 3.0搭建自己的少儿编程平台

写在前面

该系列文章是为具有开发能力的朋友写作的,目的是帮助他们在scratch 3.0的基础上开发一套完整的集scratch 3.0编程工具、用户社区和作品云端存储及、品牌集成于一体的scratch编程平台。如果您不是开发者,但想要拥有自己的教育平台和品牌,也欢迎学习交流和洽谈合作。

所以如果您是想学习scratch少儿编程课程,那请忽略该系列的文章。

为什么要自己开发而不使用官方版本?

这个问题要看我们的做少儿编程教育的需求是怎么样的。

scratch本身提供了离线版本以及官网在线平台供我们使用,这足以满足我们对于编程教学模块的需求。但是对于一些教育培训机构或者学校而言,他们可能需要在此基础上整合学生信息,整合课程体系,对学生的学习情况进行统一管理和在线数据分析,包括支持课程作品的与转发,构建自己的教学品牌并实现线上的传播推广,等等,都需要我们在支持编程教学功能的基础上,引入线上社区、课程管理、综合管理等更丰富的功能,而这些就是二次开发的主要工作。

为什么要选择3.0?

不管是从架构还是从UI上,3.0都比2.0都有了很大的改变。

特别是3.0采用HTML5而不是2.0版本使用的flash,随着H5技术逐渐发展,原本采用flash技术的已经逐渐在向H5转移,相对于flash来讲,H5在页面的装载速度上整体上会更快一些,另外也相对较为省电。更为重要的是,flash对于移动端没什么办法,IOS压根就不支持它。Scratch 3.0是可以在手机和平板上运行的,因为H5需要的是javascript的支持,这在现代浏览器上基本都是没问题的,移动端也不例外。

另外Scratch 3.0是向下兼容的,因此原来2.0的项目在3.0上也是可以运行的,但从实际测试的效果来看,有些2.0的项目还是会让3.0崩溃。

所以如果计划采用Scratch的,选择3.0是肯定的,如果已经采用了2.0的版本,我的建议是逐渐迁移到3.0上,不管怎么说,单单移动端可以运行的理由就足够了,更不用说其他的一些新的功能比如声音控制Scratch项目。当然目前来开3.0还有一些功能上不稳定的地方,但在MIT和各位开源工作者的一起努力下,都会逐渐得到解决。

Scratch 3.0为我们开源了哪些东西?

https://github/LLK是scratch官方的git仓库。3.0相关的工程主要包括以下几个:

我们这里主要基于scratch-gui项目进行二次开发,它就是一个开源scratch 3.0编辑器,效果就是https://llk.github.io/scratch-gui/develop/。

在使用它创建作品的基础上,我们希望能够实现用户管理、用户社区、作品在线编辑和云端保存、作品、自定义品牌logo和角色等主要功能。

至于其他的项目,我也还没有开始研究,但是从项目介绍我们可以知道scratch-paint是绘图编辑器,scratch-www是像scratch官网一样的作品社区,scratch-I10n是国际化相关的,而如果想对scratch编辑器本身做更高级的扩展,可以研究scratch-vm项目,它本身也是被包含在scratch-gui项目中了。

下一章我们将开始着手拉取scratch-gui项目的代码到我们本地,搭建环境将它运行起来。


【scratch二次开发教程 第2章】运行scratch-gui项目并了解工程结构

管理员
2019-12-14
21609
						【scratch二次开发教程 第2章】运行scratch-gui项目并了解工程结构
写在前面
该系列文章是为具有开发能力的朋友写作的,目的是帮助他们在scratch 3.0的基础上开发一套完整的集scratch 3.0编程工具、用户社区和作品云端存储及、品牌集成于一体的scratch编程平台。如果您不是开发者,但想要拥有自己的教育平台和品牌,也欢迎学习交流和洽谈合作。
所以如果您是想学习scratch少儿编程课程,那请忽略该系列的文章。

这一章我们开始尝试在本地将scratch-gui项目的最新代码运行起来,并对其项目结构有个初步的认识。

本地搭建运行
我们从官方的gitlab仓库拉取最新的代码:




git clone https://github/LLK/scratch-gui.git目前主分支是develop,并且在不断的更新中,注意查看一下自己克隆的分支是否正确。

进入工程,安装依赖:






cd scratch-guinpm install如果还没有安装npm,请参考https://npmjs/
这个过程中可能会碰到错误信息而安装失败,这时我们需要根据具体的错误信息自己去网上寻找解决方案。

安装完成后,启动服务:




npm start看到如下信息后:



打开浏览器,访问localhost:8601就会看到scratch 3.0的playground为render-gui完整模式的界面,像官网的一样:





由于scratch-gui也处于开发之中,我们看到的有些功能还没有实现,或者没有开源出来。但是对于编程模块的功能,我们可以自己尝试一下,基本上都是跟官方的一样的。如果我们不打算在编程功能模块做进一步扩展的话,这一块的功能我们可以直接拿来使用,而不需要再进行二次开发了。




工程目录结构


我们先一起来过一遍scratch-gui工程的基本目录结构,以便开始我们的二次开发之旅。




我们在WebStorm中打开项目,看到如下目录结构:





熟悉react-redux框架的朋友应该很快就清楚工程各个目录的作用, src目录是我们真正需要了解和二次开发重点要修改的目录,主要包括:


components: 组件目录,主要定义UI组件的结构和样式,负责数据的展示

containers:主要负责对components中部分组件的封装,处理组件的业务逻辑,将组件的数据与redux打通,不涉及样式

css: 全局样式设置文件

examples/extensions: 关于scratch扩展的配置示例 

lib: 工具方法目录,供组件调用的一些模块和方法,其中以-hoc.jsx结尾的高阶组件,是为UI组件提供额外的功能方法

playground: 多种模式的使用示例:积木模式(blocks-only)、舞台模式(player)、完整模式(render-gui)

reducers: redux的store的初始状态、actions和 reducers的定义

index.js: 作为 library 打包的入口文件


好了,本章我们成功拉取scratch-gui的代码,并在本地成功运行起来,然后简单地分析了它的代码目录结构,下一节课我们将尝试在平台中自定义我们的品牌logo.					

【scratch二次开发教程 第3章】在scratch3.0中自定义品牌logo

管理员
2019-12-14
1222
						【scratch二次开发教程 第3章】在scratch3.0中自定义品牌logo

写在前面
该系列文章是为具有开发能力的朋友写作的,目的是帮助他们在scratch 3.0的基础上开发一套完整的集scratch 3.0编程工具、用户社区和作品云端存储及、品牌集成于一体的scratch编程平台。如果您不是开发者,但想要拥有自己的教育平台和品牌,也欢迎学习交流和洽谈合作。
所以如果您是想学习scratch少儿编程课程,那请忽略该系列的文章。

前言
无论我们是作为线下或线上的少儿编程培训机构或平台提供方,肯定想拥有自己教育品牌,并且将其集成到我们的平台中,以达到品牌宣传的效果。现在比较流行的像网易卡搭那些平台就是在scratch开源平台的基础上,植入自己的品牌logo的。

本节课我们就一起来为我们的平台定制自己的品牌logo到scratch-gui项目中。

开始开发
在上节课我们运行起来的项目中,看到顶部menu菜单的左上角有一个scratch的logo:




我们准备把它换掉,换成我们自己的品牌logo,shala-logo.svg:


顺便介绍一下,这只小白猫是笔者的小主,她叫馬沙拉,沙拉少儿编程也是取自她的名字。另外她还有个好姐妹蓝猫馬沙包,后面有机会好好给大家介绍一下她俩。



前面我们说src中的components是是负责UI组件的样式的,很显然我们的logo的样式的设置应该就在这里。

我们在components中发现有如下两个目录:



看名字就知道它们就是与我们的menu菜单样式相关。




挨个打开看看:



第一个是css样式文件,我们不管。 打开menu.jsx,这里面定义了MenuComponent组件,样式是一个< ul >,很可能是跟menu的下拉菜单相关,不是我们想要的,不管。
再打开第二个目录: 


咦!我们看到了一个文件名为scratch-logo.svg,这不就是我们要找的logo文件么?打开看看,确实是它!



好,我们再来项目中搜索一下哪里用到这个logo文件。


嗯!只有两个地方用到了它。
看文件名字也可以猜出,第一个是用在了舞台的头部,第二个是用在了菜单栏里(就是我们前面看到的那里)。


现在我们先将我们自己的logo文件加入项目中,放到与scratch-logo.svg相同的目录中。



然后分别替换stage-header.jsx和menu-bar.jsx中的引用。






重新编译项目,刷新浏览器,看到我们的logo成功被替换了:





这样,我们就完成的logo的自定义。
以后使用我们平台的孩子们、第三方机构或是学校都会看到我们醒目而惹眼的品牌logo啦!通过这样的线上线下传播,相信会大大提升我们的品牌知名度,达到独树一帜的效果!					

【scratch二次开发教程 第4章】scratch-gui中集成自定义用户系统(一)

管理员
2019-12-14
1143
						

【scratch二次开发教程 第4章】scratch-gui中集成自定义用户系统(一)

写在前面

该系列文章是为具有开发能力的朋友写作的,目的是帮助他们在scratch 3.0的基础上开发一套完整的集scratch

3.0编程工具、用户社区和作品云端存储及、品牌集成于一体的scratch编程平台。如果您不是开发者,但想要拥有自己的教育平台和品牌,也欢迎学习交流和洽谈合作。

所以如果您是想学习scratch少儿编程课程,那请忽略该系列的文章。

前言

前面我们将scratch-gui工程成功搭建运行起来,并且成功植入了我们的品牌logo,这让我们对整个项目有了初步的认识。

现在我们已经有了scratch编程工具,但是我们还缺少两个主要的后台,用户社区后台和GUI的存储后台。目前Scratch3.0团队没有透露社区后端和GUI的存储部分是否有开源计划,考虑到Scratch2.0的后端并未开源,3.0社区后端开源的可能性也不大。

scratch-www项目提供了用户社区的功能,但是需要通过接口去分析它后台的数据存储的结构,我觉得比较麻烦,不如我们自己来开发一个,集成到我们的编程工具scratch-gui中。

所以接下来我们的工作是自己来提供相关的两个后端平台,并与GUI集成到一起。

引入用户登录状态

我们先一步一步来,先做一个比较简单的用户系统,再一步一步迭代。

这一章,我们先来改造一下前面的scratch-gui,引入用户登录状态的检测。

在进入项目时,检测用户是否登录,如果用户未登录,则在右上角显示登录按钮,否则显示用户头像和姓名等基本信息。

先在reducers目录中创建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;

}

export { reducer as default, initialState as userStateInitialState, UserState, UserStates, getIsLogined};

并将以上初始化信息配置在reducers/gui.js中,作为项目的用户相关的初始化信息。

在reducers/gui.js中引入user-state:

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这个props,用来标识用户是否登录:

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` props的状态值来自于user-state.js中getIsLogined中检测当前的loginState(指state中的)是否等于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有了loginState属性了,我们要把它传到menu-bar中,因为我们要在menu-bar中去控制右上角的显示状态。

在这个GUI Component中使用了components/gui/gui.jsx定义的GUIComponent这一component,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,

..ponentProps } = omit(props, 'dispatch');

...再在使用MenuBar的地方也为MenuBar定义`loginState`属性,它的值就是GUIComponent传进来的`loginState`的值:

accountNavOpen={accountNavOpen} 

authorId={authorId} 

authorThumbnailUrl={authorThumbnailUrl} 

authorUsername={authorUsername}

canCreateCopy={canCreateCopy} 

canCreateNew={canCreateNew} 

canEditTitle={canEditTitle} 

canRemix={canRemix} 

canSave={canSave}

canShare={canShare} 

className={styles.menuBarPosition} 

enableCommunity={enableCommunity} 

isShared={isShared}

renderLogin={renderLogin} 

showComingSoon={showComingSoon} 

onclickAccountNav={onclickAccountNav} 

onclickLogo={onclickLogo}

onCloseAccountNav={onCloseAccountNav} 

onLogOut={onLogOut} 

onOpenRegistration={onOpenRegistration}

onProjectTelemetryEvent={onProjectTelemetryEvent} 

onSeeCommunity={onSeeCommunity} 

onShare={onShare} 

onToggleLoginOpen={onToggleLoginOpen}

onUpdateProjectTitle={onUpdateProjectTitle} 

loginState={loginState} 

 />

 

 最后修改components/menu-bar.jsx中的MenuBar组件的显示,将右上角替换成:

{this.props.loginState ? ({'scratch-cat'}) :Login} 

如果用户已登录,就显示头像和姓名的样式(具体的用户信息需要跟后台打通,我们后面再实现):

否则显示登录按钮:

我们可以通过修改reducers/user-state.js中的loginState的初始值来查看效果:

loginState: UserState.NOT_LOGINED

loginState: UserState.LOGINED这个值我们会在后面根据用户登录的token去获取。

为了与项目整体风格一致,我们修改这个登录按钮的样式,在menu-bar目录中添加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}) => (

【scratch二次开发教程 第9章】scratch-gui中集成自定义用户系统(六)

管理员
2019-12-14
1266
						

写在前面

该系列文章是为具有开发能力的朋友写作的,目的是帮助他们在scratch 3.0的基础上开发一套完整的集scratch 3.0编程工具、用户社区和作品云端存储及分享、品牌集成于一体的scratch编程平台。如果您不是开发者,但想要拥有自己的教育平台和品牌,也欢迎学习交流和洽谈合作。

所以如果您是想学习scratch少儿编程课程,那请忽略该系列的文章。

前言

前面我们把登录的基本流程实现完成了,不过还留了一个小尾巴,就是用户登出,清除用户的token信息,以确保用户账号的安全性。

我们准备在用户姓名下面做一个下拉菜单,将登出操作放在里面,以后还会在里面放入个人中心,个人作品等菜单项,就像现在mit官网那样:

下面就来实现这一过程。

开始实现

先来实现组件的样式。

找到components/menu-bar/menu-bar.jsx,在之前我们修改的显示头像和姓名的下面,添加一个MenuBarMenu组件,如下:

我们将其open属性先设置为true,让它一直显示,以方便我们调试样式。

编译运行,看到在用户姓名和头像下面正常显示了登出菜单:

下一步来控制菜单的显示与隐藏。

在components/menu-bar/menu-bar.jsx中,为组件MenuBar增加props:userMenuOpen, onclickUser和onRequestCloseUser,分别表示用户判断下拉菜单是否打开,打开下拉菜单操作和关闭下拉菜单操作:

userMenuOpen: PropTypes.bool,onRequestCloseUser: PropTypes.func,onclickUser: PropTypes.func,

在reducers/menus.js中,增加userMenu状态:

const MENU_USER = 'userMenu';

初始为false关闭状态:

const initialState = {[MENU_ACCOUNT]: false,[MENU_FILE]: false,[MENU_EDIT]: false,[MENU_LANGUAGE]: false,[MENU_LOGIN]: false,[MENU_USER]: false};

增加打开和关闭以及状态检测的actions:

const openUserMenu = () => openMenu(MENU_USER);const closeUserMenu = () => closeMenu(MENU_USER);const userMenuOpen = state => state.scratchGui.menus[MENU_USER];

记得export以上actions:

回到components/menu-bar/menu-bar.jsx中,映射刚才的userMenuOpen props和state:

映射onclickUser:

映射onRequestCloseUser:

再完善之前显示MenuBar的控制,现在我们根据之前设置的props来控制菜单的显示和关闭:

重新运行,登录成功后并没有菜单显示,点击用户信息,出现下拉菜单,再点击,菜单关闭,一切都按照我们的设计进行的。

最后一步,我们来实现登出的过程。

先到reducers/user-state.js中增加登出的功能实现。

登出actions:

const DONE_USER_LOGOUT = 'DONE_USER_LOGOUT'; // 标识登出过程成功完成
const logoutSuccess = () => ({type: DONE_USER_LOGOUT});

登出处理方法,先清空本地token,再关闭下拉菜单,清空state中的用户信息:

const logout = dispatch => {localStorage.removeItem('shala_token');dispatch(closeUserMenu());dispatch(logoutSuccess());};

reducer里更新state:

export logout方法:

回到components/menu-bar/menu-bar.jsx中,为MenuBar组件增加props onclickLogout:

onclickLogout: PropTypes.func,

并映射dispatch:

onclickLogout: () => logout(dispatch)

最后在MenuItem的onclick方法中调用它:

运行项目成功登录,点击下拉菜单,登出,用户信息消失并出现登录按钮。打开浏览器开发工具,看到localStorage中存储的token也被清空了。说明成功登出了。

这里登出时我们没有向后台发送请求,让后台也做相关的登出操作,目前来看并没有什么大问题,只要保证客户端没有有效的token存在就行,如果后面有更高的安全需求考虑我们再来进一步完善它。

另外,在登出时,我们可能正在进行scratch项目创作,并且还没有保存,这时需要提示用户进行相关的操作,并且退出后清空用户的工作区内容。

以上工作我们在后面将作品云端保存和加载功能实现后再来一起完善它。

所以下一章起,我们计划开始集成作品管理系统,实现用户scratch作品集的云端保存和载入以及相关管理功能。对于用户系统,其实至少还需要一个管理的页面,我们计划留到作品管理系统集成完后再来完成,因为到那时我们的系统的基本框架就差不多了,围绕基本框架来丰富功能模块就会清晰很多。