从零开始使用vue2+element搭建后台管理系统(框架布局+面包屑+内容页加载实现)

先在components下分别创建侧边栏、顶部、布局等组件,用于全局配置:

CommonAside.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<template>
  <el-menu
    class="el-menu-vertical"
    background-color="#282e38"
    text-color="#f4f4f5"
    active-text-color="#f57d2d"
    :default-active="activeMenu"
    :router="true"
    :collapse="isCollapse"
    @open="handleOpen"
    @close="handleClose"
  >
    <!-- :background-color="global.menuBg" -->
    <!-- :text-color="global.menuText" -->
    <!-- :active-text-color="global.menuActiveText" -->
    <el-menu-item
      @click="clickItem(item)"
      v-for="item in noChildren"
      :key="item.name"
      :index="item.path"
    >
      <i :class="`el-icon-${item.icon}`"></i>
      <span slot="title">{{ item.label }}</span>
    </el-menu-item>
    <el-submenu
      v-for="item in hasChildren"
      :key="item.label"
      :index="item.path"
    >
      <template slot="title">
        <i :class="`el-icon-${item.icon}`"></i>
        <span slot="title">{{ item.label }}</span>
      </template>
      <el-menu-item-group v-for="subItem in item.children" :key="subItem.name">
        <el-menu-item @click="clickItem(subItem)" :index="subItem.path">{{
          subItem.label
        }}</el-menu-item>
      </el-menu-item-group>
    </el-submenu>
  </el-menu>
</template>
 
<style scoped lang="scss">
@import "@/assets/css/global.scss";
.el-menu-vertical:not(.el-menu--collapse) {
  width: 210px;
  min-height: 400px;
}
 
.el-menu {
  height: 100vh;
  border-right: none;
}
 
.el-menu-item:focus,
.el-menu-item:hover {
  outline: 0;
  background-color: $menuHover !important;
}
</style>
 
<script>
import global from "@/assets/css/global.scss";
export default {
  data() {
    return {};
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    clickItem(item) {
      // 防止自己跳自己的报错
      if (
        this.$route.path !== item.path &&
        !(this.$route.path === "/home" && item.path === "/")
      ) {
        this.$router.push(item.path);
      }
      // 面包屑
      this.$store.commit("basic/SelectMenu", item);
    },
  },
  computed: {
    // 获取菜单
    MenuData() {
      console.log(this.$store.state.basic.menu, "xxxx");
      return this.$store.state.basic.menu;
    },
    noChildren() {
      // 如果没有children则返回true,会被过滤器留下
      return this.MenuData.filter((item) => !item.children);
    },
    hasChildren() {
      return this.MenuData.filter((item) => item.children);
    },
    // 要放到计算属性,自动计算
    isCollapse() {
      return this.$store.state.basic.isCollapse;
    },
    // 当前页面
    activeMenu() {
      console.log(this.$route.path, "-----------this.$route.path");
      return this.$route.path;
    },
    // 通用样式配置
    global() {
      return global;
    },
  },
};
</script>

  

顺手贴一个公用样式文件:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$blue: #324157;
$light-blue: #3a71a8;
$red: #c03639;
$pink: #e65d6e;
$green: #30b08f;
$tiffany: #4ab7bd;
$yellow: #fec171;
$panGreen: #30b08f;
 
// $menuText: #f4f4f5;
$menuText: #cecece;
$menuActiveText: #f57d2d;
$subMenuActiveText: #f4f4f5;
 
$menuBg: #282e38;
$menuHover: #3e434c;
 
$subMenuBg: #c2c2c2;
$subMenuHover: #b6b6b6;
 
:export {
  menuText: $menuText;
  menuActiveText: $menuActiveText;
  subMenuActiveText: $subMenuActiveText;
  menuBg: $menuBg;
  menuHover: $menuHover;
  subMenuBg: $subMenuBg;
  subMenuHover: $subMenuHover;
}

  

导出后,在main.js中全局引入:

  

