refaktoryzacja service api.

wydzielenie mockow.
dodanie parsowania wyszukiwania
This commit is contained in:
2026-05-02 05:49:24 +02:00
parent 045c65c363
commit 93778065ce
20 changed files with 820 additions and 275 deletions

View File

@@ -34,9 +34,26 @@ a service powinien zajac sie reszta.
```txt
frontend/src/services/apiMode.js
frontend/src/services/productsApi.js
frontend/src/services/productsHttpApi.js
frontend/src/services/productTimelineApi.js
frontend/src/services/productTimelineHttpApi.js
frontend/src/services/productSpecificationApi.js
frontend/src/services/productSpecificationHttpApi.js
frontend/src/services/dictionariesApi.js
frontend/src/services/dictionariesHttpApi.js
```
Pliki `*Api.js` sa fasadami. Wybieraja implementacje na podstawie `USE_MOCK_API`.
Pliki `*HttpApi.js` sa implementacja prawdziwego API przez Axios.
Implementacje mockow sa poza katalogiem `services`:
```txt
frontend/src/mocks/api/productsMockApi.js
frontend/src/mocks/api/productTimelineMockApi.js
frontend/src/mocks/api/productSpecificationMockApi.js
frontend/src/mocks/api/dictionariesMockApi.js
```
## Przeplyw danych
@@ -57,6 +74,7 @@ Przyklad:
IndexPage.vue
-> productsStore.fetchFirstPage()
-> fetchProducts(params)
-> productsMockApi albo productsHttpApi
-> products.json albo GET /mayo-api/products
```
@@ -90,6 +108,20 @@ To jest lepsze, bo:
- latwiej dodac mocki,
- latwiej testowac logike store.
Po ostatnim porzadkowaniu services sa fasadami. Przyklad:
```js
export const fetchProducts = USE_MOCK_API ? fetchProductsFromMock : fetchProductsFromHttp
```
Dzieki temu `productsStore` zawsze importuje to samo:
```js
import { fetchProducts } from 'src/services/productsApi'
```
ale realna implementacja moze byc mockowa albo HTTP.
## `apiMode.js`
Plik:
@@ -140,18 +172,19 @@ frontend/src/mocks/specifications.json
frontend/src/mocks/dictionaries.json
```
Sa uzywane tylko przez warstwe `services`.
Sa uzywane tylko przez implementacje mock API w `frontend/src/mocks/api/`.
Komponenty i store nie importuja mockow bezposrednio.
Komponenty, store i fasady `services/*Api.js` nie importuja plikow JSON bezposrednio.
To jest wazne, bo komponent nie powinien wiedziec, skad przyszly dane. Dla komponentu dane z mocka i z backendu powinny wygladac identycznie.
## `productsApi.js`
## `productsApi.js` i `productsHttpApi.js`
Plik:
```txt
frontend/src/services/productsApi.js
frontend/src/services/productsHttpApi.js
```
Eksportuje:
@@ -167,6 +200,14 @@ Odpowiedzialnosc:
- obsluga `limit` i `offset`,
- zwrocenie danych w formacie oczekiwanym przez `productsStore`.
`productsApi.js` jest fasada.
`productsHttpApi.js` zna Axios i endpoint:
```http
GET /mayo-api/products
```
Zwraca:
```js
@@ -181,14 +222,20 @@ Zwraca:
}
```
W trybie mock implementacja jest w:
```txt
frontend/src/mocks/api/productsMockApi.js
```
W trybie mock:
- czyta `frontend/src/mocks/products.json`,
- filtruje po `search`, `finish`, `year`, `productionList`,
- filtruje po `orderSearch`, `finish`, `year`, `productionList`,
- ucina wynik wedlug `limit` i `offset`,
- zwraca `items` i `pageInfo`.
W trybie prawdziwego API:
W trybie prawdziwego API implementacja jest w `productsHttpApi.js`:
```http
GET /mayo-api/products
@@ -201,6 +248,7 @@ z parametrami:
limit,
offset,
search,
orderSearch,
model,
client,
finish,
@@ -209,12 +257,21 @@ z parametrami:
}
```
## `productTimelineApi.js`
`orderSearch` powstaje w `productsStore` przez parser:
```txt
frontend/src/utils/orderSearchParser.js
```
Do HTTP `orderSearch` jest serializowane jako JSON string, bo endpoint listy jest typu `GET`.
## `productTimelineApi.js` i `productTimelineHttpApi.js`
Plik:
```txt
frontend/src/services/productTimelineApi.js
frontend/src/services/productTimelineHttpApi.js
```
Eksportuje:
@@ -228,13 +285,19 @@ createProductTimelineEvent(productId, payload)
Pobiera pelny timeline produktu.
W trybie mock implementacja jest w:
```txt
frontend/src/mocks/api/productTimelineMockApi.js
```
W trybie mock:
- czyta `frontend/src/mocks/timelines.json`,
- zwraca timeline dla `productId`,
- jesli brak danych, zwraca pusty timeline.
W trybie prawdziwego API:
W trybie prawdziwego API implementacja jest w `productTimelineHttpApi.js`:
```http
GET /mayo-api/products/:id/timeline
@@ -274,12 +337,13 @@ To pozwala store od razu zaktualizowac:
- pelny timeline,
- preview na glownej karcie produktu.
## `productSpecificationApi.js`
## `productSpecificationApi.js` i `productSpecificationHttpApi.js`
Plik:
```txt
frontend/src/services/productSpecificationApi.js
frontend/src/services/productSpecificationHttpApi.js
```
Eksportuje:
@@ -293,11 +357,17 @@ refreshProductSpecification(productId)
Pobiera specyfikacje produktu.
W trybie mock implementacja jest w:
```txt
frontend/src/mocks/api/productSpecificationMockApi.js
```
W trybie mock:
- czyta `frontend/src/mocks/specifications.json`.
W trybie prawdziwego API:
W trybie prawdziwego API implementacja jest w `productSpecificationHttpApi.js`:
```http
GET /mayo-api/products/:id/specification
@@ -353,12 +423,13 @@ data.sections
Dzieki temu store nie musi znac szczegolow odpowiedzi HTTP.
## `dictionariesApi.js`
## `dictionariesApi.js` i `dictionariesHttpApi.js`
Plik:
```txt
frontend/src/services/dictionariesApi.js
frontend/src/services/dictionariesHttpApi.js
```
Eksportuje:
@@ -372,11 +443,17 @@ Odpowiedzialnosc:
- pobranie slownikow do filtrow i formularzy,
- normalizacja pustych pol do pustych tablic.
W trybie mock implementacja jest w:
```txt
frontend/src/mocks/api/dictionariesMockApi.js
```
W trybie mock:
- czyta `frontend/src/mocks/dictionaries.json`.
W trybie prawdziwego API:
W trybie prawdziwego API implementacja jest w `dictionariesHttpApi.js`:
```http
GET /mayo-api/dictionaries
@@ -405,20 +482,28 @@ Zrob nowy service:
```txt
frontend/src/services/productPhotosApi.js
frontend/src/services/productPhotosHttpApi.js
frontend/src/mocks/api/productPhotosMockApi.js
```
W nim:
Fasada:
```js
import { fetchProductPhotos as fetchProductPhotosFromHttp } from 'src/services/productPhotosHttpApi'
import { fetchProductPhotos as fetchProductPhotosFromMock } from 'src/mocks/api/productPhotosMockApi'
import { USE_MOCK_API } from 'src/services/apiMode'
export const fetchProductPhotos = USE_MOCK_API
? fetchProductPhotosFromMock
: fetchProductPhotosFromHttp
```
Implementacja HTTP:
```js
import { api } from 'src/boot/axios'
import { USE_MOCK_API, waitForMockApi } from 'src/services/apiMode'
export async function fetchProductPhotos(productId) {
if (USE_MOCK_API) {
await waitForMockApi()
return []
}
const response = await api.get(`/mayo-api/products/${productId}/photos`)
return response.data
}
@@ -463,12 +548,16 @@ Utworzono:
```txt
frontend/src/services/apiMode.js
frontend/src/services/productsApi.js
frontend/src/services/productsHttpApi.js
frontend/src/services/productTimelineApi.js
frontend/src/services/productTimelineHttpApi.js
frontend/src/services/productSpecificationApi.js
frontend/src/services/productSpecificationHttpApi.js
frontend/src/services/dictionariesApi.js
frontend/src/services/dictionariesHttpApi.js
```
Utworzono mocki:
Utworzono dane mockow:
```txt
frontend/src/mocks/products.json
@@ -477,6 +566,15 @@ frontend/src/mocks/specifications.json
frontend/src/mocks/dictionaries.json
```
Utworzono implementacje mock API:
```txt
frontend/src/mocks/api/productsMockApi.js
frontend/src/mocks/api/productTimelineMockApi.js
frontend/src/mocks/api/productSpecificationMockApi.js
frontend/src/mocks/api/dictionariesMockApi.js
```
Zaktualizowano store tak, aby uzywaly services zamiast Axiosa bezposrednio.
Zaktualizowano widok glownej strony tak, aby pobieral dane ze store zamiast trzymac lokalne mocki w komponencie.

