@coopenomics/desktop 2025.11.9-alpha-2 → 2025.11.10-alpha-1
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/package.json +6 -6
- package/src/app/App.vue +17 -5
- package/src/env.d.ts +2 -0
- package/src/features/NotificationPermissionDialog/model/index.ts +6 -2
- package/src/features/WebPushNotifications/model/index.ts +21 -3
- package/src/pages/Union/ConnectionAgreement/ConnectionAgreementPage.vue +67 -131
- package/src/widgets/ConnectionAgreementStepper/Steps/AgreementStep.vue +57 -0
- package/src/widgets/ConnectionAgreementStepper/Steps/FormStep.vue +51 -0
- package/src/widgets/ConnectionAgreementStepper/Steps/IntroStep.vue +62 -0
- package/src/widgets/ConnectionAgreementStepper/Steps/WaitingStep.vue +163 -0
- package/src/widgets/ConnectionAgreementStepper/Steps/index.ts +4 -0
- package/src/widgets/ConnectionAgreementStepper/Tariffs/TariffCard.vue +278 -0
- package/src/widgets/ConnectionAgreementStepper/Tariffs/TariffSelector.vue +95 -0
- package/src/widgets/ConnectionAgreementStepper/Tariffs/index.ts +3 -0
- package/src/widgets/ConnectionAgreementStepper/index.ts +2 -0
- package/src/widgets/ConnectionAgreementStepper/model/index.ts +1 -0
- package/src/widgets/ConnectionAgreementStepper/model/types.ts +13 -0
- package/src/widgets/ConnectionAgreementStepper/ui/ConnectionAgreementStepper.vue +123 -0
- package/src/widgets/ConnectionAgreementStepper/ui/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coopenomics/desktop",
|
|
3
|
-
"version": "2025.11.
|
|
3
|
+
"version": "2025.11.10-alpha-1",
|
|
4
4
|
"description": "A Desktop Project",
|
|
5
5
|
"productName": "Desktop App",
|
|
6
6
|
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"start": "node -r ./alias-resolver.js dist/ssr/index.js"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@coopenomics/controller": "2025.11.
|
|
29
|
-
"@coopenomics/notifications": "2025.11.
|
|
30
|
-
"@coopenomics/sdk": "2025.11.
|
|
28
|
+
"@coopenomics/controller": "2025.11.10-alpha-1",
|
|
29
|
+
"@coopenomics/notifications": "2025.11.10-alpha-1",
|
|
30
|
+
"@coopenomics/sdk": "2025.11.10-alpha-1",
|
|
31
31
|
"@dicebear/collection": "^9.0.1",
|
|
32
32
|
"@dicebear/core": "^9.0.1",
|
|
33
33
|
"@editorjs/code": "^2.9.3",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@wharfkit/wallet-plugin-privatekey": "^1.1.0",
|
|
60
60
|
"axios": "^1.2.1",
|
|
61
61
|
"compression": "^1.7.4",
|
|
62
|
-
"cooptypes": "2025.11.
|
|
62
|
+
"cooptypes": "2025.11.10-alpha-1",
|
|
63
63
|
"dompurify": "^3.1.7",
|
|
64
64
|
"dotenv": "^16.4.5",
|
|
65
65
|
"email-regex": "^5.0.0",
|
|
@@ -123,5 +123,5 @@
|
|
|
123
123
|
"npm": ">= 6.13.4",
|
|
124
124
|
"yarn": ">= 1.21.1"
|
|
125
125
|
},
|
|
126
|
-
"gitHead": "
|
|
126
|
+
"gitHead": "634766a08edc036ccf4b08438b14251fa3a8ab73"
|
|
127
127
|
}
|
package/src/app/App.vue
CHANGED
|
@@ -33,11 +33,23 @@ const { showDialog } = useNotificationPermissionDialog();
|
|
|
33
33
|
onMounted(async () => {
|
|
34
34
|
try {
|
|
35
35
|
console.log('systemInfo', info)
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
// Проверяем, нужно ли корректировать URL для hash роутера
|
|
37
|
+
// Выполняем только в клиентском режиме с hash роутером
|
|
38
|
+
const isClientMode = process.env.CLIENT === 'true';
|
|
39
|
+
const isHashRouter = process.env.VUE_ROUTER_MODE === 'hash';
|
|
40
|
+
const isNotSSR = process.env.SERVER !== 'true';
|
|
41
|
+
const hasWindow = typeof window !== 'undefined';
|
|
42
|
+
|
|
43
|
+
const shouldCorrectHashUrl =
|
|
44
|
+
isClientMode &&
|
|
45
|
+
isHashRouter &&
|
|
46
|
+
isNotSSR &&
|
|
47
|
+
hasWindow &&
|
|
48
|
+
window.location.pathname !== '/' &&
|
|
49
|
+
(!window.location.hash || !window.location.hash.includes(window.location.pathname));
|
|
50
|
+
|
|
51
|
+
if (shouldCorrectHashUrl) {
|
|
52
|
+
console.log('URL needs hash correction for hash router mode');
|
|
41
53
|
const newUrl = window.location.origin + '/#' + window.location.pathname + window.location.search;
|
|
42
54
|
console.log('Redirecting to:', newUrl);
|
|
43
55
|
window.location.replace(newUrl);
|
package/src/env.d.ts
CHANGED
|
@@ -80,14 +80,17 @@ export function useNotificationPermissionDialog() {
|
|
|
80
80
|
const handleAllow = async () => {
|
|
81
81
|
try {
|
|
82
82
|
store.isPermissionDialogProcessing = true;
|
|
83
|
-
|
|
83
|
+
console.log('handleAllow');
|
|
84
84
|
// Подписываемся на уведомления (это включает запрос разрешения)
|
|
85
85
|
const success = await subscribe();
|
|
86
|
-
|
|
86
|
+
console.log('success', success);
|
|
87
87
|
if (success) {
|
|
88
|
+
console.log('saveChoice granted');
|
|
88
89
|
saveChoice('granted');
|
|
90
|
+
console.log('hideDialog');
|
|
89
91
|
hideDialog();
|
|
90
92
|
} else {
|
|
93
|
+
console.log('saveChoice denied');
|
|
91
94
|
// Если подписка не удалась, но пользователь согласился,
|
|
92
95
|
// все равно сохраняем выбор чтобы не показывать диалог снова
|
|
93
96
|
saveChoice('denied');
|
|
@@ -95,6 +98,7 @@ export function useNotificationPermissionDialog() {
|
|
|
95
98
|
}
|
|
96
99
|
} catch (error) {
|
|
97
100
|
console.error('Ошибка при разрешении уведомлений:', error);
|
|
101
|
+
console.log('saveChoice denied');
|
|
98
102
|
saveChoice('denied');
|
|
99
103
|
hideDialog();
|
|
100
104
|
} finally {
|
|
@@ -83,7 +83,16 @@ export function useWebPushNotifications() {
|
|
|
83
83
|
throw new Error('Push уведомления не поддерживаются');
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
// Добавляем timeout на случай, если пользователь не взаимодействует с диалогом
|
|
87
|
+
const permission = await Promise.race([
|
|
88
|
+
Notification.requestPermission(),
|
|
89
|
+
new Promise<never>((_, reject) => {
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
reject(new Error('Пользователь не ответил на запрос разрешения в течение 30 секунд'));
|
|
92
|
+
}, 15000);
|
|
93
|
+
}),
|
|
94
|
+
]);
|
|
95
|
+
|
|
87
96
|
store.support.permission = permission;
|
|
88
97
|
store.support.hasPermission = permission === 'granted';
|
|
89
98
|
store.support.canSubscribe =
|
|
@@ -150,7 +159,16 @@ export function useWebPushNotifications() {
|
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
console.log('Форсированный запрос разрешения...');
|
|
153
|
-
|
|
162
|
+
|
|
163
|
+
// Добавляем timeout на случай, если пользователь не взаимодействует с диалогом
|
|
164
|
+
const permission = await Promise.race([
|
|
165
|
+
Notification.requestPermission(),
|
|
166
|
+
new Promise<never>((_, reject) => {
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
reject(new Error('Пользователь не ответил на запрос разрешения в течение 30 секунд'));
|
|
169
|
+
}, 30000);
|
|
170
|
+
}),
|
|
171
|
+
]);
|
|
154
172
|
|
|
155
173
|
store.support.permission = permission;
|
|
156
174
|
store.support.hasPermission = permission === 'granted';
|
|
@@ -267,7 +285,7 @@ export function useWebPushNotifications() {
|
|
|
267
285
|
// Создаем новую подписку
|
|
268
286
|
const subscription = await registration.pushManager.subscribe({
|
|
269
287
|
userVisibleOnly: true,
|
|
270
|
-
applicationServerKey: applicationServerKey,
|
|
288
|
+
applicationServerKey: applicationServerKey as any,
|
|
271
289
|
});
|
|
272
290
|
|
|
273
291
|
return subscription;
|
|
@@ -1,138 +1,54 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
div.row.
|
|
3
|
-
div.col-md-
|
|
2
|
+
div.row.q-pa-md
|
|
3
|
+
div.col-md-12.col-xs-12
|
|
4
4
|
div(v-if="system.info.is_providered")
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
q-badge(v-if="coop.status == 'active'" color="teal").q-ml-sm активен
|
|
25
|
-
q-badge(v-if="coop.status == 'blocked'" color="red").q-ml-sm заблокирован
|
|
26
|
-
|
|
27
|
-
q-btn(@click="reload" color="primary" size="sm").q-ml-md
|
|
28
|
-
q-icon(name="refresh")
|
|
29
|
-
span обновить
|
|
5
|
+
ConnectionAgreementStepper(
|
|
6
|
+
:initial-step="currentStep"
|
|
7
|
+
:is-finish="is_finish"
|
|
8
|
+
:signed-document="signedDocument"
|
|
9
|
+
:coop="coop"
|
|
10
|
+
:html="html"
|
|
11
|
+
:domain-valid="domainValid"
|
|
12
|
+
:installation-progress="installationProgress"
|
|
13
|
+
:instance-status="instanceStatus"
|
|
14
|
+
:subscriptions-loading="subscriptionsLoading"
|
|
15
|
+
:subscriptions-error="subscriptionsError"
|
|
16
|
+
@step-change="handleStepChange"
|
|
17
|
+
@tariff-selected="handleTariffSelected"
|
|
18
|
+
@tariff-deselected="handleTariffDeselected"
|
|
19
|
+
@continue="handleContinue"
|
|
20
|
+
@sign="sign"
|
|
21
|
+
@finish="finish"
|
|
22
|
+
@reload="reload"
|
|
23
|
+
)
|
|
30
24
|
|
|
31
|
-
div(v-else)
|
|
25
|
+
div(v-else).row
|
|
32
26
|
//- Заглушка для недоступного провайдера
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
size="sm"
|
|
46
|
-
).q-ml-md
|
|
47
|
-
|
|
48
|
-
div(v-if="system.info.is_providered").q-mt-md
|
|
49
|
-
p.text-subtitle1 Статус подписки на хостинг:
|
|
50
|
-
|
|
51
|
-
//- Статус подписки (только если провайдер доступен)
|
|
52
|
-
div
|
|
53
|
-
div(
|
|
54
|
-
v-if="subscriptionsError"
|
|
55
|
-
).q-mb-md
|
|
56
|
-
q-banner(
|
|
57
|
-
:class="'text-white bg-red-500'"
|
|
58
|
-
rounded
|
|
59
|
-
)
|
|
60
|
-
template(v-slot:avatar)
|
|
61
|
-
q-icon(name="error" color="white")
|
|
62
|
-
span {{ subscriptionsError }}
|
|
63
|
-
|
|
64
|
-
div.flex.items-center.q-gutter-sm
|
|
65
|
-
div
|
|
66
|
-
span.text-body2 Валидность домена:
|
|
67
|
-
q-badge(
|
|
68
|
-
v-if="domainValid === true"
|
|
69
|
-
color="green"
|
|
70
|
-
).q-ml-sm валиден
|
|
71
|
-
q-badge(
|
|
72
|
-
v-if="domainValid === false"
|
|
73
|
-
color="red"
|
|
74
|
-
).q-ml-sm не валиден
|
|
75
|
-
q-badge(
|
|
76
|
-
v-if="domainValid === null && !subscriptionsLoading"
|
|
77
|
-
color="grey"
|
|
78
|
-
).q-ml-sm неизвестно
|
|
79
|
-
q-badge(
|
|
80
|
-
v-if="subscriptionsLoading"
|
|
81
|
-
color="blue"
|
|
82
|
-
).q-ml-sm загрузка...
|
|
83
|
-
|
|
84
|
-
div
|
|
85
|
-
span.text-body2 Прогресс установки:
|
|
86
|
-
q-badge(
|
|
87
|
-
v-if="installationProgress !== null"
|
|
88
|
-
:color="installationProgress === 100 ? 'green' : 'orange'"
|
|
89
|
-
).q-ml-sm {{ installationProgress }}%
|
|
90
|
-
q-badge(
|
|
91
|
-
v-if="installationProgress === null && !subscriptionsLoading"
|
|
92
|
-
color="grey"
|
|
93
|
-
).q-ml-sm неизвестно
|
|
94
|
-
q-badge(
|
|
95
|
-
v-if="subscriptionsLoading"
|
|
96
|
-
color="blue"
|
|
97
|
-
).q-ml-sm загрузка...
|
|
98
|
-
|
|
99
|
-
div
|
|
100
|
-
span.text-body2 Статус сервера:
|
|
101
|
-
q-badge(
|
|
102
|
-
v-if="instanceStatus"
|
|
103
|
-
:color="instanceStatus === 'active' ? 'green' : instanceStatus === 'error' ? 'red' : 'orange'"
|
|
104
|
-
).q-ml-sm {{ instanceStatus }}
|
|
105
|
-
q-badge(
|
|
106
|
-
v-if="!instanceStatus && !subscriptionsLoading"
|
|
107
|
-
color="grey"
|
|
108
|
-
).q-ml-sm неизвестно
|
|
109
|
-
q-badge(
|
|
110
|
-
v-if="subscriptionsLoading"
|
|
111
|
-
color="blue"
|
|
112
|
-
).q-ml-sm загрузка...
|
|
113
|
-
|
|
114
|
-
p Пожалуйста, перешлите инструкцию ниже вашему техническому специалисту. После её выполнения, мы автоматически выполним запуск. Далее, Вам необходимо завершить установку уже на Вашем сайте следуя инструкциям, представленным там.
|
|
115
|
-
|
|
116
|
-
q-card(flat bordered).q-pa-sm
|
|
117
|
-
p.text-bold Инструкция
|
|
118
|
-
div.flex.justify-between
|
|
119
|
-
span {{instruction}}
|
|
120
|
-
q-btn(size="sm" icon="fas fa-copy" flat @click="copy")
|
|
27
|
+
div.col-md-12.col-xs-12
|
|
28
|
+
ColorCard(color="blue")
|
|
29
|
+
.text-center.q-pa-md
|
|
30
|
+
q-icon(name="fas fa-info-circle" size="2rem").q-mb-sm
|
|
31
|
+
.text-h6.q-mb-md Информация о подключении
|
|
32
|
+
p Для подключения к платформе Кооперативной Экономики обратитесь в ПК ВОСХОД.
|
|
33
|
+
q-btn(
|
|
34
|
+
color="primary"
|
|
35
|
+
label="Перейти на сайт"
|
|
36
|
+
@click="openProviderWebsite"
|
|
37
|
+
size="md"
|
|
38
|
+
).q-mt-md
|
|
121
39
|
|
|
122
40
|
</template>
|
|
123
41
|
<script setup lang="ts">
|
|
124
42
|
import { DigitalDocument } from 'src/shared/lib/document';
|
|
125
43
|
import { useSessionStore } from 'src/entities/Session';
|
|
126
44
|
import { useSystemStore } from 'src/entities/System/model';
|
|
127
|
-
import { DocumentHtmlReader } from 'src/shared/ui/DocumentHtmlReader';
|
|
128
45
|
import { computed, ref, onMounted, onUnmounted } from 'vue';
|
|
129
|
-
import { Loader } from 'src/shared/ui/Loader';
|
|
130
|
-
import { AddCooperativeForm } from 'src/features/Union/AddCooperative';
|
|
131
46
|
import { useLoadCooperatives } from 'src/features/Union/LoadCooperatives';
|
|
132
47
|
import { useProviderSubscriptions } from 'src/features/Provider';
|
|
133
|
-
import { copyToClipboard } from 'quasar';
|
|
134
|
-
import { SuccessAlert } from 'src/shared/api';
|
|
135
48
|
import { Cooperative } from 'cooptypes';
|
|
49
|
+
import { ConnectionAgreementStepper } from 'src/widgets/ConnectionAgreementStepper';
|
|
50
|
+
import { ColorCard } from 'src/shared/ui';
|
|
51
|
+
|
|
136
52
|
|
|
137
53
|
const session = useSessionStore()
|
|
138
54
|
const system = useSystemStore()
|
|
@@ -148,23 +64,40 @@ const {
|
|
|
148
64
|
} = useProviderSubscriptions()
|
|
149
65
|
|
|
150
66
|
const coop = ref()
|
|
151
|
-
const instruction = computed(() => `Создайте A-запись домена ${coop.value?.announce} на IP-адрес: 51.250.114.13`)
|
|
152
67
|
|
|
153
68
|
const html = computed(() => document.value?.data?.html)
|
|
154
69
|
const signedDocument = computed(() => document.value?.signedDocument)
|
|
155
70
|
const is_finish = ref(false)
|
|
156
71
|
|
|
72
|
+
// Управление шагом степпера
|
|
73
|
+
const currentStep = ref(1)
|
|
74
|
+
|
|
75
|
+
const handleStepChange = (step: number) => {
|
|
76
|
+
currentStep.value = step
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const handleTariffSelected = (tariff: any) => {
|
|
80
|
+
// Здесь можно сохранить выбранный тариф
|
|
81
|
+
console.log('Selected tariff:', tariff)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const handleTariffDeselected = () => {
|
|
85
|
+
// Здесь можно обработать снятие выбора тарифа
|
|
86
|
+
console.log('Tariff deselected')
|
|
87
|
+
}
|
|
88
|
+
|
|
157
89
|
// Остановка автообновления при размонтировании компонента
|
|
158
90
|
let stopRefresh: (() => void) | null = null
|
|
159
91
|
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
92
|
+
const handleContinue = async () => {
|
|
93
|
+
// Если документ еще не сгенерирован, генерируем его
|
|
94
|
+
if (!document.value.data?.html && !coop.value) {
|
|
95
|
+
await document.value.generate({
|
|
96
|
+
registry_id: Cooperative.Registry.CoopenomicsAgreement.registry_id,
|
|
97
|
+
coopname: 'voskhod',
|
|
98
|
+
username: session.username,
|
|
167
99
|
})
|
|
100
|
+
}
|
|
168
101
|
}
|
|
169
102
|
|
|
170
103
|
const openProviderWebsite = () => {
|
|
@@ -199,13 +132,16 @@ const init = async () => {
|
|
|
199
132
|
|
|
200
133
|
coop.value = await loadOneCooperative(session.username)
|
|
201
134
|
|
|
202
|
-
if (!coop.value)
|
|
135
|
+
if (!coop.value) {
|
|
203
136
|
await document.value.generate({
|
|
204
137
|
registry_id: Cooperative.Registry.CoopenomicsAgreement.registry_id,
|
|
205
138
|
coopname: 'voskhod',
|
|
206
139
|
username: session.username,
|
|
207
140
|
})
|
|
208
|
-
else
|
|
141
|
+
} else {
|
|
142
|
+
is_finish.value = true
|
|
143
|
+
currentStep.value = 4 // Переходим на последний шаг если кооператив уже создан
|
|
144
|
+
}
|
|
209
145
|
}
|
|
210
146
|
|
|
211
147
|
const sign = async() => {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, withDefaults } from 'vue'
|
|
3
|
+
import type { IStepProps } from '../model/types'
|
|
4
|
+
import { DocumentHtmlReader } from 'src/shared/ui/DocumentHtmlReader'
|
|
5
|
+
import { Loader } from 'src/shared/ui/Loader'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<IStepProps & {
|
|
8
|
+
html?: string
|
|
9
|
+
onSign?: () => void
|
|
10
|
+
onBack?: () => void
|
|
11
|
+
}>(), {})
|
|
12
|
+
|
|
13
|
+
const emits = defineEmits<{
|
|
14
|
+
back: []
|
|
15
|
+
sign: []
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const isActive = computed(() => props.isActive)
|
|
19
|
+
const isDone = computed(() => props.isDone)
|
|
20
|
+
|
|
21
|
+
const handleSign = () => {
|
|
22
|
+
emits('sign')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const handleBack = () => {
|
|
26
|
+
emits('back')
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template lang="pug">
|
|
31
|
+
q-step(
|
|
32
|
+
:name="2"
|
|
33
|
+
title="Соглашение о подключении"
|
|
34
|
+
icon="description"
|
|
35
|
+
:done="isDone"
|
|
36
|
+
)
|
|
37
|
+
.q-pa-md
|
|
38
|
+
template(v-if="html")
|
|
39
|
+
DocumentHtmlReader(:html="html")
|
|
40
|
+
template(v-else)
|
|
41
|
+
Loader(:text='`Готовим соглашение...`')
|
|
42
|
+
|
|
43
|
+
q-stepper-navigation.q-gutter-sm(v-if="html")
|
|
44
|
+
q-btn(
|
|
45
|
+
v-if="isActive"
|
|
46
|
+
color="grey-6"
|
|
47
|
+
flat
|
|
48
|
+
label="Назад"
|
|
49
|
+
@click="handleBack"
|
|
50
|
+
)
|
|
51
|
+
q-btn(
|
|
52
|
+
v-if="isActive"
|
|
53
|
+
color="primary"
|
|
54
|
+
label="Подписать"
|
|
55
|
+
@click="handleSign"
|
|
56
|
+
)
|
|
57
|
+
</template>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { withDefaults } from 'vue'
|
|
3
|
+
import type { IStepProps } from '../model/types'
|
|
4
|
+
import { AddCooperativeForm } from 'src/features/Union/AddCooperative'
|
|
5
|
+
|
|
6
|
+
withDefaults(defineProps<IStepProps & {
|
|
7
|
+
signedDocument?: any
|
|
8
|
+
onFinish?: () => void
|
|
9
|
+
onBack?: () => void
|
|
10
|
+
}>(), {})
|
|
11
|
+
|
|
12
|
+
const emits = defineEmits<{
|
|
13
|
+
back: []
|
|
14
|
+
finish: []
|
|
15
|
+
}>()
|
|
16
|
+
|
|
17
|
+
const handleFinish = () => {
|
|
18
|
+
emits('finish')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const handleBack = () => {
|
|
22
|
+
emits('back')
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template lang="pug">
|
|
27
|
+
q-step(
|
|
28
|
+
:name="3"
|
|
29
|
+
title="Настройка кооператива"
|
|
30
|
+
icon="settings"
|
|
31
|
+
:done="isDone"
|
|
32
|
+
)
|
|
33
|
+
.q-pa-md
|
|
34
|
+
p.text-h6.q-mb-md Предварительная настройка
|
|
35
|
+
p.q-mb-md
|
|
36
|
+
| Пожалуйста, укажите домен для установки Цифрового Кооператива. Также, укажите суммы вступительных и минимальных паевых взносов для физических лиц, юридических лиц и индивидуальных предпринимателей:
|
|
37
|
+
|
|
38
|
+
AddCooperativeForm(
|
|
39
|
+
v-if="signedDocument"
|
|
40
|
+
:document="signedDocument"
|
|
41
|
+
@finish="handleFinish"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
q-stepper-navigation.q-gutter-sm(v-if="signedDocument")
|
|
45
|
+
q-btn(
|
|
46
|
+
color="grey-6"
|
|
47
|
+
flat
|
|
48
|
+
label="Назад"
|
|
49
|
+
@click="handleBack"
|
|
50
|
+
)
|
|
51
|
+
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, withDefaults, ref } from 'vue'
|
|
3
|
+
import type { IStepProps } from '../model/types'
|
|
4
|
+
import { TariffSelector, type ITariff } from '../Tariffs'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<IStepProps & {
|
|
7
|
+
onContinue?: () => void
|
|
8
|
+
}>(), {})
|
|
9
|
+
|
|
10
|
+
const emits = defineEmits<{
|
|
11
|
+
tariffSelected: [tariff: ITariff]
|
|
12
|
+
tariffDeselected: []
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const isActive = computed(() => props.isActive)
|
|
16
|
+
const isDone = computed(() => props.isDone)
|
|
17
|
+
|
|
18
|
+
const selectedTariff = ref<ITariff | null>(null)
|
|
19
|
+
|
|
20
|
+
const canContinue = computed(() => selectedTariff.value !== null)
|
|
21
|
+
|
|
22
|
+
const handleTariffSelected = (tariff: ITariff) => {
|
|
23
|
+
selectedTariff.value = tariff
|
|
24
|
+
emits('tariffSelected', tariff)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const handleTariffDeselected = () => {
|
|
28
|
+
selectedTariff.value = null
|
|
29
|
+
emits('tariffDeselected')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const handleContinue = () => {
|
|
33
|
+
if (canContinue.value && props.onContinue) {
|
|
34
|
+
props.onContinue()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template lang="pug">
|
|
40
|
+
q-step(
|
|
41
|
+
:name="1"
|
|
42
|
+
title="Выберите тариф"
|
|
43
|
+
icon="info"
|
|
44
|
+
:done="isDone"
|
|
45
|
+
)
|
|
46
|
+
.q-pa-md
|
|
47
|
+
|
|
48
|
+
TariffSelector(
|
|
49
|
+
:disabled="!isActive"
|
|
50
|
+
@tariff-selected="handleTariffSelected"
|
|
51
|
+
@tariff-deselected="handleTariffDeselected"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
q-stepper-navigation.q-gutter-sm
|
|
55
|
+
q-btn(
|
|
56
|
+
v-if="isActive"
|
|
57
|
+
color="primary"
|
|
58
|
+
:disable="!canContinue"
|
|
59
|
+
label="Продолжить"
|
|
60
|
+
@click="handleContinue"
|
|
61
|
+
)
|
|
62
|
+
</template>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, withDefaults } from 'vue'
|
|
3
|
+
import type { IStepProps } from '../model/types'
|
|
4
|
+
import { copyToClipboard } from 'quasar'
|
|
5
|
+
import { SuccessAlert } from 'src/shared/api'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<IStepProps & {
|
|
8
|
+
coop?: any
|
|
9
|
+
domainValid?: boolean | null
|
|
10
|
+
installationProgress?: number | null
|
|
11
|
+
instanceStatus?: string | null
|
|
12
|
+
subscriptionsLoading?: boolean
|
|
13
|
+
subscriptionsError?: string | null
|
|
14
|
+
onReload?: () => void
|
|
15
|
+
onBack?: () => void
|
|
16
|
+
}>(), {
|
|
17
|
+
domainValid: null,
|
|
18
|
+
installationProgress: null,
|
|
19
|
+
instanceStatus: null,
|
|
20
|
+
subscriptionsLoading: false,
|
|
21
|
+
subscriptionsError: null
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const emits = defineEmits<{
|
|
25
|
+
back: []
|
|
26
|
+
reload: []
|
|
27
|
+
}>()
|
|
28
|
+
|
|
29
|
+
const isDone = computed(() => props.isDone)
|
|
30
|
+
|
|
31
|
+
const handleBack = () => {
|
|
32
|
+
emits('back')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleReload = () => {
|
|
36
|
+
emits('reload')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const instruction = computed(() => `Создайте A-запись домена ${props.coop?.announce} на IP-адрес: 51.250.114.13`)
|
|
40
|
+
|
|
41
|
+
const copy = () => {
|
|
42
|
+
copyToClipboard(instruction.value)
|
|
43
|
+
.then(() => {
|
|
44
|
+
SuccessAlert('Инструкция скопирована в буфер')
|
|
45
|
+
})
|
|
46
|
+
.catch((e) => {
|
|
47
|
+
console.log(e)
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template lang="pug">
|
|
53
|
+
q-step(
|
|
54
|
+
:name="4"
|
|
55
|
+
title="Установка кооператива"
|
|
56
|
+
icon="hourglass_top"
|
|
57
|
+
:done="isDone"
|
|
58
|
+
)
|
|
59
|
+
.q-pa-md
|
|
60
|
+
p.text-h6.q-mb-md Кооператив на подключении
|
|
61
|
+
p.q-mb-md Статус:
|
|
62
|
+
q-badge(
|
|
63
|
+
v-if="coop?.status == 'pending'"
|
|
64
|
+
color="orange"
|
|
65
|
+
).q-ml-sm ожидание
|
|
66
|
+
q-badge(
|
|
67
|
+
v-if="coop?.status == 'active'"
|
|
68
|
+
color="teal"
|
|
69
|
+
).q-ml-sm активен
|
|
70
|
+
q-badge(
|
|
71
|
+
v-if="coop?.status == 'blocked'"
|
|
72
|
+
color="red"
|
|
73
|
+
).q-ml-sm заблокирован
|
|
74
|
+
|
|
75
|
+
q-btn(
|
|
76
|
+
color="grey-6"
|
|
77
|
+
size="sm"
|
|
78
|
+
flat
|
|
79
|
+
label="Назад"
|
|
80
|
+
@click="handleBack"
|
|
81
|
+
)
|
|
82
|
+
q-btn(
|
|
83
|
+
color="primary"
|
|
84
|
+
size="sm"
|
|
85
|
+
icon="refresh"
|
|
86
|
+
@click="handleReload"
|
|
87
|
+
).q-ml-md
|
|
88
|
+
span обновить
|
|
89
|
+
|
|
90
|
+
.q-mt-md
|
|
91
|
+
p.text-subtitle1 Статус подписки на хостинг:
|
|
92
|
+
|
|
93
|
+
div.flex.items-center.q-gutter-sm.q-mt-sm
|
|
94
|
+
div
|
|
95
|
+
span.text-body2 Валидность домена:
|
|
96
|
+
q-badge(
|
|
97
|
+
v-if="domainValid === true"
|
|
98
|
+
color="green"
|
|
99
|
+
).q-ml-sm валиден
|
|
100
|
+
q-badge(
|
|
101
|
+
v-if="domainValid === false"
|
|
102
|
+
color="red"
|
|
103
|
+
).q-ml-sm не валиден
|
|
104
|
+
q-badge(
|
|
105
|
+
v-if="domainValid === null && !subscriptionsLoading"
|
|
106
|
+
color="grey"
|
|
107
|
+
).q-ml-sm неизвестно
|
|
108
|
+
q-badge(
|
|
109
|
+
v-if="subscriptionsLoading"
|
|
110
|
+
color="blue"
|
|
111
|
+
).q-ml-sm загрузка...
|
|
112
|
+
|
|
113
|
+
div
|
|
114
|
+
span.text-body2 Прогресс установки:
|
|
115
|
+
q-badge(
|
|
116
|
+
v-if="installationProgress !== null"
|
|
117
|
+
:color="installationProgress === 100 ? 'green' : 'orange'"
|
|
118
|
+
).q-ml-sm {{ installationProgress }}%
|
|
119
|
+
q-badge(
|
|
120
|
+
v-if="installationProgress === null && !subscriptionsLoading"
|
|
121
|
+
color="grey"
|
|
122
|
+
).q-ml-sm неизвестно
|
|
123
|
+
q-badge(
|
|
124
|
+
v-if="subscriptionsLoading"
|
|
125
|
+
color="blue"
|
|
126
|
+
).q-ml-sm загрузка...
|
|
127
|
+
|
|
128
|
+
div
|
|
129
|
+
span.text-body2 Статус сервера:
|
|
130
|
+
q-badge(
|
|
131
|
+
v-if="instanceStatus"
|
|
132
|
+
:color="instanceStatus === 'active' ? 'green' : instanceStatus === 'error' ? 'red' : 'orange'"
|
|
133
|
+
).q-ml-sm {{ instanceStatus }}
|
|
134
|
+
q-badge(
|
|
135
|
+
v-if="!instanceStatus && !subscriptionsLoading"
|
|
136
|
+
color="grey"
|
|
137
|
+
).q-ml-sm неизвестно
|
|
138
|
+
q-badge(
|
|
139
|
+
v-if="subscriptionsLoading"
|
|
140
|
+
color="blue"
|
|
141
|
+
).q-ml-sm загрузка...
|
|
142
|
+
|
|
143
|
+
.q-mt-md
|
|
144
|
+
div(
|
|
145
|
+
v-if="subscriptionsError"
|
|
146
|
+
).q-mb-md
|
|
147
|
+
q-banner(
|
|
148
|
+
:class="'text-white bg-red-500'"
|
|
149
|
+
rounded
|
|
150
|
+
)
|
|
151
|
+
template(v-slot:avatar)
|
|
152
|
+
q-icon(name="error" color="white")
|
|
153
|
+
span {{ subscriptionsError }}
|
|
154
|
+
|
|
155
|
+
p.q-mt-md
|
|
156
|
+
| Пожалуйста, перешлите инструкцию ниже вашему техническому специалисту. После её выполнения, мы автоматически выполним запуск. Далее, Вам необходимо завершить установку уже на Вашем сайте следуя инструкциям, представленным там.
|
|
157
|
+
|
|
158
|
+
q-card(flat bordered).q-pa-sm.q-mt-md
|
|
159
|
+
p.text-bold Инструкция
|
|
160
|
+
div.flex.justify-between
|
|
161
|
+
span {{ instruction }}
|
|
162
|
+
q-btn(size="sm" icon="fas fa-copy" flat @click="copy")
|
|
163
|
+
</template>
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
export interface ITariff {
|
|
5
|
+
id: string
|
|
6
|
+
name: string
|
|
7
|
+
description: string
|
|
8
|
+
features: string[]
|
|
9
|
+
price: string
|
|
10
|
+
additionalCosts?: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
tariff: ITariff
|
|
15
|
+
selected?: boolean
|
|
16
|
+
disabled?: boolean
|
|
17
|
+
}>()
|
|
18
|
+
|
|
19
|
+
const emits = defineEmits<{
|
|
20
|
+
select: [tariffId: string]
|
|
21
|
+
deselect: [tariffId: string]
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
const isSelected = computed(() => props.selected)
|
|
25
|
+
const isDisabled = computed(() => props.disabled)
|
|
26
|
+
|
|
27
|
+
const cardClasses = computed(() => {
|
|
28
|
+
const classes = ['tariff-card', 'cursor-pointer', 'transition-all', 'duration-200']
|
|
29
|
+
|
|
30
|
+
if (isSelected.value) {
|
|
31
|
+
classes.push('tariff-selected')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (isDisabled.value) {
|
|
35
|
+
classes.push('tariff-disabled')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return classes.join(' ')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const handleSelect = () => {
|
|
42
|
+
if (!isDisabled.value) {
|
|
43
|
+
// Если тариф уже выбран, снимаем выбор, иначе выбираем
|
|
44
|
+
if (isSelected.value) {
|
|
45
|
+
emits('deselect', props.tariff.id)
|
|
46
|
+
} else {
|
|
47
|
+
emits('select', props.tariff.id)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template lang="pug">
|
|
54
|
+
div.tariff-card-container
|
|
55
|
+
.tariff-card(
|
|
56
|
+
:class="cardClasses"
|
|
57
|
+
@click="handleSelect"
|
|
58
|
+
)
|
|
59
|
+
.tariff-header
|
|
60
|
+
.tariff-checkmark(v-if="isSelected")
|
|
61
|
+
q-icon(name="check_circle" size="24px" color="white")
|
|
62
|
+
.tariff-title
|
|
63
|
+
h6.text-h6 {{ tariff.name }}
|
|
64
|
+
|
|
65
|
+
.tariff-content
|
|
66
|
+
p.text-body2.text-grey-7.q-mb-md.text-center {{ tariff.description }}
|
|
67
|
+
|
|
68
|
+
.tariff-features
|
|
69
|
+
.feature-item(v-for="feature in tariff.features" :key="feature")
|
|
70
|
+
q-icon(name="check" size="16px" color="positive").q-mr-xs
|
|
71
|
+
span {{ feature }}
|
|
72
|
+
|
|
73
|
+
template(v-if="tariff.additionalCosts && tariff.additionalCosts.length")
|
|
74
|
+
.tariff-additional.q-mt-md
|
|
75
|
+
p.text-subtitle2.text-grey-8 Дополнительные расходы:
|
|
76
|
+
.additional-item(v-for="cost in tariff.additionalCosts" :key="cost")
|
|
77
|
+
q-icon(name="add_circle_outline" size="14px" color="info").q-mr-xs
|
|
78
|
+
span.text-caption {{ cost }}
|
|
79
|
+
|
|
80
|
+
.tariff-footer
|
|
81
|
+
.tariff-price
|
|
82
|
+
.price-display {{ tariff.price }}
|
|
83
|
+
.price-period(v-if="tariff.price !== 'Бесплатно'") в месяц
|
|
84
|
+
.select-hint.text-caption.text-grey-6
|
|
85
|
+
| Нажмите для выбора
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<style scoped>
|
|
89
|
+
.tariff-card-container {
|
|
90
|
+
position: relative;
|
|
91
|
+
height: 100%;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.tariff-card {
|
|
95
|
+
position: relative;
|
|
96
|
+
height: 100%;
|
|
97
|
+
min-height: 360px; /* Уменьшенная минимальная высота */
|
|
98
|
+
padding: 24px;
|
|
99
|
+
border-radius: 16px;
|
|
100
|
+
background: white;
|
|
101
|
+
border: 2px solid #f0f0f0;
|
|
102
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.tariff-card:hover:not(.tariff-disabled):not(.tariff-selected) {
|
|
111
|
+
transform: translateY(-4px);
|
|
112
|
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
|
113
|
+
border-color: #e0e0e0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.tariff-selected {
|
|
117
|
+
border-color: var(--q-accent);
|
|
118
|
+
background: linear-gradient(135deg, rgba(25, 118, 210, 0.05) 0%, rgba(25, 118, 210, 0.02) 100%);
|
|
119
|
+
box-shadow:
|
|
120
|
+
0 8px 32px rgba(25, 118, 210, 0.2),
|
|
121
|
+
0 0 0 1px var(--q-accent);
|
|
122
|
+
transform: translateY(-2px);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.tariff-disabled {
|
|
126
|
+
opacity: 0.6;
|
|
127
|
+
cursor: not-allowed !important;
|
|
128
|
+
pointer-events: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.tariff-header {
|
|
132
|
+
position: relative;
|
|
133
|
+
height: 40px; /* Фиксированная высота для предотвращения прыжков */
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: center;
|
|
137
|
+
margin-bottom: 16px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.tariff-checkmark {
|
|
141
|
+
position: absolute;
|
|
142
|
+
top: 50%;
|
|
143
|
+
right: 0;
|
|
144
|
+
transform: translateY(-50%) scale(0.8);
|
|
145
|
+
background: var(--q-accent);
|
|
146
|
+
border-radius: 50%;
|
|
147
|
+
padding: 4px;
|
|
148
|
+
box-shadow: 0 2px 8px rgba(25, 118, 210, 0.3);
|
|
149
|
+
opacity: 0;
|
|
150
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.tariff-selected .tariff-checkmark {
|
|
154
|
+
opacity: 1;
|
|
155
|
+
transform: translateY(-50%) scale(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.tariff-title {
|
|
159
|
+
text-align: center;
|
|
160
|
+
margin-right: 36px; /* Отступ для иконки */
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.tariff-content {
|
|
164
|
+
flex: 1;
|
|
165
|
+
display: flex;
|
|
166
|
+
flex-direction: column;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.tariff-features {
|
|
170
|
+
flex: 1;
|
|
171
|
+
margin-bottom: 16px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.feature-item {
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
margin-bottom: 8px;
|
|
178
|
+
padding: 4px 0;
|
|
179
|
+
transition: color 0.2s ease;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.feature-item:hover {
|
|
183
|
+
color: var(--q-primary);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.tariff-additional {
|
|
187
|
+
border-top: 1px solid #f5f5f5;
|
|
188
|
+
padding-top: 16px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.additional-item {
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
margin-bottom: 6px;
|
|
195
|
+
color: #666;
|
|
196
|
+
font-size: 12px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.tariff-footer {
|
|
200
|
+
position: relative;
|
|
201
|
+
height: 80px; /* Увеличенная высота для богатого оформления */
|
|
202
|
+
margin-top: auto;
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
justify-content: flex-end;
|
|
206
|
+
padding: 20px 32px 20px 12px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.tariff-price {
|
|
210
|
+
display: flex;
|
|
211
|
+
flex-direction: column;
|
|
212
|
+
align-items: flex-end;
|
|
213
|
+
gap: 4px;
|
|
214
|
+
text-align: right;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.price-display {
|
|
218
|
+
font-size: 36px;
|
|
219
|
+
font-weight: 600;
|
|
220
|
+
letter-spacing: -1px;
|
|
221
|
+
background: linear-gradient(135deg, var(--q-accent) 0%, rgba(25, 118, 210, 0.8) 100%);
|
|
222
|
+
-webkit-background-clip: text;
|
|
223
|
+
-webkit-text-fill-color: transparent;
|
|
224
|
+
background-clip: text;
|
|
225
|
+
line-height: 1;
|
|
226
|
+
text-shadow: 0 2px 4px rgba(25, 118, 210, 0.3);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.price-period {
|
|
230
|
+
font-size: 12px;
|
|
231
|
+
font-weight: 400;
|
|
232
|
+
color: #888;
|
|
233
|
+
letter-spacing: 0.5px;
|
|
234
|
+
text-transform: uppercase;
|
|
235
|
+
opacity: 0.8;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.select-hint {
|
|
239
|
+
position: absolute;
|
|
240
|
+
bottom: 8px;
|
|
241
|
+
left: 50%;
|
|
242
|
+
transform: translateX(-50%);
|
|
243
|
+
color: #999;
|
|
244
|
+
font-style: italic;
|
|
245
|
+
opacity: 0;
|
|
246
|
+
animation: pulse 2s infinite;
|
|
247
|
+
pointer-events: none;
|
|
248
|
+
transition: opacity 0.3s ease;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.tariff-card:not(.tariff-selected):not(.tariff-disabled) .select-hint {
|
|
252
|
+
opacity: 1;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@keyframes pulse {
|
|
256
|
+
0%, 100% {
|
|
257
|
+
opacity: 0.6;
|
|
258
|
+
}
|
|
259
|
+
50% {
|
|
260
|
+
opacity: 1;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Responsive adjustments */
|
|
265
|
+
@media (max-width: 599px) {
|
|
266
|
+
.tariff-card {
|
|
267
|
+
padding: 16px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.tariff-header {
|
|
271
|
+
margin-bottom: 12px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.tariff-title h6 {
|
|
275
|
+
font-size: 1rem;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
</style>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import { TariffCard, type ITariff } from './index'
|
|
4
|
+
|
|
5
|
+
// Доступные тарифы
|
|
6
|
+
const availableTariffs: ITariff[] = [
|
|
7
|
+
{
|
|
8
|
+
id: 'test',
|
|
9
|
+
name: 'Тестовый',
|
|
10
|
+
description: 'Единственный тариф на период бета-тестирования платформы',
|
|
11
|
+
price: '1500 RUB',
|
|
12
|
+
features: [
|
|
13
|
+
'150 AXON на счёт кооператива',
|
|
14
|
+
'достаточно для 100 пакетов документов',
|
|
15
|
+
'и 50 регистраций пайщиков',
|
|
16
|
+
'Хостинг на изолированном сервере',
|
|
17
|
+
'Техническая поддержка'
|
|
18
|
+
],
|
|
19
|
+
additionalCosts: [
|
|
20
|
+
'5 AXON в день списывается со счёта кооператива',
|
|
21
|
+
'1 AXON списывается за каждый пакет документов',
|
|
22
|
+
'1 AXON списывается за регистрацию нового пайщика',
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
defineProps<{
|
|
28
|
+
disabled?: boolean
|
|
29
|
+
}>()
|
|
30
|
+
|
|
31
|
+
const emits = defineEmits<{
|
|
32
|
+
tariffSelected: [tariff: ITariff]
|
|
33
|
+
tariffDeselected: []
|
|
34
|
+
}>()
|
|
35
|
+
|
|
36
|
+
const selectedTariffId = ref<string>('')
|
|
37
|
+
|
|
38
|
+
const selectedTariff = computed(() => {
|
|
39
|
+
return availableTariffs.find(tariff => tariff.id === selectedTariffId.value)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const handleTariffSelect = (tariffId: string) => {
|
|
43
|
+
selectedTariffId.value = tariffId
|
|
44
|
+
const tariff = availableTariffs.find(t => t.id === tariffId)
|
|
45
|
+
if (tariff) {
|
|
46
|
+
emits('tariffSelected', tariff)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const handleTariffDeselect = (tariffId: string) => {
|
|
51
|
+
if (selectedTariffId.value === tariffId) {
|
|
52
|
+
selectedTariffId.value = ''
|
|
53
|
+
emits('tariffDeselected')
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Экспортируем для использования в родительском компоненте
|
|
58
|
+
defineExpose({
|
|
59
|
+
selectedTariff,
|
|
60
|
+
hasSelection: computed(() => !!selectedTariffId.value)
|
|
61
|
+
})
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template lang="pug">
|
|
65
|
+
div
|
|
66
|
+
.text-center.q-mb-lg
|
|
67
|
+
//- h5.text-h5.q-mb-sm Выберите тариф
|
|
68
|
+
p.text-body2.text-grey-7 Выберите подходящий тариф для вашего кооператива
|
|
69
|
+
|
|
70
|
+
.tariff-grid
|
|
71
|
+
div(v-for="tariff in availableTariffs" :key="tariff.id")
|
|
72
|
+
TariffCard(
|
|
73
|
+
:tariff="tariff"
|
|
74
|
+
:selected="selectedTariffId === tariff.id"
|
|
75
|
+
:disabled="disabled"
|
|
76
|
+
@select="handleTariffSelect"
|
|
77
|
+
@deselect="handleTariffDeselect"
|
|
78
|
+
)</template>
|
|
79
|
+
|
|
80
|
+
<style scoped>
|
|
81
|
+
.tariff-grid {
|
|
82
|
+
display: grid;
|
|
83
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
84
|
+
gap: 24px;
|
|
85
|
+
max-width: 800px;
|
|
86
|
+
margin: 0 auto;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@media (max-width: 599px) {
|
|
90
|
+
.tariff-grid {
|
|
91
|
+
grid-template-columns: 1fr;
|
|
92
|
+
gap: 16px;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { IStepperProps, IStepProps } from './types'
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { IntroStep, AgreementStep, FormStep, WaitingStep } from '../Steps/index'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
initialStep?: number
|
|
7
|
+
isFinish: boolean
|
|
8
|
+
signedDocument: any
|
|
9
|
+
coop: any
|
|
10
|
+
html?: string
|
|
11
|
+
domainValid?: boolean | null
|
|
12
|
+
installationProgress?: number | null
|
|
13
|
+
instanceStatus?: string | null
|
|
14
|
+
subscriptionsLoading?: boolean
|
|
15
|
+
subscriptionsError?: string | null
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const emits = defineEmits<{
|
|
19
|
+
stepChange: [step: number]
|
|
20
|
+
tariffSelected: [tariff: any]
|
|
21
|
+
tariffDeselected: []
|
|
22
|
+
continue: []
|
|
23
|
+
sign: []
|
|
24
|
+
finish: []
|
|
25
|
+
reload: []
|
|
26
|
+
}>()
|
|
27
|
+
|
|
28
|
+
const currentStep = ref(props.initialStep || 1)
|
|
29
|
+
|
|
30
|
+
// Управление шагами
|
|
31
|
+
const goToNext = () => {
|
|
32
|
+
if (currentStep.value < 4) {
|
|
33
|
+
currentStep.value++
|
|
34
|
+
emits('stepChange', currentStep.value)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const goToPrev = () => {
|
|
39
|
+
if (currentStep.value > 1) {
|
|
40
|
+
currentStep.value--
|
|
41
|
+
emits('stepChange', currentStep.value)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const handleContinue = () => {
|
|
46
|
+
goToNext()
|
|
47
|
+
emits('continue')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const handleSign = () => {
|
|
51
|
+
goToNext()
|
|
52
|
+
emits('sign')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleFinish = () => {
|
|
56
|
+
goToNext()
|
|
57
|
+
emits('finish')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const handleReload = () => {
|
|
61
|
+
emits('reload')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const handleTariffSelected = (tariff: any) => {
|
|
65
|
+
emits('tariffSelected', tariff)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const handleTariffDeselected = () => {
|
|
69
|
+
emits('tariffDeselected')
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template lang="pug">
|
|
74
|
+
div
|
|
75
|
+
q-stepper(
|
|
76
|
+
:model-value="currentStep"
|
|
77
|
+
flat
|
|
78
|
+
vertical
|
|
79
|
+
color="accent"
|
|
80
|
+
animated
|
|
81
|
+
done-color="teal"
|
|
82
|
+
)
|
|
83
|
+
IntroStep(
|
|
84
|
+
:current-step="currentStep"
|
|
85
|
+
:is-active="currentStep === 1"
|
|
86
|
+
:is-done="currentStep > 1"
|
|
87
|
+
@continue="handleContinue"
|
|
88
|
+
@tariff-selected="handleTariffSelected"
|
|
89
|
+
@tariff-deselected="handleTariffDeselected"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
AgreementStep(
|
|
93
|
+
:current-step="currentStep"
|
|
94
|
+
:is-active="currentStep === 2"
|
|
95
|
+
:is-done="currentStep > 2"
|
|
96
|
+
:html="html"
|
|
97
|
+
@back="goToPrev"
|
|
98
|
+
@sign="handleSign"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
FormStep(
|
|
102
|
+
:current-step="currentStep"
|
|
103
|
+
:is-active="currentStep === 3"
|
|
104
|
+
:is-done="currentStep > 3"
|
|
105
|
+
:signed-document="signedDocument"
|
|
106
|
+
@back="goToPrev"
|
|
107
|
+
@finish="handleFinish"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
WaitingStep(
|
|
111
|
+
:current-step="currentStep"
|
|
112
|
+
:is-active="currentStep === 4"
|
|
113
|
+
:is-done="false"
|
|
114
|
+
:coop="coop"
|
|
115
|
+
:domain-valid="domainValid"
|
|
116
|
+
:installation-progress="installationProgress"
|
|
117
|
+
:instance-status="instanceStatus"
|
|
118
|
+
:subscriptions-loading="subscriptionsLoading"
|
|
119
|
+
:subscriptions-error="subscriptionsError"
|
|
120
|
+
@back="goToPrev"
|
|
121
|
+
@reload="handleReload"
|
|
122
|
+
)
|
|
123
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ConnectionAgreementStepper } from './ConnectionAgreementStepper.vue'
|