kedao中间件-Vue开发篇
@
一、概述
本文主要通过创建Vue2前端工程,调用kedao中间件上的服务,介绍如何快速开发前端系统功能。
使用 VSCode 作为开发工具,通过远程连接到开发服务器的方式,开展远程开发。
kedao中间件官网:https://www.yckj-kedao.com
系列文章:
《kedao中间件-安装篇》
《kedao中间件-C++服务篇》
《kedao中间件-Java服务篇》
《kedao中间件-集群及发布篇》
《kedao中间件-并发测试篇》
《kedao中间件-Linux系统安装篇》
《kedao中间件-数据库安装篇》
二、前提条件
在 kedao creator 中创建了C++/Java系统。
本文以之前创建好的Java系统 prj_java_test 为例。
三、Vue 前端开发
1、创建 Vue 工程
打开kedao creator,进入【我的系统】-【prj_java_test】-【前端工程】功能,点击【创建前端工程】来创建前端工程,如图:

创建工程名称为 my_vue2_prj,创建成功后在列表中出现
2、增加 *.vue 模板文件
展开工程树,打开到 【my_vue2_prj】-【src】-【views】,右键新建一个文件夹查询模块 query_module,然后右键 query_module 文件夹,创建文件 query_function.vue,创建成功后,默认生成一个vue模板文件,如图:

3、在 VSCode 中打开代码
VSCode 远程到开发服务器上,打开kedao的安装目录*:/opt/kedao
在 src_front/lym_test/prj_java_test/my_vue2_prj/src/views/query_module下看到刚才创建的 query_function.vue文件,如图:

