Programmatic and Declarative Navigation in Vue with Element UI

Installing Vue Router

First, add Vue Router to your project using npm or yarn:

npm install vue-router
# or
yarn add vue-router

Router Configuration

Create a router directory under src and define your routes in index.js:

// src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Dashboard from '@/components/Dashboard'
import Settings from '@/components/Settings'

Vue.use(VueRouter)

export default new VueRouter({
  routes: [
    {
      path: '/',
      name: 'Dashboard',
      component: Dashboard
    },
    {
      path: '/settings',
      name: 'Settings',
      component: Settings
    }
  ]
})

Registering Router in Main Entry

In your entry file (typically src/main.js or src/main.ts), import and attach the router:

// src/main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

Navigation Methods

Imperative Navigation

Use this.$router.push() within methods:

export default {
  methods: {
    navigateToSettings() {
      this.$router.push('/settings');
    }
  }
}

Declarative Navigation

Use <router-link> for template-based navigation:

<template>
  <el-button type="primary">View Settings</el-button>
  <router-link to="/settings" tag="el-button" type="primary">Settings</router-link>
</template>

Complete Example with Metrics Selector

<template>
  <div>
    <div class="top-wrapper">
      <div class="search el-input el-input--suffix">
        <input
          type="text"
          autocomplete="off"
          placeholder="Search metric name"
          class="el-input__inner"
        />
      </div>
    </div>
    <div class="metrics-wrapper">
      <!-- Sidebar -->
      <div class="metrics-sidebar">
        <a
          :class="{
            'category-item': true,
            'category-item--active': category.selected
          }"
          v-for="category in categories"
          :key="category.id"
          @click.prevent="handleCategoryClick(category)"
          href="#!"
        >
          {{ category.groupName }}
        </a>
      </div>
      <!-- Main Content -->
      <div class="metrics-main">
        <div
          class="metrics-section"
          v-for="category in categories"
          :key="category.id"
          :id="category.id"
        >
          <div class="section-header">
            <span class="section-title">{{ category.groupName }}</span>
          </div>
          <div class="el-row">
            <div class="el-col el-col-8" v-for="metric in category.child" :key="metric.id">
              <el-checkbox v-model="metric.selected" class="el-checkbox__input el-checkbox">
                <span class="el-checkbox__label">{{ metric.label }}</span>
              </el-checkbox>
            </div>
          </div>
        </div>
      </div>
      <!-- Drag Section -->
      <div class="flex">
        <div class="drag-container">
          <div class="drag-header">
            <div class="drag-title">Selected Metrics</div>
            <div class="drag-hint">Drag to reorder metrics</div>
            <div class="fixed-metrics">
              <div class="fixed-item mg2">Account ID</div>
            </div>
            <div class="drag-divider">Above metrics will be pinned horizontally</div>
          </div>
          <div class="drag-content-area" style="max-height: 445px">
            <section
              v-draggable="[
                selectedItems,
                {
                  animation: 150,
                  ghostClass: 'ghost',
                  group: 'people',
                  onUpdate,
                  onAdd,
                  onRemove
                }
              ]"
              class="flex flex-col gap-2 p-4 w-300px h-300px m-auto bg-gray-500/5 rounded overflow-auto"
            >
              <div
                v-for="item in selectedItems"
                :key="item.id"
                class="drag-item hover-class all-scroll mg2"
                @click="handleItemClick(item)"
              >
                {{ item.name }}
                <el-icon
                  @click.stop="deleteItem(item.id)"
                  style="
                    float: right;
                    align-items: center;
                    position: relative;
                    top: 8px;
                  "
                  class="mg-icon-close close"
                >
                  <close />
                </el-icon>
              </div>
            </section>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { vDraggable } from 'vue-draggable-plus'
import { Close } from '@element-plus/icons-vue'

