一. 需求
最近项目针对客户需求,希望在尽量少修改源代码
的情况下开发一套展示系统,只能对数据进行查看,而不能对数据进行操作,比如增删改。
项目满足条件:
进行增删改操作的主要方式为按钮和链接。并且由于项目依赖组件库,这些元素拥有
相同的类名
。因此最直接的思路就是通过类名找到这些元素,从css样式入手,简单来说就是样式覆盖。
二. 解决方案
目前一共三种方案
:入口处增加内联样式、 挂载外部脚本、 自开发Webpack Loader
三种方案均有优缺点,综合考察之后采用第三种方案
方案一:入口处增加内联样式
React的项目入口文件:index.html
<!-- index.html部分代码片段 -->
<head>
<style type="text/css">
#systemEnter[system-mode=read] .exampleClass {
/* 类exampleClass样式,例如禁止点击事件 */
pointer-events: none;
cursor: not-allowed;
}
</style>
</head>
<body>
<div id="systemEnter" system-mode="read"></div>
</body>
复制代码
优点
简单直接,能够很快的支撑需求
缺点
需要覆盖的样式较多时对入口文件改动较大
方案二:挂载外部脚本
在外部脚本中操作DOM
实现样式覆盖,同时监听路径变化
,在每一次监听触发后执行脚本中的内容
第一步:入口处引入脚本
<!-- index.html部分码片段 -->
<body>
</body>
<script src = "./disabled.js">
复制代码
第二步:编写脚本
监听地址改变,即每次路由变化都重新获取DOM节点并对其进行操作
本项目使用history路由,监听较为复杂,方法如下:
juejin.cn/post/684490…
使用hash路由监听较为简单,
/** 脚本完整代码(history路由) */
class Dep { // 订阅池
constructor(name) {
this.id = new Date() //这里简单的运用时间戳做订阅池的ID
this.subs = [] //该事件下被订阅对象的集合
}
defined() { // 添加订阅者
Dep.watch.add(this);
}
notify() { //通知订阅者有变化
this.subs.forEach((e, i) => {
if (typeof e.update === 'function') {
try {
e.update.apply(e) //触发订阅者更新函数
} catch (err) {
console.warr(err)
}
}
})
}
}
Dep.watch = null;
class Watch {
constructor(name, fn) {
this.name = name; //订阅消息的名称
this.id = new Date(); //这里简单的运用时间戳做订阅者的ID
this.callBack = fn; //订阅消息发送改变时->订阅者执行的回调函数
}
add(dep) { //将订阅者放入dep订阅池
dep.subs.push(this);
}
update() { //将订阅者更新方法
var cb = this.callBack; //赋值为了不改变函数内调用的this
cb(this.name);
}
}
var addHistoryMethod = (function () {
var historyDep = new Dep();
return function (name) {
if (name === 'historychange') {
return function (name, fn) {
var event = new Watch(name, fn)
Dep.watch = event;
historyDep.defined();
Dep.watch = null; //置空供下一个订阅者使用
}
} else if (name === 'pushState' || name === 'replaceState') {
var method = history[name];
return function () {
method.apply(history, arguments);
historyDep.notify();
}
}
}
}())
window.addHistoryListener = addHistoryMethod('historychange');
history.pushState = addHistoryMethod('pushState');
history.replaceState = addHistoryMethod('replaceState');
/** 绑定事件,操作DOM */
window.addHistoryListener('history', function () {
setTimeout(() => { //使用定时器,避免页面未加载无法获取DOM
// 禁用链接(业务相关)
var disableLabel = document.querySelectorAll('.xxx');
disableLabel.forEach((item) => {
item.setAttribute("style", "pointer-events:none")
})
// 禁用除检索之外的按钮(业务相关)
var disableButton = document.querySelectorAll('.yyy');
disableButton[0].childNodes.forEach((item) => {
item.setAttribute("disabled", "")
})
}, 500) //时间根据页面加载速度而定
})
复制代码
优点
实现了外部挂载,对源码的改动较方案一更小
缺点
- 页面加载速度不确定,有时候脚本在定时器范围内找不到DOM,从而导致脚本失效
- 每次路由变化时执行脚本,若脚本变大会影响页面渲染速度
方案三:自定义Loader
第一步:配置自定义loader
在webpack中,loader
从下往上
执行,因此必须将自定义loader写在sass/less-loader之后
/** webpack.js部分代码片段 */
'sass-loader',
{
loader: path.resolve(__dirname, './loaders/merge-css.js'), //自定义loader路径
options: {
style: path.resolve(__dirname, './disabled.css'), //自定义css文件路径
},
}
复制代码
第二步:实现自定义loader功能
将第三步编写的css样式文件读入,合并到原始文件并传给下一个loader
/** loader-css.js代码 */
const { getOptions } = require('loader-utils');
const fs = require('fs');
module.exports = function (source) {
const options = getOptions(this);
const { style } = options;
// 读取样式文件,返回字符串
const string = fs.readFileSync(style);
// 合并到原始文件,返回给下一个loader
source += string;
return source;
};
复制代码
第三步:
在disabled.css中编写css样式即可完成样式覆盖
优点
- 不影响源代码,配置webpack即可
- 扩展性强,可适用于其他需求
缺点
编写loader学习成本较大,需理解webpack相关知识
三. 最终的使用方式
考虑到后续的可扩展性,使用第三种方式
四. 实际效果展示
业务相关,仅部分展示,实际效果为全局生效
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END