二十三 网关运营管理后台框架搭建

基于 vue-manage-system 搭建运营管理后台

设计

通常在我们的系统开发中,都有一个用于管理整个应用配置和检索数据的管理后台,便于运营使用。在早期没有 vue 出现之前,大家通常使用类似 Layui 这样的框架搭建管理后台,虽然它已经停更了但不影响它的使用也真的非常好用。

不过在互联网公司很多运营后台都是由前端研发提供好 vue 框架并负责开发,或者一些简单的页面,也可以由后端研发进行处理。类似这样的 vue 运营管理框架包括:

  • vue-element-admin —— —个基于 vue2.0 和 Eelement 的控制面板 UI 框架,这是使用 vue 技术栈开发的前端程序员的首选管理系统模板,模板以及非常的成熟了,并且有相关的社区和维护人员,开发时候遇到问题也不要慌。
  • ant-design-vue-pro —— 阿里背书,蚂蚁家族的。
  • iview-admin —— iView admin 是基于 iView 的 vue 2.0 控制面板。搭配使用 iView UI组件库形成的一套后台集成解决方案。
  • d2-admin —— D2Admin 是一个完全 开源免费 的企业中后台产品前端集成方案,基于 vue.js 和 ElementUl 的管理系统前端解决方案,小于 60kb 的本地首屏 js 加载。
  • vuestic-admin —— vuestic-admin,一款免费而美妙 Vue.js 管理模板包括38 以上个定制用户界面组件,像地图,聊天,个人资料卡,图标,进度条、登录和注册的预建页面等等。
  • vue-manage-system —— 基于vue.js 2.x 系列+Element UI 的后台管理系统解决方案,弥补了 element 中缺少图片裁剪上传、富文本编辑器、图表等这些在后台管理系统中很常见的功能。适用于绝大部分的后台管理系统 (Web Management System)开发。

这里选择较为简单的 vue-manage-system 进行搭建。

实现

框架搭建

首先你要确保你本地已经安装了 node 且在 14.18+ 的版本,这是一个基本的环境要求,如果没有可以去安装 Node.js 环境。

  • 拉取代码:
1
git clone https://github.com/lin-xin/vue-manage-system.git
  • 进入模板:cd vue-manage-system
  • 构建依赖:npm install
  • 启动运行:npm run dev
  • 构建文件:npm run builld一 这一步是帮你生成一个 dist 文件夹,里面放一些 html、js、imgs 等静态文件,便于放到服务器部署的。

文件配置

在 vue 运营后台页面下,需要关注的重要文件夹;views、router、components、api、public:

  • views:存放 vue 页面。
  • router:是一个访问的路径路由,比如访问某一个地址,会被转换到具体的vue页面。
  • components:组件层,比如运营后台的标题头、左侧菜单还有标签显示。
  • api:vue 页面中的调用的 API 接口都在这里统一管理。
  • public:是存放模板和数据的,比如在 api 里查询的 json mock 数据,就可以放到这里来使用。(如果调用了 HTTP 接口,那么就不走 public 的 json mock 数据了。

vue 页面开发

views/gateway_server.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
<template>
<div>
<div class="container">
<div class="handle-box">
<el-select v-model="query.groupId" placeholder="网关分组" class="handle-select mr10">
<el-option key="10001" label="上海网关" value="10001"></el-option>
<el-option key="10002" label="北京网关" value="10002"></el-option>
<el-option key="10003" label="成都网关" value="10003"></el-option>
</el-select>
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
</div>
<el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
<el-table-column prop="groupId" label="分组编号" width="128" align="center"></el-table-column>
<el-table-column prop="groupName" label="分组名称"></el-table-column>
<el-table-column label="操作" width="220" align="center">
<template #default="scope">
<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)" v-permiss="15">
编辑
</el-button>
<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index)" v-permiss="16">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
background
layout="total, prev, pager, next"
:current-page="query.pageIndex"
:page-size="query.pageSize"
:total="pageTotal"
@current-change="handlePageChange"
></el-pagination>
</div>
</div>

<!-- 编辑弹出框 -->
<el-dialog title="编辑" v-model="editVisible" width="30%">
<el-form label-width="70px">
<el-form-item label="用户名">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="地址">
<el-input v-model="form.address"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="editVisible = false">取 消</el-button>
<el-button type="primary" @click="saveEdit">确 定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>

