From 0c610c1e498529eaa5b98137bbff3490f635c54f Mon Sep 17 00:00:00 2001 From: zyc <1439655764@qq.com> Date: Tue, 17 Mar 2026 13:17:02 +0800 Subject: [PATCH] first commit --- .gitignore | 52 + qy-lty-admin/.dockerignore | 34 + qy-lty-admin/.env.example | 7 + qy-lty-admin/.env.local.example | 2 + qy-lty-admin/.gitignore | 35 + qy-lty-admin/Dockerfile | 52 + qy-lty-admin/README.md | 137 + qy-lty-admin/app/achievements/loading.tsx | 48 + qy-lty-admin/app/achievements/page.tsx | 469 ++ qy-lty-admin/app/affinity/page.tsx | 1005 ++++ qy-lty-admin/app/ai-model/page.tsx | 445 ++ qy-lty-admin/app/dances/[id]/loading.tsx | 122 + qy-lty-admin/app/dances/[id]/page.tsx | 466 ++ qy-lty-admin/app/dances/loading.tsx | 38 + qy-lty-admin/app/dances/page.tsx | 352 ++ qy-lty-admin/app/food/[id]/loading.tsx | 65 + qy-lty-admin/app/food/[id]/page.tsx | 299 + qy-lty-admin/app/food/loading.tsx | 3 + qy-lty-admin/app/food/page.tsx | 277 + qy-lty-admin/app/forgot-password/layout.tsx | 8 + qy-lty-admin/app/forgot-password/loading.tsx | 12 + qy-lty-admin/app/forgot-password/page.tsx | 259 + qy-lty-admin/app/globals.css | 94 + qy-lty-admin/app/home-decor/[id]/loading.tsx | 65 + qy-lty-admin/app/home-decor/[id]/page.tsx | 327 ++ qy-lty-admin/app/home-decor/loading.tsx | 3 + qy-lty-admin/app/home-decor/page.tsx | 284 + qy-lty-admin/app/layout.tsx | 20 + qy-lty-admin/app/login/layout.tsx | 8 + qy-lty-admin/app/login/loading.tsx | 12 + qy-lty-admin/app/login/page.tsx | 275 + qy-lty-admin/app/outfits/[id]/page.tsx | 278 + qy-lty-admin/app/outfits/edit/[id]/page.tsx | 273 + qy-lty-admin/app/outfits/loading.tsx | 3 + qy-lty-admin/app/outfits/page.tsx | 488 ++ qy-lty-admin/app/page.tsx | 192 + qy-lty-admin/app/permissions/loading.tsx | 3 + qy-lty-admin/app/permissions/page.tsx | 431 ++ qy-lty-admin/app/props/[id]/loading.tsx | 148 + qy-lty-admin/app/props/[id]/page.tsx | 382 ++ qy-lty-admin/app/props/loading.tsx | 3 + qy-lty-admin/app/props/page.tsx | 283 + qy-lty-admin/app/register/layout.tsx | 8 + qy-lty-admin/app/register/loading.tsx | 12 + qy-lty-admin/app/register/page.tsx | 240 + qy-lty-admin/app/settings/loading.tsx | 3 + qy-lty-admin/app/settings/page.tsx | 476 ++ qy-lty-admin/app/songs/[id]/loading.tsx | 3 + qy-lty-admin/app/songs/[id]/page.tsx | 651 +++ qy-lty-admin/app/songs/loading.tsx | 3 + qy-lty-admin/app/songs/page.tsx | 598 ++ qy-lty-admin/app/users/loading.tsx | 3 + qy-lty-admin/app/users/page.tsx | 380 ++ qy-lty-admin/components.json | 21 + .../achievement-detail-dialog.tsx | 162 + .../achievements/add-achievement-dialog.tsx | 223 + qy-lty-admin/components/add-outfit-dialog.tsx | 345 ++ .../affinity/affinity-level-dialog.tsx | 320 ++ .../affinity/affinity-rule-dialog.tsx | 321 ++ .../components/dances/add-dance-dialog.tsx | 351 ++ .../components/dances/dance-detail-dialog.tsx | 154 + qy-lty-admin/components/dashboard-header.tsx | 20 + qy-lty-admin/components/dashboard-shell.tsx | 16 + .../components/delete-confirmation-dialog.tsx | 97 + .../components/food/add-food-dialog.tsx | 446 ++ .../food/add-print-batch-dialog.tsx | 152 + .../components/food/export-cards-dialog.tsx | 135 + .../components/food/food-detail-dialog.tsx | 119 + .../components/food/food-media-upload.tsx | 264 + .../home-decor/add-home-decor-dialog.tsx | 310 ++ .../home-decor/add-print-batch-dialog.tsx | 152 + .../home-decor/export-cards-dialog.tsx | 135 + .../home-decor/home-decor-detail-dialog.tsx | 106 + .../components/outfits/add-outfit-dialog.tsx | 345 ++ .../outfits/add-print-batch-dialog.tsx | 93 + .../outfits/export-cards-dialog.tsx | 131 + qy-lty-admin/components/overview.tsx | 73 + .../components/permissions/role-dialog.tsx | 424 ++ .../props/add-print-batch-dialog.tsx | 165 + .../components/props/add-prop-dialog.tsx | 306 + .../components/props/export-cards-dialog.tsx | 146 + .../components/props/prop-detail-dialog.tsx | 109 + .../publish-confirmation-dialog.tsx | 110 + qy-lty-admin/components/recent-activity.tsx | 77 + qy-lty-admin/components/sidebar.tsx | 291 + .../songs/add-print-batch-dialog.tsx | 139 + .../components/songs/add-song-dialog.tsx | 337 ++ .../components/songs/export-batch-dialog.tsx | 172 + .../components/songs/export-cards-dialog.tsx | 79 + .../components/songs/song-detail-dialog.tsx | 299 + qy-lty-admin/components/stat-card.tsx | 38 + qy-lty-admin/components/theme-provider.tsx | 11 + qy-lty-admin/components/ui/accordion.tsx | 58 + qy-lty-admin/components/ui/alert-dialog.tsx | 141 + qy-lty-admin/components/ui/alert.tsx | 59 + qy-lty-admin/components/ui/aspect-ratio.tsx | 7 + qy-lty-admin/components/ui/avatar.tsx | 50 + qy-lty-admin/components/ui/badge.tsx | 36 + qy-lty-admin/components/ui/breadcrumb.tsx | 115 + qy-lty-admin/components/ui/button.tsx | 56 + qy-lty-admin/components/ui/calendar.tsx | 66 + qy-lty-admin/components/ui/card.tsx | 79 + qy-lty-admin/components/ui/carousel.tsx | 262 + qy-lty-admin/components/ui/chart.tsx | 365 ++ qy-lty-admin/components/ui/checkbox.tsx | 30 + qy-lty-admin/components/ui/collapsible.tsx | 11 + qy-lty-admin/components/ui/command.tsx | 153 + qy-lty-admin/components/ui/context-menu.tsx | 200 + qy-lty-admin/components/ui/dialog.tsx | 122 + qy-lty-admin/components/ui/drawer.tsx | 118 + qy-lty-admin/components/ui/dropdown-menu.tsx | 200 + qy-lty-admin/components/ui/file-upload.tsx | 314 ++ qy-lty-admin/components/ui/form.tsx | 178 + qy-lty-admin/components/ui/hover-card.tsx | 29 + qy-lty-admin/components/ui/input-otp.tsx | 71 + qy-lty-admin/components/ui/input.tsx | 22 + qy-lty-admin/components/ui/label.tsx | 26 + qy-lty-admin/components/ui/menubar.tsx | 236 + .../components/ui/navigation-menu.tsx | 128 + qy-lty-admin/components/ui/pagination.tsx | 117 + qy-lty-admin/components/ui/popover.tsx | 31 + qy-lty-admin/components/ui/progress.tsx | 28 + qy-lty-admin/components/ui/radio-group.tsx | 44 + qy-lty-admin/components/ui/resizable.tsx | 45 + qy-lty-admin/components/ui/scroll-area.tsx | 48 + qy-lty-admin/components/ui/select.tsx | 160 + qy-lty-admin/components/ui/separator.tsx | 31 + qy-lty-admin/components/ui/sheet.tsx | 140 + qy-lty-admin/components/ui/sidebar.tsx | 763 +++ qy-lty-admin/components/ui/skeleton.tsx | 15 + qy-lty-admin/components/ui/slider.tsx | 28 + qy-lty-admin/components/ui/sonner.tsx | 31 + qy-lty-admin/components/ui/switch.tsx | 29 + qy-lty-admin/components/ui/table.tsx | 117 + qy-lty-admin/components/ui/tabs.tsx | 55 + qy-lty-admin/components/ui/textarea.tsx | 22 + qy-lty-admin/components/ui/toast.tsx | 129 + qy-lty-admin/components/ui/toaster.tsx | 35 + qy-lty-admin/components/ui/toggle-group.tsx | 61 + qy-lty-admin/components/ui/toggle.tsx | 45 + qy-lty-admin/components/ui/tooltip.tsx | 30 + qy-lty-admin/components/ui/upload-example.tsx | 155 + qy-lty-admin/components/ui/use-mobile.tsx | 19 + qy-lty-admin/components/ui/use-toast.ts | 194 + .../components/users/user-detail-dialog.tsx | 233 + .../components/users/user-form-dialog.tsx | 288 + qy-lty-admin/docker-compose.yml | 22 + qy-lty-admin/hooks/use-mobile.tsx | 19 + qy-lty-admin/hooks/use-toast.ts | 194 + qy-lty-admin/lib/api/achievements.ts | 266 + qy-lty-admin/lib/api/adapters.ts | 72 + qy-lty-admin/lib/api/affinity.ts | 306 + qy-lty-admin/lib/api/ai-models.ts | 185 + qy-lty-admin/lib/api/auth.ts | 108 + qy-lty-admin/lib/api/card.ts | 21 + qy-lty-admin/lib/api/client.ts | 271 + qy-lty-admin/lib/api/dances.ts | 288 + qy-lty-admin/lib/api/error-handler.ts | 144 + qy-lty-admin/lib/api/food.ts | 273 + qy-lty-admin/lib/api/home-decor.ts | 188 + qy-lty-admin/lib/api/index.ts | 196 + qy-lty-admin/lib/api/outfits.ts | 189 + qy-lty-admin/lib/api/props.ts | 188 + qy-lty-admin/lib/api/roles.ts | 177 + qy-lty-admin/lib/api/song.type.ts | 193 + qy-lty-admin/lib/api/songs.ts | 313 ++ qy-lty-admin/lib/api/token-debug.ts | 96 + qy-lty-admin/lib/api/types.ts | 301 + qy-lty-admin/lib/api/upload.ts | 362 ++ qy-lty-admin/lib/api/users.ts | 225 + qy-lty-admin/lib/utils.ts | 6 + qy-lty-admin/middleware.ts | 44 + qy-lty-admin/next.config.mjs | 52 + qy-lty-admin/package-lock.json | 4956 +++++++++++++++++ qy-lty-admin/package.json | 73 + qy-lty-admin/pnpm-lock.yaml | 5 + qy-lty-admin/postcss.config.mjs | 8 + qy-lty-admin/public/placeholder-logo.png | Bin 0 -> 958 bytes qy-lty-admin/public/placeholder-logo.svg | 1 + qy-lty-admin/public/placeholder-user.jpg | Bin 0 -> 2615 bytes qy-lty-admin/public/placeholder.jpg | Bin 0 -> 1596 bytes qy-lty-admin/public/placeholder.svg | 1 + qy-lty-admin/styles/globals.css | 94 + qy-lty-admin/tailwind.config.ts | 96 + qy-lty-admin/todo | 9 + qy-lty-admin/tsconfig.json | 27 + qy-lty-admin/yarn.lock | 2210 ++++++++ qy_lty/.env | 107 + qy_lty/CLAUDE.md | 210 + qy_lty/Dockerfile | 32 + qy_lty/achievement_app/__init__.py | 0 qy_lty/achievement_app/admin.py | 53 + qy_lty/achievement_app/apps.py | 7 + .../migrations/0001_initial.py | 173 + .../migrations/0002_auto_20250508_1800.py | 12 + .../migrations/0003_alter_achievement_icon.py | 25 + .../0004_alter_achievement_rarity.py | 29 + .../0005_alter_achievement_rarity.py | 29 + qy_lty/achievement_app/migrations/__init__.py | 0 qy_lty/achievement_app/models.py | 106 + qy_lty/achievement_app/serializers.py | 59 + qy_lty/achievement_app/tests.py | 3 + qy_lty/achievement_app/urls.py | 11 + qy_lty/achievement_app/views.py | 242 + qy_lty/aiapp/__init__.py | 0 qy_lty/aiapp/admin.py | 14 + qy_lty/aiapp/apps.py | 8 + qy_lty/aiapp/audio/AliyunAudioService.py | 218 + qy_lty/aiapp/audio/AudioService.py | 27 + qy_lty/aiapp/audio/BaseAudioService.py | 34 + qy_lty/aiapp/audio/HuoshanAudioService.py | 166 + qy_lty/aiapp/audio/HuoshanAudioService_new.py | 0 qy_lty/aiapp/audio/TencentAudioService.py | 60 + qy_lty/aiapp/audio/__init__.py | 0 qy_lty/aiapp/audio/test.py | 159 + qy_lty/aiapp/kimi.py | 29 + qy_lty/aiapp/migrations/0001_initial.py | 103 + qy_lty/aiapp/migrations/0002_initial.py | 27 + qy_lty/aiapp/migrations/__init__.py | 0 qy_lty/aiapp/models.py | 52 + qy_lty/aiapp/serializers.py | 8 + qy_lty/aiapp/tests.py | 24 + qy_lty/aiapp/urls.py | 7 + qy_lty/aiapp/views.py | 406 ++ qy_lty/ali_vi_app/__init__.py | 0 qy_lty/ali_vi_app/admin.py | 3 + qy_lty/ali_vi_app/apps.py | 6 + qy_lty/ali_vi_app/migrations/__init__.py | 0 qy_lty/ali_vi_app/models.py | 3 + qy_lty/ali_vi_app/tests.py | 3 + qy_lty/ali_vi_app/urls.py | 12 + qy_lty/ali_vi_app/vi.py | 232 + qy_lty/ali_vi_app/views.py | 208 + qy_lty/card/__init__.py | 0 qy_lty/card/admin.py | 266 + qy_lty/card/apps.py | 6 + qy_lty/card/example_data.py | 926 +++ .../commands/create_example_cards.py | 21 + qy_lty/card/migrations/0001_initial.py | 242 + qy_lty/card/migrations/0002_initial.py | 65 + ...card_batch_cardbatch_published_and_more.py | 218 + ...hingattributes_danceattributes_and_more.py | 289 + ...egory_alter_cardbatch_category_and_more.py | 64 + ...006_cardbatch_end_id_cardbatch_start_id.py | 27 + .../0007_alter_songattributes_audio_file.py | 18 + .../0008_alter_songattributes_audio_file.py | 18 + .../0009_alter_songattributes_bpm.py | 18 + qy_lty/card/migrations/__init__.py | 0 qy_lty/card/models.py | 357 ++ qy_lty/card/serializers.py | 437 ++ qy_lty/card/storage.py | 151 + qy_lty/card/tests.py | 3 + qy_lty/card/urls.py | 15 + qy_lty/card/views.py | 1022 ++++ qy_lty/claude.json | 212 + qy_lty/common/VolcEngineAccessToken.py | 204 + qy_lty/common/__init__.py | 0 qy_lty/common/aliyun_logging.py | 36 + qy_lty/common/middleware.py | 145 + qy_lty/common/oss.py | 90 + qy_lty/common/pagination.py | 9 + qy_lty/common/responses.py | 122 + qy_lty/common/swagger_utils.py | 146 + qy_lty/common/views.py | 92 + qy_lty/dev_start.bat | 45 + qy_lty/device_interaction/__init__.py | 0 qy_lty/device_interaction/admin.py | 44 + qy_lty/device_interaction/amap_api.py | 243 + qy_lty/device_interaction/apps.py | 6 + qy_lty/device_interaction/auth.py | 50 + qy_lty/device_interaction/consumers.py | 492 ++ .../device_interaction/management/__init__.py | 1 + .../management/commands/__init__.py | 1 + .../management/commands/clean_test_devices.py | 79 + .../commands/create_test_devices.py | 95 + qy_lty/device_interaction/middleware.py | 33 + .../migrations/0001_initial.py | 230 + ...attery_level_device_brightness_and_more.py | 58 + .../device_interaction/migrations/__init__.py | 0 qy_lty/device_interaction/models.py | 117 + qy_lty/device_interaction/routing.py | 7 + qy_lty/device_interaction/serializers.py | 101 + qy_lty/device_interaction/services.py | 21 + qy_lty/device_interaction/swagger.py | 205 + qy_lty/device_interaction/tests.py | 3 + qy_lty/device_interaction/urls.py | 114 + qy_lty/device_interaction/views.py | 1565 ++++++ qy_lty/device_interaction/volcengine_api.py | 157 + qy_lty/device_interaction/weather.py | 159 + qy_lty/docker-compose.yml | 32 + qy_lty/docs/README.md | 263 + qy_lty/docs/api/achievement_api.md | 782 +++ qy_lty/docs/api/ai_api.md | 463 ++ qy_lty/docs/api/card_api.md | 767 +++ qy_lty/docs/development/architecture.md | 704 +++ qy_lty/docs/development/setup.md | 611 ++ qy_lty/docs/device_api.md | 752 +++ qy_lty/docs/device_commands.md | 131 + qy_lty/docs/device_errors.md | 160 + qy_lty/docs/device_module.md | 228 + qy_lty/docs/features/websocket.md | 1197 ++++ qy_lty/docs/integrations/audio_services.md | 926 +++ qy_lty/docs/user_authentication_api.md | 256 + qy_lty/environment.yml | 39 + qy_lty/food_app/__init__.py | 0 qy_lty/food_app/admin.py | 192 + qy_lty/food_app/apps.py | 6 + qy_lty/food_app/migrations/0001_initial.py | 376 ++ ...nimation_file_alter_food_image_and_more.py | 46 + qy_lty/food_app/migrations/__init__.py | 0 qy_lty/food_app/models.py | 273 + qy_lty/food_app/serializers.py | 90 + qy_lty/food_app/tests.py | 3 + qy_lty/food_app/urls.py | 21 + qy_lty/food_app/views.py | 342 ++ qy_lty/function_call_test.py | 222 + qy_lty/locale/en/LC_MESSAGES/django.po | 147 + qy_lty/locale/zh_HAns/LC_MESSAGES/django.po | 153 + qy_lty/manage.py | 22 + qy_lty/qy_lty/__init__.py | 0 qy_lty/qy_lty/asgi.py | 26 + qy_lty/qy_lty/settings.py | 649 +++ qy_lty/qy_lty/throttling.py | 20 + qy_lty/qy_lty/urls.py | 83 + qy_lty/qy_lty/wsgi.py | 16 + qy_lty/readme.md | 243 + qy_lty/requirements.txt | 41 + qy_lty/run.sh | 2 + qy_lty/start.bat | 35 + qy_lty/subscription_app/__init__.py | 0 qy_lty/subscription_app/admin.py | 26 + qy_lty/subscription_app/apps.py | 23 + .../migrations/0001_initial.py | 167 + .../migrations/0002_initial.py | 74 + .../subscription_app/migrations/__init__.py | 0 qy_lty/subscription_app/models.py | 95 + qy_lty/subscription_app/scheduler.py | 28 + qy_lty/subscription_app/tasks.py | 30 + .../templates/admin/base.html | 17 + .../templates/admin/base_site.html | 17 + qy_lty/subscription_app/tests.py | 3 + qy_lty/subscription_app/views.py | 3 + qy_lty/templates/admin/base copy.html | 17 + qy_lty/templates/admin/index.html | 387 ++ qy_lty/templates/simpleui/admin/base.html | 17 + .../templates/simpleui/admin/base_site.html | 17 + qy_lty/templates/simpleui/admin/index.html | 17 + qy_lty/test.mp3 | Bin 0 -> 247248 bytes qy_lty/userapp/README.md | 69 + qy_lty/userapp/__init__.py | 0 qy_lty/userapp/admin.py | 32 + qy_lty/userapp/admin_urls.py | 11 + qy_lty/userapp/apps.py | 6 + qy_lty/userapp/auth_urls.py | 111 + qy_lty/userapp/authentication.py | 31 + qy_lty/userapp/migrations/0001_initial.py | 151 + ...diseuser_userapp_par_usernam_583392_idx.py | 20 + ...hday_paradiseuser_favorability_and_more.py | 88 + qy_lty/userapp/migrations/__init__.py | 0 qy_lty/userapp/models.py | 76 + qy_lty/userapp/serializers.py | 71 + qy_lty/userapp/tests.py | 3 + qy_lty/userapp/urls.py | 28 + qy_lty/userapp/utils.py | 47 + qy_lty/userapp/views.py | 697 +++ qy_lty/userapp/views_old.py | 694 +++ qy_lty/weather_test.py | 208 + qy_lty/workflow_app/__init__.py | 0 qy_lty/workflow_app/admin.py | 3 + qy_lty/workflow_app/apps.py | 6 + qy_lty/workflow_app/migrations/__init__.py | 0 qy_lty/workflow_app/models.py | 3 + qy_lty/workflow_app/tests.py | 3 + qy_lty/workflow_app/views.py | 3 + 374 files changed, 61104 insertions(+) create mode 100644 .gitignore create mode 100644 qy-lty-admin/.dockerignore create mode 100644 qy-lty-admin/.env.example create mode 100644 qy-lty-admin/.env.local.example create mode 100644 qy-lty-admin/.gitignore create mode 100644 qy-lty-admin/Dockerfile create mode 100644 qy-lty-admin/README.md create mode 100644 qy-lty-admin/app/achievements/loading.tsx create mode 100644 qy-lty-admin/app/achievements/page.tsx create mode 100644 qy-lty-admin/app/affinity/page.tsx create mode 100644 qy-lty-admin/app/ai-model/page.tsx create mode 100644 qy-lty-admin/app/dances/[id]/loading.tsx create mode 100644 qy-lty-admin/app/dances/[id]/page.tsx create mode 100644 qy-lty-admin/app/dances/loading.tsx create mode 100644 qy-lty-admin/app/dances/page.tsx create mode 100644 qy-lty-admin/app/food/[id]/loading.tsx create mode 100644 qy-lty-admin/app/food/[id]/page.tsx create mode 100644 qy-lty-admin/app/food/loading.tsx create mode 100644 qy-lty-admin/app/food/page.tsx create mode 100644 qy-lty-admin/app/forgot-password/layout.tsx create mode 100644 qy-lty-admin/app/forgot-password/loading.tsx create mode 100644 qy-lty-admin/app/forgot-password/page.tsx create mode 100644 qy-lty-admin/app/globals.css create mode 100644 qy-lty-admin/app/home-decor/[id]/loading.tsx create mode 100644 qy-lty-admin/app/home-decor/[id]/page.tsx create mode 100644 qy-lty-admin/app/home-decor/loading.tsx create mode 100644 qy-lty-admin/app/home-decor/page.tsx create mode 100644 qy-lty-admin/app/layout.tsx create mode 100644 qy-lty-admin/app/login/layout.tsx create mode 100644 qy-lty-admin/app/login/loading.tsx create mode 100644 qy-lty-admin/app/login/page.tsx create mode 100644 qy-lty-admin/app/outfits/[id]/page.tsx create mode 100644 qy-lty-admin/app/outfits/edit/[id]/page.tsx create mode 100644 qy-lty-admin/app/outfits/loading.tsx create mode 100644 qy-lty-admin/app/outfits/page.tsx create mode 100644 qy-lty-admin/app/page.tsx create mode 100644 qy-lty-admin/app/permissions/loading.tsx create mode 100644 qy-lty-admin/app/permissions/page.tsx create mode 100644 qy-lty-admin/app/props/[id]/loading.tsx create mode 100644 qy-lty-admin/app/props/[id]/page.tsx create mode 100644 qy-lty-admin/app/props/loading.tsx create mode 100644 qy-lty-admin/app/props/page.tsx create mode 100644 qy-lty-admin/app/register/layout.tsx create mode 100644 qy-lty-admin/app/register/loading.tsx create mode 100644 qy-lty-admin/app/register/page.tsx create mode 100644 qy-lty-admin/app/settings/loading.tsx create mode 100644 qy-lty-admin/app/settings/page.tsx create mode 100644 qy-lty-admin/app/songs/[id]/loading.tsx create mode 100644 qy-lty-admin/app/songs/[id]/page.tsx create mode 100644 qy-lty-admin/app/songs/loading.tsx create mode 100644 qy-lty-admin/app/songs/page.tsx create mode 100644 qy-lty-admin/app/users/loading.tsx create mode 100644 qy-lty-admin/app/users/page.tsx create mode 100644 qy-lty-admin/components.json create mode 100644 qy-lty-admin/components/achievements/achievement-detail-dialog.tsx create mode 100644 qy-lty-admin/components/achievements/add-achievement-dialog.tsx create mode 100644 qy-lty-admin/components/add-outfit-dialog.tsx create mode 100644 qy-lty-admin/components/affinity/affinity-level-dialog.tsx create mode 100644 qy-lty-admin/components/affinity/affinity-rule-dialog.tsx create mode 100644 qy-lty-admin/components/dances/add-dance-dialog.tsx create mode 100644 qy-lty-admin/components/dances/dance-detail-dialog.tsx create mode 100644 qy-lty-admin/components/dashboard-header.tsx create mode 100644 qy-lty-admin/components/dashboard-shell.tsx create mode 100644 qy-lty-admin/components/delete-confirmation-dialog.tsx create mode 100644 qy-lty-admin/components/food/add-food-dialog.tsx create mode 100644 qy-lty-admin/components/food/add-print-batch-dialog.tsx create mode 100644 qy-lty-admin/components/food/export-cards-dialog.tsx create mode 100644 qy-lty-admin/components/food/food-detail-dialog.tsx create mode 100644 qy-lty-admin/components/food/food-media-upload.tsx create mode 100644 qy-lty-admin/components/home-decor/add-home-decor-dialog.tsx create mode 100644 qy-lty-admin/components/home-decor/add-print-batch-dialog.tsx create mode 100644 qy-lty-admin/components/home-decor/export-cards-dialog.tsx create mode 100644 qy-lty-admin/components/home-decor/home-decor-detail-dialog.tsx create mode 100644 qy-lty-admin/components/outfits/add-outfit-dialog.tsx create mode 100644 qy-lty-admin/components/outfits/add-print-batch-dialog.tsx create mode 100644 qy-lty-admin/components/outfits/export-cards-dialog.tsx create mode 100644 qy-lty-admin/components/overview.tsx create mode 100644 qy-lty-admin/components/permissions/role-dialog.tsx create mode 100644 qy-lty-admin/components/props/add-print-batch-dialog.tsx create mode 100644 qy-lty-admin/components/props/add-prop-dialog.tsx create mode 100644 qy-lty-admin/components/props/export-cards-dialog.tsx create mode 100644 qy-lty-admin/components/props/prop-detail-dialog.tsx create mode 100644 qy-lty-admin/components/publish-confirmation-dialog.tsx create mode 100644 qy-lty-admin/components/recent-activity.tsx create mode 100644 qy-lty-admin/components/sidebar.tsx create mode 100644 qy-lty-admin/components/songs/add-print-batch-dialog.tsx create mode 100644 qy-lty-admin/components/songs/add-song-dialog.tsx create mode 100644 qy-lty-admin/components/songs/export-batch-dialog.tsx create mode 100644 qy-lty-admin/components/songs/export-cards-dialog.tsx create mode 100644 qy-lty-admin/components/songs/song-detail-dialog.tsx create mode 100644 qy-lty-admin/components/stat-card.tsx create mode 100644 qy-lty-admin/components/theme-provider.tsx create mode 100644 qy-lty-admin/components/ui/accordion.tsx create mode 100644 qy-lty-admin/components/ui/alert-dialog.tsx create mode 100644 qy-lty-admin/components/ui/alert.tsx create mode 100644 qy-lty-admin/components/ui/aspect-ratio.tsx create mode 100644 qy-lty-admin/components/ui/avatar.tsx create mode 100644 qy-lty-admin/components/ui/badge.tsx create mode 100644 qy-lty-admin/components/ui/breadcrumb.tsx create mode 100644 qy-lty-admin/components/ui/button.tsx create mode 100644 qy-lty-admin/components/ui/calendar.tsx create mode 100644 qy-lty-admin/components/ui/card.tsx create mode 100644 qy-lty-admin/components/ui/carousel.tsx create mode 100644 qy-lty-admin/components/ui/chart.tsx create mode 100644 qy-lty-admin/components/ui/checkbox.tsx create mode 100644 qy-lty-admin/components/ui/collapsible.tsx create mode 100644 qy-lty-admin/components/ui/command.tsx create mode 100644 qy-lty-admin/components/ui/context-menu.tsx create mode 100644 qy-lty-admin/components/ui/dialog.tsx create mode 100644 qy-lty-admin/components/ui/drawer.tsx create mode 100644 qy-lty-admin/components/ui/dropdown-menu.tsx create mode 100644 qy-lty-admin/components/ui/file-upload.tsx create mode 100644 qy-lty-admin/components/ui/form.tsx create mode 100644 qy-lty-admin/components/ui/hover-card.tsx create mode 100644 qy-lty-admin/components/ui/input-otp.tsx create mode 100644 qy-lty-admin/components/ui/input.tsx create mode 100644 qy-lty-admin/components/ui/label.tsx create mode 100644 qy-lty-admin/components/ui/menubar.tsx create mode 100644 qy-lty-admin/components/ui/navigation-menu.tsx create mode 100644 qy-lty-admin/components/ui/pagination.tsx create mode 100644 qy-lty-admin/components/ui/popover.tsx create mode 100644 qy-lty-admin/components/ui/progress.tsx create mode 100644 qy-lty-admin/components/ui/radio-group.tsx create mode 100644 qy-lty-admin/components/ui/resizable.tsx create mode 100644 qy-lty-admin/components/ui/scroll-area.tsx create mode 100644 qy-lty-admin/components/ui/select.tsx create mode 100644 qy-lty-admin/components/ui/separator.tsx create mode 100644 qy-lty-admin/components/ui/sheet.tsx create mode 100644 qy-lty-admin/components/ui/sidebar.tsx create mode 100644 qy-lty-admin/components/ui/skeleton.tsx create mode 100644 qy-lty-admin/components/ui/slider.tsx create mode 100644 qy-lty-admin/components/ui/sonner.tsx create mode 100644 qy-lty-admin/components/ui/switch.tsx create mode 100644 qy-lty-admin/components/ui/table.tsx create mode 100644 qy-lty-admin/components/ui/tabs.tsx create mode 100644 qy-lty-admin/components/ui/textarea.tsx create mode 100644 qy-lty-admin/components/ui/toast.tsx create mode 100644 qy-lty-admin/components/ui/toaster.tsx create mode 100644 qy-lty-admin/components/ui/toggle-group.tsx create mode 100644 qy-lty-admin/components/ui/toggle.tsx create mode 100644 qy-lty-admin/components/ui/tooltip.tsx create mode 100644 qy-lty-admin/components/ui/upload-example.tsx create mode 100644 qy-lty-admin/components/ui/use-mobile.tsx create mode 100644 qy-lty-admin/components/ui/use-toast.ts create mode 100644 qy-lty-admin/components/users/user-detail-dialog.tsx create mode 100644 qy-lty-admin/components/users/user-form-dialog.tsx create mode 100644 qy-lty-admin/docker-compose.yml create mode 100644 qy-lty-admin/hooks/use-mobile.tsx create mode 100644 qy-lty-admin/hooks/use-toast.ts create mode 100644 qy-lty-admin/lib/api/achievements.ts create mode 100644 qy-lty-admin/lib/api/adapters.ts create mode 100644 qy-lty-admin/lib/api/affinity.ts create mode 100644 qy-lty-admin/lib/api/ai-models.ts create mode 100644 qy-lty-admin/lib/api/auth.ts create mode 100644 qy-lty-admin/lib/api/card.ts create mode 100644 qy-lty-admin/lib/api/client.ts create mode 100644 qy-lty-admin/lib/api/dances.ts create mode 100644 qy-lty-admin/lib/api/error-handler.ts create mode 100644 qy-lty-admin/lib/api/food.ts create mode 100644 qy-lty-admin/lib/api/home-decor.ts create mode 100644 qy-lty-admin/lib/api/index.ts create mode 100644 qy-lty-admin/lib/api/outfits.ts create mode 100644 qy-lty-admin/lib/api/props.ts create mode 100644 qy-lty-admin/lib/api/roles.ts create mode 100644 qy-lty-admin/lib/api/song.type.ts create mode 100644 qy-lty-admin/lib/api/songs.ts create mode 100644 qy-lty-admin/lib/api/token-debug.ts create mode 100644 qy-lty-admin/lib/api/types.ts create mode 100644 qy-lty-admin/lib/api/upload.ts create mode 100644 qy-lty-admin/lib/api/users.ts create mode 100644 qy-lty-admin/lib/utils.ts create mode 100644 qy-lty-admin/middleware.ts create mode 100644 qy-lty-admin/next.config.mjs create mode 100644 qy-lty-admin/package-lock.json create mode 100644 qy-lty-admin/package.json create mode 100644 qy-lty-admin/pnpm-lock.yaml create mode 100644 qy-lty-admin/postcss.config.mjs create mode 100644 qy-lty-admin/public/placeholder-logo.png create mode 100644 qy-lty-admin/public/placeholder-logo.svg create mode 100644 qy-lty-admin/public/placeholder-user.jpg create mode 100644 qy-lty-admin/public/placeholder.jpg create mode 100644 qy-lty-admin/public/placeholder.svg create mode 100644 qy-lty-admin/styles/globals.css create mode 100644 qy-lty-admin/tailwind.config.ts create mode 100644 qy-lty-admin/todo create mode 100644 qy-lty-admin/tsconfig.json create mode 100644 qy-lty-admin/yarn.lock create mode 100644 qy_lty/.env create mode 100644 qy_lty/CLAUDE.md create mode 100644 qy_lty/Dockerfile create mode 100644 qy_lty/achievement_app/__init__.py create mode 100644 qy_lty/achievement_app/admin.py create mode 100644 qy_lty/achievement_app/apps.py create mode 100644 qy_lty/achievement_app/migrations/0001_initial.py create mode 100644 qy_lty/achievement_app/migrations/0002_auto_20250508_1800.py create mode 100644 qy_lty/achievement_app/migrations/0003_alter_achievement_icon.py create mode 100644 qy_lty/achievement_app/migrations/0004_alter_achievement_rarity.py create mode 100644 qy_lty/achievement_app/migrations/0005_alter_achievement_rarity.py create mode 100644 qy_lty/achievement_app/migrations/__init__.py create mode 100644 qy_lty/achievement_app/models.py create mode 100644 qy_lty/achievement_app/serializers.py create mode 100644 qy_lty/achievement_app/tests.py create mode 100644 qy_lty/achievement_app/urls.py create mode 100644 qy_lty/achievement_app/views.py create mode 100644 qy_lty/aiapp/__init__.py create mode 100644 qy_lty/aiapp/admin.py create mode 100644 qy_lty/aiapp/apps.py create mode 100644 qy_lty/aiapp/audio/AliyunAudioService.py create mode 100644 qy_lty/aiapp/audio/AudioService.py create mode 100644 qy_lty/aiapp/audio/BaseAudioService.py create mode 100644 qy_lty/aiapp/audio/HuoshanAudioService.py create mode 100644 qy_lty/aiapp/audio/HuoshanAudioService_new.py create mode 100644 qy_lty/aiapp/audio/TencentAudioService.py create mode 100644 qy_lty/aiapp/audio/__init__.py create mode 100644 qy_lty/aiapp/audio/test.py create mode 100644 qy_lty/aiapp/kimi.py create mode 100644 qy_lty/aiapp/migrations/0001_initial.py create mode 100644 qy_lty/aiapp/migrations/0002_initial.py create mode 100644 qy_lty/aiapp/migrations/__init__.py create mode 100644 qy_lty/aiapp/models.py create mode 100644 qy_lty/aiapp/serializers.py create mode 100644 qy_lty/aiapp/tests.py create mode 100644 qy_lty/aiapp/urls.py create mode 100644 qy_lty/aiapp/views.py create mode 100644 qy_lty/ali_vi_app/__init__.py create mode 100644 qy_lty/ali_vi_app/admin.py create mode 100644 qy_lty/ali_vi_app/apps.py create mode 100644 qy_lty/ali_vi_app/migrations/__init__.py create mode 100644 qy_lty/ali_vi_app/models.py create mode 100644 qy_lty/ali_vi_app/tests.py create mode 100644 qy_lty/ali_vi_app/urls.py create mode 100644 qy_lty/ali_vi_app/vi.py create mode 100644 qy_lty/ali_vi_app/views.py create mode 100644 qy_lty/card/__init__.py create mode 100644 qy_lty/card/admin.py create mode 100644 qy_lty/card/apps.py create mode 100644 qy_lty/card/example_data.py create mode 100644 qy_lty/card/management/commands/create_example_cards.py create mode 100644 qy_lty/card/migrations/0001_initial.py create mode 100644 qy_lty/card/migrations/0002_initial.py create mode 100644 qy_lty/card/migrations/0003_remove_card_quantity_card_batch_cardbatch_published_and_more.py create mode 100644 qy_lty/card/migrations/0004_clothingattributes_danceattributes_and_more.py create mode 100644 qy_lty/card/migrations/0005_alter_card_category_alter_cardbatch_category_and_more.py create mode 100644 qy_lty/card/migrations/0006_cardbatch_end_id_cardbatch_start_id.py create mode 100644 qy_lty/card/migrations/0007_alter_songattributes_audio_file.py create mode 100644 qy_lty/card/migrations/0008_alter_songattributes_audio_file.py create mode 100644 qy_lty/card/migrations/0009_alter_songattributes_bpm.py create mode 100644 qy_lty/card/migrations/__init__.py create mode 100644 qy_lty/card/models.py create mode 100644 qy_lty/card/serializers.py create mode 100644 qy_lty/card/storage.py create mode 100644 qy_lty/card/tests.py create mode 100644 qy_lty/card/urls.py create mode 100644 qy_lty/card/views.py create mode 100644 qy_lty/claude.json create mode 100644 qy_lty/common/VolcEngineAccessToken.py create mode 100644 qy_lty/common/__init__.py create mode 100644 qy_lty/common/aliyun_logging.py create mode 100644 qy_lty/common/middleware.py create mode 100644 qy_lty/common/oss.py create mode 100644 qy_lty/common/pagination.py create mode 100644 qy_lty/common/responses.py create mode 100644 qy_lty/common/swagger_utils.py create mode 100644 qy_lty/common/views.py create mode 100644 qy_lty/dev_start.bat create mode 100644 qy_lty/device_interaction/__init__.py create mode 100644 qy_lty/device_interaction/admin.py create mode 100644 qy_lty/device_interaction/amap_api.py create mode 100644 qy_lty/device_interaction/apps.py create mode 100644 qy_lty/device_interaction/auth.py create mode 100644 qy_lty/device_interaction/consumers.py create mode 100644 qy_lty/device_interaction/management/__init__.py create mode 100644 qy_lty/device_interaction/management/commands/__init__.py create mode 100644 qy_lty/device_interaction/management/commands/clean_test_devices.py create mode 100644 qy_lty/device_interaction/management/commands/create_test_devices.py create mode 100644 qy_lty/device_interaction/middleware.py create mode 100644 qy_lty/device_interaction/migrations/0001_initial.py create mode 100644 qy_lty/device_interaction/migrations/0002_device_battery_level_device_brightness_and_more.py create mode 100644 qy_lty/device_interaction/migrations/__init__.py create mode 100644 qy_lty/device_interaction/models.py create mode 100644 qy_lty/device_interaction/routing.py create mode 100644 qy_lty/device_interaction/serializers.py create mode 100644 qy_lty/device_interaction/services.py create mode 100644 qy_lty/device_interaction/swagger.py create mode 100644 qy_lty/device_interaction/tests.py create mode 100644 qy_lty/device_interaction/urls.py create mode 100644 qy_lty/device_interaction/views.py create mode 100644 qy_lty/device_interaction/volcengine_api.py create mode 100644 qy_lty/device_interaction/weather.py create mode 100644 qy_lty/docker-compose.yml create mode 100644 qy_lty/docs/README.md create mode 100644 qy_lty/docs/api/achievement_api.md create mode 100644 qy_lty/docs/api/ai_api.md create mode 100644 qy_lty/docs/api/card_api.md create mode 100644 qy_lty/docs/development/architecture.md create mode 100644 qy_lty/docs/development/setup.md create mode 100644 qy_lty/docs/device_api.md create mode 100644 qy_lty/docs/device_commands.md create mode 100644 qy_lty/docs/device_errors.md create mode 100644 qy_lty/docs/device_module.md create mode 100644 qy_lty/docs/features/websocket.md create mode 100644 qy_lty/docs/integrations/audio_services.md create mode 100644 qy_lty/docs/user_authentication_api.md create mode 100644 qy_lty/environment.yml create mode 100644 qy_lty/food_app/__init__.py create mode 100644 qy_lty/food_app/admin.py create mode 100644 qy_lty/food_app/apps.py create mode 100644 qy_lty/food_app/migrations/0001_initial.py create mode 100644 qy_lty/food_app/migrations/0002_alter_food_animation_file_alter_food_image_and_more.py create mode 100644 qy_lty/food_app/migrations/__init__.py create mode 100644 qy_lty/food_app/models.py create mode 100644 qy_lty/food_app/serializers.py create mode 100644 qy_lty/food_app/tests.py create mode 100644 qy_lty/food_app/urls.py create mode 100644 qy_lty/food_app/views.py create mode 100644 qy_lty/function_call_test.py create mode 100644 qy_lty/locale/en/LC_MESSAGES/django.po create mode 100644 qy_lty/locale/zh_HAns/LC_MESSAGES/django.po create mode 100644 qy_lty/manage.py create mode 100644 qy_lty/qy_lty/__init__.py create mode 100644 qy_lty/qy_lty/asgi.py create mode 100644 qy_lty/qy_lty/settings.py create mode 100644 qy_lty/qy_lty/throttling.py create mode 100644 qy_lty/qy_lty/urls.py create mode 100644 qy_lty/qy_lty/wsgi.py create mode 100644 qy_lty/readme.md create mode 100644 qy_lty/requirements.txt create mode 100644 qy_lty/run.sh create mode 100644 qy_lty/start.bat create mode 100644 qy_lty/subscription_app/__init__.py create mode 100644 qy_lty/subscription_app/admin.py create mode 100644 qy_lty/subscription_app/apps.py create mode 100644 qy_lty/subscription_app/migrations/0001_initial.py create mode 100644 qy_lty/subscription_app/migrations/0002_initial.py create mode 100644 qy_lty/subscription_app/migrations/__init__.py create mode 100644 qy_lty/subscription_app/models.py create mode 100644 qy_lty/subscription_app/scheduler.py create mode 100644 qy_lty/subscription_app/tasks.py create mode 100644 qy_lty/subscription_app/templates/admin/base.html create mode 100644 qy_lty/subscription_app/templates/admin/base_site.html create mode 100644 qy_lty/subscription_app/tests.py create mode 100644 qy_lty/subscription_app/views.py create mode 100644 qy_lty/templates/admin/base copy.html create mode 100644 qy_lty/templates/admin/index.html create mode 100644 qy_lty/templates/simpleui/admin/base.html create mode 100644 qy_lty/templates/simpleui/admin/base_site.html create mode 100644 qy_lty/templates/simpleui/admin/index.html create mode 100644 qy_lty/test.mp3 create mode 100644 qy_lty/userapp/README.md create mode 100644 qy_lty/userapp/__init__.py create mode 100644 qy_lty/userapp/admin.py create mode 100644 qy_lty/userapp/admin_urls.py create mode 100644 qy_lty/userapp/apps.py create mode 100644 qy_lty/userapp/auth_urls.py create mode 100644 qy_lty/userapp/authentication.py create mode 100644 qy_lty/userapp/migrations/0001_initial.py create mode 100644 qy_lty/userapp/migrations/0002_paradiseuser_userapp_par_usernam_583392_idx.py create mode 100644 qy_lty/userapp/migrations/0003_paradiseuser_birthday_paradiseuser_favorability_and_more.py create mode 100644 qy_lty/userapp/migrations/__init__.py create mode 100644 qy_lty/userapp/models.py create mode 100644 qy_lty/userapp/serializers.py create mode 100644 qy_lty/userapp/tests.py create mode 100644 qy_lty/userapp/urls.py create mode 100644 qy_lty/userapp/utils.py create mode 100644 qy_lty/userapp/views.py create mode 100644 qy_lty/userapp/views_old.py create mode 100644 qy_lty/weather_test.py create mode 100644 qy_lty/workflow_app/__init__.py create mode 100644 qy_lty/workflow_app/admin.py create mode 100644 qy_lty/workflow_app/apps.py create mode 100644 qy_lty/workflow_app/migrations/__init__.py create mode 100644 qy_lty/workflow_app/models.py create mode 100644 qy_lty/workflow_app/tests.py create mode 100644 qy_lty/workflow_app/views.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a88c62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# === General === +.DS_Store +*.swp +*.swo +*~ + +# === qy-lty-admin (Next.js) === +# dependencies +qy-lty-admin/node_modules/ +# next.js +qy-lty-admin/.next/ +qy-lty-admin/out/ +# production +qy-lty-admin/build/ +# debug logs +qy-lty-admin/npm-debug.log* +qy-lty-admin/yarn-debug.log* +qy-lty-admin/yarn-error.log* +qy-lty-admin/.pnpm-debug.log* +# env files +# qy-lty-admin/.env +# qy-lty-admin/.env.local +# qy-lty-admin/.env.development +# qy-lty-admin/.env.production +# qy-lty-admin/.env.development.local +# qy-lty-admin/.env.test.local +# qy-lty-admin/.env.production.local +# vercel +qy-lty-admin/.vercel/ +# typescript +qy-lty-admin/*.tsbuildinfo +qy-lty-admin/next-env.d.ts + +# === qy_lty (Django / Python) === +# python bytecode +__pycache__/ +*.py[cod] +*.pyo +# env files +# qy_lty/.env +# logs +qy_lty/logs/ +# static files collected +qy_lty/staticfiles/ +# database +*.sqlite3 +# virtual env +.venv/ +venv/ +env/ +# conda +environment.lock.yml diff --git a/qy-lty-admin/.dockerignore b/qy-lty-admin/.dockerignore new file mode 100644 index 0000000..aed0438 --- /dev/null +++ b/qy-lty-admin/.dockerignore @@ -0,0 +1,34 @@ +# Git +.git +.gitignore + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# Node.js +node_modules +npm-debug.log +yarn-debug.log +yarn-error.log + +# Next.js +# 不再排除.next目录,因为需要将其复制到Docker镜像中 +# .next +out + +# Environment variables +.env +.env.local +.env.development +.env.test +# 不排除生产环境变量,因为在docker-compose中使用 +# .env.production + +# Misc +README.md +.vscode +.idea +*.log +*.md \ No newline at end of file diff --git a/qy-lty-admin/.env.example b/qy-lty-admin/.env.example new file mode 100644 index 0000000..9bd9e89 --- /dev/null +++ b/qy-lty-admin/.env.example @@ -0,0 +1,7 @@ +# General environment variables +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api + +# For different environments, use these files: +# .env.development - Development environment variables (next dev) +# .env.production - Production environment variables (next start) +# .env.local - Local overrides (loaded for all environments) \ No newline at end of file diff --git a/qy-lty-admin/.env.local.example b/qy-lty-admin/.env.local.example new file mode 100644 index 0000000..87793d1 --- /dev/null +++ b/qy-lty-admin/.env.local.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api +# Add other local-specific variables below \ No newline at end of file diff --git a/qy-lty-admin/.gitignore b/qy-lty-admin/.gitignore new file mode 100644 index 0000000..8941b99 --- /dev/null +++ b/qy-lty-admin/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env +.env.local +.env.development +.env.production +.env.development.local +.env.test.local +.env.production.local +# but keep the examples +!.env.example +!.env.local.example + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/qy-lty-admin/Dockerfile b/qy-lty-admin/Dockerfile new file mode 100644 index 0000000..2931ce5 --- /dev/null +++ b/qy-lty-admin/Dockerfile @@ -0,0 +1,52 @@ +# 构建阶段 +FROM node:22.10.0-alpine AS builder + +# 设置工作目录 +WORKDIR /app + +# 设置yarn镜像源为淘宝镜像 +RUN yarn config set registry https://registry.npmmirror.com && \ + yarn config set disturl https://npmmirror.com/dist --global + +# 复制package.json和yarn.lock +COPY package.json yarn.lock* ./ + +# 安装依赖(包括devDependencies用于构建) +RUN yarn install --frozen-lockfile + +# 复制源代码 +COPY . . + +# 构建应用 +RUN yarn build + +# 运行阶段 +FROM node:22.10.0-alpine AS runner + +# 设置工作目录 +WORKDIR /app + +# 设置yarn镜像源为淘宝镜像 +RUN yarn config set registry https://registry.npmmirror.com && \ + yarn config set disturl https://npmmirror.com/dist --global + +# 复制package.json和yarn.lock +COPY package.json yarn.lock* ./ + +# 仅安装生产依赖 +RUN yarn install --production --frozen-lockfile + +# 设置环境变量 +ENV NODE_ENV=production +ENV PATH=/app/node_modules/.bin:$PATH + +# 从构建阶段复制构建产物 +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +COPY --from=builder /app/next.config.mjs ./ + +# 暴露端口 +EXPOSE 3000 + +# 启动应用 +CMD ["yarn", "start"] \ No newline at end of file diff --git a/qy-lty-admin/README.md b/qy-lty-admin/README.md new file mode 100644 index 0000000..b11d433 --- /dev/null +++ b/qy-lty-admin/README.md @@ -0,0 +1,137 @@ +# 洛天依应用管理后台 + +这是一个基于 Next.js 和 React 构建的洛天依应用管理后台系统,提供完整的管理功能,包括用户管理、AI模型管理、卡牌管理、内容管理等功能。 + +## 功能特点 + +- **用户管理**:查看用户数据、活跃用户统计 +- **AI模型管理**:大模型框架系统、模型微调、语音克隆与合成、知识库管理 +- **卡牌管理**:服装卡牌、道具卡牌、家居装饰卡牌、食物卡牌 +- **内容管理**:歌曲管理、舞蹈管理、好感度系统、成就系统 +- **数据分析**:用户活跃度统计、系统运行概览 +- **安全认证**:用户登录/注册系统 + +## 技术栈 + +- [Next.js](https://nextjs.org/) - React 框架 +- [React](https://reactjs.org/) - UI 库 +- [Tailwind CSS](https://tailwindcss.com/) - 样式框架 +- [Radix UI](https://www.radix-ui.com/) - 无障碍组件库 +- [Lucide React](https://lucide.dev/) - 图标库 +- [Recharts](https://recharts.org/) - 图表库 + +## 安装与运行 + +### 前提条件 + +- Node.js 22.x 或更高版本 +- npm 或 yarn 或 pnpm + +### 安装步骤 + +1. 克隆仓库 +```bash +git clone +cd admin-dashboard +``` + +2. 安装依赖 +```bash +npm install +# 或 +yarn +# 或 +pnpm install +``` + +3. 启动开发服务器 +```bash +npm run dev +# 或 +yarn dev +# 或 +pnpm dev +``` + +4. 打开浏览器访问 http://localhost:3000 + +## 构建与部署 + +```bash +# 构建项目 +npm run build +# 或 +yarn build +# 或 +pnpm build + +# 启动生产环境服务器 +npm run start +# 或 +yarn start +# 或 +pnpm start +``` + +## 项目结构 + +``` +admin-dashboard/ +├── app/ # Next.js 应用程序目录 +│ ├── ai-model/ # AI模型管理相关页面 +│ ├── achievements/ # 成就系统页面 +│ ├── affinity/ # 好感度系统页面 +│ ├── dances/ # 舞蹈管理页面 +│ ├── food/ # 食物卡牌管理页面 +│ ├── home-decor/ # 家居装饰卡牌管理页面 +│ ├── login/ # 登录页面 +│ ├── outfits/ # 服装卡牌管理页面 +│ ├── permissions/ # 权限管理页面 +│ ├── props/ # 道具卡牌管理页面 +│ ├── register/ # 注册页面 +│ ├── settings/ # 系统设置页面 +│ ├── songs/ # 歌曲管理页面 +│ └── users/ # 用户管理页面 +├── components/ # React 组件 +├── hooks/ # 自定义 React hooks +├── lib/ # 工具函数和辅助库 +├── public/ # 静态资源 +└── styles/ # 全局样式 +``` + +## 浏览器支持 + +- Chrome (最新版本) +- Firefox (最新版本) +- Safari (最新版本) +- Edge (最新版本) + +## 许可证 + +[MIT](LICENSE) + +## Environment Configuration + +This project uses environment variables for configuration across different environments. The configuration files are: + +- `.env` - Base environment variables (lowest priority) +- `.env.development` - Development environment variables (used with `next dev`) +- `.env.production` - Production environment variables (used with `next start`) +- `.env.local` - Local overrides for any environment (highest priority) + +### Setup + +1. Copy the example files to create your environment configuration: + +```bash +cp .env.example .env +cp .env.local.example .env.local +``` + +2. Update the variables in these files as needed for your environment. + +3. Environment variables that need to be exposed to the browser should be prefixed with `NEXT_PUBLIC_`. + +### Environment Variables + +- `NEXT_PUBLIC_API_BASE_URL`: The base URL for API requests \ No newline at end of file diff --git a/qy-lty-admin/app/achievements/loading.tsx b/qy-lty-admin/app/achievements/loading.tsx new file mode 100644 index 0000000..f6adb13 --- /dev/null +++ b/qy-lty-admin/app/achievements/loading.tsx @@ -0,0 +1,48 @@ +import { DashboardHeader } from "@/components/dashboard-header" +import { DashboardShell } from "@/components/dashboard-shell" +import { Button } from "@/components/ui/button" +import { Card } from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" +import { Trophy } from "lucide-react" + +export default function AchievementsLoading() { + return ( + + + + +
+ +
+ + + +
+
+ {Array(6) + .fill(null) + .map((_, i) => ( + +
+ +
+ + +
+
+ +
+ + +
+
+ ))} +
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/achievements/page.tsx b/qy-lty-admin/app/achievements/page.tsx new file mode 100644 index 0000000..082b8f5 --- /dev/null +++ b/qy-lty-admin/app/achievements/page.tsx @@ -0,0 +1,469 @@ +"use client" + +import { useState, useEffect } from "react" +import { DashboardHeader } from "@/components/dashboard-header" +import { DashboardShell } from "@/components/dashboard-shell" +import { Button } from "@/components/ui/button" +import { Card, CardContent } from "@/components/ui/card" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Input } from "@/components/ui/input" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Badge } from "@/components/ui/badge" +import { Progress } from "@/components/ui/progress" +import { + Award, + Search, + MoreHorizontal, + Edit, + Trash2, + Trophy, + Filter, + ArrowUpDown, + Zap, + Heart, + Coins, + Gift, + Shirt, + BadgeCheck, +} from "lucide-react" +import { toast } from "@/components/ui/use-toast" +import { AddAchievementDialog } from "@/components/achievements/add-achievement-dialog" +import { AchievementDetailDialog } from "@/components/achievements/achievement-detail-dialog" +import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog" +import type { Achievement } from "@/lib/api/types" + +export default function AchievementsPage() { + const [achievements, setAchievements] = useState([]) + const [loading, setLoading] = useState(true) + const [searchQuery, setSearchQuery] = useState("") + const [activeTab, setActiveTab] = useState("all") + + useEffect(() => { + fetchAchievements() + }, []) + + const fetchAchievements = async () => { + setLoading(true) + try { + // 模拟API调用 + await new Promise((resolve) => setTimeout(resolve, 1000)) + + // 这里应该是实际的API调用 + // const response = await fetch('/api/achievements') + // const data = await response.json() + + // 使用模拟数据 + const mockData = [ + { + id: "1", + name: "初次见面", + description: "第一次与洛天依对话", + icon: "message-circle", + category: "互动", + requirement: "与洛天依进行第一次对话", + rewardType: "经验值", + rewardAmount: 100, + rewardIcon: "zap", + isHidden: false, + unlockRate: 98.5, + createdAt: "2023-01-01", + updatedAt: "2023-01-01", + }, + { + id: "2", + name: "亲密无间", + description: "与洛天依好感度达到80", + icon: "heart", + category: "好感度", + requirement: "将洛天依的好感度提升至80", + rewardType: "虚拟币", + rewardAmount: 500, + rewardIcon: "coins", + isHidden: false, + unlockRate: 45.2, + createdAt: "2023-01-02", + updatedAt: "2023-01-02", + }, + { + id: "3", + name: "形影不离", + description: "与洛天依好感度达到100", + icon: "heart-handshake", + category: "好感度", + requirement: "将洛天依的好感度提升至100", + rewardType: "称号", + rewardAmount: 1, + rewardIcon: "badge", + isHidden: false, + unlockRate: 12.8, + createdAt: "2023-01-03", + updatedAt: "2023-01-03", + }, + { + id: "4", + name: "热情如火", + description: "与洛天依单日互动达到50次", + icon: "flame", + category: "互动", + requirement: "在一天内与洛天依互动50次", + rewardType: "好感度", + rewardAmount: 20, + rewardIcon: "heart", + isHidden: false, + unlockRate: 23.7, + createdAt: "2023-01-04", + updatedAt: "2023-01-04", + }, + { + id: "5", + name: "坚持不懈", + description: "连续7天与洛天依互动", + icon: "calendar-check", + category: "互动", + requirement: "连续7天每天至少与洛天依互动1次", + rewardType: "道具", + rewardAmount: 1, + rewardIcon: "gift", + isHidden: false, + unlockRate: 34.1, + createdAt: "2023-01-05", + updatedAt: "2023-01-05", + }, + { + id: "6", + name: "时尚达人", + description: "收集10套服装", + icon: "shirt", + category: "收集", + requirement: "收集10套不同的服装", + rewardType: "服装", + rewardAmount: 1, + rewardIcon: "shirt", + isHidden: false, + unlockRate: 56.3, + createdAt: "2023-01-06", + updatedAt: "2023-01-06", + }, + { + id: "7", + name: "音乐鉴赏家", + description: "收听20首不同的歌曲", + icon: "music", + category: "收集", + requirement: "收听20首不同的歌曲", + rewardType: "虚拟币", + rewardAmount: 300, + rewardIcon: "coins", + isHidden: false, + unlockRate: 42.9, + createdAt: "2023-01-07", + updatedAt: "2023-01-07", + }, + { + id: "8", + name: "舞动青春", + description: "观看10支不同的舞蹈", + icon: "footprints", + category: "收集", + requirement: "观看10支不同的舞蹈表演", + rewardType: "经验值", + rewardAmount: 200, + rewardIcon: "zap", + isHidden: false, + unlockRate: 38.5, + createdAt: "2023-01-08", + updatedAt: "2023-01-08", + }, + { + id: "9", + name: "美食家", + description: "收集15种不同的食物", + icon: "utensils", + category: "收集", + requirement: "收集15种不同的食物", + rewardType: "道具", + rewardAmount: 1, + rewardIcon: "gift", + isHidden: false, + unlockRate: 27.4, + createdAt: "2023-01-09", + updatedAt: "2023-01-09", + }, + { + id: "10", + name: "隐藏的秘密", + description: "发现洛天依的秘密", + icon: "sparkles", + category: "隐藏", + requirement: "???", + rewardType: "称号", + rewardAmount: 1, + rewardIcon: "badge", + isHidden: true, + unlockRate: 5.2, + createdAt: "2023-01-10", + updatedAt: "2023-01-10", + }, + ] + + setAchievements(mockData) + } catch (error) { + console.error("获取成就列表失败:", error) + toast({ + title: "获取失败", + description: "获取成就列表时发生错误", + variant: "destructive", + }) + } finally { + setLoading(false) + } + } + + const handleDeleteAchievement = async (id: string) => { + try { + // 模拟API调用 + await new Promise((resolve) => setTimeout(resolve, 1000)) + + // 更新本地状态 + setAchievements((prev) => prev.filter((achievement) => achievement.id !== id)) + + toast({ + title: "删除成功", + description: "成就已成功删除", + }) + } catch (error) { + toast({ + title: "删除失败", + description: "删除成就时发生错误", + variant: "destructive", + }) + } + } + + const getCategoryColor = (category: string) => { + switch (category) { + case "互动": + return "bg-blue-500" + case "好感度": + return "bg-pink-500" + case "收集": + return "bg-purple-500" + case "探索": + return "bg-green-500" + case "特殊": + return "bg-amber-500" + case "隐藏": + return "bg-gray-500" + default: + return "bg-gray-500" + } + } + + const getRewardIcon = (rewardType: string) => { + switch (rewardType) { + case "经验值": + return + case "好感度": + return + case "虚拟币": + return + case "道具": + return + case "服装": + return + case "称号": + return + default: + return + } + } + + // 过滤成就 + const filteredAchievements = achievements.filter((achievement) => { + // 搜索过滤 + const matchesSearch = + searchQuery === "" || + achievement.name.toLowerCase().includes(searchQuery.toLowerCase()) || + achievement.description.toLowerCase().includes(searchQuery.toLowerCase()) + + // 标签过滤 + const matchesTab = + activeTab === "all" || (activeTab === "hidden" && achievement.isHidden) || achievement.category === activeTab + + return matchesSearch && matchesTab + }) + + return ( + + + + + + + +
+
+ + setSearchQuery(e.target.value)} + /> +
+
+ + + + + + setActiveTab("all")}>全部成就 + setActiveTab("互动")}>互动成就 + setActiveTab("好感度")}>好感度成就 + setActiveTab("收集")}>收集成就 + setActiveTab("探索")}>探索成就 + setActiveTab("特殊")}>特殊成就 + setActiveTab("hidden")}>隐藏成就 + + + + + + + + + 按名称排序 + 按解锁率排序 + 按创建日期排序 + + +
+
+ + + + 全部 + 互动 + 好感度 + 收集 + 探索 + 特殊 + 隐藏 + + + +
+ {loading ? ( + // 加载状态 + Array(6) + .fill(null) + .map((_, i) => ( + +
+
+
+
+
+
+
+
+
+
+
+
+
+ )) + ) : filteredAchievements.length > 0 ? ( + filteredAchievements.map((achievement) => ( + + +
+
+
+ +
+
+

{achievement.name}

+
+ {achievement.category} + {achievement.isHidden && 隐藏} +
+
+
+ + + + + + + + 编辑 + + + handleDeleteAchievement(achievement.id)} + trigger={ +
+ + 删除 +
+ } + /> +
+
+
+
+ +

{achievement.description}

+ +
+
+ 解锁率 + {achievement.unlockRate}% +
+ +
+ +
+
+ 奖励: +
+ {getRewardIcon(achievement.rewardType)} + + {achievement.rewardAmount} {achievement.rewardType} + +
+
+ {}} /> +
+
+
+ )) + ) : ( +
+ +

未找到成就

+

没有符合当前筛选条件的成就

+
+ )} +
+
+
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/affinity/page.tsx b/qy-lty-admin/app/affinity/page.tsx new file mode 100644 index 0000000..989178e --- /dev/null +++ b/qy-lty-admin/app/affinity/page.tsx @@ -0,0 +1,1005 @@ +"use client" + +import { useState } from "react" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Slider } from "@/components/ui/slider" +import { Switch } from "@/components/ui/switch" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Badge } from "@/components/ui/badge" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Heart, Save, RefreshCw, Clock, Gift, BarChart3, Edit } from "lucide-react" +import { AffinityRuleDialog, type AffinityRule } from "@/components/affinity/affinity-rule-dialog" +import { AffinityLevelDialog, type AffinityLevel } from "@/components/affinity/affinity-level-dialog" + +// 初始互动规则数据 +const initialRules: AffinityRule[] = [ + { + id: "rule-1", + name: "使用卡片", + type: "card", + description: "用户使用洛天依卡片", + minChange: 1, + maxChange: 3, + singleCap: 3, + dailyCap: 10, + isNegative: false, + isEnabled: true, + }, + { + id: "rule-2", + name: "对话", + type: "chat", + description: "与洛天依进行对话", + minChange: 1, + maxChange: 5, + singleCap: 5, + dailyCap: 15, + isNegative: false, + isEnabled: true, + }, + { + id: "rule-3", + name: "喂食", + type: "feed", + description: "给洛天依喂食", + minChange: 2, + maxChange: 8, + singleCap: 8, + dailyCap: 16, + isNegative: false, + isEnabled: true, + }, + { + id: "rule-4", + name: "抚摸", + type: "touch", + description: "抚摸洛天依", + minChange: 1, + maxChange: 3, + singleCap: 3, + dailyCap: 9, + isNegative: false, + isEnabled: true, + }, + { + id: "rule-5", + name: "换装", + type: "dress", + description: "为洛天依更换服装", + minChange: 2, + maxChange: 6, + singleCap: 6, + dailyCap: 12, + isNegative: false, + isEnabled: true, + }, + { + id: "rule-6", + name: "使用道具", + type: "prop", + description: "使用互动道具", + minChange: 1, + maxChange: 4, + singleCap: 4, + dailyCap: 12, + isNegative: false, + isEnabled: true, + }, + { + id: "rule-7", + name: "送礼物", + type: "gift", + description: "赠送礼物给洛天依", + minChange: 5, + maxChange: 15, + singleCap: 15, + dailyCap: 20, + isNegative: false, + isEnabled: true, + }, + { + id: "rule-8", + name: "无互动衰减", + type: "decay", + description: "长时间不互动导致好感度下降", + minChange: -3, + maxChange: -1, + singleCap: 3, + dailyCap: 5, + isNegative: true, + isEnabled: true, + }, +] + +// 初始好感度等级数据 +const initialLevels: AffinityLevel[] = [ + { + id: "level-1", + level: 1, + name: "初识", + minAffinity: 0, + maxAffinity: 20, + unlockContent: "基础对话功能", + rewardType: "unlock", + isEnabled: true, + }, + { + id: "level-2", + level: 2, + name: "相识", + minAffinity: 21, + maxAffinity: 40, + unlockContent: "基础服装、道具使用", + rewardType: "unlock", + isEnabled: true, + }, + { + id: "level-3", + level: 3, + name: "熟悉", + minAffinity: 41, + maxAffinity: 60, + unlockContent: "更多服装、特殊对话", + rewardType: "unlock", + isEnabled: true, + }, + { + id: "level-4", + level: 4, + name: "亲密", + minAffinity: 61, + maxAffinity: 80, + unlockContent: "限定服装、特殊互动", + rewardType: "unlock", + isEnabled: true, + }, + { + id: "level-5", + level: 5, + name: "挚友", + minAffinity: 81, + maxAffinity: 100, + unlockContent: "专属内容、特殊剧情", + rewardType: "unlock", + isEnabled: true, + }, +] + +export default function AffinityPage() { + // 状态管理 + const [rules, setRules] = useState(initialRules) + const [levels, setLevels] = useState(initialLevels) + + // 处理规则保存 + const handleSaveRule = (updatedRule: AffinityRule) => { + setRules((prevRules) => { + const index = prevRules.findIndex((rule) => rule.id === updatedRule.id) + if (index >= 0) { + // 更新现有规则 + const newRules = [...prevRules] + newRules[index] = updatedRule + return newRules + } else { + // 添加新规则 + return [...prevRules, updatedRule] + } + }) + } + + // 处理等级保存 + const handleSaveLevel = (updatedLevel: AffinityLevel) => { + setLevels((prevLevels) => { + const index = prevLevels.findIndex((level) => level.id === updatedLevel.id) + if (index >= 0) { + // 更新现有等级 + const newLevels = [...prevLevels] + newLevels[index] = updatedLevel + return newLevels + } else { + // 添加新等级 + return [...prevLevels.sort((a, b) => a.level - b.level), updatedLevel].sort((a, b) => a.level - b.level) + } + }) + } + + // 处理设置保存 + const handleSaveSettings = () => { + alert("设置已保存!") + } + + return ( + + + + + + + + + 系统概览 + + + 互动规则 + + + 等级奖励 + + + 数据统计 + + + + {/* 系统概览标签页 */} + +
+ +
+ + 平均好感度 +
+ +
+
+ +
78.5
+