侧边栏因为涉及到左侧菜单,因此需要对菜单进行管理,首先获取用户信息的接口需要返回菜单(假接口):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
export function getInfo(mobile) {
  console.log(mobile, "mobile");
  return {
    code: 10000,
    msg: "成功",
    data: {
      name: "WUN",
      avatar:
        "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png",
      menuList: [
        {
          path: "/account/account",
          name: "user",
          label: "烛月账号管理",
          icon: "s-custom",
          url: "account/AccountView",
        },
        {
          path: "/order/order",
          name: "order",
          label: "订单管理",
          icon: "s-marketing",
          url: "order/OrderView",
        },
        {
          path: "/auth/auth",
          name: "auth",
          label: "权限管理",
          icon: "s-tools",
          url: "auth/AuthView",
        },
      ],
    },
  };
   
}

  

然后登录成功后,获取用户信息时进行存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取用户信息
async getUserInfo(mobile) {
  try {
    this.loading = true;
    const res = await getInfo(mobile);
    if (res.code !== 10000)
      this.$message.error(res.msg || "获取用户信息失败!");
    if (res.code === 10000 && res.data) {
      this.$store.commit("user/SET_NAME", res.data.name);
      this.$store.commit("user/SET_AVATAR", res.data.avatar);
      this.$store.commit("basic/setMenu", res.data.menuList);
      setTimeout(() => {
        this.$router.push("/home");
      });
    }
  } finally {
    this.loading = false;
  }
},

  

这里因为是假接口,所以直接在方法中进行了处理,实际上是在store的actions中进行请求及数据管理的。接下来是store文件夹下basic.js文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import Vue from "vue";
import Vuex from "vuex";
import Cookie from "js-cookies";
import { getMenu } from "@/utils/auth";
 
Vue.use(Vuex);
 
const getDefaultState = () => {
  return {
    // 导航栏是否折叠
    isCollapse: false,
    // 页签数据
    tabList: [
      {
        path: "/",
        name: "home",
        label: "首页",
        icon: "s-home",
        url: "Home/Home",
      },
    ],
    // 菜单
    menu: getMenu(),
  };
};
 
const state = getDefaultState();
 
const mutations = {
  // 修改导航栏展开和收起的方法
  CollapseMenu(state) {
    state.isCollapse = !state.isCollapse;
  },
  // 更新页签数据
  SelectMenu(state, item) {
    // 如果点击的不在页签数据中,则添加
    const index = state.tabList.findIndex((val) => val.name === item.name);
    if (index === -1) {
      state.tabList.push(item);
    }
  },
  // 删除tag:删除tabList中对应的item
  closeTag(state, item) {
    const index = state.tabList.findIndex((val) => val.name === item.name);
    state.tabList.splice(index, 1);
  },
  // 设置不同用户的菜单
  setMenu(state, val) {
    state.menu = val;
    return Cookie.setItem("menu", JSON.stringify(val));
  },
  // 动态添加路由
  addMenu(state, router) {
    // 判断Cookie
    if (!Cookie.getItem("menu")) return;
    const menu = JSON.parse(Cookie.getItem("menu"));
    state.menu = menu;
 
    const menuArray = [];
 
    // 组装路由
    menu.forEach((item) => {
      // 判断是否有子路由
      if (item.children) {
        item.children = item.children.map((child) => {
          child.component = () => import(`../../views/${child.url}`);
          return child;
        });
        menuArray.push(...item.children);
      } else {
        item.component = () => import(`../../views/${item.url}`);
        menuArray.push(item);
      }
    });
 
    menuArray.forEach((item) => {
      router.addRoute("Main", item);
    });
  },
};
 
const actions = {};
 
export default {
  namespaced: true,
  state,
  mutations,
  actions,
};

  

其中包括获取cookie中缓存的菜单数组,方法在utils/auth下:

1
2
3
4
5
6
7
8
export function getMenu() {
  const menuStr = Cookie.getItem("menu");
  if (menuStr) {
    return JSON.parse(menuStr);
  } else {
    return [];
  }
}

因为cookie缓存是转字符串的,因此做一层格式处理。简单的侧边栏就完成咯。

 

CommonHeader.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
<template>
  <div class="header-container">
    <div class="l-content">
      <i v-if="isCollapse" class="el-icon-s-unfold" @click="handleMenu"></i>
      <i v-else class="el-icon-s-fold" @click="handleMenu"></i>
      <img class="header-icon" src="../assets/logo.png" alt="" />
      <h3>
        {{ isCollapse ? "后台" : "后台管理系统" }}
      </h3>
      <!-- 面包屑 -->
      <el-breadcrumb class="app-breadcrumb" separator="/">
        <transition-group name="breadcrumb">
          <el-breadcrumb-item
            v-for="(item, index) in levelList"
            :key="item.path"
          >
            <span
              v-if="
                item.redirect === 'noRedirect' || index == levelList.length - 1
              "
              class="no-redirect"
              >{{ item.meta.title }}</span
            >
            <a v-else @click.prevent="handleLink(item)">{{
              item.meta.title
            }}</a>
          </el-breadcrumb-item>
        </transition-group>
      </el-breadcrumb>
    </div>
    <div class="r-content">
      <el-dropdown @command="handleClick">
        <span class="el-dropdown-link">
          <img class="user" src="../assets/logo.png" alt="" />
        </span>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item>个人信息</el-dropdown-item>
          <el-dropdown-item command="logout">退出</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>
 
<script>
export default {
  data() {
    return {
      levelList: null,
    };
  },
  watch: {
    $route() {
      this.getBreadcrumb();
    },
  },
  created() {
    this.getBreadcrumb();
  },
  methods: {
    // 切换菜单伸缩
    handleMenu() {
      this.$store.commit("basic/CollapseMenu");
    },
    // 退出登录
    handleClick(command) {
      if (command === "logout") {
        this.$store.dispatch("user/logout").then(() => {
          this.$router.push({
            name: "/login",
          });
        });
      }
    },
    // 获取面包屑
    getBreadcrumb() {
      // this.$route.matched匹配到一个路由数组
      let matched = this.$route.matched.filter(
        (item) => item.meta && item.meta.title
      );
      const first = matched[0];
      if (!this.isHome(first)) {
        matched = [
          {
            path: "/home",
            meta: { title: "首页", redirect: "/home", path: "/home" },
          },
        ].concat(matched);
      }
      this.levelList = matched.filter(
        (item) => item.meta && item.meta.title && item.meta.breadcrumb !== false
      );
    },
    // 判断是否为主页
    isHome(route) {
      const name = route && route.name;
      if (!name) {
        return false;
      }
      // 对路由进行大小写处理
      return name.trim().toLocaleLowerCase() === "home".toLocaleLowerCase();
    },
    // 转译路由
    pathCompile(path) {
      // 将字符串转化为正则表达式的方法
      var pathToRegexp = require("path-to-regexp");
      // 参阅:https://github.com/PanJiaChen/vue-element-admin/issues/561
      const { params } = this.$route;
      const toPath = pathToRegexp.compile(path);
      return toPath(params);
    },
    // 跳转
    handleLink(item) {
      const { redirect, path } = item;
      if (redirect) {
        this.$router.push(redirect);
        return;
      }
      this.$router.push(this.pathCompile(path));
    },
  },
  computed: {
    isCollapse() {
      return this.$store.state.basic.isCollapse;
    },
  },
};
</script>
 
<style lang="scss" scoped>
.header-container {
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  height: 60px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 20px;
 
  .el-dropdown-link {
    cursor: pointer;
    color: #409eff;
 
    .user {
      width: 40px;
      height: 40px;
      border-radius: 50%;
    }
  }
  .header-icon {
    margin: 0 16px;
    height: 48px;
  }
  h3 {
    text-align: center;
    line-height: 48px;
    color: #ffffff;
    font-size: 18px;
    font-weight: 600;
    color: #454545;
  }
}
 
.l-content {
  display: flex;
  align-items: center;
  cursor: pointer;
 
  .el-breadcrumb {
    margin-left: 15px;
 
    .el-breadcrumb__item {
      .el-breadcrumb__inner {
        &.is-link {
          color: #666666;
        }
      }
 
      &:last-child {
        .el-breadcrumb__inner {
          color: #ffffff;
        }
      }
    }
  }
}
</style>

  

注意头部因为有面包屑,因此需要导入path-to-regexp插件用来处理 url 中地址与参数:yarn add path-to-regexp

 

LayoutView.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
  <el-container>
    <el-aside width="auto">
    </el-aside>
    <el-container>
      <el-header>
        <common-header />
      </el-header>
      <!-- <common-tags /> -->
      <el-main>
        <div class="page-container-wn">
          <router-view></router-view>
        </div>
      </el-main>
    </el-container>
  </el-container>
</template>
 
<script>
import CommonAside from "../components/CommonAside.vue";
import CommonHeader from "../components/CommonHeader.vue";
export default {
  data() {
    return {};
  },
  components: {
    CommonAside,
    CommonHeader,
    // CommonTags,
  },
};
</script>
 
<style lang="scss" scoped>
.el-header {
  padding: 0;
}
</style>

  

就可以对应上一篇博客的router文件夹下的index文件中的路由配置中的父级component

 

同时,我们需要在页面中间的内容部分做一个全局加载的功能,避免element框架loading功能的全屏加载不够友好。在utils文件夹下新建loading.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import Vue from "vue";
// loading框设置局部刷新,且所有请求完成后关闭loading框
let loading;
let needLoadingRequestCount = 0; // 声明一个对象用于存储请求个数
function startLoading() {
  loading = Vue.prototype.$loading({
    lock: true,
    text: "努力加载中(っ•̀ω•́)っ⁾⁾",
    background: "rgba(255,255,255,.4)",
    target: document.querySelector(".page-container-wn"), // 设置加载动画区域
  });
}
function endLoading() {
  loading.close();
}
export function showPageLoading(target) {
  if (needLoadingRequestCount === 0) {
    startLoading(target);
  }
  needLoadingRequestCount++;
}
export function hidePageLoading() {
  if (needLoadingRequestCount <= 0) return;
  needLoadingRequestCount--;
  if (needLoadingRequestCount === 0) {
    endLoading();
  }
}
export default {
  showPageLoading,
  hidePageLoading,
};

  

