服务端工程师进化史-从零开始的APP开发(5)

1 篇文章 0 订阅
1 篇文章 0 订阅

前章

1.开发环境搭建
2.项目环境搭建
3.golang项目基础框架-前篇
4.golang项目基础框架-后篇

开始

本篇开始搭建管理后台的前端项目,其实纯讲前端好像没啥营养,刚好本项目是采用旧项目改造的基础框架搭建的,就讲讲改造过程中,遇到难点,以及如何处理!主要是笔者工作期间的产物,在给后端项目做前后端技术分离时,将bootstrap+jquery+adminLTE改造成vue项目。

当前项目:
https://github.com/ZTLiao/liaz-admin-web.git
先附上app成品的gitee地址(漫画/轻小说app):
https://gitee.com/liaz-app/liaz-android/releases/download/1.0.0/app-arm64-v8a-release.apk

先说一句,这后台样式可能会有大部分人熟悉,像是某某音、某某聊、某某界的运营后台;如果有幸认识它的话,贵公司有机会能尝试笔者这旧改方案哦!(改造项目已上线运行)
经典皮肤

难点

1.jquery与vue兼容
2.adminLTE菜单加载
3.第三方插件库兼容
4.批量代码搬迁

改造过程中,第三方库的兼容问题是最大,困扰了笔者好几天,最后还好能解决。

技术架构

{
  "name": "liaz-admin-web",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "vue-cli-service serve --mode development",
    "build": "vue-cli-service build --mode production",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "admin-lte": "^2.3.2",
    "axios": "^1.5.1",
    "bootstrap": "^3.3.5",
    "core-js": "^3.8.3",
    "font-awesome": "^4.6.3",
    "ionicons": "^2.0.1",
    "jquery": "^2.2.0",
    "jquery.md5": "^1.0.0",
    "knockout": "^3.5.1",
    "less": "^4.2.0",
    "less-loader": "^11.1.3",
    "popper.js": "^1.16.1",
    "vue": "^3.2.13",
    "vue-router": "^4.0.3",
    "vuex": "^4.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-plugin-router": "~5.0.0",
    "@vue/cli-plugin-vuex": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "sass": "^1.32.7",
    "sass-loader": "^12.0.0"
  }
}

涉及的第三库改造,定制化本地引入

bootstrap-combobox
bootstrap-datepicker
bootstrap-datetimepicker
bootstrap-multiselect
bootstrap-select
bootstrap-table
bootstrap-treeview
clipboard
jquery.form
jQuery-Validation-Engine

vue项目创建

vue create liaz-admin-web

手动搭建

Vue CLI v5.0.8
? Please pick a preset: (Use arrow keys)
  Default ([Vue 3] babel, eslint) 
  Default ([Vue 2] babel, eslint) 
❯ Manually select features 

选择Router、Vuex、CSS Pre-processors、Linter / Formatter

Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: (Press <space> to select, <a> to 
toggle all, <i> to invert selection, and <enter> to proceed)
❯◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◉ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

选vue3(Vue当然是用最新的,哈哈哈)

? Choose a version of Vue.js that you want to start the project with (Use arrow 
keys)3.x 
  2.x 

路由的访问方式(姑且没什么讲究,就选了no)

Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS 
Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use history mode for router? (Requires proper server setup for index fallback 
in production) (Y/n) n

less样式加载器(再新入新库时,有些需要依赖)

Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS 
Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use history mode for router? (Requires proper server setup for index fallback 
in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported 
by default): 
  Sass/SCSS (with dart-sass) 
❯ Less 
  Stylus 

ESLint选择推荐就好,标准会有问题(ESLint真的是个有爱有恨的角色)

Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS 
Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use history mode for router? (Requires proper server setup for index fallback 
in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported 
by default): Less
? Pick a linter / formatter config: 
❯ ESLint with error prevention only 
  ESLint + Airbnb config 
  ESLint + Standard config 
  ESLint + Prettier 
Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS 
Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use history mode for router? (Requires proper server setup for index fallback 
in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported 
by default): Less
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, 
<i> to invert selection, and <enter> to proceed)
❯◉ Lint on save
 ◯ Lint and fix on commit
Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS 
Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use history mode for router? (Requires proper server setup for index fallback 
in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported 
by default): Less
? Pick a linter / formatter config: Standard
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
❯ In dedicated config files 
  In package.json 
Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS 
Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use history mode for router? (Requires proper server setup for index fallback 
in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported 
by default): Less
? Pick a linter / formatter config: Standard
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated 
config files
? Save this as a preset for future projects? (y/N) N

到这一步,vue项目已经创建好,开始旧改的艰难旅程。。。

npm安装

经过一段时间摸索,原先旧项目中有部分库在npm中可安装使用,便直接安装,但需指定版本。(因选择eslint标准配置,存在版本不兼容报错,需强制执行–force)

npm install admin-lte@2.3.2 --force
npm install bootstrap@3.3.5 --force
npm install font-awesome@4.6.3 --force
npm install ionicons@2.0.1 --force
npm install jquery@2.2.0 --force
npm install jquery.md5@1.0.0 --force
npm install knockout@3.5.1 --force
npm install popper.js@1.16.1 --force
npm install axios --force

顺带改一下package.json执行命令

  "scripts": {
    "dev": "vue-cli-service serve --mode development",
    "build": "vue-cli-service build --mode production",
    "lint": "vue-cli-service lint"
  },

jquery和adminLTE整合

main.js导入关键依赖

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import 'jquery'
import 'jquery.md5'

import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.min'

import 'font-awesome/css/font-awesome.min.css'
import 'ionicons/css/ionicons.min.css'

import 'admin-lte/dist/css/AdminLTE.min.css'
import 'admin-lte/dist/css/skins/skin-red-light.min.css'
import 'admin-lte/dist/js/app.min'

createApp(App).use(store).use(router).mount('#app')

public/index.html添加adminLTE的样式class:hold-transition skin-blue sidebar-mini

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body class="hold-transition skin-blue sidebar-mini">
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

难点一:jquery与vue兼容

第一个难点来了,vue识别不出jquery,在执行npm run dev后页面报错

index.js:235 Uncaught ReferenceError: jQuery is not defined
    at eval (index.js:235:4)
    at ./node_modules/jquery.md5/index.js (chunk-vendors.js:202:1)
    at __webpack_require__ (app.js:313:32)
    at fn (app.js:564:21)
    at eval (main.js:8:68)
    at ./src/main.js (app.js:74:1)
    at __webpack_require__ (app.js:313:32)
    at app.js:1488:109
    at __webpack_require__.O (app.js:355:23)
    at app.js:1489:53

需修改eslint的配置

.eslintrc.js

添加jquery:true

module.exports = {
  root: true,
  env: {
    node: true,
    jquery: true
  },
  extends: [
    'plugin:vue/vue3-essential',
    '@vue/standard'
  ],
  parserOptions: {
    parser: '@babel/eslint-parser'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  }
}

vue.config.js

添加webpack插件,指定全局jquery变量

const { defineConfig } = require('@vue/cli-service')
var webpack = require('webpack')
module.exports = defineConfig({
  transpileDependencies: true,
  chainWebpack: config => {
    config
      .plugin('html')
      .tap(args => {
        args[0].title = '管理后台'
        return args
      })
  },
  configureWebpack: {
    plugins: [
      new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery',
        'windows.jQuery': 'jquery',
        Popper: ['popper.js', 'default']
      }),
    ],
  },
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    proxy: {
      '/': {
        ws: false,
        target: process.env.VUE_APP_API_BASE_URL,
        changeOrigin: true,
        pathRewrite: {
          '^/': ''
        }
      }
    },
  },
})

到此,jquery与vue的兼容问题解决。

难点二:adminLTE加载菜单

adminLTE是个模版框架,并没有提供兼容vue写法,只能自己想办法处理。
笔者想到利用vue的component标签,通过切换组件名称,实现页面切换,因此需要将所有的vue页面预先加载到vue中。

<component :is="componentName"></component>

components.js

export default {
    install: function (Vue) {
        const files = require.context('@/views', true, /\.vue$/);
        let components = {};
        files.keys().forEach(key => {
            components[key.replace(/(\.\/|\.vue)/g, '')] = files(key).default;
        });
        Object.keys(components).forEach(item => {
            if (components[item].name) {
                Vue.component(components[item].name, components[item]);
            }
        });
    },
}

在main.js导入

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import 'jquery'
import 'jquery.md5'

import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.min'

import 'font-awesome/css/font-awesome.min.css'
import 'ionicons/css/ionicons.min.css'

import 'admin-lte/dist/css/AdminLTE.min.css'
import 'admin-lte/dist/css/skins/skin-red-light.min.css'
import 'admin-lte/dist/js/app.min'

import components from '@/utils/components'

createApp(App).use(store).use(router).use(components).mount('#app')

也同时将adminLTE提供的模版页面(node_modules/admin-lte/index.html),拆解成三部分

footer

<template>
    <footer class="main-footer">
        <div class="pull-right hidden-xs">
            <b>Version</b> 2.0.0
        </div>
        <strong>Copyright &copy; 2024 <a href="">LIAZ</a>.</strong> All rights
        reserved.
    </footer>
</template>
  
<script>
export default {
    name: 'FooterView',
    components: {
    }
}
</script>
  
<style scoped>
</style>

header

<template>
    <!-- Main Header -->
    <header class="main-header">
        <!-- Logo -->
        <a class="logo">
            <!-- mini logo for sidebar mini 50x50 pixels -->
            <span class="logo-mini"><b>L</b>Z</span>
            <!-- logo for regular state and mobile devices -->
            <span class="logo-lg"><b>LIAZ</b>管理系统</span>
        </a>
        <!-- Header Navbar: style can be found in header.less -->
        <nav class="navbar navbar-static-top" role="navigation">
            <!-- Sidebar toggle button-->
            <a class="sidebar-toggle" data-toggle="offcanvas" role="button">
                <span class="sr-only">Toggle navigation</span>
            </a>
            <!-- Navbar Right Menu -->
            <div class="navbar-custom-menu">
                <ul class="nav navbar-nav">
                    <!-- User Account: style can be found in dropdown.less -->
                    <li class="dropdown user user-menu">
                        <a class="dropdown-toggle" data-toggle="dropdown" style="height:50px;">
                            <img :src="avatar" class="user-image" :alt="name">
                            <span class="hidden-xs"><span :text="name">{{ name }}</span></span>
                        </a>
                        <ul class="dropdown-menu">
                            <!-- User image -->
                            <li class="user-header">
                                <img :src="avatar" class="img-circle" :alt="name" />
                                <p>
                                    <span :text="name">{{ name }}</span>
                                    <small>上次登录:<span :text="lastTime">{{ lastTime }}</span></small>
                                </p>
                            </li>
                            <!-- Menu Footer-->
                            <li class="user-footer">
                                <div class="pull-right">
                                    <a @click="logout" class="btn btn-default btn-flat" id="signOut">退出</a>
                                </div>
                            </li>
                        </ul>
                    </li>
                </ul>
            </div>
        </nav>
    </header>
    <!-- Left side column. contains the logo and sidebar -->
    <aside class="main-sidebar">
        <!-- sidebar: style can be found in sidebar.less -->
        <section class="sidebar">
            <!-- Sidebar user panel -->
            <div class="user-panel">
                <div class="pull-left image">
                    <img :src="avatar" class="img-circle" :alt="name">
                </div>
                <div class="pull-left info">
                    <p><span :text="name">{{ name }}</span></p>
                    <a><i class="fa fa-circle text-success"></i> Online</a>
                </div>
            </div>
            <ul class="sidebar-menu" id="mainMenu">
                <li class="header">MAIN NAVIGATION</li>
            </ul>
        </section>
        <!-- /.sidebar -->
    </aside>
</template>
  
<script>

import store from '@/store';