View File

@@ -182,7 +182,6 @@ Jeśli na karcie produktu ma być widoczny timeline, ten store powinien trzymać
},
filters: {
search: '',
modelId: null,
clientId: null,
finish: null,
@@ -190,6 +189,19 @@ Jeśli na karcie produktu ma być widoczny timeline, ten store powinien trzymać
year: null,
},
searchQuery: '123/1',
orderSearch: [
{
raw: '123/1',
type: 'orderNumberIndex',
match: 'exact',
orderNumber: 123,
orderNumberPrefix: null,
orderYear: null,
orderIndex: 1,
},
],
limit: 30,
offset: 30,
total: 184,
@@ -204,7 +216,7 @@ Jeśli na karcie produktu ma być widoczny timeline, ten store powinien trzymać
Endpoint:
```http
GET /mayo-api/products?limit=30&offset=0&search=regius
GET /mayo-api/products?limit=30&offset=0&search=123/1
```
Odpowiedź:
@@ -261,10 +273,22 @@ await productsStore.fetchNextPage()
Pobiera kolejną porcję produktów. Używaj tego przy lazy loading, gdy użytkownik zbliża się do końca listy.
```js
await productsStore.applyFilters({ search: 'regius' })
await productsStore.applyFilters({ finish: 'GLOSS' })
```
Ustawia filtry i pobiera listę od początku.
Ustawia filtry i pobiera listę od początku. Searchbar nie jest zwyklym filtrem, bo ma osobny parser numerow zamowien.
```js
productsStore.setSearchQuery('123/1')
```
Zapisuje tekst z searchbara i parsuje go do `orderSearch`.
```js
await productsStore.applySearch('123/1')
```
Zapisuje tekst searchbara, parsuje go i pobiera listę od początku.
```js
productsStore.applyTimelinePreviewUpdate(productId, timelinePreview)
@@ -914,7 +938,7 @@ Komponent wywołuje:
```js
productsStore.applyFilters({
search: 'regius',
finish: 'GLOSS',
})
```
@@ -924,6 +948,14 @@ Store:
2. resetuje offset,
3. pobiera pierwszą stronę listy od nowa.
Searchbar jest obslugiwany osobno:
```js
productsStore.applySearch('123/1')
```
To jest celowe, bo wyszukiwanie po numerze zamowienia ma wlasny parser i nie powinno mieszac sie z filtrami typu model, klient, finish albo lista produkcyjna.
### 4. Użytkownik otwiera specyfikację produktu
Komponent wywołuje: