Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
I
img-manager
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ivan Lan
img-manager
Commits
c139b140
Commit
c139b140
authored
Mar 09, 2025
by
Ivan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: update images
parent
75d4a205
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
314 additions
and
347 deletions
+314
-347
images_controller.rb
app/controllers/admin/images_controller.rb
+1
-1
images_controller.rb
app/controllers/images_controller.rb
+9
-16
ImageCard.jsx
app/frontend/components/ImageCard.jsx
+0
-72
ComImageCard.jsx
app/frontend/components/images/ComImageCard.jsx
+49
-0
ComImageIndex.jsx
app/frontend/components/images/ComImageIndex.jsx
+10
-55
ComImageShow.jsx
app/frontend/components/images/ComImageShow.jsx
+160
-0
ComImageStatusTag.jsx
app/frontend/components/images/ComImageStatusTag.jsx
+17
-0
ComTagsIndex.jsx
app/frontend/components/tags/ComTagsIndex.jsx
+2
-2
Layout.jsx
app/frontend/pages/Layout.jsx
+3
-3
Index.jsx
app/frontend/pages/admin/images/Index.jsx
+3
-3
Show.jsx
app/frontend/pages/admin/images/Show.jsx
+19
-0
Index.jsx
app/frontend/pages/admin/tags/Index.jsx
+1
-1
Index.jsx
app/frontend/pages/admin/tags/images/Index.jsx
+3
-3
New.jsx
app/frontend/pages/admin/tags/images/New.jsx
+6
-6
Edit.jsx
app/frontend/pages/images/Edit.jsx
+9
-9
Index.jsx
app/frontend/pages/images/Index.jsx
+3
-3
New.jsx
app/frontend/pages/images/New.jsx
+5
-5
Show.jsx
app/frontend/pages/images/Show.jsx
+12
-166
image.rb
app/models/image.rb
+2
-2
No files found.
app/controllers/admin/images_controller.rb
View file @
c139b140
...
@@ -97,7 +97,7 @@ class Admin::ImagesController < ApplicationController
...
@@ -97,7 +97,7 @@ class Admin::ImagesController < ApplicationController
end
end
def
destroy
def
destroy
@image
.
destroy
@image
.
destroy
!
redirect_to
admin_images_path
,
notice:
"Image was successfully deleted."
redirect_to
admin_images_path
,
notice:
"Image was successfully deleted."
end
end
...
...
app/controllers/images_controller.rb
View file @
c139b140
...
@@ -3,10 +3,9 @@ class ImagesController < ApplicationController
...
@@ -3,10 +3,9 @@ class ImagesController < ApplicationController
# allow_unauthenticated_access only: [:index, :show, :search]
# allow_unauthenticated_access only: [:index, :show, :search]
before_action
:set_image
,
only:
[
:show
,
:edit
,
:update
,
:destroy
,
:approve
,
:reject
]
before_action
:set_image
,
only:
[
:show
,
:edit
,
:update
,
:destroy
,
:approve
,
:reject
]
before_action
:authorize_admin
,
only:
[
:approve
,
:reject
,
:destroy
]
def
index
def
index
@q
=
current_
user
.
images
.
includes
(
:user
,
:tags
).
ransack
(
params
[
:q
])
@q
=
Current
.
user
.
images
.
includes
(
:user
,
:tags
).
ransack
(
params
[
:q
])
@images
=
@q
.
result
(
distinct:
true
).
with_attached_file
.
order
(
created_at: :desc
).
page
(
params
[
:page
]).
per
(
12
)
@images
=
@q
.
result
(
distinct:
true
).
with_attached_file
.
order
(
created_at: :desc
).
page
(
params
[
:page
]).
per
(
12
)
render
inertia:
"images/Index"
,
props:
{
render
inertia:
"images/Index"
,
props:
{
...
@@ -23,8 +22,9 @@ class ImagesController < ApplicationController
...
@@ -23,8 +22,9 @@ class ImagesController < ApplicationController
def
show
def
show
render
inertia:
"images/Show"
,
props:
{
render
inertia:
"images/Show"
,
props:
{
image:
@image
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
image:
@image
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
can_edit:
Current
.
user
==
@image
.
user
,
can_edit:
Current
.
user
==
@image
.
user
&&
@image
.
status
==
"pending"
,
can_approve:
Current
.
user
&
.
admin?
can_approve:
Current
.
user
&
.
admin?
,
can_delete:
Current
.
user
==
@image
.
user
&&
@image
.
status
==
"pending"
||
Current
.
user
&
.
admin?
}
}
end
end
...
@@ -60,6 +60,10 @@ class ImagesController < ApplicationController
...
@@ -60,6 +60,10 @@ class ImagesController < ApplicationController
def
update
def
update
authorize_user
authorize_user
unless
@image
.
status
==
"pending"
||
Current
.
user
&
.
admin?
return
redirect_to
image_path
(
@image
),
notice:
"Image was successfully updated."
end
if
@image
.
update
(
image_params
)
if
@image
.
update
(
image_params
)
if
params
[
:tags
].
present?
if
params
[
:tags
].
present?
@image
.
tags
.
clear
@image
.
tags
.
clear
...
@@ -80,17 +84,6 @@ class ImagesController < ApplicationController
...
@@ -80,17 +84,6 @@ class ImagesController < ApplicationController
redirect_to
images_path
,
notice:
"Image was successfully deleted."
redirect_to
images_path
,
notice:
"Image was successfully deleted."
end
end
def
search
@q
=
Image
.
approved
.
includes
(
:user
,
:tags
).
ransack
(
params
[
:q
])
@images
=
@q
.
result
(
distinct:
true
).
with_attached_file
.
order
(
created_at: :desc
).
page
(
params
[
:page
]).
per
(
12
)
render
inertia:
"images/Search"
,
props:
{
images:
@images
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
filters:
params
[
:q
]
||
{},
tags:
Tag
.
all
.
pluck
(
:name
)
}
end
def
approve
def
approve
@image
.
approved!
@image
.
approved!
redirect_to
image_path
(
@image
),
notice:
"Image was successfully approved."
redirect_to
image_path
(
@image
),
notice:
"Image was successfully approved."
...
@@ -104,7 +97,7 @@ class ImagesController < ApplicationController
...
@@ -104,7 +97,7 @@ class ImagesController < ApplicationController
private
private
def
set_image
def
set_image
@image
=
Image
.
find
(
params
[
:id
])
@image
=
Current
.
user
.
images
.
find
(
params
[
:id
])
end
end
def
image_params
def
image_params
...
...
app/frontend/components/ImageCard.jsx
deleted
100644 → 0
View file @
75d4a205
import
React
from
'react'
import
{
Link
}
from
'@inertiajs/react'
export
default
function
ImageCard
({
image
,
showActions
=
true
})
{
const
statusColors
=
{
pending
:
'bg-yellow-100 text-yellow-800'
,
approved
:
'bg-green-100 text-green-800'
,
rejected
:
'bg-red-100 text-red-800'
,
}
return
(
<
div
className=
"bg-white overflow-hidden shadow rounded-lg"
>
<
div
className=
"relative pb-[75%]"
>
<
img
src=
{
image
.
file_url
}
alt=
{
image
.
title
}
className=
"absolute h-full w-full object-cover"
/>
</
div
>
<
div
className=
"px-4 py-4"
>
<
h3
className=
"text-lg font-medium text-gray-900 truncate"
title=
{
image
.
title
}
>
{
image
.
title
}
</
h3
>
<
p
className=
"mt-1 text-sm text-gray-500"
>
Uploaded by
{
image
.
user
?.
name
||
'Unknown'
}
on
{
' '
}
{
new
Date
(
image
.
created_at
).
toLocaleDateString
()
}
</
p
>
{
image
.
status
&&
(
<
span
className=
{
`inline-flex mt-2 items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${statusColors[image.status] || 'bg-gray-100 text-gray-800'}`
}
>
{
image
.
status
.
charAt
(
0
).
toUpperCase
()
+
image
.
status
.
slice
(
1
)
}
</
span
>
)
}
{
image
.
tags
&&
image
.
tags
.
length
>
0
&&
(
<
div
className=
"mt-2 flex flex-wrap gap-1"
>
{
image
.
tags
.
slice
(
0
,
3
).
map
((
tag
)
=>
(
<
span
key=
{
tag
.
id
}
className=
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-800"
>
{
tag
.
name
}
</
span
>
))
}
{
image
.
tags
.
length
>
3
&&
(
<
span
className=
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800"
>
+
{
image
.
tags
.
length
-
3
}
more
</
span
>
)
}
</
div
>
)
}
{
showActions
&&
(
<
div
className=
"mt-4 flex space-x-2"
>
<
Link
href=
{
`/images/${image.id}`
}
className=
"inline-flex items-center px-2.5 py-1.5 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
View
</
Link
>
<
Link
href=
{
`/images/${image.id}/edit`
}
className=
"inline-flex items-center px-2.5 py-1.5 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Edit
</
Link
>
</
div
>
)
}
</
div
>
</
div
>
)
}
app/frontend/components/images/ComImageCard.jsx
0 → 100644
View file @
c139b140
import
{
Link
}
from
"@inertiajs/react"
;
import
ComImageStatusTag
from
"./ComImageStatusTag"
;
export
function
ComImageCard
({
image
,
path
,
showActions
=
false
})
{
return
(
<
div
className=
"bg-white overflow-hidden shadow rounded-lg flex flex-col"
>
<
div
className=
"relative pb-[75%]"
>
<
img
loading=
"lazy"
src=
{
image
.
file_url
}
alt=
{
image
.
title
}
className=
"absolute h-full w-full object-cover"
/>
<
div
className=
"absolute top-2 right-2"
>
<
ComImageStatusTag
image=
{
image
}
/>
</
div
>
</
div
>
<
div
className=
"px-4 py-4 flex flex-col justify-between flex-grow"
>
<
h3
className=
"text-lg font-medium text-gray-900 truncate"
>
{
image
.
title
}
</
h3
>
<
div
className=
"mt-2 flex flex-wrap gap-1"
>
{
image
.
tags
.
map
((
tag
)
=>
(
<
span
key=
{
`tag-${tag.id}`
}
className=
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800"
>
{
tag
.
name
}
</
span
>
))
}
</
div
>
<
div
className=
"mt-4 flex justify-between"
>
<
Link
href=
{
`${path}/${image.id}`
}
className=
"text-sm text-indigo-600 hover:text-indigo-900"
>
查看详情
</
Link
>
<
span
className=
"text-sm text-gray-500"
>
{
new
Date
(
image
.
created_at
).
toLocaleDateString
()
}
</
span
>
</
div
>
</
div
>
</
div
>
)
}
\ No newline at end of file
app/frontend/components/images/ComImageIndex.jsx
View file @
c139b140
...
@@ -3,6 +3,7 @@ import { Head, Link, router } from '@inertiajs/react'
...
@@ -3,6 +3,7 @@ import { Head, Link, router } from '@inertiajs/react'
import
*
as
Form
from
'@radix-ui/react-form'
import
*
as
Form
from
'@radix-ui/react-form'
import
*
as
Collapsible
from
'@radix-ui/react-collapsible'
import
*
as
Collapsible
from
'@radix-ui/react-collapsible'
import
{
uniqBy
}
from
'lodash-es'
import
{
uniqBy
}
from
'lodash-es'
import
{
ComImageCard
}
from
'./ComImageCard'
export
default
function
ComImageIndex
({
title
,
description
,
path
,
images
,
pagination
,
filters
,
auth
,
backLink
,
actionButton
})
{
export
default
function
ComImageIndex
({
title
,
description
,
path
,
images
,
pagination
,
filters
,
auth
,
backLink
,
actionButton
})
{
const
[
allImages
,
setAllImages
]
=
useState
(
images
||
[])
const
[
allImages
,
setAllImages
]
=
useState
(
images
||
[])
...
@@ -164,7 +165,7 @@ export default function ComImageIndex({ title, description, path, images, pagina
...
@@ -164,7 +165,7 @@ export default function ComImageIndex({ title, description, path, images, pagina
href=
{
`${path}/new`
}
href=
{
`${path}/new`
}
className=
"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
className=
"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
>
上传新
文件
上传新
图片
</
Link
>
</
Link
>
)
}
)
}
</
div
>
</
div
>
...
@@ -286,59 +287,13 @@ export default function ComImageIndex({ title, description, path, images, pagina
...
@@ -286,59 +287,13 @@ export default function ComImageIndex({ title, description, path, images, pagina
</
div
>
</
div
>
)
}
)
}
{
allImages
.
map
((
image
,
index
)
=>
(
{
allImages
.
map
((
image
,
index
)
=>
(
<
div
<
ComImageCard
key=
{
image
.
id
}
key=
{
image
.
id
}
ref=
{
index
===
allImages
.
length
-
1
?
lastImageElementRef
:
null
}
ref=
{
index
===
allImages
.
length
-
1
?
lastImageElementRef
:
null
}
className=
"bg-white overflow-hidden shadow rounded-lg"
image=
{
image
}
>
path=
{
path
}
<
div
className=
"relative pb-[75%]"
>
// showActions=
{
showActions
}
<
img
/>
loading=
"lazy"
src=
{
image
.
file_url
}
alt=
{
image
.
title
}
className=
"absolute h-full w-full object-cover"
/>
<
div
className=
"absolute top-2 right-2"
>
<
span
className=
{
`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
image.status === 'pending'
? 'bg-yellow-100 text-yellow-800'
: image.status === 'approved'
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`
}
>
{
image
.
status
}
</
span
>
</
div
>
</
div
>
<
div
className=
"px-4 py-4"
>
<
h3
className=
"text-lg font-medium text-gray-900 truncate"
>
{
image
.
title
}
</
h3
>
<
div
className=
"mt-2 flex flex-wrap gap-1"
>
{
image
.
tags
.
map
((
tag
)
=>
(
<
span
key=
{
`tag-${tag.id}`
}
className=
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800"
>
{
tag
.
name
}
</
span
>
))
}
</
div
>
<
div
className=
"mt-4 flex justify-between"
>
<
Link
href=
{
`${path}/${image.id}`
}
className=
"text-sm text-indigo-600 hover:text-indigo-900"
>
查看详情
</
Link
>
<
span
className=
"text-sm text-gray-500"
>
{
new
Date
(
image
.
created_at
).
toLocaleDateString
()
}
</
span
>
</
div
>
</
div
>
</
div
>
))
}
))
}
</
div
>
</
div
>
)
:
(
)
:
(
...
@@ -367,17 +322,17 @@ export default function ComImageIndex({ title, description, path, images, pagina
...
@@ -367,17 +322,17 @@ export default function ComImageIndex({ title, description, path, images, pagina
/>
/>
</
svg
>
</
svg
>
<
h3
className=
"mt-2 text-sm font-medium text-gray-900"
>
<
h3
className=
"mt-2 text-sm font-medium text-gray-900"
>
暂无
文件
暂无
图片
</
h3
>
</
h3
>
<
p
className=
"mt-1 text-sm text-gray-500"
>
<
p
className=
"mt-1 text-sm text-gray-500"
>
开始上传
文件
开始上传
图片
</
p
>
</
p
>
<
div
className=
"mt-6"
>
<
div
className=
"mt-6"
>
<
Link
<
Link
href=
{
`${path}/new`
}
href=
{
`${path}/new`
}
className=
"inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
className=
"inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
>
上传新
文件
上传新
图片
</
Link
>
</
Link
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
app/frontend/components/images/ComImageShow.jsx
0 → 100644
View file @
c139b140
import
{
useState
}
from
'react'
import
{
Link
}
from
'@inertiajs/react'
import
*
as
Dialog
from
'@radix-ui/react-dialog'
import
ComImageStatusTag
from
'./ComImageStatusTag'
export
default
function
ComImageShow
({
path
,
image
,
can_edit
,
can_approve
,
isAdmin
=
false
})
{
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
false
)
return
(
<
div
className=
"bg-white shadow overflow-hidden sm:rounded-lg"
>
<
div
className=
"px-4 py-5 sm:px-6 flex justify-between items-center"
>
<
div
>
<
h1
className=
"text-2xl font-bold text-gray-900"
>
{
image
.
title
}
</
h1
>
<
p
className=
"mt-1 max-w-2xl text-sm text-gray-500"
>
由
{
image
.
user
.
name
}
在
{
' '
}
{
new
Date
(
image
.
created_at
).
toLocaleDateString
()
}{
' '
}
上传
</
p
>
</
div
>
<
div
className=
"flex space-x-2"
>
{
can_edit
&&
(
<
Link
href=
{
`${path}/edit`
}
className=
"inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
编辑
</
Link
>
)
}
{
can_approve
&&
image
.
status
===
'pending'
&&
(
<>
<
Link
href=
{
`${path}/approve`
}
method=
"patch"
as=
"button"
className=
"inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
>
批准
</
Link
>
<
Link
href=
{
`${path}/reject`
}
method=
"patch"
as=
"button"
className=
"inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
拒绝
</
Link
>
</>
)
}
{
can_approve
&&
(
<
Link
href=
{
`${path}`
}
method=
"delete"
as=
"button"
className=
"inline-flex items-center px-3 py-1.5 border border-gray-300 text-sm font-medium rounded-md shadow-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
onClick=
{
(
e
)
=>
{
if
(
!
confirm
(
'确定要删除这张图片吗?'
))
{
e
.
preventDefault
()
}
}
}
>
删除
</
Link
>
)
}
</
div
>
</
div
>
<
div
className=
"border-t border-gray-200"
>
<
div
className=
"flex flex-col md:flex-row"
>
<
div
className=
"md:w-2/3 p-4"
>
<
div
className=
"relative pb-[75%]"
>
<
img
src=
{
image
.
file_url
}
alt=
{
image
.
title
}
className=
"absolute h-full w-full object-contain cursor-pointer"
onClick=
{
()
=>
setIsModalOpen
(
true
)
}
/>
</
div
>
</
div
>
<
div
className=
"md:w-1/3 p-4 border-t md:border-t-0 md:border-l border-gray-200"
>
<
div
className=
"mb-4"
>
<
h3
className=
"text-lg font-medium text-gray-900"
>
审核状态
</
h3
>
<
ComImageStatusTag
image=
{
image
}
/>
</
div
>
<
div
className=
"mb-4"
>
<
h3
className=
"text-lg font-medium text-gray-900"
>
标签
</
h3
>
<
div
className=
"mt-2 flex flex-wrap gap-1"
>
{
image
.
tags
.
length
>
0
?
(
image
.
tags
.
map
((
tag
)
=>
(
<
span
key=
{
tag
.
id
}
className=
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800"
>
{
tag
.
name
}
</
span
>
))
)
:
(
<
p
className=
"text-sm text-gray-500"
>
无标签
</
p
>
)
}
</
div
>
</
div
>
<
div
className=
"mb-4"
>
<
h3
className=
"text-lg font-medium text-gray-900"
>
上传者
</
h3
>
<
p
className=
"text-sm text-gray-500"
>
{
image
.
user
.
name
}
</
p
>
<
p
className=
"text-sm text-gray-500"
>
{
image
.
user
.
email_address
}
</
p
>
</
div
>
<
div
className=
"mb-4"
>
<
h3
className=
"text-lg font-medium text-gray-900"
>
上传日期
</
h3
>
<
p
className=
"text-sm text-gray-500"
>
{
new
Date
(
image
.
created_at
).
toLocaleDateString
()
}
</
p
>
</
div
>
</
div
>
</
div
>
</
div
>
{
/* 全屏图片模态框 */
}
<
Dialog
.
Root
open=
{
isModalOpen
}
onOpenChange=
{
setIsModalOpen
}
>
<
Dialog
.
Portal
>
<
Dialog
.
Overlay
className=
"fixed inset-0 bg-black/50"
/>
<
Dialog
.
Content
className=
"fixed inset-0 flex items-center justify-center"
>
<
div
className=
"relative w-full h-full max-w-screen-xl max-h-screen p-4"
>
<
img
src=
{
image
.
file_url
}
alt=
{
image
.
title
}
className=
"w-full h-full object-contain"
/>
<
Dialog
.
Close
asChild
>
<
button
className=
"absolute top-4 right-4 p-2 rounded-full bg-white/80 hover:bg-white text-gray-800"
aria
-
label=
"关闭"
>
<
svg
xmlns=
"http://www.w3.org/2000/svg"
className=
"h-6 w-6"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
>
<
path
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
{
2
}
d=
"M6 18L18 6M6 6l12 12"
/>
</
svg
>
</
button
>
</
Dialog
.
Close
>
</
div
>
</
Dialog
.
Content
>
</
Dialog
.
Portal
>
</
Dialog
.
Root
>
</
div
>
)
}
app/frontend/components/images/ComImageStatusTag.jsx
0 → 100644
View file @
c139b140
export
default
function
ComImageStatusTag
({
image
})
{
return
(
<
span
className=
{
`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
image.status === 'pending'
? 'bg-yellow-100 text-yellow-800'
: image.status === 'approved'
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`
}
>
{
image
.
status
===
'pending'
?
'待审核'
:
image
.
status
===
'approved'
?
'已批准'
:
'已拒绝'
}
</
span
>
)
}
\ No newline at end of file
app/frontend/components/tags/ComTagsIndex.jsx
View file @
c139b140
...
@@ -159,7 +159,7 @@ export default function ComTagsIndex({
...
@@ -159,7 +159,7 @@ export default function ComTagsIndex({
href=
{
`${path}/${tag.id}`
}
href=
{
`${path}/${tag.id}`
}
method=
"delete"
method=
"delete"
as=
"button"
as=
"button"
data=
{
{
confirm
:
"确定要删除这个标签吗?这将从所有关联的
文件
中移除该标签。"
}
}
data=
{
{
confirm
:
"确定要删除这个标签吗?这将从所有关联的
图片
中移除该标签。"
}
}
className=
"text-xs text-red-600 hover:text-red-900"
className=
"text-xs text-red-600 hover:text-red-900"
>
>
删除
删除
...
@@ -206,7 +206,7 @@ export default function ComTagsIndex({
...
@@ -206,7 +206,7 @@ export default function ComTagsIndex({
创建新标签
创建新标签
</
Dialog
.
Title
>
</
Dialog
.
Title
>
<
Dialog
.
Description
className=
"mt-2 text-sm text-gray-500"
>
<
Dialog
.
Description
className=
"mt-2 text-sm text-gray-500"
>
添加一个新标签用于
文件
分类。
添加一个新标签用于
图片
分类。
</
Dialog
.
Description
>
</
Dialog
.
Description
>
<
Form
.
Root
className=
"mt-4"
onSubmit=
{
handleCreateSubmit
}
>
<
Form
.
Root
className=
"mt-4"
onSubmit=
{
handleCreateSubmit
}
>
...
...
app/frontend/pages/Layout.jsx
View file @
c139b140
...
@@ -13,7 +13,7 @@ export default function Layout({ children, user, title }) {
...
@@ -13,7 +13,7 @@ export default function Layout({ children, user, title }) {
<
div
className=
"flex"
>
<
div
className=
"flex"
>
<
div
className=
"flex-shrink-0 flex items-center"
>
<
div
className=
"flex-shrink-0 flex items-center"
>
<
Link
href=
"/"
className=
"text-xl font-bold text-indigo-600"
>
<
Link
href=
"/"
className=
"text-xl font-bold text-indigo-600"
>
文件
管理
图片
管理
</
Link
>
</
Link
>
</
div
>
</
div
>
<
nav
className=
"hidden sm:ml-6 sm:flex sm:space-x-8"
>
<
nav
className=
"hidden sm:ml-6 sm:flex sm:space-x-8"
>
...
@@ -62,7 +62,7 @@ export default function Layout({ children, user, title }) {
...
@@ -62,7 +62,7 @@ export default function Layout({ children, user, title }) {
</
DropdownMenu
.
Item
>
</
DropdownMenu
.
Item
>
<
DropdownMenu
.
Item
className=
"text-sm text-gray-700 px-4 py-2 rounded hover:bg-gray-100"
>
<
DropdownMenu
.
Item
className=
"text-sm text-gray-700 px-4 py-2 rounded hover:bg-gray-100"
>
<
Link
href=
"/images"
className=
"block w-full text-left"
>
<
Link
href=
"/images"
className=
"block w-full text-left"
>
我的
文件
我的
图片
</
Link
>
</
Link
>
</
DropdownMenu
.
Item
>
</
DropdownMenu
.
Item
>
<
DropdownMenu
.
Separator
className=
"h-px bg-gray-200 my-1"
/>
<
DropdownMenu
.
Separator
className=
"h-px bg-gray-200 my-1"
/>
...
@@ -185,7 +185,7 @@ export default function Layout({ children, user, title }) {
...
@@ -185,7 +185,7 @@ export default function Layout({ children, user, title }) {
href=
"/images"
href=
"/images"
className=
"block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100"
className=
"block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100"
>
>
我的
文件
我的
图片
</
Link
>
</
Link
>
<
Link
<
Link
href=
"/session"
href=
"/session"
...
...
app/frontend/pages/admin/images/Index.jsx
View file @
c139b140
...
@@ -5,11 +5,11 @@ import ComImageIndex from '../../../components/images/ComImageIndex'
...
@@ -5,11 +5,11 @@ import ComImageIndex from '../../../components/images/ComImageIndex'
export
default
function
AdminImagesIndex
({
auth
,
images
,
pagination
,
filters
})
{
export
default
function
AdminImagesIndex
({
auth
,
images
,
pagination
,
filters
})
{
return
(
return
(
<
Layout
user=
{
auth
.
user
}
>
<
Layout
user=
{
auth
.
user
}
>
<
Head
title=
"
文件
管理"
/>
<
Head
title=
"
图片
管理"
/>
<
ComImageIndex
<
ComImageIndex
auth=
{
auth
}
auth=
{
auth
}
title=
"
文件
管理"
title=
"
图片
管理"
description=
"审批并管理用户上传的
文件
"
description=
"审批并管理用户上传的
图片
"
path=
"/admin/images"
path=
"/admin/images"
images=
{
images
}
images=
{
images
}
pagination=
{
pagination
}
pagination=
{
pagination
}
...
...
app/frontend/pages/admin/images/Show.jsx
0 → 100644
View file @
c139b140
import
{
Head
}
from
'@inertiajs/react'
import
Layout
from
'../../Layout'
import
ComImageShow
from
'../../../components/images/ComImageShow'
export
default
function
AdminImagesShow
({
image
,
auth
})
{
return
(
<
Layout
user=
{
auth
}
>
<
Head
title=
{
`管理 - ${image.title}`
}
/>
<
ComImageShow
path=
{
`/admin/images/${image.id}`
}
image=
{
image
}
can_edit=
{
true
}
can_approve=
{
true
}
can_delete=
{
true
}
isAdmin=
{
true
}
/>
</
Layout
>
)
}
app/frontend/pages/admin/tags/Index.jsx
View file @
c139b140
...
@@ -8,7 +8,7 @@ export default function AdminTagsIndex({ tags, auth, errors = {} }) {
...
@@ -8,7 +8,7 @@ export default function AdminTagsIndex({ tags, auth, errors = {} }) {
<
Head
title=
"标签管理"
/>
<
Head
title=
"标签管理"
/>
<
ComTagsIndex
<
ComTagsIndex
title=
"标签管理"
title=
"标签管理"
description=
"创建、编辑和删除
文件
分类标签"
description=
"创建、编辑和删除
图片
分类标签"
tags=
{
tags
}
tags=
{
tags
}
auth=
{
auth
}
auth=
{
auth
}
isAdmin=
{
true
}
isAdmin=
{
true
}
...
...
app/frontend/pages/admin/tags/images/Index.jsx
View file @
c139b140
...
@@ -5,11 +5,11 @@ import ComImageIndex from '../../../../components/images/ComImageIndex'
...
@@ -5,11 +5,11 @@ import ComImageIndex from '../../../../components/images/ComImageIndex'
export
default
function
TagsImagesIndex
({
auth
,
tag
,
images
,
pagination
,
filters
})
{
export
default
function
TagsImagesIndex
({
auth
,
tag
,
images
,
pagination
,
filters
})
{
return
(
return
(
<
Layout
user=
{
auth
.
user
}
>
<
Layout
user=
{
auth
.
user
}
>
<
Head
title=
{
`${tag.name} - 标签
文件
`
}
/>
<
Head
title=
{
`${tag.name} - 标签
图片
`
}
/>
<
ComImageIndex
<
ComImageIndex
auth=
{
auth
}
auth=
{
auth
}
title=
{
tag
.
name
}
title=
{
tag
.
name
}
description=
{
`查看关联
文件
`
}
description=
{
`查看关联
图片
`
}
path=
{
`/admin/tags/${tag.id}/images`
}
path=
{
`/admin/tags/${tag.id}/images`
}
images=
{
images
}
images=
{
images
}
pagination=
{
pagination
}
pagination=
{
pagination
}
...
@@ -23,7 +23,7 @@ export default function TagsImagesIndex({ auth, tag, images, pagination, filters
...
@@ -23,7 +23,7 @@ export default function TagsImagesIndex({ auth, tag, images, pagination, filters
href=
{
`/admin/tags/${tag.id}/images/new`
}
href=
{
`/admin/tags/${tag.id}/images/new`
}
className=
"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
className=
"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
>
添加新
文件
到此标签
添加新
图片
到此标签
</
Link
>
</
Link
>
)
}
)
}
/>
/>
...
...
app/frontend/pages/admin/tags/images/New.jsx
View file @
c139b140
...
@@ -32,7 +32,7 @@ export default function New({ auth, tag, errors = {} }) {
...
@@ -32,7 +32,7 @@ export default function New({ auth, tag, errors = {} }) {
return
(
return
(
<
Layout
user=
{
auth
.
user
}
>
<
Layout
user=
{
auth
.
user
}
>
<
Head
title=
{
`添加
文件
到标签: ${tag.name}`
}
/>
<
Head
title=
{
`添加
图片
到标签: ${tag.name}`
}
/>
<
div
className=
"bg-white shadow overflow-hidden sm:rounded-lg"
>
<
div
className=
"bg-white shadow overflow-hidden sm:rounded-lg"
>
<
div
className=
"px-4 py-5 sm:px-6"
>
<
div
className=
"px-4 py-5 sm:px-6"
>
<
div
className=
"flex items-center mb-4"
>
<
div
className=
"flex items-center mb-4"
>
...
@@ -43,12 +43,12 @@ export default function New({ auth, tag, errors = {} }) {
...
@@ -43,12 +43,12 @@ export default function New({ auth, tag, errors = {} }) {
<
svg
xmlns=
"http://www.w3.org/2000/svg"
className=
"h-5 w-5 mr-1"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
>
<
svg
xmlns=
"http://www.w3.org/2000/svg"
className=
"h-5 w-5 mr-1"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
>
<
path
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
{
2
}
d=
"M10 19l-7-7m0 0l7-7m-7 7h18"
/>
<
path
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
{
2
}
d=
"M10 19l-7-7m0 0l7-7m-7 7h18"
/>
</
svg
>
</
svg
>
返回标签
文件
列表
返回标签
图片
列表
</
Link
>
</
Link
>
</
div
>
</
div
>
<
h1
className=
"text-2xl font-bold text-gray-900"
>
添加
文件
到标签:
{
tag
.
name
}
</
h1
>
<
h1
className=
"text-2xl font-bold text-gray-900"
>
添加
图片
到标签:
{
tag
.
name
}
</
h1
>
<
p
className=
"mt-1 max-w-2xl text-sm text-gray-500"
>
<
p
className=
"mt-1 max-w-2xl text-sm text-gray-500"
>
上传新
文件
并自动添加到标签 "
{
tag
.
name
}
"
上传新
图片
并自动添加到标签 "
{
tag
.
name
}
"
</
p
>
</
p
>
</
div
>
</
div
>
<
div
className=
"border-t border-gray-200 px-4 py-5 sm:px-6"
>
<
div
className=
"border-t border-gray-200 px-4 py-5 sm:px-6"
>
...
@@ -76,7 +76,7 @@ export default function New({ auth, tag, errors = {} }) {
...
@@ -76,7 +76,7 @@ export default function New({ auth, tag, errors = {} }) {
<
Form
.
Field
name=
"file"
className=
"space-y-2"
>
<
Form
.
Field
name=
"file"
className=
"space-y-2"
>
<
Form
.
Label
className=
"block text-sm font-medium text-gray-700"
>
<
Form
.
Label
className=
"block text-sm font-medium text-gray-700"
>
文件
图片
</
Form
.
Label
>
</
Form
.
Label
>
<
div
className=
"mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md"
>
<
div
className=
"mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md"
>
<
div
className=
"space-y-1 text-center"
>
<
div
className=
"space-y-1 text-center"
>
...
@@ -109,7 +109,7 @@ export default function New({ auth, tag, errors = {} }) {
...
@@ -109,7 +109,7 @@ export default function New({ auth, tag, errors = {} }) {
htmlFor=
"file-upload"
htmlFor=
"file-upload"
className=
"relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
className=
"relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
>
>
<
span
>
上传
文件
</
span
>
<
span
>
上传
图片
</
span
>
<
Form
.
Control
asChild
>
<
Form
.
Control
asChild
>
<
input
<
input
id=
"file-upload"
id=
"file-upload"
...
...
app/frontend/pages/images/Edit.jsx
View file @
c139b140
...
@@ -19,14 +19,14 @@ export default function Edit({ image, tags, auth, errors = {} }) {
...
@@ -19,14 +19,14 @@ export default function Edit({ image, tags, auth, errors = {} }) {
<
Head
title=
{
`Edit ${image.title}`
}
/>
<
Head
title=
{
`Edit ${image.title}`
}
/>
<
div
className=
"bg-white shadow overflow-hidden sm:rounded-lg"
>
<
div
className=
"bg-white shadow overflow-hidden sm:rounded-lg"
>
<
div
className=
"px-4 py-5 sm:px-6"
>
<
div
className=
"px-4 py-5 sm:px-6"
>
<
h1
className=
"text-2xl font-bold text-gray-900"
>
Edit Image
</
h1
>
<
h1
className=
"text-2xl font-bold text-gray-900"
>
编辑图片
</
h1
>
<
p
className=
"mt-1 max-w-2xl text-sm text-gray-500"
>
<
p
className=
"mt-1 max-w-2xl text-sm text-gray-500"
>
Update image details
更新图片信息
</
p
>
</
p
>
</
div
>
</
div
>
<
div
className=
"border-t border-gray-200 px-4 py-5 sm:px-6"
>
<
div
className=
"border-t border-gray-200 px-4 py-5 sm:px-6"
>
<
div
className=
"mb-6"
>
<
div
className=
"mb-6"
>
<
im
g
<
im
age
src=
{
image
.
file_url
}
src=
{
image
.
file_url
}
alt=
{
image
.
title
}
alt=
{
image
.
title
}
className=
"max-h-64 mx-auto object-contain"
className=
"max-h-64 mx-auto object-contain"
...
@@ -36,7 +36,7 @@ export default function Edit({ image, tags, auth, errors = {} }) {
...
@@ -36,7 +36,7 @@ export default function Edit({ image, tags, auth, errors = {} }) {
<
Form
.
Root
className=
"space-y-6"
onSubmit=
{
handleSubmit
}
>
<
Form
.
Root
className=
"space-y-6"
onSubmit=
{
handleSubmit
}
>
<
Form
.
Field
name=
"title"
className=
"space-y-2"
>
<
Form
.
Field
name=
"title"
className=
"space-y-2"
>
<
Form
.
Label
className=
"block text-sm font-medium text-gray-700"
>
<
Form
.
Label
className=
"block text-sm font-medium text-gray-700"
>
Title
标题
</
Form
.
Label
>
</
Form
.
Label
>
<
Form
.
Control
asChild
>
<
Form
.
Control
asChild
>
<
input
<
input
...
@@ -57,7 +57,7 @@ export default function Edit({ image, tags, auth, errors = {} }) {
...
@@ -57,7 +57,7 @@ export default function Edit({ image, tags, auth, errors = {} }) {
<
Form
.
Field
name=
"tags"
className=
"space-y-2"
>
<
Form
.
Field
name=
"tags"
className=
"space-y-2"
>
<
Form
.
Label
className=
"block text-sm font-medium text-gray-700"
>
<
Form
.
Label
className=
"block text-sm font-medium text-gray-700"
>
Tags
标签
</
Form
.
Label
>
</
Form
.
Label
>
<
Form
.
Control
asChild
>
<
Form
.
Control
asChild
>
<
input
<
input
...
@@ -66,11 +66,11 @@ export default function Edit({ image, tags, auth, errors = {} }) {
...
@@ -66,11 +66,11 @@ export default function Edit({ image, tags, auth, errors = {} }) {
value=
{
data
.
tags
}
value=
{
data
.
tags
}
onChange=
{
(
e
)
=>
setData
(
'tags'
,
e
.
target
.
value
)
}
onChange=
{
(
e
)
=>
setData
(
'tags'
,
e
.
target
.
value
)
}
className=
"mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
className=
"mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
placeholder=
"
Enter tags separated by commas (e.g. nature, landscape, mountains)
"
placeholder=
""
/>
/>
</
Form
.
Control
>
</
Form
.
Control
>
<
p
className=
"text-xs text-gray-500"
>
<
p
className=
"text-xs text-gray-500"
>
Enter tags separated by commas
{
/* 请输入标签 */
}
</
p
>
</
p
>
</
Form
.
Field
>
</
Form
.
Field
>
...
@@ -79,7 +79,7 @@ export default function Edit({ image, tags, auth, errors = {} }) {
...
@@ -79,7 +79,7 @@ export default function Edit({ image, tags, auth, errors = {} }) {
href=
{
`/images/${image.id}`
}
href=
{
`/images/${image.id}`
}
className=
"inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
className=
"inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
>
Cancel
取消
</
a
>
</
a
>
<
Form
.
Submit
asChild
>
<
Form
.
Submit
asChild
>
<
button
<
button
...
@@ -87,7 +87,7 @@ export default function Edit({ image, tags, auth, errors = {} }) {
...
@@ -87,7 +87,7 @@ export default function Edit({ image, tags, auth, errors = {} }) {
disabled=
{
processing
}
disabled=
{
processing
}
className=
"inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
className=
"inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
>
>
{
processing
?
'
Saving...'
:
'Save Changes
'
}
{
processing
?
'
保存中...'
:
'保存
'
}
</
button
>
</
button
>
</
Form
.
Submit
>
</
Form
.
Submit
>
</
div
>
</
div
>
...
...
app/frontend/pages/images/Index.jsx
View file @
c139b140
...
@@ -5,11 +5,11 @@ import { Head } from '@inertiajs/react'
...
@@ -5,11 +5,11 @@ import { Head } from '@inertiajs/react'
export
default
function
Index
({
auth
,
images
,
pagination
,
filters
})
{
export
default
function
Index
({
auth
,
images
,
pagination
,
filters
})
{
return
(
return
(
<
Layout
user=
{
auth
}
>
<
Layout
user=
{
auth
}
>
<
Head
title=
"我的
文件
"
/>
<
Head
title=
"我的
图片
"
/>
<
ComImageIndex
<
ComImageIndex
auth=
{
auth
}
auth=
{
auth
}
title=
"我的
文件
"
title=
"我的
图片
"
description=
"管理已上传的
文件
"
description=
"管理已上传的
图片
"
path=
"/images"
path=
"/images"
images=
{
images
}
images=
{
images
}
pagination=
{
pagination
}
pagination=
{
pagination
}
...
...
app/frontend/pages/images/New.jsx
View file @
c139b140
...
@@ -33,10 +33,10 @@ export default function New({ auth, errors = {} }) {
...
@@ -33,10 +33,10 @@ export default function New({ auth, errors = {} }) {
return
(
return
(
<
Layout
user=
{
auth
.
user
}
>
<
Layout
user=
{
auth
.
user
}
>
<
Head
title=
"上传新
文件
"
/>
<
Head
title=
"上传新
图片
"
/>
<
div
className=
"bg-white shadow overflow-hidden sm:rounded-lg"
>
<
div
className=
"bg-white shadow overflow-hidden sm:rounded-lg"
>
<
div
className=
"px-4 py-5 sm:px-6"
>
<
div
className=
"px-4 py-5 sm:px-6"
>
<
h1
className=
"text-2xl font-bold text-gray-900"
>
上传新
文件
</
h1
>
<
h1
className=
"text-2xl font-bold text-gray-900"
>
上传新
图片
</
h1
>
<
p
className=
"mt-1 max-w-2xl text-sm text-gray-500"
>
<
p
className=
"mt-1 max-w-2xl text-sm text-gray-500"
>
上传并等待审核
上传并等待审核
</
p
>
</
p
>
...
@@ -66,7 +66,7 @@ export default function New({ auth, errors = {} }) {
...
@@ -66,7 +66,7 @@ export default function New({ auth, errors = {} }) {
<
Form
.
Field
name=
"file"
className=
"space-y-2"
>
<
Form
.
Field
name=
"file"
className=
"space-y-2"
>
<
Form
.
Label
className=
"block text-sm font-medium text-gray-700"
>
<
Form
.
Label
className=
"block text-sm font-medium text-gray-700"
>
文件
图片
</
Form
.
Label
>
</
Form
.
Label
>
<
div
className=
"mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md"
>
<
div
className=
"mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md"
>
<
div
className=
"space-y-1 text-center"
>
<
div
className=
"space-y-1 text-center"
>
...
@@ -99,7 +99,7 @@ export default function New({ auth, errors = {} }) {
...
@@ -99,7 +99,7 @@ export default function New({ auth, errors = {} }) {
htmlFor=
"file-upload"
htmlFor=
"file-upload"
className=
"relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
className=
"relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
>
>
<
span
>
上传
文件
</
span
>
<
span
>
上传
图片
</
span
>
<
Form
.
Control
asChild
>
<
Form
.
Control
asChild
>
<
input
<
input
id=
"file-upload"
id=
"file-upload"
...
@@ -150,7 +150,7 @@ export default function New({ auth, errors = {} }) {
...
@@ -150,7 +150,7 @@ export default function New({ auth, errors = {} }) {
disabled=
{
processing
}
disabled=
{
processing
}
className=
"inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
className=
"inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
>
>
{
processing
?
'上传中...'
:
'上传
文件
'
}
{
processing
?
'上传中...'
:
'上传
图片
'
}
</
button
>
</
button
>
</
Form
.
Submit
>
</
Form
.
Submit
>
</
div
>
</
div
>
...
...
app/frontend/pages/images/Show.jsx
View file @
c139b140
import
{
Head
,
Link
}
from
'@inertiajs/react'
import
{
Head
}
from
'@inertiajs/react'
import
Layout
from
'../Layout'
import
Layout
from
'../Layout'
import
*
as
Dialog
from
'@radix-ui/react-dialog'
import
ComImageShow
from
'../../components/images/ComImageShow'
import
{
useState
}
from
'react'
export
default
function
Show
({
image
,
can_edit
,
can_approve
,
auth
})
{
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
false
)
export
default
function
Show
({
image
,
can_edit
,
can_approve
,
can_delete
,
auth
})
{
return
(
return
(
<
Layout
user=
{
auth
.
user
}
>
<
Layout
user=
{
auth
}
>
<
Head
title=
{
image
.
title
}
/>
<
Head
title=
{
image
.
title
}
/>
<
div
className=
"bg-white shadow overflow-hidden sm:rounded-lg"
>
<
ComImageShow
<
div
className=
"px-4 py-5 sm:px-6 flex justify-between items-center"
>
path=
{
`/images/${image.id}`
}
<
div
>
image=
{
image
}
<
h1
className=
"text-2xl font-bold text-gray-900"
>
{
image
.
title
}
</
h1
>
can_edit=
{
can_edit
}
<
p
className=
"mt-1 max-w-2xl text-sm text-gray-500"
>
can_approve=
{
can_approve
}
Uploaded by
{
image
.
user
.
name
}
on
{
' '
}
can_delete=
{
can_delete
}
{
new
Date
(
image
.
created_at
).
toLocaleDateString
()
}
isAdmin=
{
false
}
</
p
>
/>
</
div
>
<
div
className=
"flex space-x-2"
>
{
can_edit
&&
(
<
Link
href=
{
`/images/${image.id}/edit`
}
className=
"inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Edit
</
Link
>
)
}
{
can_approve
&&
image
.
status
===
'pending'
&&
(
<>
<
Link
href=
{
`/images/${image.id}/approve`
}
method=
"patch"
as=
"button"
className=
"inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
>
Approve
</
Link
>
<
Link
href=
{
`/images/${image.id}/reject`
}
method=
"patch"
as=
"button"
className=
"inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
Reject
</
Link
>
</>
)
}
{
can_approve
&&
(
<
Link
href=
{
`/images/${image.id}`
}
method=
"delete"
as=
"button"
className=
"inline-flex items-center px-3 py-1.5 border border-gray-300 text-sm font-medium rounded-md shadow-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
onClick=
{
(
e
)
=>
{
if
(
!
confirm
(
'Are you sure you want to delete this image?'
))
{
e
.
preventDefault
()
}
}
}
>
Delete
</
Link
>
)
}
</
div
>
</
div
>
<
div
className=
"border-t border-gray-200"
>
<
div
className=
"flex flex-col md:flex-row"
>
<
div
className=
"md:w-2/3 p-4"
>
<
div
className=
"relative pb-[75%]"
>
<
img
src=
{
image
.
file_url
}
alt=
{
image
.
title
}
className=
"absolute h-full w-full object-contain cursor-pointer"
onClick=
{
()
=>
setIsModalOpen
(
true
)
}
/>
</
div
>
</
div
>
<
div
className=
"md:w-1/3 p-4 border-t md:border-t-0 md:border-l border-gray-200"
>
<
div
className=
"mb-4"
>
<
h3
className=
"text-lg font-medium text-gray-900"
>
Status
</
h3
>
<
span
className=
{
`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
image.status === 'pending'
? 'bg-yellow-100 text-yellow-800'
: image.status === 'approved'
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`
}
>
{
image
.
status
}
</
span
>
</
div
>
<
div
className=
"mb-4"
>
<
h3
className=
"text-lg font-medium text-gray-900"
>
Tags
</
h3
>
<
div
className=
"mt-2 flex flex-wrap gap-1"
>
{
image
.
tags
.
length
>
0
?
(
image
.
tags
.
map
((
tag
)
=>
(
<
span
key=
{
tag
.
id
}
className=
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800"
>
{
tag
.
name
}
</
span
>
))
)
:
(
<
p
className=
"text-sm text-gray-500"
>
No tags
</
p
>
)
}
</
div
>
</
div
>
<
div
className=
"mb-4"
>
<
h3
className=
"text-lg font-medium text-gray-900"
>
Uploader
</
h3
>
<
p
className=
"text-sm text-gray-500"
>
{
image
.
user
.
name
}
</
p
>
<
p
className=
"text-sm text-gray-500"
>
{
image
.
user
.
email_address
}
</
p
>
</
div
>
<
div
className=
"mb-4"
>
<
h3
className=
"text-lg font-medium text-gray-900"
>
Upload Date
</
h3
>
<
p
className=
"text-sm text-gray-500"
>
{
new
Date
(
image
.
created_at
).
toLocaleDateString
()
}
</
p
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
{
/* Full screen image modal */
}
<
Dialog
.
Root
open=
{
isModalOpen
}
onOpenChange=
{
setIsModalOpen
}
>
<
Dialog
.
Portal
>
<
Dialog
.
Overlay
className=
"fixed inset-0 bg-black/50"
/>
<
Dialog
.
Content
className=
"fixed inset-0 flex items-center justify-center"
>
<
div
className=
"relative w-full h-full max-w-screen-xl max-h-screen p-4"
>
<
img
src=
{
image
.
file_url
}
alt=
{
image
.
title
}
className=
"w-full h-full object-contain"
/>
<
Dialog
.
Close
asChild
>
<
button
className=
"absolute top-4 right-4 p-2 rounded-full bg-white/80 hover:bg-white text-gray-800"
aria
-
label=
"Close"
>
<
svg
xmlns=
"http://www.w3.org/2000/svg"
className=
"h-6 w-6"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
>
<
path
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
{
2
}
d=
"M6 18L18 6M6 6l12 12"
/>
</
svg
>
</
button
>
</
Dialog
.
Close
>
</
div
>
</
Dialog
.
Content
>
</
Dialog
.
Portal
>
</
Dialog
.
Root
>
</
Layout
>
</
Layout
>
)
)
}
}
app/models/image.rb
View file @
c139b140
...
@@ -9,7 +9,7 @@ class Image < ApplicationRecord
...
@@ -9,7 +9,7 @@ class Image < ApplicationRecord
validates
:file
,
presence:
true
,
on: :create
validates
:file
,
presence:
true
,
on: :create
# Status for review process
# Status for review process
# enum status: { pending: 0, approved: 1, rejected: 2 }, _
default: :pending
enum
:status
,
{
pending:
0
,
approved:
1
,
rejected:
2
},
default: :pending
# Scopes for filtering
# Scopes for filtering
scope
:pending
,
->
{
where
(
status: :pending
)
}
scope
:pending
,
->
{
where
(
status: :pending
)
}
...
@@ -21,7 +21,7 @@ class Image < ApplicationRecord
...
@@ -21,7 +21,7 @@ class Image < ApplicationRecord
end
end
def
self
.
ransackable_associations
(
auth_object
=
nil
)
def
self
.
ransackable_associations
(
auth_object
=
nil
)
[
"image_tags"
,
"tags"
,
"user"
]
[
"image_tags"
,
"tags"
,
"user"
]
end
end
# Returns the URL for the attached file
# Returns the URL for the attached file
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment