TTeamDesk 学习手册

src/stores/drafts.ts

此代码来自构建时的仓库快照。修改源码后重新构建即可更新。

ts
import { createStore } from 'zustand/vanilla';
import {
  createJSONStorage,
  persist,
  type StateStorage,
} from 'zustand/middleware';
import { ticketPriorities } from '@/shared/ticket';
import { z } from 'zod';

const entrySchema = z.object({
  text: z.string().max(10000).optional(),
  requestId: z.uuid().optional(),
  ticket: z
    .object({
      title: z.string().max(100),
      description: z.string().max(10000),
      priority: z.enum(ticketPriorities),
    })
    .partial()
    .optional(),
});
export type Draft = z.infer<typeof entrySchema>;
export type DraftState = {
  drafts: Record<string, Draft>;
  hydrated: boolean;
  save: (key: string, draft: Draft) => void;
  remove: (key: string) => void;
};
export const draftStorageKey = (userId: string) => `teamdesk-drafts:${userId}`;
export const draftKey = (teamId: string, ticketId: string) =>
  `${teamId}:${ticketId}`;

// 工厂函数让每个登录账号拥有独立 store,避免 SSR 进程中的全局变量串号。
export function createDraftStore(userId: string, storage?: StateStorage) {
  return createStore<DraftState>()(
    persist(
      (set) => ({
        drafts: {},
        hydrated: false,
        save(key, draft) {
          set((state) => {
            const drafts = { ...state.drafts, [key]: draft };
            // 本地草稿只保留最近编辑的 100 项,避免无限占用浏览器存储。
            delete drafts[key];
            drafts[key] = draft;
            while (Object.keys(drafts).length > 100)
              delete drafts[Object.keys(drafts)[0]];
            return { drafts };
          });
        },
        remove(key) {
          set((state) => {
            const drafts = { ...state.drafts };
            delete drafts[key];
            return { drafts };
          });
        },
      }),
      {
        name: draftStorageKey(userId),
        storage: createJSONStorage(() => storage ?? sessionStorage),
        partialize: (state) => ({ drafts: state.drafts }),
        skipHydration: true,
        merge(persisted, current) {
          // 浏览器存储也可能被改写;恢复时校验形状,只读取数据,不恢复任何函数。
          const parsed = z
            .object({ drafts: z.record(z.string(), entrySchema) })
            .safeParse(persisted);
          return {
            ...current,
            drafts: parsed.success
              ? Object.fromEntries(
                  Object.entries(parsed.data.drafts).slice(-100),
                )
              : {},
          };
        },
      },
    ),
  );
}

搜索教程

输入关键词,搜索全部章节。

阅读设置

外观
正文字号

从一张工单出发,读懂数据如何流转。

设置与进度仅保存在当前浏览器。