Browse Source

project thumbnail

Andrew 1 day ago
parent
commit
d340925e94

+ 8
- 0
.gitignore View File

22
 Homestead.json
22
 Homestead.json
23
 Homestead.yaml
23
 Homestead.yaml
24
 Thumbs.db
24
 Thumbs.db
25
+.mcp.json
26
+CLAUDE.md
27
+AGENTS.md
28
+junie/
29
+boost.json
30
+/.claude
31
+/.cursor
32
+/.junie

+ 124
- 123
app/Filament/Resources/ProjectResource.php View File

2
 
2
 
3
 namespace App\Filament\Resources;
3
 namespace App\Filament\Resources;
4
 
4
 
5
-use AbdulmajeedJamaan\FilamentTranslatableTabs\TranslatableTabs;
6
 use App\Filament\Resources\ProjectResource\Pages;
5
 use App\Filament\Resources\ProjectResource\Pages;
7
-use App\Filament\Resources\ProjectResource\RelationManagers;
8
 use App\Models\Badge;
6
 use App\Models\Badge;
9
 use App\Models\Project;
7
 use App\Models\Project;
10
 use App\Models\Region;
8
 use App\Models\Region;
11
-use Filament\Forms;
9
+use App\Service\DeepLService;
12
 use Filament\Forms\Components\DatePicker;
10
 use Filament\Forms\Components\DatePicker;
13
 use Filament\Forms\Components\FileUpload;
11
 use Filament\Forms\Components\FileUpload;
14
 use Filament\Forms\Components\Group;
12
 use Filament\Forms\Components\Group;
30
 use Filament\Tables\Filters\SelectFilter;
28
 use Filament\Tables\Filters\SelectFilter;
31
 use Filament\Tables\Table;
29
 use Filament\Tables\Table;
32
 use Illuminate\Database\Eloquent\Builder;
30
 use Illuminate\Database\Eloquent\Builder;
33
-use Illuminate\Database\Eloquent\SoftDeletingScope;
34
 use Illuminate\Support\Facades\Storage;
31
 use Illuminate\Support\Facades\Storage;
35
 use Illuminate\Support\Str;
32
 use Illuminate\Support\Str;
36
 use SolutionForest\FilamentTranslateField\Forms\Component\Translate;
33
 use SolutionForest\FilamentTranslateField\Forms\Component\Translate;
37
-use App\Service\DeepLService;
38
 
34
 
39
 class ProjectResource extends Resource
35
 class ProjectResource extends Resource