const categories = ref([
  {
    id: 1,
    groupName: 'Basic Information',
    child: [
      { prop: 'uuid', label: 'Account ID' },
      { prop: 'name', label: 'Name' },
      { prop: 'companyName', label: 'Company Name' },
      { prop: 'allBalance', label: 'Total Balance' }
    ]
  },
  {
    id: 2,
    groupName: 'Impression Data',
    child: [
      { prop: 'updateTime', label: 'Spend' },
      { prop: 'summary.spent', label: 'Impressions' },
      { prop: 'summary.clickCount', label: 'Clicks' },
      { prop: 'summary.downloadCount', label: 'Downloads' }
    ]
  },
  {
    id: 3,
    groupName: 'Conversion Data',
    child: [
      { prop: 'summary.activateCount', label: 'New Activations' },
      { prop: 'summary.registerCount', label: 'Game Registrations' },
      { prop: 'summary.formsubmitCount', label: 'Form Submissions' },
      { prop: 'summary.normalActivateCount', label: 'Standard Activations' },
      { prop: 'summary.backActivateCount', label: 'Custom Activations' },
      { prop: 'summary.backRegisterCount', label: 'Custom Registrations' },
      { prop: 'summary.addDesktopCount', label: 'Desktop Adds' },
      { prop: 'summary.customRetainCount', label: 'Custom Retention' },
      { prop: 'summary.gamePayCount', label: 'Game Payments' },
      { prop: 'summary.customPayCount', label: 'Custom Payments' },
      { prop: 'summary.reactivation', label: 'Reactivation' },
      { prop: 'summary.webPay', label: 'Web Purchases' },
      { prop: 'summary.gameAppointment', label: 'Game Appiontments' },
      { prop: 'summary.buttonClick', label: 'Button Clicks' },
      { prop: 'summary.fastappPay', label: 'Fast App Payments' },
      { prop: 'summary.personalizedEvents', label: 'Personalized Events' }
    ]
  },
  {
    id: 4,
    groupName: 'Conversion Data (Billing Time)',
    child: [
      { prop: 'summary.activateC', label: 'New Activations (Billing)' },
      { prop: 'summary.backActivateC', label: 'Custom Activations (Billing)' },
      { prop: 'summary.backRegisterC', label: 'Registrations (Billing)' },
      { prop: 'summary.addDesktopC', label: 'Desktop Adds (Billing)' },
      { prop: 'summary.cDownloadCount', label: 'Downloads (Billing)' },
      { prop: 'summary.customRetainC', label: 'Custom Retention (Billing)' },
      { prop: 'summary.gamePayC', label: 'Game Payments (Billing)' },
      { prop: 'summary.customPayC', label: 'Custom Payments (Billing)' },
      { prop: 'summary.reactivationC', label: 'Reactivation (Billing)' },
      { prop: 'summary.gameAppointmentC', label: 'Appointments (Billing)' },
      { prop: 'summary.firstDayRecoveryAdMonetizationC', label: 'Day 1 Revenue - Ad (Billing)' },
      { prop: 'summary.totalRecoveryAdMonetizationC', label: 'Total Revenue - Ad (Billing)' },
      { prop: 'summary.firstDayRecoveryPaidRechargeC', label: 'Day 1 Revenue - Paid (Billing)' },
      { prop: 'summary.totalRecoveryPaidRechargeC', label: 'Total Revenue - Paid (Billing)' },
      { prop: 'summary.cFastappPay', label: 'Fast App Payments (Billing)' },
      { prop: 'summary.cPersonalizedEvents', label: 'Events (Billing)' },
      { prop: 'summary.cNormalActivateCount', label: 'Standard Activations (Billing)' },
      { prop: 'summary.cCreditCount', label: 'Custom Credit (Billing)' },
      { prop: 'summary.cInstallDoneCount', label: 'Installs Complete (Billing)' },
      { prop: 'summary.wechatgameRegisterC', label: 'Mini Game Reg. (Billing)' },
      { prop: 'summary.wechatgamePayC', label: 'Mini Game Pay (Billing)' },
      { prop: 'summary.cReactivationRetentionCount', label: 'Reactivation Retention (Billing)' },
      { prop: 'summary.creditCount', label: 'Custom Credit (Conversion)' },
      { prop: 'summary.installDoneCount', label: 'Installs Complete (Conversion)' },
      { prop: 'summary.wechatgameRegisterCount', label: 'Mini Game Reg. (Conversion)' },
      { prop: 'summary.wechatgamePayCount', label: 'Mini Game Pay (Conversion)' },
      { prop: 'summary.reactivationRetentionCount', label: 'Reactivation Retention (Conversion)' },
      { prop: 'summary.reserveCount', label: 'Calendar Reservations' },
      { prop: 'summary.taCount', label: 'Target Audience (Conversion)' },
      { prop: 'summary.cTaCount', label: 'Target Audience (Billing)' },
      { prop: 'summary.payOneTimeCount', label: 'App Pay Count (Conversion)' },
      { prop: 'summary.cPayOneTimeCount', label: 'App Pay Count (Billing)' },
      { prop: 'summary.payOneTimeAmount', label: 'App Pay Amount (Conversion)' },
      { prop: 'summary.cPayOneTimeAmount', label: 'App Pay Amount (Billing)' }
    ]
  },
  {
    id: 5,
    groupName: 'Engagement Data',
    child: [
      { prop: 'summary.identifyCodeCount', label: 'WeChat QR Scans' },
      { prop: 'summary.addWechatMpaCount', label: 'WeChat Adds' },
      { prop: 'summary.dialogueMpaCount', label: 'WeChat First Messages' },
      { prop: 'summary.oneDialogueCount', label: 'Valid Consultations' },
      { prop: 'summary.firstDayRecoveryPaidCount', label: 'Day 1 First Payment' }
    ]
  }
])

const handleCategoryClick = (item) => {
  categories.value.forEach((el) => (el.selected = false))
  item.selected = !item.selected
  const element = document.getElementById(item.id)
  if (element) {
    element.scrollIntoView({ behavior: 'smooth' })
  }
}

const deleteItem = (id) => {
  selectedItems.value = selectedItems.value.filter((item) => item.id !== id)
}

const selectedItems = ref([
  { id: 1, name: 'Account ID' },
  { id: 2, name: 'Name' },
  { id: 3, name: 'Account Entity' },
  { id: 4, name: 'Total Balance' }
])

const handleItemClick = (item) => {
  selectedItems.value.forEach((el) => (el.selected = false))
  item.selected = !item.selected
}

function onUpdate() {
  console.log('update')
}

function onAdd() {
  console.log('add')
}

function onRemove() {
  console.log('remove')
}
</script>

<style scoped lang="scss">
::v-deep .el-scrollbar {
  overflow: hidden;
  height: 100%;
  position: static !important;
}

::v-deep .el-checkbox__input.is-checked .el-checkbox__inner {
  background-color: #409eff;
}

::-webkit-scrollbar-thumb {
  border-radius: 5px;
  background-color: rgb(255, 255, 255, 0.2);
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

.top-wrapper {
  display: flex;
  justify-content: flex-start;
  margin-bottom: 16px;
}

.top-wrapper .search {
  width: 250px;
}

.el-input {
  position: relative;
  font-size: 14px;
}

.el-input__inner {
  -webkit-appearance: none;
  background-color: #fff;
  background-image: none;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  color: #606266;
  display: inline-block;
  height: 40px;
  line-height: 40px;
  outline: 0;
  padding: 0 15px;
  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  width: 100%;
  font-size: inherit;
  -webkit-transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}

.el-input .el-input__inner {
  height: 32px;
  line-height: 32px;
  border-radius: 2px;
}

.metrics-sidebar .category-item {
  padding-left: 16px;
  font-size: 14px;
  line-height: 40px;
  color: #333;
  cursor: pointer;
  display: block;
}

.metrics-sidebar .category-item--active {
  color: #197afb;
  background-color: #d6eaff;
}

.metrics-section {
  padding: 16px 0 0 24px;
  border-bottom: 1px solid #eaebec;
}

.section-header {
  display: flex;
  justify-content: flex-start;
}

.section-title {
  margin-bottom: 16px;
  font-weight: 700;
  color: #333;
}

.el-checkbox__input.is-checked .el-checkbox__label {
  color: #409eff;
}

.el-checkbox__label {
  color: #333;
  font-size: 12px;
  color: #666;
  display: inline-block;
  padding-left: 1px;
  line-height: 19px;
}

.drag-container .drag-header {
  padding: 0 16px;
}

.drag-container .drag-title {
  font-size: 14px;
  font-weight: 700;
  line-height: 100%;
  color: #333;
}

.drag-container .drag-hint {
  margin: 8px 0;
  font-size: 12px;
  line-height: 100%;
  color: #999;
}

.drag-container .drag-divider {
  position: relative;
  margin: 16px 0 0;
  font-size: 12px;
  color: #999;
  text-align: center;
}

.drag-container .drag-content-area {
  max-height: 445px;
  padding: 0 16px;
  margin-top: 16px;
  overflow-x: hidden;
  overflow-y: auto;
}

.drag-container .mg2 {
  margin-bottom: 2px;
}

.drag-container .drag-item {
  position: relative;
  height: 40px;
  padding: 0 30px 0 36px;
  overflow: hidden;
  line-height: 40px;
  text-overflow: ellipsis;
  white-space: nowrap;
  background-color: #fff;
  border-bottom: 1px solid #e8eaec;
}

.drag-container .drag-item .close {
  position: absolute;
  top: 13px;
  line-height: 100%;
  color: #cecece;
  cursor: pointer;
}

.metrics-wrapper {
  display: flex;
  width: 832px;
  height: 516px;
  border: 1px solid #eaebec;
  border-radius: 4px;
}

.metrics-sidebar {
  flex-shrink: 0;
  width: 160px;
  overflow: auto;
  border-right: 1px solid #eaebec;
}

.metrics-main {
  width: 672px;
  overlfow: auto;
  scroll-behavior: smooth;
}

.drag-container {
  position: absolute;
  top: 0;
  right: 0;
  flex-shrink: 0;
  width: 216px;
  padding: 25px 0;
  overflow: auto;
  background-color: #f8f8f9;
  border-right: 1px solid #eaebec;
}
</style>

Tags: vue Vue Router Element UI Navigation frontend

Posted on Fri, 29 May 2026 22:23:55 +0000 by everlifefree