
# （可选）开发自定义登录组件
"开发自定义登录组件"步骤可以直接跳过，本示例已为您提供了开发好的自定义登录组件。如果您想要自定义登录组件的开发方法，可参考本章节执行。
#### 自定义登录组件
1. 进入[创建"设备维修管理系统"应用](https://support.huaweicloud.com/bestpractice-astrozero/astrozero_07_1009.html)中创建的应用。
2. 在左侧资产下的组件模板中，单击"widgetVue3Template"，再单击"下载"。 
   图1下载组件模板   
   ![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000002131900256.png "点击放大")
   
   
3. 在弹出的窗口中，输入组件名称，并单击"保存"，将组件模板保存到本地，并解压。 
   图2输入组件名称   
   ![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000002167220277.png "点击放大")
   
   
4. 查看解压后的组件目录。 
   目录结构如[图3]所示，其中userLogin.js文件是写vue业务逻辑的代码，userLogin.ftl用于写html代码，userLogin.css用于写样式代码，userLogin.editor.js以及packageinfo.json是配置文件。
   图3目录结构   
   ![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000002167182141.png "点击放大")
   
   
5. 在解压后的文件夹中，创建一个imgs文件夹，并放入一个登录页面的背景图片。
   
   假设放入的背景图片名称为"imagebg.jpg"。
   
   
6. 在本地编辑器中打开文件夹，把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',
   },
   ],
   },
   ],
   ```
   
   
7. 把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"
   }
   ]
   }
   ```
   
   
8. 将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>
   ```
   
   
9. 将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]);
   },
   })
   ```
   
   
10. 将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;
    }
    ```
    
    
11. 将修改后的组件文件压缩成一个zip包。 
    压缩后，即可根据需要上传到华为云Astro轻应用，供高级页面使用。
    
    
 
#### 更新自定义组件
当开发的自定义组件功能有变动，即组件代码发生变动后，需要更新组件版本。更新后，组件所在的页面也会随之生效。
1. 在应用中，单击左下方的"页面设置"，再选择"插件"页签，找到需要更新的组件（例如userLogin），单击组件所在行右侧"查看详情"，进入组件详情页。 
   图4页面设置下的组件列表   
   ![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000002143216734.png "点击放大")
   如果页面![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000002325859286.png)图标高亮，则需要先单击![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000002326019106.png)解锁页面。
   
   
2. 单击"更新"按钮进入组件更新页面。 
   图5选择更新按钮   
   ![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000002143217450.png "点击放大")
   
   
3. 单击"请选择源文件(.zip)"，上传本地的组件zip包，再单击"更新"。 
   图6上传本地组件包   
   ![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000002143376366.png "点击放大")
   
   
4. 返回"插件"页签，单击刚刚上传组件（userLogin）的升级按钮![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000001920233088.png)，然后单击![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000001949072313.png "点击放大")保存升级，最后单击![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000001920073420.png)发布即可。
   
   图7更新组件   
   ![](https://support.huaweicloud.com/bestpractice-astrozero/zh-cn_image_0000002178696029.png "点击放大")
   
   
 
