第4(b)部分:如何使用VUE JS构建待办事项应用程序:创建可重复使用的UI
#javascript #网络开发人员 #前端 #vue

在我们项目的上一个阶段中,我们通过设置项目样板,集成必要的库以及编写Login.vueRegister.vueDashboard.vue(例如Register.vueDashboard.vue)来成功建立了基础方面。我们还配置了路由以确保整个应用程序中的平稳导航。

如果您达到了这一点,恭喜这项出色的成就!现在,我们即将深入研究可重复使用的组件的领域,这是建立模块化和可维护应用的关键一步。

要提供我们当前文件夹结构的快照,您可以参考下图:

folder-structure

/src/components目录中,让我们根据其功能将组件组织到不同的文件夹中。您将创建四个主要文件夹:/auth/common/dashboard/utility

虽然可以根据您的喜好对名称进行调整,但该结构促进了一种以功能为中心的方法。例如,与身份验证关联的组件将位于/src/auth文件夹中,并且类似地用于其他功能。

但是,/src/common文件夹将是我们制作可重复使用组件的起点。

创建一个“ Navbar”组件:

/src/components/common/Navbar.vue

<template>
   <header class="flex justify-between items-center bg-primary p-5">
      <h1 class="font-popins text-4xl text-[#fff]"> {{ returnResponsiveTitle }}</h1>
      <div class="flex items-center">    
         <nav v-if="!isMobile">
            <ul class="flex ">
               <li
                  class="p-4 w-[120px] text-center font-popins rounded-2xl" 
                  v-for="(url , index) in urlDatas " :key="index">
                  <router-link class="text-[#FFFFFF] " :to="{name: url.url}">{{ url.name }}</router-link>
               </li>
            </ul>
         </nav>
         <div class="flex items-center  justify-center bg-[#fff] w-[50px] h-[50px] rounded-full">
            <h1 class="text-2xl font-popins font-bold">MA</h1>
         </div>
         <div v-if="isMobile" class="flex items-center justify-center p-2 rounded-sm">
            <font-awesome-icon
            @click="toggleNav" 
            class="text-4xl text-[#ffff] cursor-pointer"
            :icon="['fas', `${showNav ? 'times': 'hamburger'}`]"/>
         </div>
      </div>
   </header>
   <!-- show on mobile -->
      <div class="bg-primary" v-if="isMobile">
         <div class="flex flex-col" v-if="showNav">    
            <nav>
               <ul class="flex flex-col">
                  <li
                     class="p-4 w-[120px]  font-popins rounded-2xl" 
                     v-for="(url , index) in urlDatas " :key="index">
                     <router-link class="text-[#FFFFFF] " :to="{name: url.url}">{{ url.name }}</router-link>
                  </li>
               </ul>
            </nav>
         </div>
      </div>
</template>

<script>
export default {
   name:'NavBar',
   data(){
      return {
        urlDatas: [
            {
              url: 'login',
              name: 'Login',
            },
            {
              url: 'register',
              name: 'Register',
            },
            {
              url: 'dashboard',
              name: 'Dashboard',
            }
        ],
        isMobile: false,
        showNav: false,
      }
   },
   mounted(){
      this.checkScreenSize();
      addEventListener('resize' , this.checkScreenSize )
   },
   methods: {
      checkScreenSize() {
         this.isMobile = window.innerWidth <= '768'
      },
      toggleNav(){
         this.showNav = !this.showNav
      }
   },
   computed: {
      returnResponsiveTitle() {
         if(this.isMobile){
            return 'Vma'
         }
         return 'VueMadeEasy'
      }
   }
}
</script>


此“ Navbar”组件是一个响应式导航栏,可根据屏幕尺寸调整其布局。它具有一个动态标题,该标题在移动设备和“ Vuemadeeasy”的“ VMA”之间更改。导航项目显示为链接,并且在移动设备上显示一个切换按钮以展开或折叠导航菜单。

接下来,您将注册并使用app.vue文件中的navbar组件。

/src/App.vue

