Toggle navigation
Toggle navigation
This project
Loading...
Sign in
卢阳
/
front_backend_zImage
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
ly0303521
2025-12-22 15:42:22 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
c0865656c37ecee1dc5140fdcb230f29ef720905
c0865656
1 parent
dc803720
修复每个用户只能看到自己的点赞
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
81 additions
and
24 deletions
backend/main.py
z-image-generator/App.tsx
z-image-generator/services/galleryService.ts
z-image-generator/types.ts
backend/main.py
View file @
c086565
...
...
@@ -61,6 +61,7 @@ class GalleryImage(BaseModel):
likes
:
int
=
0
is_mock
:
bool
=
Field
(
default
=
False
,
alias
=
"isMock"
)
negative_prompt
:
Optional
[
str
]
=
None
liked_by
:
List
[
str
]
=
Field
(
default_factory
=
list
,
alias
=
"likedBy"
)
ImageGenerationResponse
.
model_rebuild
()
...
...
@@ -129,6 +130,31 @@ class GalleryStore:
self
.
_write
(
data
)
return
payload
def
toggle_like
(
self
,
image_id
:
str
,
user_id
:
str
)
->
Optional
[
dict
]:
with
self
.
lock
:
data
=
self
.
_read
()
images
=
data
.
get
(
"images"
,
[])
target_image
=
next
((
img
for
img
in
images
if
img
.
get
(
"id"
)
==
image_id
),
None
)
if
not
target_image
:
return
None
liked_by
=
target_image
.
get
(
"likedBy"
,
[])
# Handle legacy data where likedBy might be missing
if
not
isinstance
(
liked_by
,
list
):
liked_by
=
[]
if
user_id
in
liked_by
:
liked_by
.
remove
(
user_id
)
target_image
[
"likes"
]
=
max
(
0
,
target_image
.
get
(
"likes"
,
0
)
-
1
)
else
:
liked_by
.
append
(
user_id
)
target_image
[
"likes"
]
=
target_image
.
get
(
"likes"
,
0
)
+
1
target_image
[
"likedBy"
]
=
liked_by
self
.
_write
(
data
)
return
target_image
gallery_store
=
GalleryStore
(
GALLERY_DATA_PATH
,
GALLERY_MAX_ITEMS
)
...
...
@@ -160,6 +186,18 @@ async def health() -> dict:
return
{
"status"
:
"ok"
}
@app.post
(
"/likes/{image_id}"
)
async
def
toggle_like
(
image_id
:
str
,
user_id
:
str
=
Query
(
...
,
alias
=
"userId"
)
)
->
dict
:
"""Toggle like status for an image by a user."""
updated_image
=
gallery_store
.
toggle_like
(
image_id
,
user_id
)
if
not
updated_image
:
raise
HTTPException
(
status_code
=
404
,
detail
=
"Image not found"
)
return
updated_image
@app.get
(
"/gallery"
)
async
def
gallery
(
limit
:
int
=
Query
(
200
,
ge
=
1
,
le
=
1000
),
...
...
z-image-generator/App.tsx
View file @
c086565
...
...
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { ImageItem, ImageGenerationParams, UserProfile } from './types';
import { SHOWCASE_IMAGES, ADMIN_ID } from './constants';
import { generateImage } from './services/imageService';
import { fetchGallery } from './services/galleryService';
import { fetchGallery
, toggleLike
} from './services/galleryService';
import MasonryGrid from './components/MasonryGrid';
import InputBar from './components/InputBar';
import HistoryBar from './components/HistoryBar';
...
...
@@ -83,18 +83,17 @@ const App: React.FC = () => {
try {
const remoteImages = await fetchGallery();
setImages(prev => {
const localState = new Map(
prev.map(img => [
img.id,
{ likes: img.likes, isLikedByCurrentUser: !!img.isLikedByCurrentUser },
])
);
const normalized = remoteImages.map(img => ({
...img,
likes: localState.get(img.id)?.likes ?? img.likes ?? 0,
isLikedByCurrentUser: localState.get(img.id)?.isLikedByCurrentUser ?? false,
}));
// Map server images, calculating isLikedByCurrentUser
const normalized = remoteImages.map(img => {
const isLiked = img.likedBy && currentUser
? img.likedBy.includes(currentUser.employeeId)
: false;
return {
...img,
likes: img.likes, // Trust server
isLikedByCurrentUser: isLiked,
};
});
if (normalized.length >= MIN_GALLERY_ITEMS) {
return normalized;
...
...
@@ -110,17 +109,9 @@ const App: React.FC = () => {
});
} catch (err) {
console.error("Failed to sync gallery", err);
setImages(prev => {
if (prev.length >= MIN_GALLERY_ITEMS) return prev;
const existingIds = new Set(prev.map(img => img.id));
const filler = SHOWCASE_IMAGES.filter(img => !existingIds.has(img.id)).slice(
0,
Math.max(0, MIN_GALLERY_ITEMS - prev.length)
);
return [...prev, ...filler];
});
// Keep previous state if sync fails
}
}, []);
}, [
currentUser
]);
useEffect(() => {
syncGallery();
...
...
@@ -170,11 +161,14 @@ const App: React.FC = () => {
}
};
const handleLike = (image: ImageItem) => {
const handleLike =
async
(image: ImageItem) => {
if (!currentUser) {
setIsAuthModalOpen(true);
return;
}
// Optimistic update
const previousImages = [...images];
setImages(prev => prev.map(img => {
if (img.id === image.id) {
const isLiked = !!img.isLikedByCurrentUser;
...
...
@@ -186,6 +180,14 @@ const App: React.FC = () => {
}
return img;
}));
try {
await toggleLike(image.id, currentUser.employeeId);
} catch (e) {
console.error("Like failed", e);
setImages(previousImages); // Revert
alert("操作失败");
}
};
const handleGenerateSimilar = (params: ImageGenerationParams) => {
...
...
z-image-generator/services/galleryService.ts
View file @
c086565
...
...
@@ -20,3 +20,19 @@ export const fetchGallery = async (authorId?: string): Promise<ImageItem[]> => {
const
data
:
GalleryResponse
=
await
response
.
json
();
return
data
.
images
??
[];
};
export
const
toggleLike
=
async
(
imageId
:
string
,
userId
:
string
)
:
Promise
<
ImageItem
>
=>
{
if
(
API_BASE_URL
===
Z_IMAGE_DIRECT_BASE_URL
)
{
throw
new
Error
(
"Cannot like images in direct mode"
);
}
const
response
=
await
fetch
(
`
$
{
API_BASE_URL
}
/likes/
$
{
imageId
}?
userId
=
$
{
userId
}
`
,
{
method
:
'POST'
,
});
if
(
!
response
.
ok
)
{
const
errorText
=
await
response
.
text
();
throw
new
Error
(
`
Like
failed
(
$
{
response
.
status
}):
$
{
errorText
}
`
);
}
return
await
response
.
json
();
};
...
...
z-image-generator/types.ts
View file @
c086565
...
...
@@ -17,6 +17,7 @@ export interface ImageItem extends ImageGenerationParams {
// New fields for community features
authorId
?:
string
;
// The 8-digit employee ID
likes
:
number
;
likedBy
?:
string
[];
// List of user IDs who liked this image
isLikedByCurrentUser
?:
boolean
;
// UI state
isMock
?:
boolean
;
// True if it's a static showcase image
...
...
Please
register
or
login
to post a comment