组件继承----基于Vue和PHP打造前后端分离的通用管理系统(七)

不知不觉已写到第7篇。前面6篇都是探索性的实现,是学习理论的过程,从这篇开始,开始用理论来指导实践了。
接上篇,我们现在通过组件继承实现Admin页面内导航。

动态导航

我们要的导航,是根据登录用户的权限,显示不同的导航菜单。根据职能分工,菜单内容的确定由后端来做更符合实践,前端只需要实现动态渲染菜单即可。好在这是Vue擅长的。


页内跳转.PNG

在components目录下新建Aside.vue。在Aside.vue中放置一个el-menu,我们先用一个静态的占个位,然后实现成动态的。静态的我们直接用ElemenUI自己的例子好了。

<template>
  <el-row>
    <el-col :span="24">
      <h5>管理后台</h5>
      <el-menu
        default-active="2"
        class="el-menu-vertical-demo"
        background-color="#545c64"
        text-color="#fff"
        active-text-color="#ffd04b">
        <el-submenu index="1">
          <template slot="title">
            <i class="el-icon-location"></i>
            <span>导航一</span>
          </template>
          <el-menu-item-group>
            <template slot="title">分组一</template>
            <el-menu-item index="1-1">选项1</el-menu-item>
            <el-menu-item index="1-2">选项2</el-menu-item>
          </el-menu-item-group>
          <el-menu-item-group title="分组2">
            <el-menu-item index="1-3">选项3</el-menu-item>
          </el-menu-item-group>
          <el-submenu index="1-4">
            <template slot="title">选项4</template>
            <el-menu-item index="1-4-1">选项1</el-menu-item>
          </el-submenu>
        </el-submenu>
        <el-menu-item index="2">
          <i class="el-icon-menu"></i>
          <span slot="title">导航二</span>
        </el-menu-item>
        <el-menu-item index="3" disabled>
          <i class="el-icon-document"></i>
          <span slot="title">导航三</span>
        </el-menu-item>
        <el-menu-item index="4">
          <i class="el-icon-setting"></i>
          <span slot="title">导航四</span>
        </el-menu-item>
      </el-menu>
    </el-col>
  </el-row>
</template>

<script>
export default {
};
</script>

<style scoped>
/*去掉最右边的白框,个人习惯*/
.el-menu {
  border-right: 0;
}
</style>

Admin.vue

<el-aside width="300px"><Aside /></el-aside>
name: 'App',
  components: {
    Aside,
  },

修改下Admin.vue中的背景色,否则太难看

.el-aside {
  background-color: #545c64;/*这里*/
  color: #333;
  text-align: center;
}

现在,npm run dev,正常的话,显示效果已经出来了!
下面来实现动态效果。
现在修改Aside.vue来实现自定义菜单

<template>
  <el-row>
    <el-col :span="24">
      <h5>管理后台</h5>
      <el-menu
        default-active="2"
        class="el-menu-vertical-demo"
        background-color="#545c64"
        text-color="#fff"
        active-text-color="#ffd04b">
        <template v-for="menu in menus">
        <el-submenu :index="menu.index" v-if="menu.items" :key="menu.index">
          <template slot="title"><i :class="getIcon(menu.icon)"></i>{{menu.label}}</template>
          <el-menu-item v-for="item in menu.items"
           :index="item.index" @click="send(item)" :key="item.index">
            <i :class="getIcon(item.icon)"></i>{{item.label}}
          </el-menu-item>
        </el-submenu>
        <el-menu-item :index="menu.index" @click="send(menu)" :key="menu.index" v-else>
          <i :class="getIcon(menu.icon)"></i>{{menu.label}}
        </el-menu-item>
      </template>
      </el-menu>
    </el-col>
  </el-row>
</template>

<script>
/**
 * 定义一个menus,体验菜单的动态渲染
 * 将来会通过props传进来
 */
const menus = [
  {
    index: '1',
    label: '配置管理',
    icon: 'document',
    items: [
      {
        index: '11',
        label: '网站配置',
        url: 'web.json',
      },
      {
        index: '12',
        label: '数据库配置',
        url: 'core,json',
      },
    ],
  },
  {
    index: '2',
    label: '用户管理',
    url: 'login.json',
    redirect: true,
  },
  {
    index: '3',
    label: '统计信息',
    url: 'status.json',
    icon: 'location',
  },
];

export default {
  data() {
    return { menus };
  },
  methods: {
    send(item) {
      this.$emit('redirect', item);
    },
    getIcon(icon = 'menu') {
      return `el-icon-${icon}`;
    },
  },
};
</script>

