эффект страницы
анализ спроса
На панели навигации по умолчанию открывается страница (страница списка).При нажатии на элемент списка открывается страница сведений, которую необходимо добавить в навигацию метки, а содержимое страницы текущей метки отображается на в то же время. Продолжайте нажимать элемент списка, чтобы добавить метку к навигации. И так далее, эффект будет таким, как показано выше.
Специальные функции:
- Вкладки можно закрыть;
- Метка может быть переключена, и содержимое может быть переключено одновременно;
- Метка больше, чем видимый диапазон, и ее необходимо добавить в список вкладок, который можно переключить, щелкнув;
- При нажатии на вкладку в списке вкладок необходимо переключиться на последнюю вкладку в видимой области;
- Количество отображаемых меток является адаптивным;
думать
В некоторых фоновых системах я видел аналогичную функцию, которая использует тег iframe для добавления ссылок для достижения этой функции. Итак, как реализовать это в vue?
Прежде всего, это одностраничное веб-приложение, которое использует vue-router для внешней маршрутизации, затем мы можем использовать именованное представление vue-router вместо iframe.
Официальный код vue-router с именем view:
<router-view class="view one"></router-view><router-view class="view two" name="a"></router-view><router-view class="view three" name="b"></router-view>
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
Структура проекта
Каталог проекта похож, поэтому я не буду слишком много объяснять~~
файл маршрута - именованное представление
код роутера:
import Vue from "vue";import Router from "vue-router";import store from '../store';Vue.use(Router);// layout页面const Layout = () => import(/* webpackChunkName: "layout" */ "@/views/layout/Layout.vue");// 产品相关页面const Product = () => import(/* webpackChunkName: "product" */ "@/views/product/Index.vue");const ProductDetail = () => import(/* webpackChunkName: "productDetail" */ "@/views/product/Detail.vue");// 新闻相关页面const News = () => import(/* webpackChunkName: "news" */ "@/views/news/Index.vue");const NewsDetail = () => import(/* webpackChunkName: "newsDetail" */ "@/views/news/Detail.vue");const rotuer = new Router({ mode: "history", base: process.env.BASE_URL, routes: [ { path: "/", name: "layout", component: Layout, redirect: "/product", children: [ { path: "/product", name: "product", meta: { title: "产品", tabName: "产品列表" }, components: { default: Product, detail: ProductDetail } }, { path: "/news", name: "news", meta: { title: "新闻", tabName: "新闻列表" }, components: { default: News, detail: NewsDetail } } ] } ]});rotuer.afterEach(to => { // 初始化navTag标签 if (to.meta && to.meta.tabName) { store.dispatch('navTab/addTab', { tabName: to.meta.tabName, initStatus: true }) }});export default rotuer;
В соответствии с приведенным выше функциональным анализом маршрутизация использует метод именованных представлений.Страница списка каждого модуля имеет атрибут имени.В то же время компоненты определяют страницу по умолчанию и страницу сведений.Если есть другие страницы, вы можете продолжайте добавлять их к обычным. Например:
components: {
default: Product,
detail: ProductDetail,
create: ProductCreate
}
файл макета
В файле layou.vue тег циклически генерируется для navTabs (массив тегов, определенных в vuex), и это имя соответствует определению компонентов атрибут в значении ключа маршрута, например: "detail".
Часть кода layout.vue:
<template> <el-row class="container"> <el-col :span="24"> <Header @collapse="collapseHandler" :isCollapse="isCollapse"></Header> </el-col> <el-col :span="24" class="main"> <Menu :isCollapse="isCollapse"></Menu> <el-main class="content-container"> <nav-tab :navTabs="navTabs"></nav-tab> <div class="main-content"> <router-view v-if="!navTabs.length"></router-view> <div class="navTab-content" v-for="item in navTabs" :key="item.tabId" v-show="item.active"> <keep-alive> <router-view :name="item.vName"></router-view> </keep-alive> </div> </div> </el-main> </el-col> </el-row> </template>
В основном используйте
использовать векс
Давайте посмотрим, что такое navTabs? Поместите данные метки вкладки в состояние vuex, вы можете продолжать работать и управлять набором меток глобально, а полный код будет опубликован позже.
Ниже приводится описание части кода в navTab.js:
1. Определение государства
const tabIdStr = "tab_"; // 标签的id前缀
const state = {
tabs: [],
tabMaxNum: 0
};
Переменная tabIdStr используется для предотвращения конфликтов с другими именами при добавлении вкладок.
государственный объект:
- свойство tabs: сохранить данные вкладок
- tabMaxNum: максимальное количество отображаемых вкладок (визуальная область навигации).
2. Добавьте теги
/**
* 添加标签
* @param {Any} tabId tab的唯一id标识
* @param {String} tabName tab的标题
* @param {String} vName 对应router命名视图的名称
* @param {Object} pParams 参数传递
* @param {Boolean} initStatus 初始化状态
*/
addTab({ commit, state }, { tabId, tabName, vName, pParams, initStatus = false }) {
// 设置标签id
let newTabId = tabIdStr + (tabId || 0); // 默认 0
let opts = {
tabName: "",
vName: "default",
pParams: {},
active: true,
...{
tabId: newTabId,
tabName,
vName,
pParams
}
};
// 初始化时,重置标签
if (initStatus) {
commit("resetTabs");
}
// 判断函数
let hasTabId = item => {
return item.tabId === newTabId;
};
// 判断新增标签是否已存在,如果存在直接激活,否则新增
if (state.tabs.some(hasTabId)) {
// 激活标签
commit("activeTab", newTabId);
return false;
}
// 添加标签
commit("addTab", opts);
},
Главное, на что следует обратить внимание в этом коде:
// 初始化时,重置标签
if (initStatus) {
commit("resetTabs");
}
Например, в модуле продукта при переходе на модуль новостей данные вкладок нужно обнулить, что можно использовать в методе afterEach маршрутизатора Маршрутизирующая часть кода:
rotuer.afterEach(to => {
// 初始化navTag标签
if (to.meta && to.meta.tabName) {
store.dispatch('navTab/addTab', {
tabName: to.meta.tabName,
initStatus: true
})
}
});
Полный код navTab.js:
const tabIdStr = "tab_"; // 标签的id前缀
const state = {
tabs: [],
tabMaxNum: 0
};
const getters = {
getNavTabs: state => state.tabs
};
const actions = {
/**
* 添加标签
* @param {Any} tabId tab的唯一id标识
* @param {String} tabName tab的标题
* @param {String} vName 对应router命名视图的名称
* @param {Object} pParams 参数传递
* @param {Boolean} initStatus 初始化状态
*/
addTab({ commit, state }, { tabId, tabName, vName, pParams, initStatus = false }) {
// 设置标签id
let newTabId = tabIdStr + (tabId || 0); // 默认 0
let opts = {
tabName: "",
vName: "default",
pParams: {},
active: true,
...{
tabId: newTabId,
tabName,
vName,
pParams
}
};
// 初始化时,重置标签
if (initStatus) {
commit("resetTabs");
}
// 判断函数
let hasTabId = item => {
return item.tabId === newTabId;
};
// 判断新增标签是否已存在,如果存在直接激活,否则新增
if (state.tabs.some(hasTabId)) {
// 激活标签
commit("activeTab", newTabId);
return false;
}
// 添加标签
commit("addTab", opts);
},
/**
* 切换标签
* @param {String} tabId tab的唯一id标识
*/
changeTab({ commit }, { tabId }) {
// 激活标签
commit('activeTab', tabId);
},
/**
* 更多标签处理
* @param {Number} index tabs数组下标
*/
handleMoreTab({ commit }, { index }) {
commit('handleMoreTab', index);
},
/**
* 删除标签
* @param {Number} index tabs数组下标
*/
deleteTab({ commit }, { index }) {
commit('deleteTab', index);
},
/**
* 删除其他标签
*/
deleteOtherTab({ commit, state }) {
// 保存第一个标签
let firstTab = state.tabs[0];
// 如果第一个当前标签是第一个,则直接删除全部
if(firstTab.active) {
commit('deleteAllTab');
} else {
commit('deleteOtherTab');
}
},
/**
* 删除全部标签
*/
deleteAllTab({ commit }) {
commit('deleteAllTab');
}
};
const mutations = {
/**
* 添加标签
*/
addTab(state, opts) {
// 隐藏其他标签状态
state.tabs.forEach(item => {
item.active = false;
});
// 当tabs数量大于或等于标签的最大显示数,新添加的标签放在可显示的最后一位
if(state.tabs.length >= state.tabMaxNum) {
state.tabs.splice(state.tabMaxNum - 1, 0, opts);
} else {
state.tabs.push(opts);
}
},
/**
* 激活标签
*/
activeTab(state, tabId) {
state.tabs.forEach(item => {
item.active = false;
if (item.tabId === tabId) {
item.active = true;
}
});
},
/**
* 更多标签处理
*/
handleMoreTab(state, index) {
let tabs = state.tabs;
let _index = state.tabMaxNum + index;
// 激活点击标签
tabs[_index].active = true;
// 拷贝点击标签
let copyTab = [tabs[_index]];
// 删除点击标签
tabs.splice(_index, 1);
// 隐藏其他标签
tabs.forEach(item => {
item.active = false;
});
// 插入到可显示的标签最后一个位置
tabs.splice([state.tabMaxNum - 1], 0, ...copyTab);
},
/**
* 删除标签
*/
deleteTab(state, index) {
let tabs = state.tabs;
// 判断删除的是当前标签,需激活上一个标签
if(tabs[index].active && tabs.length > 0) {
tabs[index -1].active = true;
}
tabs.splice(index, 1);
},
/**
* 删除其他标签
*/
deleteOtherTab(state) {
// 解构第一个标签,其他标签
let [firstTab, ...otherTabs] = state.tabs;
// 获取当前标签
let curTab = otherTabs.filter(item => item.active);
state.tabs = [firstTab, ...curTab];
},
/**
* 删除全部标签
*/
deleteAllTab(state) {
let tabs = state.tabs;
// 除了第一个标签其他的都删除
let firstTab = tabs[0];
firstTab.active = true;
state.tabs = [firstTab];
},
/**
* 重置标签
*/
resetTabs(state) {
state.tabs = [];
},
/**
* 设置显示标签最大值
*/
setMaxTabVal(state, val) {
state.tabMaxNum = parseInt(val);
},
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
tps: navTab.js реализует такие функции, как добавление, нажатие, переключение и удаление вкладок.
Компонент NavTab
Навигационный компонент метки, для работы метки, используйте методы в navTab.js для добавления, удаления, изменения и проверки данных вкладок~~
Часть кода:
<template> <div class="nav-wrap"> <div class="nav-title"> <strong>{{$route.meta.title}}</strong> </div> <div class="nav-tabs" ref="tabsNav"> <div class="tabs-item" :class="{ 'acitve': item.active }" @click="handleClickTab(item)" v-for="(item, index) in navTabs.slice(0, this.tabMaxNum)" :key="item.tabId"> {{item.tabName}} <i class="el-icon-close icon-close" @click.stop="handleCloseTab(index)" v-if="index"></i> </div> <div class="more"> <div class="more-btn" @click="handleClickMore"> <i class="icon el-icon-arrow-down"></i> </div> <ul class="more-dropdown-menu" v-show="moreStatus"> <li @click.stop="handleClickMoreTab(index)" v-for="(item, index) in navTabs.slice(this.tabMaxNum)" :key="item.tabId"> <span>{{item.tabName}}</span> <i class="el-icon-close icon-close" @click.stop="handleCloseTab(item, index)"></i> </li> <li @click.stop="handleClickDelAll"> <span>关闭全部</span> </li> <li @click.stop="handleClickDelOther"> <span>关闭其他</span> </li> </ul> </div> </div> </div></template><script>import { mapGetters } from 'vuex';export default { data() { return { tabMaxNum: 1, moreStatus: false } }, computed: { ...mapGetters({ navTabs: 'navTab/getNavTabs' }) }, mounted() { // 初始化 this.init(); window.addEventListener('resize', this.init, false); }, deactivated() { window.removeEventListener('resize', this.init, false); }, methods: { /** * 初始化 */ init() { // 计算标签最大显示个数 this.calcTabMaxNum(); }, /** * 计算标签最大显示个数 */ calcTabMaxNum() { if (!this.$refs.tabsNav) { return false; } let tabsNav = this.$refs.tabsNav; let tabsItem = tabsNav.querySelectorAll('.tabs-item'); let moreW = tabsNav.querySelector('.more').getBoundingClientRect().width; let navW = tabsNav.getBoundingClientRect().width - moreW; let itemW = tabsItem[0].getBoundingClientRect().width; // 设置最大值 this.tabMaxNum = Math.floor(navW / itemW); this.$store.commit('navTab/setMaxTabVal', this.tabMaxNum); }, /** * 点击标签 */ handleClickTab(item) { let { tabId, acitve } = item; if(acitve) return; this.hideMore(); this.$store.dispatch('navTab/changeTab', { tabId }); }, /** * 点击更多 */ handleClickMore() { this.moreStatus = !this.moreStatus; }, /** * 更多标签点击 */ handleClickMoreTab(index) { this.hideMore(); this.$store.dispatch('navTab/handleMoreTab', { index }); }, /** * 关闭标签 */ handleCloseTab(index) { this.$store.dispatch('navTab/deleteTab', { index }); }, /** * 关闭全部 */ handleClickDelAll() { if(this.navTabs.length === 1) return; this.hideMore(); this.$store.dispatch('navTab/deleteAllTab'); }, /** * 关闭其他 */ handleClickDelOther() { if(this.navTabs.length === 1) return; this.hideMore(); this.$store.dispatch('navTab/deleteOtherTab'); }, /** * 隐藏更多列表 */ hideMore() { this.moreStatus = false; } }}</script>
Здесь выполняется инициализация и выполняется расчет максимального количества видимых меток.
Метод calcTabMaxNum вычисляется в соответствии с шириной навигации по вкладкам и шириной вкладки, а полученное значение присваивается tabMaxNum в состоянии.
Создать страницу с вкладками
handleRowClick(row) {
let { id, title, intro } = row;
this.$store.dispatch('navTab/addTab', {
tabId: 'detail_' + id,
tabName: title,
vName: 'detail',
pParams: {
title,
intro
}
})
}
Вызовите метод addTab вкладки.
- tabId: уникальный идентификатор вкладки
- tabName: значение заголовка навигации по вкладкам
- vName: «detail» указывает на значение ключа, определенное компонентами в маршруте.
- pParams: параметры, которые необходимо передать другим страницам.
Как получить переданные параметры (значение pParams)
Методы, определенные в utils/index.js:
/**
* 获取当前标签的传递参数
* @param {Array} tabs 标签数据
*/
export const getCurTabParams = (tabs) => {
if(!tabs || !Array.isArray(tabs)) return {};
// 查找当前标签
let curTab = tabs.filter(item => {
return item.active;
});
return curTab.length > 0 ? curTab[0].pParams : {};
}
Часть кода в Detail.vue, использование метода getCurTabParams:
computed: {
...mapGetters({
navTabs: 'navTab/getNavTabs'
}),
tabParams() {
return getCurTabParams(this.navTabs) ? getCurTabParams(this.navTabs) : {};
}
},
Реализация основных функций завершена, и на этом она закончена.Пишу анализ в первый раз, и мысли немного сумбурны.Если есть какие-либо ошибки, пожалуйста, поправьте меня~~
Прикрепите адрес проекта:GitHub.com/стрелка GU или UE…