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>