export default {
    name: 'HeaderView',
    data() {
        return {
            name: "",
            avatar: "",
            lastTime: ""
        };
    },
    created() {
        this.name = store.getters.name;
        this.avatar = store.getters.avatar;
        this.lastTime = store.getters.lastTime;
    },
    methods: {
        logout() {
            store.dispatch('logout');
        }
    }
}
</script>
  
<style scoped></style>

maintainer

这部分是重点,将菜单加载改成使用component标签切换,点击菜单时修改局部变量componentName,实现vue组件切换。

<template>
    <!-- Left side column. contains the logo and sidebar -->
    <aside class="main-sidebar" style="height: 100%; overflow: hidden; overflow: scroll;">
        <!-- sidebar: style can be found in sidebar.less -->
        <section class="sidebar">
            <!-- Sidebar user panel (optional) -->
            <div class="user-panel">
                <div class="pull-left image">
                    <img :src="avatar" class="img-circle" :alt="name">
                </div>
                <div class="pull-left info">
                    <p>{{ name }}</p>
                    <!-- Status -->
                    <a href="#"><i class="fa fa-circle text-success"></i> Online</a>
                </div>
            </div>
            <!-- search form (Optional) -->
            <form method="get" class="sidebar-form" onsubmit="return false;">
                <div class="input-group">
                    <input type="text" name="q" class="form-control" placeholder="Search..." @input="search">
                    <span class="input-group-btn">
                        <button name="search" id="search-btn" class="btn btn-flat" @click="search">
                            <i class="fa fa-search"></i>
                        </button>
                    </span>
                </div>
            </form>
            <!-- /.search form -->
            <!-- Sidebar Menu -->
            <ul class="sidebar-menu">
                <li class="header">主导航</li>
                <!-- Optionally, you can add icons to the links -->
                <li v-for="(parent, parentIndex) in parentMenus" :key="parent" :data-index="parentIndex" class="treeview">
                    <a>
                        <i :class="[parent.icon ? parent.icon : 'fa fa-link']"></i>
                        <span>{{ parent.name }}</span>
                        <span class="label pull-right bg-yellow" :id="['pid_' + parent.menuId]">
                            {{ getChildLength(parent.menuId) }}
                        </span>
                    </a>
                    <ul class="treeview-menu" :id="['cid_' + parent.menuId]">
                        <li v-for="(child, childIndex) in getChilds(parent.menuId)" :key="child" :data-index="childIndex">
                            <a :data-url="child.path" @click="handleClick(child)">
                                <i
                                    :class="[child.icon && child.icon != '' ? child.icon : 'fa fa-circle-o text-yellow']"></i>
                                <span>{{ child.name }}</span>
                            </a>
                        </li>
                    </ul>
                </li>
            </ul>
            <!-- /.sidebar-menu -->
        </section>
        <!-- /.sidebar -->
    </aside>
    <!-- Content Wrapper. Contains page content -->
    <div class="content-wrapper" style="height: 100%;">
        <!-- Content Header (Page header) -->
        <section class="content-header"
            :style="[childMenu.name && childMenu.name != '' ? 'display:block;' : 'display:none;']">
            <h1>
                {{ childMenu.name }}
                <small>{{ childMenu.description }}</small>
            </h1>
            <ol class="breadcrumb">
                <li><a><i class="fa fa-dashboard"></i> {{ childMenu.parentName }}</a></li>
                <li class="active">{{ childMenu.name }}</li>
            </ol>
        </section>
        <!-- Main content -->
        <section class="content" style="height: 100%; overflow: hidden; overflow: scroll;">
            <!-- Your Page Content Here -->
            <component :is="componentName"></component>
        </section>
        <!-- /.content -->
    </div>
    <!-- /.content-wrapper -->
    <div class="modal fade" id="tipModal" tabindex="-1" role="dialog" aria-labelledby="modalLabel">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h4 class="modal-title">提示信息</h4>
                </div>
                <div class="modal-body" id="tipMsg"></div>
            </div>
        </div>
    </div>
    <div class="modal fade" id="confirmModal" tabindex="-1" role="dialog" aria-labelledby="modalLabel"
        data-backdrop="static">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h4 class="modal-title">确认信息</h4>
                </div>
                <div class="modal-body" id="confirmMsg"></div>
            </div>
        </div>
    </div>
    <div class="modal fade loading" style="background: #00000040;" tabindex="-1" role="dialog"
        aria-labelledby="loadingModalLabel" aria-hidden="true" data-backdrop="static" data-keyboard="false">
        <div class="loadingGif" style="position: fixed;"><img :src="loading"></div>
    </div>
