组件封装
MemorableSearch
SortableFilterGroup/SortablePanelGroup
- 可拖拽排序+折叠展开的组合筛选器
- 可拖拽排序+折叠展开的面板组
- 参考 如何实现拖拽排序
SmartPatchTable
- 基于Table做二次封装的带有全选和批量操作功能的表格
<!--
注入数据
:dataArr="billArr 账单模块丢入账单数组给SmartPatchTable做渲染
:actionBtnText="批量收款" 批量操作按钮文字
@onActionBtnClick="patchCollect" 用户点击批量操作时SPT子组件会发送自定义事件 携带参数为选中行的id数组
-->
<SmartPatchTable
:dataArr="billArr"
:actionBtnText="批量收款"
@onActionBtnClick="patchCollect"
></SmartPatchTable>
/* 处理SPT发送的自定义事件 入参id数组 */
patchCollect(ids=[]) {
//ids例如[1,2,5]代表用户在
ids.forEach(billId=>collect(billId))
}
自定义Hook
useMousePosition
- 对需要实时鼠标位置的页面提供响应式数据: x + y
- 数据响应式逻辑:组件挂载即监听window的mousemove事件,将e.pageX和e.pageY实时同步到响应式数据中;
- 组件卸载时移除mousemove事件监听;
- hook代码
import {ref,toRef,toRefs,reactive,computed,onMounted,onUnmounted,} from "vue";
function useMousePosition() {
const state = reactive({
x: 1,
y: 2,
});
const updateMousePosition = (e)=>{
state.x = e.pageX
state.y = e.pageY
}
onMounted(
()=>window.addEventListener("mousemove",updateMousePosition)
)
onUnmounted(
()=>window.removeEventListener("mousemove",updateMousePosition)
)
return toRefs(state);
}
import useScroll from '@/hooks/useMousePosition.js'
const { x, y } = useMousePosition();
useScroll
- 对需要纵向滚动的页面提供三个响应式数据:scrollTop + scrollBottom + bodyHeight;
- 对需要纵向滚动的页面提供【返回顶部】+【移动到任意位置】两个功能:toTop + yScrollTo;
- 响应式逻辑:组件挂载即建立window.onscroll事件监听,将e.scrollTop同步到scrollTop,同时手动计算出scrollBottom和bodyHeight两个属性的值并更新;
- 组件卸载时自动移除scroll事件监听;
- hook代码:
import { onMounted, onUnmounted, reactive, toRefs } from 'vue'
export default function useScroll() {
const state = reactive({
scrollTop: 0,
scrollBottom: 0,
bodyHeight: 0
})
const onScroll = () => {
state.scrollTop = document.documentElement.scrollTop
state.scrollBottom =
document.body.clientHeight - state.scrollTop - window.innerHeight
state.bodyHeight = document.body.clientHeight
}
let timer = null
const toTop = (millis = 1000) => {
yScrollTo(0, millis)
}
const yScrollTo = (y, millis = 1000) => {
if (!timer) {
const offset = document.documentElement.scrollTop - y
const frameOffset = Math.abs(offset / (millis / 40))
timer = setInterval(() => {
if (
offset > 0 &&
document.documentElement.scrollTop - y > frameOffset
) {
document.documentElement.scrollTop -= frameOffset
} else if (
offset < 0 &&
y - document.documentElement.scrollTop > frameOffset
) {
document.documentElement.scrollTop += frameOffset
} else {
document.documentElement.scrollTop = y
clearInterval(timer)
timer = null
}
}, 40)
}
}
onMounted(() => {
window.addEventListener('scroll', onScroll)
})
onUnmounted(() => window.removeEventListener('scroll', onScroll))
return { ...toRefs(state), toTop, yScrollTo }
}
import useScroll from '@/hooks/useScroll.js'
const {
scrollTop: stRef,
scrollBottom: sbRef,
toTop,
yScrollTo
} = useScroll()
useAxios
- 对需要网络通信的页面提供三个响应式数据:loading + data/err
- 响应式变化监听逻辑:加载即发起axios/ajax通信,loading为true
- 通信成功或失败时,data/err一实一空
- hook核心代码 参考:axios的取消
function useAxios(ajaxConf){
const {url,method,data,onSuccess,onFail} = ajaxConf
const state = reactive({
loading:false,
data:null,
err:null
})
let source = null;
onMounted(()=>{
state.loading = true
const CancelToken = axios.CancelToken;
source = CancelToken.source();
axios.get(url,{...ajaxConf,
cancelToken: source.token
}).then(
data=>{
state.loading = false
state.data = data
state.err = null
}
).catch(
err=>{
state.loading = false
state.data = null
state.err = err
}
)
})
onBeforeUnmount(()=>{
state.loading && source.cancel('Operation canceled by the user.');
})
return toRefs(state)
}
useEventTarget
- 为页面提供自动的事件绑定与解绑
- 此hook无响应式数据逻辑,仅仅是抽离了事件绑定与解绑逻辑
- 组件挂载时自动绑定DOM事件到指定元素身上,组件卸载时再执行解绑
- 可配置事件传播方向与防抖/节流功能
- 核心代码:
function useEventTarget(conf){
let {domSelector,eventType,eventHandler,useCapture,useDebouce,useThrottle} = conf
const element = document.querySelector(domSelector)
if(useDebouce){
eventHandler = debounce(eventHandler)
}else if(useThrottle){
eventHandler = throttle(eventHandler)
}
onMounted(()=>{
element.addEventListener(
eventType,
eventHandler,
useCapture
)
})
onUnmounted(()=>{
element.removeEventListener(eventType,eventHandler)
})
}
其它小hook(不计其数)
- 核心思想就是返回响应式数据及其附属的二手数据和操作函数等
- 几乎任意一个Vue的项目中都一定能用到
- 示例代码:商品价格自变化逻辑hook
function usePrice() {
let price = ref(10);
let discountedPrice = computed(() => {
return {
half: price.value / 2,
bazhe: price.value * 0.8,
jiuzhe: price.value * 0.9,
};
});
const editPrice = (value) => {
price.value = value;
};
return {
price,
discountedPrice,
editPrice,
};
}
const {price,discountedPrice,editPrice} = usePrice()
性能优化
数据缓存
- 如无手动刷新,则5分钟以内使用Vuex内的持久化数据缓存;
- 在vuex中设置一个map为每个数据配置一个缓存时效+定时更新的timerId,每当利用action获取数据成功后立即拉起一个延时定时器,执行下一次的自动更新;
- 入用户在延时更新启动前再次调用action,则先取消上一次的timerId,然后再次设置延时;
事件防抖:防止重复提交表单;
<form v-submit.debounce="{handler:onSubmit,delay:500}">
<input/>
<button>提交</button>
</form>
app.directive("submit",{
mounted:(el,binding)=>{
let {handler,delay} = binding.value
if(binding.modifiers["debounce"]){
handler = debounce(handler,delay)
}else if(binding.modifiers["throttle"]){
handler = throttle(handler,delay)
}
el.addEventListener(
"submit",
handler
)
}
})
数据预加载:登录后通过Promise队列预加载后续菜单数据;
- 在管理页主菜单加载完毕后,使用Promise.allSettled拉起一个并发的ajaxPromise数组;
- 在任何一个Promise任务成功回调中更新Vuex中的数据;
频繁的组合筛选:使用自带结果缓存的高阶函数;
- 定义一个专门用于从既定数组中筛选结果的函数cachedArrFilter(arr,filters);
- 该函数在执行之前会看看相同入参的执行结果是否曾经缓存过,是则直接调用缓存,否则调用arr.filter执行筛选并缓存结果;
- 该缓存的具体实现为一个LRUCache,即自动对最近最常使用的查询结果进行缓存而非无度缓存;
- 参见 Javascript中如何实现函数缓存
- 参见 JavaScript实现LRUCache
function cachedFn(fn) {
const cacheObj = {};
return (...args) => {
console.log("args", args);
if (!cacheObj[args]) {
let result = fn.apply(null, args);
cacheObj[args] = result;
console.log("重新计算结果");
return cacheObj[args];
} else {
console.log("读取缓存结果");
return cacheObj[args];
}
};
}
function add(a, b) {
return a + b;
}
const cadd = cachedFn(add);
console.log(cadd(100, 200));
console.log(cadd(100, 200));
对上传的房源图片使用自动压缩;
- 对需要上传的房源图片默认执行一下压缩
- 监听文件上传的input框中的onchange事件,将拿到的文件信息使用github上的第三方库
img-compressor进行压缩上传;
- 通常5M级别的图片会被压缩到500多K;
- 还未来得及研读该库的核心原理;
- 参考 # JS实现图片压缩上传
快搜租客/社区:按拼音索引将大数组拆分成26个小组;
- 使用v-autoindex自定义指令实现input框的中文输入自动提取英文首字母作为查询索引;
- 后台给回的房源列表和租客列表均有按拼音字段,前端根据该字段自动将大列表拆分为包含26个子数组的对象;
- 当用户执行搜索时,会在大数组的缓存结果中根据首字母首先拿到对应的小数组,然后再执行进一步搜索,极大地提高了搜索效率;
- 参考 Vue自定义指令
特色功能
大量的批量操作优化体验,如批量催租、批量上下架、批量置顶等,使用SmartPatchTable;
支持用户自定义常用功能面板;
- 每个菜单项右侧包含该菜单的常用操作;
- 将这些面板分为常用和不常用两组,不常用组可以折叠,点击面板右上角的转移按钮可以将面板从当前组转移到另一组;
- 常用组面板支持拖拽排序;
- 参考 如何实现拖拽排序