隐患详情重新设计,检查计划重新设计成做题排查式
This commit is contained in:
102
utils/draftCache.js
Normal file
102
utils/draftCache.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/** 草稿 Storage 命名空间(统一 key 前缀) */
|
||||
export const DRAFT_NS = {
|
||||
ASSIGN: 'draft_assign',
|
||||
ACCEPT: 'draft_accept',
|
||||
RECTIFY: 'draft_rectify',
|
||||
INSPECTION_RESULT: 'draft_inspection_result',
|
||||
INSPECTION_SHEET: 'draft_inspection_sheet'
|
||||
};
|
||||
|
||||
/**
|
||||
* 构建草稿 Storage key(与迁移前页面逻辑一致,保留空片段)
|
||||
* 例:draft_rectify_100_ 、draft_accept_
|
||||
* @param {string} namespace 命名空间,如 DRAFT_NS.ASSIGN
|
||||
* @param {...string|number} parts 业务 id 片段
|
||||
*/
|
||||
export function buildDraftKey(namespace, ...parts) {
|
||||
return [namespace, ...parts.map((p) => String(p ?? ''))].join('_');
|
||||
}
|
||||
|
||||
/** 迁移期 compact key(曾错误地 filter 掉空片段) */
|
||||
export function buildDraftKeyCompact(namespace, ...parts) {
|
||||
return [namespace, ...parts.map((p) => String(p ?? '')).filter(Boolean)].join('_');
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取草稿,支持回退到其它 key(迁移兼容)
|
||||
* @param {string} primaryKey
|
||||
* @param {string[]} [fallbackKeys]
|
||||
*/
|
||||
export function loadDraftWithFallback(primaryKey, fallbackKeys = []) {
|
||||
const primary = loadDraft(primaryKey);
|
||||
if (primary) {
|
||||
return { data: primary, key: primaryKey, fromFallback: false };
|
||||
}
|
||||
|
||||
for (const fallbackKey of fallbackKeys) {
|
||||
if (!fallbackKey || fallbackKey === primaryKey) continue;
|
||||
const fallback = loadDraft(fallbackKey);
|
||||
if (fallback) {
|
||||
return { data: fallback, key: primaryKey, fromFallback: true, fallbackKey };
|
||||
}
|
||||
}
|
||||
|
||||
return { data: null, key: primaryKey, fromFallback: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取草稿
|
||||
* @param {string} key
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
export function loadDraft(key) {
|
||||
if (!key) return null;
|
||||
|
||||
const raw = uni.getStorageSync(key);
|
||||
if (!raw) return null;
|
||||
|
||||
try {
|
||||
return typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||
} catch (error) {
|
||||
console.error('[draftCache] 解析草稿失败:', key, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入草稿
|
||||
* @param {string} key
|
||||
* @param {Object} data
|
||||
*/
|
||||
export function saveDraftToStorage(key, data) {
|
||||
if (!key) return;
|
||||
|
||||
uni.setStorageSync(
|
||||
key,
|
||||
JSON.stringify({
|
||||
...data,
|
||||
updatedAt: Date.now()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除草稿
|
||||
* @param {string} key
|
||||
*/
|
||||
export function removeDraft(key) {
|
||||
if (!key) return;
|
||||
uni.removeStorageSync(key);
|
||||
}
|
||||
|
||||
/** 默认:payload 中任一字段有值即视为有内容(忽略 updatedAt) */
|
||||
export function defaultHasContent(payload) {
|
||||
if (!payload || typeof payload !== 'object') return false;
|
||||
|
||||
return Object.entries(payload).some(([key, value]) => {
|
||||
if (key === 'updatedAt') return false;
|
||||
if (Array.isArray(value)) return value.length > 0;
|
||||
if (value && typeof value === 'object') return Object.keys(value).length > 0;
|
||||
return value !== '' && value != null;
|
||||
});
|
||||
}
|
||||
158
utils/useDraftCache.js
Normal file
158
utils/useDraftCache.js
Normal file
@@ -0,0 +1,158 @@
|
||||
import { ref, nextTick, watch } from 'vue';
|
||||
import {
|
||||
loadDraftWithFallback,
|
||||
saveDraftToStorage,
|
||||
removeDraft,
|
||||
defaultHasContent
|
||||
} from './draftCache.js';
|
||||
|
||||
/**
|
||||
* 页面草稿 Composable:统一 save / restore / clear 流程
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {() => string} options.getKey 返回 Storage key
|
||||
* @param {() => Object} options.getPayload 返回要保存的数据
|
||||
* @param {(payload: Object) => boolean} [options.hasContent] 判断是否有有效草稿
|
||||
* @param {(payload: Object) => void} options.applyPayload 恢复草稿到表单
|
||||
* @param {() => void} options.clearForm 清空表单
|
||||
* @param {() => boolean} [options.canSave] 是否允许保存(如 hazardId 未就绪时为 false)
|
||||
* @param {boolean} [options.requireInitialized=false] 为 true 时,restore 完成前不自动 save
|
||||
* @param {string} [options.restoreToast]
|
||||
* @param {string} [options.clearToast]
|
||||
* @param {boolean} [options.showRestoreToast=true]
|
||||
* @param {(payload: Object) => void} [options.onAfterRestore] 恢复并 apply 后的额外处理
|
||||
* @param {() => string[]} [options.getFallbackKeys] 读取草稿时的兼容 key 列表
|
||||
*/
|
||||
export function useDraftCache(options) {
|
||||
const {
|
||||
getKey,
|
||||
getPayload,
|
||||
hasContent = defaultHasContent,
|
||||
applyPayload,
|
||||
clearForm,
|
||||
canSave = () => true,
|
||||
requireInitialized = false,
|
||||
restoreToast = '已自动恢复您上次未提交的内容',
|
||||
clearToast = '草稿已清空',
|
||||
showRestoreToast = true,
|
||||
onAfterRestore,
|
||||
getFallbackKeys
|
||||
} = options;
|
||||
|
||||
const hasDraft = ref(false);
|
||||
const showRestoreBanner = ref(false);
|
||||
const isRestoring = ref(false);
|
||||
const isInitialized = ref(false);
|
||||
|
||||
const finishRestoreState = (callback) => {
|
||||
nextTick(() => {
|
||||
isRestoring.value = false;
|
||||
isInitialized.value = true;
|
||||
callback?.();
|
||||
});
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (isRestoring.value) return;
|
||||
if (requireInitialized && !isInitialized.value) return;
|
||||
if (!canSave()) return;
|
||||
|
||||
const key = getKey();
|
||||
const payload = getPayload();
|
||||
|
||||
if (!hasContent(payload)) {
|
||||
removeDraft(key);
|
||||
hasDraft.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
saveDraftToStorage(key, payload);
|
||||
hasDraft.value = true;
|
||||
};
|
||||
|
||||
const clear = (showToast = true) => {
|
||||
removeDraft(getKey());
|
||||
hasDraft.value = false;
|
||||
showRestoreBanner.value = false;
|
||||
isRestoring.value = true;
|
||||
clearForm();
|
||||
finishRestoreState();
|
||||
|
||||
if (showToast) {
|
||||
uni.showToast({ title: clearToast, icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 恢复草稿
|
||||
* @returns {boolean} 是否成功恢复
|
||||
*/
|
||||
const restore = () => {
|
||||
const key = getKey();
|
||||
const { data, fromFallback, fallbackKey } = loadDraftWithFallback(
|
||||
key,
|
||||
getFallbackKeys?.() || []
|
||||
);
|
||||
|
||||
if (!data || !hasContent(data)) {
|
||||
isInitialized.value = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
isRestoring.value = true;
|
||||
|
||||
try {
|
||||
applyPayload(data);
|
||||
hasDraft.value = true;
|
||||
showRestoreBanner.value = true;
|
||||
onAfterRestore?.(data);
|
||||
|
||||
// 从兼容 key 读到后,立即写回标准 key 并清理旧 key
|
||||
if (fromFallback && fallbackKey) {
|
||||
saveDraftToStorage(key, data);
|
||||
removeDraft(fallbackKey);
|
||||
}
|
||||
|
||||
finishRestoreState(() => {
|
||||
if (showRestoreToast) {
|
||||
uni.showToast({
|
||||
title: restoreToast,
|
||||
icon: 'none',
|
||||
duration: 2500
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[useDraftCache] 恢复草稿失败:', error);
|
||||
isRestoring.value = false;
|
||||
isInitialized.value = true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 绑定自动保存(等价于页面里的 watch + saveDraft)
|
||||
* @param {import('vue').WatchSource|import('vue').WatchSource[]} source
|
||||
* @param {import('vue').WatchOptions} [watchOptions]
|
||||
*/
|
||||
const bindAutoSave = (source, watchOptions = {}) => {
|
||||
watch(
|
||||
source,
|
||||
() => save(),
|
||||
{ deep: true, ...watchOptions }
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
hasDraft,
|
||||
showRestoreBanner,
|
||||
isRestoring,
|
||||
isInitialized,
|
||||
save,
|
||||
clear,
|
||||
restore,
|
||||
bindAutoSave
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user