router ==> index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
// import cookie from 'js-cookie'

import Home from '../views/index'
import Error from '../views/error/index.vue'
import MessageCenter from '../views/message-center/center.vue'
import HelpCenter from '../views/help-center/index.vue'
import MessageCenterDetail from '../views/message-center/message-details.vue'
import MessageWrapper from '../views/message-center/index.vue'

// import cookieKeys from '@/const/cookie-keys'
import {passWithouTokenValidate} from '@/utils/token'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home,
  },
  {
    path: '/error',
    name: 'error',
    component: Error,
  },
  {                                         //嵌套路由写法
    path: '/main/message',
    name: 'messageWrapper',
    component: MessageWrapper,
    children: [
      {
        path: '/',
        name: 'messageCenter',
        component: MessageCenter,
      },
      {
        path: 'details/:id',
        name: 'messageCenterDetail',
        component: MessageCenterDetail,
        props: true,
      },
    ],
  },

  {
    path: '/main/help-center',
    name: 'helpCenter',
    component: HelpCenter,
  },
  {
    path: '&',
    name: 'Error',
    component: Error,
  },
]

const router = new VueRouter({
  routes,
  mode: 'hash',
})

router.beforeEach((to, from, next) => {
  // 不需要做重定向跳转
  if (passWithouTokenValidate(to)) {
    return next()
  }

  next()
})

export default router
 
 
嵌套路由  文件夹以及对应的文件情况
 
index.vue
<template>
  <div class="message-center">
    <div class="left-wrapper">
      <vui-tag-view
        :menu="messageTypeList"
        ref="menuTitle"
        menuType="tree"
        :props="menuProps"
        :showTags="false"
        @node-click="onNodeClick"
      />
    </div>
    <div class="right-wrapper">
      <router-view :activeName="activeName"></router-view>
    </div>
  </div>
</template>

<script>
import {utils} from '@vv-package/utils/lib'
import {mapGetters, mapState} from 'vuex'
import {
  tableConfig,
  getTableDataHeader,
  messageTypeList,
  readStatus_hadRead,
  barsData,
} from './config'
import {getMessages, markReadedAll} from '@/services/deepexi-cloud'
import pick from 'lodash/pick'

