BLOG-44 Post overall page #64
@ -35,7 +35,8 @@
|
|||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.20.0",
|
||||||
"vite": "^7.0.4"
|
"vite": "^7.0.4",
|
||||||
|
"zod": "^4.0.5"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
|
8
frontend/pnpm-lock.yaml
generated
8
frontend/pnpm-lock.yaml
generated
@ -74,6 +74,9 @@ importers:
|
|||||||
vite:
|
vite:
|
||||||
specifier: ^7.0.4
|
specifier: ^7.0.4
|
||||||
version: 7.0.5(jiti@2.4.2)(lightningcss@1.30.1)
|
version: 7.0.5(jiti@2.4.2)(lightningcss@1.30.1)
|
||||||
|
zod:
|
||||||
|
specifier: ^4.0.5
|
||||||
|
version: 4.0.5
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@ -1527,6 +1530,9 @@ packages:
|
|||||||
zimmerframe@1.1.2:
|
zimmerframe@1.1.2:
|
||||||
resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
|
resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
|
||||||
|
|
||||||
|
zod@4.0.5:
|
||||||
|
resolution: {integrity: sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==}
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@ampproject/remapping@2.3.0':
|
'@ampproject/remapping@2.3.0':
|
||||||
@ -2760,3 +2766,5 @@ snapshots:
|
|||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
zimmerframe@1.1.2: {}
|
zimmerframe@1.1.2: {}
|
||||||
|
|
||||||
|
zod@4.0.5: {}
|
||||||
|
@ -35,6 +35,6 @@ samp {
|
|||||||
@apply font-mono;
|
@apply font-mono;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
.container {
|
||||||
@apply h-[--tool-bar-height];
|
@apply mx-auto max-w-screen-xl px-4 md:px-6;
|
||||||
}
|
}
|
||||||
|
26
frontend/src/lib/common/adapter/presenter/asyncState.ts
Normal file
26
frontend/src/lib/common/adapter/presenter/asyncState.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export enum StatusType {
|
||||||
|
Idle,
|
||||||
|
Loading,
|
||||||
|
Success,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IdleState {
|
||||||
|
status: StatusType.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoadingState {
|
||||||
|
status: StatusType.Loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuccessState<T> {
|
||||||
|
status: StatusType.Success;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorState {
|
||||||
|
status: StatusType.Error;
|
||||||
|
error: Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AsyncState<T> = IdleState | LoadingState | SuccessState<T> | ErrorState;
|
5
frontend/src/lib/environment.ts
Normal file
5
frontend/src/lib/environment.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { env } from '$env/dynamic/public';
|
||||||
|
|
||||||
|
export abstract class Environment {
|
||||||
|
static readonly API_BASE_URL = env.PUBLIC_BACKEND_URL ?? 'http://localhost:5173/api';
|
||||||
|
}
|
11
frontend/src/lib/home/framework/ui/HomePage.svelte
Normal file
11
frontend/src/lib/home/framework/ui/HomePage.svelte
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Motto from '$lib/home/framework/ui/Motto.svelte';
|
||||||
|
import Terminal from '$lib/home/framework/ui/Terminal.svelte';
|
||||||
|
import TitleScreen from '$lib/home/framework/ui/TitleScreen.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TitleScreen />
|
||||||
|
<Terminal />
|
||||||
|
<Motto />
|
||||||
|
</div>
|
@ -2,9 +2,7 @@
|
|||||||
import MottoAnimatedMark from './MottoAnimatedMark.svelte';
|
import MottoAnimatedMark from './MottoAnimatedMark.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="container flex h-screen flex-col items-center justify-center gap-y-2.5 md:gap-y-8">
|
||||||
class="mx-auto flex h-screen max-w-screen-xl flex-col items-center justify-center gap-y-2.5 px-4 md:gap-y-8 md:px-6"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="flex w-[19rem] flex-col gap-y-3 text-3xl font-bold text-gray-800 md:w-[38rem] md:gap-y-4 md:text-6xl"
|
class="flex w-[19rem] flex-col gap-y-3 text-3xl font-bold text-gray-800 md:w-[38rem] md:gap-y-4 md:text-6xl"
|
||||||
>
|
>
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="mx-auto flex max-w-screen-xl flex-col items-center justify-center gap-y-2.5 px-4 py-32 md:gap-y-8 md:px-24 md:py-32"
|
class="container flex flex-col items-center justify-center gap-y-2.5 py-32 md:gap-y-8 md:px-24 md:py-32"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
bind:this={element}
|
bind:this={element}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
import TitleScreenAnimatedTags from '$lib/home/framework/ui/TitleScreenAnimatedTags.svelte';
|
import TitleScreenAnimatedTags from '$lib/home/framework/ui/TitleScreenAnimatedTags.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="container flex min-h-content-height flex-col justify-center gap-y-2.5 md:gap-y-8">
|
||||||
class="mx-auto flex min-h-content-height max-w-screen-xl flex-col justify-center gap-y-2.5 px-4 md:gap-y-8 md:px-6"
|
|
||||||
>
|
|
||||||
<h2 class="text-3xl font-bold text-gray-800 md:text-6xl">Hello 大家好!</h2>
|
<h2 class="text-3xl font-bold text-gray-800 md:text-6xl">Hello 大家好!</h2>
|
||||||
<h1 class="flex flex-row items-center gap-x-2 text-4xl font-extrabold text-gray-800 md:text-7xl">
|
<h1 class="flex flex-row items-center gap-x-2 text-4xl font-extrabold text-gray-800 md:text-7xl">
|
||||||
<span>我是</span>
|
<span>我是</span>
|
||||||
|
37
frontend/src/lib/post/adapter/gateway/labelResponseDto.ts
Normal file
37
frontend/src/lib/post/adapter/gateway/labelResponseDto.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Label } from '$lib/post/domain/entity/label';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const LabelResponseSchema = z.object({
|
||||||
|
id: z.int32(),
|
||||||
|
name: z.string(),
|
||||||
|
color: z.string().startsWith('#').length(9)
|
||||||
|
});
|
||||||
|
|
||||||
|
export class LabelResponseDto {
|
||||||
|
readonly id: number;
|
||||||
|
readonly name: string;
|
||||||
|
readonly color: string;
|
||||||
|
|
||||||
|
private constructor(props: { id: number; name: string; color: string }) {
|
||||||
|
this.id = props.id;
|
||||||
|
this.name = props.name;
|
||||||
|
this.color = props.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json: unknown): LabelResponseDto {
|
||||||
|
const parsedJson = LabelResponseSchema.parse(json);
|
||||||
|
return new LabelResponseDto({
|
||||||
|
id: parsedJson.id,
|
||||||
|
name: parsedJson.name,
|
||||||
|
color: parsedJson.color
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toEntity(): Label {
|
||||||
|
return new Label({
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
color: this.color
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
5
frontend/src/lib/post/adapter/gateway/postApiService.ts
Normal file
5
frontend/src/lib/post/adapter/gateway/postApiService.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import type { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
||||||
|
|
||||||
|
export interface PostApiService {
|
||||||
|
getAllPosts(): Promise<PostInfoResponseDto[]>;
|
||||||
|
}
|
60
frontend/src/lib/post/adapter/gateway/postInfoResponseDto.ts
Normal file
60
frontend/src/lib/post/adapter/gateway/postInfoResponseDto.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { LabelResponseDto, LabelResponseSchema } from '$lib/post/adapter/gateway/labelResponseDto';
|
||||||
|
import { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
export const PostInfoResponseSchema = z.object({
|
||||||
|
id: z.int32(),
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
preview_image_url: z.url(),
|
||||||
|
labels: z.array(LabelResponseSchema),
|
||||||
|
published_time: z.number().int(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export class PostInfoResponseDto {
|
||||||
|
readonly id: number;
|
||||||
|
readonly title: string;
|
||||||
|
readonly description: string;
|
||||||
|
readonly previewImageUrl: URL;
|
||||||
|
readonly labels: readonly LabelResponseDto[];
|
||||||
|
readonly publishedTime: Date;
|
||||||
|
|
||||||
|
private constructor(props: {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
previewImageUrl: URL;
|
||||||
|
labels: LabelResponseDto[];
|
||||||
|
publishedTime: Date;
|
||||||
|
}) {
|
||||||
|
this.id = props.id;
|
||||||
|
this.title = props.title;
|
||||||
|
this.description = props.description;
|
||||||
|
this.previewImageUrl = props.previewImageUrl;
|
||||||
|
this.labels = props.labels;
|
||||||
|
this.publishedTime = props.publishedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json: unknown): PostInfoResponseDto {
|
||||||
|
const parsedJson = PostInfoResponseSchema.parse(json);
|
||||||
|
return new PostInfoResponseDto({
|
||||||
|
id: parsedJson.id,
|
||||||
|
title: parsedJson.title,
|
||||||
|
description: parsedJson.description,
|
||||||
|
previewImageUrl: new URL(parsedJson.preview_image_url),
|
||||||
|
labels: parsedJson.labels.map((label) => LabelResponseDto.fromJson(JSON.stringify(label))),
|
||||||
|
publishedTime: new Date(parsedJson.published_time / 1000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toEntity(): PostInfo {
|
||||||
|
return new PostInfo({
|
||||||
|
id: this.id,
|
||||||
|
title: this.title,
|
||||||
|
description: this.description,
|
||||||
|
previewImageUrl: this.previewImageUrl,
|
||||||
|
labels: this.labels.map((label) => label.toEntity()),
|
||||||
|
publishedTime: this.publishedTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
12
frontend/src/lib/post/adapter/gateway/postRepositoryImpl.ts
Normal file
12
frontend/src/lib/post/adapter/gateway/postRepositoryImpl.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
||||||
|
import type { PostRepository } from '$lib/post/application/repository/postRepository';
|
||||||
|
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
|
||||||
|
export class PostRepositoryImpl implements PostRepository {
|
||||||
|
constructor(private readonly postApiService: PostApiService) {}
|
||||||
|
|
||||||
|
async getAllPosts(): Promise<PostInfo[]> {
|
||||||
|
const dtos = await this.postApiService.getAllPosts();
|
||||||
|
return dtos.map((dto) => dto.toEntity());
|
||||||
|
}
|
||||||
|
}
|
19
frontend/src/lib/post/adapter/presenter/labelViewModel.ts
Normal file
19
frontend/src/lib/post/adapter/presenter/labelViewModel.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export class LabelViewModel {
|
||||||
|
readonly id: number;
|
||||||
|
readonly name: string;
|
||||||
|
readonly color: string;
|
||||||
|
|
||||||
|
private constructor(props: { id: number; name: string; color: string }) {
|
||||||
|
this.id = props.id;
|
||||||
|
this.name = props.name;
|
||||||
|
this.color = props.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromEntity(label: { id: number; name: string; color: string }): LabelViewModel {
|
||||||
|
return new LabelViewModel({
|
||||||
|
id: label.id,
|
||||||
|
name: label.name,
|
||||||
|
color: label.color
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
38
frontend/src/lib/post/adapter/presenter/postInfoViewModel.ts
Normal file
38
frontend/src/lib/post/adapter/presenter/postInfoViewModel.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { LabelViewModel } from '$lib/post/adapter/presenter/labelViewModel';
|
||||||
|
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
|
||||||
|
export class PostInfoViewModel {
|
||||||
|
readonly id: number;
|
||||||
|
readonly title: string;
|
||||||
|
readonly description: string;
|
||||||
|
readonly previewImageUrl: URL;
|
||||||
|
readonly labels: readonly LabelViewModel[];
|
||||||
|
readonly publishedTime: Date;
|
||||||
|
|
||||||
|
private constructor(props: {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
previewImageUrl: URL;
|
||||||
|
labels: readonly LabelViewModel[];
|
||||||
|
publishedTime: Date;
|
||||||
|
}) {
|
||||||
|
this.id = props.id;
|
||||||
|
this.title = props.title;
|
||||||
|
this.description = props.description;
|
||||||
|
this.previewImageUrl = props.previewImageUrl;
|
||||||
|
this.labels = props.labels;
|
||||||
|
this.publishedTime = props.publishedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromEntity(postInfo: PostInfo): PostInfoViewModel {
|
||||||
|
return new PostInfoViewModel({
|
||||||
|
id: postInfo.id,
|
||||||
|
title: postInfo.title,
|
||||||
|
description: postInfo.description,
|
||||||
|
previewImageUrl: postInfo.previewImageUrl,
|
||||||
|
labels: postInfo.labels.map((label) => LabelViewModel.fromEntity(label)),
|
||||||
|
publishedTime: postInfo.publishedTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
44
frontend/src/lib/post/adapter/presenter/postListBloc.ts
Normal file
44
frontend/src/lib/post/adapter/presenter/postListBloc.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { StatusType, type AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
||||||
|
import { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
||||||
|
import type { GetAllPostUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export class PostListBloc {
|
||||||
|
constructor(private readonly getAllPostsUseCase: GetAllPostUseCase) {}
|
||||||
|
|
||||||
|
private readonly state = writable<AsyncState<readonly PostInfoViewModel[]>>({
|
||||||
|
status: StatusType.Idle
|
||||||
|
});
|
||||||
|
|
||||||
|
get subscribe() {
|
||||||
|
return this.state.subscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(event: PostListEvent) {
|
||||||
|
switch (event.event) {
|
||||||
|
case PostListEventType.PostListLoadedEvent:
|
||||||
|
this.loadPosts();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadPosts() {
|
||||||
|
this.state.set({ status: StatusType.Loading });
|
||||||
|
const posts = await this.getAllPostsUseCase.execute();
|
||||||
|
const postViewModels = posts.map((post) => PostInfoViewModel.fromEntity(post));
|
||||||
|
this.state.set({
|
||||||
|
status: StatusType.Success,
|
||||||
|
data: postViewModels
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PostListEventType {
|
||||||
|
PostListLoadedEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostListLoadedEvent {
|
||||||
|
event: PostListEventType.PostListLoadedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PostListEvent = PostListLoadedEvent;
|
@ -0,0 +1,5 @@
|
|||||||
|
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
|
||||||
|
export interface PostRepository {
|
||||||
|
getAllPosts(): Promise<PostInfo[]>;
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import type { PostRepository } from '$lib/post/application/repository/postRepository';
|
||||||
|
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
|
||||||
|
export class GetAllPostUseCase {
|
||||||
|
constructor(private readonly postRepository: PostRepository) {}
|
||||||
|
|
||||||
|
execute(): Promise<PostInfo[]> {
|
||||||
|
return this.postRepository.getAllPosts();
|
||||||
|
}
|
||||||
|
}
|
11
frontend/src/lib/post/domain/entity/label.ts
Normal file
11
frontend/src/lib/post/domain/entity/label.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export class Label {
|
||||||
|
readonly id: number;
|
||||||
|
readonly name: string;
|
||||||
|
readonly color: string;
|
||||||
|
|
||||||
|
constructor(props: { id: number; name: string; color: string }) {
|
||||||
|
this.id = props.id;
|
||||||
|
this.name = props.name;
|
||||||
|
this.color = props.color;
|
||||||
|
}
|
||||||
|
}
|
26
frontend/src/lib/post/domain/entity/postInfo.ts
Normal file
26
frontend/src/lib/post/domain/entity/postInfo.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import type { Label } from '$lib/post/domain/entity/label';
|
||||||
|
|
||||||
|
export class PostInfo {
|
||||||
|
readonly id: number;
|
||||||
|
readonly title: string;
|
||||||
|
readonly description: string;
|
||||||
|
readonly previewImageUrl: URL;
|
||||||
|
readonly labels: readonly Label[];
|
||||||
|
readonly publishedTime: Date;
|
||||||
|
|
||||||
|
constructor(props: {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
previewImageUrl: URL;
|
||||||
|
labels: readonly Label[];
|
||||||
|
publishedTime: Date;
|
||||||
|
}) {
|
||||||
|
this.id = props.id;
|
||||||
|
this.title = props.title;
|
||||||
|
this.description = props.description;
|
||||||
|
this.previewImageUrl = props.previewImageUrl;
|
||||||
|
this.labels = props.labels;
|
||||||
|
this.publishedTime = props.publishedTime;
|
||||||
|
}
|
||||||
|
}
|
19
frontend/src/lib/post/framework/api/postApiServiceImpl.ts
Normal file
19
frontend/src/lib/post/framework/api/postApiServiceImpl.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Environment } from '$lib/environment';
|
||||||
|
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
||||||
|
import { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
||||||
|
|
||||||
|
export class PostApiServiceImpl implements PostApiService {
|
||||||
|
async getAllPosts(): Promise<PostInfoResponseDto[]> {
|
||||||
|
const url = new URL(Environment.API_BASE_URL);
|
||||||
|
url.pathname += '/post/all';
|
||||||
|
|
||||||
|
const response = await fetch(url.href);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
return json.map(PostInfoResponseDto.fromJson);
|
||||||
|
}
|
||||||
|
}
|
23
frontend/src/lib/post/framework/ui/PostListPage.svelte
Normal file
23
frontend/src/lib/post/framework/ui/PostListPage.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { StatusType } from '$lib/common/adapter/presenter/asyncState';
|
||||||
|
import { PostListBloc, PostListEventType } from '$lib/post/adapter/presenter/postListBloc';
|
||||||
|
import { getContext, onMount } from 'svelte';
|
||||||
|
|
||||||
|
const postListBloc = getContext<PostListBloc>(PostListBloc.name);
|
||||||
|
let state = $derived($postListBloc);
|
||||||
|
|
||||||
|
$inspect(state, 'PostListBloc State');
|
||||||
|
|
||||||
|
onMount(() => postListBloc.dispatch({ event: PostListEventType.PostListLoadedEvent }));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="py-9 text-center text-3xl font-bold text-gray-800 md:py-20 md:text-5xl">文章</div>
|
||||||
|
{#if state.status === StatusType.Loading || state.status === StatusType.Idle}
|
||||||
|
<div>Loading</div>
|
||||||
|
{:else if state.status === StatusType.Success}
|
||||||
|
<div>{JSON.stringify(state.data)}</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-red-500">Error loading posts</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
@ -1,11 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import Motto from '$lib/home/framework/ui/Motto.svelte';
|
import HomePage from '$lib/home/framework/ui/HomePage.svelte';
|
||||||
import Terminal from '$lib/home/framework/ui/Terminal.svelte';
|
|
||||||
import TitleScreen from '$lib/home/framework/ui/TitleScreen.svelte';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<HomePage />
|
||||||
<TitleScreen />
|
|
||||||
<Terminal />
|
|
||||||
<Motto />
|
|
||||||
</div>
|
|
||||||
|
17
frontend/src/routes/post/+page.svelte
Normal file
17
frontend/src/routes/post/+page.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
|
||||||
|
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
|
||||||
|
import { GetAllPostUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
||||||
|
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
|
||||||
|
import PostListPage from '$lib/post/framework/ui/PostListPage.svelte';
|
||||||
|
import { setContext } from 'svelte';
|
||||||
|
|
||||||
|
const postApiService = new PostApiServiceImpl();
|
||||||
|
const postRepository = new PostRepositoryImpl(postApiService);
|
||||||
|
const getAllPostsUseCase = new GetAllPostUseCase(postRepository);
|
||||||
|
const postListBloc = new PostListBloc(getAllPostsUseCase);
|
||||||
|
|
||||||
|
setContext(PostListBloc.name, postListBloc);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<PostListPage />
|
@ -8,5 +8,14 @@ export default defineConfig({
|
|||||||
plugins: [tailwindcss(), sveltekit()],
|
plugins: [tailwindcss(), sveltekit()],
|
||||||
define: {
|
define: {
|
||||||
'App.__VERSION__': JSON.stringify(version)
|
'App.__VERSION__': JSON.stringify(version)
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://127.0.0.1:8080',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user