timeSeriesJump.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import type { TimeSeriesQuery } from '@jeesite/otg/api/types/query';
  2. import type { LocationQueryValue } from 'vue-router';
  3. export const BEHAVIOR_SPACETIME_JUMP_QUERY_KEY = 'jumpPayload';
  4. export const BEHAVIOR_SPACETIME_JUMP_RESULT_KEY = 'behaviorSpacetimeJumpResult';
  5. export interface BehaviorSpacetimeJumpPayload extends Partial<TimeSeriesQuery> {
  6. tableName?: string;
  7. personName?: string;
  8. personNames?: string[];
  9. keyword?: string;
  10. startDate?: string;
  11. endDate?: string;
  12. seriesDateTime?: string;
  13. }
  14. type TimeSeriesJumpResponseLike =
  15. | BehaviorSpacetimeJumpPayload
  16. | Array<Record<string, unknown>>
  17. | {
  18. data?: BehaviorSpacetimeJumpPayload;
  19. query?: BehaviorSpacetimeJumpPayload;
  20. record?: BehaviorSpacetimeJumpPayload;
  21. row?: BehaviorSpacetimeJumpPayload;
  22. obj?: BehaviorSpacetimeJumpPayload;
  23. result?: BehaviorSpacetimeJumpPayload;
  24. }
  25. | null
  26. | undefined;
  27. const pickRouteQueryValue = (value?: LocationQueryValue | LocationQueryValue[]) => {
  28. return Array.isArray(value) ? value[0] : value;
  29. };
  30. const normalizeText = (value: unknown) => {
  31. if (value === 0) return '0';
  32. const text = String(value ?? '').trim();
  33. if (!text || text === 'null' || text === 'undefined') return undefined;
  34. return text;
  35. };
  36. const normalizeStringArray = (value: unknown) => {
  37. if (Array.isArray(value)) {
  38. const values = value.map((item) => normalizeText(item)).filter((item): item is string => Boolean(item));
  39. return values.length ? Array.from(new Set(values)) : undefined;
  40. }
  41. const text = normalizeText(value);
  42. if (!text) return undefined;
  43. if (text.startsWith('[') && text.endsWith(']')) {
  44. try {
  45. const parsed = JSON.parse(text);
  46. if (Array.isArray(parsed)) return normalizeStringArray(parsed);
  47. } catch {
  48. // Ignore invalid JSON and fall back to comma split.
  49. }
  50. }
  51. const values = text
  52. .split(',')
  53. .map((item) => normalizeText(item))
  54. .filter((item): item is string => Boolean(item));
  55. return values.length ? Array.from(new Set(values)) : undefined;
  56. };
  57. const extractIndexedObjectList = (value: unknown) => {
  58. if (!value || Array.isArray(value) || typeof value !== 'object') return [];
  59. return Object.entries(value as Record<string, unknown>)
  60. .filter(([key, item]) => /^\d+$/.test(key) && !!item && typeof item === 'object')
  61. .sort((left, right) => Number(left[0]) - Number(right[0]))
  62. .map(([, item]) => item as Record<string, unknown>);
  63. };
  64. const normalizeResponseList = (resp?: TimeSeriesJumpResponseLike) => {
  65. if (Array.isArray(resp)) return resp;
  66. if (Array.isArray(resp?.data)) return resp.data;
  67. const indexedDataList = extractIndexedObjectList(resp?.data);
  68. if (indexedDataList.length) return indexedDataList;
  69. if (Array.isArray((resp as Record<string, unknown> | undefined)?.records)) {
  70. return (resp as Record<string, unknown>).records as Array<Record<string, unknown>>;
  71. }
  72. if (Array.isArray((resp as Record<string, unknown> | undefined)?.list)) {
  73. return (resp as Record<string, unknown>).list as Array<Record<string, unknown>>;
  74. }
  75. if (Array.isArray((resp as Record<string, unknown> | undefined)?.result)) {
  76. return (resp as Record<string, unknown>).result as Array<Record<string, unknown>>;
  77. }
  78. const indexedRespList = extractIndexedObjectList(resp);
  79. if (indexedRespList.length) return indexedRespList;
  80. return [];
  81. };
  82. const pickJumpSource = (resp?: TimeSeriesJumpResponseLike) => {
  83. if (!resp || Array.isArray(resp)) return {};
  84. return resp.data ?? resp.query ?? resp.record ?? resp.row ?? resp.obj ?? resp.result ?? resp;
  85. };
  86. const normalizePayload = (source?: Record<string, unknown>) => {
  87. const tableNameEn = normalizeText(source?.tableNameEn) || normalizeText(source?.tableName);
  88. const tableName = normalizeText(source?.tableName) || tableNameEn;
  89. const personNames = normalizeStringArray(source?.personNames);
  90. const personName = normalizeText(source?.personName) || (personNames?.length === 1 ? personNames[0] : undefined);
  91. const id = normalizeText(source?.id);
  92. const rowId = normalizeText(source?.rowId);
  93. const startDate = normalizeText(source?.startDate);
  94. const endDate = normalizeText(source?.endDate);
  95. const seriesDateTime = normalizeText(source?.seriesDateTime);
  96. return {
  97. hasLocal: normalizeText(source?.hasLocal),
  98. types: normalizeStringArray(source?.types),
  99. tableName,
  100. tableNameEn,
  101. tableNameCn: normalizeText(source?.tableNameCn),
  102. id,
  103. rowId,
  104. personName,
  105. personNames,
  106. keyword: normalizeText(source?.keyword),
  107. startDate,
  108. endDate,
  109. seriesDateTime,
  110. } satisfies BehaviorSpacetimeJumpPayload;
  111. };
  112. export const buildTimeSeriesJumpQuery = (query?: Partial<BehaviorSpacetimeJumpPayload>) => {
  113. return normalizePayload((query || {}) as Record<string, unknown>);
  114. };
  115. export const hasMeaningfulJumpPayload = (payload?: Partial<BehaviorSpacetimeJumpPayload> | null) => {
  116. if (!payload) return false;
  117. return Boolean(
  118. payload.id ||
  119. payload.rowId ||
  120. payload.tableNameEn ||
  121. payload.tableName ||
  122. payload.personName ||
  123. payload.personNames?.length ||
  124. payload.types?.length ||
  125. payload.keyword ||
  126. payload.startDate ||
  127. payload.endDate ||
  128. payload.seriesDateTime,
  129. );
  130. };
  131. export const resolveBehaviorSpacetimeJumpPayload = (
  132. resp?: TimeSeriesJumpResponseLike,
  133. fallback?: Partial<BehaviorSpacetimeJumpPayload>,
  134. ) => {
  135. const responseList = normalizeResponseList(resp);
  136. if (responseList.length) {
  137. const fallbackPayload = buildTimeSeriesJumpQuery(fallback);
  138. const responsePersonNames = normalizeStringArray(
  139. responseList.map((item) => normalizeText(item?.personName)).filter(Boolean),
  140. );
  141. const personNames = fallbackPayload.personNames?.length ? fallbackPayload.personNames : responsePersonNames;
  142. const orderedDateTimes = responseList
  143. .map((item) => normalizeText(item?.seriesDateTime))
  144. .filter((item): item is string => Boolean(item))
  145. .sort((left, right) => new Date(left).getTime() - new Date(right).getTime());
  146. const startDate = orderedDateTimes[0];
  147. const endDate = orderedDateTimes[orderedDateTimes.length - 1];
  148. return buildTimeSeriesJumpQuery({
  149. ...fallbackPayload,
  150. personName: fallbackPayload.personName || (personNames?.length === 1 ? personNames[0] : undefined),
  151. personNames,
  152. keyword: undefined,
  153. startDate: fallbackPayload.startDate || startDate,
  154. endDate: fallbackPayload.endDate || endDate,
  155. });
  156. }
  157. const normalizedResp = normalizePayload(pickJumpSource(resp) as Record<string, unknown>);
  158. if (hasMeaningfulJumpPayload(normalizedResp)) return normalizedResp;
  159. return buildTimeSeriesJumpQuery(fallback);
  160. };
  161. export const persistBehaviorSpacetimeJumpResult = (resp?: unknown) => {
  162. if (typeof window === 'undefined') return;
  163. try {
  164. if (!resp) {
  165. window.sessionStorage.removeItem(BEHAVIOR_SPACETIME_JUMP_RESULT_KEY);
  166. return;
  167. }
  168. window.sessionStorage.setItem(BEHAVIOR_SPACETIME_JUMP_RESULT_KEY, JSON.stringify(resp));
  169. } catch {
  170. // Ignore storage errors.
  171. }
  172. };
  173. export const consumeBehaviorSpacetimeJumpResult = () => {
  174. if (typeof window === 'undefined') return null;
  175. try {
  176. const raw = window.sessionStorage.getItem(BEHAVIOR_SPACETIME_JUMP_RESULT_KEY);
  177. if (!raw) return null;
  178. window.sessionStorage.removeItem(BEHAVIOR_SPACETIME_JUMP_RESULT_KEY);
  179. return JSON.parse(raw);
  180. } catch {
  181. return null;
  182. }
  183. };
  184. const buildBehaviorSpacetimeNavigationPayload = (payload?: Partial<BehaviorSpacetimeJumpPayload>) => {
  185. const normalized = buildTimeSeriesJumpQuery(payload);
  186. return {
  187. ...normalized,
  188. id: normalized.id || normalized.rowId,
  189. rowId: undefined,
  190. } satisfies BehaviorSpacetimeJumpPayload;
  191. };
  192. export const serializeBehaviorSpacetimeJumpPayload = (payload?: Partial<BehaviorSpacetimeJumpPayload>) => {
  193. return JSON.stringify(buildBehaviorSpacetimeNavigationPayload(payload));
  194. };
  195. export const parseBehaviorSpacetimeJumpPayload = (raw?: LocationQueryValue | LocationQueryValue[]) => {
  196. const text = pickRouteQueryValue(raw);
  197. if (!text) return null;
  198. try {
  199. const parsed = JSON.parse(String(text));
  200. const payload = buildTimeSeriesJumpQuery(parsed as Partial<BehaviorSpacetimeJumpPayload>);
  201. return hasMeaningfulJumpPayload(payload) ? payload : null;
  202. } catch {
  203. return null;
  204. }
  205. };