tantee-nuxt-commons 0.0.174 → 0.0.175
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -1
- package/dist/runtime/components/device/IdCardButton.vue +83 -0
- package/dist/runtime/components/device/IdCardWebSocket.vue +195 -0
- package/dist/runtime/components/device/Scanner.vue +338 -0
- package/dist/runtime/components/form/Table.vue +4 -1
- package/dist/runtime/components/form/TableData.vue +4 -1
- package/dist/runtime/components/model/Autocomplete.vue +4 -1
- package/dist/runtime/components/model/Combobox.vue +4 -1
- package/dist/runtime/components/model/Select.vue +4 -1
- package/dist/runtime/composables/api.d.ts +4 -4
- package/dist/runtime/composables/api.js +33 -21
- package/dist/runtime/composables/clientConfig.d.ts +15 -0
- package/dist/runtime/composables/clientConfig.js +79 -0
- package/dist/runtime/composables/hostAgent.d.ts +260 -0
- package/dist/runtime/composables/hostAgent.js +74 -0
- package/dist/runtime/composables/hostAgentWs.d.ts +272 -0
- package/dist/runtime/composables/hostAgentWs.js +145 -0
- package/dist/runtime/composables/localStorageModel.d.ts +38 -0
- package/dist/runtime/composables/localStorageModel.js +88 -0
- package/dist/runtime/plugins/clientConfig.d.ts +2 -0
- package/dist/runtime/plugins/clientConfig.js +22 -0
- package/dist/runtime/plugins/default.d.ts +2 -0
- package/dist/runtime/plugins/default.js +50 -0
- package/dist/runtime/types/clientConfig.d.ts +13 -0
- package/package.json +3 -2
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { IdCardWsClientMessage, IdCardWsEvent } from './hostAgent.js';
|
|
2
|
+
export declare function useHostAgentWs(): {
|
|
3
|
+
ws: import("vue").ShallowRef<WebSocket | null, WebSocket | null>;
|
|
4
|
+
wsConnected: import("vue").ComputedRef<boolean>;
|
|
5
|
+
wsSubscribed: import("vue").ComputedRef<boolean>;
|
|
6
|
+
wsReader: import("vue").ComputedRef<string | null>;
|
|
7
|
+
wsWithPhoto: import("vue").ComputedRef<boolean>;
|
|
8
|
+
wsLastMessage: import("vue").ComputedRef<{
|
|
9
|
+
type: "monitor.subscribed";
|
|
10
|
+
ok: boolean;
|
|
11
|
+
reader?: string | null | undefined;
|
|
12
|
+
withPhoto?: boolean | undefined;
|
|
13
|
+
} | {
|
|
14
|
+
type: "monitor.started";
|
|
15
|
+
reader?: string | null | undefined;
|
|
16
|
+
} | {
|
|
17
|
+
type: "monitor.switchAck";
|
|
18
|
+
ok: boolean;
|
|
19
|
+
reader?: string | null | undefined;
|
|
20
|
+
} | {
|
|
21
|
+
type: "monitor.switched";
|
|
22
|
+
reader?: string | null | undefined;
|
|
23
|
+
} | {
|
|
24
|
+
type: "monitor.unsubscribed";
|
|
25
|
+
ok: boolean;
|
|
26
|
+
} | {
|
|
27
|
+
type: "pong";
|
|
28
|
+
ts?: string | undefined;
|
|
29
|
+
} | {
|
|
30
|
+
type: "card.inserted";
|
|
31
|
+
payload: {
|
|
32
|
+
patient: {
|
|
33
|
+
citizenId?: string | null | undefined;
|
|
34
|
+
citizenIdVerified: boolean;
|
|
35
|
+
dob?: string | null | undefined;
|
|
36
|
+
dobPrecision?: "yearMonthDay" | "yearMonth" | "year" | string | undefined;
|
|
37
|
+
gender?: string | null | undefined;
|
|
38
|
+
meta?: import("~/.nuxt/imports").JsonMeta | null;
|
|
39
|
+
};
|
|
40
|
+
patientNames?: {
|
|
41
|
+
nameType: string;
|
|
42
|
+
prefix?: string | null | undefined;
|
|
43
|
+
firstName: string;
|
|
44
|
+
middleName?: string | null | undefined;
|
|
45
|
+
lastName?: string | null | undefined;
|
|
46
|
+
suffix?: string | null | undefined;
|
|
47
|
+
}[] | null | undefined;
|
|
48
|
+
patientAddresses?: {
|
|
49
|
+
mrn?: string | null | undefined;
|
|
50
|
+
addressType: string;
|
|
51
|
+
text: string;
|
|
52
|
+
place?: string | null | undefined;
|
|
53
|
+
villageNo?: string | null | undefined;
|
|
54
|
+
alleyWay?: string | null | undefined;
|
|
55
|
+
alley?: string | null | undefined;
|
|
56
|
+
street?: string | null | undefined;
|
|
57
|
+
subDistrictCode?: string | null | undefined;
|
|
58
|
+
districtCode?: string | null | undefined;
|
|
59
|
+
provinceCode?: string | null | undefined;
|
|
60
|
+
}[] | null | undefined;
|
|
61
|
+
patientPhotos?: {
|
|
62
|
+
photoSource?: string | null | undefined;
|
|
63
|
+
base64String: string;
|
|
64
|
+
}[] | null | undefined;
|
|
65
|
+
};
|
|
66
|
+
} | {
|
|
67
|
+
type: "card.removed";
|
|
68
|
+
} | {
|
|
69
|
+
type: "card.error";
|
|
70
|
+
message?: string | undefined;
|
|
71
|
+
} | {
|
|
72
|
+
type: "error";
|
|
73
|
+
code?: string | undefined;
|
|
74
|
+
message?: string | undefined;
|
|
75
|
+
} | {
|
|
76
|
+
[x: string]: any;
|
|
77
|
+
type: string;
|
|
78
|
+
} | null>;
|
|
79
|
+
wsLastRaw: import("vue").ComputedRef<{
|
|
80
|
+
readonly data: any;
|
|
81
|
+
readonly lastEventId: string;
|
|
82
|
+
readonly origin: string;
|
|
83
|
+
readonly ports: readonly {
|
|
84
|
+
close: {
|
|
85
|
+
(): void;
|
|
86
|
+
(): void;
|
|
87
|
+
};
|
|
88
|
+
postMessage: {
|
|
89
|
+
(message: any, transfer: Transferable[]): void;
|
|
90
|
+
(message: any, options?: StructuredSerializeOptions): void;
|
|
91
|
+
(message: any, transfer: Transferable[]): void;
|
|
92
|
+
(message: any, options?: StructuredSerializeOptions): void;
|
|
93
|
+
};
|
|
94
|
+
start: {
|
|
95
|
+
(): void;
|
|
96
|
+
(): void;
|
|
97
|
+
};
|
|
98
|
+
addEventListener: {
|
|
99
|
+
<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
100
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
101
|
+
<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
102
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
103
|
+
};
|
|
104
|
+
removeEventListener: {
|
|
105
|
+
<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
106
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
107
|
+
<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
108
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
109
|
+
};
|
|
110
|
+
dispatchEvent: {
|
|
111
|
+
(event: Event): boolean;
|
|
112
|
+
(event: Event): boolean;
|
|
113
|
+
};
|
|
114
|
+
onmessage: ((this: MessagePort, ev: MessageEvent) => any) | null;
|
|
115
|
+
onmessageerror: ((this: MessagePort, ev: MessageEvent) => any) | null;
|
|
116
|
+
}[];
|
|
117
|
+
readonly source: Window | {
|
|
118
|
+
close: {
|
|
119
|
+
(): void;
|
|
120
|
+
(): void;
|
|
121
|
+
};
|
|
122
|
+
postMessage: {
|
|
123
|
+
(message: any, transfer: Transferable[]): void;
|
|
124
|
+
(message: any, options?: StructuredSerializeOptions): void;
|
|
125
|
+
(message: any, transfer: Transferable[]): void;
|
|
126
|
+
(message: any, options?: StructuredSerializeOptions): void;
|
|
127
|
+
};
|
|
128
|
+
start: {
|
|
129
|
+
(): void;
|
|
130
|
+
(): void;
|
|
131
|
+
};
|
|
132
|
+
addEventListener: {
|
|
133
|
+
<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
134
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
135
|
+
<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
136
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
137
|
+
};
|
|
138
|
+
removeEventListener: {
|
|
139
|
+
<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
140
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
141
|
+
<K extends keyof MessagePortEventMap>(type: K, listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
142
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
143
|
+
};
|
|
144
|
+
dispatchEvent: {
|
|
145
|
+
(event: Event): boolean;
|
|
146
|
+
(event: Event): boolean;
|
|
147
|
+
};
|
|
148
|
+
onmessage: ((this: MessagePort, ev: MessageEvent) => any) | null;
|
|
149
|
+
onmessageerror: ((this: MessagePort, ev: MessageEvent) => any) | null;
|
|
150
|
+
} | {
|
|
151
|
+
onstatechange: ((this: ServiceWorker, ev: Event) => any) | null;
|
|
152
|
+
readonly scriptURL: string;
|
|
153
|
+
readonly state: ServiceWorkerState;
|
|
154
|
+
postMessage: {
|
|
155
|
+
(message: any, transfer: Transferable[]): void;
|
|
156
|
+
(message: any, options?: StructuredSerializeOptions): void;
|
|
157
|
+
(message: any, transfer: Transferable[]): void;
|
|
158
|
+
(message: any, options?: StructuredSerializeOptions): void;
|
|
159
|
+
};
|
|
160
|
+
addEventListener: {
|
|
161
|
+
<K extends keyof ServiceWorkerEventMap>(type: K, listener: (this: ServiceWorker, ev: ServiceWorkerEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
162
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
163
|
+
<K extends keyof ServiceWorkerEventMap>(type: K, listener: (this: ServiceWorker, ev: ServiceWorkerEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
164
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
165
|
+
};
|
|
166
|
+
removeEventListener: {
|
|
167
|
+
<K extends keyof ServiceWorkerEventMap>(type: K, listener: (this: ServiceWorker, ev: ServiceWorkerEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
168
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
169
|
+
<K extends keyof ServiceWorkerEventMap>(type: K, listener: (this: ServiceWorker, ev: ServiceWorkerEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
170
|
+
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
171
|
+
};
|
|
172
|
+
dispatchEvent: {
|
|
173
|
+
(event: Event): boolean;
|
|
174
|
+
(event: Event): boolean;
|
|
175
|
+
};
|
|
176
|
+
onerror: ((this: AbstractWorker, ev: ErrorEvent) => any) | null;
|
|
177
|
+
} | null;
|
|
178
|
+
initMessageEvent: {
|
|
179
|
+
(type: string, bubbles?: boolean, cancelable?: boolean, data?: any, origin?: string, lastEventId?: string, source?: MessageEventSource | null, ports?: MessagePort[]): void;
|
|
180
|
+
(type: string, bubbles?: boolean, cancelable?: boolean, data?: any, origin?: string, lastEventId?: string, source?: MessageEventSource | null, ports?: Iterable<MessagePort>): void;
|
|
181
|
+
(type: string, bubbles?: boolean, cancelable?: boolean, data?: any, origin?: string, lastEventId?: string, source?: MessageEventSource | null, ports?: MessagePort[]): void;
|
|
182
|
+
};
|
|
183
|
+
readonly bubbles: boolean;
|
|
184
|
+
cancelBubble: boolean;
|
|
185
|
+
readonly cancelable: boolean;
|
|
186
|
+
readonly composed: boolean;
|
|
187
|
+
readonly currentTarget: {
|
|
188
|
+
addEventListener: {
|
|
189
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
|
|
190
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
|
|
191
|
+
};
|
|
192
|
+
dispatchEvent: {
|
|
193
|
+
(event: Event): boolean;
|
|
194
|
+
(event: Event): boolean;
|
|
195
|
+
};
|
|
196
|
+
removeEventListener: {
|
|
197
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
|
|
198
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
|
|
199
|
+
};
|
|
200
|
+
} | null;
|
|
201
|
+
readonly defaultPrevented: boolean;
|
|
202
|
+
readonly eventPhase: number;
|
|
203
|
+
readonly isTrusted: boolean;
|
|
204
|
+
returnValue: boolean;
|
|
205
|
+
readonly srcElement: {
|
|
206
|
+
addEventListener: {
|
|
207
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
|
|
208
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
|
|
209
|
+
};
|
|
210
|
+
dispatchEvent: {
|
|
211
|
+
(event: Event): boolean;
|
|
212
|
+
(event: Event): boolean;
|
|
213
|
+
};
|
|
214
|
+
removeEventListener: {
|
|
215
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
|
|
216
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
|
|
217
|
+
};
|
|
218
|
+
} | null;
|
|
219
|
+
readonly target: {
|
|
220
|
+
addEventListener: {
|
|
221
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
|
|
222
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
|
|
223
|
+
};
|
|
224
|
+
dispatchEvent: {
|
|
225
|
+
(event: Event): boolean;
|
|
226
|
+
(event: Event): boolean;
|
|
227
|
+
};
|
|
228
|
+
removeEventListener: {
|
|
229
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
|
|
230
|
+
(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
|
|
231
|
+
};
|
|
232
|
+
} | null;
|
|
233
|
+
readonly timeStamp: DOMHighResTimeStamp;
|
|
234
|
+
readonly type: string;
|
|
235
|
+
composedPath: {
|
|
236
|
+
(): EventTarget[];
|
|
237
|
+
(): EventTarget[];
|
|
238
|
+
};
|
|
239
|
+
initEvent: {
|
|
240
|
+
(type: string, bubbles?: boolean, cancelable?: boolean): void;
|
|
241
|
+
(type: string, bubbles?: boolean, cancelable?: boolean): void;
|
|
242
|
+
};
|
|
243
|
+
preventDefault: {
|
|
244
|
+
(): void;
|
|
245
|
+
(): void;
|
|
246
|
+
};
|
|
247
|
+
stopImmediatePropagation: {
|
|
248
|
+
(): void;
|
|
249
|
+
(): void;
|
|
250
|
+
};
|
|
251
|
+
stopPropagation: {
|
|
252
|
+
(): void;
|
|
253
|
+
(): void;
|
|
254
|
+
};
|
|
255
|
+
readonly NONE: 0;
|
|
256
|
+
readonly CAPTURING_PHASE: 1;
|
|
257
|
+
readonly AT_TARGET: 2;
|
|
258
|
+
readonly BUBBLING_PHASE: 3;
|
|
259
|
+
} | null>;
|
|
260
|
+
wsLastError: import("vue").ComputedRef<any>;
|
|
261
|
+
connectIdCardWs: () => WebSocket;
|
|
262
|
+
disconnectIdCardWs: (code?: number, reason?: string) => void;
|
|
263
|
+
sendIdCardWs: (message: IdCardWsClientMessage) => void;
|
|
264
|
+
subscribeIdCardWs: (params?: {
|
|
265
|
+
reader?: string | null;
|
|
266
|
+
withPhoto?: boolean;
|
|
267
|
+
}) => void;
|
|
268
|
+
pingIdCardWs: () => void;
|
|
269
|
+
switchReaderIdCardWs: (reader?: string | null) => void;
|
|
270
|
+
unsubscribeIdCardWs: () => void;
|
|
271
|
+
onIdCardWsEvent: (cb: (ev: IdCardWsEvent) => void) => () => boolean;
|
|
272
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { computed, ref, shallowRef, onScopeDispose, getCurrentScope } from "vue";
|
|
2
|
+
import { useApi } from "./api.js";
|
|
3
|
+
function toWsUrl(httpUrl) {
|
|
4
|
+
if (httpUrl.startsWith("https://")) return "wss://" + httpUrl.slice("https://".length);
|
|
5
|
+
if (httpUrl.startsWith("http://")) return "ws://" + httpUrl.slice("http://".length);
|
|
6
|
+
return httpUrl;
|
|
7
|
+
}
|
|
8
|
+
export function useHostAgentWs() {
|
|
9
|
+
const api = useApi();
|
|
10
|
+
const base = ["agent"];
|
|
11
|
+
const ws = shallowRef(null);
|
|
12
|
+
const wsConnected = ref(false);
|
|
13
|
+
const wsSubscribed = ref(false);
|
|
14
|
+
const wsReader = ref(null);
|
|
15
|
+
const wsWithPhoto = ref(false);
|
|
16
|
+
const wsLastMessage = ref(null);
|
|
17
|
+
const wsLastRaw = ref(null);
|
|
18
|
+
const wsLastError = ref(null);
|
|
19
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
20
|
+
const emit = (ev) => {
|
|
21
|
+
for (const cb of subscribers) cb(ev);
|
|
22
|
+
};
|
|
23
|
+
function disconnectIdCardWs(code, reason) {
|
|
24
|
+
if (ws.value) {
|
|
25
|
+
try {
|
|
26
|
+
ws.value.close(code, reason);
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
ws.value = null;
|
|
31
|
+
wsConnected.value = false;
|
|
32
|
+
wsSubscribed.value = false;
|
|
33
|
+
}
|
|
34
|
+
function connectIdCardWs() {
|
|
35
|
+
disconnectIdCardWs();
|
|
36
|
+
const httpUrl = api.urlBuilder([...base, "idcard/ws"]);
|
|
37
|
+
const socket = new WebSocket(toWsUrl(httpUrl));
|
|
38
|
+
ws.value = socket;
|
|
39
|
+
wsConnected.value = false;
|
|
40
|
+
wsSubscribed.value = false;
|
|
41
|
+
wsLastMessage.value = null;
|
|
42
|
+
wsLastRaw.value = null;
|
|
43
|
+
wsLastError.value = null;
|
|
44
|
+
socket.onopen = () => {
|
|
45
|
+
wsConnected.value = true;
|
|
46
|
+
emit({ type: "open" });
|
|
47
|
+
};
|
|
48
|
+
socket.onclose = (e) => {
|
|
49
|
+
wsConnected.value = false;
|
|
50
|
+
wsSubscribed.value = false;
|
|
51
|
+
emit({ type: "close", code: e.code, reason: e.reason });
|
|
52
|
+
};
|
|
53
|
+
socket.onerror = (e) => {
|
|
54
|
+
wsLastError.value = e;
|
|
55
|
+
emit({ type: "error", error: e });
|
|
56
|
+
};
|
|
57
|
+
socket.onmessage = (e) => {
|
|
58
|
+
wsLastRaw.value = e;
|
|
59
|
+
let msg;
|
|
60
|
+
if (typeof e.data === "string") {
|
|
61
|
+
try {
|
|
62
|
+
msg = JSON.parse(e.data);
|
|
63
|
+
} catch {
|
|
64
|
+
msg = { type: "error", message: "invalid json from server", raw: e.data };
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
msg = { type: "error", message: "non-text ws message", raw: e.data };
|
|
68
|
+
}
|
|
69
|
+
wsLastMessage.value = msg;
|
|
70
|
+
switch (msg.type) {
|
|
71
|
+
case "monitor.subscribed":
|
|
72
|
+
wsSubscribed.value = !!msg.ok;
|
|
73
|
+
wsReader.value = msg.reader ?? null;
|
|
74
|
+
wsWithPhoto.value = !!msg.withPhoto;
|
|
75
|
+
break;
|
|
76
|
+
case "monitor.started":
|
|
77
|
+
wsReader.value = msg.reader ?? null;
|
|
78
|
+
break;
|
|
79
|
+
case "monitor.switchAck":
|
|
80
|
+
if (msg.ok) wsReader.value = msg.reader ?? null;
|
|
81
|
+
break;
|
|
82
|
+
case "monitor.switched":
|
|
83
|
+
wsReader.value = msg.reader ?? null;
|
|
84
|
+
break;
|
|
85
|
+
case "monitor.unsubscribed":
|
|
86
|
+
wsSubscribed.value = false;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
emit({ type: "message", data: msg, raw: e });
|
|
90
|
+
};
|
|
91
|
+
return socket;
|
|
92
|
+
}
|
|
93
|
+
function sendIdCardWs(message) {
|
|
94
|
+
if (!ws.value || ws.value.readyState !== WebSocket.OPEN) {
|
|
95
|
+
throw new Error("IDCard WS is not connected");
|
|
96
|
+
}
|
|
97
|
+
ws.value.send(JSON.stringify(message));
|
|
98
|
+
}
|
|
99
|
+
function subscribeIdCardWs(params) {
|
|
100
|
+
const reader = params?.reader ?? null;
|
|
101
|
+
const withPhoto = !!params?.withPhoto;
|
|
102
|
+
wsReader.value = reader;
|
|
103
|
+
wsWithPhoto.value = withPhoto;
|
|
104
|
+
sendIdCardWs({ action: "subscribe", reader, withPhoto });
|
|
105
|
+
}
|
|
106
|
+
function pingIdCardWs() {
|
|
107
|
+
sendIdCardWs({ action: "ping" });
|
|
108
|
+
}
|
|
109
|
+
function switchReaderIdCardWs(reader) {
|
|
110
|
+
sendIdCardWs({ action: "switchReader", reader: reader ?? null });
|
|
111
|
+
}
|
|
112
|
+
function unsubscribeIdCardWs() {
|
|
113
|
+
sendIdCardWs({ action: "unsubscribe" });
|
|
114
|
+
}
|
|
115
|
+
function onIdCardWsEvent(cb) {
|
|
116
|
+
subscribers.add(cb);
|
|
117
|
+
return () => subscribers.delete(cb);
|
|
118
|
+
}
|
|
119
|
+
if (getCurrentScope()) {
|
|
120
|
+
onScopeDispose(() => {
|
|
121
|
+
disconnectIdCardWs();
|
|
122
|
+
subscribers.clear();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
// WS state
|
|
127
|
+
ws,
|
|
128
|
+
wsConnected: computed(() => wsConnected.value),
|
|
129
|
+
wsSubscribed: computed(() => wsSubscribed.value),
|
|
130
|
+
wsReader: computed(() => wsReader.value),
|
|
131
|
+
wsWithPhoto: computed(() => wsWithPhoto.value),
|
|
132
|
+
wsLastMessage: computed(() => wsLastMessage.value),
|
|
133
|
+
wsLastRaw: computed(() => wsLastRaw.value),
|
|
134
|
+
wsLastError: computed(() => wsLastError.value),
|
|
135
|
+
// WS controls
|
|
136
|
+
connectIdCardWs,
|
|
137
|
+
disconnectIdCardWs,
|
|
138
|
+
sendIdCardWs,
|
|
139
|
+
subscribeIdCardWs,
|
|
140
|
+
pingIdCardWs,
|
|
141
|
+
switchReaderIdCardWs,
|
|
142
|
+
unsubscribeIdCardWs,
|
|
143
|
+
onIdCardWsEvent
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
export interface PersistSlimProps {
|
|
3
|
+
/** enable persistence */
|
|
4
|
+
persist?: boolean;
|
|
5
|
+
/** key name in localStorage (required when persist=true) */
|
|
6
|
+
persistKey?: string;
|
|
7
|
+
/** optional prefix namespace */
|
|
8
|
+
persistPrefix?: string;
|
|
9
|
+
/** write debounce in ms */
|
|
10
|
+
persistDebounce?: number;
|
|
11
|
+
/** remove key when model is empty */
|
|
12
|
+
persistClearOnEmpty?: boolean;
|
|
13
|
+
/** TTL in seconds (optional) */
|
|
14
|
+
persistTtl?: number;
|
|
15
|
+
/** slim encryption (optional) */
|
|
16
|
+
persistEncrypt?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* slim secret (required if persistEncrypt=true)
|
|
19
|
+
* (you can also set it globally via ls.config)
|
|
20
|
+
*/
|
|
21
|
+
persistSecret?: string;
|
|
22
|
+
/** if true, hydrate even when model already has value */
|
|
23
|
+
persistAlwaysHydrate?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function useLocalStorageModel<T>(model: Ref<T>, props: PersistSlimProps, options?: {
|
|
26
|
+
/** map model -> stored payload */
|
|
27
|
+
serializer?: (v: T) => any;
|
|
28
|
+
/** map stored payload -> model */
|
|
29
|
+
deserializer?: (raw: any) => T;
|
|
30
|
+
/** custom empty checker */
|
|
31
|
+
isEmpty?: (v: T) => boolean;
|
|
32
|
+
}): {
|
|
33
|
+
isHydrated: Ref<boolean, boolean>;
|
|
34
|
+
storageKey: import("vue").ComputedRef<string>;
|
|
35
|
+
remove: () => void;
|
|
36
|
+
read: () => unknown;
|
|
37
|
+
write: (val: T) => void;
|
|
38
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { computed, onMounted, ref, watch } from "vue";
|
|
2
|
+
import { useDebounceFn } from "@vueuse/core";
|
|
3
|
+
import ls from "localstorage-slim";
|
|
4
|
+
function isEmptyValue(v) {
|
|
5
|
+
if (v == null) return true;
|
|
6
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
export function useLocalStorageModel(model, props, options) {
|
|
10
|
+
const isHydrated = ref(false);
|
|
11
|
+
const storageKey = computed(() => {
|
|
12
|
+
if (!props.persist) return "";
|
|
13
|
+
const key = props.persistKey?.trim();
|
|
14
|
+
if (!key) return "";
|
|
15
|
+
const prefix = (props.persistPrefix ?? "").trim();
|
|
16
|
+
return prefix ? `${prefix}:${key}` : key;
|
|
17
|
+
});
|
|
18
|
+
const isEmpty = options?.isEmpty ?? isEmptyValue;
|
|
19
|
+
const serializer = options?.serializer ?? ((v) => v);
|
|
20
|
+
const deserializer = options?.deserializer ?? ((raw) => raw);
|
|
21
|
+
function read() {
|
|
22
|
+
if (!storageKey.value) return void 0;
|
|
23
|
+
try {
|
|
24
|
+
return ls.get(storageKey.value, {
|
|
25
|
+
decrypt: !!props.persistEncrypt,
|
|
26
|
+
secret: props.persistSecret
|
|
27
|
+
});
|
|
28
|
+
} catch {
|
|
29
|
+
return void 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function write(val) {
|
|
33
|
+
if (!storageKey.value) return;
|
|
34
|
+
try {
|
|
35
|
+
ls.set(storageKey.value, serializer(val), {
|
|
36
|
+
ttl: props.persistTtl,
|
|
37
|
+
// seconds
|
|
38
|
+
encrypt: !!props.persistEncrypt,
|
|
39
|
+
secret: props.persistSecret
|
|
40
|
+
});
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function remove() {
|
|
45
|
+
if (!storageKey.value) return;
|
|
46
|
+
try {
|
|
47
|
+
ls.remove(storageKey.value);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
onMounted(() => {
|
|
52
|
+
if (!props.persist) {
|
|
53
|
+
isHydrated.value = true;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const stored = read();
|
|
57
|
+
const shouldHydrate = props.persistAlwaysHydrate || isEmpty(model.value);
|
|
58
|
+
if (stored !== void 0 && shouldHydrate) {
|
|
59
|
+
try {
|
|
60
|
+
model.value = deserializer(stored);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
isHydrated.value = true;
|
|
65
|
+
});
|
|
66
|
+
const persistNow = () => {
|
|
67
|
+
if (!props.persist || !storageKey.value) return;
|
|
68
|
+
const clearOnEmpty = props.persistClearOnEmpty ?? true;
|
|
69
|
+
const val = model.value;
|
|
70
|
+
if (clearOnEmpty && isEmpty(val)) remove();
|
|
71
|
+
else write(val);
|
|
72
|
+
};
|
|
73
|
+
const persistDebounced = useDebounceFn(persistNow, () => props.persistDebounce ?? 200);
|
|
74
|
+
watch(
|
|
75
|
+
model,
|
|
76
|
+
() => {
|
|
77
|
+
persistDebounced();
|
|
78
|
+
},
|
|
79
|
+
{ deep: true }
|
|
80
|
+
);
|
|
81
|
+
return {
|
|
82
|
+
isHydrated,
|
|
83
|
+
storageKey,
|
|
84
|
+
remove,
|
|
85
|
+
read,
|
|
86
|
+
write
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createClientConfigService } from "../composables/clientConfig.js";
|
|
2
|
+
import { defineNuxtPlugin } from "nuxt/app";
|
|
3
|
+
import { ref } from "vue";
|
|
4
|
+
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
5
|
+
const clientConfigService = createClientConfigService({
|
|
6
|
+
storageKey: "client_config"
|
|
7
|
+
});
|
|
8
|
+
const clientConfig = ref(clientConfigService.get() ?? {});
|
|
9
|
+
nuxtApp.provide("clientConfig", clientConfig);
|
|
10
|
+
nuxtApp.provide("clientConfigRefresh", async () => {
|
|
11
|
+
const loaded = await clientConfigService.load();
|
|
12
|
+
clientConfig.value = loaded ?? {};
|
|
13
|
+
return clientConfig.value;
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
const loaded = await clientConfigService.load();
|
|
17
|
+
clientConfig.value = loaded ?? {};
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.warn("[client-config] init load failed:", e?.message || e);
|
|
20
|
+
clientConfig.value = clientConfigService.get() ?? {};
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from "nuxt/app";
|
|
2
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
3
|
+
nuxtApp.vueApp.directive("default", {
|
|
4
|
+
beforeMount(el, binding) {
|
|
5
|
+
const def = binding.value;
|
|
6
|
+
if (def === void 0 || def === null || def === "") return;
|
|
7
|
+
const apply = (value) => {
|
|
8
|
+
if ("value" in el) {
|
|
9
|
+
;
|
|
10
|
+
el.value = value;
|
|
11
|
+
}
|
|
12
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
13
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
14
|
+
};
|
|
15
|
+
apply(def);
|
|
16
|
+
const inner = el.querySelector?.("input, textarea, select");
|
|
17
|
+
if (inner) {
|
|
18
|
+
const onMountedSync = () => {
|
|
19
|
+
const cur = inner.value;
|
|
20
|
+
if (cur === "" || cur == null) {
|
|
21
|
+
inner.value = String(def);
|
|
22
|
+
inner.dispatchEvent(new Event("input", { bubbles: true }));
|
|
23
|
+
inner.dispatchEvent(new Event("change", { bubbles: true }));
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
queueMicrotask(onMountedSync);
|
|
27
|
+
el.__vDefaultCleanup = () => {
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
// If the default value changes later, you can decide whether to re-apply.
|
|
32
|
+
// This version only applies when the current DOM value is empty.
|
|
33
|
+
updated(el, binding) {
|
|
34
|
+
const def = binding.value;
|
|
35
|
+
if (def === void 0 || def === null || def === "") return;
|
|
36
|
+
const target = "value" in el ? el : el.querySelector?.("input, textarea, select");
|
|
37
|
+
if (!target) return;
|
|
38
|
+
const cur = target.value;
|
|
39
|
+
if (cur === "" || cur === void 0 || cur === null) {
|
|
40
|
+
target.value = def;
|
|
41
|
+
target.dispatchEvent(new Event("input", { bubbles: true }));
|
|
42
|
+
target.dispatchEvent(new Event("change", { bubbles: true }));
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
unmounted(el) {
|
|
46
|
+
el.__vDefaultCleanup?.();
|
|
47
|
+
delete el.__vDefaultCleanup;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ClientConfig,ClientConfigService } from "../composables/clientConfig";
|
|
2
|
+
|
|
3
|
+
declare module "#app" {
|
|
4
|
+
interface NuxtApp {
|
|
5
|
+
$clientConfig: ClientConfig;
|
|
6
|
+
$clientConfigService: ClientConfigService;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface RuntimeNuxtApp {
|
|
10
|
+
$clientConfig: ClientConfig;
|
|
11
|
+
$clientConfigService: ClientConfigService;
|
|
12
|
+
}
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tantee-nuxt-commons",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.175",
|
|
4
4
|
"description": "Ramathibodi Nuxt modules for common components",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"@mdi/font": "^7.4.47",
|
|
71
71
|
"@nuxt/kit": "^3.16.2",
|
|
72
72
|
"@nuxtjs/apollo": "5.0.0-alpha.14",
|
|
73
|
+
"@thumbmarkjs/thumbmarkjs": "^1.6.1",
|
|
73
74
|
"@vue/apollo-composable": "^4.2.2",
|
|
74
75
|
"@vuepic/vue-datepicker": "^7.4.1",
|
|
75
76
|
"@vueuse/integrations": "^11.3.0",
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
"fuse.js": "^7.1.0",
|
|
81
82
|
"gql-query-builder": "^3.8.0",
|
|
82
83
|
"graphql": "^16.10.0",
|
|
83
|
-
"localstorage-
|
|
84
|
+
"localstorage-slim": "^2.7.1",
|
|
84
85
|
"lodash": "^4.17.21",
|
|
85
86
|
"luxon": "^3.6.1",
|
|
86
87
|
"maska": "^2.1.11",
|