<template>
  <NavBar />
  <RouterView />
</template>
<script>
import NavBar from './components/common/NavBar.vue'

export default {
  components: {
    NavBar
  }
}
</script>

通过在App.Vue中集成“ Navbar”组件,您已经在应用程序上建立了一个一致的导航元素。这种模块化方法简化了开发并确保组件的可重复使用性。

现在,让我们继续使用/utility文件夹。在这里,您将创建组件,这些组件用作应用程序多个部分中使用的构件。这些组件在保持一致性的同时显着增强了控制和灵活性。

考虑更改应用程序中输入字段的焦点颜色。您永远不想在任何地方开始这样做。在这种情况下,您只需将所有必要的更改应用于/utility文件夹中的组件即可。瞧!更改将反映在使用组件的应用程序的所有领域。

创建实用程序组件:

/src/components/utility/BaseButton.vue

<template>
   <div class="div">
      <label 
      class="size-2xl"
      :for="id">{{ label }}</label>
      <input 
         :id="id"
         :class="[isFocused && 'focused']"
         class="p-2 border-2 border-[#eee] w-full rounded-xl"
         :type="text"
         :placeholder="placeHolder"
         @focus="isFocused = true"
         @blur="isFocused = false"
         @input="handleText"
         :value="value"
         :aria-label="label"
         :aria-describedby="`${id}-description`"
      >
      <div 
         v-if="error" 
         class="h-[10px] mt-1">
         <span class="text-[red]">{{ msg }}</span>
      </div>
   </div>
</template>

<script>
   export default {
      name:'BaseInput',
      props: {
         id: String,
         label:String,
         text: String,
         placeHolder: String,
         value: [String , Number],
         error: {
            type: Boolean,
            default: false,
         },
         msg: {
            type: String,
            msg: '',
         }
      },
      data(){
         return {
           isFocused: false,
         }
      },
      methods: {
         handleText(e){
           this.$emit('update:modelValue', e.target.value )
         }
      }
   }
</script>

<style scoped>
.focused{
   outline: 2px solid #414066;
   box-shadow: 0px 2px 5px rgba(0, 0, 0, .3);
}
</style>

在脚本部分中,该组件被命名为“ baseinput”,并采用包括ID,标签,占位符,值,错误和MSG在内的道具。它利用ISFOCEDE数据属性来监视输入焦点,而Handletext方法触发了update:modelValue事件,在更改输入值时会发出输入值。

/src/components/utility/BaseModal.vue

<template>
   <div class="flex items-center justify-center h-[100%] bg-[#242222b7] w-full">   
      <div class="bg-[#fff] w-1/5 h-2/5 flex flex-col drop-shadow-sm rounded-lg">
         <span class="flex items-center justify-end  p-4 right-0">
            <font-awesome-icon 
            @click="handleClose"
            class="mb-5 text-2xl c-tst cursor-pointer" :icon="['fa', 'times']" /> 
         </span>
         <div class="flex flex-col items-center justify-center w-full">    
            <font-awesome-icon class="text-7xl mb-5 text-[green]" :icon="['fa', 'circle-check']" />
            <h1 class="text-4xl font-popins font-bold">Success</h1>
         </div>
      </div>
   </div>
</template>

<script>
   export default {
      name:'BaseModal',
      methods: {
         handleClose(){
            this.$emit('close')
         }
      }
   }
</script>

接下来,使用暗半透明背景定义模态组件。在模态内部,有一个带有白色背景的容器,显示了一个检查标记图标和成功消息。由时图表示的关闭按钮位于右上角。

在脚本部分中,名为“ basemodal”的组件包括一个名为“ handleclose”的方法,该方法在触发时发出'Close'事件。

此组件似乎是一种成功模式,它显示了Checkmark图标以及成功消息和关闭按钮。单击时,“关闭”按钮会发出自定义的“关闭”事件,允许父组件处理模态闭合行为。

modal

/src/components/utility/BaseButton

