(可选)开发自定义登录组件
“开发自定义登录组件”步骤可以直接跳过,本示例已为您提供了开发好的自定义登录组件。如果您想要自定义登录组件的开发方法,可参考本章节执行。
自定义登录组件
- 进入创建“设备维修管理系统”应用中创建的应用。
- 在左侧资产下的组件模板中,单击“widgetVue3Template”,再单击“下载”。
图1 下载组件模板
- 在弹出的窗口中,输入组件名称,并单击“保存”,将组件模板保存到本地,并解压。
图2 输入组件名称
- 查看解压后的组件目录。
目录结构如图3所示,其中userLogin.js文件是写vue业务逻辑的代码,userLogin.ftl用于写html代码,userLogin.css用于写样式代码,userLogin.editor.js以及packageinfo.json是配置文件。
- 在解压后的文件夹中,创建一个imgs文件夹,并放入一个登录页面的背景图片。
假设放入的背景图片名称为“imagebg.jpg”。
- 在本地编辑器中打开文件夹,把userLogin.editor.js文件中的propertiesConfig代码改为如下代码,用于配置桥接器。
propertiesConfig: [ { headerTitle: 'Parameters', accordion: true, accordionState: 'open', config: [ { type: 'text', name: 'loginAPI', label: 'login API', value: '', }, ], }, { config: [ { type: 'connectorV2', name: 'FlowConnector', label: 'Flow Connector', model: 'ViewModel', }, { type: 'connectorV2', name: 'common.GetConnector', label: 'View API Get Connector', model: 'ViewModel', }, { type: 'connectorV2', name: 'common.PostConnector', label: 'View API Post Connector', model: 'ViewModel', }, { type: 'connectorV2', name: 'common.PutConnector', label: 'View API Put Connector', model: 'ViewModel', }, { type: 'connectorV2', name: 'common.DeleteConnector', label: 'View API Delete Connector', model: 'ViewModel', }, ], }, ],
- 把packageinfo.json文件中加入如下加粗内容。
{ "widgetApi": [ { "name": "userLogin" } ], "widgetDescription": "", "authorName": "", "localFileBasePath": "", "requires": [ { "name": "global_Vue3", "version": "3.3.4" }, { "name": "global_ElementPlus", "version": "2.6.0" }, { "name": "global_Vue3I18n", "version": "9.10.1" }, { "name": "global_Vue3Router", "version": "4.3.0" } ] }
- 将userLogin.ftl文件中的内容,替换为如下示例。
<style> [v-cloak] { display: none } </style> <div v-cloak id="userLogin" class="page-login" v-cloak> <div :class="backgroundClass" class="bg-box"> <!-- <div class="title"> <span>设备管理系统</span> </div> --> <div class="login-box"> <div class="login-title">用户登录</div> <input name="username" type="text" style="display: none" /> <input name="password" type="password" style="display: none" /> <div class="login-form"> <div v-show="errorDesc" class="error-line"> <!-- <img :src="BasePath + 'imgs/btn_errorInfo.png'" /> --> <span class="error-text" v-html="errorDesc"></span> </div> <div class="login-item mg-top10"> <!-- :placeholder="isUserName ? getTransLang('ds.commerce.storefront.web.loginname') : getTransLang('ds.commerce.storefront.web.loginEmailOrPhone')" --> <input ref="accountInput" v-model="account" placeholder="请输入用户名" @keyup.enter="validateBeforeSubmit" autocomplete="off" maxlength="32" type="text" /> </div> <div class="login-item mg-top10"> <!-- :placeholder="getTransLang('ds.commerce.storefront.web.password')" --> <input v-model="password" @keyup.enter="validateBeforeSubmit" placeholder="请输入密码" autocomplete="off" maxlength="32" type="password" /> </div> <div v-if="needVerify" class="login-item"> <!-- :placeholder="getTransLang('ds.commerce.storefront.web.verificationcode')" --> <input v-model="inputImgCode" @keyup.enter="validateBeforeSubmit" placeholder="请输入验证码" autocomplete="off" maxlength="10" type="text" /> <div class="verify-code"> <img :src="imgCode" @click="getVerifyCode()" /> </div> </div> <div class="login-button" @click="validateBeforeSubmit"> 登录 </div> </div> </div> </div> </div>
- 将userLogin.js文件内容,替换为如下示例。
userLogin = StudioWidgetWrapper.extend({ init: function () { var thisObj = this thisObj._super.apply(thisObj, arguments) thisObj.render() thisObj.initBusi() if (typeof Studio != 'undefined' && Studio) { Studio.registerEvents(thisObj, 'goHomepage', 'go Homepage', []) } }, render: function () { var thisObj = this let tenantId; if (Studio.inReader) { tenantId = STUDIO_DATA.catalogProperties["tenant-id"].value } else { tenantId = magno.pageService.getCatalogProperties()["tenant-id"].value } HttpUtils.setCookie("tenant-id", tenantId) HttpUtils.setCookie("locale","zh_CN"); var elem = thisObj.getContainer() if (elem) { var containerDiv = $('.scfClientRenderedContainer', elem) if (containerDiv.length) { $(containerDiv).empty() } else { containerDiv = document.createElement('div') containerDiv.className = 'scfClientRenderedContainer' $(elem).append(containerDiv) } } thisObj.sksBindItemEvent() $(window).resize(function () { thisObj.sksRefreshEvents() }) }, initBusi: function () { var thisObj = this var widgetProperties = thisObj.getProperties() var BasePath = thisObj.getWidgetBasePath() // 本地路径 var elem = thisObj.getContainer() const app = Vue.createApp({ data(){ return { BasePath: BasePath, imgCode: '', account: '', password: '', inputImgCode: '', errorDesc: '', needRead: false, backgroundClass: 'backgroundClass', needVerify: false, accountName: '', isMobile: false, } }, created() { this.getVerifyCode() this.checkIsLogin() this.isMobile = /Android|webOS|iPhone|iPad|BlackBerry/i.test( navigator.userAgent ) }, watch: { account(newVal, oldVal) { if (newVal !== oldVal) { this.password = '' } }, }, methods: { checkIsLogin() { const userInfo = JSON.parse( window.sessionStorage.getItem('userInfo') ) if (userInfo !== null && userInfo.username) { setTimeout(() => { thisObj.triggerEvent('goHomepage', {}) }, 1000) } }, shouldShowImgCode() { connService( thisObj, `${ds_baseUrl}/identities/isNeedCaptcha`, { username: this.account, }, 'common.PostConnector' ) .then((res) => { const { data = [], resp = {} } = res if (resp.code !== '0') { this.$message.error(resp.message) } if (data && data.length) { this.needVerify = data[0].needVerify if (!this.needVerify) { this.confirmLogin() } } }) .catch((e) => { this.$message.error(e.response.resMsg) }) }, // 登录按钮 validateBeforeSubmit() { if (!this.password) { this.errorDesc = '请输入密码。' this.needVerify = true return false } if (!this.account) { this.errorDesc = '请输入用户名。' this.needVerify = true return false } if (!this.inputImgCode && this.needVerify) { this.errorDesc = '验证码错误。' return false } if (this.needVerify && this.inputImgCode) { this.confirmLogin() return } this.confirmLogin() }, // 调登录接口 confirmLogin() { var request = { username: this.account, password: this.password, captcha: this.inputImgCode, } // this.callFlowConn1( widgetProperties.loginAPI, request, this.callLogin, this.loginFail, 'post' ) }, // 登录接口成功函数 callLogin(response) { if (response) { this.errorDesc = '' let userInfo = { username: response.data[0].username, userId: response.data[0].userId, profile: response.data[0].profile, } HttpUtils.setCookie('isLogged', true) window.sessionStorage.setItem( 'userInfo', JSON.stringify(userInfo) ) thisObj.triggerEvent('goHomepage', {}) } }, // 登录失败resMsg loginFail(data){ this.errorDesc = data.response.resMsg this.getVerifyCode() this.needVerify = true }, // 获取验证码 getVerifyCode() { this.imgCode = '/u-route/baas/sys/v1.0/verificationcode?type=login&t=' + Date.parse(new Date()) }, // 封装flow调用后台 callFlowConn1: function (service, param, callbackFunc,callbackfail, method) { var thisView = this let mMethod switch (method) { case 'get': mMethod = 'common.GetConnector' break case 'post': mMethod = 'common.PostConnector' break case 'put': mMethod = 'common.PutConnector' break default: mMethod = 'common.FlowConnector' break } var connector = thisObj.getConnectorInstanceByName(mMethod) if (connector) { connector.setInputParams({ service: service, needSchema: 'data', async: false, }) connector .query(param) // 调用接口,以param为入参 .done(function (response) { if (response.resp && response.resp.code) { callbackFunc.call(thisView, response) } }) .fail(function (response) { // 代表接口执行失败 callbackfail.call(thisView, response) }) } }, }, }) app.use(ElementPlus); thisObj.vm = app.mount($("#userLogin", elem)[0]); }, })
- 将userLogin.css文件内容,替换为如下示例。
需要注意的是,“background”的取值需要和5中创建的文件夹和图片名称保持一致。
.page-login { /* 使用webkit内核的浏览器 */ /* Firefox版本4-18 */ /* Firefox版本19+ */ } .page-login * { box-sizing: border-box; } .page-login .flex { display: flex; } .page-login .login_tip { font-size: 12px; color: #167aeb; } .page-login .bg-box { position: relative; display: flex; justify-content: center; width: 100%; height: 100%; background: url('imgs/imagebg.jpg') no-repeat 50%; background-size: cover; } .page-login .bg-box > img { width: 100%; } @media (min-width: 767px) { .page-login .bg-box .login-box { position: absolute; right: 18%; top: 26%; padding: 30px 30px 20px 30px; width: 380px; background: #fff; border-radius: 10px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.1); } .page-login .bg-box .login-box .login-title { font-size: 24px; color: #333; text-align: center; } .page-login .bg-box .login-box .login-form { padding-top: 20px; } .page-login .bg-box .login-box .login-form .error-line { display: flex; align-items: center; background: #ffebeb; color: #e4393c; border: 1px solid #faccc6; padding: 3px 10px 3px 10px; height: auto; text-align: left; font-size: 12px; } .page-login .bg-box .login-box .login-form .error-line .error-text { padding-left: 5px; } .page-login .bg-box .login-box .login-form .error-line .error-text .change_tab { color: #167aeb; cursor: pointer; } .page-login .bg-box .login-box .login-form .login-item { display: flex; align-items: center; margin-top: 20px; } .page-login .bg-box .login-box .login-form .login-item .el-input__inner { height: 36px; } .page-login .bg-box .login-box .login-form .login-item > input { flex: 1; padding: 5px 10px; height: 36px; border-radius: 3px; border: 1px solid #ddd; } .page-login .bg-box .login-box .login-form .login-item .verify-code { height: 36px; margin-left: 5px; } .page-login .bg-box .login-box .login-form .login-item .verify-code img { height: 100%; } .page-login .bg-box .login-box .login-form .login-item .el-checkbox { margin-right: 10px; } .page-login .bg-box .login-box .login-form .login-item .read-text { font-size: 12px; color: #333; } .page-login .bg-box .login-box .login-form .login-item .read-text .clickable { color: #167aeb; cursor: pointer; } .page-login .bg-box .login-box .login-form .login-item .read-text .clickable:hover { text-decoration: underline; } .page-login .bg-box .login-box .login-form .mg-top10 { margin-top: 10px; } .page-login .bg-box .login-box .login-button { margin-top: 20px; height: 40px; line-height: 40px; border-radius: 3px; background: #3d88ff; text-align: center; color: #fff; font-size: 14px; cursor: pointer; } .page-login .bg-box .login-box .login-button:hover { opacity: 0.8; } .page-login .bg-box .login-box .divide-line { margin-top: 20px; height: 1px; background: #eee; opacity: 0.8; } .page-login .bg-box .login-box .login-bottom { margin-top: 20px; justify-content: center; align-items: center; } .page-login .bg-box .login-box .login-bottom .bottom-text { font-size: 12px; color: #999; } .page-login .bg-box .login-box .login-bottom .type-item { margin-left: 10px; width: 30px; height: 23px; cursor: pointer; } .page-login .bg-box .login-box .login-bottom .type-item img { width: 100%; } } @media (max-width: 767px) { .page-login .bg-box .login-box { position: absolute; right: auto; top: 26%; padding: 30px 30px 20px 30px; width: 380px; background: #fff; border-radius: 10px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.1); } .page-login .bg-box .login-box .login-title { font-size: 24px; color: #333; text-align: center; } .page-login .bg-box .login-box .login-form { padding-top: 20px; } .page-login .bg-box .login-box .login-form .error-line { display: flex; align-items: center; background: #ffebeb; color: #e4393c; border: 1px solid #faccc6; padding: 3px 10px 3px 10px; height: auto; text-align: left; font-size: 12px; } .page-login .bg-box .login-box .login-form .error-line .error-text { padding-left: 5px; } .page-login .bg-box .login-box .login-form .error-line .error-text .change_tab { color: #167aeb; cursor: pointer; } .page-login .bg-box .login-box .login-form .login-item { display: flex; align-items: center; margin-top: 20px; } .page-login .bg-box .login-box .login-form .login-item .el-input__inner { height: 36px; } .page-login .bg-box .login-box .login-form .login-item > input { flex: 1; padding: 5px 10px; height: 36px; border-radius: 3px; border: 1px solid #ddd; } .page-login .bg-box .login-box .login-form .login-item .verify-code { height: 36px; margin-left: 5px; } .page-login .bg-box .login-box .login-form .login-item .verify-code img { height: 100%; } .page-login .bg-box .login-box .login-form .login-item .el-checkbox { margin-right: 10px; } .page-login .bg-box .login-box .login-form .login-item .read-text { font-size: 12px; color: #333; } .page-login .bg-box .login-box .login-form .login-item .read-text .clickable { color: #167aeb; cursor: pointer; } .page-login .bg-box .login-box .login-form .login-item .read-text .clickable:hover { text-decoration: underline; } .page-login .bg-box .login-box .login-form .mg-top10 { margin-top: 10px; } .page-login .bg-box .login-box .login-button { margin-top: 20px; height: 40px; line-height: 40px; border-radius: 3px; background: #3d88ff; text-align: center; color: #fff; font-size: 14px; cursor: pointer; } .page-login .bg-box .login-box .login-button:hover { opacity: 0.8; } .page-login .bg-box .login-box .divide-line { margin-top: 20px; height: 1px; background: #eee; opacity: 0.8; } .page-login .bg-box .login-box .login-bottom { margin-top: 20px; justify-content: center; align-items: center; } .page-login .bg-box .login-box .login-bottom .bottom-text { font-size: 12px; color: #999; } .page-login .bg-box .login-box .login-bottom .type-item { margin-left: 10px; width: 30px; height: 23px; cursor: pointer; } .page-login .bg-box .login-box .login-bottom .type-item img { width: 100%; } } .page-login ::-webkit-input-placeholder { font-size: 12px; color: #aaa; } .page-login :-moz-placeholder { font-size: 12px; color: #aaa; } .page-login ::-moz-placeholder { font-size: 12px; color: #aaa; } .page-login :-ms-input-placeholder { font-size: 12px; color: #aaa; } .page-login .logining-text { margin-top: 120px; text-align: center; font-size: 30px; color: #333; }
- 将修改后的组件文件压缩成一个zip包。
压缩后,即可根据需要上传到华为云Astro轻应用,供高级页面使用。
更新自定义组件
当开发的自定义组件功能有变动,即组件代码发生变动后,需要更新组件版本。更新后,组件所在的页面也会随之生效。
- 在应用中,单击左下方的“页面设置”,再选择“插件”页签,找到需要更新的组件(例如userLogin),单击组件所在行右侧“查看详情”,进入组件详情页。
图4 页面设置下的组件列表
如果页面
图标高亮,则需要先单击
解锁页面。
- 单击“更新”按钮进入组件更新页面。
图5 选择更新按钮
- 单击“请选择源文件(.zip)”,上传本地的组件zip包,再单击“更新”。
图6 上传本地组件包
- 返回“插件”页签,单击刚刚上传组件(userLogin)的升级按钮
,然后单击
保存升级,最后单击
发布即可。
图7 更新组件