图片列表接口

This commit is contained in:
Wisp X 2022-02-20 16:13:42 +08:00
parent c047b0a27e
commit b507d71ddd
8 changed files with 266 additions and 66 deletions

View File

@ -6,17 +6,17 @@ use Illuminate\Http\Response;
trait Api
{
public function success(string $message = 'success', array $data = []): Response
public function success(string $message = 'success', $data = []): Response
{
return $this->response(true, $message, $data);
}
public function error(string $message = 'error', array $data = []): Response
public function error(string $message = 'error', $data = []): Response
{
return $this->response(false, $message, $data);
}
public function response(bool $status, string $message = '', array $data = []): Response
public function response(bool $status, string $message = '', $data = []): Response
{
$data = $data ?: new \stdClass;
return response(compact('status', 'message', 'data'));

View File

@ -2,11 +2,14 @@
namespace App\Http\Controllers\Api\V1;
use App\Enums\ImagePermission;
use App\Exceptions\UploadException;
use App\Http\Controllers\Controller;
use App\Models\Image;
use App\Models\User;
use App\Services\ImageService;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
@ -52,12 +55,24 @@ class ImageController extends Controller
return $this->error('服务异常,请稍后再试');
}
return $this->success('上传成功', $image->setAppends(['pathname', 'links'])->only(
'key', 'name', 'extension', 'pathname', 'origin_name', 'size', 'mimetype', 'md5', 'sha1', 'links'
'key', 'name', 'pathname', 'origin_name', 'size', 'mimetype', 'extension', 'md5', 'sha1', 'links'
));
}
public function images(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
$images = $user->images()->filter($request)->with('group', 'strategy')->paginate(40)->withQueryString();
$images->getCollection()->each(function (Image $image) {
$image->human_date = $image->created_at->diffForHumans();
$image->date = $image->created_at->format('Y-m-d H:i:s');
$image->append(['pathname', 'links'])->setVisible([
'key', 'name', 'pathname', 'origin_name', 'size', 'mimetype', 'extension', 'md5', 'sha1',
'width', 'height', 'links', 'human_date', 'date',
]);
});
return $this->success('success', $images);
}
}

View File