然后写一个简单的动态渲染的表单组件,在components文件夹下新增CustomForm.vue,这样就可以用后端返回的表单配置json直接进行渲染啦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
<template>
  <div class="filterPanel">
    <!--是否行内表单-->
    <el-form
      class="form"
      :inline="inline"
      :model="form"
      :rules="rules"
      ref="form"
    >
      <!--标签显示名称-->
      <div class="labelGroup">
        <slot></slot>
        <el-form-item
          v-for="item in formLabel"
          :key="item.model"
          :label="item.label"
          :prop="item.model"
        >
          <!--根据type来显示是什么标签-->
          <!-- 一般输入框 -->
          <el-input
            v-model="form[item.model]"
            :placeholder="item.placeholder || '请输入' + item.label"
            v-if="item.type === 'input'"
          >
          </el-input>
          <!-- 数字输入框 -->
          <el-input
            v-model="form[item.model]"
            :min="0"
            type="number"
            :placeholder="item.placeholder || '请输入' + item.label"
            v-if="item.type === 'number'"
          >
          </el-input>
 
          <el-autocomplete
            class="inline-input"
            v-model="form[item.model]"
            v-if="item.type === 'searchInput'"
            :fetch-suggestions="
              (queryString, cb) => {
                searchOptionName(queryString, cb, item.opts);
              }
            "
            :placeholder="item.placeholder || '请输入' + item.label"
            :trigger-on-focus="false"
          ></el-autocomplete>
 
          <el-select
            v-model="form[item.model]"
            :placeholder="item.placeholder || '请选择' + item.label"
            v-if="item.type === 'select'"
          >
            <!--如果是select或者checkbox 、Radio,还需要选项信息-->
            <el-option
              v-for="item in item.opts"
              :key="item.value"
              v-show="item.label"
              :label="item.label"
              :value="item.value"
            ></el-option>
          </el-select>
          <!-- 开关 -->
          <el-switch
            v-model="form[item.model]"
            v-if="item.type === 'switch'"
          ></el-switch>
          <!-- 单个日期选择器 -->
          <el-date-picker
            v-model="form[item.model]"
            type="date"
            placeholder="选择日期"
            v-if="item.type === 'date'"
            value-format="yyyy-MM-dd"
          >
          </el-date-picker>
          <!-- 日期范围选择器 -->
          <el-date-picker
            v-model="form[item.model]"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            type="datetimerange"
            placeholder="选择日期"
            v-if="item.type === 'dateRange'"
            value-format="yyyy-MM-dd"
          >
          </el-date-picker>
          <!-- 日期时间选择器 -->
          <el-date-picker
            v-model="form[item.model]"
            type="datetimerange"
            range-separator="至"
            v-if="item.type === 'dateTimeRange'"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="yyyy-MM-dd HH:mm:ss"
          >
          </el-date-picker>
        </el-form-item>
      </div>
      <div class="btnGroup">
        <el-form-item>
          <el-button type="primary" @click="search">搜索</el-button>
          <el-button @click="reset">重置</el-button>
        </el-form-item>
      </div>
    </el-form>
  </div>
</template>
 
<script>
//   import Bus from "./formEventBus";
export default {
  name: "CustomForm",
  //inline 属性可以让表单域变为行内的表单域
  //form 表单数据 formLabel 是标签数据
  props: {
    inline: {
      type: Boolean,
      default: true,
    },
 
    formLabel: Array,
    rules: Object,
  },
  watch: {
    formLabel: {
      handler(newVal) {
        if (newVal) {
          newVal.forEach((item) => {
            this.$set(this.form, item.model, item.default || "");
          });
        }
      },
      immediate: true,
    },
  },
  data() {
    return {
      form: {},
    };
  },
  mounted() {
    let obj = {};
    this.formLabel.forEach(async (item, index) => {
      if (item.optsConfig) {
        //获取动态下拉选项
        let val = await this.getOpts(item);
        this.$set(this.formLabel[index], "opts", val);
        obj[item.model] = val;
        this.$emit("getSelect", obj);
      }
    });
  },
  methods: {
    searchOptionName(queryString, cb, data) {
      var restaurants = data;
 
      var results = queryString
        ? restaurants.filter(this.createFilter(queryString))
        : restaurants;
      cb(results);
    },
 
    createFilter(queryString) {
      return (restaurant) => {
        return (
          restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) !=
          -1
        );
      };
    },
 
    reset() {
      this.form.pageNum = 1;
      this.$refs["form"].resetFields();
      this.$emit("confirm", this.form);
      // Bus.$emit('getParam', this.form);//给Table传查询参数
    },
    search() {
      this.form.pageNum = 1;
 
      this.$emit("confirm", this.form);
      // Bus.$emit('getParam', this.form);//给Table传查询参数
    },
    async getOpts(oData) {
      let { api, param, labelKey, valueKey } = oData.optsConfig;
      let opts = [];
      const res = await api(param);
      if (res.code === 1) {
        opts = res.data.map((item) => {
          if (oData.model === "goodsSku" && item["goodsSku"] != "") {
            //SKU特殊处理
 
            const itemObj = JSON.parse(item.goodsSku);
 
            return {
              label: itemObj[labelKey].join("-"),
              value: itemObj[valueKey],
              ...item,
            };
          } else {
            return {
              label: item[labelKey],
              value: item[valueKey],
              ...item,
            };
          }
        });
      }
      return opts;
    },
  },
};
</script>
<style lang="scss">
.filterPanel {
  margin-bottom: 20px;
  padding: 20px 20px 0 20px;
 
  .form {
    display: flex;
    justify-content: space-between;
 
    .btnGroup {
      min-width: 150px;
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
    }
  }
 
  .el-input__inner {
    background-color: #fff;
    height: 33px;
    line-height: 33px;
  }
 
  .filterPanel {
    width: 100%;
 
    border-radius: 4px;
    background: #f7f8fa;
 
    padding-top: 20px;
    padding-right: 20px;
  }
 
  .btnContainer {
    margin-bottom: 20px;
  }
 
  .el-button {
    height: 32px !important;
    padding: 0 16px;
  }
 
  .el-date-editor .el-range__icon,
  .el-range-separator {
    line-height: 26px;
  }
}
</style>

  