<style scoped>
/*去掉菜单最右边的白框,个人习惯*/
.el-menu {
  border-right: 0;
  text-align: left;
}
</style>

我们的菜单已经是通过一个数组menus来自定义了,根据vue特性,修改menus菜单就会变化哦,现在我们先不测试这个,留待将来在测试。我们这个有更重要的,菜单点击后会向Admin组件发布redirect消息,我们修改Admin组件,处理这个消息。
Admin组件添加响应消息的地方

<el-aside width="240px"><Aside @redirect="onRedirect"/></el-aside>
...
<el-main><component :config="view" v-bind:is="view.name" /></el-main>
...
<script>
export default {
  data() {
    return {
      view: {
      },
    };
  },
  methods: {
    submit() {
      this.$emit('redirect', { url: '/login.json' });
    },
    onRedirect(action) {
      // 冒泡
      if (this.passUp(action)) return this.$emit('redirect', action);
      // 本地处理
      return this.$HttpSend(action).then((response) => {
        this.view = response.view;
        throw new Error('route');
      }).catch(() => {});
    },
    passUp(action) {
      return action && action.redirect;
    },
  },
};
</script>

Admin组件添加几个临时组件方便效果展示


const Table = {
  template: '<H1>Table</H1>',
};

const Form = {
  template: '<H1>Form</H1>',
};

const Chart = {
  template: '<H1>Chart</H1>',
};

export default {
  components: {
    Aside,
    Table,
    Form,
    Chart,
  },
data() {...

突发奇想,就想修改下Http.js(HttpSend参数格式变了)

/**
   * 远程调用的核心方法
   * @returns {Promise<T>}
   */
  static HttpSend = (action = {}) => {
    /** @type {boolean} */
    const withValue = action && action.value;
    const option = {
      url: Http.createUrl((action) ? action.url : undefined),
    };
    if (action && action.post) {
      option.method = 'post';
      if (withValue) option.data = Qs.stringify(action.value);
      option.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
    } else {
      if (withValue) option.params = JSON.parse(JSON.stringify(action.value));
      option.method = 'get';
    }
    // 开启跨域cookie
    option.withCredentials = true;
    option.paramsSerializer = params => Qs.stringify(params, { arrayFormat: 'brackets' });
    return $http(option).then((response) => {
      const data = response.data;
      // 判断返回结果信息
      if ((function isError(v) {
        if (typeof v !== 'object') return false;
        if (v.status === false) return true;
        return (v.status && Number(v.status) <= 0);
      })(data)) return Http.onError(data, '操作无效');
      return Http.onReceive(data);
    }, error => Http.onError(error, '数据获取失败'))
      .catch(error => Http.onError(error, '内部错误'));
  };

  /**
   * 触发钩子函数
   * @type {Function}
   * @param {Object}  data
   * @returns {Object}
   */
  static onReceive = (data) => {
    Http.receivers.forEach(receiver => receiver(data));
    return data;
  }

  /**
   * 插件安装函数
   */
  static install(Vue) {
    this.vue = Vue;

    this.createUrl = this.vue.createUrl || (url => url);

    this.vue.prototype.$httpGet = (url, value = null) => this.HttpSend({ url, value });
    this.vue.prototype.$HttpPost = (url, value = null) => this.HttpSend({ url, value, post: true });
    this.vue.prototype.$HttpSend = Http.HttpSend;

    this.vue.prototype.$addReceiver = receiver => this.receivers.push(receiver);

    this.vue.prototype.$removeReceiver = id => this.receivers.splice(id, 1);
  }

看看神奇的效果吧。原来的几个按钮,因为Http.js的修改,失效了,改起来很简单,不会就问我。

这样呢,App和Admin有一部分重复的功能,我们提取出来

<script>
export default {
  data() {
    return {
      view: {
      },
    };
  },
  methods: {
    onRedirect(action) {
      // 冒泡
      if (this.passUp(action)) return this.$emit('redirect', action);
      // 本地处理
      return this.$HttpSend(action).then((response) => {
        this.view = response.view;
        throw new Error('route');
      }).catch(() => {});
    },
    passUp(action) {
      return action && action.redirect;
    },
  },
};
</script>

取名叫Container.vue放在components下。
修改App.vue

import Father from './components/Container';
...
export default {
  name: 'App',
  extends: Father,
  data() {
    return {
      trace: {
        rows: [],
      },
    };
  },
  ...
  methods: {
    passUp() {
      return false;
    },
  },
};
</script>

修改Admin.vue

export default {
  extends: Father,
   ...
  methods: {
    submit() {
      this.$emit('redirect', { url: '/login.json' });
    },
  },
};

看看效果,妈呀,组件继承就这么简单!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容