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);
});
相关推荐
用户543308144194几秒前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo4 分钟前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
恋猫de小郭31 分钟前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木36 分钟前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮40 分钟前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati43 分钟前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉1 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain
wuhen_n1 小时前
双端 Diff 算法详解
前端·javascript·vue.js
UrbanJazzerati1 小时前
Vue 3 纯小白快速入门指南
前端·面试
雮尘1 小时前
手把手带你玩转Android gRPC:一篇搞定原理、配置与客户端开发
android·前端·grpc