<dd id="3pk9i"><optgroup id="3pk9i"><video id="3pk9i"></video></optgroup></dd>
<rp id="3pk9i"></rp>

<dd id="3pk9i"><track id="3pk9i"></track></dd>
      1. <th id="3pk9i"></th>
        1. <ol id="3pk9i"><ruby id="3pk9i"></ruby></ol>

        2. 聊聊Vue3+hook怎么寫彈窗組件更快更高效-世界看熱訊

          來源:php中文網 | 2022-12-28 21:00:11 |

          為什么會有這個想法

          在管理后臺開發過程中,涉及到太多的彈窗業務彈窗,其中最多的就是“添加XX數據”,“編輯XX數據”,“查看XX詳情數據”等彈窗類型最多?!鞠嚓P推薦:vuejs視頻教程、web前端開發】

          這些彈窗組件的代碼,很多都是相同的,例如組件狀態,表單組件相關的方法...


          (資料圖片僅供參考)

          于是,我簡單地對Dialog組件進行的二次封裝和hooks,減少了一些重復的代碼

          要封裝什么

          如果是普通彈窗使用的話,直接使用el-dialog組件已經足夠了

          但我還是一個比較愛折騰的人,我們先看看官方dialog文檔有什么可以添加的功能

          ...

          大概看了一下,我打算封裝一下功能

          提供全屏操作按鈕(右上角)默認提供“確認”,“關閉”按鈕內部添加Loading效果

          封裝Dialog

          確定了要封裝的功能之后,先來一個簡單的dialog組件。

          把雙向綁定處理一下,這樣外部就可以直接通過v-model直接控制彈窗了。

          <template>    <el-dialog :model-value="props.modelValue"></el-dialog></template><script setup>interface PropsType {  modelValue?: boolean;}const props = withDefaults(defineProps<PropsType>(), {  modelValue: false,});const emits = defineEmits<{  (e: "update:modelValue"): void;}>();</script>

          header

          如沒有安裝,請執行npm install @element-plus/icons-vue

          使用el-dialog提供的header插槽,將全屏圖表和關閉圖標放置到右上角中。給el-dialog傳遞show-close屬性關閉默認圖標。

          <template>  <el-dialog :model-value="props.modelValue" :show-close="false">    <template #header>      <div>        <span>{{ props.title }}</span>      </div>      <div>        <el-icon><FullScreen /></el-icon>        <el-icon><Close /></el-icon>      </div>    </template>  </el-dialog></template><script setup>import { FullScreen, Close } from "@element-plus/icons-vue";</script><style scoped>// 處理樣式:deep(.el-dialog__header) {  border-bottom: 1px solid #eee;  display: flex;  padding: 12px 16px;  align-items: center;  justify-content: space-between;  margin: 0;}.dialog-title {  line-height: 24px;  font-size: 18px;  color: #303133;}.btns {  display: flex;  align-items: center;  i {    margin-right: 8px;    font-size: 16px;    cursor: pointer;  }  i:last-child {    margin-right: 0;  }}</style>

          彈窗的標題文字內容通過props進行傳遞,默認為空(""

          <script lang="ts" setup>interface PropsType {  // 忽略之前的代碼  title?: string;}const props = withDefaults(defineProps<PropsType>(), {  title: "",});</script>

          我們看看現在頭部的效果(這里沒傳入標題,默認為""

          現在這個按鈕只有樣式效果,還沒有寫上對應的功能 ~

          給他們先綁定上對應的事件和指令

          <template>    <el-dialog    :model-value="props.modelValue"    :show-close="false"    :fullscreen="attrs?.fullscreen ?? isFullscreen"    >        <template #header>        <div>            <span class="dialog-title">{{ props.title }}</span>        </div>        <div class="btns">            <el-icon v-if="isFullScreenBtn" @click="handleFullscreen"            ><FullScreen            /></el-icon>            <el-icon @click="handleClose"><Close /></el-icon>        </div>        </template>    </el-dialog></template><script setup lang="ts">import { FullScreen, Close } from "@element-plus/icons-vue";interface PropsType {  title?: string;  modelValue?: boolean;  hiddenFullBtn?: boolean;}const props = withDefaults(defineProps<PropsType>(), {  title: "",  modelValue: false,  hiddenFullBtn: false,});const emits = defineEmits<{  (e: "update:modelValue"): void;  (e: "close"): void;}>();// 當前是否處于全屏狀態const isFullscreen = ref(false);// 是否顯示全屏效果圖標const isFullScreenBtn = computed(() => {  if (props.hiddenFullBtn) return false;  if (attrs?.fullscreen) return false;  return true;});// 開啟、關閉全屏效果const handleFullscreen = () => {  if (attrs?.fullscreen) return;  isFullscreen.value = !isFullscreen.value;};// 關閉彈窗時向外部發送close事件const handleClose = () => {  emits("close");};</script>

          再點擊下全屏圖標看看效果怎么樣

          NICE 頭部功能也就完成了

          Footer

          接下來,再處理下底部內容,默認提供兩個按鈕,分別是“確定”和“關閉”,這個名稱也是可以通過props屬性修改的。

          兩個按鈕綁定點擊事件,向外發送不同的事件。

          <template>  <div class="">    <el-dialog      v-bind="attrs"      :model-value="props.modelValue"      :show-close="false"      :fullscreen="attrs?.fullscreen ?? isFullscreen"    >      <template #footer>        <!-- 如果沒有提供其他footer插槽,就使用默認的 -->        <span v-if="!slots.footer" class="dialog-footer">          <el-button type="primary" @click="handleConfirm">{{            props.confirmText          }}</el-button>          <el-button @click="handleClose">{{ props.cancelText }}</el-button>        </span>        <!-- 使用傳入進來的插槽 -->        <slot v-else name="footer"></slot>      </template>    </el-dialog>  </div></template><script setup lang="ts">import { useSlots } from "vue";// 獲取插槽const slots = useSlots();interface PropsType {    title?: string;    width?: string | number;    isDraggable?: boolean;    modelValue?: boolean;    hiddenFullBtn?: boolean;    confirmText?: string;    cancelText?: string;}const props = withDefaults(defineProps<PropsType>(), {    title: "",    isDraggable: false,    modelValue: false,    hiddenFullBtn: false,    confirmText: "確認",    cancelText: "關閉",});const handleClose = () => {    emits("close");};const handleConfirm = () => {    emits("confirm");};</script>

          又搞定了一部分了,就剩下Content了 ~

          Content

          彈窗內容通過默認插槽的方式傳入進來,在外層的div元素上添加v-loading標簽,實現加載態。

          如果你想整個彈窗實現loading效果,請把v-loading移到最外層元素即可。注意不能是el-dialog元素上,否則無法實現可能是el-dialog使用了teleport組件,導致v-loading無法正常工作。等有空研究一下 ~

          <template>  <div class="">    <el-dialog      v-bind="attrs"      :model-value="props.modelValue"      :show-close="false"      :fullscreen="attrs?.fullscreen ?? isFullscreen"    >        <div class="content" v-loading="props.loading">            <slot></slot>        </div>    </el-dialog>  </div></template><script lang="ts" setup>interface PropsType {  loading?: boolean;}const props = withDefaults(defineProps<PropsType>(), {  loading: false,});</script>

          試試看中間的loading效果

          剩下一些細節處理

          el-dialog組件提供了很多個props屬性供用戶選擇,但我們現在封裝的dialog組件只使用到了一小部分props屬性。當用戶想要使用其他的props屬性時該怎么辦?

          例如使用width屬性時,難道要在我們封裝的組件中接收props.width再傳遞給<el-dialog :width="props.width" />組件嗎?

          不不不,還有另外一種方法,還記得剛剛在做全屏操作的時候使用到的useAttrs輔助函數嗎

          它可以獲取當前組件傳遞進來的屬性。有了這個方法之后,再配合并即可將外部傳遞進來的函數再傳遞到el-dialog組件上面啦

          <el-dialog    v-bind="attrs"    :model-value="props.modelValue"    :show-close="false"    :fullscreen="attrs?.fullscreen ?? isFullscreen"    :before-close="handleClose">    <!-- 忽略其他代碼 --></el-dialog>

          為了避免內部傳遞的props被覆蓋掉,v-bind="attrs"需要放在最前面

          在使用時,可能會給before-close屬性傳遞一個函數,但到了后面被內部的handleClose方法給覆蓋掉了。

          解決方案是在handleClose函數中,獲取attrs.["before-close"]屬性,如果類型是函數函數,先執行它。

          const handleClose = () => {  if (    Reflect.has(attrs, "before-close") &&    typeof attrs["before-close"] === "function"  ) {    attrs["before-close"]();  }  emits("close");};

          有關于el-dialog組件的封裝就到這里了

          封裝hooks

          利用Vue composition Api再封裝一下在使用el-dialog組件狀態的管理hook

          useDialog

          簡單處理顯示和加載態開關的hook

          import { ref } from "vue";export default function useDialog() {  const visible = ref(false);  const loading = ref(false);  const openDialog = () => (visible.value = true);  const closeDialog = () => (visible.value = false);  const openLoading = () => (loading.value = true);  const closeLoading = () => (loading.value = false);  return {    visible,    loading,    openDialog,    closeDialog,    openLoading,    closeLoading,  };}

          useDialog Demo

          <template><el-button @click="openDialog1">普通彈窗</el-button><DialogCmp  title="DialogCmp1"  :hiddenFullBtn="true"  v-model="visible1"  @confirm="handleConfirm"  @close="handleClose">  <h3>DialogCmp1</h3></DialogCmp></template><script setup lang="ts">import useDialog from "./components/useDialog";import DialogCmp from "./components/Dialog.vue";const {  visible: visible1,  openDialog: openDialog1,  closeDialog: closeDialog1,} = useDialog();</script>

          useDialogState 和 useDialogWithForm

          useDialogState

          針對開發管理后臺彈窗狀態封裝的一個hook,搭配下面的useDialogWithForm使用。

          export enum MODE {  ADD,  EDIT,}
          import { ref } from "vue";import { MODE } from "./types";export default function useDialogState() {  const mode = ref<MODE>(MODE.ADD);  const visible = ref(false);  const updateMode = (target: MODE) => {    mode.value = target;  };  return { mode, visible, updateMode };}

          useDialogWithForm

          針對表單彈窗組件封裝的hooks,接收一個formRef實例,負責控制彈窗內標題及清空表單中的校驗結果,減少多余的代碼 ~

          import { FormInstance } from "element-plus";import { Ref, ref } from "vue";import { MODE } from "./types";import useDialogState from "./useDialogState";export default function useDialogFn(  formInstance: Ref<FormInstance>) {  const { visible, mode, updateMode } = useDialogState();  const closeDialog = () => {    formInstance.value.resetFields();    visible.value = false;  };  const openDialog = (target: MODE) => {    updateMode(target);    visible.value = true;  };  return { visible, mode, openDialog, closeDialog };}

          useDialogWithForm Demo

          <template>  <Dialog    :before-close="customClose"    @confirm="confirm"    v-model="visible"    :title="mode == MODE.ADD ? "添加數據" : "編輯信息""    :confirm-text="mode == MODE.ADD ? "添加" : "修改""  >    <el-form      label-width="100px"      :model="formData"      ref="formDataRef"      style="max-width: 460px"      :rules="rules"    >      <el-form-item label="姓名" prop="name">        <el-input v-model="formData.name" />      </el-form-item>      <el-form-item label="年齡" prop="age">        <el-input v-model="formData.age" />      </el-form-item>      <el-form-item label="手機號碼" prop="mobile">        <el-input v-model="formData.mobile" />      </el-form-item>    </el-form>  </Dialog></template><script setup>import { ElMessage, FormInstance } from "element-plus";import { Ref, ref } from "vue";import Dialog from "./Dialog.vue";import { MODE } from "./types";import useDialogWithForm from "./useDialogWithForm";const rules = {  name: {    type: "string",    required: true,    pattern: /^[a-z]+$/,    trigger: "change",    message: "只能是英文名稱哦",    transform(value: string) {      return value.trim();    },  },  age: {    type: "string",    required: true,    pattern: /^[0-9]+$/,    trigger: "change",    message: "年齡只能是數字哦",    transform(value: string) {      return value.trim();    },  },  mobile: {    type: "string",    required: true,    pattern:      /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/,    trigger: "change",    message: "請輸入正確的手機號碼",    transform(value: string) {      return value.trim();    },  },};interface FromDataType {  name: string;  age: string;  mobile: string;}const formDataRef = ref<FormInstance | null>(null);let formData = ref<FromDataType>({  name: "",  age: "",  mobile: "",});const { visible, closeDialog, openDialog, mode } = useDialogWithForm(  formDataRef as Ref<FormInstance>);const confirm = () => {  if (!formDataRef.value) return;  formDataRef.value.validate((valid) => {    if (valid) {      console.log("confirm");      ElMessage({        message: "提交成功",        type: "success",      });      closeDialog();    }  });};const customClose = () => {  ElMessage({    message: "取消提交",    type: "info",  });  closeDialog();};defineExpose({  closeDialog,  openDialog,});</script><style scoped></style>

          倉庫地址

          useDialog

          在線demo地址

          如果您覺得本文對您有幫助,請幫幫忙點個star

          您的反饋 是我更新的動力!

          (學習視頻分享:vuejs入門教程、編程基礎視頻)

          以上就是聊聊Vue3+hook怎么寫彈窗組件更快更高效的詳細內容,更多請關注php中文網其它相關文章!

          關鍵詞: vue3

          中文字幕在线观看2021
          <dd id="3pk9i"><optgroup id="3pk9i"><video id="3pk9i"></video></optgroup></dd>
          <rp id="3pk9i"></rp>

          <dd id="3pk9i"><track id="3pk9i"></track></dd>
            1. <th id="3pk9i"></th>
              1. <ol id="3pk9i"><ruby id="3pk9i"></ruby></ol>