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_IDVUE_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工程师根据不同的客户,设计好全局的样式,开发人员只管开发功能就可以了。

posted @ 2025-05-07 16:14  lym_1978  阅读(37)  评论(0)    收藏  举报