3

我有一个 VueJS/Vuetify 应用程序,它具有使用v-tabs/v-tab组件在页面之间导航的标签栏。我已经使用元素中的click事件实现了代码,该事件v-tab检查以确保当用户单击另一个选项卡时没有未保存的内容,如果有,则显示一个v-dialog用于提醒用户的模式。如果用户选择继续,它将继续到所需的选项卡/组件。但是,如果用户Cancel在模式中进行选择,则页面将留在原来的位置。

这是选项卡组件:

<template>
  <div>
    <!-- Tabs -->
    <v-tabs
      color="secondary"
      :value="currentTab"
    >
      <v-tab
        v-for="(tab, i) in userTabs"
        :key="i"
        :href="tab.href"
        @click="tabClick(tab.component, tab.link)"
        :disabled="isDisabled(tab)"
      >
        {{ tab.title }}
      </v-tab>
    </v-tabs>
    <BaseConfirmModal
      :value="showUnsaved"
      :title="unsavedContentTitle"
      :text="unsavedContentText"
      declineText="Cancel"
      @clicked="unsavedModalConfirm"
    />
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex';
import baseTabMixin from '@/components/mixins/workspace/baseTabMixin';

export default {
  name: 'UserTabs',
  data: () => ({
    userTabs: [
      {
        title: 'General Info',
        href: '#tab-general',
        link: 'tab-general',
        component: 'UserEdit',
      },
      {
        title: 'Enrollments',
        href: '#tab-enrollments',
        link: 'tab-enrollments',
        component: 'UserEnrollmentEdit',
      },
      {
        title: 'Alerts',
        href: '#tab-alerts',
        link: 'tab-alerts',
        component: 'UserAlertEdit',
      },
      {
        title: 'Devices',
        href: '#tab-devices',
        link: 'tab-devices',
        component: 'UserDeviceEdit',
      },
    ],
  }),
  computed: {
    ...mapGetters('app', ['getStickyTenant', 'roleAtLeastTa', 'getUnsaved']),
    ...mapGetters('users', ['getCurrent']),
    ...mapGetters('tabs', {
      currentTab: 'getSelected',
    }),
  },
  methods: {
    ...mapActions('tabs', {
      setCurrentTab: 'setSelected',
    }),
    isDisabled(item) {
      if (item.component === 'UserEdit') {
        return false;
      }
      if (item.component === 'UserDeviceEdit' && !this.roleAtLeastTa) {
        return true;
      }

      return !this.getCurrent.userId;
    },
  },
  mixins: [baseTabMixin],
};
</script>

和参考baseTabMixin

import { mapGetters, mapActions } from 'vuex';

const baseTabMixin = {
  data: () => ({
    showUnsaved: false,
    unsavedContentTitle: 'Unsaved Changes',
    unsavedContentText: 'You have made changes to this page that are not saved. Do you wish to continue?',
    destTabComponent: '',
    destTabLink: '',
  }),
  components: {
    BaseConfirmModal: () => import('@/components/base/BaseConfirmModal'),
  },
  computed: {
    ...mapGetters('app', ['getUnsaved']),
  },
  methods: {
    ...mapActions('app', ['setUnsaved']),
    tabClick(component, tab) {
      // Check to see if getUnsaved === true; if it is,
      // set variable to display warning modal.
      if (this.getUnsaved) {
        this.showUnsaved = true;
      } else {
        // There is no unsaved content, so continue to the desired tab.
        this.destTabComponent = component;
        this.destTabLink = tab;
        this.setCurrentTab(tab);
        this.$router.push({ name: component });
      }
    },
    unsavedModalConfirm(confirm) {
      if (confirm) {
        this.setCurrentTab(this.destTabLink);
        this.$router.push({ name: this.destTabComponent });
      }
      this.showUnsaved = false;
    },
  },
};

export default baseTabMixin;