<template>
   <button
      :class="['p-3 text-center w-full rounded-4xl', variantClass ]"
      :disabled="disabled"
      :aria-disabled="disabled"
      @click="handleClick"
      type="submit"
   >
    {{ label }}
  </button>
</template>

<script>
   export default {
      name:'BaseButton',
      props: {
         label: String,
         variant: {
            type: String,
            default: 'primary',
         },
         disabled: Boolean,
      },
      computed: {
         variantClass(){
            if(this.disabled){
               return  `variant-${this.variant}-disabled`;
            }
            return `variant-${this.variant}`;
         }
      },
      methods: {
         handleClick() {
            this.$emit('clk');
         },
      }
   }
</script>

<style>
   /* variant style */
   .variant-primary {
      background-color: #414066;
      color: #fff;
   }

   .variant-primary-disabled {
      background-color: #414066c3;
      cursor: not-allowed;
   }

   .variant-secondary {
      background-color: #4C6640;
      color: #fff;
   }

   .variant-secondary-disabled {
      background-color: #4c6640b4;
      cursor: not-allowed;
   }

</style>

接下来,为“ basebutton”创建.VUE文件。根据文件中的变体和禁用状态定义一个带有动态类的按钮。按钮的单击事件触发了发射“ clk”事件的处理方法。

在脚本部分中,名为“ basebutton”的组件接受包括标签,变体和禁用的道具。计算的属性“ VariantClass”基于变体和残疾状态动态生成类名称。 HandleClick方法发出了“ CLK”事件。

样式部分包括针对按钮不同变体的CSS规则,根据变体和残疾状态调整背景颜色和光标。

/src/components/utility/BaseToast.vue

<template>
   <div v-if="show" class="p-3 flex border-t-4 border-t-[#525252] justify-between items-center bg-[#fee2e2] h-[70px]">
      <h2 class="text-[#e74e3c] text-sm">Oops! {{ msg }}</h2>
      <div class="p-3">
         <font-awesome-icon 
         class="text-4xl text-[#e74e3c]"
         :icon="['fas', 'exclamation']" />
      </div>
   </div>
</template>

<script>
   export default {
      name:'AppToast',
      props: {
         variant: {
           type: String,
           default: 'default',
         },
         show: Boolean,
         msg: String,
      },
   }
</script>

A Toast组件是根据“表演”道具的条件渲染而创建的。烤面包在彩色条中显示消息和图标。该消息是用“糟糕!”动态生成的。字首。条形和图标的颜色由“变体”支撑

确定

在脚本部分中,名为“ apptoast”的组件接受包括“变体”,“ show”和“ msg”的道具。 “变体”支柱定义了配色方案,“显示”控制是否显示了吐司,而“ msg”保存了消息内容。

此组件旨在显示带有可自定义消息和颜色的通知敬酒,为用户提供了各种消息或警报的可视化指示器。

现在您已经创建了所需的所有实用程序组件,让我们使用它们来创建您的“登录”和“注册”屏幕。

/src/components/auth/AuthLogin.vue


<template>
   <auth-wrapper class="m-auto mt-5">
      <h1 class="text-primary pt-5 pb-5 font-bold text-2xl font-popins">Login</h1>
      <app-input 
         :id = "'username'" 
         :label = "'Username :'"
         :value = "userName"
         :error = "user.error"
         :text = "'text'"
         :placeHolder="'Enter your username'"
         :msg = "user.msg"
         v-model="userName"
      />
      <app-input 
         class="mt-7"
         :id = "password" 
         :label = "'Password :'"
         :text = "'password'"
         :placeHolder="'Enter your password'"
         :value = "password"
         :error = "user.error"
         :msg = "user.msg"
         v-model="userName"
      />
      <app-button 
         class="mt-7 mb-3"
         :disabled="false" 
         :label="'Sign In'" 
         :variant="'primary'"
         @clk="handleSubmit"
      />
      <p class="mb-10 text-right">Already have an account? 
      <router-link 
      class="text-primary" 
      :to="{name:'register'}">sign up
      </router-link></p>
   </auth-wrapper>   