const tableConfigObject = tableConfig()
export default {
  data() {
    return {
      dataPath: 'payload.content',
      totalPath: 'payload.totalElements',
      pageSizeName: 'size',
      pageNumName: 'page',
      tableData: {
        ...tableConfigObject.tableData,
      },
      query: {},

      activeName: messageTypeList[0].value,
      messageTypeList,

      tableBodyMaxHeight: 0,
      menuProps: {
        label: 'label',
        count: 'count',
        id: 'id',
      },
      barsData: [],
    }
  },
  computed: {
    ...mapState('notification', ['unReadCount']),
    ...mapGetters(['token', 'userId', 'tenantId', 'user']),
  },
  watch: {
    unReadCount(val, oval) {
      // 监听导航栏数据展示,更新页面数据
      if (val.toString() !== oval.toString() && this.user && this.user.userId) {
        this.$refs.tableCommon && this.$refs.tableCommon.refreshData()
      }
    },
    user: {
      handler(val, oval) {
        if (val && val !== oval && val.userId) {
          // 由于页面初始化时,可能还未获取到user,需要在这里重新获取
          this.init()
        }
      },
    },
  },
  created() {
    this.barsData = barsData
  },
  mounted() {
    if (this.user && this.user.userId) {
      this.$nextTick(() => {
        this.init()
      })
    }
    this.$nextTick(() => {
      this.tableBodyMaxHeight = utils.getTableBobyHeight() - 70
    })
  },
  methods: {
    handleBtnClick(comData) {
      this[comData.funName](comData)
    },
    onNodeClick(data) {
      this.activeName = data.value

      if (this.$route.path === '/main/message') {
        return
      }
      this.$router.push('/main/message')
    },
    cellClassName({row}) {
      if (row.readStatus === readStatus_hadRead) {
        return 'opacity-low'
      }
      return
    },
    init() {
      this.activeName = messageTypeList[0].value
      this.tableData.header = getTableDataHeader(this)

      this.query = pick(this.user, ['userId', 'admin'])
      this.query.registerTime = this.user.createdTime

      this.getMessageCount()
      this.$refs.tableCommon && this.$refs.tableCommon.refreshData()
    },
    updateUnReadCount(val) {
      this.$store.commit('notification/unReadCount', val ? val : '')
    },
    getMessages(params) {
      return getMessages({...this.query, ...params})
    },
    async getMessageCount() {
      //  获取各类型消息数量, 受限于接口,这里通过获取消息列表(传类型)的length作为数量展示
      for (let i = 0; i < messageTypeList.length; i++) {
        const res = await this.getMessages({
          type: messageTypeList[i].bizIdentification,
        })
        this.$refs.menuTitle.setCount(
          this.messageTypeList[i].id,
          res.payload.totalElements
        )
      }
    },
    markReadedAll() {
      this.barsData[0].isShowLoading = true
      this.barsData[0].isActive = true
      markReadedAll({...this.query, userId: this.user.userId})
        .then(() => {
          this.messageList = []
          // 默认操作成功,未读列表更新为0
          this.updateUnReadCount('')

          this.$vui_message({
            type: 'success',
            message: '操作成功',
          })
        })
        .finally(() => {
          this.barsData[0].isShowLoading = false
          this.barsData[0].isActive = false
          // 重新获取数据, 这里监听了导航栏数据变动,不需要主动更新列表
        })
    },
  },
}
</script>
 
 
center.vue
<template>
  <div class="message-center">
    <div class="right-wrapper">
      <vui-toolbar
        :bars-data="barsData"
        :column="4"
        :handle-click="handleBtnClick"
        :last-set-right="false"
        class="vui-todo-toolbar-top"
        type="tool"
      />
      <vui-table-common
        id="tableId"
        ref="tableCommon"
        v-model="tableData"
        :axios-config="axiosConfig"
        :data-default-path="dataPath"
        :page-size-name="pageSizeName"
        :page-num-name="pageNumName"
        :data-total-path="totalPath"
        data-src="fakeUrl"
        :autoHandleSearch="false"
        :is-selection="false"
        :cellClassName="cellClassName"
        :max-height="tableBodyMaxHeight"
        style="margin-top: 16px;"
      >
        <!-- slot 自定义应用名称 -->
        <template slot-scope="{scope}" slot="subject">
          <div class="app-name-wrapper">
            <a @click="routerLinkTodetail(scope)">{{ scope.subject }}</a>
          </div>
        </template>
      </vui-table-common>
    </div>
  </div>
</template>

<script>
import {utils} from '@vv-package/utils/lib'
import {mapGetters, mapState} from 'vuex'
import {
  tableConfig,
  getTableDataHeader,
  messageTypeList,
  readStatus_hadRead,
  barsData,
} from './config'
import {
  getMessages,
  getMessagesUrl,
  markReadedAll,
} from '@/services/deepexi-cloud'
import pick from 'lodash/pick'
import {stringify} from 'qs'