</template>
  
<script>
import store from '@/store';
import { getStore } from '@/utils/store';
import loading from '@/assets/images/loading.gif';

export default {
    name: 'MaintainerView',
    data() {
        return {
            componentName: "",
            name: "",
            avatar: "",
            parentMenus: [],
            childMenus: [],
            childMenu: {
                name: "",
                parentName: "",
                description: "",
            }
        };
    },
    created() {
        this.getMenu();
        this.name = store.getters.name;
        this.avatar = store.getters.avatar;
    },
    methods: {
        getMenu() {
            this.parentMenus = getStore({ name: 'parent_menus' });
            this.childMenus = getStore({ name: 'child_menus' });
            store.dispatch('getMenu').then(res => {
                console.log(res);
                this.parentMenus = getStore({ name: 'parent_menus' });
                this.childMenus = getStore({ name: 'child_menus' });
            });
        },
        getChilds(parentId) {
            return this.childMenus.filter(v => v.parentId == parentId);
        },
        getChildLength(parentId) {
            return this.childMenus.filter(v => v.parentId == parentId).length;
        },
        handleClick(menu) {
            this.childMenu.name = menu.name;
            this.childMenu.parentName = this.parentMenus.filter(v => v.menuId == menu.parentId)[0].name;
            this.childMenu.description = menu.description;
            store.dispatch('getViewComponent', menu.path).then(componentName => {
                console.log(componentName);
                this.componentName = componentName;
            });
        },
        search() {
            let text = $("input[type='text']").val();
            this.childMenus = store.getters.childMenus.filter(v => v.name.indexOf(text) >= 0);
            if (!this.childMenus.length || this.childMenus.length == 0) {
                this.parentMenus = store.getters.parentMenus.filter(v => v.name.indexOf(text) >= 0);
            } else {
                let parentIds = this.childMenus.map(v => v.parentid);
                let parentMenus = store.getters.parentMenus.filter(v => v.name.indexOf(text) >= 0);
                if (parentMenus && parentMenus.length > 0) {
                    parentMenus.forEach(v => {
                        parentIds.push(v.id);
                    });
                }
                this.parentMenus = store.getters.parentMenus.filter(v1 => parentIds.filter(v2 => v1.id == v2).length > 0);
            }
        }
    },
}

/**
 * 扩展date函数
 * author:c3gen
 */
Date.prototype.format = function (format) {
    var o = {
        "M+": this.getMonth() + 1,
        "d+": this.getDate(),
        "h+": this.getHours(),
        "m+": this.getMinutes(),
        "s+": this.getSeconds(),
        "q+": Math.floor((this.getMonth() + 3) / 3),
        "S": this.getMilliseconds()
    }
    if (/(y+)/.test(format)) {
        format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    }
    for (var k in o) {
        if (new RegExp("(" + k + ")").test(format)) {
            format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
        }
    }
    return format;
}
</script>
  
<style scoped>
@import '@/css/main.css';
</style>

难点三:第三方库改造

旧项目中使用的第三方插件库,大部分都因年代久远,失去维护;在baidu与google上都找不到相同的源代码,巨头疼。
尝试以下几个方案:

一、npm安装接近版本或类似的库,因旧代码报错,无法兼容(失败)
二、直接复制源代码过来,在main.js导入,代码无法加载,都报undefined(失败)

最后经过好几天摸索,发现以下两种方式可解决。

1.插件库改造

对插件库代码进行改造,套上nodejs的加载结构,导入便可自动执行,主要都是jquery的插件库。