<script setup lang="ts" name="basetable">
import { ref, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
import { gatewayServerData } from '../api/index';

interface TableItem {
id: number;
name: string;
money: string;
state: string;
date: string;
address: string;
}

const query = reactive({
groupId: '',
pageIndex: 1,
pageSize: 10
});
const tableData = ref<TableItem[]>([]);
const pageTotal = ref(0);
// 获取表格数据
const getData = () => {
gatewayServerData(query).then(res => {
tableData.value = res.data.list;
pageTotal.value = res.data.pageTotal || 50;
});
};
getData();

// 查询操作
const handleSearch = () => {
query.pageIndex = 1;
getData();
};
// 分页导航
const handlePageChange = (val: number) => {
query.pageIndex = val;
getData();
};

// 删除操作
const handleDelete = (index: number) => {
// 二次确认删除
ElMessageBox.confirm('确定要删除吗?', '提示', {
type: 'warning'
})
.then(() => {
ElMessage.success('删除成功');
tableData.value.splice(index, 1);
})
.catch(() => {});
};

// 表格编辑时弹窗和保存
const editVisible = ref(false);
let form = reactive({
name: '',
address: ''
});
let idx: number = -1;
const handleEdit = (index: number, row: any) => {
idx = index;
form.name = row.name;
form.address = row.address;
editVisible.value = true;
};
const saveEdit = () => {
editVisible.value = false;
ElMessage.success(`修改第 ${idx + 1} 行成功`);
tableData.value[idx].name = form.name;
tableData.value[idx].address = form.address;
};
</script>

<style scoped>
.handle-box {
margin-bottom: 20px;
}

.handle-select {
width: 120px;
}

.handle-input {
width: 300px;
}
.table {
width: 100%;
font-size: 14px;
}
.red {
color: #F56C6C;
}
.mr10 {
margin-right: 10px;
}
.table-td-thumb {
display: block;
margin: auto;
width: 40px;
height: 40px;
}
</style>

一个 vue 页面分为三部分,包括:template、script、style, 也就是最上面的 template 写 html 代码、script 写 js 代码、style 写 css 样式代码。

页面 API 调用

在 gateway_server.vue 的 JS 代码块部分 gatewayServerData()执行的是 api 包下 index.ts 中的内容。—— 注意 api 包下可以有多个 index.ts 你可以按照不同领域来提供,并在页面中引入即可。

api/index.ts

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
import request from '../utils/request';
import {config} from "md-editor-v3/lib/MdEditor/config";

export const fetchData = () => {
return request({
url: './table.json',
method: 'get'
});
};

// export const gatewayServerData = () => {
// return request({
// url: './gateway_server.json',
// method: 'get'
// });
// };

// function 方式定义函数
// @ts-ignore
export function gatewayServerData(query) {
return request({
url: 'http://localhost:8001/wg/admin/data/queryGatewayServer?groupId=' + query.groupId + '&page=' + query.pageIndex + '&limit=' + query.pageSize,
method: 'get'
});
}

// const 方式定义函数
// @ts-ignore
export const gatewayServerDetailData = (query) => {
return request({
// url: './gateway_server_detail.json',
url: 'http://localhost:8001/wg/admin/data/queryGatewayServerDetail?groupId=' + query.groupId + '&gatewayId=' + query.gatewayId + '&page=' + query.pageIndex + '&limit=' + query.pageSize,
method: 'get'
});
};
// @ts-ignore
export const gatewayDistributionData = (query) => {
return request({
// url: './gateway_distribution.json',
url: 'http://localhost:8001/wg/admin/data/queryGatewayDistribution?groupId=' + query.groupId + '&gatewayId=' + query.gatewayId + '&page=' + query.pageIndex + '&limit=' + query.pageSize,
method: 'get'
});
};
// @ts-ignore
export const applicationSystemData = (query) => {
return request({
// url: './application_system.json',
url: 'http://localhost:8001/wg/admin/data/queryApplicationSystem?systemId=' + query.systemId + '&systemName=' + query.systemName + '&page=' + query.pageIndex + '&limit=' + query.pageSize,
method: 'get'
});
};
// @ts-ignore
export const applicationInterfaceData = (query) => {
return request({
// url: './application_interface.json',
url: 'http://localhost:8001/wg/admin/data/queryApplicationInterface?systemId=' + query.systemId + '&interfaceId=' + query.interfaceId + '&page=' + query.pageIndex + '&limit=' + query.pageSize,
method: 'get'
});
};
// @ts-ignore
export const applicationInterfaceMethodData = (query) => {
return request({
// url: './application_interface_method.json',
url: 'http://localhost:8001/wg/admin/data/queryApplicationInterfaceMethodList?systemId=' + query.systemId + '&interfaceId=' + query.interfaceId + '&page=' + query.pageIndex + '&limit=' + query.pageSize,
method: 'get'
});
};

mock 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"list": [
{
"groupId": "10001",
"groupName": "上海网关"
},
{
"groupId": "10002",
"groupName": "北京网关"
},
{
"groupId": "10003",
"groupName": "成都网关"
}
],
"pageTotal": 3
}

路由配置

router/index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 {
path: '/',
name: 'Home',
component: Home,
children: [
{
path: '/dashboard',
name: 'dashboard',
meta: {
title: '系统首页',
permiss: '1',
},
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
},
{
path: '/gateway_server',
name: 'gateway_server',
meta: {
title: '网关分组',
permiss: '2',
},
component: () => import(/* webpackChunkName: "table" */ '../views/gateway_server.vue'),
},
………………

在路由ts中,有一块是专门把 vue 和路径关联起来的配置,如果没有做这部分配置页面会访问失败。

菜单配置

components/sidebar.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
icon: 'Calendar',
index: '1',
title: '网关配置',
permiss: '2',
subs: [
{
index: '/gateway_server',
title: '网关分组',
permiss: '2',
},
{
index: '/gateway_server_detail',
title: '算力节点',
permiss: '2',
},
{
index: '/gateway_distribution',
title: '网关映射',
permiss: '2',
},
],
},