+ + 较上月增长 5.2% +

+
+
+ + +
+ + 最高好感度 +
+ +
+
+ +
100
+

+ + 共有 156 名用户达到 +

+
+
+ + +
+ + 互动次数/日 +
+ +
+
+ +
12,543
+

+ + 较上周增长 8.7% +

+
+
+ + +
+ + 活跃用户比例 +
+ +
+
+ +
86.4%
+

+ + 较上月增长 3.2% +

+
+
+
+ + + + + + 好感度系统设置 + +
+
+ 配置好感度系统的基本参数 +
+ +
+
+ +
+ + / 100 +
+

新用户初始好感度值

+
+
+ +
+ + +
+

好感度上限值

+
+
+ +
+
+ +
+ + 点 / 天 +
+

每日好感度增长上限

+
+
+ +
+ + 点 / 天 +
+

无互动时好感度衰减速率

+
+
+ +
+ +
+ +
+
+ 1天 + 3天 + 7天 + 14天 +
+

用户多少天不互动开始衰减好感度

+
+ +
+ + +
+ +
+ + +
+
+ + + + +
+
+ + {/* 互动规则标签页 */} + + + +
+ + + 互动规则列表 + +
+
+ 管理各种互动行为的好感度变化规则 +
+ +
+ + + + + 互动类型 + 描述 + 好感度变化 + 单次上限 + 每日上限 + 状态 + 操作 + + + + {rules.map((rule) => ( + + {rule.name} + {rule.description} + + {rule.isNegative ? "" : "+"} + {rule.minChange}~{rule.maxChange} + + {rule.singleCap} + {rule.dailyCap} + + + {rule.isEnabled ? "已启用" : "已禁用"} + + + + + + + } + /> + + + ))} + +
+
+
+ + + + + + 衰减规则设置 + +
+
+ 配置好感度衰减规则 +
+ +
+
+ + +
+
+ +
+ + 点 / 天 +
+
+
+ +
+
+ +
+ + 点 / 天 +
+
+
+ +
+ + 点 / 天 +
+
+
+ +
+ +
+ + 点 / 天 +
+
+ +
+ +
+ + +
+

好感度不会低于此值

+
+ +
+ + +
+
+ + + + +
+
+ + {/* 等级奖励标签页 */} + + + +
+ + + 好感度等级设置 + +
+
+ 管理好感度等级和对应奖励 +
+ +
+ + + + + 等级 + 等级名称 + 所需好感度 + 解锁内容 + 状态 + 操作 + + + + {levels.map((level) => ( + + {level.level} + {level.name} + + {level.minAffinity}-{level.maxAffinity} + + {level.unlockContent} + + + {level.isEnabled ? "已启用" : "已禁用"} + + + + + + + } + /> + + + ))} + +
+
+
+ + + + + + 奖励设置 + +
+
+ 配置好感度等级奖励机制 +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + + + +
+
+ + {/* 数据统计标签页 */} + + + + + + 好感度数据统计 + +
+
+ 查看好感度系统的数据统计 +
+ +
+ + + 平均好感度 + + +
78.5
+

↑ 5.2%

+
+
+ + + 好感度分布 + + +
正态分布
+

集中在60-80区间

+
+
+ + + 最常见互动 + + +
对话
+

占总互动的42%

+
+
+ + + 活跃用户比例 + + +
86.4%
+

↑ 3.2%

+
+
+
+ +
+

好感度等级分布

+
+
+
+ 等级5 (81-100) + 15% +
+
+
+
+
+
+
+ 等级4 (61-80) + 32% +
+
+
+
+
+
+
+ 等级3 (41-60) + 28% +
+
+
+
+
+
+
+ 等级2 (21-40) + 18% +
+
+
+
+
+
+
+ 等级1 (0-20) + 7% +
+
+
+
+
+
+
+
+
+ + + + + + 互动数据分析 + +
+
+ 分析用户互动行为数据 +
+ +
+

互动类型分布

+
+
+
+ 对话 + 42% +
+
+
+
+
+
+
+ 换装 + 18% +
+
+
+
+
+
+
+ 使用道具 + 15% +
+
+
+
+
+
+
+ 喂食 + 12% +
+
+
+
+
+
+
+ 抚摸 + 8% +
+
+
+
+
+
+
+ 其他 + 5% +
+
+
+
+
+
+
+ +
+
+ +
+

24.7次

+

↑ 8.3%

+
+
+
+ +
+

38分钟

+

↑ 12.5%

+
+
+
+ +
+
+ +
+

20:00-22:00

+

占总互动的32%

+
+
+
+ +
+

对话+换装

+

组合互动最多

+
+
+
+
+
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/ai-model/page.tsx b/qy-lty-admin/app/ai-model/page.tsx new file mode 100644 index 0000000..4a34cbb --- /dev/null +++ b/qy-lty-admin/app/ai-model/page.tsx @@ -0,0 +1,445 @@ +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Brain, Mic, Database, Plus, Sparkles, Edit, Play, Sliders, User } from "lucide-react" + +export default function AIModelPage() { + return ( + + + + + + + + + 大模型框架 + + + 模型微调 + + + 语音合成 + + + 知识库 + + + + + + + + + 大模型框架系统 + +
+
+ 管理洛天依使用的大语言模型框架 +
+ +
+ +
+ + 当前使用模型 +
+ +
+
+ +
+ GPT-4o +
+

版本: 2.0 | 上次更新: 2024-03-15

+
+ + + +
+ + +
+ + 备用模型 +
+ +
+
+ +
Claude 3
+

版本: 1.5 | 上次更新: 2024-02-20

+
+ + + +
+ + +
+ + 备用模型 +
+ +
+
+ +
Llama 3
+

版本: 1.0 | 上次更新: 2024-01-10

+
+ + + +
+
+
+ + + +
+
+ + + + + + + 大模型微调 + +
+
+ 管理洛天依的人格和行为特征 +
+ +
+ + + + + 人格设定 + + + +
+
+
+

性格特点

+

活泼、开朗、善良、有音乐天赋

+
+
+

语言风格

+

亲切、自然、略带俏皮

+
+
+
+
+

兴趣爱好

+

唱歌、跳舞、与用户交流

+
+
+

背景设定

+

虚拟歌手、16岁、身高158cm

+
+
+
+
+ + + +
+ + + + + + 微调数据集 + + + +
+
+

基础对话数据集

+

10,000 条对话

+
+
+

歌曲相关对话

+

2,500 条对话

+
+
+

舞蹈相关对话

+

1,800 条对话

+
+
+

服装相关对话

+

3,200 条对话

+
+
+
+ + + + +
+
+
+
+
+ + + + + + + 语音克隆与合成 + +
+
+ 管理洛天依的语音模型 +
+ +
+ + + + + 语音模型 + + + +
+
+

当前模型

+

洛天依官方语音 v3.0

+
+
+

采样率

+

48kHz

+
+
+

音质

+

高清 (24bit)

+
+
+

上次更新

+

2024-03-01

+
+
+
+ + + +
+ + + + + + 语音参数 + + + +
+
+

音调

+

标准 (0)

+
+
+

语速

+

中等 (1.0x)

+
+
+

情感强度

+

中等 (0.7)

+
+
+

音量

+

标准 (1.0)

+
+
+
+ + + +
+
+
+ + + + +
+
+ + + + + + + 知识库管理 + +
+
+ 管理用户信息和洛天依的知识库 +
+ +
+ + + + + 用户模型 + + + +
+
+

活跃用户模型

+

5,732 个

+
+
+

平均模型大小

+

2.4 MB

+
+
+

上次更新

+

今天 08:45

+
+
+
+ + + +
+ + + + + + 基础知识库 + + + +
+
+

洛天依相关知识

+

1,200 条

+
+
+

歌曲知识

+

850 条

+
+
+

舞蹈知识

+

320 条

+
+
+

通用知识

+

5,000+ 条

+
+
+
+ + + +
+
+
+ + + + +
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/dances/[id]/loading.tsx b/qy-lty-admin/app/dances/[id]/loading.tsx new file mode 100644 index 0000000..57b63dc --- /dev/null +++ b/qy-lty-admin/app/dances/[id]/loading.tsx @@ -0,0 +1,122 @@ +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Skeleton } from "@/components/ui/skeleton" +import { ArrowLeft } from "lucide-react" + +export default function DanceDetailLoading() { + return ( + + + + + +
+ + + + + + + + + + + +
+ + + +
+
+
+ + + + + + + + + + + +
+ + + + +
+ + + +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ + + + + 详情 + + + 动作管理 + + + 相关舞蹈 + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/dances/[id]/page.tsx b/qy-lty-admin/app/dances/[id]/page.tsx new file mode 100644 index 0000000..ace96dc --- /dev/null +++ b/qy-lty-admin/app/dances/[id]/page.tsx @@ -0,0 +1,466 @@ +"use client" + +import { useState, useEffect } from "react" +import { useParams, useRouter } from "next/navigation" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Badge } from "@/components/ui/badge" +import { useToast } from "@/hooks/use-toast" +import { AddDanceDialog } from "@/components/dances/add-dance-dialog" +import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog" +import { ArrowLeft, Download, Edit, Play, Upload, FileText, AlertTriangle } from "lucide-react" +import type { Dance } from "@/lib/api/types" + +// 模拟获取舞蹈详情的API +const getDanceById = async (id: string): Promise => { + // 模拟API延迟 + await new Promise((resolve) => setTimeout(resolve, 500)) + + // 模拟舞蹈数据 + const mockDances: Dance[] = [ + { + id: "1", + name: "千本樱", + choreographer: "洛天依工作室", + duration: "3:45", + difficulty: "中等", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "基于《千本樱》歌曲的经典舞蹈编排,动作流畅优美,适合中等水平的舞者。", + motionFile: "senbonzakura_motion.fbx", + category: "日式", + tags: ["经典", "流行", "日式"], + createdAt: "2023-01-15T08:30:00Z", + updatedAt: "2023-02-20T14:15:00Z", + status: "已发布", + activatedCount: 1245, + printedCount: 2000, + }, + { + id: "2", + name: "权御天下", + choreographer: "洛天依动作组", + duration: "4:20", + difficulty: "高级", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "中国风舞蹈,动作幅度大,需要较高的舞蹈基础,展现古风韵味。", + motionFile: "quanyutianxia_motion.fbx", + category: "中国风", + tags: ["高难度", "中国风", "古风"], + createdAt: "2023-03-10T10:45:00Z", + updatedAt: "2023-04-05T16:30:00Z", + status: "已发布", + activatedCount: 1823, + printedCount: 3000, + }, + { + id: "3", + name: "达拉崩吧", + choreographer: "洛天依舞蹈工作室", + duration: "3:10", + difficulty: "初级", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "轻松欢快的舞蹈,适合初学者,动作简单易学。", + motionFile: "dalabengba_motion.fbx", + category: "流行", + tags: ["简单", "欢快", "流行"], + createdAt: "2023-05-20T09:15:00Z", + updatedAt: "2023-05-25T11:20:00Z", + status: "已发布", + activatedCount: 1356, + printedCount: 2500, + }, + { + id: "4", + name: "普通DISCO", + choreographer: "洛天依动作设计组", + duration: "3:30", + difficulty: "中等", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "现代disco风格舞蹈,节奏感强,动作活力四射。", + motionFile: "disco_motion.fbx", + category: "现代", + tags: ["活力", "现代", "disco"], + createdAt: "2023-06-05T14:30:00Z", + updatedAt: "2023-06-10T17:45:00Z", + status: "已发布", + activatedCount: 1578, + printedCount: 3000, + }, + { + id: "5", + name: "华灯宴", + choreographer: "古风舞蹈团队", + duration: "4:50", + difficulty: "高级", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "古风舞蹈,动作优美细腻,需要较高的舞蹈技巧和表现力。", + motionFile: "hualangyan_motion.fbx", + category: "中国风", + tags: ["古风", "优美", "高难度"], + createdAt: "2023-07-12T11:20:00Z", + updatedAt: "2023-07-18T13:40:00Z", + status: "未发布", + activatedCount: 0, + printedCount: 2000, + }, + ] + + const dance = mockDances.find((dance) => dance.id === id) + return dance || null +} + +export default function DanceDetailPage() { + const params = useParams() + const router = useRouter() + const { toast } = useToast() + const [dance, setDance] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const [isPlaying, setIsPlaying] = useState(false) + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) + + const id = params.id as string + + useEffect(() => { + const loadDance = async () => { + setIsLoading(true) + try { + const data = await getDanceById(id) + setDance(data) + } catch (error) { + toast({ + title: "加载失败", + description: "无法加载舞蹈详情,请重试。", + variant: "destructive", + }) + } finally { + setIsLoading(false) + } + } + + loadDance() + }, [id, toast]) + + const handleEditDance = (updatedDance: Dance) => { + setDance(updatedDance) + toast({ + title: "更新成功", + description: `舞蹈 ${updatedDance.name} 已成功更新`, + }) + } + + const handleDeleteDance = async () => { + // 模拟删除API + await new Promise((resolve) => setTimeout(resolve, 500)) + + toast({ + title: "删除成功", + description: `舞蹈 ${dance?.name} 已成功删除`, + variant: "destructive", + }) + + // 返回舞蹈列表页 + router.push("/dances") + } + + const togglePlay = () => { + setIsPlaying(!isPlaying) + } + + if (isLoading) { + return ( + + + + +
+ + + +
+
+ +
+
+
+ +
+
+
+
+
+ ) + } + + if (!dance) { + return ( + +
+ +

舞蹈不存在

+

找不到ID为 {id} 的舞蹈

+ +
+
+ ) + } + + const isPublished = dance.status === "已发布" + + return ( + +
+ +
+ + +
+ {!isPublished && ( + + )} + +
+
+
+ + + + 舞蹈详情 + 批次管理 + 数据分析 + + + +
+ + + 舞蹈预览 + + +
+ {dance.name} +
+ +
+
+ {dance.status} +
+
+
+
+ + + + 舞蹈详情 + + +
+
+

编舞者

+

{dance.choreographer}

+
+
+

分类

+

{dance.category}

+
+
+

时长

+

{dance.duration}

+
+
+

难度

+

{dance.difficulty}

+
+
+

激活数量

+

{dance.activatedCount}

+
+
+

印刷总数

+

{dance.printedCount}

+
+
+

剩余库存

+

{dance.printedCount - dance.activatedCount}

+
+
+ +
+

标签

+
+ {dance.tags?.map((tag, index) => ( + + {tag} + + ))} +
+
+ + {isPublished && ( +
+ +

该舞蹈已发布,基本属性不可修改。您仍可以增加印刷数量。

+
+ )} +
+
+
+ + + + 舞蹈描述 + + +

{dance.description || "暂无描述"}

+
+
+
+ + + + +
+ 印刷批次 + 管理舞蹈卡牌的印刷批次和卡牌ID +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + {isPublished && ( + + + + + + + + + )} + +
批次ID创建日期数量起始ID结束ID操作
B0012023-04-011000DNC{dance.id}-0001DNC{dance.id}-1000 + +
B0022023-08-151000DNC{dance.id}-1001DNC{dance.id}-2000 + +
+
+
+
+
+ + + + + 数据分析 + 查看舞蹈卡牌的激活数据和使用情况 + + +
+
+

激活数据图表

+
+
+

地区分布图表

+
+
+

时间趋势图表

+
+
+
+
+
+
+ + {/* 编辑舞蹈对话框 */} + + + {/* 删除确认对话框 */} + + + ) +} diff --git a/qy-lty-admin/app/dances/loading.tsx b/qy-lty-admin/app/dances/loading.tsx new file mode 100644 index 0000000..6d802ed --- /dev/null +++ b/qy-lty-admin/app/dances/loading.tsx @@ -0,0 +1,38 @@ +import { DashboardHeader } from "@/components/dashboard-header" +import { DashboardShell } from "@/components/dashboard-shell" +import { Card, CardContent, CardHeader } from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" + +export default function DancesLoading() { + return ( + + + + +
+ +
+ + + + + + +
+ + {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
+
+ +
+ + +
+
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/dances/page.tsx b/qy-lty-admin/app/dances/page.tsx new file mode 100644 index 0000000..e8dc1dc --- /dev/null +++ b/qy-lty-admin/app/dances/page.tsx @@ -0,0 +1,352 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Badge } from "@/components/ui/badge" +import { Search, Edit, Video, Eye, Plus, Trash2 } from "lucide-react" +import { AddDanceDialog } from "@/components/dances/add-dance-dialog" +import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog" +import { useToast } from "@/hooks/use-toast" +import type { Dance } from "@/lib/api/types" + +// 初始舞蹈数据 +const initialDances: Dance[] = [ + { + id: "1", + name: "千本樱", + choreographer: "洛天依工作室", + duration: "3:45", + difficulty: "中等", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "基于《千本樱》歌曲的经典舞蹈编排,动作流畅优美,适合中等水平的舞者。", + motionFile: "senbonzakura_motion.fbx", + category: "日式", + tags: ["经典", "流行", "日式"], + createdAt: "2023-01-15T08:30:00Z", + updatedAt: "2023-02-20T14:15:00Z", + }, + { + id: "2", + name: "权御天下", + choreographer: "洛天依动作组", + duration: "4:20", + difficulty: "高级", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "中国风舞蹈,动作幅度大,需要较高的舞蹈基础,展现古风韵味。", + motionFile: "quanyutianxia_motion.fbx", + category: "中国风", + tags: ["高难度", "中国风", "古风"], + createdAt: "2023-03-10T10:45:00Z", + updatedAt: "2023-04-05T16:30:00Z", + }, + { + id: "3", + name: "达拉崩吧", + choreographer: "洛天依舞蹈工作室", + duration: "3:10", + difficulty: "初级", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "轻松欢快的舞蹈,适合初学者,动作简单易学。", + motionFile: "dalabengba_motion.fbx", + category: "流行", + tags: ["简单", "欢快", "流行"], + createdAt: "2023-05-20T09:15:00Z", + updatedAt: "2023-05-25T11:20:00Z", + }, + { + id: "4", + name: "普通DISCO", + choreographer: "洛天依动作设计组", + duration: "3:30", + difficulty: "中等", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "现代disco风格舞蹈,节奏感强,动作活力四射。", + motionFile: "disco_motion.fbx", + category: "现代", + tags: ["活力", "现代", "disco"], + createdAt: "2023-06-05T14:30:00Z", + updatedAt: "2023-06-10T17:45:00Z", + }, + { + id: "5", + name: "华灯宴", + choreographer: "古风舞蹈团队", + duration: "4:50", + difficulty: "高级", + videoUrl: "/placeholder.svg?height=300&width=400", + coverUrl: "/placeholder.svg?height=300&width=400", + description: "古风舞蹈,动作优美细腻,需要较高的舞蹈技巧和表现力。", + motionFile: "hualangyan_motion.fbx", + category: "中国风", + tags: ["古风", "优美", "高难度"], + createdAt: "2023-07-12T11:20:00Z", + updatedAt: "2023-07-18T13:40:00Z", + }, +] + +export default function DancesPage() { + const { toast } = useToast() + const router = useRouter() + const [dances, setDances] = useState(initialDances) + const [searchTerm, setSearchTerm] = useState("") + const [currentPage, setCurrentPage] = useState(1) + const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) + const [danceToEdit, setDanceToEdit] = useState(undefined) + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) + const [danceToDelete, setDanceToDelete] = useState(null) + + const itemsPerPage = 5 + + // 过滤和分页 + const filteredDances = dances.filter( + (dance) => + dance.name.toLowerCase().includes(searchTerm.toLowerCase()) || + dance.id.toLowerCase().includes(searchTerm.toLowerCase()) || + (dance.choreographer && dance.choreographer.toLowerCase().includes(searchTerm.toLowerCase())) || + (dance.category && dance.category.toLowerCase().includes(searchTerm.toLowerCase())) || + (dance.tags && dance.tags.some((tag) => tag.toLowerCase().includes(searchTerm.toLowerCase()))), + ) + + const totalPages = Math.ceil(filteredDances.length / itemsPerPage) + const paginatedDances = filteredDances.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage) + + // 处理添加舞蹈 + const handleAddDance = (newDance: Dance) => { + setDances((prevDances) => [...prevDances, newDance]) + toast({ + title: "添加成功", + description: `舞蹈 ${newDance.name} 已成功添加`, + }) + } + + // 处理编辑舞蹈 + const handleEditDance = (updatedDance: Dance) => { + setDances((prevDances) => prevDances.map((dance) => (dance.id === updatedDance.id ? updatedDance : dance))) + setDanceToEdit(undefined) + setIsAddDialogOpen(false) + toast({ + title: "更新成功", + description: `舞蹈 ${updatedDance.name} 已成功更新`, + }) + } + + // 处理删除舞蹈 + const handleDeleteDance = () => { + if (!danceToDelete) return + + setDances((prevDances) => prevDances.filter((dance) => dance.id !== danceToDelete.id)) + setDanceToDelete(null) + setIsDeleteDialogOpen(false) + toast({ + title: "删除成功", + description: `舞蹈 ${danceToDelete.name} 已成功删除`, + variant: "destructive", + }) + } + + // 打开编辑对话框 + const openEditDialog = (dance: Dance) => { + setDanceToEdit(dance) + setIsAddDialogOpen(true) + } + + // 查看舞蹈详情 + const viewDanceDetails = (dance: Dance) => { + router.push(`/dances/${dance.id}`) + } + + // 打开删除确认对话框 + const openDeleteDialog = (dance: Dance) => { + setDanceToDelete(dance) + setIsDeleteDialogOpen(true) + } + + return ( + + + + + +
+
+
+ + { + setSearchTerm(e.target.value) + setCurrentPage(1) // 重置到第一页 + }} + /> +
+
+
+ + + + + 舞蹈列表 +
+
+ 管理洛天依可以表演的舞蹈 +
+ + + + + + ID + 舞蹈名称 + 编舞者 + 难度 + 分类 + 时长 + 操作 + + + + {paginatedDances.map((dance) => ( + + +
+
+
+ {dance.id} + {dance.name} + {dance.choreographer || "未知"} + + + {dance.difficulty || "未知"} + + + {dance.category || "未分类"} + {dance.duration || "未知"} + +
+ + + +
+
+
+ ))} + + {paginatedDances.length === 0 && ( + + + 没有找到匹配的舞蹈 + + + )} +
+
+
+ +
+ 显示 {paginatedDances.length > 0 ? (currentPage - 1) * itemsPerPage + 1 : 0}- + {Math.min(currentPage * itemsPerPage, filteredDances.length)} 共 {filteredDances.length} 个舞蹈 +
+
+ + +
+
+
+ + {/* 添加/编辑舞蹈对话框 */} + + + {/* 删除确认对话框 */} + +
+ ) +} diff --git a/qy-lty-admin/app/food/[id]/loading.tsx b/qy-lty-admin/app/food/[id]/loading.tsx new file mode 100644 index 0000000..81a0db8 --- /dev/null +++ b/qy-lty-admin/app/food/[id]/loading.tsx @@ -0,0 +1,65 @@ +import { Skeleton } from "@/components/ui/skeleton" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" + +export default function FoodDetailLoading() { + return ( + +
+ + } text={}> +
+ +
+
+
+ + + + 食物详情 + 批次管理 + 数据分析 + + + +
+ + + + + + + + + + + + + + +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+ + +
+ ))} +
+ + +
+
+
+
+
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/food/[id]/page.tsx b/qy-lty-admin/app/food/[id]/page.tsx new file mode 100644 index 0000000..5265100 --- /dev/null +++ b/qy-lty-admin/app/food/[id]/page.tsx @@ -0,0 +1,299 @@ +"use client" + +import { useState, useEffect } from "react" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { ArrowLeft, Edit, AlertTriangle, FileText, Loader2 } from "lucide-react" +import Link from "next/link" +import { AddPrintBatchDialog } from "@/components/food/add-print-batch-dialog" +import { ExportCardsDialog } from "@/components/food/export-cards-dialog" +import { useToast } from "@/components/ui/use-toast" +import { getFood } from "@/lib/api/food" +import type { Food } from "@/components/food/food-detail-dialog" + +// 扩展Food类型以包含批次相关信息 +type FoodWithBatches = Food & { + printedCount?: number + batches?: Array<{ + id: string + date: string + quantity: number + startId: string + endId: string + activatedCount: number + }> +} + +export default function FoodDetailPage({ params }: { params: { id: string } }) { + const { toast } = useToast() + const [food, setFood] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + // 获取食物详情 + const fetchFoodDetail = async () => { + try { + setLoading(true) + setError(null) + const response = await getFood(params.id) + + if (response.success && response.data) { + // 为演示目的,添加一些模拟的批次数据 + const foodWithBatches: FoodWithBatches = { + ...response.data, + printedCount: response.data.activatedCount + Math.floor(Math.random() * 1000), + batches: [ + { + id: "B001", + date: "2023-08-01", + quantity: 2000, + startId: `${response.data.id}-0001`, + endId: `${response.data.id}-2000`, + activatedCount: Math.floor(response.data.activatedCount * 0.6), + }, + { + id: "B002", + date: "2023-11-15", + quantity: 1000, + startId: `${response.data.id}-2001`, + endId: `${response.data.id}-3000`, + activatedCount: Math.floor(response.data.activatedCount * 0.4), + }, + ] + } + setFood(foodWithBatches) + } else { + setError("食物不存在或获取失败") + } + } catch (error) { + console.error("获取食物详情失败:", error) + setError(error instanceof Error ? error.message : "获取食物详情失败") + toast({ + title: "获取数据失败", + description: error instanceof Error ? error.message : "无法获取食物详情", + variant: "destructive", + }) + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchFoodDetail() + }, [params.id]) + + if (loading) { + return ( + +
+ + 加载中... +
+
+ ) + } + + if (error || !food) { + return ( + +
+ +

食物不存在

+

+ {error || `找不到ID为 ${params.id} 的食物`} +

+ +
+
+ ) + } + + const isPublished = food.status === "已发布" + + return ( + +
+ +
+ + +
+ {!isPublished && ( + + )} + +
+
+
+ + + + 食物详情 + 批次管理 + 数据分析 + + + +
+ + + 食物预览 + + +
+ {food.name} +
+ {food.status} +
+
+
+
+ + + + 食物详情 + + +
+
+

类型

+

{food.type}

+
+
+

稀有度

+

{food.rarity}

+
+
+

发布日期

+

{food.releaseDate || "尚未发布"}

+
+
+

状态

+

{food.status}

+
+
+

激活数量

+

{food.activatedCount}

+
+
+

印刷总数

+

{food.printedCount || 0}

+
+
+

描述

+

{food.description}

+
+
+ + {isPublished && ( +
+ +

该食物已发布,基本属性不可修改。您仍可以增加印刷数量。

+
+ )} +
+
+
+
+ + + + +
+ 印刷批次 + 管理食物卡牌的印刷批次和卡牌ID +
+ +
+ +
+ + + + + + + + + + + + + + {food.batches?.map((batch) => ( + + + + + + + + + + ))} + {(!food.batches || food.batches.length === 0) && ( + + + + )} + +
批次ID创建日期数量起始ID结束ID激活数量操作
{batch.id}{batch.date}{batch.quantity}{batch.startId}{batch.endId}{batch.activatedCount} + +
+ 暂无批次数据 +
+
+
+
+
+ + + + + 数据分析 + 查看食物卡牌的激活数据和使用情况 + + +
+
+

激活数据图表

+
+
+

地区分布图表

+
+
+

时间趋势图表

+
+
+
+
+
+
+ + ) +} \ No newline at end of file diff --git a/qy-lty-admin/app/food/loading.tsx b/qy-lty-admin/app/food/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/qy-lty-admin/app/food/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/qy-lty-admin/app/food/page.tsx b/qy-lty-admin/app/food/page.tsx new file mode 100644 index 0000000..2624075 --- /dev/null +++ b/qy-lty-admin/app/food/page.tsx @@ -0,0 +1,277 @@ +"use client" + +import { useState, useEffect } from "react" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Badge } from "@/components/ui/badge" +import { Search, Edit, Eye, Loader2 } from "lucide-react" +import { AddFoodDialog } from "@/components/food/add-food-dialog" +import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog" +import { useToast } from "@/components/ui/use-toast" +import Link from "next/link" +import type { Food } from "@/components/food/food-detail-dialog" +import { getFoods, deleteFood as deleteFoodApi } from "@/lib/api/food" + +export default function FoodPage() { + const { toast } = useToast() + const [foods, setFoods] = useState([]) + const [loading, setLoading] = useState(true) + const [searchTerm, setSearchTerm] = useState("") + const [currentPage, setCurrentPage] = useState(1) + const [totalPages, setTotalPages] = useState(1) + const [total, setTotal] = useState(0) + const [selectedFood, setSelectedFood] = useState(null) + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) + + const itemsPerPage = 10 + + // 状态显示映射 + const getStatusDisplay = (status: string) => { + const statusMap: Record = { + 'published': '已发布', + 'draft': '草稿', + 'pending': '待审核', + } + return statusMap[status] || status + } + + // 获取食物列表数据 + const fetchFoods = async (page: number = 1, search: string = "") => { + try { + setLoading(true) + const response = await getFoods({ + page, + pageSize: itemsPerPage, + search: search || undefined, + }) + + if (response.success && response.data) { + setFoods(response.data.results || []) + setTotal(response.data.count || 0) + setTotalPages(Math.ceil((response.data.count || 0) / itemsPerPage)) + } + } catch (error) { + console.error("获取食物列表失败:", error) + toast({ + title: "获取数据失败", + description: error instanceof Error ? error.message : "无法获取食物列表", + variant: "destructive", + }) + setFoods([]) + } finally { + setLoading(false) + } + } + + // 初始化数据 + useEffect(() => { + fetchFoods(currentPage, searchTerm) + }, [currentPage]) + + // 搜索处理 + const handleSearch = (value: string) => { + setSearchTerm(value) + setCurrentPage(1) + fetchFoods(1, value) + } + + // 处理添加食物成功后的刷新 + const handleAddFood = async () => { + // 食物已经在 AddFoodDialog 中创建成功,这里只需要刷新列表 + fetchFoods(currentPage, searchTerm) + } + + // 处理编辑食物成功后的刷新 + const handleEditFood = async () => { + // 食物已经在 AddFoodDialog 中更新成功,这里只需要刷新列表和关闭对话框 + setSelectedFood(null) + setIsEditDialogOpen(false) + fetchFoods(currentPage, searchTerm) + } + + // 处理删除食物 + const handleDeleteFood = async (foodId: string) => { + try { + const response = await deleteFoodApi(foodId) + + if (response.success) { + toast({ + title: "删除成功", + description: "食物已成功删除", + variant: "destructive", + }) + // 重新获取当前页数据 + fetchFoods(currentPage, searchTerm) + } + } catch (error) { + console.error("删除食物失败:", error) + toast({ + title: "删除失败", + description: error instanceof Error ? error.message : "删除食物失败", + variant: "destructive", + }) + } + } + + // 打开编辑对话框 + const openEditDialog = (food: Food) => { + setSelectedFood(food) + setIsEditDialogOpen(true) + } + + return ( + + + + + +
+
+
+ + handleSearch(e.target.value)} + /> +
+
+
+ + + + + 食物列表 +
+
+ 管理洛天依的食物卡牌 +
+ + {loading ? ( +
+ + 加载中... +
+ ) : ( + + + + ID + 食物名称 + 类型 + 稀有度 + 发布日期 + 状态 + 激活数量 + 操作 + + + + {foods.map((food) => ( + + {food.id} + {food.name} + {food.food_type} + {food.rarity} + {food.releaseDate || "-"} + + + {getStatusDisplay(food.status)} + + + {food.activatedCount} + + + + {food.status !== "published" && food.status !== "已发布" && ( + <> + + + handleDeleteFood(food.id)} + /> + + )} + + + ))} + + {foods.length === 0 && !loading && ( + + + {searchTerm ? "没有找到匹配的食物" : "暂无食物数据"} + + + )} + +
+ )} +
+ +
+ 显示 {foods.length > 0 ? (currentPage - 1) * itemsPerPage + 1 : 0}- + {Math.min(currentPage * itemsPerPage, total)} 共 {total} 个食物 +
+
+ + +
+
+
+ + {/* 编辑食物对话框 */} + {selectedFood && isEditDialogOpen && ( + + )} +
+ ) +} \ No newline at end of file diff --git a/qy-lty-admin/app/forgot-password/layout.tsx b/qy-lty-admin/app/forgot-password/layout.tsx new file mode 100644 index 0000000..a3d329a --- /dev/null +++ b/qy-lty-admin/app/forgot-password/layout.tsx @@ -0,0 +1,8 @@ +import type React from "react" +export default function ForgotPasswordLayout({ + children, +}: { + children: React.ReactNode +}) { + return
{children}
+} diff --git a/qy-lty-admin/app/forgot-password/loading.tsx b/qy-lty-admin/app/forgot-password/loading.tsx new file mode 100644 index 0000000..37fd194 --- /dev/null +++ b/qy-lty-admin/app/forgot-password/loading.tsx @@ -0,0 +1,12 @@ +import { Loader2 } from "lucide-react" + +export default function Loading() { + return ( +
+
+ +

加载中...

+
+
+ ) +} diff --git a/qy-lty-admin/app/forgot-password/page.tsx b/qy-lty-admin/app/forgot-password/page.tsx new file mode 100644 index 0000000..f444539 --- /dev/null +++ b/qy-lty-admin/app/forgot-password/page.tsx @@ -0,0 +1,259 @@ +"use client" + +import type React from "react" + +import { useState } from "react" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Label } from "@/components/ui/label" +import { Sparkles, Mail, Phone, ArrowRight, Loader2, CheckCircle2 } from "lucide-react" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" + +export default function ForgotPasswordPage() { + const router = useRouter() + const [isLoading, setIsLoading] = useState(false) + const [resetMethod, setResetMethod] = useState<"email" | "phone">("email") + const [email, setEmail] = useState("") + const [phone, setPhone] = useState("") + const [verificationCode, setVerificationCode] = useState("") + const [isSendingCode, setIsSendingCode] = useState(false) + const [countdown, setCountdown] = useState(0) + const [isSuccess, setIsSuccess] = useState(false) + + const handleResetPassword = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + + try { + // 模拟重置密码请求 + await new Promise((resolve) => setTimeout(resolve, 1500)) + + // 显示成功信息 + setIsSuccess(true) + + // 3秒后跳转到登录页 + setTimeout(() => { + router.push("/login") + }, 3000) + } catch (error) { + console.error("重置密码失败", error) + } finally { + setIsLoading(false) + } + } + + const handleSendVerificationCode = async () => { + if (!phone || phone.length !== 11 || isSendingCode) return + + setIsSendingCode(true) + + try { + // 模拟发送验证码请求 + await new Promise((resolve) => setTimeout(resolve, 1000)) + + // 开始倒计时 + setCountdown(60) + const timer = setInterval(() => { + setCountdown((prev) => { + if (prev <= 1) { + clearInterval(timer) + setIsSendingCode(false) + return 0 + } + return prev - 1 + }) + }, 1000) + } catch (error) { + console.error("发送验证码失败", error) + setIsSendingCode(false) + } + } + + if (isSuccess) { + return ( +
+
+
+ + + +
+
+ +
+
+ 重置密码链接已发送 + + 我们已向您{resetMethod === "email" ? "的邮箱" : "的手机"}发送了重置密码的 + {resetMethod === "email" ? "链接" : "验证码"}, 请 + {resetMethod === "email" ? "查看您的邮箱" : "注意查收短信"}。 + +
+ +

页面将在3秒后自动跳转到登录页面...

+ +
+
+
+ ) + } + + return ( +
+
+
+ + + +
+
+ +
+
+ + 找回密码 + + 请选择找回密码的方式 +
+ + setResetMethod(value as "email" | "phone")}> + + + 邮箱找回 + + + 手机找回 + + + + +
+
+ +
+ + setEmail(e.target.value)} + required + /> +
+

我们将向您的邮箱发送重置密码的链接

+
+ + +
+
+ + +
+
+ +
+ + setPhone(e.target.value)} + required + /> +
+
+ +
+ +
+
+ setVerificationCode(e.target.value)} + required + /> +
+ +
+
+ + +
+
+
+
+ +
+ 记起密码了?{" "} + + 返回登录 + +
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/globals.css b/qy-lty-admin/app/globals.css new file mode 100644 index 0000000..ac68442 --- /dev/null +++ b/qy-lty-admin/app/globals.css @@ -0,0 +1,94 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/qy-lty-admin/app/home-decor/[id]/loading.tsx b/qy-lty-admin/app/home-decor/[id]/loading.tsx new file mode 100644 index 0000000..e7d719c --- /dev/null +++ b/qy-lty-admin/app/home-decor/[id]/loading.tsx @@ -0,0 +1,65 @@ +import { Skeleton } from "@/components/ui/skeleton" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" + +export default function HomeDecorDetailLoading() { + return ( + +
+ + } text={}> +
+ +
+
+
+ + + + 家居装饰详情 + 批次管理 + 数据分析 + + + +
+ + + + + + + + + + + + + + +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+ + +
+ ))} +
+ + +
+
+
+
+
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/home-decor/[id]/page.tsx b/qy-lty-admin/app/home-decor/[id]/page.tsx new file mode 100644 index 0000000..367a4dc --- /dev/null +++ b/qy-lty-admin/app/home-decor/[id]/page.tsx @@ -0,0 +1,327 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { ArrowLeft, Edit, AlertTriangle, FileText } from "lucide-react" +import Link from "next/link" +import { AddPrintBatchDialog } from "@/components/home-decor/add-print-batch-dialog" +import { ExportCardsDialog } from "@/components/home-decor/export-cards-dialog" + +// Mock data for the home decor details +const decorData = { + DEC001: { + id: "DEC001", + name: "星空投影灯", + type: "灯饰", + rarity: "稀有", + description: "可以在房间内投影出美丽的星空,营造浪漫氛围。", + releaseDate: "2023-10-20", + status: "已发布", + activatedCount: 1342, + printedCount: 2500, + image: "/placeholder.svg?height=300&width=300", + batches: [ + { + id: "B001", + date: "2023-09-01", + quantity: 1500, + startId: "DEC001-0001", + endId: "DEC001-1500", + activatedCount: 842, + }, + { + id: "B002", + date: "2023-12-15", + quantity: 1000, + startId: "DEC001-1501", + endId: "DEC001-2500", + activatedCount: 500, + }, + ], + }, + DEC002: { + id: "DEC002", + name: "音乐主题壁纸", + type: "墙饰", + rarity: "普通", + description: "以音乐元素为主题的壁纸,适合洛天依的房间装饰。", + releaseDate: "2023-11-05", + status: "已发布", + activatedCount: 2156, + printedCount: 3000, + image: "/placeholder.svg?height=300&width=300", + batches: [ + { + id: "B003", + date: "2023-10-10", + quantity: 3000, + startId: "DEC002-0001", + endId: "DEC002-3000", + activatedCount: 2156, + }, + ], + }, + DEC003: { + id: "DEC003", + name: "音符地毯", + type: "地饰", + rarity: "稀有", + description: "音符形状的地毯,踩上去会发出悦耳的音符声。", + releaseDate: "2023-12-15", + status: "已发布", + activatedCount: 987, + printedCount: 2000, + image: "/placeholder.svg?height=300&width=300", + batches: [ + { + id: "B004", + date: "2023-11-20", + quantity: 2000, + startId: "DEC003-0001", + endId: "DEC003-2000", + activatedCount: 987, + }, + ], + }, + DEC004: { + id: "DEC004", + name: "全息投影装置", + type: "科技装饰", + rarity: "传说", + description: "可以投影出洛天依的全息影像,实现虚拟互动。", + releaseDate: "2024-01-20", + status: "已发布", + activatedCount: 456, + printedCount: 1000, + image: "/placeholder.svg?height=300&width=300", + batches: [ + { + id: "B005", + date: "2024-01-05", + quantity: 1000, + startId: "DEC004-0001", + endId: "DEC004-1000", + activatedCount: 456, + }, + ], + }, + DEC005: { + id: "DEC005", + name: "樱花主题家具套装", + type: "家具套装", + rarity: "史诗", + description: "以樱花为主题的家具套装,包含床、桌椅、柜子等多件家具。", + releaseDate: "", + status: "未发布", + activatedCount: 0, + printedCount: 1500, + image: "/placeholder.svg?height=300&width=300", + batches: [ + { + id: "B006", + date: "2024-02-10", + quantity: 1500, + startId: "DEC005-0001", + endId: "DEC005-1500", + activatedCount: 0, + }, + ], + }, +} + +export default function HomeDecorDetailPage({ params }: { params: { id: string } }) { + const decor = decorData[params.id as keyof typeof decorData] + + if (!decor) { + return ( + +
+ +

家居装饰不存在

+

找不到ID为 {params.id} 的家居装饰

+ +
+
+ ) + } + + const isPublished = decor.status === "已发布" + + return ( + +
+ +
+ + +
+ {!isPublished && ( + + )} + +
+
+
+ + + + 家居装饰详情 + 批次管理 + 数据分析 + + + +
+ + + 家居装饰预览 + + +
+ {decor.name} +
+ {decor.status} +
+
+
+
+ + + + 家居装饰详情 + + +
+
+

类型

+

{decor.type}

+
+
+

稀有度

+

{decor.rarity}

+
+
+

发布日期

+

{decor.releaseDate || "尚未发布"}

+
+
+

状态

+

{decor.status}

+
+
+

激活数量

+

{decor.activatedCount}

+
+
+

印刷总数

+

{decor.printedCount}

+
+
+

描述

+

{decor.description}

+
+
+ + {isPublished && ( +
+ +

该家居装饰已发布,基本属性不可修改。您仍可以增加印刷数量。

+
+ )} +
+
+
+
+ + + + +
+ 印刷批次 + 管理家居装饰卡牌的印刷批次和卡牌ID +
+ +
+ +
+ + + + + + + + + + + + + + {decor.batches.map((batch) => ( + + + + + + + + + + ))} + +
批次ID创建日期数量起始ID结束ID激活数量操作
{batch.id}{batch.date}{batch.quantity}{batch.startId}{batch.endId}{batch.activatedCount} + +
+
+
+
+
+ + + + + 数据分析 + 查看家居装饰卡牌的激活数据和使用情况 + + +
+
+

激活数据图表

+
+
+

地区分布图表

+
+
+

时间趋势图表

+
+
+
+
+
+
+ + ) +} diff --git a/qy-lty-admin/app/home-decor/loading.tsx b/qy-lty-admin/app/home-decor/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/qy-lty-admin/app/home-decor/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/qy-lty-admin/app/home-decor/page.tsx b/qy-lty-admin/app/home-decor/page.tsx new file mode 100644 index 0000000..15af04b --- /dev/null +++ b/qy-lty-admin/app/home-decor/page.tsx @@ -0,0 +1,284 @@ +"use client" + +import { useState } from "react" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Badge } from "@/components/ui/badge" +import { Search, Edit, Eye } from "lucide-react" +import { AddHomeDecorDialog } from "@/components/home-decor/add-home-decor-dialog" +import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog" +import { useToast } from "@/components/ui/use-toast" +import Link from "next/link" +import type { HomeDecor } from "@/components/home-decor/home-decor-detail-dialog" + +// 初始家居装饰数据 +const initialDecors: HomeDecor[] = [ + { + id: "DEC001", + name: "星空投影灯", + type: "灯饰", + rarity: "稀有", + description: "可以在房间内投影出美丽的星空,营造浪漫氛围。", + releaseDate: "2023-10-20", + status: "已发布", + activatedCount: 1342, + image: "/placeholder.svg?height=300&width=300", + }, + { + id: "DEC002", + name: "音乐主题壁纸", + type: "墙饰", + rarity: "普通", + description: "以音乐元素为主题的壁纸,适合洛天依的房间装饰。", + releaseDate: "2023-11-05", + status: "已发布", + activatedCount: 2156, + image: "/placeholder.svg?height=300&width=300", + }, + { + id: "DEC003", + name: "音符地毯", + type: "地饰", + rarity: "稀有", + description: "音符形状的地毯,踩上去会发出悦耳的音符声。", + releaseDate: "2023-12-15", + status: "已发布", + activatedCount: 987, + image: "/placeholder.svg?height=300&width=300", + }, + { + id: "DEC004", + name: "全息投影装置", + type: "科技装饰", + rarity: "传说", + description: "可以投影出洛天依的全息影像,实现虚拟互动。", + releaseDate: "2024-01-20", + status: "已发布", + activatedCount: 456, + image: "/placeholder.svg?height=300&width=300", + }, + { + id: "DEC005", + name: "樱花主题家具套装", + type: "家具套装", + rarity: "史诗", + description: "以樱花为主题的家具套装,包含床、桌椅、柜子等多件家具。", + releaseDate: "", + status: "未发布", + activatedCount: 0, + image: "/placeholder.svg?height=300&width=300", + }, +] + +export default function HomeDecorPage() { + const { toast } = useToast() + const [decors, setDecors] = useState(initialDecors) + const [searchTerm, setSearchTerm] = useState("") + const [currentPage, setCurrentPage] = useState(1) + const [selectedDecor, setSelectedDecor] = useState(null) + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) + + const itemsPerPage = 5 + + // 过滤和分页 + const filteredDecors = decors.filter( + (decor) => + decor.name.toLowerCase().includes(searchTerm.toLowerCase()) || + decor.id.toLowerCase().includes(searchTerm.toLowerCase()) || + decor.type.toLowerCase().includes(searchTerm.toLowerCase()), + ) + + const totalPages = Math.ceil(filteredDecors.length / itemsPerPage) + const paginatedDecors = filteredDecors.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage) + + // 处理添加家居装饰 + const handleAddDecor = (newDecor: HomeDecor) => { + setDecors((prevDecors) => [...prevDecors, newDecor]) + toast({ + title: "添加成功", + description: `家居装饰 ${newDecor.name} 已成功添加`, + }) + } + + // 处理编辑家居装饰 + const handleEditDecor = (updatedDecor: HomeDecor) => { + setDecors((prevDecors) => prevDecors.map((decor) => (decor.id === updatedDecor.id ? updatedDecor : decor))) + setSelectedDecor(null) + setIsEditDialogOpen(false) + toast({ + title: "更新成功", + description: `家居装饰 ${updatedDecor.name} 已成功更新`, + }) + } + + // 处理删除家居装饰 + const handleDeleteDecor = async (decorId: string) => { + // 模拟API请求 + await new Promise((resolve) => setTimeout(resolve, 1000)) + + setDecors((prevDecors) => prevDecors.filter((decor) => decor.id !== decorId)) + toast({ + title: "删除成功", + description: "家居装饰已成功删除", + variant: "destructive", + }) + } + + // 打开编辑对话框 + const openEditDialog = (decor: HomeDecor) => { + setSelectedDecor(decor) + setIsEditDialogOpen(true) + } + + return ( + + + + + +
+
+
+ + { + setSearchTerm(e.target.value) + setCurrentPage(1) // 重置到第一页 + }} + /> +
+
+
+ + + + + + 家居装饰列表 + +
+
+ 管理洛天依的家居装饰卡牌 +
+ + + + + ID + 装饰名称 + 类型 + 稀有度 + 发布日期 + 状态 + 激活数量 + 操作 + + + + {paginatedDecors.map((decor) => ( + + {decor.id} + {decor.name} + {decor.type} + {decor.rarity} + {decor.releaseDate || "-"} + + + {decor.status} + + + {decor.activatedCount} + + + + {decor.status !== "已发布" && ( + <> + + + handleDeleteDecor(decor.id)} + /> + + )} + + + ))} + + {paginatedDecors.length === 0 && ( + + + 没有找到匹配的家居装饰 + + + )} + +
+
+ +
+ 显示 {paginatedDecors.length > 0 ? (currentPage - 1) * itemsPerPage + 1 : 0}- + {Math.min(currentPage * itemsPerPage, filteredDecors.length)} 共 {filteredDecors.length} 个家居装饰 +
+
+ + +
+
+
+ + {/* 编辑家居装饰对话框 - 当选中家居装饰时显示 */} + {selectedDecor && isEditDialogOpen && ( + + )} +
+ ) +} diff --git a/qy-lty-admin/app/layout.tsx b/qy-lty-admin/app/layout.tsx new file mode 100644 index 0000000..17b2ce8 --- /dev/null +++ b/qy-lty-admin/app/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from 'next' +import './globals.css' + +export const metadata: Metadata = { + title: 'v0 App', + description: 'Created with v0', + generator: 'v0.dev', +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + {children} + + ) +} diff --git a/qy-lty-admin/app/login/layout.tsx b/qy-lty-admin/app/login/layout.tsx new file mode 100644 index 0000000..6b2ceb2 --- /dev/null +++ b/qy-lty-admin/app/login/layout.tsx @@ -0,0 +1,8 @@ +import type React from "react" +export default function LoginLayout({ + children, +}: { + children: React.ReactNode +}) { + return
{children}
+} diff --git a/qy-lty-admin/app/login/loading.tsx b/qy-lty-admin/app/login/loading.tsx new file mode 100644 index 0000000..37fd194 --- /dev/null +++ b/qy-lty-admin/app/login/loading.tsx @@ -0,0 +1,12 @@ +import { Loader2 } from "lucide-react" + +export default function Loading() { + return ( +
+
+ +

加载中...

+
+
+ ) +} diff --git a/qy-lty-admin/app/login/page.tsx b/qy-lty-admin/app/login/page.tsx new file mode 100644 index 0000000..7fd7238 --- /dev/null +++ b/qy-lty-admin/app/login/page.tsx @@ -0,0 +1,275 @@ +"use client" + +import type React from "react" + +import { useState } from "react" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Label } from "@/components/ui/label" +import { Checkbox } from "@/components/ui/checkbox" +import { Sparkles, Mail, Lock, Phone, ArrowRight, Loader2 } from "lucide-react" +import { emailLogin, saveAuthToken } from "@/lib/api/auth" + +export default function LoginPage() { + const router = useRouter() + const [isLoading, setIsLoading] = useState(false) + const [loginMethod, setLoginMethod] = useState<"email" | "phone">("email") + const [email, setEmail] = useState("") + const [password, setPassword] = useState("") + const [phone, setPhone] = useState("") + const [verificationCode, setVerificationCode] = useState("") + const [isSendingCode, setIsSendingCode] = useState(false) + const [countdown, setCountdown] = useState(0) + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + + try { + if (loginMethod === "email") { + // 使用真实的邮箱登录接口 + const response = await emailLogin(email, password) + console.log(response) + + // 保存登录凭证 + saveAuthToken(response.data.token) + + // 设置登录状态 + localStorage.setItem("isLoggedIn", "true") + + // 登录成功后跳转到首页 + router.push("/") + } else { + // 手机号登录逻辑保持不变 + // 模拟登录请求 + await new Promise((resolve) => setTimeout(resolve, 1500)) + + // 设置登录状态 + localStorage.setItem("isLoggedIn", "true") + + // 登录成功后跳转到首页 + router.push("/") + } + } catch (error) { + console.error("登录失败", error) + alert(error instanceof Error ? error.message : "登录失败,请重试") + } finally { + setIsLoading(false) + } + } + + const handleSendVerificationCode = async () => { + if (!phone || phone.length !== 11 || isSendingCode) return + + setIsSendingCode(true) + + try { + // 模拟发送验证码请求 + await new Promise((resolve) => setTimeout(resolve, 1000)) + + // 开始倒计时 + setCountdown(60) + const timer = setInterval(() => { + setCountdown((prev) => { + if (prev <= 1) { + clearInterval(timer) + setIsSendingCode(false) + return 0 + } + return prev - 1 + }) + }, 1000) + } catch (error) { + console.error("发送验证码失败", error) + setIsSendingCode(false) + } + } + + return ( +
+
+
+ + + +
+
+ +
+
+ + 洛天依管理系统 + + 请登录您的账户以继续 +
+ + setLoginMethod(value as "email" | "phone")}> + + + 邮箱登录 + + + 手机登录 + + + + +
+
+ +
+ + setEmail(e.target.value)} + required + /> +
+
+ +
+
+ + + 忘记密码? + +
+
+ + setPassword(e.target.value)} + required + /> +
+
+ +
+ + +
+ + +
+
+ + +
+
+ +
+ + setPhone(e.target.value)} + required + /> +
+
+ +
+ +
+
+ setVerificationCode(e.target.value)} + required + /> +
+ +
+
+ +
+ + +
+ + +
+
+
+
+ +
+ 还没有账户?{" "} + + 注册新账户 + +
+
+
+
+ ) +} diff --git a/qy-lty-admin/app/outfits/[id]/page.tsx b/qy-lty-admin/app/outfits/[id]/page.tsx new file mode 100644 index 0000000..ccb4b2b --- /dev/null +++ b/qy-lty-admin/app/outfits/[id]/page.tsx @@ -0,0 +1,278 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { ArrowLeft, Edit, AlertTriangle, FileText } from "lucide-react" +import Link from "next/link" +import { AddPrintBatchDialog } from "@/components/outfits/add-print-batch-dialog" +import { ExportCardsDialog } from "@/components/outfits/export-cards-dialog" + +// Mock data for the outfit details +const outfitData = { + OFT001: { + id: "OFT001", + name: "经典原创服装", + type: "常规", + rarity: "稀有", + description: "洛天依的经典原创服装,简约而不失时尚,适合各种场合穿着。", + releaseDate: "2023-10-15", + status: "已发布", + activatedCount: 1245, + printedCount: 2000, + image: "/placeholder.svg?height=300&width=300", + batches: [ + { id: "B001", date: "2023-09-01", quantity: 1000, startId: "OFT001-0001", endId: "OFT001-1000" }, + { id: "B002", date: "2023-12-15", quantity: 1000, startId: "OFT001-1001", endId: "OFT001-2000" }, + ], + }, + OFT002: { + id: "OFT002", + name: "夏日泳装", + type: "季节限定", + rarity: "史诗", + description: "专为夏季设计的清凉泳装,让洛天依在夏日活动中更加亮眼。", + releaseDate: "2023-06-01", + status: "已发布", + activatedCount: 876, + printedCount: 1500, + image: "/placeholder.svg?height=300&width=300", + batches: [{ id: "B003", date: "2023-05-10", quantity: 1500, startId: "OFT002-0001", endId: "OFT002-1500" }], + }, + OFT003: { + id: "OFT003", + name: "冬日圣诞服", + type: "节日限定", + rarity: "史诗", + description: "圣诞节特别设计的节日服装,温暖而喜庆,让洛天依陪你度过欢乐的圣诞。", + releaseDate: "2023-12-01", + status: "已发布", + activatedCount: 1032, + printedCount: 2000, + image: "/placeholder.svg?height=300&width=300", + batches: [{ id: "B004", date: "2023-11-15", quantity: 2000, startId: "OFT003-0001", endId: "OFT003-2000" }], + }, + OFT004: { + id: "OFT004", + name: "校园制服", + type: "常规", + rarity: "稀有", + description: "清新可爱的校园风格制服,展现洛天依青春活力的一面。", + releaseDate: "2024-01-15", + status: "已发布", + activatedCount: 1567, + printedCount: 3000, + image: "/placeholder.svg?height=300&width=300", + batches: [ + { id: "B005", date: "2024-01-05", quantity: 2000, startId: "OFT004-0001", endId: "OFT004-2000" }, + { id: "B006", date: "2024-02-10", quantity: 1000, startId: "OFT004-2001", endId: "OFT004-3000" }, + ], + }, + OFT005: { + id: "OFT005", + name: "演唱会礼服", + type: "特别版", + rarity: "传说", + description: "为重要演唱会设计的华丽礼服,尽显洛天依的优雅与魅力。", + releaseDate: "", + status: "未发布", + activatedCount: 0, + printedCount: 1000, + image: "/placeholder.svg?height=300&width=300", + batches: [{ id: "B007", date: "2024-03-20", quantity: 1000, startId: "OFT005-0001", endId: "OFT005-1000" }], + }, +} + +export default function OutfitDetailPage({ params }: { params: { id: string } }) { + const outfit = outfitData[params.id as keyof typeof outfitData] + + if (!outfit) { + return ( + +
+ +

服装不存在

+

找不到ID为 {params.id} 的服装

+ +
+
+ ) + } + + const isPublished = outfit.status === "已发布" + + return ( + +
+ +
+ + +
+ {!isPublished && ( + + )} + +
+
+
+ + + + 服装详情 + 批次管理 + 数据分析 + + + +
+ + + 服装预览 + + +
+ {outfit.name} +
+ {outfit.status} +
+
+
+
+ + + + 服装详情 + + +
+
+

服装类型

+

{outfit.type}

+
+
+

稀有度

+

{outfit.rarity}

+
+
+

发布日期

+

{outfit.releaseDate || "尚未发布"}

+
+
+

激活数量

+

{outfit.activatedCount}

+
+
+

印刷总数

+

{outfit.printedCount}

+
+
+

剩余库存

+

{outfit.printedCount - outfit.activatedCount}

+
+
+

服装描述

+

{outfit.description}

+
+
+ + {isPublished && ( +
+ +

该服装已发布,基本属性不可修改。您仍可以增加印刷数量。

+
+ )} +
+
+
+
+ + + + +
+ 印刷批次 + 管理服装卡牌的印刷批次和卡牌ID +
+ +
+ +
+ + + + + + + + + + + + + {outfit.batches.map((batch) => ( + + + + + + + + + ))} + +
批次ID创建日期数量起始ID结束ID操作
{batch.id}{batch.date}{batch.quantity}{batch.startId}{batch.endId} + +
+
+
+
+
+ + + + + 数据分析 + 查看服装卡牌的激活数据和使用情况 + + +
+
+

激活数据图表

+
+
+

地区分布图表

+
+
+

时间趋势图表

+
+
+
+
+
+
+ + ) +} diff --git a/qy-lty-admin/app/outfits/edit/[id]/page.tsx b/qy-lty-admin/app/outfits/edit/[id]/page.tsx new file mode 100644 index 0000000..d7af1aa --- /dev/null +++ b/qy-lty-admin/app/outfits/edit/[id]/page.tsx @@ -0,0 +1,273 @@ +import { DashboardShell } from "@/components/dashboard-shell" +import { DashboardHeader } from "@/components/dashboard-header" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Textarea } from "@/components/ui/textarea" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { ArrowLeft, Save, AlertTriangle, Upload } from "lucide-react" +import Link from "next/link" + +// Mock data for the outfit details (same as in [id]/page.tsx) +const outfitData = { + OFT001: { + id: "OFT001", + name: "经典原创服装", + type: "常规", + rarity: "稀有", + description: "洛天依的经典原创服装,简约而不失时尚,适合各种场合穿着。", + releaseDate: "2023-10-15", + status: "已发布", + activatedCount: 1245, + printedCount: 2000, + image: "/placeholder.svg?height=300&width=300", + batches: [ + { id: "B001", date: "2023-09-01", quantity: 1000, startId: "OFT001-0001", endId: "OFT001-1000" }, + { id: "B002", date: "2023-12-15", quantity: 1000, startId: "OFT001-1001", endId: "OFT001-2000" }, + ], + }, + OFT002: { + id: "OFT002", + name: "夏日泳装", + type: "季节限定", + rarity: "史诗", + description: "专为夏季设计的清凉泳装,让洛天依在夏日活动中更加亮眼。", + releaseDate: "2023-06-01", + status: "已发布", + activatedCount: 876, + printedCount: 1500, + image: "/placeholder.svg?height=300&width=300", + batches: [{ id: "B003", date: "2023-05-10", quantity: 1500, startId: "OFT002-0001", endId: "OFT002-1500" }], + }, + OFT003: { + id: "OFT003", + name: "冬日圣诞服", + type: "节日限定", + rarity: "史诗", + description: "圣诞节特别设计的节日服装,温暖而喜庆,让洛天依陪你度过欢乐的圣诞。", + releaseDate: "2023-12-01", + status: "已发布", + activatedCount: 1032, + printedCount: 2000, + image: "/placeholder.svg?height=300&width=300", + batches: [{ id: "B004", date: "2023-11-15", quantity: 2000, startId: "OFT003-0001", endId: "OFT003-2000" }], + }, + OFT004: { + id: "OFT004", + name: "校园制服", + type: "常规", + rarity: "稀有", + description: "清新可爱的校园风格制服,展现洛天依青春活力的一面。", + releaseDate: "2024-01-15", + status: "已发布", + activatedCount: 1567, + printedCount: 3000, + image: "/placeholder.svg?height=300&width=300", + batches: [ + { id: "B005", date: "2024-01-05", quantity: 2000, startId: "OFT004-0001", endId: "OFT004-2000" }, + { id: "B006", date: "2024-02-10", quantity: 1000, startId: "OFT004-2001", endId: "OFT004-3000" }, + ], + }, + OFT005: { + id: "OFT005", + name: "演唱会礼服", + type: "特别版", + rarity: "传说", + description: "为重要演唱会设计的华丽礼服,尽显洛天依的优雅与魅力。", + releaseDate: "", + status: "未发布", + activatedCount: 0, + printedCount: 1000, + image: "/placeholder.svg?height=300&width=300", + batches: [{ id: "B007", date: "2024-03-20", quantity: 1000, startId: "OFT005-0001", endId: "OFT005-1000" }], + }, +} + +export default function EditOutfitPage({ params }: { params: { id: string } }) { + const outfit = outfitData[params.id as keyof typeof outfitData] + + if (!outfit) { + return ( + +
+ +

服装不存在

+

找不到ID为 {params.id} 的服装

+ +
+
+ ) + } + + const isPublished = outfit.status === "已发布" + + if (isPublished) { + return ( + +
+ +

无法编辑

+

该服装已发布,基本属性不可修改

+

您可以在详情页面增加印刷数量

+
+ + +
+
+
+ ) + } + + return ( + +
+ +
+ + +
+ +
+ + + 服装图片 + + +
+
+ {outfit.name} +
+ +
+ +

更换服装图片

+
+
+
+
+ + + + 服装信息 + 编辑服装的基本信息 + + +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +

印刷数量在批次管理中修改

+
+
+ +
+ +