</template>

<script>
   import AppButton from '../utility/BaseButton.vue';
   import AppInput from '../utility/BaseInput.vue';
   import AuthWrapper from './part/AuthWrapper.vue';

   export default {
      name:'AuthLogin',
      components: {
         AuthWrapper,
         AppInput,
         AppButton,
      },
      data(){
         return {
            userName: '',
            password: '',
            user: {
               error: false,
               msg: 'invalid input field!',
            }
         }
      },
      methods: {
         handleSubmit(){
            console.log('hello')
         }
      }
   }
</script>

接下来,您必须在登录视图中使用此组件

src/views/Login.vue

<template>
   <auth-login/>
</template>

<script>
import AuthLogin from '../components/auth/AuthLogin.vue'

export default {
   name:'LoginView',
   components: {
      AuthLogin
   },
}
</script>

此代码创建一个称为“ loginview”的视图组件,该组件使用“ authlogin”组件呈现用户登录表单。 “ authlogin”组件被导入并注册为儿童组件以可重复使用和模块化。

login-snapshot

/src/components/auth/AuthSignup.vue

<template>
   <auth-wrapper class="m-auto mt-5">
      <h1 class="text-primary pt-5 pb-5 font-bold text-2xl font-popins">Register</h1>
      <app-input 
         :id = "'username'" 
         :label = "'Username :'"
         :value = "userName"
         :error = "user.error"
         :text = "'text'"
         :placeHolder="'Enter your username'"
         :msg = "user.msg"
         v-model="userName"
      />

      <app-input 
         class="mt-7"
         :id = "'email'" 
         :label = "'Email :'"
         :value = "email"
         :error = "user.error"
         :text = "'email'"
         :placeHolder="'Enter your username'"
         :msg = "user.msg"
         v-model="email"
      />

      <app-input 
         class="mt-7"
         :id = "password" 
         :label = "'Password :'"
         :text = "'password'"
         :placeHolder="'Enter your password'"
         :value = "password"
         :error = "user.error"
         :msg = "user.msg"
         v-model="userName"
      />
      <app-input 
         class="mt-7"
         :id = "'confpass'" 
         :label = "'Confirm Password :'"
         :text = "'password'"
         :placeHolder="'Confirm your password'"
         :value = "confirmPass"
         :error = "user.error"
         :msg = "user.msg"
         v-model="confirmPass"
      />
      <app-button 
         class="mt-7 mb-3"
         :disabled="false" 
         :label="'Sign In'" 
         :variant="'primary'"
         @clk="handleSubmit"
      />
      <p class="mb-10 text-right">Already have an account? 
      <router-link 
      class="text-primary" 
      :to="{name:'login'}">sign in
      </router-link></p>
   </auth-wrapper>   
</template>

<script>
   import AppButton from '../utility/BaseButton.vue';
   import AppInput from '../utility/BaseInput.vue';
   import AuthWrapper from './part/AuthWrapper.vue';

   export default {
      name:'AuthSignup',
      components: {
         AuthWrapper,
         AppInput,
         AppButton,
      },
      data(){
         return {
            userName: '',
            email:'',
            password: '',
            confirmPass: '',
            user: {
               error: false,
               msg: 'invalid input field!',
            }
         }
      },
      methods: {
         handleSubmit(){
            console.log('hello')
         }
      }
   }
</script>

/src/views/Register.vue

<template>
   <auth-signup/>
</template>
<script>

import AuthSignup from '../components/auth/AuthSignup.vue'

export default {
   name:'RegisterView',
   components: {
      AuthSignup
   },
}
</script>

此代码定义了名为“ registerview”的VUE视图,该视图使用导入的“ AuthSignup”组件显示用户注册表单。 “ authsignup”组件封装在单独的文件和目录中,以进行模块化和易于管理。

register-snapshot

通过使用模块化和组件驱动的方法,您不仅可以改善代码组织,而且还为可扩展且可维护的vue.js应用程序奠定了基础。愉快的编码!