问题与标签项突出显示有关。单击新选项卡时,滑块会移动到新选项卡,并且在tabClick()调用单击事件(在本例中)之前,新选项卡标题会加粗。当我Cancel在我的模态中选择时,它会离开页面(如预期的那样),但单击的选项卡仍然突出显示,下方的滑块和粗体文本。由于这一切都发生在我的点击处理程序被调用之前,有没有办法a)在点击事件被调用之前停止突出显示,或者b)将突出显示反转回当前选项卡?

4

2 回答 2

4

链接到工作

注意代码的关键部分:-

<v-tabs :value="currentTab" @change="onTabChange">
      <v-tab v-for="(tab, i) in tabs" :key="i">{{tab.title}}</v-tab>
</v-tabs>
async onTabChange(clickedTab)
{
    this.currentTab = clickedTab;
    await this.$nextTick();
    if (!this.allowTabChange) this.currentTab = this.previousTab;
    else this.previousTab = this.currentTab;
}

v-tabs组件使用内部模型来维护它的状态,即当前哪个选项卡处于活动状态。使用该:value属性,我们可以设置一个初始活动选项卡。当用户单击不同的选项卡时,内部模型会更新,导致单击的选项卡突出显示。v-tabs还会发出一个更改事件来通知父组件此更改。我们需要监听这个事件并:value自己管理变量以维护父级中的状态。由于是andv-model的抽象,我们也可以使用它。:value@change

要侦听此更改事件,请添加@changev-tabs. 您可以将您拥有的整个逻辑转移@click@change更清晰的代码中。

为了防止选择下一个选项卡,我们需要使用一些棘手的代码,因为v-tabs我们无法访问内部模型。我们需要允许currentTab临时更改为单击的选项卡,然后在Vue 获取当前批次更新后将其重置为previousTabin ie。$nextTick选项卡本身下方的突出显示发生在之后,$nextTick因此它不会改变,因为我们已经将其重置:valuepreviousTab

如果您可以让突出显示暂时转移到新选项卡,那么您需要做的就是更新currentTab到下一个选项卡的索引,突出显示将正常转移。然后根据用户做出的决定,您可以设置currentTabpreviousTab恢复更改,或者您可以使用下一个选项卡previousTab的新值进行更新。currentTab在这种情况下,$nextTick黑客是不必要的。

于 2019-12-04T19:39:01.993 回答
1

感谢@ParaBolt 和他的 CodePen 的回答,我得到了这个工作。我的回答与他的略有不同,因为我currentTab的存储在状态中,而不是组件中,并且我还使用了一种filter方法来获取component值,但是结构是相同的。

以下是 mixin 的重要部分:

 data: () => ({
    ..
    previousTab: '',
  }),
  methods: {
    ...mapActions('app', ['setUnsaved']),
    async tabChange(newTab) {
      this.setCurrentTab(newTab);
      // We need to get the corresponding component value from the tabs array.
      const newComponent = this.tabList.filter(tab => tab.link === newTab)[0].component;
      // Stash these in case we need then in unsavedModalConfirm()
      this.destTabComponent = newComponent;
      this.destTabLink = newTab;
      // Check to see if getUnsaved === true; if it is,
      // set variable to display warning modal.
      await this.$nextTick();
      if (this.getUnsaved) {
        this.setCurrentTab(this.previousTab);
        this.showUnsaved = true;
      } else {
        // There is no unsaved content, so continue to the desired tab.
        this.setCurrentTab(newTab);
        this.$router.push({ name: newComponent });
      }
    },
    unsavedModalConfirm(confirm) {
      if (confirm) {
        // User selected Continue from modal.
        this.setCurrentTab(this.destTabLink);
        this.$router.push({ name: this.destTabComponent });
        this.setUnsaved(false);
      } else {
        // User selected Cancel
        this.setCurrentTab(this.previousTab);
      }
      this.showUnsaved = false;
    },
  },
  mounted() {
    this.previousTab = this.currentTab;
  },

每当我从模式中取消时,它只会停留在原处,如果我选择继续,它会转到所需的选项卡。

于 2019-12-06T01:06:08.360 回答