(function (factory) {
	var define;
	if (typeof define === 'function' && define.amd) {
		define(['jquery'], factory);
	} else if (typeof exports === 'object') {
		factory(require('jquery'));
	} else {
		factory(jQuery);
	}
}(function ($, obj = undefined) {
	// 此次放原先插件库的代码
	...
}));

main.js导入

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import 'jquery'
import 'jquery.md5'

import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.min'

import 'font-awesome/css/font-awesome.min.css'
import 'ionicons/css/ionicons.min.css'

import 'admin-lte/dist/css/AdminLTE.min.css'
import 'admin-lte/dist/css/skins/skin-red-light.min.css'
import 'admin-lte/dist/js/app.min'

import '@/assets/plugins/bootstrap-table/css/bootstrap-table.css'
import '@/assets/plugins/bootstrap-table/js/bootstrap-table'
import '@/assets/plugins/bootstrap-table/js/locale/bootstrap-table-zh-CN'
import '@/assets/plugins/bootstrap-table/js/extensions/editable/bootstrap-table-editable'

import '@/assets/plugins/jquery/jquery.form'

import '@/assets/plugins/bootstrap-datepicker/css/datepicker3.css'
import '@/assets/plugins/bootstrap-datepicker/js/bootstrap-datepicker'

import '@/assets/plugins/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css'
import '@/assets/plugins/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min'
import '@/assets/plugins/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN'

import '@/assets/plugins/bootstrap-combobox/css/bootstrap-combobox.css'
import '@/assets/plugins/bootstrap-combobox/js/bootstrap-combobox'

import '@/assets/plugins/bootstrap-select/css/bootstrap-select.css'
import '@/assets/plugins/bootstrap-select/js/bootstrap-select'

import '@/assets/plugins/bootstrap-multiselect/css/bootstrap-multiselect.css'
import '@/assets/plugins/bootstrap-multiselect/js/bootstrap-multiselect'

import '@/assets/plugins/jQuery-Validation-Engine/css/validationEngine.jquery.css'
import '@/assets/plugins/jQuery-Validation-Engine/js/jquery.validationEngine'
import '@/assets/plugins/jQuery-Validation-Engine/js/languages/jquery.validationEngine-zh_CN'

import '@/assets/plugins/bootstrap-treeview/css/bootstrap-treeview.min.css'
import '@/assets/plugins/bootstrap-treeview/js/bootstrap-treeview.min'

import components from '@/utils/components'

createApp(App).use(store).use(router).use(components).mount('#app')

.eslintrc.js

还需关闭eslint一些校验,旧代码不规范,直接启动不了,害

module.exports = {
  root: true,
  env: {
    node: true,
    jquery: true
  },
  extends: [
    'plugin:vue/vue3-essential',
    'eslint:recommended'
  ],
  parserOptions: {
    parser: '@babel/eslint-parser'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-unused-vars': 'off',
    'no-useless-escape': 'off',
    'no-control-regex': 'off',
  }
}

2.public/index.html导入

在index.html内通过

难点三:登录后的样式污染

目前就这个问题,笔者这三脚猫的前端技术,实在无法攻克;只能通过一种取巧的办法,先跳转到个空白页面,再判断是否链接跳转来源方式,从而重定向回首页。
登录后样式半屏

views/login/index.vue

登录函数代码