const tableConfigObject = tableConfig()
export default {
  props: {
    activeName: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      dataPath: 'payload.content',
      totalPath: 'payload.totalElements',
      pageSizeName: 'size',
      pageNumName: 'page',
      tableData: {
        ...tableConfigObject.tableData,
      },
      query: {},

      messageTypeList,

      tableBodyMaxHeight: 0,
      menuProps: {
        label: 'label',
        count: 'count',
        id: 'id',
      },
      barsData: [],
    }
  },
  computed: {
    ...mapState('notification', ['unReadCount']),
    ...mapGetters(['token', 'userId', 'tenantId', 'user']),
    axiosConfig() {
      return config => {
        config.headers.Authorization = `Bearer ${this.token}`
        let target = messageTypeList.find(i => i.value === this.activeName)
        let q = stringify(this.query)
        if (target.value !== messageTypeList[0].value)
          q += `&type=${target.bizIdentification}`

        config.url = `${getMessagesUrl()}?${q}&tenantId=${this.tenantId}`
        return config
      }
    },
  },
  watch: {
    unReadCount(val, oval) {
      // 监听导航栏数据展示,更新页面数据
      if (val.toString() !== oval.toString() && this.user && this.user.userId) {
        this.$refs.tableCommon && this.$refs.tableCommon.refreshData()
      }
    },
    activeName: {
      handler(val, oval) {
        if (val && val !== oval && this.user && this.user.userId) {
          this.tableData.page = tableConfigObject.tableData.page
          this.$nextTick(() => {
            this.$refs.tableCommon && this.$refs.tableCommon.refreshData()
          })
        }
      },
    },
    user: {
      handler(val, oval) {
        if (val && val !== oval && val.userId) {
          // 由于页面初始化时,可能还未获取到user,需要在这里重新获取
          this.init()
        }
      },
    },
  },
  created() {
    this.barsData = barsData
  },
  mounted() {
    if (this.user && this.user.userId) {
      this.$nextTick(() => {
        this.init()
      })
    }
    this.$nextTick(() => {
      this.tableBodyMaxHeight = utils.getTableBobyHeight() - 70
    })
  },
  methods: {
    handleBtnClick(comData) {
      this[comData.funName](comData)
    },

    cellClassName({row}) {
      if (row.readStatus === readStatus_hadRead) {
        return 'opacity-low'
      }
      return
    },
    init() {
      this.activeName = messageTypeList[0].value
      this.tableData.header = getTableDataHeader(this)

      this.query = pick(this.user, ['userId', 'admin'])
      this.query.registerTime = this.user.createdTime

      this.getMessageCount()
      this.$refs.tableCommon && this.$refs.tableCommon.refreshData()
    },
    updateUnReadCount(val) {
      this.$store.commit('notification/unReadCount', val ? val : '')
    },
    getMessages(params) {
      return getMessages({...this.query, ...params})
    },
    async getMessageCount() {
      //  获取各类型消息数量, 受限于接口,这里通过获取消息列表(传类型)的length作为数量展示
      // for (let i = 0; i < messageTypeList.length; i++) {
      //   const res = await this.getMessages({
      //     type: messageTypeList[i].bizIdentification,
      //   })
      //   console.log(res)
      //   // this.$nextTick(() => {
      //   // this.$refs.menuTitle.setCount(
      //   //   this.messageTypeList[i].id,
      //   //   res.payload.totalElements
      //   // )
      //   // })
      // }
    },
    markReadedAll() {
      this.barsData[0].isShowLoading = true
      this.barsData[0].isActive = true
      markReadedAll({...this.query, userId: this.user.userId})
        .then(() => {
          this.messageList = []
          // 默认操作成功,未读列表更新为0
          this.updateUnReadCount('')

          this.$vui_message({
            type: 'success',
            message: '操作成功',
          })
        })
        .finally(() => {
          this.barsData[0].isShowLoading = false
          this.barsData[0].isActive = false
          // 重新获取数据, 这里监听了导航栏数据变动,不需要主动更新列表
        })
    },
    routerLinkTodetail(item) {
      this.$router.push({
        path: `/main/message/details/${item.id}`,
      })
      // 这里需要更新详情内容的状态,更新未读列表状态, 自动标记的,不需要前端调用接口
    },
  },
}
</script>
 
 
message-details.vue
<template>
  <div class="message-details" v-loading="pageLoading">
    <div>
      <div class="msg-header">
        <div class="msg-details-title">
          <div class="title-text">{{ content.subject }}</div>
          <div class="line"></div>
        </div>
        <div class="msg-info">
          <div class="msg-details-time">
            {{ content.createDate | formatterTime }}
          </div>
          <div class="msg-details-type">
            {{ content.bizIdentification | getMsgType }}
          </div>
        </div>
      </div>
      <div class="msg-content markdown-body" v-html="content.content"></div>
      <div class="pre-next-nav">
        <div class="pre btn-item">
          <div class="btn-inner" v-show="preItem">
            <vui-button text @click="handleClick(preItem)" class="btn"
              ><i class="vui-icon-arrow-pagination-left"></i>上一条</vui-button
            >
            <span class="nav-info" :title="preItem && preItem.subject">{{
              preItem && preItem.subject
            }}</span>
          </div>
        </div>
        <div class="next btn-item">
          <div class="btn-inner" v-show="nextItem">
            <span class="nav-info" :title="nextItem && nextItem.subject">{{
              nextItem && nextItem.subject
            }}</span>
            <vui-button text @click="handleClick(nextItem)" class="btn"
              >下一条
              <i class="vui-icon-arrow-pagination-right"></i>
            </vui-button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import {messageTypeList, breadcrumbData, formatDate} from './config'
