@andrey4emk/npm-app-front-b24 0.1.20 → 0.3.0
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/ConfigLogs.vue +402 -0
- package/README.md +139 -12
- package/Subscriptions.vue +976 -0
- package/index.d.ts +39 -0
- package/package.json +12 -22
- package/dist/npm-app-front-b24.es.js +0 -5984
- package/dist/npm-app-front-b24.umd.js +0 -1
- package/dist/style.css +0 -1
package/ConfigLogs.vue
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="config-logs">
|
|
3
|
+
<h2 class="config-logs__title">Уровни логирования</h2>
|
|
4
|
+
|
|
5
|
+
<div v-if="loading" class="config-logs__loading">Загрузка...</div>
|
|
6
|
+
|
|
7
|
+
<div v-else-if="error" class="config-logs__error">
|
|
8
|
+
<p>{{ error }}</p>
|
|
9
|
+
<button @click="loadConfig">Повторить</button>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div v-else>
|
|
13
|
+
<table class="config-logs__table">
|
|
14
|
+
<thead>
|
|
15
|
+
<tr>
|
|
16
|
+
<th>Уровень</th>
|
|
17
|
+
<th>Включён</th>
|
|
18
|
+
<th>JSON-формат</th>
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
<tr v-for="level in levels" :key="level.key">
|
|
23
|
+
<td>
|
|
24
|
+
<span class="config-logs__level-name">{{ level.label }}</span>
|
|
25
|
+
<span class="config-logs__level-key">{{ level.key }}</span>
|
|
26
|
+
</td>
|
|
27
|
+
<td>
|
|
28
|
+
<label class="config-logs__switch">
|
|
29
|
+
<input
|
|
30
|
+
type="checkbox"
|
|
31
|
+
v-model="config[level.key].enabled"
|
|
32
|
+
/>
|
|
33
|
+
<span class="config-logs__slider"></span>
|
|
34
|
+
</label>
|
|
35
|
+
</td>
|
|
36
|
+
<td>
|
|
37
|
+
<label class="config-logs__switch">
|
|
38
|
+
<input
|
|
39
|
+
type="checkbox"
|
|
40
|
+
v-model="config[level.key].json"
|
|
41
|
+
/>
|
|
42
|
+
<span class="config-logs__slider"></span>
|
|
43
|
+
</label>
|
|
44
|
+
</td>
|
|
45
|
+
</tr>
|
|
46
|
+
</tbody>
|
|
47
|
+
</table>
|
|
48
|
+
|
|
49
|
+
<div class="config-logs__actions">
|
|
50
|
+
<button @click="saveConfig" :disabled="loading || saving">
|
|
51
|
+
{{ saving ? "Сохранение..." : "Сохранить" }}
|
|
52
|
+
</button>
|
|
53
|
+
<button @click="loadConfig" :disabled="loading || saving">Обновить</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div v-if="message" :class="['config-logs__message', messageType === 'ok' ? 'config-logs__message--success' : 'config-logs__message--error']">
|
|
57
|
+
{{ message }}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<script setup>
|
|
64
|
+
import { ref, reactive, onMounted } from "vue";
|
|
65
|
+
|
|
66
|
+
const props = defineProps({
|
|
67
|
+
apiBase: {
|
|
68
|
+
type: String,
|
|
69
|
+
default: "",
|
|
70
|
+
},
|
|
71
|
+
secretKey: {
|
|
72
|
+
type: String,
|
|
73
|
+
default: "",
|
|
74
|
+
},
|
|
75
|
+
configName: {
|
|
76
|
+
type: String,
|
|
77
|
+
default: "log",
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const levels = [
|
|
82
|
+
{ key: "trace", label: "Трассировка" },
|
|
83
|
+
{ key: "debug", label: "Отладка" },
|
|
84
|
+
{ key: "info", label: "Информация" },
|
|
85
|
+
{ key: "warn", label: "Предупреждения" },
|
|
86
|
+
{ key: "error", label: "Ошибки" },
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const config = reactive({
|
|
90
|
+
trace: { enabled: false, json: true, color: "" },
|
|
91
|
+
debug: { enabled: true, json: true, color: "" },
|
|
92
|
+
info: { enabled: true, json: true, color: "" },
|
|
93
|
+
warn: { enabled: true, json: true, color: "" },
|
|
94
|
+
error: { enabled: true, json: true, color: "" },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const loading = ref(false);
|
|
98
|
+
const saving = ref(false);
|
|
99
|
+
const error = ref("");
|
|
100
|
+
const message = ref("");
|
|
101
|
+
const messageType = ref("ok");
|
|
102
|
+
|
|
103
|
+
function getHeaders() {
|
|
104
|
+
const headers = { "Content-Type": "application/json" };
|
|
105
|
+
if (props.secretKey) {
|
|
106
|
+
headers.secret = props.secretKey;
|
|
107
|
+
}
|
|
108
|
+
return headers;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getUrl() {
|
|
112
|
+
const base = props.apiBase.replace(/\/+$/, "");
|
|
113
|
+
return `${base}/api/config/${props.configName}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Разбирает тело ответа: JSON — объект, любой другой Content-Type — null.
|
|
117
|
+
// Отдельная функция нужна, потому что сервер на неизвестный маршрут отвечает
|
|
118
|
+
// HTML-страницей с кодом 200, и слепой res.json() падает с невнятной ошибкой
|
|
119
|
+
async function parseJsonBody(res) {
|
|
120
|
+
const contentType = res.headers.get("content-type") || "";
|
|
121
|
+
if (!contentType.includes("application/json")) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
return await res.json();
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Текст ошибки по HTTP-статусу. Если сервер прислал message — он попадает в текст
|
|
132
|
+
// при любом коде, включая 401/403 и 5xx: иначе причина отказа теряется
|
|
133
|
+
function httpErrorText(res, json) {
|
|
134
|
+
if (res.status === 401 || res.status === 403) {
|
|
135
|
+
return json?.message
|
|
136
|
+
? `Нет доступа (${res.status}): ${json.message}`
|
|
137
|
+
: "Нет доступа, проверьте secretKey";
|
|
138
|
+
}
|
|
139
|
+
if (res.status >= 500) {
|
|
140
|
+
return json?.message
|
|
141
|
+
? `Ошибка на сервере (${res.status}): ${json.message}`
|
|
142
|
+
: `Ошибка на сервере (${res.status})`;
|
|
143
|
+
}
|
|
144
|
+
const detail = json?.message ? `: ${json.message}` : "";
|
|
145
|
+
return `Ошибка запроса (${res.status})${detail}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Раскладывает пришедший конфиг по реактивным уровням
|
|
149
|
+
function applyConfig(data) {
|
|
150
|
+
for (const level of levels) {
|
|
151
|
+
if (!data[level.key]) continue;
|
|
152
|
+
config[level.key].enabled = data[level.key].enabled ?? false;
|
|
153
|
+
config[level.key].json = data[level.key].json ?? true;
|
|
154
|
+
config[level.key].color = data[level.key].color ?? "";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function loadConfig() {
|
|
159
|
+
// Пока идёт загрузка или сохранение, повторный запуск запрещён:
|
|
160
|
+
// иначе ответы двух запросов затирают друг друга
|
|
161
|
+
if (loading.value || saving.value) return;
|
|
162
|
+
|
|
163
|
+
loading.value = true;
|
|
164
|
+
error.value = "";
|
|
165
|
+
message.value = "";
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const res = await fetch(getUrl(), { headers: getHeaders() });
|
|
169
|
+
const json = await parseJsonBody(res);
|
|
170
|
+
|
|
171
|
+
if (!res.ok) {
|
|
172
|
+
error.value = httpErrorText(res, json);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!json) {
|
|
176
|
+
error.value = "Неожиданный ответ сервера: тело не JSON. Проверьте apiBase";
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (json.status !== "ok" || !json.data) {
|
|
180
|
+
error.value = json.message || "Не удалось загрузить конфиг";
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
applyConfig(json.data);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
// Сюда попадает только сбой сети: fetch не бросает на 4xx и 5xx
|
|
187
|
+
error.value = `Сервер недоступен: ${e.message}`;
|
|
188
|
+
} finally {
|
|
189
|
+
loading.value = false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function saveConfig() {
|
|
194
|
+
// Пока идёт загрузка или сохранение, повторный запуск запрещён:
|
|
195
|
+
// иначе ответы двух запросов затирают друг друга
|
|
196
|
+
if (loading.value || saving.value) return;
|
|
197
|
+
|
|
198
|
+
saving.value = true;
|
|
199
|
+
message.value = "";
|
|
200
|
+
|
|
201
|
+
const payload = {};
|
|
202
|
+
for (const level of levels) {
|
|
203
|
+
payload[level.key] = {
|
|
204
|
+
enabled: config[level.key].enabled,
|
|
205
|
+
color: config[level.key].color,
|
|
206
|
+
json: config[level.key].json,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const res = await fetch(getUrl(), {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: getHeaders(),
|
|
214
|
+
body: JSON.stringify(payload),
|
|
215
|
+
});
|
|
216
|
+
const json = await parseJsonBody(res);
|
|
217
|
+
|
|
218
|
+
if (!res.ok) {
|
|
219
|
+
message.value = httpErrorText(res, json);
|
|
220
|
+
messageType.value = "error";
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!json) {
|
|
224
|
+
message.value = "Неожиданный ответ сервера: тело не JSON. Проверьте apiBase";
|
|
225
|
+
messageType.value = "error";
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (json.status !== "ok") {
|
|
229
|
+
message.value = json.message || "Ошибка сохранения";
|
|
230
|
+
messageType.value = "error";
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
message.value = json.message || "Конфиг сохранён";
|
|
235
|
+
messageType.value = "ok";
|
|
236
|
+
} catch (e) {
|
|
237
|
+
message.value = `Сервер недоступен: ${e.message}`;
|
|
238
|
+
messageType.value = "error";
|
|
239
|
+
} finally {
|
|
240
|
+
saving.value = false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
onMounted(() => {
|
|
245
|
+
loadConfig();
|
|
246
|
+
});
|
|
247
|
+
</script>
|
|
248
|
+
|
|
249
|
+
<style>
|
|
250
|
+
.config-logs {
|
|
251
|
+
margin: 20px 0;
|
|
252
|
+
padding: 16px;
|
|
253
|
+
border: 1px solid #ddd;
|
|
254
|
+
border-radius: 8px;
|
|
255
|
+
background: #fafafa;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.config-logs__title {
|
|
259
|
+
margin: 0 0 16px 0;
|
|
260
|
+
font-size: 18px;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.config-logs__loading,
|
|
264
|
+
.config-logs__error {
|
|
265
|
+
padding: 12px;
|
|
266
|
+
text-align: center;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.config-logs__error {
|
|
270
|
+
color: #d32f2f;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.config-logs__table {
|
|
274
|
+
width: 100%;
|
|
275
|
+
border-collapse: collapse;
|
|
276
|
+
margin-bottom: 16px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.config-logs__table th,
|
|
280
|
+
.config-logs__table td {
|
|
281
|
+
padding: 10px 12px;
|
|
282
|
+
text-align: left;
|
|
283
|
+
border-bottom: 1px solid #eee;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.config-logs__table th {
|
|
287
|
+
font-weight: 600;
|
|
288
|
+
color: #555;
|
|
289
|
+
font-size: 13px;
|
|
290
|
+
text-transform: uppercase;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.config-logs__level-name {
|
|
294
|
+
display: block;
|
|
295
|
+
font-weight: 500;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.config-logs__level-key {
|
|
299
|
+
display: block;
|
|
300
|
+
font-size: 12px;
|
|
301
|
+
color: #888;
|
|
302
|
+
font-family: monospace;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.config-logs__switch {
|
|
306
|
+
position: relative;
|
|
307
|
+
display: inline-block;
|
|
308
|
+
width: 44px;
|
|
309
|
+
height: 24px;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.config-logs__switch input {
|
|
313
|
+
opacity: 0;
|
|
314
|
+
width: 0;
|
|
315
|
+
height: 0;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.config-logs__slider {
|
|
319
|
+
position: absolute;
|
|
320
|
+
cursor: pointer;
|
|
321
|
+
top: 0;
|
|
322
|
+
left: 0;
|
|
323
|
+
right: 0;
|
|
324
|
+
bottom: 0;
|
|
325
|
+
background-color: #ccc;
|
|
326
|
+
transition: 0.3s;
|
|
327
|
+
border-radius: 24px;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.config-logs__slider::before {
|
|
331
|
+
content: "";
|
|
332
|
+
position: absolute;
|
|
333
|
+
height: 18px;
|
|
334
|
+
width: 18px;
|
|
335
|
+
left: 3px;
|
|
336
|
+
bottom: 3px;
|
|
337
|
+
background-color: #fff;
|
|
338
|
+
transition: 0.3s;
|
|
339
|
+
border-radius: 50%;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.config-logs__switch input:checked + .config-logs__slider {
|
|
343
|
+
background-color: #4caf50;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.config-logs__switch input:checked + .config-logs__slider::before {
|
|
347
|
+
transform: translateX(20px);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.config-logs__actions {
|
|
351
|
+
display: flex;
|
|
352
|
+
gap: 8px;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.config-logs__actions button {
|
|
356
|
+
padding: 8px 20px;
|
|
357
|
+
border: 1px solid #ccc;
|
|
358
|
+
border-radius: 4px;
|
|
359
|
+
background: #fff;
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
font-size: 14px;
|
|
362
|
+
transition: background 0.2s;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.config-logs__actions button:hover:not(:disabled) {
|
|
366
|
+
background: #f0f0f0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.config-logs__actions button:first-child {
|
|
370
|
+
background: #4caf50;
|
|
371
|
+
color: #fff;
|
|
372
|
+
border-color: #4caf50;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.config-logs__actions button:first-child:hover:not(:disabled) {
|
|
376
|
+
background: #43a047;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.config-logs__actions button:disabled {
|
|
380
|
+
opacity: 0.6;
|
|
381
|
+
cursor: not-allowed;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.config-logs__message {
|
|
385
|
+
margin-top: 12px;
|
|
386
|
+
padding: 8px 12px;
|
|
387
|
+
border-radius: 4px;
|
|
388
|
+
font-size: 14px;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.config-logs__message--success {
|
|
392
|
+
background: #e8f5e9;
|
|
393
|
+
color: #2e7d32;
|
|
394
|
+
border: 1px solid #a5d6a7;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.config-logs__message--error {
|
|
398
|
+
background: #ffebee;
|
|
399
|
+
color: #c62828;
|
|
400
|
+
border: 1px solid #ef9a9a;
|
|
401
|
+
}
|
|
402
|
+
</style>
|
package/README.md
CHANGED
|
@@ -1,27 +1,154 @@
|
|
|
1
|
-
|
|
1
|
+
# @andrey4emk/npm-app-front-b24
|
|
2
|
+
|
|
3
|
+
Vue 3 компоненты для Bitrix24.
|
|
4
|
+
|
|
5
|
+
## Установка
|
|
2
6
|
|
|
3
7
|
```bash
|
|
4
8
|
npm install @andrey4emk/npm-app-front-b24
|
|
5
9
|
```
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
## Компоненты
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
### Subscriptions
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
Управление подписками на события и оффлайн-очередью событий Bitrix24: список подписок,
|
|
16
|
+
подписка и отписка, просмотр и очистка оффлайн-сущностей, повторная запись сущности
|
|
17
|
+
с последующим снятием из очереди.
|
|
18
|
+
|
|
19
|
+
```vue
|
|
20
|
+
<script setup>
|
|
21
|
+
import Subscriptions from "@andrey4emk/npm-app-front-b24/Subscriptions.vue";
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<Subscriptions />
|
|
26
|
+
</template>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Props:**
|
|
30
|
+
|
|
31
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
32
|
+
|------|-----|-------------|----------|
|
|
33
|
+
| `b24` | Object | `null` | Клиент Bitrix24 REST API (`B24Frame` из `@bitrix24/b24jssdk`) |
|
|
34
|
+
| `authConnector` | String | `""` | Значение `auth_connector` для методов работы с событиями |
|
|
35
|
+
| `portalDomain` | String | `""` | Адрес портала для ссылок на сущности, `portal.bitrix24.ru` или с `https://` |
|
|
36
|
+
|
|
37
|
+
Клиент ищется в таком порядке: prop `b24`, затем `inject("$b24")` (если приложение сделало
|
|
38
|
+
`app.provide("$b24", client)`), затем глобальный `window.$b24`. Без клиента компонент показывает
|
|
39
|
+
сообщение «Клиент Bitrix24 не инициализирован» и не падает.
|
|
40
|
+
|
|
41
|
+
Адрес портала без `portalDomain` берётся из `client.getTargetOrigin()`, а если его нет, из
|
|
42
|
+
`client.auth.getAuthData().domain`.
|
|
43
|
+
|
|
44
|
+
Состояние вкладок хранится внутри компонента, глобальный миксин с `tabs` и `selectTab` больше
|
|
45
|
+
не нужен.
|
|
46
|
+
|
|
47
|
+
### ConfigLogs
|
|
48
|
+
|
|
49
|
+
Управление уровнями логирования через `config/log.json` на сервере.
|
|
50
|
+
|
|
51
|
+
```vue
|
|
52
|
+
<script setup>
|
|
53
|
+
import ConfigLogs from "@andrey4emk/npm-app-front-b24/ConfigLogs.vue";
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<ConfigLogs api-base="" :secret-key="secretKey" />
|
|
58
|
+
</template>
|
|
14
59
|
```
|
|
15
60
|
|
|
16
|
-
|
|
61
|
+
**Props:**
|
|
62
|
+
|
|
63
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
64
|
+
|------|-----|-------------|----------|
|
|
65
|
+
| `apiBase` | String | `""` | Базовый URL сервера |
|
|
66
|
+
| `secretKey` | String | `""` | Ключ авторизации (заголовок `secret`) |
|
|
67
|
+
| `configName` | String | `"log"` | Имя конфига (без `.json`) |
|
|
68
|
+
|
|
69
|
+
Компонент ходит на `GET` и `POST` `${apiBase}/api/config/${configName}` и ждёт JSON вида
|
|
70
|
+
`{ status: "ok", data: {...} }`. Ошибки сети, ответы 401/403 и 5xx и не-JSON тела показываются
|
|
71
|
+
пользователю с кодом и текстом сервера.
|
|
72
|
+
|
|
73
|
+
## Стили
|
|
74
|
+
|
|
75
|
+
Стили компонентов глобальные, без `scoped`, классы с префиксами `subscriptions__*` и
|
|
76
|
+
`config-logs__*`. Их можно переопределять из CSS приложения по имени класса.
|
|
77
|
+
|
|
78
|
+
В `Subscriptions` старые имена классов `tooltip`, `tooltipParent`, `switch`, `slider` остаются
|
|
79
|
+
на элементах вторым классом ради совместимости со стилями потребителей. Они будут сняты
|
|
80
|
+
в следующей minor-версии.
|
|
81
|
+
|
|
82
|
+
## Типы
|
|
83
|
+
|
|
84
|
+
Пакет содержит `index.d.ts` с объявлениями props обоих компонентов. Чтобы TypeScript
|
|
85
|
+
потребителя (Vite + vue-tsc) их подхватил, подключите объявления явно, иначе сработает общий
|
|
86
|
+
shim для `*.vue` и props станут `any`:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// env.d.ts
|
|
90
|
+
/// <reference types="@andrey4emk/npm-app-front-b24" />
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
или в `tsconfig.json`:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"compilerOptions": {
|
|
98
|
+
"types": ["@andrey4emk/npm-app-front-b24"]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Требования
|
|
104
|
+
|
|
105
|
+
- Vue 3 (`^3.0.0`) — peer dependency
|
|
106
|
+
- Проект-потребитель должен поддерживать компиляцию `.vue` файлов (Vite / webpack + vue-loader)
|
|
107
|
+
|
|
108
|
+
Других зависимостей нет: даты форматируются через `Intl.DateTimeFormat`.
|
|
109
|
+
|
|
110
|
+
## История версий
|
|
111
|
+
|
|
112
|
+
### 0.3.0
|
|
113
|
+
|
|
114
|
+
- `Subscriptions`: состояние вкладок внутри компонента, props `b24`, `authConnector`,
|
|
115
|
+
`portalDomain`, адрес портала из клиента вместо захардкоженного домена, ссылки на
|
|
116
|
+
смарт-процессы
|
|
117
|
+
- `Subscriptions`: починены «Изменить» для смарт-процессов и «Удалить все», автообновление
|
|
118
|
+
останавливается при уходе со страницы, ошибки показываются на экране, подтверждение
|
|
119
|
+
удаления вторым кликом вместо `confirm()`
|
|
120
|
+
- `ConfigLogs`: разбор ответа сервера с проверкой `res.ok` и Content-Type
|
|
121
|
+
- Классы стилей с префиксами, добавлены стили тумблера
|
|
122
|
+
- Снята зависимость `luxon`
|
|
123
|
+
- Добавлен `index.d.ts` с типами props
|
|
124
|
+
|
|
125
|
+
### 0.2.0
|
|
126
|
+
|
|
127
|
+
Пакет поставляет исходные `.vue` файлы вместо скомпилированного бандла. Компиляция
|
|
128
|
+
происходит на стороне проекта-потребителя. При обновлении с `0.1.x` измените импорты:
|
|
17
129
|
|
|
18
130
|
```js
|
|
19
|
-
|
|
131
|
+
// Было (0.1.x)
|
|
20
132
|
import { Subscriptions } from "@andrey4emk/npm-app-front-b24";
|
|
133
|
+
import "@andrey4emk/npm-app-front-b24/style.css";
|
|
134
|
+
|
|
135
|
+
// Стало (0.2.0+)
|
|
136
|
+
import Subscriptions from "@andrey4emk/npm-app-front-b24/Subscriptions.vue";
|
|
21
137
|
```
|
|
22
138
|
|
|
23
|
-
|
|
139
|
+
## Публикация
|
|
140
|
+
|
|
141
|
+
Версия по semver: `patch` для исправлений, `minor` для новых функций, `major` для ломающих
|
|
142
|
+
изменений.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
git add .
|
|
146
|
+
git commit -m "upd(client): описание"
|
|
147
|
+
npm version minor # или patch / major
|
|
148
|
+
git push origin main --follow-tags
|
|
149
|
+
npm publish --access public
|
|
150
|
+
npm info @andrey4emk/npm-app-front-b24 version
|
|
151
|
+
```
|
|
24
152
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- подключение через `<link>` в `index.html`.
|
|
153
|
+
Если `npm publish` отвечает 403: проверить `npm whoami`, при необходимости `npm login`,
|
|
154
|
+
для scoped-пакета флаг `--access public` обязателен.
|