web架构师编辑器内容-添加自动保存的功能

对于频繁改动的应用,自动保存的功能是一个非常有用的功能,可以避免用户在没有保存的情况下丢失自己保存过的数据。

对于自动保存,一般有两种实现,参考语雀和石墨:

  • 语雀采用的是定时保存的方式,大约在3分半后自动发送一次请求
  • 石墨文档采用的是实时保存:对于文本区域,直接输入字符的时候,会发送请求,就是在文本区域onchange的时候,绑定一个事件,有新内容介入的时候发送请求。

这里我们可以采用第一种方法,定时发送请求。采用这种方式之前我们可以进行一个优化:为了避免一股脑的发送请求,比如说如果用户没有做任何操作,到时候还是会发送请求,所以呢,我们要给他添加一个特殊字段isDirty,这个字段可以标记一次数据是否有修改,加入有修改,才进行保存,这里采用的方式和做历史数据功能的方式类似,不会采用对比新旧数据比较耗时的方法,而是触发特定的mutations才会标记字段,比如说addComponentdeleteComponentupdateComponentupdatPage

js 复制代码
// 特殊字段
isDirty

// 在一些 mutation 的时候同时修改该字段
addComponent
deleteComponent
updateComponent
updatePage

state.isDirty = true

// 在 save 之后将 isDirty 置为 false
state.isDirty = false

代码实现:添加isDirty字段

ts 复制代码
// store/editor.ts
export interface EditorProps {
  // 供中间编辑器渲染的数组
  components: ComponentData[];
  // 当前编辑的是哪个元素,uuid
  currentElement: string;
  // 当然最后保存的时候还有有一些项目信息,这里并没有写出,等做到的时候再补充
  page: PageData;
  // 当前被复制的组件
  copiedComponent?: ComponentData;
  // 当前操作的历史记录
  histories: HistoryProps[];
  // 当前历史记录的操作位置
  historyIndex: number;
  // 开始更新时的缓存值
  cachedOldValues: any;
  // 保存最多历史条目记录数
  maxHistoryNumber: number;
  // 数据是否有修改
  isDirty: boolean;
}

然后在一系列mutations中给isDirty设置为true,这都是一系列重复的功能,这时候就想到之前使用过的高阶函数处理过浏览器的默认行为:web架构师编辑器内容-快捷键操作的实现,这里我们也可以封装成高阶函数来完成一系列操作,比较方便:

ts 复制代码
// store/editor.ts
const setDirtyWrapper = (callback: Mutation<EditorProps>) => {
  return (state: EditorProps, payload: any) => {
    state.isDirty = true;
    callback(state, payload);
  };
};

// mutations
// 添加元素
addComponent: setDirtyWrapper((state, component: ComponentData) => {
  component.layerName = "图层" + (state.components.length + 1);
  state.components.push(component);
  pushHistory(state, {
    id: uuidv4(),
    componentId: component.id,
    type: "add",
    data: cloneDeep(component),
  });
})

// 粘贴元素
pasteCopiedComponent: setDirtyWrapper((state) => {
  if (state.copiedComponent) {
    const clone = cloneDeep(state.copiedComponent);
    clone.id = uuidv4();
    clone.layerName = clone.layerName + "副本";
    state.components.push(clone);
    message.success("已黏贴当前图层", 1);
    pushHistory(state, {
      id: uuidv4(),
      componentId: clone.id,
      type: "add",
      data: cloneDeep(clone),
    });
  }
})

// 删除元素
deleteComponent: setDirtyWrapper((state, id) => {
  const currentComponent = state.components.find(
    (component) => component.id === id
  );
  if (currentComponent) {
    const currentIndex = state.components.findIndex(
      (component) => component.id === id
    );
    state.components = state.components.filter(
      (component) => component.id !== id
    );
    pushHistory(state, {
      id: uuidv4(),
      componentId: currentComponent.id,
      type: "delete",
      data: currentComponent,
      index: currentIndex,
    });
    message.success("删除当前图层成功", 1);
  }
})

//更新元素
updateComponent: setDirtyWrapper(
  (state, { key, value, id, isRoot }: UpdateComponentData) => {
    const updatedComponent = state.components.find(
      (component) => component.id === (id || state.currentElement)
    );
    if (updatedComponent) {
      if (isRoot) {
        (updatedComponent as any)[key as string] = value;
      } else {
        const oldValue = Array.isArray(key)
          ? key.map((key) => updatedComponent.props[key])
          : updatedComponent.props[key];
        if (!state.cachedOldValues) {
          state.cachedOldValues = oldValue;
        }
        pushHistoryDebounce(state, { key, value, id });
        if (Array.isArray(key) && Array.isArray(value)) {
          key.forEach((keyName, index) => {
            updatedComponent.props[keyName] = value[index];
          });
        } else if (typeof key === "string" && typeof value === "string") {
          updatedComponent.props[key] = value;
        }
      }
    }
  }
)

发送完请求后,将isDirty置为false

ts 复制代码
// store/editor.ts
saveWork(state) {
  state.isDirty = false;
},

onMounted生命周期里面增加对于的逻辑:

ts 复制代码
onMounted(() => {
  timer = setInterval(() => {
    if (isDirty.value) {
      saveWork();
    }
  }, 1000 * 50);
});
onUnmounted(() => {
  clearInterval(timer);
});
相关推荐
Mintopia4 分钟前
Three.js 深度冲突:当像素在 Z 轴上玩起 "挤地铁" 游戏
前端·javascript·three.js
Penk是个码农9 分钟前
web前端面试-- MVC、MVP、MVVM 架构模式对比
前端·面试·mvc
MrSkye12 分钟前
🔥JavaScript 入门必知:代码如何运行、变量提升与 let/const🔥
前端·javascript·面试
白瓷梅子汤16 分钟前
跟着官方示例学习 @tanStack-form --- Linked Fields
前端·react.js
爱学习的茄子20 分钟前
深入理解JavaScript闭包:从入门到精通的实战指南
前端·javascript·面试
zhanshuo1 小时前
不依赖框架,如何用 JS 实现一个完整的前端路由系统
前端·javascript·html
火柴盒zhang1 小时前
websheet在线电子表格(spreadsheet)在集团型企业财务报表中的应用
前端·html·报表·合并·spreadsheet·websheet·集团财务
khalil1 小时前
基于 Vue3实现一款简历生成工具
前端·vue.js
拾光拾趣录1 小时前
浏览器对队头阻塞问题的深度优化策略
前端·浏览器
用户8122199367221 小时前
[已完结]后端开发必备高阶技能--自研企业级网关组件(Netty+Nacos+Disruptor)
前端