import {mapGetters} from 'vuex'
import {getMessages, getMessageDetail} from '@/services/deepexi-cloud'

import pick from 'lodash/pick'
export default {
  name: 'messageDetails',
  props: {
    id: {
      type: [String, Number],
      default: null,
    },
  },
  filters: {
    formatterTime: function(time) {
      return formatDate(parseInt(time, 10))
    },
    getMsgType(val) {
      const target =
        messageTypeList.find(i => i.bizIdentification === val) || {}
      return target.label
    },
  },
  data() {
    return {
      query: {},
      content: '',
      msgList: [],
      preItem: null,
      nextItem: null,

      pageLoading: false,
    }
  },
  beforeRouteLeave(_, __, next) {
    this.$store.commit('breadcrumb/setBreadcrumb', [])
    next()
  },
  created() {
    setTimeout(() => {
      this.$store.commit('breadcrumb/setBreadcrumb', breadcrumbData)
    }, 0)
    if (this.user && this.user.userId) this.init()
  },
  computed: {
    ...mapGetters(['user']),
  },
  watch: {
    id: {
      handler(val, oval) {
        if (val && oval && val !== oval) {
          // 由于 vue-router 使用动态路由时,进行push,会复用当前页面组件,导致不会进行生命周期的变化,这里对路由参数进行监听,重新获取数据
          this.pageLoading = true
          this.getMessageDetail()
            .then(() => {
              this.getPreAndNext()
            })
            .finally(() => {
              this.pageLoading = false
            })
        }
      },
    },
    user: {
      handler(val, oval) {
        // 由于页面初始化时,可能还未获取到user,需要在这里重新获取
        if (val && val !== oval && val.userId) {
          this.init()
        }
      },
    },
  },
  methods: {
    getPreAndNext() {
      // 获取上一条和下一条消息,目前通过获取全部消息列表进行比对,获取到当前消息的上一条和下一条消息
      const currentId = this.id
      const index = this.msgList.findIndex(
        i => i.id.toString() === currentId.toString()
      )
      this.preItem =
        index - 1 >= 0 ? this.msgList.slice(index - 1, index)[0] : null
      this.nextItem =
        index + 1 < this.msgList.length
          ? this.msgList.slice(index + 1, index + 2)[0]
          : null
    },
    init() {
      this.query = pick(this.user, ['userId', 'admin'])
      this.query.registerTime = this.user.createdTime

      this.pageLoading = true
      Promise.all([this.getMessages(), this.getMessageDetail()])
        .then(() => {
          this.getPreAndNext()
        })
        .finally(() => {
          this.pageLoading = false
        })
    },
    getMessages() {
      // 用来获取前后条数据
      const params = {...this.query}
      return getMessages(params).then(({payload}) => {
        this.msgList = payload.content
      })
    },
    getMessageDetail() {
      const params = {messageId: this.id}
      return getMessageDetail(params).then(res => {
        this.content = res.payload
        // 通知未读列表去更新数据
        this.$store.commit('notification/noticeMessage')
      })
    },
    handleClick(item = {}) {
      this.$router.push({
        path: `/main/message/details/${item.id}`,
      })
      // 这里需要更新下一条的状态,更新未读列表状态, 是自动标记的,不需要前端调用接口
    },
  },
}
</script>