接下来整一个页面看看效果。首先在api文件夹下新增此页面的api文件,我称之为order.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
 * 订单管理接口列表
 */
 
export function orderInit() {
  return {
    code: 10000,
    msg: "请求成功",
    data: {
      searchList: [
        {
          label: "",
          model: "time",
          type: "dateTimeRange",
        },
        {
          label: "",
          placeholder: "请选择游戏",
          model: "game",
          type: "select",
          opts: [
            {
              label: "游戏1",
              value: "game1",
            },
            {
              label: "游戏2",
              value: "game2",
            },
          ],
        },
        {
          label: "",
          placeholder: "全部订单",
          model: "order",
          type: "select",
          opts: [
            {
              label: "订单1",
              value: "order1",
            },
            {
              label: "订单2",
              value: "order2",
            },
          ],
        },
        {
          label: "",
          placeholder: "订单类型",
          model: "orderType",
          type: "select",
          opts: [
            {
              label: "平台订单号",
              value: "orderId",
            },
            {
              label: "游戏订单号",
              value: "gameOrderId",
            },
          ],
        },
        {
          label: "",
          placeholder: "输入文本",
          model: "text",
          type: "input",
        },
      ],
      searchRules: {},
      tableHeader: [
        { id: "orderId", name: "平台订单号" },
        { id: "gameName", name: "游戏名称" },
        { id: "userName", name: "用户名" },
        { id: "avaName", name: "区服/角色名" },
        { id: "sum", name: "金额" },
        { id: "createTime", name: "创建时间" },
        { id: "payStatus", name: "支付状态" },
        { id: "noticeStatus", name: "通知状态" },
      ],
    },
  };
}
 
export function orderList(params) {
  console.log(params, "params");
  return {
    code: 10000,
    msg: "请求成功",
    data: {
      tableData: [
        {
          id: "1212121",
          orderId: "xxxx",
          gameName: "华夏回事路",
          userName: "138xxxx2323",
          avaName: "xxxxxxxxx",
          sum: "648",
          createTime: "2023-05-26 00:00:00",
          payStatus: "成功",
          noticeStatus: "成功",
        },
        {
          id: "12121221",
          orderId: "xxxx",
          gameName: "华夏回事路2",
          userName: "138xxxx2323",
          avaName: "xxxxxxxxx",
          sum: "648",
          createTime: "2023-05-26 00:00:00",
          payStatus: "成功",
          noticeStatus: "成功",
        },
        {
          id: "12121321",
          orderId: "xxxx",
          gameName: "华夏回3事路",
          userName: "138xxxx2323",
          avaName: "xxxxxxxxx",
          sum: "648",
          createTime: "2023-05-26 00:00:00",
          payStatus: "成功",
          noticeStatus: "成功",
        },
      ],
      page: {
        pageSize: 10,
        current: 1,
        total: 200,
      },
    },
  };
}

  