export default {
    name: "LoginView",
    created() {
        this.init();
        this.bubble();
    },
    methods: {
        bubble() {
            var $ul = $('<ul id="bubble-wrapper"/>');
            for (var i = 0; i < 10; i++) {
                $ul.append($('<li/>'));
            }
            $("body").append($ul);
        },
        init() {
            this.$nextTick(function () {
                let $this = this;
                $("#loginBtn").click(function () {
                    $this.login();
                });

            });
        },
        login() {
            let username = $("#username").val();
            let password = $("#password").val();
            if (username.trim() == '') {
                $(".loginTips").html("账号不能为空!");
                return;
            } else if (password.trim() == '') {
                $(".loginTips").html("密码不能为空!");
                return;
            }
            login({
                'username': username,
                'password': $.md5(password),
            }).then(res => {
                if (res.code == global.HTTP_STATUS.OK) {
                    console.log("login success.");
                    this.storeAccessToken(res.data.accessToken);
                    this.getSysConfs();
                } else {
                    $(".loginTips").html(res.message).css("padding", "3px 5px");
                }
            });
        },
        storeAccessToken(accessToken) {
            setStore({
                name: "access_token",
                content: accessToken,
                type: "session"
            });
        },
        getSysConfs() {
            store.dispatch('getSysConfs').then(res => {
                console.log(res);
                this.updateUser();
            });
        },
        updateUser() {
            store.dispatch('getUser').then(res => {
                console.log(res);
                window.location.href = '#/blank';
            });
        },
    },
};

BlankView.vue

<template>
    <div></div>
</template>
  
<script>
import router from '@/router';

export default {
    name: 'BlankView',
    created() {
        //处理css样式污染问题
        if (window.performance.navigation.type == window.performance.navigation.TYPE_RELOAD) {
            router.push('/home');
        } else {
            router.go(0);
        }
    }
}
</script>
  
<style scoped></style>

难点四:jquery与vue的冲突

在业务开发中,vue页面如果同时使用vue和jquery,会因vue的渲染导致jquery一些事件失效,所以jquery的代码需放置到vue的this.$nextTick渲染之后执行。

<template>
    <section class="content">
        <div class="box box-danger">
            <div class="box-body">
                <!-- Content Header (Page header) -->
                <section class="content-header">
                    <h1 id="itemTitle"></h1>
                </section>
                <!-- .content -->
                <section class="content">
                    <div id="table"></div>
                </section><!-- .content -->
            </div>
        </div>
    </section>
</template>
  
<script>
import TableHelper from '@/utils/bootstrap-table-helper';

export default {
    name: 'template',
    data() {
        return {
            columns: [],
        };
    },
    created() {
        this.init();
    },
    methods: {
        init() {
            this.$nextTick(function () {
                // jquery执行代码
                let $this = this;
                $this.initTable();
            });
        },
        initTable() {
            let $this = this;
            TableHelper.destroy('#table');
            $('#table').bootstrapTable({
                columns: $this.columns,
                cache: false,
                striped: true,
                showRefresh: true,
                search: true,
                pageSize: 10,
                pagination: true,
                pageList: [1, 10, 20, 30, 50],
                sidePagination: "client",
                queryParamsType: "undefined",
                toolbar: '#toolbar',
            });
        },
    },
}
</script>
  
<style scoped></style>

难点五:代码迁移

以下附上JAVA批量处理脚本,将原项目中的所有页面批量转化为vue页面。

import java.io.*;

public class VueScriptTest {

    public static void main(String[] args) {
        String sourcePath = "/html";
        String targetPath = "/views";
        File sourceDirectory = new File(sourcePath);
        File targetDirectory = new File(targetPath);
        if (!targetDirectory.exists()) {
            boolean isMkdir = targetDirectory.mkdirs();
        }
        processFiles(sourceDirectory, targetDirectory);
    }

    private static void processFiles(File sourceDirectory, File targetDirectory) {
        File[] files = sourceDirectory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    File newTargetDirectory = new File(targetDirectory, file.getName());
                    boolean isMkdir = newTargetDirectory.mkdirs();
                    processFiles(file, newTargetDirectory);
                } else if (file.getName().endsWith(".html")) {
                    try {
                        String newFileNameBase = toCamelCase(file.getName().replace(".html", "")).trim();
                        String componentName = upperFirst(newFileNameBase) + "View";
                        BufferedReader reader = new BufferedReader(new FileReader(file));
                        StringBuilder content = new StringBuilder();
                        StringBuilder styleContent = new StringBuilder();
                        StringBuilder scriptContent = new StringBuilder();
                        boolean inStyleTag = false;
                        boolean inScriptTag = false;
                        String line;
                        while ((line = reader.readLine()) != null) {
                            if (isNonEmptyNonCommentLine(line)) {
                                if (line.trim().startsWith("<style>")) {
                                    inStyleTag = true;
                                    line = line.replace("<style>", "<style scoped>");
                                }
                                if (line.trim().startsWith("<script>")) {
                                    inScriptTag = true;
                                }
                                if (inStyleTag) {
                                    styleContent.append(line).append("\n");
                                } else if (inScriptTag) {
                                    if (line.trim().startsWith("<script>")) {
                                        scriptContent.append(line).append("\n");
                                        scriptContent.append("export default {\n" + "  name: \"").append(componentName).append("\",\n").append("  setup() {},\n").append("  created() {\n").append("    this.$nextTick(function () {\n").append("      this.initData();\n").append("    });\n").append("  },\n").append("  methods: {\n").append("    initData() {\n");
                                    } else {
                                        if (line.contains(".bootstrapTable") && line.contains("destroy")) {
                                            scriptContent.append(line).append("\n");
                                            scriptContent.append("// 清空分页组件的容器\n");
                                            scriptContent.append("$('.fixed-table-pagination').empty();\n");
                                        } else {
                                            if (line.trim().contains("</script>")) {
                                                scriptContent.append("    }\n" + "  }\n" + "};\n");
                                            }
                                            scriptContent.append(line).append("\n");
                                        }
                                    }

                                } else {
                                    // 添加import语句
                                    if (line.contains("<script src=\"/static/js/admin/style.js\"></script>")) {
                                        // 在scriptContent中找到<script>的位置,下一行插入import语句
                                        int scriptIndex = scriptContent.indexOf("<script>");
                                        if (scriptIndex != -1) {
                                            scriptContent.insert(scriptIndex + "<script>".length(), "\nimport '@/utils/style.js';");
                                        }
                                    } else {
                                        content.append(line).append("\n");
                                    }
                                }
                            } else {
                                content.append(line).append("\n");
                            }
                            if (inStyleTag && line.trim().endsWith("</style>")) {
                                inStyleTag = false;
                            }
                            if (inScriptTag && line.trim().endsWith("</script>")) {
                                inScriptTag = false;
                            }
                        }
                        reader.close();
                        content.insert(0, "<template>\n");
                        content.append("</template>\n");
                        content = new StringBuilder(content.toString().replace("type=\"date\"", ""));
                        // 添加import语句
                        if (scriptContent.toString().contains("TableHelper")) {
                            // 在scriptContent中找到<script>的位置,下一行插入import语句
                            int scriptIndex = scriptContent.indexOf("<script>");
                            if (scriptIndex != -1) {
                                scriptContent.insert(scriptIndex + "<script>".length(), "\nimport TableHelper from '@/utils/bootstrap-table-helper';");
                            }
                        }
                        content.append(scriptContent).append("\n");
                        content.append(styleContent);
                        String newFileName = upperFirst(newFileNameBase) + "View.vue";
                        File newFile = new File(targetDirectory, newFileName);
                        FileWriter writer = new FileWriter(newFile);
                        writer.write(content.toString());
                        writer.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private static boolean isNonEmptyNonCommentLine(String line) {
        return !line.trim().isEmpty() && !line.trim().startsWith("<!--");
    }

    private static String toCamelCase(String fileName) {
        StringBuilder result = new StringBuilder();
        boolean nextUpperCase = false;
        for (char c : fileName.toCharArray()) {
            if (c == '-' || c == '_') {
                nextUpperCase = true;
            } else {
                if (nextUpperCase) {
                    result.append(Character.toUpperCase(c));
                    nextUpperCase = false;
                } else {
                    result.append(Character.toLowerCase(c));
                }
            }
        }
        return result.toString();
    }

    private static String upperFirst(String name) {
        char[] ch = name.toCharArray();
        ch[0] -= 32;
        return new String(ch);
    }
}

总结

整个项目改造真的细节很多,写可能很大部分没写全,文章前面已经放上git地址,大伙可下载代码,自行参透。

  • 33
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值