@ -9,7 +9,6 @@ use App\Models\Album;
use App\Models\Image;
use App\Models\User;
use App\Services\UserService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
@ -28,38 +27,7 @@ class ImageController extends Controller
/** @var User $user */
$user = Auth::user();
$images = Image::with('group', 'strategy')
->where('user_id', $user->id)
->when($request->query('order') ?: 'newest', function (Builder $builder, $order) {
switch ($order) {
case 'earliest':
$builder->orderBy('created_at');
break;
case 'utmost':
$builder->orderByDesc('size');
break;
case 'least':
$builder->orderBy('size');
break;
default:
$builder->latest();
}
})->when($request->query('visibility') ?: 'all', function (Builder $builder, $visibility) {
switch ($visibility) {
case 'public':
$builder->where('permission', ImagePermission::Public);
break;
case 'private':
$builder->where('permission', ImagePermission::Private);
break;
}
})->when($request->query('keyword'), function (Builder $builder, $keyword) {
$builder->whereRaw("concat(origin_name,alias_name) like ?", ["%{$keyword}%"]);
})->when((int) $request->query('album_id'), function (Builder $builder, $albumId) {
$builder->where('album_id', $albumId);
}, function (Builder $builder) {
$builder->whereNull('album_id');
})->paginate(40);
$images = $user->images()->filter($request)->with('group', 'strategy')->paginate(40);
$images->getCollection()->each(function (Image $image) {
$image->human_date = $image->created_at->diffForHumans();
$image->date = $image->created_at->format('Y-m-d H:i:s');

View File

@ -3,13 +3,16 @@
namespace App\Models;
use App\Enums\GroupConfigKey;
use App\Enums\ImagePermission;
use App\Enums\StrategyKey;
use App\Services\ImageService;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
@ -108,6 +111,40 @@ class Image extends Model
});
}
public function scopeFilter(Builder $builder, Request $request)
{
return $builder->when($request->query('order') ?: 'newest', function (Builder $builder, $order) {
switch ($order) {
case 'earliest':
$builder->orderBy('created_at');
break;
case 'utmost':
$builder->orderByDesc('size');
break;
case 'least':
$builder->orderBy('size');
break;
default:
$builder->latest();
}
})->when($request->query('permission') ?: 'all', function (Builder $builder, $permission) {
switch ($permission) {
case 'public':
$builder->where('permission', ImagePermission::Public);
break;
case 'private':
$builder->where('permission', ImagePermission::Private);
break;
}
})->when($request->query('keyword'), function (Builder $builder, $keyword) {
$builder->whereRaw("concat(origin_name,alias_name) like ?", ["%{$keyword}%"]);
})->when((int) $request->query('album_id'), function (Builder $builder, $albumId) {
$builder->where('album_id', $albumId);
}, function (Builder $builder) {
$builder->whereNull('album_id');
});
}
public function filename(): Attribute
{
return new Attribute(fn() => $this->alias_name ?: $this->origin_name);

View File

@ -1775,6 +1775,14 @@ select {
--tw-text-opacity: 1;
color: rgb(252 165 165 / var(--tw-text-opacity));
}
.text-sky-400 {
--tw-text-opacity: 1;
color: rgb(56 189 248 / var(--tw-text-opacity));
}
.text-sky-500 {
--tw-text-opacity: 1;
color: rgb(14 165 233 / var(--tw-text-opacity));
}
.underline {
-webkit-text-decoration-line: underline;
text-decoration-line: underline;

View File

@ -3,7 +3,7 @@
<x-app-layout>
<div class="my-6 md:my-9">
<p class="text-xl mb-2 text-gray-800 font-semibold">接口说明</p>
<div class="space-y-4 bg-gray-50 p-3 rounded-md mb-5">
<div class="space-y-4 bg-white p-3 rounded-md mb-5">
<div>
<p class="text-lg text-gray-700 font-semibold">接口URL</p>
<x-code>{{ request()->getSchemeAndHttpHost() }}/api/v1</x-code>
@ -22,7 +22,7 @@
<div class="my-4 overflow-x-auto">
<p class="text-sm mb-2">公共响应 headers 说明</p>
<table class="min-w-full">
<thead class="bg-gray-50 border">
<thead class="bg-white border">
<tr>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
字段
@ -53,14 +53,14 @@
</div>
<p class="text-xl mb-2 text-gray-800 font-semibold">授权相关</p>
<div class="space-y-4 bg-gray-50 p-3 rounded-md mb-5">
<div class="space-y-4 bg-white p-3 rounded-md mb-5">
<div>
<p class="text-lg text-gray-700 font-semibold">生成 Token</p>
<x-code><span class="text-green-500 select-none">POST </span>/tokens</x-code>
<div class="my-4 overflow-x-auto">
<p class="text-sm mb-2">请求参数</p>
<p class="text-sm mb-2">请求参数(Body)</p>
<table class="min-w-full">
<thead class="bg-gray-50 border">
<thead class="bg-white border">
<tr>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
字段
@ -90,7 +90,7 @@
<div class="my-4 overflow-x-auto">
<p class="text-sm mb-2">返回参数</p>
<table class="min-w-full">
<thead class="bg-gray-50 border">
<thead class="bg-white border">
<tr>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
字段
@ -135,7 +135,7 @@
<div class="my-4 overflow-x-auto">
<p class="text-sm mb-2">返回参数</p>
<table class="min-w-full">
<thead class="bg-gray-50 border">
<thead class="bg-white border">
<tr>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
字段
@ -171,7 +171,7 @@
</div>
<p class="text-xl mb-2 text-gray-800 font-semibold">图片相关</p>
<div class="space-y-4 bg-gray-50 p-3 rounded-md mb-5">
<div class="space-y-4 bg-white p-3 rounded-md mb-5">
<div>
<p class="text-lg text-gray-700 font-semibold">上传图片</p>
<x-code><span class="text-green-500 select-none">POST </span>/upload</x-code>
@ -179,7 +179,7 @@
<div class="my-4 overflow-x-auto">
<p class="text-sm mb-2">Headers</p>
<table class="min-w-full">
<thead class="bg-gray-50 border">
<thead class="bg-white border">
<tr>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
字段
@ -203,9 +203,9 @@
</div>
<div class="my-4 overflow-x-auto">
<p class="text-sm mb-2">请求参数</p>
<p class="text-sm mb-2">请求参数(Body)</p>
<table class="min-w-full">
<thead class="bg-gray-50 border">
<thead class="bg-white border">
<tr>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
字段
@ -231,7 +231,7 @@
<div class="my-4 overflow-x-auto">
<p class="text-sm mb-2">返回参数</p>
<table class="min-w-full">
<thead class="bg-gray-50 border">
<thead class="bg-white border">
<tr>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
字段
@ -270,11 +270,6 @@
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片名称</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-6">extension</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片拓展名</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-6">pathname</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
@ -295,6 +290,11 @@
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片类型</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-6">extension</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片拓展名</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-6">md5</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
@ -344,6 +344,177 @@
</table>
</div>
</div>
<div>
<p class="text-lg text-gray-700 font-semibold">图片列表</p>
<x-code><span class="text-sky-500 select-none">GET </span>/images</x-code>
<div class="my-4 overflow-x-auto">
<p class="text-sm mb-2">请求参数(Query)</p>
<table class="min-w-full">
<thead class="bg-white border">
<tr>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
字段
</th>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
类型
</th>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
说明
</th>
</tr>
</thead>
<tbody class="bg-white border divide-y text-sm">
<tr>
<td class="px-3 py-2 whitespace-nowrap">page</td>
<td class="px-3 py-2 whitespace-nowrap">Integer</td>
<td class="px-3 py-2 whitespace-nowrap">页码</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap">order</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">排序方式newest=最新earliest=最早utmost=最大least=最小</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap">permission</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">权限public=公开的private=私有的</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap">album_id</td>
<td class="px-3 py-2 whitespace-nowrap">Integer</td>
<td class="px-3 py-2 whitespace-nowrap">相册 ID</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap">keyword</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">筛选关键字</td>
</tr>
</tbody>
</table>
</div>
<div class="my-4 overflow-x-auto">
<p class="text-sm mb-2">返回参数</p>
<table class="min-w-full">
<thead class="bg-white border">
<tr>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
字段
</th>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
类型
</th>
<th scope="col" class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
说明
</th>
</tr>
</thead>
<tbody class="bg-white border divide-y text-sm">
<tr>
<td class="px-3 py-2 whitespace-nowrap">status</td>
<td class="px-3 py-2 whitespace-nowrap">Boolean</td>
<td class="px-3 py-2 whitespace-nowrap">状态true false</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap">message</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">描述信息</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap">data</td>
<td class="px-3 py-2 whitespace-nowrap">Object</td>
<td class="px-3 py-2 whitespace-nowrap">数据</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-6">current_page</td>
<td class="px-3 py-2 whitespace-nowrap">Integer</td>
<td class="px-3 py-2 whitespace-nowrap">当前所在页页码</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-6">last_page</td>
<td class="px-3 py-2 whitespace-nowrap">Integer</td>
<td class="px-3 py-2 whitespace-nowrap">最后一页页码</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-6">per_page</td>
<td class="px-3 py-2 whitespace-nowrap">Integer</td>
<td class="px-3 py-2 whitespace-nowrap">每页展示数据数量</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-6">total</td>
<td class="px-3 py-2 whitespace-nowrap">Integer</td>
<td class="px-3 py-2 whitespace-nowrap">图片总数量</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-6">data</td>
<td class="px-3 py-2 whitespace-nowrap">Object[]</td>
<td class="px-3 py-2 whitespace-nowrap">图片列表</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">key</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片唯一密钥</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">name</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片名称</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">origin_name</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片原始名称</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">pathname</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片路径名</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">size</td>
<td class="px-3 py-2 whitespace-nowrap">Float</td>
<td class="px-3 py-2 whitespace-nowrap">图片大小,单位 KB</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">width</td>
<td class="px-3 py-2 whitespace-nowrap">Integer</td>
<td class="px-3 py-2 whitespace-nowrap">图片宽度</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">height</td>
<td class="px-3 py-2 whitespace-nowrap">Integer</td>
<td class="px-3 py-2 whitespace-nowrap">图片高度</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">md5</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片 md5 </td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">sha1</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">图片 sha1 </td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">human_date</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">上传时间(友好格式)</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">date</td>
<td class="px-3 py-2 whitespace-nowrap">String</td>
<td class="px-3 py-2 whitespace-nowrap">上传日期(yyyy-MM-dd HH:mm:ss)</td>
</tr>
<tr>
<td class="px-3 py-2 whitespace-nowrap pl-10">links</td>
<td class="px-3 py-2 whitespace-nowrap">Object</td>
<td class="px-3 py-2 whitespace-nowrap">链接,与上传接口返回参数中的 links 相同</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@ -59,18 +59,18 @@
</x-dropdown>
<x-dropdown direction="left">
<x-slot name="trigger">
<a id="visibility" class="text-sm py-2 px-3 hover:bg-gray-100 rounded text-gray-800" href="javascript:void(0)">
<a id="permission" class="text-sm py-2 px-3 hover:bg-gray-100 rounded text-gray-800" href="javascript:void(0)">
<span>全部</span>
<i class="fas fa-eye text-blue-500"></i>
</a>
</x-slot>
<x-slot name="content">
<x-dropdown-link href="javascript:void(0)" @click="open = false; setVisibility('all')">全部
<x-dropdown-link href="javascript:void(0)" @click="open = false; setPermission('all')">全部
</x-dropdown-link>
<x-dropdown-link href="javascript:void(0)" @click="open = false; setVisibility('public')">公开
<x-dropdown-link href="javascript:void(0)" @click="open = false; setPermission('public')">公开
</x-dropdown-link>
<x-dropdown-link href="javascript:void(0)" @click="open = false; setVisibility('private')">私有
<x-dropdown-link href="javascript:void(0)" @click="open = false; setPermission('private')">私有
</x-dropdown-link>
</x-slot>
</x-dropdown>
@ -447,9 +447,9 @@
$('#order span').text({newest: '最新', earliest: '最早', utmost: '最大', least: '最小'}[sort]);
};
const setVisibility = function (visibility) {
resetImages({page: 1, visibility: visibility})
$('#visibility span').text({public: '公开', private: '私有', all: '全部'}[visibility]);
const setPermission = function (permission) {
resetImages({page: 1, permission: permission})
$('#permission span').text({public: '公开', private: '私有', all: '全部'}[permission]);
};
$('#search').keydown(function (e) {
@ -569,7 +569,7 @@
}
});
},
visibility() {
permission() {
Swal.fire({
title: '选择一个权限',
text: '选择公开将会出现在画廊中(若平台开启了画廊)',
@ -780,9 +780,9 @@
text: '删除',
action: _ => methods.delete(),
},
visibility: {
permission: {
text: '设置可见性',
action: _ => methods.visibility(),
action: _ => methods.permission(),
},
};
// right click 'images scroll' container
@ -800,7 +800,7 @@
actions.open,
actions.movements,
actions.remove,
actions.visibility,
actions.permission,
actions.detail,
{divider: true},
actions.rename,
@ -842,7 +842,7 @@
methods.rename(selected[0]);
break;
case 'permission': // 设置可见性
methods.visibility();
methods.permission();
break;
case 'detail':
methods.detail(selected[0]);

View File

@ -22,6 +22,7 @@ Route::group(['prefix' => 'v1'], function () {
Route::group([
'middleware' => 'auth:sanctum',
], function () {
Route::get('images', [ImageController::class, 'images']);
Route::delete('tokens', [TokenController::class, 'clear']);
});
});