当然接口是假的,接下来写页面,在views文件夹下新增order文件夹,新建index.vue文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<template>
  <div>
    <el-card class="box-card search-card">
      <CustomForm
        :formLabel="searchList"
        :rules="searchRules"
        @confirm="handleSearch"
      />
    </el-card>
    <el-card class="box-card">
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        style="width: 100%; margin-bottom: 16px"
      >
        <el-table-column
          v-for="item in tableHeader || []"
          :key="item.id"
          :prop="item.id"
          :label="item.name"
        />
        <el-table-column fixed="right" label="操作" width="120">
          <template slot-scope="scope">
            <el-button @click="handleDetail(scope.row)" type="text"
              >详情</el-button
            >
            <el-button type="text" disabled>补单</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="tablePage.current || 1"
        :page-size="tablePage.current || 10"
        layout="total, sizes, prev, pager, next"
        :total="tablePage.total || total"
      >
        <!-- :page-sizes="[10, 20, 50, 100]" -->
      </el-pagination>
    </el-card>
    <DataDetail
      :rowInfo="rowInfo"
      :visible="drawerVisible"
      @updateVisible="updateVisible"
    />
  </div>
</template>
<script>
import CustomForm from "@/components/CustomForm.vue";
import DataDetail from "./DataDetail";
import { orderInit, orderList } from "@/api/order";
import { showPageLoading, hidePageLoading } from "@/utils/loading";
 
export default {
  name: "OrderView",
  data() {
    return {
      formInline: {
        user: "",
        region: "",
      },
      tableHeader: [],
      tableData: [],
      searchList: [],
      searchRules: {},
      tablePage: {
        pageSize: 10,
        current: 1,
        total: 400,
      },
      searchData: {},
      rowInfo: {},
      drawerVisible: false,
      tableLoading: false,
    };
  },
  mounted() {
    this.init();
    this.handleSearch();
  },
  methods: {
    // 初始化
    async init() {
      try {
        showPageLoading(); // 开启
        const res = await orderInit();
        if (res.code !== 10000) this.$message.error(res.msg);
        if (res.code === 10000 && res.data) {
          this.searchList = res.data.searchList;
          this.searchRules = res.data.searchRules;
          this.tableHeader = res.data.tableHeader;
        }
      } finally {
        setTimeout(() => {
          hidePageLoading();
        }, 2000);
      }
    },
    // 查询列表数据
    async handleSearch(data) {
      try {
        const tableParams = { ...this.tablePage, ...(data || {}) };
        this.searchData = { ...tableParams };
        this.tableLoading = true;
        const res = await orderList(tableParams);
        if (res.code !== 10000) this.$message.error(res.msg);
        if (res.code === 10000 && res.data) {
          this.tableData = res.data.tableData;
          this.tablePage = { ...res.data.page };
        }
      } finally {
        this.tableLoading = false;
      }
    },
    // 查看详情
    handleDetail(data) {
      this.rowInfo = data;
      this.drawerVisible = true;
    },
    // 切换每页展示条数
    handleSizeChange(val) {
      this.tablePage = { ...this.tablePage, pageSize: val };
      this.handleSearch({
        ...this.searchData,
        ...this.tablePage,
        pageSize: val,
      });
    },
    // 切换页码
    handleCurrentChange(val) {
      this.tablePage = { ...this.tablePage, current: val };
      this.handleSearch({
        ...this.searchData,
        ...this.tablePage,
        current: val,
      });
    },
    updateVisible() {
      this.drawerVisible = !this.drawerVisible;
    },
  },
  components: {
    CustomForm,
    DataDetail,
  },
};
</script>
 
<style lang="scss" scoped>
.search-card {
  margin-bottom: 16px;
}
</style>

  

可以看到引入了@/utils/loading中导出的两个方法,并在初始化接口中进行了使用(写了个2秒的setTimeOut看效果)

然后新建详情抽屉组件,依旧在order文件夹下,新建DataDetail.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<template>
  <el-drawer :visible.sync="showDrawer" size="45%" title="详情">
    <!-- @tab-click="handleCheckTab" -->
    <el-tabs v-model="activeName" style="margin: 24px">
      <el-tab-pane label="订单详情" name="first">
        <el-descriptions :column="2" :labelStyle="{ fontWeight: 'bold' }">
          <el-descriptions-item
            v-for="item in data.first || []"
            :key="item.key"
            :label="item.label"
            >{{ item.value }}</el-descriptions-item
          >
        </el-descriptions>
      </el-tab-pane>
      <el-tab-pane label="支付回调" name="second">
        <el-table
          :data="data.second?.tableData || []"
          border
          style="width: 100%; margin-bottom: 16px"
        >
          <el-table-column
            v-for="item in data.second?.tableHeader || []"
            :key="item.id"
            :prop="item.id"
            :label="item.name"
          >
          </el-table-column>
        </el-table>
      </el-tab-pane>
      <el-tab-pane label="通知游戏" name="third">
        <el-table
          :data="data.third?.tableData || []"
          border
          style="width: 100%; margin-bottom: 16px"
        >
          <el-table-column
            v-for="item in data.third?.tableHeader || []"
            :key="item.id"
            :prop="item.id"
            :label="item.name"
          >
          </el-table-column>
        </el-table>
      </el-tab-pane>
    </el-tabs>
  </el-drawer>
</template>
<script>
export default {
  name: "DataDetail",
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    rowInfo: Object,
  },
  data() {
    return {
      activeName: "first",
      data: {},
    };
  },
  computed: {
    showDrawer: {
      get() {
        return this.visible;
      },
      set(val) {
        this.$emit("updateVisible", val);
      },
    },
  },
  mounted() {
    this.data = {
      first: [
        { key: "orderId", label: "平台订单号", value: "xxxxx" },
        { key: "orderId2", label: "支付消息", value: "xxxxx" },
        { key: "orderId3", label: "支付方式", value: "支付宝" },
        { key: "orderId4", label: "游戏名称", value: "xxxxx" },
        { key: "orderId5", label: "支付渠道单号", value: "xxxxxxxxxxxxxxx" },
        { key: "orderId6", label: "订单金额", value: "648.00" },
        { key: "orderId7", label: "支付状态", value: "成功" },
        { key: "orderId8", label: "通知游戏状态", value: "成功" },
        { key: "orderId9", label: "游戏区服", value: "xxx1服" },
        { key: "orderId10", label: "游戏角色名", value: "ABB" },
        { key: "orderId11", label: "游戏订单号", value: "xxxxxxxxxx" },
        { key: "orderId12", label: "商品名称", value: "648礼包" },
        { key: "orderId13", label: "回调地址", value: "https://xxxx.ccc.com" },
      ],
      second: {
        tableHeader: [
          { id: "xxTime", name: "回调时间" },
          { id: "xxStatus", name: "回调状态" },
        ],
        tableData: [
          { id: "12121212", xxTime: "2022-05-21 23-33", xxStatus: "成功" },
          { id: "12134321212", xxTime: "2022-05-21 13-33", xxStatus: "失败" },
          { id: "1212221212", xxTime: "2022-05-21 23-23", xxStatus: "成功" },
        ],
      },
      third: {
        tableHeader: [
          { id: "xxTime", name: "通知时间" },
          { id: "xxStatus", name: "通知状态" },
        ],
        tableData: [
          { id: "12121212", xxTime: "2022-05-21 23-33", xxStatus: "成功" },
          { id: "12121212", xxTime: "2022-05-21 13-33", xxStatus: "失败" },
          { id: "12121212", xxTime: "2022-05-21 23-23", xxStatus: "成功" },
        ],
      },
    };
  },
  methods: {
    // handleCheckTab(val) {
    //   console.log(val);
    // },
  },
};
</script>

  

看看效果:

 

 查看详情:

 

posted @   芝麻小仙女  阅读(2137)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示