40
 {
36
 {
41
     protected static ?string $model = Project::class;
37
     protected static ?string $model = Project::class;
42
 
38
 
43
     protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
39
     protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
40
+
44
     protected static ?string $navigationGroup = '專案項目管理';
41
     protected static ?string $navigationGroup = '專案項目管理';
45
-    protected static ?string $navigationLabel = "專案項目管理";
46
-    protected static ?string $modelLabel = "專案項目管理";
42
+
43
+    protected static ?string $navigationLabel = '專案項目管理';
44
+
45
+    protected static ?string $modelLabel = '專案項目管理';
46
+
47
     protected static ?int $navigationSort = 5;
47
     protected static ?int $navigationSort = 5;
48
 
48
 
49
     public static function form(Form $form): Form
49
     public static function form(Form $form): Form
50
     {
50
     {
51
         return $form
51
         return $form
52
             ->schema([
52
             ->schema([
53
-                Section::make("")->schema([
53
+                Section::make('')->schema([
54
                     Translate::make()->schema(fn (string $locale) => [
54
                     Translate::make()->schema(fn (string $locale) => [
55
-                        TextInput::make("name")->label("項目名稱"),
56
-                        TextInput::make("sub_name")->label("項目子名稱"),
55
+                        TextInput::make('name')->label('項目名稱'),
56
+                        TextInput::make('sub_name')->label('項目子名稱'),
57
                     ])
57
                     ])
58
-                    ->locales(["zh_TW", "en"])
59
-                    ->actions([
60
-                        app(DeepLService::class)->createTranslationAction("Main", ["name", "sub_name"])
61
-                    ])->columnSpanFull()->id("main"),
58
+                        ->locales(['zh_TW', 'en'])
59
+                        ->actions([
60
+                            app(DeepLService::class)->createTranslationAction('Main', ['name', 'sub_name']),
61
+                        ])->columnSpanFull()->id('main'),
62
                     Select::make('tags')
62
                     Select::make('tags')
63
                         ->multiple()
63
                         ->multiple()
64
                         ->relationship('tags', 'name')
64
                         ->relationship('tags', 'name')
65
                         ->preload()
65
                         ->preload()
66
                         ->label('標籤'),
66
                         ->label('標籤'),
67
-                    FileUpload::make("img_url")->label("圖片")->directory("project")->multiple()->maxFiles(5),
68
-                    TextInput::make('order')->label("排序")->default("0")
67
+                    FileUpload::make('thumbnail')->label('縮圖')->directory('project')->image(),
68
+                    FileUpload::make('img_url')->label('圖片')->directory('project')->multiple()->maxFiles(5),
69
+                    TextInput::make('order')->label('排序')->default('0'),
69
                 ]),
70
                 ]),
70
                 Tabs::make()->schema([
71
                 Tabs::make()->schema([
71
-                    Tab::make("專案概要")->schema([
72
-                        Select::make("region_id")->label("地區")->options(function (){
73
-                            return Region::where("visible",true)->pluck("name", "id");
72
+                    Tab::make('專案概要')->schema([
73
+                        Select::make('region_id')->label('地區')->options(function () {
74
+                            return Region::where('visible', true)->pluck('name', 'id');
74
                         }),
75
                         }),
75
                         Translate::make()->schema(fn (string $locale) => [
76
                         Translate::make()->schema(fn (string $locale) => [
76
-                            Textarea::make("summaries")->label("簡述"),
77
-                            TextInput::make("district")->label("區域"),
78
-                            TextInput::make("address")->label("地址"),
79
-                            Textarea::make("floor_plan")->label("樓層規劃"),
80
-                            Textarea::make("building_structure")->label("建築結構"),
81
-                            Textarea::make("design_unit")->label("設計單位")
77
+                            Textarea::make('summaries')->label('簡述'),
78
+                            TextInput::make('district')->label('區域'),
79
+                            TextInput::make('address')->label('地址'),
80
+                            Textarea::make('floor_plan')->label('樓層規劃'),
81
+                            Textarea::make('building_structure')->label('建築結構'),
82
+                            Textarea::make('design_unit')->label('設計單位'),
82
                         ])
83
                         ])
83
-                        ->locales(["zh_TW", "en"])
84
-                        ->actions([
85
-                            app(DeepLService::class)->createTranslationAction("summaries", ["summaries","address",
86
-                            "floor_plan","building_structure","design_unit"])
87
-                        ])->columnSpanFull()->id("summaries"),
88
-                        Radio::make("badge_type")->label("")->options([1 => "永續目標", 2 => "取得標章"])->default(1)->inline(),
89
-                        Repeater::make("badgesTarget")->label("永續目標")->schema([
90
-                            Hidden::make("award_type")->default(1),
84
+                            ->locales(['zh_TW', 'en'])
85
+                            ->actions([
86
+                                app(DeepLService::class)->createTranslationAction('summaries', ['summaries', 'address',
87
+                                    'floor_plan', 'building_structure', 'design_unit']),
88
+                            ])->columnSpanFull()->id('summaries'),
89
+                        Radio::make('badge_type')->label('')->options([1 => '永續目標', 2 => '取得標章'])->default(1)->inline(),
90
+                        Repeater::make('badgesTarget')->label('永續目標')->schema([
91
+                            Hidden::make('award_type')->default(1),
91
                             Select::make('badge_id')
92
                             Select::make('badge_id')
92
                                 ->options(function () {
93
                                 ->options(function () {
93
                                     return Badge::all()->mapWithKeys(function ($badge) {
94
                                     return Badge::all()->mapWithKeys(function ($badge) {
94
                                         return [
95
                                         return [
95
                                             $badge->id => '<div class="flex items-center gap-2">
96
                                             $badge->id => '<div class="flex items-center gap-2">
96
-                                                <img src="' . Storage::url($badge->img_url) . '" class="w-6 h-6 rounded-full">
97
-                                                <span>' . $badge->title . '</span>
98
-                                            </div>'
97
+                                                <img src="'.Storage::url($badge->img_url).'" class="w-6 h-6 rounded-full">
98
+                                                <span>'.$badge->title.'</span>
99
+                                            </div>',
99
                                         ];
100
                                         ];
100
                                     });
101
                                     });
101
                                 })
102
                                 })
105
                                 ->label('')
106
                                 ->label('')
106
                                 ->required(),
107
                                 ->required(),
107
                         ])->reorderable(false),
108
                         ])->reorderable(false),
108
-                        Repeater::make("badgesAward")->label("取得標章")->schema([
109
-                            Hidden::make("award_type")->default(2),
109
+                        Repeater::make('badgesAward')->label('取得標章')->schema([
110
+                            Hidden::make('award_type')->default(2),
110
                             Select::make('badge_id')
111
                             Select::make('badge_id')
111
                                 ->options(function () {
112
                                 ->options(function () {
112
                                     return Badge::all()->mapWithKeys(function ($badge) {
113
                                     return Badge::all()->mapWithKeys(function ($badge) {
113
                                         return [
114
                                         return [
114
                                             $badge->id => '<div class="flex items-center gap-2">
115
                                             $badge->id => '<div class="flex items-center gap-2">
115
-                                                <img src="' . Storage::url($badge->img_url) . '" class="w-6 h-6 rounded-full">
116
-                                                <span>' . $badge->title . '</span>
117
-                                            </div>'
116
+                                                <img src="'.Storage::url($badge->img_url).'" class="w-6 h-6 rounded-full">
117
+                                                <span>'.$badge->title.'</span>
118
+                                            </div>',
118
                                         ];
119
                                         ];
119
                                     });
120
                                     });
120
                                 })
121
                                 })
128
                                 ->format('Y-m')
129
                                 ->format('Y-m')
129
                                 ->displayFormat('Y年m月')
130
                                 ->displayFormat('Y年m月')
130
                                 ->native(false)
131
                                 ->native(false)
131
-                                ->closeOnDateSelection()
132
+                                ->closeOnDateSelection(),
132
                         ])->reorderable(false),
133
                         ])->reorderable(false),
133
                     ]),
134
                     ]),
134
-                    Tab::make("開發歷程")->schema([
135
-                        Repeater::make("histories")->label("")->schema([
135
+                    Tab::make('開發歷程')->schema([
136
+                        Repeater::make('histories')->label('')->schema([
136
                             TextInput::make('histories_item_key')
137
                             TextInput::make('histories_item_key')
137
-                            ->default(fn () => Str::random())
138
-                            ->hidden()
139
-                            ->afterStateHydrated(function (TextInput $component, $state) {
140
-                                if (empty($state)) {
141
-                                    $component->state(Str::random());
142
-                                }
143
-                            }),
144
-                            DatePicker::make("operate_date")->label("歷程日期")->columnSpan(1),
138
+                                ->default(fn () => Str::random())
139
+                                ->hidden()
140
+                                ->afterStateHydrated(function (TextInput $component, $state) {
141
+                                    if (empty($state)) {
142
+                                        $component->state(Str::random());
143
+                                    }
144
+                                }),
145
+                            DatePicker::make('operate_date')->label('歷程日期')->columnSpan(1),
145
                             Translate::make()->schema(fn (string $locale) => [
146
                             Translate::make()->schema(fn (string $locale) => [
146
-                                TextInput::make("title")->label("標題")->columnSpanFull()
147
+                                TextInput::make('title')->label('標題')->columnSpanFull(),
147
                             ])
148
                             ])
148
-                            ->locales(["zh_TW", "en"])
149
-                            ->actions([
150
-                                app(DeepLService::class)->createTranslationAction("histories", ["title"])
151
-                            ])->columnSpanFull()
152
-                            ->id(fn ($get) => "histories_" . $get('histories_item_key')),
149
+                                ->locales(['zh_TW', 'en'])
150
+                                ->actions([
151
+                                    app(DeepLService::class)->createTranslationAction('histories', ['title']),
152
+                                ])->columnSpanFull()
153
+                                ->id(fn ($get) => 'histories_'.$get('histories_item_key')),
153
                         ])
154
                         ])
154
-                        ->relationship("histories", function ($query,Get $get, $livewire) {
155
-                            return $query->orderby("operate_date","desc");
156
-                        })
157
-                        ->collapsible()
158
-                        ->cloneable()
155
+                            ->relationship('histories', function ($query, Get $get, $livewire) {
156
+                                return $query->orderby('operate_date', 'desc');
157
+                            })
158
+                            ->collapsible()
159
+                            ->cloneable(),
159
                     ]),
160
                     ]),
160
-                    Tab::make("空間資訊")->schema([
161
+                    Tab::make('空間資訊')->schema([
161
                         Group::make()->schema([
162
                         Group::make()->schema([
162
                             Translate::make()->schema(fn (string $locale) => [
163
                             Translate::make()->schema(fn (string $locale) => [
163
-                                TextInput::make("contact_unit")->label("物管單位"),
164
-                                TextInput::make("contact_phone")->label("物管電話"),
165
-                                TextInput::make("inversment_phone")->label("招商電話"),
164
+                                TextInput::make('contact_unit')->label('物管單位'),
165
+                                TextInput::make('contact_phone')->label('物管電話'),
166
+                                TextInput::make('inversment_phone')->label('招商電話'),
166
                             ])
167
                             ])
167
-                            ->locales(["zh_TW", "en"])
168
-                            ->actions([
169
-                                app(DeepLService::class)->createTranslationAction("contact", ["contact_unit", "contact_phone", "inversment_phone"])
170
-                            ])->columnSpanFull()->columns(3)
171
-                            ->id("contact"),
172
-                            TextInput::make("offical_link")->label("官網連結"),
168
+                                ->locales(['zh_TW', 'en'])
169
+                                ->actions([
170
+                                    app(DeepLService::class)->createTranslationAction('contact', ['contact_unit', 'contact_phone', 'inversment_phone']),
171
+                                ])->columnSpanFull()->columns(3)
172
+                                ->id('contact'),
173
+                            TextInput::make('offical_link')->label('官網連結'),
173
                         ])->columnSpanFull(),
174
                         ])->columnSpanFull(),
174
-                        Repeater::make("spaceInfos")->label("")->schema([
175
+                        Repeater::make('spaceInfos')->label('')->schema([
175
                             Translate::make()->schema(fn (string $locale) => [
176
                             Translate::make()->schema(fn (string $locale) => [
176
-                                TextInput::make("title")->label("標題")->columnSpanFull(),
177
-                                Textarea::make("content")->label("內文")->columnSpanFull()
177
+                                TextInput::make('title')->label('標題')->columnSpanFull(),
178
+                                Textarea::make('content')->label('內文')->columnSpanFull(),
178
                             ])
179
                             ])
179
-                            ->locales(["zh_TW", "en"])
180
-                            ->actions([
181
-                                app(DeepLService::class)->createTranslationAction("spaceInfos", ["title", "content"])
182
-                            ])->columnSpanFull()
183
-                            ->id(fn ($get) => "spaceInfos_" . $get('item_key')),
180
+                                ->locales(['zh_TW', 'en'])
181
+                                ->actions([
182
+                                    app(DeepLService::class)->createTranslationAction('spaceInfos', ['title', 'content']),
183
+                                ])->columnSpanFull()
184
+                                ->id(fn ($get) => 'spaceInfos_'.$get('item_key')),
184
                         ])
185
                         ])
185
-                        ->relationship("spaceInfos")
186
-                        ->label("")
187
-                        ->collapsible()
188
-                        ->reorderableWithButtons()
189
-                        ->orderColumn('order')
190
-                        ->cloneable(),
191
-                    ])
186
+                            ->relationship('spaceInfos')
187
+                            ->label('')
188
+                            ->collapsible()
189
+                            ->reorderableWithButtons()
190
+                            ->orderColumn('order')
191
+                            ->cloneable(),
192
+                    ]),
192
                 ])->columnSpanFull(),
193
                 ])->columnSpanFull(),
193
             ]);
194
             ]);
194
     }
195
     }
198
         return $table
199
         return $table
199
             ->columns([
200
             ->columns([
200
                 //
201
                 //
201
-                TextColumn::make("name")->label("項目名稱")->alignCenter(),
202
-                ImageColumn::make("first_list_img_url")->label("列表圖")->alignCenter(),
203
-                TextColumn::make("region.name")->label("地址")->alignCenter()
204
-                ->formatStateUsing(fn ($record) => $record->region->getTranslation("name", "zh_TW") . ' | ' . $record->getTranslation("address", "zh_TW")),
202
+                TextColumn::make('name')->label('項目名稱')->alignCenter(),
203
+                ImageColumn::make('thumbnail_url')->label('縮圖')->alignCenter(),
204
+                ImageColumn::make('first_list_img_url')->label('列表圖')->alignCenter(),
205
+                TextColumn::make('region.name')->label('地址')->alignCenter()
206
+                    ->formatStateUsing(fn ($record) => $record->region->getTranslation('name', 'zh_TW').' | '.$record->getTranslation('address', 'zh_TW')),
205
             ])
207
             ])
206
             ->filters([
208
             ->filters([
207
-                SelectFilter::make('visible')->label("上/下架")
208
-                ->options([
209
-                    0 => "下架",
210
-                    1 => "上架",
211
-                ])
212
-                ->query(
213
-                    fn (array $data, Builder $query): Builder =>
214
-                    $query->when(
215
-                        $data['value'],
216
-                        fn (Builder $query, $value): Builder => $query->where('visible', $data['value'])
217
-                    )
218
-                ),
209
+                SelectFilter::make('visible')->label('上/下架')
210
+                    ->options([
211
+                        0 => '下架',
212
+                        1 => '上架',
213
+                    ])
214
+                    ->query(
215
+                        fn (array $data, Builder $query): Builder => $query->when(
216
+                            $data['value'],
217
+                            fn (Builder $query, $value): Builder => $query->where('visible', $data['value'])
218
+                        )
219
+                    ),
219
             ])
220
             ])
220
             ->actions([
221
             ->actions([
221
                 Tables\Actions\EditAction::make(),
222
                 Tables\Actions\EditAction::make(),
222
                 Tables\Actions\DeleteAction::make(),
223
                 Tables\Actions\DeleteAction::make(),
223
-                \Filament\Tables\Actions\Action::make("audit")
224
-                ->label(fn ($record) => match ($record->visible) {
225
-                    0 => '上架',
226
-                    1 => '下架',
227
-                })
228
-                ->color(fn ($record) => match ($record->visible) {
229
-                    0 => 'warning',
230
-                    1 => 'gray',
231
-                })
232
-                ->icon(fn ($record) => match ($record->visible) {
233
-                    0 => 'heroicon-m-chevron-double-up',
234
-                    1 => 'heroicon-m-chevron-double-down',
235
-                })
236
-                ->action(function ($record): void {
237
-                    $record->visible = !$record->visible;
238
-                    $record->save();
239
-                })
240
-                ->outlined()
241
-                ->requiresConfirmation(),
224
+                \Filament\Tables\Actions\Action::make('audit')
225
+                    ->label(fn ($record) => match ($record->visible) {
226
+                        0 => '上架',
227
+                        1 => '下架',
228
+                    })
229
+                    ->color(fn ($record) => match ($record->visible) {
230
+                        0 => 'warning',
231
+                        1 => 'gray',
232
+                    })
233
+                    ->icon(fn ($record) => match ($record->visible) {
234
+                        0 => 'heroicon-m-chevron-double-up',
235
+                        1 => 'heroicon-m-chevron-double-down',
236
+                    })
237
+                    ->action(function ($record): void {
238
+                        $record->visible = ! $record->visible;
239
+                        $record->save();
240
+                    })
241
+                    ->outlined()
242
+                    ->requiresConfirmation(),
242
             ])
243
             ])
243
             ->bulkActions([
244
             ->bulkActions([
244
                 Tables\Actions\BulkActionGroup::make([
245
                 Tables\Actions\BulkActionGroup::make([

+ 79
- 80
app/Http/Controllers/Api/ProjectController.php View File

3
 namespace App\Http\Controllers\Api;
3
 namespace App\Http\Controllers\Api;
4
 
4
 
5
 use App\Http\Controllers\Controller;
5
 use App\Http\Controllers\Controller;
6
-use App\Models\News;
7
-use App\Models\NewsCategory;
8
 use App\Models\Project;
6
 use App\Models\Project;
9
 use App\Models\Region;
7
 use App\Models\Region;
10
 use App\Models\Tag;
8
 use App\Models\Tag;
11
 use App\Supports\Response;
9
 use App\Supports\Response;
12
 use Carbon\Carbon;
10
 use Carbon\Carbon;
13
 use Illuminate\Http\Request;
11
 use Illuminate\Http\Request;
14
-use Illuminate\Support\Facades\DB;
15
-use Illuminate\Support\Facades\Log;
16
-use App\Http\Helper\Helper;
17
 
12
 
18
 /**
13
 /**
19
  * @group Lottery Prize
14
  * @group Lottery Prize
21
 class ProjectController extends Controller
16
 class ProjectController extends Controller
22
 {
17
 {
23
     public function __construct(
18
     public function __construct(
24
-    )
25
-    {
26
-    }
19
+    ) {}
27
 
20
 
28
     public function list(Request $request, $locale = 'tw')
21
     public function list(Request $request, $locale = 'tw')
29
     {
22
     {
30
-        $locale = $locale == "tw" ? "zh_TW" : $locale;
23
+        $locale = $locale == 'tw' ? 'zh_TW' : $locale;
31
         $result = [];
24
         $result = [];
32
 
25
 
33
-        //年份清單
34
-        $regions = Region::select(['id', 'name'])->where("visible", 1)->get()->map(function ($record) use ($locale){
26
+        // 年份清單
27
+        $regions = Region::select(['id', 'name'])->where('visible', 1)->get()->map(function ($record) use ($locale) {
35
             return [
28
             return [
36
-                "id" => $record->id,
37
-                "name" => $record->getTranslation("name", $locale)
29
+                'id' => $record->id,
30
+                'name' => $record->getTranslation('name', $locale),
38
             ];
31
             ];
39
         });
32
         });
40
-        $tags = Tag::select(['id', 'name'])->where("visible", 1)->get()->map(function ($record) use ($locale){
33
+        $tags = Tag::select(['id', 'name'])->where('visible', 1)->get()->map(function ($record) use ($locale) {
41
             return [
34
             return [
42
-                "id" => $record->id,
43
-                "name" => $record->getTranslation("name", $locale)
35
+                'id' => $record->id,
36
+                'name' => $record->getTranslation('name', $locale),
44
             ];
37
             ];
45
         });
38
         });
46
 
39
 
47
-        $result["regions"] = $regions;
48
-        $result["tags"] = $tags;
40
+        $result['regions'] = $regions;
41
+        $result['tags'] = $tags;
49
 
42
 
50
-        //文章列表
51
-        $projects = Project::where("visible", true);
52
-        if($request->has('region')){
53
-            $projects->where("region_id", $request->input('region'));
43
+        // 文章列表
44
+        $projects = Project::where('visible', true);
45
+        if ($request->has('region')) {
46
+            $projects->where('region_id', $request->input('region'));
54
         }
47
         }
55
-        if($request->has('tags')){
48
+        if ($request->has('tags')) {
56
             $request_tags = $request->input('tags');
49
             $request_tags = $request->input('tags');
57
-            $projects->whereHas("tags", function ($query) use ($request_tags) {
58
-                return $query->whereIn("tags.id", $request_tags);
50
+            $projects->whereHas('tags', function ($query) use ($request_tags) {
51
+                return $query->whereIn('tags.id', $request_tags);
59
             });
52
             });
60
         }
53
         }
61
 
54
 
62
-        $projects = $projects->with(['tags', 'region'])->orderByDesc("order")->get();
63
-        foreach($projects as $project){
64
-            $result["list"][] = [
65
-                "id" => $project->id,
66
-                "region_id" => $project->region_id,
67
-                "region" => $project->region->getTranslation("name", $locale),
68
-                "district" => $project->getTranslation("district", $locale),
69
-                "address" => $project->getTranslation("address", $locale),
70
-                "tags" => $project->tags->map(function ($record) use ($locale){
55
+        $projects = $projects->with(['tags', 'region'])->orderByDesc('order')->get();
56
+        foreach ($projects as $project) {
57
+            $result['list'][] = [
58
+                'id' => $project->id,
59
+                'region_id' => $project->region_id,
60
+                'region' => $project->region->getTranslation('name', $locale),
61
+                'district' => $project->getTranslation('district', $locale),
62
+                'address' => $project->getTranslation('address', $locale),
63
+                'tags' => $project->tags->map(function ($record) use ($locale) {
71
                     return [
64
                     return [
72
-                        "id" => $record->id,
73
-                        "name" => $record->getTranslation("name", $locale)
65
+                        'id' => $record->id,
66
+                        'name' => $record->getTranslation('name', $locale),
74
                     ];
67
                     ];
75
                 }),
68
                 }),
76
-                "name" => $project->getTranslation("name", $locale),
77
-                "imgUrl" => $project->first_list_img_url
69
+                'name' => $project->getTranslation('name', $locale),
70
+                'thumbnail' => $project->thumbnail_url,
71
+                'imgUrl' => $project->first_list_img_url,
78
             ];
72
             ];
79
         }
73
         }
74
+
80
         return Response::ok($result);
75
         return Response::ok($result);
81
     }
76
     }
82
 
77
 
83
-    public function detail($locale = 'tw', $id){
78
+    public function detail($locale, $id)
79
+    {
84
 
80
 
85
-        $locale = $locale == "tw" ? "zh_TW" : $locale;
81
+        $locale = $locale == 'tw' ? 'zh_TW' : $locale;
86
         $project = Project::find($id);
82
         $project = Project::find($id);
87
 
83
 
88
         $projectHistories = [];
84
         $projectHistories = [];
89
         $projectSpaceInfo = [];
85
         $projectSpaceInfo = [];
90
-        foreach($project->histories as $history){
86
+        foreach ($project->histories as $history) {
91
             $operateDate = Carbon::parse($history->operate_date);
87
             $operateDate = Carbon::parse($history->operate_date);
92
-            $projectHistories[$operateDate->format("Y")][] = [
93
-                "operateDate" => $operateDate->format("m/d"),
94
-                "title" => $history->getTranslation("title", $locale)
88
+            $projectHistories[$operateDate->format('Y')][] = [
89
+                'operateDate' => $operateDate->format('m/d'),
90
+                'title' => $history->getTranslation('title', $locale),
95
             ];
91
             ];
96
         }
92
         }
97
-        foreach($project->spaceInfos as $spaceInfo){
93
+        foreach ($project->spaceInfos as $spaceInfo) {
98
             $projectSpaceInfo[] = [
94
             $projectSpaceInfo[] = [
99
-                "title" => $spaceInfo->getTranslation("title", $locale),
100
-                "content" => $spaceInfo->getTranslation("content", $locale)
95
+                'title' => $spaceInfo->getTranslation('title', $locale),
96
+                'content' => $spaceInfo->getTranslation('content', $locale),
101
             ];
97
             ];
102
         }
98
         }
103
 
99
 
104
         $result = [
100
         $result = [
105
-            "name" => $project->getTranslation("name", $locale),
106
-            "subName" => $project->getTranslation("sub_name", $locale),
107
-            "description" => $project->getTranslation("summaries", $locale),
108
-            "images" => $project->img_list,
109
-            "region_id" => $project->region_id,
110
-            "region" => $project->region->getTranslation("name", $locale),
111
-            "address" => $project->getTranslation("address", $locale),
112
-            "district" => $project->getTranslation("district", $locale),
113
-            "tags" => $project->tags->map(function ($record) use ($locale){
101
+            'name' => $project->getTranslation('name', $locale),
102
+            'subName' => $project->getTranslation('sub_name', $locale),
103
+            'description' => $project->getTranslation('summaries', $locale),
104
+            'thumbnail' => $project->thumbnail_url,
105
+            'images' => $project->img_list,
106
+            'region_id' => $project->region_id,
107
+            'region' => $project->region->getTranslation('name', $locale),
108
+            'address' => $project->getTranslation('address', $locale),
109
+            'district' => $project->getTranslation('district', $locale),
110
+            'tags' => $project->tags->map(function ($record) use ($locale) {
114
                 return [
111
                 return [
115
-                    "id" => $record->id,
116
-                    "name" => $record->getTranslation("name", $locale)
112
+                    'id' => $record->id,
113
+                    'name' => $record->getTranslation('name', $locale),
117
                 ];
114
                 ];
118
             }),
115
             }),
119
-            "floor_plan" => $project->getTranslation("floor_plan", $locale),
120
-            "building_structure" => $project->getTranslation("building_structure", $locale),
121
-            "design_unit" => $project->getTranslation("design_unit", $locale),
122
-            "badge_type" => $project->badge_type,
123
-            "badges" => $project->getBadgesByType($locale, $project->badge_type),
124
-            "contact_info" => [
125
-                "unitName" => $project->getTranslation("contact_unit", $locale),
126
-                "unitPhone" => $project->getTranslation("contact_phone", $locale),
127
-                "inversmentPhone" => $project->getTranslation("inversment_phone", $locale),
128
-                "officalLink" => $project->offical_link,
116
+            'floor_plan' => $project->getTranslation('floor_plan', $locale),
117
+            'building_structure' => $project->getTranslation('building_structure', $locale),
118
+            'design_unit' => $project->getTranslation('design_unit', $locale),
119
+            'badge_type' => $project->badge_type,
120
+            'badges' => $project->getBadgesByType($locale, $project->badge_type),
121
+            'contact_info' => [
122
+                'unitName' => $project->getTranslation('contact_unit', $locale),
123
+                'unitPhone' => $project->getTranslation('contact_phone', $locale),
124
+                'inversmentPhone' => $project->getTranslation('inversment_phone', $locale),
125
+                'officalLink' => $project->offical_link,
129
             ],
126
             ],
130
-            "spaceInfo" => $projectSpaceInfo,
131
-            "historyList" => $projectHistories
127
+            'spaceInfo' => $projectSpaceInfo,
128
+            'historyList' => $projectHistories,
132
         ];
129
         ];
130
+
133
         return Response::ok($result);
131
         return Response::ok($result);
134
     }
132
     }
135
 
133
 
136
-    public function badges($locale = 'tw') {
137
-        $locale = $locale == "tw" ? "zh_TW" : $locale;
134
+    public function badges($locale = 'tw')
135
+    {
136
+        $locale = $locale == 'tw' ? 'zh_TW' : $locale;
138
 
137
 
139
-        $projects = Project::where("badge_type", 2)->get();
138
+        $projects = Project::where('badge_type', 2)->get();
140
         $result = [];
139
         $result = [];
141
 
140
 
142
-        foreach($projects as $project){
141
+        foreach ($projects as $project) {
143
             $badgesData = $project->awardBadges;
142
             $badgesData = $project->awardBadges;
144
             $badges = [];
143
             $badges = [];
145
-            foreach($badgesData as $badge){
144
+            foreach ($badgesData as $badge) {
146
                 $badges[] = [
145
                 $badges[] = [
147
-                    "imgUrl" => $badge->imgUrlLink,
148
-                    "name" => $badge->getTranslation("title", $locale),
149
-                    "rewardYear" => "取得時間 : " . date("Y.m", strtotime($badge->pivot->award_date))
146
+                    'imgUrl' => $badge->imgUrlLink,
147
+                    'name' => $badge->getTranslation('title', $locale),
148
+                    'rewardYear' => '取得時間 : '.date('Y.m', strtotime($badge->pivot->award_date)),
150
                 ];
149
                 ];
151
             }
150
             }
152
             $result[] = [
151
             $result[] = [
153
-                "name" => $project->getTranslation("name", $locale),
154
-                "badges" => $badges
152
+                'name' => $project->getTranslation('name', $locale),
153
+                'badges' => $badges,
155
             ];
154
             ];
156
         }
155
         }
157
 
156
 

+ 43
- 32
app/Models/Project.php View File

11
 class Project extends Model
11
 class Project extends Model
12
 {
12
 {
13
     use HasTranslations, SoftDeletes;
13
     use HasTranslations, SoftDeletes;
14
+
14
     protected $guarded = ['id'];
15
     protected $guarded = ['id'];
15
 
16
 
16
-    protected $translatable = ["name", "sub_name", "summaries", "img_alt", "address", "floor_plan",
17
-        "building_structure", "design_unit", "contact_unit", "contact_phone", "inversment_phone", "district"
17
+    protected $translatable = ['name', 'sub_name', 'summaries', 'img_alt', 'address', 'floor_plan',
18
+        'building_structure', 'design_unit', 'contact_unit', 'contact_phone', 'inversment_phone', 'district',
18
     ];
19
     ];
19
-    protected $casts = ["img_url" => "array"];
20
 
20
 
21
-    protected $appends = ["first_list_img_url", "img_list"];
21
+    protected $casts = ['img_url' => 'array'];
22
+
23
+    protected $appends = ['first_list_img_url', 'img_list', 'thumbnail_url'];
22
 
24
 
23
     public function region()
25
     public function region()
24
     {
26
     {
33
     public function badges()
35
     public function badges()
34
     {
36
     {
35
         return $this->morphToMany(Badge::class, 'badgeable')
37
         return $this->morphToMany(Badge::class, 'badgeable')
36
-        ->withPivot('award_date', 'award_type')  // ✅ 關鍵
37
-        ->withTimestamps();
38
+            ->withPivot('award_date', 'award_type')  // ✅ 關鍵
39
+            ->withTimestamps();
38
     }
40
     }
39
 
41
 
40
     /**
42
     /**
72
     public function firstListImgUrl(): Attribute
74
     public function firstListImgUrl(): Attribute
73
     {
75
     {
74
         return Attribute::make(
76
         return Attribute::make(
75
-            get: fn ($value) => !empty($this->img_url) ? Storage::url($this->img_url[0]) : null,
77
+            get: fn ($value) => ! empty($this->img_url) ? Storage::url($this->img_url[0]) : null,
76
         );
78
         );
77
     }
79
     }
78
 
80
 
79
     public function imgList(): Attribute
81
     public function imgList(): Attribute
80
     {
82
     {
81
         $imgList = [];
83
         $imgList = [];
82
-        if(!is_null($this->img_url) && count($this->img_url) > 0){
83
-            foreach($this->img_url as $img){
84
+        if (! is_null($this->img_url) && count($this->img_url) > 0) {
85
+            foreach ($this->img_url as $img) {
84
                 $imgList[] = Storage::url($img);
86
                 $imgList[] = Storage::url($img);
85
             }
87
             }
86
         }
88
         }
89
+
87
         return Attribute::make(
90
         return Attribute::make(
88
             get: fn ($value) => $imgList,
91
             get: fn ($value) => $imgList,
89
         );
92
         );
90
     }
93
     }
91
 
94
 
92
-    public function getBadges($locale) : array
95
+    public function thumbnailUrl(): Attribute
96
+    {
97
+        return Attribute::make(
98
+            get: fn ($value) => ! empty($this->thumbnail) ? Storage::url($this->thumbnail) : null,
99
+        );
100
+    }
101
+
102
+    public function getBadges($locale): array
93
     {
103
     {
94
         $badges = [];
104
         $badges = [];
95
-        if($this->badges->count() > 0){
96
-            foreach($this->badges as $badge){
105
+        if ($this->badges->count() > 0) {
106
+            foreach ($this->badges as $badge) {
97
                 $badges[] = [
107
                 $badges[] = [
98
-                    "imgUrl" => $badge->imgUrlLink,
99
-                    "name" => $badge->getTranslation("title", $locale),
100
-                    "awardDate" => "取得時間 : " . date("Y.m", strtotime($badge->pivot->award_date))
108
+                    'imgUrl' => $badge->imgUrlLink,
109
+                    'name' => $badge->getTranslation('title', $locale),
110
+                    'awardDate' => '取得時間 : '.date('Y.m', strtotime($badge->pivot->award_date)),
101
                 ];
111
                 ];
102
             }
112
             }
103
         }
113
         }
105
         return $badges;
115
         return $badges;
106
     }
116
     }
107
 
117
 
108
-    public function getBadgesByType($locale, $badgeType) : array
118
+    public function getBadgesByType($locale, $badgeType): array
109
     {
119
     {
110
         $badges = [];
120
         $badges = [];
111
-        switch($badgeType){
112
-            case "1":
121
+        switch ($badgeType) {
122
+            case '1':
113
                 $badgesData = $this->targetBadges;
123
                 $badgesData = $this->targetBadges;
114
-                foreach($badgesData as $badge){
124
+                foreach ($badgesData as $badge) {
115
                     $badges[] = [
125
                     $badges[] = [
116
-                        "imgUrl" => $badge->imgUrlLink,
117
-                        "name" => $badge->getTranslation("title", $locale),
118
-                        "awardDate" => null
126
+                        'imgUrl' => $badge->imgUrlLink,
127
+                        'name' => $badge->getTranslation('title', $locale),
128
+                        'awardDate' => null,
119
                     ];
129
                     ];
120
                 }
130
                 }
121
                 break;
131
                 break;
122
-            case "2":
132
+            case '2':
123
                 $badgesData = $this->awardBadges;
133
                 $badgesData = $this->awardBadges;
124
-                foreach($badgesData as $badge){
134
+                foreach ($badgesData as $badge) {
125
                     $badges[] = [
135
                     $badges[] = [
126
-                        "imgUrl" => $badge->imgUrlLink,
127
-                        "name" => $badge->getTranslation("title", $locale),
128
-                        "awardDate" => "取得時間 : " . date("Y.m", strtotime($badge->pivot->award_date))
136
+                        'imgUrl' => $badge->imgUrlLink,
137
+                        'name' => $badge->getTranslation('title', $locale),
138
+                        'awardDate' => '取得時間 : '.date('Y.m', strtotime($badge->pivot->award_date)),
129
                     ];
139
                     ];
130
                 }
140
                 }
131
                 break;
141
                 break;
132
         }
142
         }
143
+
133
         return $badges;
144
         return $badges;
134
     }
145
     }
135
 
146
 
136
-    public function getTags($locale) : array
147
+    public function getTags($locale): array
137
     {
148
     {
138
         $tags = [];
149
         $tags = [];
139
-        if($this->tags->count() > 0){
140
-            foreach($this->tags as $tag){
150
+        if ($this->tags->count() > 0) {
151
+            foreach ($this->tags as $tag) {
141
                 $tags[] = [
152
                 $tags[] = [
142
-                    "id" => $tag->id,
143
-                    "name" => $tag->getTranslation("name", $locale),
153
+                    'id' => $tag->id,
154
+                    'name' => $tag->getTranslation('name', $locale),
144
                 ];
155
                 ];
145
             }
156
             }
146
         }
157
         }

+ 1
- 0
composer.json View File

21
     },
21
     },
22
     "require-dev": {
22
     "require-dev": {
23
         "fakerphp/faker": "^1.23",
23
         "fakerphp/faker": "^1.23",
24
+        "laravel/boost": "^1.1",
24
         "laravel/pail": "^1.2.2",
25
         "laravel/pail": "^1.2.2",
25
         "laravel/pint": "^1.24",
26
         "laravel/pint": "^1.24",
26
         "laravel/sail": "^1.41",
27
         "laravel/sail": "^1.41",

+ 192
- 2
composer.lock View File

4
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
4
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
         "This file is @generated automatically"
5
         "This file is @generated automatically"
6
     ],
6
     ],
7
-    "content-hash": "e904da9638c3c3831ee33c8e624ce412",
7
+    "content-hash": "44b40d372827055e89cb5b1d76a38986",
8
     "packages": [
8
     "packages": [
9
         {
9
         {
10
             "name": "abdulmajeed-jamaan/filament-translatable-tabs",
10
             "name": "abdulmajeed-jamaan/filament-translatable-tabs",
8759
             "time": "2025-04-30T06:54:44+00:00"
8759
             "time": "2025-04-30T06:54:44+00:00"
8760
         },
8760
         },
8761
         {
8761
         {
8762
+            "name": "laravel/boost",
8763
+            "version": "v1.1.5",
8764
+            "source": {
8765
+                "type": "git",
8766
+                "url": "https://github.com/laravel/boost.git",
8767
+                "reference": "4bd1692c064b2135eb2f8f7bd8bcb710e5e75f86"
8768
+            },
8769
+            "dist": {
8770
+                "type": "zip",
8771
+                "url": "https://api.github.com/repos/laravel/boost/zipball/4bd1692c064b2135eb2f8f7bd8bcb710e5e75f86",
8772
+                "reference": "4bd1692c064b2135eb2f8f7bd8bcb710e5e75f86",
8773
+                "shasum": ""
8774
+            },
8775
+            "require": {
8776
+                "guzzlehttp/guzzle": "^7.9",
8777
+                "illuminate/console": "^10.0|^11.0|^12.0",
8778
+                "illuminate/contracts": "^10.0|^11.0|^12.0",
8779
+                "illuminate/routing": "^10.0|^11.0|^12.0",
8780
+                "illuminate/support": "^10.0|^11.0|^12.0",
8781
+                "laravel/mcp": "^0.1.1",
8782
+                "laravel/prompts": "^0.1.9|^0.3",
8783
+                "laravel/roster": "^0.2.5",
8784
+                "php": "^8.1"
8785
+            },
8786
+            "require-dev": {
8787
+                "laravel/pint": "^1.14",
8788
+                "mockery/mockery": "^1.6",
8789
+                "orchestra/testbench": "^8.22.0|^9.0|^10.0",
8790
+                "pestphp/pest": "^2.0|^3.0",
8791
+                "phpstan/phpstan": "^2.0"
8792
+            },
8793
+            "type": "library",
8794
+            "extra": {
8795
+                "laravel": {
8796
+                    "providers": [
8797
+                        "Laravel\\Boost\\BoostServiceProvider"
8798
+                    ]
8799
+                },
8800
+                "branch-alias": {
8801
+                    "dev-master": "1.x-dev"
8802
+                }
8803
+            },
8804
+            "autoload": {
8805
+                "psr-4": {
8806
+                    "Laravel\\Boost\\": "src/"
8807
+                }
8808
+            },
8809
+            "notification-url": "https://packagist.org/downloads/",
8810
+            "license": [
8811
+                "MIT"
8812
+            ],
8813
+            "description": "Laravel Boost accelerates AI-assisted development to generate high-quality, Laravel-specific code.",
8814
+            "homepage": "https://github.com/laravel/boost",
8815
+            "keywords": [
8816
+                "ai",
8817
+                "dev",
8818
+                "laravel"
8819
+            ],
8820
+            "support": {
8821
+                "issues": "https://github.com/laravel/boost/issues",
8822
+                "source": "https://github.com/laravel/boost"
8823
+            },
8824
+            "time": "2025-09-18T07:33:27+00:00"
8825
+        },
8826
+        {
8827
+            "name": "laravel/mcp",
8828
+            "version": "v0.1.1",
8829
+            "source": {
8830
+                "type": "git",
8831
+                "url": "https://github.com/laravel/mcp.git",
8832
+                "reference": "6d6284a491f07c74d34f48dfd999ed52c567c713"
8833
+            },
8834
+            "dist": {
8835
+                "type": "zip",
8836
+                "url": "https://api.github.com/repos/laravel/mcp/zipball/6d6284a491f07c74d34f48dfd999ed52c567c713",
8837
+                "reference": "6d6284a491f07c74d34f48dfd999ed52c567c713",
8838
+                "shasum": ""
8839
+            },
8840
+            "require": {
8841
+                "illuminate/console": "^10.0|^11.0|^12.0",
8842
+                "illuminate/contracts": "^10.0|^11.0|^12.0",
8843
+                "illuminate/http": "^10.0|^11.0|^12.0",
8844
+                "illuminate/routing": "^10.0|^11.0|^12.0",
8845
+                "illuminate/support": "^10.0|^11.0|^12.0",
8846
+                "illuminate/validation": "^10.0|^11.0|^12.0",
8847
+                "php": "^8.1|^8.2"
8848
+            },
8849
+            "require-dev": {
8850
+                "laravel/pint": "^1.14",
8851
+                "orchestra/testbench": "^8.22.0|^9.0|^10.0",
8852
+                "phpstan/phpstan": "^2.0"
8853
+            },
8854
+            "type": "library",
8855
+            "extra": {
8856
+                "laravel": {
8857
+                    "aliases": {
8858
+                        "Mcp": "Laravel\\Mcp\\Server\\Facades\\Mcp"
8859
+                    },
8860
+                    "providers": [
8861
+                        "Laravel\\Mcp\\Server\\McpServiceProvider"
8862
+                    ]
8863
+                }
8864
+            },
8865
+            "autoload": {
8866
+                "psr-4": {
8867
+                    "Laravel\\Mcp\\": "src/",
8868
+                    "Workbench\\App\\": "workbench/app/",
8869
+                    "Laravel\\Mcp\\Tests\\": "tests/",
8870
+                    "Laravel\\Mcp\\Server\\": "src/Server/"
8871
+                }
8872
+            },
8873
+            "notification-url": "https://packagist.org/downloads/",
8874
+            "license": [
8875
+                "MIT"
8876
+            ],
8877
+            "description": "The easiest way to add MCP servers to your Laravel app.",
8878
+            "homepage": "https://github.com/laravel/mcp",
8879
+            "keywords": [
8880
+                "dev",
8881
+                "laravel",
8882
+                "mcp"
8883
+            ],
8884
+            "support": {
8885
+                "issues": "https://github.com/laravel/mcp/issues",
8886
+                "source": "https://github.com/laravel/mcp"
8887
+            },
8888
+            "time": "2025-08-16T09:50:43+00:00"
8889
+        },
8890
+        {
8762
             "name": "laravel/pail",
8891
             "name": "laravel/pail",
8763
             "version": "v1.2.3",
8892
             "version": "v1.2.3",
8764
             "source": {
8893
             "source": {
8907
             "time": "2025-07-10T18:09:32+00:00"
9036
             "time": "2025-07-10T18:09:32+00:00"
8908
         },
9037
         },
8909
         {
9038
         {
9039
+            "name": "laravel/roster",
9040
+            "version": "v0.2.9",
9041
+            "source": {
9042
+                "type": "git",
9043
+                "url": "https://github.com/laravel/roster.git",
9044
+                "reference": "82bbd0e2de614906811aebdf16b4305956816fa6"
9045
+            },
9046
+            "dist": {
9047
+                "type": "zip",
9048
+                "url": "https://api.github.com/repos/laravel/roster/zipball/82bbd0e2de614906811aebdf16b4305956816fa6",
9049
+                "reference": "82bbd0e2de614906811aebdf16b4305956816fa6",
9050
+                "shasum": ""
9051
+            },
9052
+            "require": {
9053
+                "illuminate/console": "^10.0|^11.0|^12.0",
9054
+                "illuminate/contracts": "^10.0|^11.0|^12.0",
9055
+                "illuminate/routing": "^10.0|^11.0|^12.0",
9056
+                "illuminate/support": "^10.0|^11.0|^12.0",
9057
+                "php": "^8.1|^8.2",
9058
+                "symfony/yaml": "^6.4|^7.2"
9059
+            },
9060
+            "require-dev": {
9061
+                "laravel/pint": "^1.14",
9062
+                "mockery/mockery": "^1.6",
9063
+                "orchestra/testbench": "^8.22.0|^9.0|^10.0",
9064
+                "pestphp/pest": "^2.0|^3.0",
9065
+                "phpstan/phpstan": "^2.0"
9066
+            },
9067
+            "type": "library",
9068
+            "extra": {
9069
+                "laravel": {
9070
+                    "providers": [
9071
+                        "Laravel\\Roster\\RosterServiceProvider"
9072
+                    ]
9073
+                },
9074
+                "branch-alias": {
9075
+                    "dev-master": "1.x-dev"
9076
+                }
9077
+            },
9078
+            "autoload": {
9079
+                "psr-4": {
9080
+                    "Laravel\\Roster\\": "src/"
9081
+                }
9082
+            },
9083
+            "notification-url": "https://packagist.org/downloads/",
9084
+            "license": [
9085
+                "MIT"
9086
+            ],
9087
+            "description": "Detect packages & approaches in use within a Laravel project",
9088
+            "homepage": "https://github.com/laravel/roster",
9089
+            "keywords": [
9090
+                "dev",
9091
+                "laravel"
9092
+            ],
9093
+            "support": {
9094
+                "issues": "https://github.com/laravel/roster/issues",
9095
+                "source": "https://github.com/laravel/roster"
9096
+            },
9097
+            "time": "2025-10-20T09:56:46+00:00"
9098
+        },
9099
+        {
8910
             "name": "laravel/sail",
9100
             "name": "laravel/sail",
8911
             "version": "v1.45.0",
9101
             "version": "v1.45.0",
8912
             "source": {
9102
             "source": {
10935
         "php": "^8.2"
11125
         "php": "^8.2"
10936
     },
11126
     },
10937
     "platform-dev": [],
11127
     "platform-dev": [],
10938
-    "plugin-api-version": "2.6.0"
11128
+    "plugin-api-version": "2.3.0"
10939
 }
11129
 }

+ 28
- 0
database/migrations/2025_11_18_082640_add_thumbnail_to_projects_table.php View File

1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::table('projects', function (Blueprint $table) {
15
+            $table->string('thumbnail')->nullable()->after('img_url')->comment('縮圖');
16
+        });
17
+    }
18
+
19
+    /**
20
+     * Reverse the migrations.
21
+     */
22
+    public function down(): void
23
+    {
24
+        Schema::table('projects', function (Blueprint $table) {
25
+            $table->dropColumn('thumbnail');
26
+        });
27
+    }
28
+};