朱治龙个人博客 - Vue
type: channel
link: https://blog.zhuzhilong.cn/category/vue.html
description: Vue开发学习
items: 将vue-cli搭建的Vue前端工程更新构建工具为pnpm+vite经验分享
items: 随动帮助文档实现
items: 记一次使用v-charts绘制图表出现重影的处理经历分享
items: vue-cli工程打包生成gzip相关压缩文件
items: 使用「Relation Graph」优化图表展现效果
items: 在vue-cli3使用sass(scss)定义的全局样式及变量
items: 一个简单的自定义工作流设计器实现
items: 分享一个在管理系统中一些公共组件的调用方式
items: 浅谈vue中axios的封装

将vue-cli搭建的Vue前端工程更新构建工具为pnpm+vite经验分享
type: item
date: 2023-06-24T02:42:00+00:00
description: 缘起我们团队负责的超算云服务控制台前端工程(console-ui)从提交第一行代码（2021-03-18）至今已 2 年多时间，两年多的时间内随着需求的增加，前端工程也越来越庞大，陈旧的基础设施暴露出了如下问题：1、Node.js 版本仍然使用 2021 年发布的 14.x，而最新的 Node 大版本已更新到20.x，仍然使用旧版本导致一些构建工具版本不能随意更新，也同时留下一定的安全隐患。2、项目目前仍使用两年前发行的 Webpack4.46.0 版本作为项目构建工具，随着项目逐渐庞大，Webpack 在开发时编译项目时间通常达到 2 分钟以上，严重影响开发效率。3、前端生态日新月异，守着老旧的生态，不便于团队成长。借本次升级 console-ui 核心 vue版本的契机，考虑同时升级到新的构建工具。Vite 从 2021 年发布以来，在构建速度及开发体验上一直口碑不错，经过两年多的迭代，也趋于稳定，完全可以应用于产线了。且根据官方的介绍 Vite 4.3 开始的版本比之前的版本在开发及构建速度上又有了成倍的提升：发行日志见链接：https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md开始折腾1、升级 Node.js目前 console-ui 构建还是用的Node.js 14.x, 而现在 Node.js 的 LTS 版本是18.x，按照 Node.js 的版本发行规律，预计目前最新的 20.x 版本在今年的 10 月份将成为下一个 LTS 版本，且 Node.js 的向下兼容性较强，所以目前咱们可以拥抱 Node.js 20.x 相关生态。所以本次升级就直接选用 Node.js 20.x 了。开发机是使用nvm管理 Node.js 多版本的。使用如下命令安装并使用Node.js 20:nvm install 20.3.0 nvm use 20.3.02、升级构建工具在升级过程如下:创建初始工程使用官方开始页面提供的命令初始化一个初始工程：pnpm create vite console-ui --template vue3、console-ui 已有功能最简验证3.1、支持多主题console-ui的设定是可以打包不同主题的页面，构建时根据 env 变量打包不同主题的版本，然后采用不同主题文件。经验证vite也支持，vite.config.js 核心配置代码如下:css: { preprocessorOptions: { scss: { additionalData: `@import "@/styles/themes/${env.VITE_APP_DIST_MODE}.scss";` } } }由于不同的主题有些不同的资源文件(如favicon.ico、主题相关的图片等)需要引用，在这里我们在public 目录下新建2个目录用于管理不同主题资源文件的管理，然后配置不同的publicDir:publicDir: `./public/${distMode}`,3.2、验证多页面支持console-ui提供针对普通用户的 UI 界面，同时也提供管理员使用的维护界面，这两套界面在原工程是使用多页面机制实现的。经验证，vite 也能很好的支持，核心配置代码如下：plugins: [ createHtmlPlugin({ minify: true, pages: [ { entry: '/src/main.js', filename: 'index.html', template: 'index.html', injectOptions: { data: { title: '超算云服务控制台' } } }, { entry: 'src/pages/admin/index.js', filename: 'admin.html', template: 'admin.html', injectOptions: { data: { title: '超算运服务控制台-系统维护' } } } ] }) ]4、开始整合整合时会涉及一些资源文件引用的改造，一般改动会比较多，为不影响主分支开发，建议单独开个分支处理。整合过程比较繁琐，就不一一罗列了，主要涉及如下事项：替换 ENV 变量：vue-cli 工程的环境变量都是以Vue_APP 开头的变量，可以全局替换为VITE_APP开头，而且 Webpack 工程一般使用 process.env.XXX 访问ENV变量，而 Vite 是使用 import.meta.env.XXX 访问 ENV 变量，为了少改动，可以使用如下方式继续使用process.env.XXX 方式访问：define: { 'process.env': …

随动帮助文档实现
type: item
date: 2023-06-10T07:26:00+00:00
description: 背景介绍我们项目提供了在线的项目文档，便于用户在使用过程中有什么问题可以通过直接查文档解决，也在一定程度上减少了公司运营及客服成本。在实际使用过程中，用户反馈造作文档的使用不是很方便，基本上要打开新Tab页签，不能方便的一遍对照文档，一边操作。解决方案为解决以上问题，在参考阿里云及腾讯云相关产品的文档模式后，引入了随动帮助文档机制。该机制主要实现模式如下：1、在相关功能指定位置添加文档入口2、单击文档名称后，在页面左下角显示文档弹出层(以下简称「文档查看器」)，主要是系统的大部分功能使用右侧展开的抽屉组件 (Drawer)进行显示。3、打开的文档查看器，可垂直方向最大，可上下左右四边及四个角落通过拖拽调整大小4、可拖拽到页面任意位置5、文档查看器不影响页面中其他组件的交互该功能是系统每个功能模块都有可能会用到的，所以将起提取为一个公共组件。组件只需传入功能标识，然后调用内容管理系统的接口获取数据，如果有数据则显示文档入口，由于一个功能极有可能会对应有多篇帮助文档，用户从文档入口单击将展开文档列表，用户单击需要查看的文档，则在左下角显示文档。实现过程示例中均使用模拟的数据，实际数据在生产环境调用内容管理系统的接口获取。实现文档查看器组件HelpModal.vue文档查看器的拖拽及大小调整均通过指令控制 <el-dialog ref="dlg" v-dragAndResize :modal="false" top="auto" :visible.sync="isShow" custom-class="help-modal-wrap" width="400px" :close-on-click-modal="false" append-to-body > 功能指南 如何使用一键转区 更新时间：2022-08-09 10:10 可使用【一键转区】功能，将超算账号的数据转移至指定的超算上进行使用，如超算中心A3分区的sc0001账号（即为源目标）转移至超算分区A6的sc0001（即为目的目标），具体操作如下: 1. 单击【一键转区】中的【申请一键转区】 2. 在【源目标】处选择可用的超算分区及超算账号，例如A3超算分区的sc0001； 在【目的目标】处选择匹配的超算分区，不用选择转区后的超算账号，例如A6超算分区； 在【源目标账号环境信息】处填软件使用时的环境要求。 3. 单击【确定】，会有提示框告知源目标超算账号保存3个月，3个月后超算账号回收的提醒。 4. 在【一键转区】的页面查看工单进展。 <el-button type="primary" class="btn-open-in-window" @click="openDocLink"> 新窗口查看文档 </el-button> </el-dialog> modal-resize.scss.dialog-can-resize .resize-handle { position: absolute; z-index: 100; display: block } .dialog-can-resize .resize-top { cursor: n-resize; top: -3px; left: 0px; height: 7px; width: 100% } .dialog-can-resize .resize-bottom { cursor: s-resize; bottom: -3px; left: 0px; height: 7px; width: 100% } .dialog-can-resize .resize-right { cursor: e-resize; right: -3px; top: 0px; width: 7px; height: 100% } .dialog-can-resize .resize-left { cursor: w-resize; left: -3px; top: 0px; width: 7px; height: 100% } .dialog-can-resize .resize-bottom-right { cursor: se-resize; width: 20px; height: 20px; right: -8px; bottom: -8px; background: url('./resize_corner.png') no-repeat; opacity: .4; filter: alpha(opacity=40) } .dialog-can-resize .resize-bottom-left { cursor: sw-resize; width: 16px; height: 16px; left: -8px; bottom: -8…

记一次使用v-charts绘制图表出现重影的处理经历分享
type: item
date: 2023-05-24T04:47:00+00:00
description: 背景介绍目前维护的项目属于多年积累的老项目了，使用的 vue2 + vue-router + vuex + element-ui + webpack 的技术架构。涉及图表相关的库主要使用 echarts@4.9.0 和 饿了么前端团队基于 echarts 4.x 封装的 v-charts 1.19.0。近期接运营要求，需要增加一些图表效果，基本界面效果实现如下：问题但在实现过程中，在区域缩放组件拖动调整数据显示范围时，发现x轴会出现重影的现象，如下图所示：排查过程初期怀疑跟图表配置项的 xAxis 相关配置信息有关，随将 position: 'bottom' 配置项注释掉，发现重影现象没有了，但是上下出现了重复的x 轴label如下图所示：将echarts 图表配置信息拷贝到 echarts 实例页面（https://echarts.apache.org/v4/examples/zh/editor.html?c=bar-large）进行验证，发现在该页面不存在重影现象：配置信息如下：var option = { "color": ["#004CA1", "#32D4E3", "#03A2DC", "#B581E3", "#0F7DFA", "#c4b4e4"], "tooltip": { "trigger": "axis", "axisPointer": { "type": "shadow" } }, "legend": { "show": true, "top": -5, "data": ["CPU", "GPU"] }, "grid": { "right": "5%", "top": "10%", "bottom": "20%", "containLabel": true }, "dataZoom": [ { "type": "inside" }, { "type": "slider", "left": 77, "right": 77 } ], "xAxis": { "data": [ "2022-11-26", "2022-11-27", "2022-11-28", "2022-11-29", "2022-11-30", "2022-12-01", "2022-12-02", "2022-12-03", "2022-12-04", "2022-12-05", "2022-12-06", "2022-12-07", "2022-12-08", "2022-12-09", "2022-12-10", "2022-12-11", "2022-12-12", "2022-12-13", "2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17", "2022-12-18", "2022-12-19", "2022-12-20", "2022-12-21", "2022-12-22", "2022-12-23", "2022-12-24", "2022-12-25", "2022-12-26", "2022-12-27", "2022-12-28", "2022-12-29", "2022-12-30", "2022-12-31", "2023-01-01", "2023-01-02", "2023-01-03", "2023-01-04", "2023-01-05", "2023-01-06", "2023-01-07", "2023-01-08", "2023-01-09", "2023-01-10", "2023-01-11", "2023-01-12", "2023-01-13", "2023-01-14", "2023-01-15", "2023-01-16", "2023-01-17", "2023-01-18", "2023-01-19", "2023-01-20", "2023-01-21", "2023-01-22", "2023-01-23", "2023-01-24", "2023-01-25", "2023-01-26", "2023-01-27", "2023-01-28", "2023-01-29", "2023-01-30", "2023-01-31", "2023-02-01", "2023-02-02", "2023-02-03", "2023-02-04", "2023-02-05", "2023-02-06", "2023-02-07", "2023-02-08", "2023-02-09", "2023-02-10", "2023-02-11", "2023-02-12", "2023-02-13", "2023-02-14", "2023-…

vue-cli工程打包生成gzip相关压缩文件
type: item
date: 2022-06-06T06:38:00+00:00
description: 一、添加依赖yarn add vue-cli-plugin-compression -D二、更新vue.config.js文件添加zlib引用const zlib = require('zlib')module.exports 中添加如下配置信息：pluginOptions: { compression:{ brotli: { filename: '[file].br[query]', algorithm: 'brotliCompress', include: /\.(js|css|html|svg|json)(\?.*)?$/i, compressionOptions: { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11, }, }, minRatio: 0.8, }, gzip: { filename: '[file].gz[query]', algorithm: 'gzip', include: /\.(js|css|html|svg|json)(\?.*)?$/i, minRatio: 0.8, } } }效果打包后生成相应的.br和.gz结尾的文件

使用「Relation Graph」优化图表展现效果
type: item
date: 2022-04-11T00:17:00+00:00
description: 背景说明近期检查前端工程的打包情况，发现@antv/g6占用了很大的空间：经过前端团队内部了解，这个主要是用于项目内超算中心的关系图展示，关系图这一块也随着业务的需求使用了不同的组件来实现：echarts > Mindelixir > @antv/G6实际使用@antv/G6实现完的效果,我的直观感觉也不是很理想，数据量大的情况下，显示太小，而且看了下相关的代码，感觉实现过程也不够优雅。所以秉承公司精益求精的企业文化，为了减少打包体积及优化使用体验等目的，准备使用「Relation Graph」对这一块进行大刀阔斧的改造。经过三四天的打磨，整体效果得到了运营同事的认可，先看看实现后的效果：本次调整后，打包体积上有较大的缩减：优化前：(原始大小：13.69M；压缩后：4.71M；开启gzip:1.42M)优化后：(原始大小：8.97M；压缩后：3.14M；开启gzip:957.42K)代码实现下面将项目中关系图相关的一些核心代码进行讲解视图代码<relation-graph ref="relationGraph" :options="graphOptions"> <el-popover v-if="node.data.status === 'user'" trigger="hover" placement="right" popper-class="account-graph-popver" > {{ $t('userCenter.name') }}: {{ node.data.realname }} {{ $t('common.email') }}: {{ node.data.email }} {{ $t('common.mobile') }}: {{ node.data.phone }} <el-divider /> <el-button slot="reference" type="text" class="padding-lr-xs" @click="unBind(node)"> <para-lang code="selfService.group.accountUnbinding" /> </el-button> </el-popover> <el-popover v-else-if="node.data.status !== 'accountMessage' && stopClick('accountMgmt')" trigger="hover" placement="right" popper-class="account-graph-popver" > {{ $t('common.SCAccount') }}: {{ node.text }} <el-divider /> <el-button v-if="belongingType==='belonging'" type="text" @click="modifyAccountRelation(node)"> 修改所属关系 </el-button> <el-button v-if="belongingType==='use'" type="text" @click="bindAccount(node)"> <para-lang code="selfService.group.accountBinding" /> </el-button> {{ $t('common.SCCode') }}: {{ node.text }} <el-divider /> <el-button type="text" @click="showAccountApply(node.text)"> <para-lang code="selfService.group.applicationAccount" /> </el-button> </el-popover> </relation-graph>图标配置项在Vue 组件的 data 里添加如下信息graphOptions: { 'debug': envName === 'development', // 仅开发或Istio环境下为调试模式 'allowShowMiniView': true, 'allowSwitchLineShape': true, 'defaultFocusRootNode': false, 'layouts': [ { 'label': '树状-横向', 'layoutName': 'tree', 'useLayoutStyleOptions': true, 'layoutClassName': 'member-layout-tree-v', 'defaultLineColor': '#DDDDDD', 'leve…

在vue-cli3使用sass(scss)定义的全局样式及变量
type: item
date: 2021-07-14T01:34:00+00:00
description: 为统一风格及便于后续维护，在项目中常用的主色调、圆角、阴影相关的样式定义到一个全局变量文件中，常规的使用需要每个Vue组件中进行import,该操作会比较繁琐，经过了解后发现可以基于webpack的loader参数引入全局变量的方式来实现，相关实现过程如下所示：vue.config.jsmodule.exports = { // ... css: { // 为便于在Vue组件中使用，全局引入variables.scss中定义的变量 loaderOptions: { sass: { prependData: `@import "@/styles/variables.scss";` } } } variables.css// base color $blue:#324157; $light-blue:#3A71A8; $red:#C03639; $pink: #E65D6E; $green: #30B08F; $tiffany: #4AB7BD; $yellow:#FEC171; $panGreen: #30B08F; $theme: #004ca1; // ...vue 组件

一个简单的自定义工作流设计器实现
type: item
date: 2021-06-30T07:44:00+00:00
description: 先瞅瞅产品提供的丑陋至极的原型效果再看看经过本人精雕细琢之后的惊艳效果Show me the code流程设计弹出层 <el-dialog width="760px" class="hkt-dlg-darkblue" :title="`流程设计：${formData.name}`" :visible.sync="isShow" destroy-on-close :close-on-click-modal="false"> <el-row class="hkt-block-title"> <el-col :span="12"> 流程配置 </el-col> <el-col :span="12" class="text-right"> <el-button type="text" @click="formModalChange('New', null)"> 添加审核节点</el-button> </el-col> </el-row> <el-steps direction="vertical" :active="nodes.length"> <el-step v-for="(step, stepIndex) in nodes" class="step-item-wrap" :class="'step-item-signmodel-' + step.signModel" :key="step + stepIndex"> <el-row> <el-col :span="12"> </el-col> <el-col :span="12"> <el-button size="mini" type="text" @click="stepSort(stepIndex, 'up')" v-if="stepIndex > 0"> 上移</el-button> <el-button size="mini" type="text"@click="stepSort(stepIndex, 'down')" v-if="stepIndex < nodes.length - 1"> 下移</el-button> <el-button size="mini" type="text" @click="formModalChange('Edit', step, stepIndex)"> 修改</el-button> <el-button size="mini" type="text" @click="removeStep(step, stepIndex)"> 删除</el-button> <el-button size="mini" type="text" @click="addPersons(step, stepIndex)"> 配置审批人</el-button> </el-col> </el-row> 暂未配置审批人员 <el-tag v-for="(person, personIndex) in step.approveNames" :key="person" disable-transitions @close="removePerson(stepIndex, personIndex)" closable>{{person}}</el-tag> </el-step> </el-steps> 当前流程暂未配置审核节点信息， 请单击右上角的“添加审核节点”按钮进行配置 <el-button @click="isShow = false">取 消</el-button> <el-button type="primary" :loading="submitting" @click="save">{{submitting ? '保存中': '确 定'}}</el-button> <step-form-modal :show.sync="formModalVisible" v-if="formModalVisible" :formData="currentRow" :mode="formMode" @callback="formCallback" /> </el-dialog> 步骤编辑弹出层 <el-dialog width="460px" class="hkt-dlg-darkblue" :title="(form.id ? '编辑' : '新建') + '步骤'" :visible.sync="isShow" append-to-body destroy-on-close :close-on-click-modal="false"> <el-form ref="form" :model="form"> <el-form-item label="步骤名称" pro…

分享一个在管理系统中一些公共组件的调用方式
type: item
date: 2021-06-30T07:18:00+00:00
description: 公共组件效果查看合同：地图描点：公共资源文件上传：人员选择-多选：人员选择-单选：增加公共组件目录核心代码如下： <resource-modal :title="resourcePickerDlg.dialogTitle" :resourceDialogProps.sync="resourcePickerDlg.resourceDialogProps" v-if="resourcePickerDlg.show" :show.sync="resourcePickerDlg.show" @callback="resourceCallback"></resource-modal> <select-user-modal :title="userSelectDlg.dialogTitle" :dialogProps.sync="userSelectDlg.dialogProps" v-if="userSelectDlg.show" :show.sync="userSelectDlg.show" @callback="userSelectedCallback" ></select-user-modal> <contract-detail-modal :show.sync="contractDetailDlg.show" v-if="contractDetailDlg.show" :detail-id="contractDetailDlg.dialogProps.contractId"></contract-detail-modal> <picker-q-map-position-modal :show.sync="qMaplDlg.show" v-if="qMaplDlg.show" @callback="qMapCallback"></picker-q-map-position-modal> main.js主方法中引入公共组件：import HktCommon from './views/hktcommon/HktCommon.vue' // 增加公共组件对象 const commonEl = document.createElement('div') document.body.appendChild(commonEl) new Vue({ name: 'AppCommonRoot', el: commonEl, render: h => h(HktCommon) })在其他组件中调用全局方法：// 上传资源 hktCommon.uploadResource(this.dialogTitle, dialogProps, (resources) => { this.uploadCallback(resources) }) // 选择用户： window.hktCommon.selectUser(`配置 ${row.nodeName} 节点的审批人`, dialogProps, (res) => { row.approveArray = res.map(item => item.id) row.approveNames = res.map(item => item.name) this.nodes.splice(stepIndex, 1, this.$hktUtils.deepClone(row)) }) // 显示合同信息 hktCommon.showContract = function (contractId) { self.contractDetailDlg.dialogProps.contractId = contractId self.contractDetailDlg.show = true } // 地图描点 window.hktCommon.pickQMap((res) => { self.projectForm.areaCode = `${res.latitude},${res.longitude}` })

浅谈vue中axios的封装
type: item
date: 2020-03-25T12:26:00+00:00
description: 在Vue项目中我们一般都会使用 axios 作为 Ajax 请求库。它是基于promise的http库，他有很多优秀的特性，例如拦截请求和响应、取消请求、转换json、客户端防御cSRF等。所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护，直接推荐我们使用axios库。在实际开发过程我们一般都会将常用的方法做一些封装以便于在项目中调用。工程添加axios依赖yarn add axios -S建立axios封装文件htttp.jsimport axios from 'axios'; import { Message } from 'element-ui'; axios.defaults.timeout = 5000; axios.defaults.baseURL =''; //http request 拦截器 axios.interceptors.request.use( config => { // const token = getCookie('名称');注意使用的时候需要引入cookie方法，推荐js-cookie config.data = JSON.stringify(config.data); config.headers = { 'Content-Type':'application/x-www-form-urlencoded' } // if(token){ // config.params = {'token':token} // } return config; }, error => { return Promise.reject(err); } ); //http response 拦截器 axios.interceptors.response.use( response => { if(response.data.errCode ==2){ router.push({ path:"/login", query:{redirect:router.currentRoute.fullPath}//从哪个页面跳转 }) } return response; }, error => { return Promise.reject(error) } ) /** * 封装get方法 * @param url * @param data * @returns {Promise} */ export function fetch(url,params={}){ return new Promise((resolve,reject) => { axios.get(url,{ params:params }) .then(response => { resolve(response.data); }) .catch(err => { reject(err) }) }) } /** * 封装post请求 * @param url * @param data * @returns {Promise} */ export function post(url,data = {}){ return new Promise((resolve,reject) => { axios.post(url,data) .then(response => { resolve(response.data); },err => { reject(err) }) }) } /** * 封装patch请求 * @param url * @param data * @returns {Promise} */ export function patch(url,data = {}){ return new Promise((resolve,reject) => { axios.patch(url,data) .then(response => { resolve(response.data); },err => { reject(err) }) }) } /** * 封装put请求 * @param url * @param data * @returns {Promise} */ export function put(url,data = {}){ return new Promise((resolve,reject) => { axios.put(url,data) .then(response => { resolve(response.data); },err => { reject(err) }) }) }在入口文件main.js中引入并注册为vue原型方法import axios from 'axios' import {…