3、核心源代码
3.1、环境变量
3个环境变量:.env.development、.env.production、.env.staging,默认内容如下:
# just a flag
ENV = 'development'
# 使用 VUE_APP_ 开头的变量会被 webpack 自动加载
# base api
VUE_APP_BASE_API = 'http://192.168.43.30'
# API参数中的appid
VUE_APP_APP_ID = 'f4137243797841788feffdb4e4d1d15e'
# 签名字符串不建议写在代码中,应该通过服务获取
# API参数中参与签名的key
VUE_APP_SIGN_KEY = 'd7d76b9c36624db88e62665baabf6c37c5f073f471e042bd8dd9b79977f66a13514366d75250487ba7872db8d6259db3291365fdacaa4e4dbd889794da19424d'
# API参数的系统名称
VUE_APP_SYS_NAME = 'prj_java_test'
# 是否AES加密
VUE_APP_IS_ENCRYPT = '0'
# AES加密密钥和向量 不要使用这3个符号 ~ | &
VUE_APP_AES_KEY = ''
VUE_APP_AES_IV = ''
# 部署时相对于www根目录的路径
VUE_APP_PUBLIC_PATH = '/'
说明:
VUE_APP_BASE_API:默认是访问开发服务器kedao中间件的 url,如果使用了非默认端口 80 和 443,如8090端口,则 Url 填写为 http://192.168.43.100:8090
VUE_APP_APP_ID 和 VUE_APP_SIGN_KEY:appid,sign_key 在创建系统页面中查看,这里会自动带过来,这是调用服务时组织参数的必要参数,也是服务身份识别标志。
VUE_APP_SYS_NAME:系统名称,组织api参数时必传。
VUE_APP_IS_ENCRYPT:加密标志,自定义参数。
VUE_APP_AES_KEY:aes-key,自定义参数。
VUE_APP_AES_IV:aes-iv,自定义参数。
VUE_APP_PUBLIC_PATH:部署网站时的相对于根目录的路径,"/" 表示将打包的文件部署到网站的根目录。
3.2、API接口
打开 src/api/call_api.js 文件,这里封装了3个接api:call_api、multipart_upload、download,代码如下:
import request from '@/utils/request'
/**
* post 请求,具体访问的服务在 Request_Obj 对象中指定
* @param {*} req 的数据结构为 Request_Obj 类型
* @returns 返回的结果
*/
export function call_api(req = {}, _headers = { 'Content-Type': 'application/json; charset=utf-8' }, _baseURL = '') {
return request({
baseURL: (_baseURL === '' ? process.env.VUE_APP_BASE_API : _baseURL),
url: '/api',
method: 'post',
data: req,
headers: _headers
})
}
export function multipart_upload(req = {}, _headers = { 'Content-Type': 'multipart/form-data' }, _baseURL = '') {
let reqstr = encodeURIComponent(req.reqstr)
return request({
baseURL: (_baseURL === '' ? process.env.VUE_APP_BASE_API : _baseURL),
url: '/multipart_upload?reqstr=' + reqstr,
method: 'post',
data: req.body,
headers: _headers
})
}
export function download(reqstr = '', _headers = { 'Content-Type': 'text/plain' }, _baseURL = '') {
reqstr = encodeURIComponent(reqstr)
return request({
baseURL: (_baseURL === '' ? process.env.VUE_APP_BASE_API : _baseURL),
url: '/download?reqstr=' + reqstr,
method: 'get',
headers: _headers,
responseType: 'blob' // 告诉 axios 返回的是 Blob 对象(文件),必须要设置该参数。否则,返回的数据将不正确
})
}
说明:
call_api:所有的服务请求,都通过该接口访问。
multipart_upload:带身份验证的上传文件api,可以指定上传到服务器的任意位置。
download:带身份验证的下载文件api,可以指定下载服务器上任意位置的文件。
以上3个接口并非直接使用,下面结合服务参数会进一步封装。
3.3、API 的全局方法
打开 src/store/modules/call_svc.js 文件,这里封装了3个全局方法:call_kedao_api、call_upload_api、call_download_api,代码如下:
// 调用服务
import { call_api, multipart_upload, download } from '@/api/call_api'
import { deepClone } from '@/utils';
import { sign } from "@/utils/sign";
import { get_mime_type } from "@/utils/get-mime-type";
import { getToken, getUsr_id, getMain_usr_id, getOrg_id, getSys_id} from '@/utils/auth';
const state = {}
const mutations = {}
const actions = {
call_kedao_api({ commit }, req_obj) {
let request_obj = {
appid: process.env.VUE_APP_APP_ID,
sys_name: process.env.VUE_APP_SYS_NAME,
mdl_name: req_obj.mdl_name,
svc_name: req_obj.svc_name,
body: {
sys_head: {
usr_id: getUsr_id(),
org_id: getOrg_id(),
mdl_func_id: "",
login_key: getToken()
},
data: deepClone(req_obj.data)
}
}
// 处理 headers
let headers = {}
headers['Content-Type'] = 'application/json; charset=utf-8'
// 加密处理(根据自己业务的需要进行加密处理)
/*
if (process.env.VUE_APP_IS_ENCRYPT === '1') {
if (typeof (request_obj.data) === 'object') {
// 将对象转换为字符串
request_obj.data = JSON.stringify(request_obj.data)
}
request_obj.data = AES_CBC_encrypt(request_obj.data)
}
*/
// 签名处理
request_obj.sign = sign(request_obj)
// 处理异步任务
return new Promise((resolve, reject) => {
call_api(request_obj, headers).then(response_obj => {
if (typeof response_obj !== 'object') {
// 如果服务中做了加密处理,这里进行相应的解密
/*
if (process.env.VUE_APP_IS_ENCRYPT === '1') {
response_obj = JSON.parse(AES_CBC_decrypt(response_obj);
}
*/
resolve(JSON.parse(response_obj));
} else {
resolve(response_obj)
}
}).catch(error => {
let out_obj = {
code: -1,
err_msg: error
}
resolve(out_obj)
})
})
},
/**
* 上传文件
* @param {*} param0
* @param {*} formData 要上传的文件是一个数组: [{name:'', path:''},{},...]; 其中,name 为上传文件的服务器端目标路径,path为本地要上传的文件路径
* @returns
*/
call_upload_api({ commit }, formData) {
// 生成参数对象
// 说明:在上传文件时,会调用 sys_commons 模块下的 svc_verify_login_identity 服务,校验用户身份的有效性,如果身份不合法,则不允许上传
let verify_request_obj = {
appid: process.env.VUE_APP_APP_ID,
sys_name: process.env.VUE_APP_SYS_NAME,
mdl_name: 'sys_commons',
svc_name: 'svc_verify_login_identity',
body: {
sys_head: {
usr_id: getUsr_id(),
org_id: getOrg_id(),
sys_id: getSys_id(),
mdl_func_id: "",
login_key: getToken()
},
data: '' // 自定义校验数据
}
}
// 组织参数并进行签名
verify_request_obj.sign = sign(verify_request_obj)
let req_str = JSON.stringify(verify_request_obj);
let request_obj = {
body: formData,
reqstr: req_str
}
// 处理 headers
let headers = {}
headers['Content-Type'] = 'multipart/form-data'
// 处理异步任务
return new Promise((resolve, reject) => {
multipart_upload(request_obj, headers).then(response_obj => {
if (typeof response_obj !== 'object') {
// 如果服务中做了加密处理,这里进行相应的解密
/*
if (process.env.VUE_APP_IS_ENCRYPT === '1') {
response_obj = JSON.parse(AES_CBC_decrypt(response_obj);
}
*/
resolve(JSON.parse(response_obj));
} else {
resolve(response_obj)
}
}).catch(error => {
let out_obj = {
code: -1,
err_msg: error
}
resolve(out_obj)
})
})
},
/**
*
* @param {*} param0
* @param {*} filename 为服务器上的文件路径(绝对路径或相对路径)
* @returns
*/
call_download_api({ commit }, filename) {
// 生成参数对象
// 说明:在下载文件时,会调用 sys_commons 模块下的 svc_verify_login_identity 服务,校验用户身份的有效性,如果身份不合法,则不允许下载
let request_obj = {
appid: process.env.VUE_APP_APP_ID,
sys_name: process.env.VUE_APP_SYS_NAME,
mdl_name: 'sys_commons',
svc_name: 'svc_verify_login_identity',
body: {
sys_head: {
usr_id: getUsr_id(),
org_id: getOrg_id(),
sys_id: getSys_id(),
mdl_func_id: "",
login_key: getToken()
},
data: filename // 要下载的文件
}
}
// 加密处理(根据自己业务的需要进行加密处理)
/*
if (process.env.VUE_APP_IS_ENCRYPT === '1') {
if (typeof (request_obj.data) === 'object') {
// 将对象转换为字符串
request_obj.data = JSON.stringify(request_obj.data)
}
request_obj.data = AES_CBC_encrypt(request_obj.data)
}
*/
// 签名
request_obj.sign = sign(request_obj)
// 对象转字符串
let reqstr = JSON.stringify(request_obj)
// 获取文件后缀
let pos = filename.lastIndexOf('.')
let extent = filename.substr(pos, filename.length)
// 处理 headers
let headers = {}
headers['Content-Type'] = get_mime_type(extent)
// 处理异步任务
return new Promise((resolve, reject) => {
download(reqstr, headers).then(response_obj => {
if (typeof response_obj !== 'object') {
// 如果服务中做了加密处理,这里进行相应的解密
/*
if (process.env.VUE_APP_IS_ENCRYPT === '1') {
response_obj = JSON.parse(AES_CBC_decrypt(response_obj);
}
*/
resolve(JSON.parse(response_obj));
} else {
resolve(response_obj)
}
}).catch(error => {
reject('')
})
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
3.3.1 call_kedao_api
1)关于 call_kedao_api()方法中服务请求的参数对象request_obj
let request_obj = {
appid: process.env.VUE_APP_APP_ID,
sys_name: process.env.VUE_APP_SYS_NAME,
mdl_name: req_obj.mdl_name,
svc_name: req_obj.svc_name,
body: {
sys_head: {
usr_id: getUsr_id(),
org_id: getOrg_id(),
mdl_func_id: "",
login_key: getToken()
},
data: deepClone(req_obj.data)
}
}
// 签名处理
request_obj.sign = sign(request_obj)
appid:必填,创建系统时生成,可以在【基础功能】-【创建系统】的系统列表中【签名和appid】中查看。
sys_name:必填,创建系统时生成,系统名称。
mdl_name:必填,注册服务时的模块名称。
svc_name:必填,注册服务时的服务名称。
body:必填,注册服务时,服务函数的入参对象。body的对象可以自定义,这里是默认的泛型对象SVC_REQUEST_OBJ<T>
body.sys_head:默认的头部数据,用于调用服务的身份认证。
body.data:业务数据对象。
sign:服务要求签名时,必填,无签名时非必填,签名字符串,签名方法:
import { SHA256 } from '@/utils/crypto'
export function sign(request_obj) {
// 拼接 SHA256 签名的字符串
let str_sign = ''
str_sign += 'appid=' + request_obj.appid
str_sign += '&body=' + (typeof (request_obj.body) === 'object' ? JSON.stringify(request_obj.body) : request_obj.body)
str_sign += '&mdl_name=' + request_obj.mdl_name
str_sign += '&svc_name=' + request_obj.svc_name
str_sign += '&sys_name=' + request_obj.sys_name
str_sign += '&key=' + process.env.VUE_APP_SIGN_KEY
// 进行md5加密,并给签名赋值
// return MD5(str_sign).toString()
return SHA256(str_sign).toString()
}
request_obj 对象数据结构可以自定义定义,但要注意:可以自定义的是 body 对象的结构,其他部分不能变动。
在 *.vue 文件中调用服务:
let req_obj = {
mdl_name:'模块名称',
svc_name:'服务名称',
data: 业务数据对象
}
this.$store.dispatch('call_svc/call_kedao_api',req_obj).then(res => {
if (res.code === 0) {
// 调用成功
// 业务数据对象:res.data
}else{
// 服务内发生错误
this.$message({
message: res.err_msg,
type: 'error',
showClose: true,
duration: 6000,
offset: window.screen.height / 2.62
});
}
})
3.3.2 call_upload_api
2)关于 call_upload_api() 中服务请求的数据对象 request_obj
// 生成参数对象
// 说明:在上传文件时,会调用 sys_commons 模块下的 svc_verify_login_identity 服务,校验用户身份的有效性,如果身份不合法,则不允许上传
let verify_request_obj = {
appid: process.env.VUE_APP_APP_ID,
sys_name: process.env.VUE_APP_SYS_NAME,
mdl_name: 'sys_commons',
svc_name: 'svc_verify_login_identity',
body: {
sys_head: {
usr_id: getUsr_id(),
org_id: getOrg_id(),
sys_id: getSys_id(),
mdl_func_id: "",
login_key: getToken()
},
data: '' // 自定义校验数据
}
}
// 组织参数并进行签名
verify_request_obj.sign = sign(verify_request_obj)
let req_str = JSON.stringify(verify_request_obj);
let request_obj = {
body: formData,
reqstr: req_str
}
body:上传到数据formData,在 *.vue 文件中调用上传文件的服务,具体实现参考 upload_download.vue :
// 组织上传的文件
let formData = new FormData()
this.fileList.forEach((item, index)=>{
let len = this.upload_download_data.server_upload_path.length;
if (this.upload_download_data.server_upload_path.substring(len - 1, len) === '/') {
// 服务器保存的目标文件-本地文件
formData.append(this.upload_download_data.server_upload_path + item.name, item.raw)
} else {
// 服务器保存的目标文件-本地文件
formData.append(this.upload_download_data.server_upload_path + '/' + item.name, item.raw)
}
})
// 调用上传api
this.$store.dispatch('call_svc/call_upload_api', formData).then(res => {
if (res.code === 0) {
this.$message({
message: '文件上传成功。',
type: 'success',
offset: window.screen.height / 2.62
})
} else {
this.$message({
message: res.err_msg,
type: 'error',
showClose: true,
duration: 6000,
offset: window.screen.height / 2.62
})
}
})
reqstr:为一个验证服务的请求数据对象的 Json 字符串,服务 svc_verify_login_identity 可以是自己编写的任意服务
3.3.3 call_download_api
3)关于 call_download_api() 中服务请求的数据对象 request_obj:
// 说明:在下载文件时,会调用 sys_commons 模块下的 svc_verify_login_identity 服务,校验用户身份的有效性,如果身份不合法,则不允许下载
let request_obj = {
appid: process.env.VUE_APP_APP_ID,
sys_name: process.env.VUE_APP_SYS_NAME,
mdl_name: 'sys_commons',
svc_name: 'svc_verify_login_identity',
body: {
sys_head: {
usr_id: getUsr_id(),
org_id: getOrg_id(),
sys_id: getSys_id(),
mdl_func_id: "",
login_key: getToken()
},
data: filename // 要下载的文件
}
}
// 签名
request_obj.sign = sign(request_obj)
// 对象转字符串
let reqstr = JSON.stringify(request_obj)
// 获取文件后缀
let pos = filename.lastIndexOf('.')
let extent = filename.substr(pos, filename.length)
// 处理 headers
let headers = {}
headers['Content-Type'] = get_mime_type(extent)
reqstr:需要将 request_obj 转换为 Json 字符串,然后将 reqstr 拼接到 Url 的参数中:
export function download(reqstr = '', _headers = { 'Content-Type': 'text/plain' }, _baseURL = '') {
reqstr = encodeURIComponent(reqstr)
return request({
baseURL: (_baseURL === '' ? process.env.VUE_APP_BASE_API : _baseURL),
url: '/download?reqstr=' + reqstr,
method: 'get',
headers: _headers,
responseType: 'blob' // 告诉 axios 返回的是 Blob 对象(文件),必须要设置该参数。否则,返回的数据将不正确
})
}
同样上传文件一样,服务 svc_verify_login_identity 可以是自己定义的任意服务。
headers:必须将文件类型对应的 mime type 传入到头部 Content-Type 中。
在 *.vue 文件中调用下载文件的服务,具体实现参考 upload_download.vue
// this.upload_download_data.server_filename:服务器上要下载的文件绝对路径或相对于kedao安装路径bin目录(如:/opt/kedao/bin)的相对路径
this.$store.dispatch('call_svc/call_download_api', this.upload_download_data.server_filename).then(res => {
// res 为下载文件的内容
if (res !== '') {
let filename = this.upload_download_data.server_filename
let len = filename.length
let pos = filename.lastIndexOf('/')
let file_name = filename.substring(pos + 1, len)
// 获取文件后缀
let extent_pos = filename.lastIndexOf('.')
let extent = filename.substr(extent_pos, len)
// 获取文件对应的 mime_type
let Content_Type = get_mime_type(extent)
// 将内容转换为 Blob 格式
const blob_data = new Blob([res], { type: Content_Type }); // new Blob([res], { type: 'text/plain' });
// const blob_data = new Blob([res]); // new Blob([res], { type: 'text/plain' });
const link = document.createElement('a');
// window.URL.createObjectURL() 方法用于创建一个表示指定对象的 URL,需要传入一个 Blob 对象或者 File 对象作为参数
link.href = window.URL.createObjectURL(blob_data)
// 文件保存到操作系统默认的 download 目录下
link.setAttribute('download', file_name)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
} else {
this.$message({
message: '下载失败。',
type: 'error',
showClose: true,
duration: 6000,
offset: window.screen.height / 2.62
})
}
})
3.4、登录
登录涉及到路由权限及设置一些全局变量,实现的代码文件 src/store/modules/user.js,主要方法是login():
login({ commit }, req_obj) {
let request_obj = {
appid: process.env.VUE_APP_APP_ID,
sys_name: process.env.VUE_APP_SYS_NAME,
mdl_name: 'sys_login',
svc_name: 'svc_login',
body: {
sys_head: {
usr_id: "", // 当前登录用户的id,在登录时返回并写入cookie中,通过 cookie 获取
org_id: "", // 当前登录用户所属的机构id,在登录时返回并写入cookie中,通过 cookie 获取
sys_id: "", // 业务系统id,在登录时返回并写入cookie中,通过 cookie 获取
mdl_func_id: "", // 功能id,当前功能的id,通过共名称查找。作用是根据用户是否具有操作该功能的权限来控制此次服务的执行
login_key: '' // 登录用户的 token
},
data: req_obj
}
}
// 签名
request_obj.sign = sign(request_obj)
let headers = {}
headers['Content-Type'] = 'application/json; charset=utf-8'
// 处理异步任务
return new Promise((resolve, reject) => {
call_api(request_obj, headers).then(response_obj => {
if (response_obj.code !== 0) {
resolve(response_obj)
}
// 生成动态路由
const userRoutes = addDynamicRoutes(response_obj.data.obj_2)
commit('SET_ROUTES', userRoutes) // 路由
commit('SET_TOKEN', response_obj.data.obj_1.login_key)
commit('SET_ROLES', ['admin']) // 角色,这里固定值,因为服务没有返回角色
commit('SET_NAME', response_obj.data.obj_1.usr_name) // 用户名称
// 设置 cookies
setToken(response_obj.data.obj_1.login_key)
setUsr_id(response_obj.data.obj_1.usr_id)
setOrg_id(response_obj.data.obj_1.org_id)
setUsr_name(response_obj.data.obj_1.usr_name)
resolve(response_obj)
}).catch(error => {
let out_obj = {
code: -1,
err_msg: error
}
resolve(out_obj)
})
})
}
这里要注意的是动态路由的实现。
*.vue登录的实现代码参考:src/views/login/index.vue
四、功能开发
在VSCode中,打开一开始创建 Vue 工程时增加的文件 src/views/query_module/query_function.vue
开发一个查询功能,调用服务,然后展示服务返回的结果
模块名称:mdl_test
服务名称:svc_test
请求参数:SVC_REQUEST_OBJ<SYS_FUNCTION> 中的 body.data 对象数据结构为 SYS_FUNCTION
响应结果:SVC_RESPONSE_OBJ<List<SYS_FUNCTION>> 中的data 对象数据结构为 List<SYS_FUNCTION>
注:svc_test 是在《kedao-Java服务篇》或者《kedao-C++服务篇》中创建的服务**;
1、在 methods 中调用服务
修改服务模板中对应的内容
将 xxx_svc_name 改为 svc_test
同时修改 mdl_name 的值为: mdl_test
修改 svc_name 的值为: svc_test
2、修改 el-header 中的查询条件
修改 el-header中的查询条件,将 svc_request_data 中的 field_name1, field_name2, ... 为具体的字段,如:
field_name1 改为 func_code // 功能编码
field_name2 改为 menu_title // 菜单名称
删除掉 field_name3、field_name4
3、修改 el-header 中的查询条件
修改 el-table中展示的字段:
将 lst_svc_response_data 中的 field_name1, field_name2, ... 为具体的字段:
field_name1 改为 func_code // 功能编码
field_name2 改为 menu_title // 菜单名称
field_name3 改为 router_path // 路由路径
field_name4 改为 component_path // 组件路径
修改后完整的代码如下:
<template>
<div class='app-container' style="height: 100%;">
<el-container>
<el-header height="100%" style="align-content: center;">
<!-- 查询条件 -->
<el-form ref="form_query_function" :model="svc_request_data" label-width="80px">
<el-row>
<el-col :span="6">
<el-form-item label="功能编码">
<el-input v-model="svc_request_data.func_code"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="菜单名称">
<el-input v-model="svc_request_data.menu_title"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col span="24">
<div style="float: right;">
<el-button type="primary" @click="onQuery">查询</el-button>
<el-button type="primary" @click="onAdd">增加</el-button>
<el-button type="primary" @click="onReset">重置</el-button>
</div>
</el-col>
</el-row>
</el-form>
</el-header>
<el-main>
<!-- 数据表格 -->
<el-table :height="tableHeight"
border
stripe
style="margin-top: 6px;"
:cell-style="{padding: '3px'}"
:data="lst_svc_response_data"
:header-cell-style="{background:'rgb(245,247,250)',color:'#606266','text-align':'center'}"
v-loading.body="tableLoading"
highlight-current-row
tooltip-effect="light">
<el-table-column show-overflow-tooltip label="功能编码" align="left" width="180px">
<template slot-scope="scope">
<span>{{scope.row.func_code}}</span>
</template>
</el-table-column>
<el-table-column show-overflow-tooltip label="菜单名称" align="left" width="180px">
<template slot-scope="scope">
<span>{{scope.row.menu_title}}</span>
</template>
</el-table-column>
<el-table-column show-overflow-tooltip label="路由路径" align="left" width="180px">
<template slot-scope="scope">
<span>{{scope.row.router_path}}</span>
</template>
</el-table-column>
<el-table-column show-overflow-tooltip label="组件路径" align="left">
<template slot-scope="scope">
<span>{{scope.row.component_path}}</span>
</template>
</el-table-column>
<el-table-column show-overflow-tooltip label="操作" fixed="right" align="center" width="160px" >
<template slot-scope="scope">
<el-button type="text" @click="onRowEdit(scope.row)">编辑</el-button>
<el-button type="text" @click="onRowDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-main>
</el-container>
<!-- 增加对话框(略)-->
<!-- 编辑对话框(略)-->
</div>
</template>
<script>
export default {
data(){
return{
tableHeight: 0,
tableLoading: false,
showAddDialogFlag: false,
showEditDialogFlag: false,
svc_request_data: {}, // 服务请求参数对象
lst_svc_response_data: [] // 服务响应参数对象
}
},
created() {
// 180是表格外其它布局占的高度,这个数值根据自己实际情况修改
this.tableHeight = window.innerHeight - 264
},
activated () {
/**
* 解决页面切换时行错乱
*/
this.$nextTick(() => {
this.$refs.tbServerList.doLayout()
})
},
mounted() {
// 监听浏览器窗口变化,动态计算表格高度
window.onresize = () => {
return (() => {
this.tableHeight = window.innerHeight - 264
})()
}
},
methods:{
onQuery() {
// 组织或校验服务的请求参数
// 调用 XXX 服务(异步)
this.svc_test(this.svc_request_data)
},
onAdd() {
// 设置显示增加对话框
this.showAddDialogFlag = true
},
onReset() {
// 重置查询条件
this.svc_request_data = {
func_code: "",
menu_title: "",
}
},
onRowEdit(row) {
// 设置显示编辑对话框
this.showEditDialogFlag = true
},
onRowDelete(row) {
this.$confirm(`确定要删除该记录吗`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'confirm',
}).then(()=> {
// 确定之后做的事情
}).catch(err => {
// 异常错误
// console.error(err)
})
},
svc_test(in_data) {
let that = this
let req_obj = {
mdl_name:'mdl_test',
svc_name:'svc_test',
data: in_data
}
this.tableLoading = true
// 调用全局服务api(异步)
this.$store.dispatch('call_svc/call_kedao_api',req_obj).then(res => {
if (res.code === 0) {
// 接收服务返回的结果
that.lst_svc_response_data = res.data
} else {
this.$message({
message: res.err_msg,
type: 'error',
showClose: true,
duration: 6000,
offset: window.screen.height / 2.62
})
}
}).finally(() => {
that.tableLoading = false
})
}
}
}
</script>
<style scoped>
</style>
这里着重演示如何调用服务,及服务的入参、出参的数据绑定,具体需要自己去阅读理解。
现代系统的开发,业务逻辑实现基本上在服务端实现,前端主要用于展现,以及界面操作的控制。
前端掌握服务的调用和数据绑定的关系基本就可以,不应关注业务逻辑;而是关注页面交互的友好性和样式美观。
五、安装插件
在终端中,进入到 src_front/lym_test/prj_java_test/my_vue2_prj 目录,执行 npm install
在 VSCode 的终端中执行,打开一个终端,进入到 src_front/lym_test/prj_java_test/my_vue2_prj, 输入并执行命令 npm install,安装可能需要10几分钟,安装完成的截图:

五、启动工程
在工程目录 my_vue2_prj 执行 npm run dev 命令,启动开发模式,如图:

You may use special comments to disable some warnings.
Use // eslint-disable-next-line to ignore the next line.
Use /* eslint-disable */ to ignore all warnings in a file.
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.43.30:8080/
Note that the development build is not optimized.
To create a production build, run npm run build.
系统Url :http://192.168.43.30:8080/
六、菜单及路由
在浏览器地址栏输入 http://192.168.43.30:8080/,打开链接:

使用默认用户登录系统,在系统菜单中找到【功能管理】,查询,在基础功能行的操作列中,点击增加同级,增加一个查询模块,功能编码/路由name:query_module,路由path:/query_module,query_module为一开始创建vue工程时在views下创建的文件夹,,如图:

在查询模块行操作列中,点增加下级,增加一个路由功能查询,功能编码/路由name:query_function,路由path:/query_function,组件路径:@/views/query_module/query_function.vue,组件路径是源代码目录中相对于 views 的路径,如图:

增加成功后,在左侧菜单能立刻看到增加的功能,如图:

点击菜单路由功能查询,打开功能界面,点击查询,就能看到服务返回的结果,如图:

到这,一个功能就开发完成了。
前端的功能开发,核心就是调用服务,绑定数据,及数据展示,其他就交给UI工程师进行界面美化了。
七、打包发布
1、打包
打包时,要注意生产环境的环境变量 .env.production 中的 VUE_APP_PUBLIC_PATH,如果将打包结果放到网站的根目录,则设置为 "/";如果要放到根目录的其他路径,如:mywebsite,则设置为 "/mywebsite"。这里,设置为 "/",将打包文件放到根目录下。
终端中在项目路径下执行 npm run build:prod 命令进行打包,打包完成后,打包文件放在 dist 目录下
如果打包出现错误:
ERROR Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:79:19)
at Object.createHash (node:crypto:139:10)
at /opt/kedao/src_front/lym_test/prj_java_test/my_vue2_prj/node_modules/compression-webpack-plugin/dist/index.js:243:42
at CompressionPlugin.compress (/opt/kedao/src_front/lym_test/prj_java_test/my_vue2_prj/node_modules/compression-webpack-plugin/dist/index.js:284:9)
at /opt/kedao/src_front/lym_test/prj_java_test/my_vue2_prj/node_modules/compression-webpack-plugin/dist/index.js:305:12
at _next2 (eval at create (/opt/kedao/src_front/lym_test/prj_java_test/my_vue2_prj/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:22:17)
at eval (eval at create (/opt/kedao/src_front/lym_test/prj_java_test/my_vue2_prj/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:39:1)
at /opt/kedao/src_front/lym_test/prj_java_test/my_vue2_prj/node_modules/copy-webpack-plugin/dist/index.js:91:9
出现这个错误是因为 node.js V17及以上的版本中使用了 OpenSSL3.0,,对一些使用 OpenSSL1.1的插件产生了兼容性问题。
解决方案:修改 package.json,在打包命令前面增加 export NODE_OPTIONS=--openssl-legacy-provider,如:
"dev": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve"
"build:prod": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve"
"build:stage": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --mode staging"
打包时出现以下错误:
ReferenceError: Validation failed. errors:
`stringArrayEncoding` errors:
- each value in stringArrayEncoding must be one of the following values: none, base64, rc4
, - All stringArrayEncoding's elements must be unique
, - stringArrayEncoding must be an array
解决方案:把 vue.config.js 文件中的js加密部分注释掉或去掉:
// js代码加密
// new JavaScriptObfuscator({
// compact: true,
// controlFlowFlattening: false,
// deadCodeInjection: false,
// debugProtection: false,
// debugProtectionInterval: false,
// disableConsoleOutput: true,
// identifierNamesGenerator: 'hexadecimal',
// log: false,
// renameGlobals: false,
// rotateStringArray: true,
// selfDefending: true,
// stringArray: true,
// stringArrayEncoding: false,
// stringArrayThreshold: 0.75,
// unicodeEscapeSequence: false
// }, []) //[] 里的是不混淆的代码
打包成功后,在 dist 目录下生成静态文件:

2、发布到 bin/www 目录下
将文件拷贝到 /opt/kedao/bin/www/ 目录下,注意,拷贝时不包括 dist 目录
重启kedao服务 或者修改 config.ini 文件中的 website_release_status 值为 1 ,即:website_release_status=1,加载网站静态文件。
通过修改 website_release_status=1 的方式,约1分钟左右,加载完成。
在浏览器输入kedao中间件api的 url:http://192.168.43.30,打开部署好的网站:

3、使用 Electron 打包
3.1 Electron 工程源码路径
Electron 工程源码根路径在 /opt/kedao/electron_pack/,具体打包工程的所在路径结构与Vue工程的路径结构相同。
3.2 修改Vue工程的环境变量
# 部署时相对于www根目录的路径,对于 Electron 打包,要改成 './'
VUE_APP_PUBLIC_PATH = './'
3.3 编译打包 vue 工程
在vue工程下执行 npm run build:prod 命令打包。
3.4 拷贝 vue工程 dist 下的静态文件
拷贝 vue工程 dist 下的静态文件(不包含 dist 目录)到 Electron 工程的根目录下(与 main.js 同级的目录)。
如果是第一次打包,需要执行 npm install 安装插件。
在 Linux 环境下,执行命令 npm run linux 进行打包。
在 Windows 环境下,执行命令 npm run win 进行打包。
预览不打包,执行命令 npm run start 。
至此,Vue开发篇完成。
八、总结
前端只要掌握好 html 和 JavaScript 的编程基础知识就可以轻松上手,开发出优秀的程序。
前后端分离并不是什么好的选择,UI设计和功能开发是两回事。
UI工程师根据不同的客户,设计好全局的样式,开发人员只管开发功能就可以了。

浙公网安备 33010602011771号