├── public ├── favicon.ico ├── robots.txt ├── assets │ ├── morpho │ │ ├── banner │ │ │ ├── 1.webp │ │ │ ├── 2.webp │ │ │ └── 3.webp │ │ ├── app-icons │ │ │ ├── icon-180x180.png │ │ │ └── icon-32x32.png │ │ ├── icons │ │ │ └── cartzilla-icons.woff2 │ │ ├── fonts │ │ │ └── inter-variable-latin.woff2 │ │ └── js │ │ │ └── scroll-to-top.js │ └── common │ │ └── images │ │ ├── 3dian.png │ │ ├── logo.png │ │ ├── android.png │ │ ├── banner.png │ │ ├── default.jpg │ │ ├── iphone.png │ │ └── logo.svg ├── js │ └── filament │ │ └── forms │ │ └── components │ │ ├── textarea.js │ │ ├── tags-input.js │ │ └── key-value.js ├── .htaccess ├── web.config └── index.php ├── resources ├── sass │ └── app.scss ├── js │ ├── app.js │ └── bootstrap.js ├── views │ ├── email │ │ └── mail.blade.php │ ├── filament │ │ └── pages │ │ │ ├── login.blade.php │ │ │ └── manage-email-test.blade.php │ ├── themes │ │ └── morpho │ │ │ ├── theme.json │ │ │ └── views │ │ │ └── layouts │ │ │ ├── default.blade.php │ │ │ ├── _script.blade.php │ │ │ ├── _cart_dropdown.blade.php │ │ │ ├── seo.blade.php │ │ │ └── _footer.blade.php │ ├── admin │ │ └── pages │ │ │ └── manage-theme-settings.blade.php │ └── vendor │ │ └── geetest │ │ └── geetest.blade.php └── lang │ ├── zh_CN │ ├── articles.php │ ├── emailtpl.php │ ├── goods-group.php │ ├── extension.php │ ├── article.php │ ├── theme-setting.php │ ├── email-test.php │ ├── coupon.php │ ├── pay.php │ ├── remote-server.php │ ├── menu.php │ ├── global.php │ ├── luna.php │ ├── order.php │ ├── carmis.php │ ├── goods.php │ └── system-setting.php │ ├── zh_TW │ ├── articles.php │ ├── emailtpl.php │ ├── goods-group.php │ ├── menu.php │ ├── coupon.php │ ├── extension.php │ ├── article.php │ ├── theme-setting.php │ ├── email-test.php │ ├── pay.php │ ├── carmis.php │ ├── order.php │ ├── remote-server.php │ ├── luna.php │ ├── global.php │ ├── goods.php │ └── system-setting.php │ └── en │ ├── pagination.php │ ├── auth.php │ └── passwords.php ├── storage ├── framework │ ├── views │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── .gitignore ├── logs │ └── .gitignore └── app │ ├── public │ └── .gitignore │ └── .gitignore ├── bootstrap ├── cache │ └── .gitignore └── app.php ├── database ├── .gitignore ├── seeds │ ├── DatabaseSeeder.php │ └── OrderTableSeeder.php └── factories │ └── OrderFactory.php ├── .gitattributes ├── Dockerfile ├── docker-compose.yml ├── .gitignore ├── app ├── Admin │ ├── Resources │ │ ├── Payments │ │ │ └── Pages │ │ │ │ ├── CreatePay.php │ │ │ │ ├── ListPays.php │ │ │ │ └── EditPay.php │ │ ├── Servers │ │ │ └── Pages │ │ │ │ ├── CreateRemoteServer.php │ │ │ │ ├── EditRemoteServer.php │ │ │ │ └── ListRemoteServers.php │ │ ├── Cards │ │ │ └── Pages │ │ │ │ ├── CreateCarmis.php │ │ │ │ ├── EditCarmis.php │ │ │ │ └── ListCarmis.php │ │ ├── Orders │ │ │ └── Pages │ │ │ │ ├── CreateOrder.php │ │ │ │ ├── EditOrder.php │ │ │ │ └── ListOrders.php │ │ ├── Coupons │ │ │ └── Pages │ │ │ │ ├── CreateCoupon.php │ │ │ │ ├── EditCoupon.php │ │ │ │ └── ListCoupons.php │ │ ├── Products │ │ │ └── Pages │ │ │ │ ├── CreateGoods.php │ │ │ │ ├── EditGoods.php │ │ │ │ └── ListGoods.php │ │ ├── Categories │ │ │ └── Pages │ │ │ │ ├── CreateGoodsGroup.php │ │ │ │ ├── EditGoodsGroup.php │ │ │ │ └── ListGoodsGroups.php │ │ ├── ArticleAdmin │ │ │ └── Pages │ │ │ │ ├── CreateArticle.php │ │ │ │ ├── ViewArticle.php │ │ │ │ ├── ListArticles.php │ │ │ │ └── EditArticle.php │ │ ├── EmailTemplates │ │ │ └── Pages │ │ │ │ ├── CreateEmailtpl.php │ │ │ │ ├── ViewEmailtpl.php │ │ │ │ ├── ListEmailtpls.php │ │ │ │ └── EditEmailtpl.php │ │ ├── ArticleCategoryResource │ │ │ └── Pages │ │ │ │ ├── CreateArticleCategory.php │ │ │ │ ├── ViewArticleCategory.php │ │ │ │ ├── ListArticleCategories.php │ │ │ │ └── EditArticleCategory.php │ │ ├── Users │ │ │ └── Pages │ │ │ │ ├── CreateUser.php │ │ │ │ ├── EditUser.php │ │ │ │ └── ListUsers.php │ │ ├── UserLevels │ │ │ └── Pages │ │ │ │ ├── CreateUserLevel.php │ │ │ │ ├── ListUserLevels.php │ │ │ │ └── EditUserLevel.php │ │ ├── Roles │ │ │ └── Pages │ │ │ │ ├── ListRoles.php │ │ │ │ ├── CreateRole.php │ │ │ │ └── EditRole.php │ │ └── AdminUsers │ │ │ └── Pages │ │ │ ├── ListAdminUsers.php │ │ │ ├── CreateAdminUser.php │ │ │ └── EditAdminUser.php │ ├── Widgets │ │ ├── CustomFilamentInfoWidget.php │ │ └── SalesOverviewWidget.php │ └── Pages │ │ └── Login.php ├── Exceptions │ ├── AppException.php │ ├── RuleValidationException.php │ └── Handler.php ├── Services │ ├── Contracts │ │ ├── PaymentServiceInterface.php │ │ └── OrderServiceInterface.php │ ├── Coupons.php │ ├── Cards.php │ ├── ThemeService.php │ ├── Payment.php │ ├── ConfigService.php │ └── Email.php ├── Http │ ├── Middleware │ │ ├── EncryptCookies.php │ │ ├── CheckForMaintenanceMode.php │ │ ├── TrimStrings.php │ │ ├── PayGateWay.php │ │ ├── Authenticate.php │ │ ├── VerifyCsrfToken.php │ │ ├── InstallCheck.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrustProxies.php │ │ ├── DujiaoBoot.php │ │ └── Challenge.php │ └── Controllers │ │ ├── Controller.php │ │ ├── BaseController.php │ │ ├── Home │ │ ├── ArticleController.php │ │ └── CartController.php │ │ └── InstallController.php ├── Providers │ ├── SimpleThemeServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── AuthServiceProvider.php │ ├── ConfigServiceProvider.php │ ├── EventServiceProvider.php │ ├── RouteServiceProvider.php │ ├── PaymentServiceProvider.php │ └── AppServiceProvider.php ├── Settings │ ├── ThemeSettings.php │ ├── MailSettings.php │ ├── ShopSettings.php │ └── SystemSettings.php ├── Models │ ├── Emailtpl.php │ ├── GoodsGroup.php │ ├── BaseModel.php │ ├── ArticleCategory.php │ ├── RemoteServer.php │ ├── Coupon.php │ ├── OrderItem.php │ ├── GoodsSub.php │ ├── Carmis.php │ ├── AdminUser.php │ ├── Pay.php │ └── Articles.php ├── Listeners │ ├── GoodsDeleted.php │ └── GoodsGroupDeleted.php ├── Rules │ ├── SearchPwd.php │ └── VerifyImg.php ├── Events │ ├── GoodsDeleted.php │ ├── OrderUpdated.php │ └── GoodsGroupDeleted.php ├── Helpers │ └── CdnHelper.php ├── Console │ └── Kernel.php ├── Jobs │ ├── CouponBack.php │ ├── OrderExpired.php │ ├── ServerJiang.php │ ├── ApiHook.php │ ├── MailSend.php │ ├── TelegramPush.php │ └── BarkPush.php ├── PaymentGateways │ └── Contracts │ │ └── PaymentDriverInterface.php └── Config │ └── ConfigManager.php ├── config ├── settings.php ├── dujiaoka.php ├── services.php ├── view.php ├── hashing.php ├── broadcasting.php └── filesystems.php ├── routes ├── common │ └── pay.php ├── channels.php ├── console.php ├── web.php └── api.php ├── server.php ├── .env.default ├── index.html ├── package.json ├── LICENSE ├── phpunit.xml ├── artisan └── composer.json /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/sass/app.scss: -------------------------------------------------------------------------------- 1 | // 2 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | require('./bootstrap'); 2 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | *.sqlite-journal 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /resources/views/email/mail.blade.php: -------------------------------------------------------------------------------- 1 | {!! $body !!} 2 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/articles.php: -------------------------------------------------------------------------------- 1 | '文章链接' 4 | ]; -------------------------------------------------------------------------------- /resources/lang/zh_TW/articles.php: -------------------------------------------------------------------------------- 1 | '文章連結' 4 | ]; -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | 4 | Allow: /$ 5 | Allow: /buy 6 | Allow: /article -------------------------------------------------------------------------------- /resources/views/filament/pages/login.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/morpho/banner/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/morpho/banner/1.webp -------------------------------------------------------------------------------- /public/assets/morpho/banner/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/morpho/banner/2.webp -------------------------------------------------------------------------------- /public/assets/morpho/banner/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/morpho/banner/3.webp -------------------------------------------------------------------------------- /public/assets/common/images/3dian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/common/images/3dian.png -------------------------------------------------------------------------------- /public/assets/common/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/common/images/logo.png -------------------------------------------------------------------------------- /public/assets/common/images/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/common/images/android.png -------------------------------------------------------------------------------- /public/assets/common/images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/common/images/banner.png -------------------------------------------------------------------------------- /public/assets/common/images/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/common/images/default.jpg -------------------------------------------------------------------------------- /public/assets/common/images/iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/common/images/iphone.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /public/assets/morpho/app-icons/icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/morpho/app-icons/icon-180x180.png -------------------------------------------------------------------------------- /public/assets/morpho/app-icons/icon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/morpho/app-icons/icon-32x32.png -------------------------------------------------------------------------------- /public/assets/morpho/icons/cartzilla-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/morpho/icons/cartzilla-icons.woff2 -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down -------------------------------------------------------------------------------- /public/assets/morpho/fonts/inter-variable-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiouttime/dujiaoka/HEAD/public/assets/morpho/fonts/inter-variable-latin.woff2 -------------------------------------------------------------------------------- /resources/views/themes/morpho/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "morpho", 3 | "display_name": "morpho 霓虹主题", 4 | "version": "2.0.0", 5 | "author": "Riniba" 6 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM webdevops/php-nginx:7.4 2 | COPY . /app 3 | WORKDIR /app 4 | RUN [ "sh", "-c", "composer install --ignore-platform-reqs" ] 5 | RUN [ "sh", "-c", "chmod -R 777 /app" ] 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.2" 2 | services: 3 | web: 4 | build: . 5 | ports: 6 | - "80:80" 7 | - "9000:9000" 8 | environment: 9 | WEB_DOCUMENT_ROOT: "/app/public" 10 | tty: true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /public/hot 2 | /public/storage 3 | /public/uploads 4 | public/vendor/dcat-admin-extensions 5 | /storage/*.key 6 | /vendor 7 | /dcat-admin-extensions 8 | install.lock 9 | .env 10 | .user.ini 11 | .htaccess 12 | .DS_Store 13 | -------------------------------------------------------------------------------- /app/Admin/Resources/Payments/Pages/CreatePay.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{ $this->form }} 4 | 5 |
6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /app/Admin/Resources/Servers/Pages/CreateRemoteServer.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Emailtpl' => '邮件模板', 6 | 'emailtpl' => '邮件模板', 7 | ], 8 | 'fields' => [ 9 | 'tpl_name' => '邮件标题', 10 | 'tpl_content' => '邮件内容', 11 | 'tpl_token' => '邮件标识', 12 | ], 13 | 'options' => [ 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/goods-group.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'GoodsGroup' => '分类', 6 | 'goods-group' => '分类', 7 | ], 8 | 'fields' => [ 9 | 'gp_name' => '分类名称', 10 | 'is_open' => '是否启用', 11 | 'ord' => '排序权重 越大越靠前', 12 | ], 13 | 'options' => [ 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/emailtpl.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Emailtpl' => '郵件模板', 6 | 'emailtpl' => '郵件模板', 7 | ], 8 | 'fields' => [ 9 | 'tpl_name' => '郵件標題', 10 | 'tpl_content' => '郵件內容', 11 | 'tpl_token' => '郵件標識', 12 | ], 13 | 'options' => [ 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/goods-group.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'GoodsGroup' => '分類', 6 | 'goods-group' => '分類', 7 | ], 8 | 'fields' => [ 9 | 'gp_name' => '分類名稱', 10 | 'is_open' => '是否啟用', 11 | 'ord' => '排序權重 越大越靠前', 12 | ], 13 | 'options' => [ 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /resources/views/admin/pages/manage-theme-settings.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ $this->form }} 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/Admin/Resources/Coupons/Pages/CreateCoupon.php: -------------------------------------------------------------------------------- 1 | call(OrderTableSeeder::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Admin/Resources/Products/Pages/CreateGoods.php: -------------------------------------------------------------------------------- 1 | create(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config/settings.php: -------------------------------------------------------------------------------- 1 | 'database', 5 | 6 | 'repositories' => [ 7 | 'database' => [ 8 | 'type' => Spatie\LaravelSettings\SettingsRepositories\DatabaseSettingsRepository::class, 9 | 'connection' => null, 10 | 'table' => 'settings', 11 | ], 12 | ], 13 | ]; -------------------------------------------------------------------------------- /app/Admin/Resources/Categories/Pages/CreateGoodsGroup.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 15 | } 16 | } -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | app->singleton(ThemeService::class); 13 | } 14 | 15 | public function boot() 16 | { 17 | $this->app->make(ThemeService::class); 18 | } 19 | } -------------------------------------------------------------------------------- /app/Settings/ThemeSettings.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 15 | } 16 | } -------------------------------------------------------------------------------- /app/Admin/Resources/Roles/Pages/ListRoles.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'index' => '首頁', 6 | 'admin' => '系統', 7 | 'users' => '管理員', 8 | 'roles' => '角色', 9 | 'permission' => '權限', 10 | 'menu' => '菜單', 11 | 'operation_log' => '操作日誌', 12 | 'helpers' => '開發工具', 13 | 'extensions' => '擴展', 14 | 'scaffold' => '代碼生成器', 15 | 'icons' => '圖示', 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /app/Admin/Resources/AdminUsers/Pages/ListAdminUsers.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @include('morpho::layouts._header') 5 | 6 | 7 |
8 | @include('morpho::layouts._nav') 9 | @yield('content') 10 | @include('morpho::layouts._footer') 11 | 12 | @include('morpho::layouts._script') 13 | @include('morpho::layouts._cart_dropdown') 14 | @section('js') 15 | @show 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/Admin/Resources/ArticleAdmin/Pages/EditArticle.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright assimon 7 | * @link http://utf8.hk/ 8 | */ 9 | use Illuminate\Support\Facades\Route; 10 | 11 | // 支付网关入口 12 | Route::get('pay/{driver}/{payway}/{orderSN}', 'UnifiedPaymentController@gateway') 13 | ->middleware('dujiaoka.pay_gate_way'); 14 | 15 | // 支付回调统一入口 16 | Route::post('pay/{driver}/notify', 'UnifiedPaymentController@notify'); 17 | Route::get('pay/{driver}/return', 'UnifiedPaymentController@return'); 18 | -------------------------------------------------------------------------------- /app/Admin/Resources/ArticleCategoryResource/Pages/ViewArticleCategory.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Coupon' => '折扣碼', 6 | 'coupon' => '折扣碼', 7 | ], 8 | 'fields' => [ 9 | 'type' => '折扣類型', 10 | 'discount' => '折扣金額/系数', 11 | 'is_open' => '是否啟用', 12 | 'coupon' => '折扣碼', 13 | 'ret' => '剩余使用次數', 14 | 'type_one_time' => '一次性使用', 15 | 'type_repeat' => '重復使用', 16 | 'type_percent' => '系数折扣 (0-1)', 17 | 'type_fixed' => '固定金额', 18 | 'goods_id' => '可用商品' 19 | ], 20 | 'options' => [ 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 16 | }); 17 | -------------------------------------------------------------------------------- /app/Admin/Resources/ArticleCategoryResource/Pages/ListArticleCategories.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Admin/Resources/Payments/Pages/EditPay.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Extensions' => '扩展', 6 | ], 7 | 'fields' => [ 8 | 'alias' => '别名', 9 | 'description' => '描述', 10 | 'authors' => '开发者', 11 | 'homepage' => '主页', 12 | 'require' => '依赖', 13 | 'require_dev' => '开发环境依赖', 14 | 'name' => '包名', 15 | 'version' => '版本', 16 | 'enable' => '启用', 17 | 'config' => '配置', 18 | 'imported' => '导入', 19 | ], 20 | 'options' => [ 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/extension.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Extensions' => '擴展', 6 | ], 7 | 'fields' => [ 8 | 'alias' => '別名', 9 | 'description' => '說明', 10 | 'authors' => '作者', 11 | 'homepage' => '首頁', 12 | 'require' => '依賴', 13 | 'require_dev' => '開發環境依賴', 14 | 'name' => '名稱', 15 | 'version' => '版本', 16 | 'enable' => '啟用', 17 | 'config' => '設定', 18 | 'imported' => '導入', 19 | ], 20 | 'options' => [ 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /config/dujiaoka.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright assimon 7 | * @link http://utf8.hk/ 8 | */ 9 | 10 | return [ 11 | 'dujiaoka_version' => 'v3.0.0', 12 | // 模板集合 13 | 'templates' => [ 14 | 'morpho' => 'morpho[Author: @Riniba]', 15 | ], 16 | // 语言 17 | 'language' => [ 18 | 'zh_CN' => '简体中文', 19 | 'zh_TW' => '繁体中文', 20 | ], 21 | // 货币 22 | 'currencies' => [ 23 | 'cny' => '人民币', 24 | 'usd' => '美元', 25 | ] 26 | ]; 27 | -------------------------------------------------------------------------------- /app/Services/Contracts/OrderServiceInterface.php: -------------------------------------------------------------------------------- 1 | '文章', 4 | 'total' => '文章列表', 5 | 'newest' => '最新文章', 6 | 'recommend' => '推薦文章', 7 | 'more' => '更多', 8 | 'labels' => [ 9 | 'Article' => '文章管理', 10 | 'article' => '文章管理', 11 | ], 12 | 'fields' => [ 13 | 'link' => '文章連結', 14 | 'title' => '文章標題', 15 | 'content' => '文章內容', 16 | 'category' => '文章分類' 17 | ], 18 | 'options' => [ 19 | ], 20 | 'helps' => [ 21 | 'link' => '設置訪問文章的連結,如果留空則會自動生成。', 22 | 'category' => '商品將能夠指派相同分類的文章,顯示在商品詳細頁。' 23 | ] 24 | ]; -------------------------------------------------------------------------------- /app/Admin/Resources/ArticleCategoryResource/Pages/EditArticleCategory.php: -------------------------------------------------------------------------------- 1 | '文章', 4 | 'total' => '文章列表', 5 | 'newest' => '最新文章', 6 | 'recommend' => '推荐文章', 7 | 'more' => '更多', 8 | 'labels' => [ 9 | 'Article' => '文章管理', 10 | 'article' => '文章管理', 11 | ], 12 | 'fields' => [ 13 | 'link' => '文章链接', 14 | 'title' => '文章标题', 15 | 'content' => '文章内容', 16 | 'category' => '文章分类' 17 | ], 18 | 'options' => [ 19 | ], 20 | 'helps' => [ 21 | 'link' => '设置访问文章的链接,如果留空则会自动生成。', 22 | 'category' => '商品将能够指派相同分类的文章,显示在商品详细页。' 23 | ] 24 | ]; 25 | -------------------------------------------------------------------------------- /resources/views/themes/morpho/views/layouts/_script.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{-- 页脚自定义代码 --}} 9 | @if(!empty(app(\App\Settings\ShopSettings::class)->footer)) 10 | {!! app(\App\Settings\ShopSettings::class)->footer !!} 11 | @endif 12 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/theme-setting.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright outtime 7 | * @link https://outti.me 8 | */ 9 | 10 | return [ 11 | 'labels' => [ 12 | 'ThemeSetting' => '主題設置', 13 | 'theme_setting' => '主題設置', 14 | ], 15 | 'fields' => [ 16 | 'notice' => '頂部輪播公告', 17 | 'invert_logo' => '圖片Logo顏色填充反轉' 18 | ], 19 | 'helps' => [ 20 | 'notice' => '站點頂部輪播公告,一行一個;留空則不顯示。', 21 | 'invert_logo' => '使用 filter:brightness 來使Logo適配亮/暗色模式。' 22 | ], 23 | ]; -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 18 | })->describe('Display an inspiring quote'); 19 | -------------------------------------------------------------------------------- /app/Admin/Resources/UserLevels/Pages/EditUserLevel.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 23 | } 24 | } -------------------------------------------------------------------------------- /resources/lang/zh_CN/theme-setting.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright outtime 7 | * @link https://outti.me 8 | */ 9 | 10 | return [ 11 | 'labels' => [ 12 | 'ThemeSetting' => '主题设置', 13 | 'theme_setting' => '主题设置', 14 | ], 15 | 'fields' => [ 16 | 'notice' => '顶部轮播公告', 17 | 'invert_logo' => 'Logo适配模式' 18 | ], 19 | 'helps' => [ 20 | 'notice' => '站点顶部轮播公告,一行一个;留空则不显示。', 21 | 'invert_logo' => '开启后,浅色模式下保持Logo原始亮度,深色模式下不应用滤镜。适用于深色Logo在不同主题下的显示适配。' 22 | ], 23 | ]; -------------------------------------------------------------------------------- /app/Settings/MailSettings.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright ZhangYiQiu 7 | * @link http://zhangyiqiu.net/ 8 | */ 9 | 10 | return [ 11 | 'labels' => [ 12 | 'EmailTest' => '郵件測試', 13 | 'email-test' => '郵件測試', 14 | 'success' => '發送成功', 15 | 'to' => '收件人', 16 | 'title' => '郵件標題', 17 | 'body' => '郵件內容', 18 | 'send_test_email' => '發送測試郵件', 19 | ], 20 | 'fields' => [ 21 | 22 | ], 23 | 'options' => [ 24 | ], 25 | 'rule_messages' => [ 26 | ] 27 | ]; -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/email-test.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright ZhangYiQiu 7 | * @link http://zhangyiqiu.net/ 8 | */ 9 | 10 | return [ 11 | 'labels' => [ 12 | 'EmailTest' => '邮件测试', 13 | 'email-test' => '邮件测试', 14 | 'success' => '发送成功', 15 | 'to' => '收件人', 16 | 'title' => '邮件标题', 17 | 'body' => '邮件内容', 18 | 'send_test_email' => '发送测试邮件', 19 | ], 20 | 'fields' => [ 21 | 22 | ], 23 | 'options' => [ 24 | ], 25 | 'rule_messages' => [ 26 | ] 27 | ]; 28 | -------------------------------------------------------------------------------- /app/Admin/Resources/Users/Pages/EditUser.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 24 | } 25 | } -------------------------------------------------------------------------------- /app/Http/Middleware/InstallCheck.php: -------------------------------------------------------------------------------- 1 | {this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /.env.default: -------------------------------------------------------------------------------- 1 | APP_NAME=独角数卡 2 | APP_ENV=local 3 | APP_KEY=base64:5w/wNQ0mfhDgd6xzUktH2RRh/yedU0HV0puVCjJN26o= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | LOG_CHANNEL=stack 8 | 9 | DB_CONNECTION=mysql 10 | DB_HOST=127.0.0.1 11 | DB_PORT=3306 12 | DB_DATABASE=laravel 13 | DB_USERNAME=root 14 | DB_PASSWORD= 15 | 16 | REDIS_HOST=127.0.0.1 17 | REDIS_PASSWORD=null 18 | REDIS_PORT=6379 19 | 20 | BROADCAST_DRIVER=log 21 | SESSION_DRIVER=file 22 | SESSION_LIFETIME=120 23 | 24 | CACHE_DRIVER=file 25 | 26 | QUEUE_CONNECTION=sync 27 | 28 | # 后台语言 29 | ## zh_CN 简体中文 30 | ## zh_TW 繁体中文 31 | ## en 英文 32 | DUJIAO_ADMIN_LANGUAGE=zh_CN 33 | 34 | # 后台登录地址 35 | ADMIN_ROUTE_PREFIX=/admin 36 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | ['auth:api'],'namespace' => 'Api'], function () { 17 | Route::get('/user', function (Request $request) { 18 | return $request->user(); 19 | }); 20 | }); -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Handle Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/coupon.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Coupon' => '优惠码', 6 | 'coupon' => '优惠码', 7 | ], 8 | 'fields' => [ 9 | 'type' => '优惠类型', 10 | 'discount' => '优惠金额/系数', 11 | 'is_open' => '是否启用', 12 | 'coupon' => '优惠码', 13 | 'ret' => '剩余使用次数', 14 | 'type_one_time' => '一次性使用', 15 | 'type_repeat' => '重复使用', 16 | 'type_percent' => '系数折扣 (0-1)', 17 | 'type_fixed' => '整体固定金额', 18 | 'type_each' => '每件固定金额', 19 | 'goods_id' => '可用商品' 20 | ], 21 | 'options' => [ 22 | ], 23 | 'helps' =>[ 24 | 'discount' => '系数折扣:价格在下单时会乘以这个数字,0.9就是九折。固定金额:下单时价格会直接减去。' 25 | ] 26 | ]; 27 | -------------------------------------------------------------------------------- /app/Admin/Resources/Roles/Pages/CreateRole.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 16 | } 17 | 18 | protected function getCreatedNotification(): ?Notification 19 | { 20 | return Notification::make() 21 | ->success() 22 | ->title('角色创建成功') 23 | ->body('新的角色已经创建。'); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/Models/Emailtpl.php: -------------------------------------------------------------------------------- 1 | tpl_token); 22 | }); 23 | 24 | static::deleted(function ($emailtpl) { 25 | CacheManager::forgetEmailTemplate($emailtpl->tpl_token); 26 | }); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 22 | return redirect(RouteServiceProvider::HOME); 23 | } 24 | 25 | return $next($request); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->registerPolicies(); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Admin/Resources/AdminUsers/Pages/CreateAdminUser.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 16 | } 17 | 18 | protected function getCreatedNotification(): ?Notification 19 | { 20 | return Notification::make() 21 | ->success() 22 | ->title('管理员创建成功') 23 | ->body('新的管理员账户已经创建。'); 24 | } 25 | } -------------------------------------------------------------------------------- /app/Listeners/GoodsDeleted.php: -------------------------------------------------------------------------------- 1 | where('goods_id', $event->goods->id)->delete(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Models/GoodsGroup.php: -------------------------------------------------------------------------------- 1 | GoodsGroupDeleted::class 24 | ]; 25 | 26 | /** 27 | * 关联商品 28 | * 29 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 30 | * 31 | */ 32 | public function goods() 33 | { 34 | return $this->hasMany(Goods::class, 'group_id'); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | where('group_id', $event->goodsGroup->id)->delete(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Models/BaseModel.php: -------------------------------------------------------------------------------- 1 | __('dujiaoka.status_open'), 31 | self::STATUS_CLOSE => __('dujiaoka.status_close') 32 | ]; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/Models/ArticleCategory.php: -------------------------------------------------------------------------------- 1 | 'boolean', 19 | 'sort' => 'integer', 20 | ]; 21 | 22 | /** 23 | * 关联文章 24 | */ 25 | public function articles() 26 | { 27 | return $this->hasMany(Articles::class, 'category_id'); 28 | } 29 | 30 | /** 31 | * 获取启用的分类 32 | */ 33 | public static function active() 34 | { 35 | return self::where('is_active', true)->orderBy('sort', 'desc')->orderBy('id', 'asc'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 17 | 'sent' => 'We have e-mailed your password reset link!', 18 | 'throttled' => 'Please wait before retrying.', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /app/Settings/ShopSettings.php: -------------------------------------------------------------------------------- 1 | e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; 2 | -------------------------------------------------------------------------------- /resources/views/themes/morpho/views/layouts/_cart_dropdown.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/Admin/Resources/Roles/Pages/EditRole.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 24 | } 25 | 26 | protected function getSavedNotification(): ?Notification 27 | { 28 | return Notification::make() 29 | ->success() 30 | ->title('角色更新成功') 31 | ->body('角色信息已经更新。'); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /resources/lang/zh_TW/pay.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Pay' => '支付通道', 6 | 'pay' => '支付通道', 7 | ], 8 | 'fields' => [ 9 | 'merchant_id' => '商戶 ID', 10 | 'merchant_key' => '商戶 KEY', 11 | 'merchant_pem' => '商戶金鑰', 12 | 'pay_check' => '支付標識', 13 | 'pay_client' => '支付場景', 14 | 'pay_handleroute' => '支付處理模組', 15 | 'pay_method' => '支付方式', 16 | 'pay_name' => '支付名稱', 17 | 'is_open' => '是否啟用', 18 | 'enable' => '是否啟用', 19 | 'pay_fee' => '通道費率', 20 | 'merchant_key_64' => '商戶密鑰(Base64)', 21 | 'china_only' => '僅允許中國大陸下單', 22 | 'method_jump' => '跳躍', 23 | 'method_scan' => '掃碼', 24 | 'pay_client_pc' => '計算機PC', 25 | 'pay_client_mobile' => '行動電話', 26 | 'pay_client_all' => '通用', 27 | ], 28 | 'options' => [ 29 | ], 30 | ]; 31 | -------------------------------------------------------------------------------- /app/Admin/Resources/AdminUsers/Pages/EditAdminUser.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 24 | } 25 | 26 | protected function getSavedNotification(): ?Notification 27 | { 28 | return Notification::make() 29 | ->success() 30 | ->title('管理员更新成功') 31 | ->body('管理员信息已经更新。'); 32 | } 33 | } -------------------------------------------------------------------------------- /app/Models/RemoteServer.php: -------------------------------------------------------------------------------- 1 | 23 | * @copyright outtime 24 | * @link https://outti.me 25 | */ 26 | public static function getServerTypeMap() 27 | { 28 | return [ 29 | self::HTTP_SERVER => __('remote-server.types.http_server'), 30 | self::RCON_COMMAND => __('remote-server.types.rcon_command'), 31 | self::SQL_EXECUTE => __('remote-server.types.sql_execute'), 32 | ]; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/carmis.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Carmis' => '卡密', 6 | 'carmis' => '卡密', 7 | ], 8 | 'fields' => [ 9 | 'goods_id' => '所屬商品', 10 | 'status' => '狀態', 11 | 'carmi' => '卡密內容', 12 | 'status_unsold' => '未售出', 13 | 'status_sold' => '已售出', 14 | 'is_loop' => '循環卡密', 15 | 'yes'=>'是', 16 | 'import_carmis' => '匯入卡密', 17 | 'carmis_list' => '卡密清單', 18 | 'carmis_txt' => '卡密文字', 19 | 'are_you_import_sure' => '確定要匯入卡密嗎?', 20 | 'remove_duplication' => '是否去重', 21 | ], 22 | 'options' => [ 23 | ], 24 | 'helps' => [ 25 | 'carmis_list' => '一行一個,輸入鍵分隔。請勿匯入單個文字長度過大的卡密,容易導致記憶體溢出。如果卡密過大建議修改商品為人工處理' 26 | ], 27 | 'rule_messages' => [ 28 | 'carmis_list_and_carmis_txt_can_not_be_empty' => '請填寫需要匯入的卡密或選取需要上傳的卡密檔案', 29 | 'import_carmis_success' => '匯入卡密成功!' 30 | ] 31 | ]; 32 | -------------------------------------------------------------------------------- /app/Http/Middleware/DujiaoBoot.php: -------------------------------------------------------------------------------- 1 | setLocale($lang); 30 | } catch (\Exception $e) { 31 | app()->setLocale('zh_CN'); 32 | } 33 | 34 | return $next($request); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 恭喜,站点创建成功! 6 | 27 | 28 | 29 |
30 |

恭喜, 站点创建成功!

31 |

这是默认index.html,本页面由系统自动生成

32 |
    33 |
  • 本页面在FTP根目录下的index.html
  • 34 |
  • 您可以修改、删除或覆盖本页面
  • 35 |
  • FTP相关信息,请到“面板系统后台 > FTP” 查看
  • 36 |
37 |
38 | 39 | -------------------------------------------------------------------------------- /app/Models/Coupon.php: -------------------------------------------------------------------------------- 1 | belongsToMany(Goods::class, 'coupons_goods', 'coupons_id', 'goods_id'); 27 | } 28 | 29 | 30 | public static function getTypeMap() 31 | { 32 | return [ 33 | self::TYPE_PERCENT => __('coupon.fields.type_percent'), 34 | self::TYPE_FIXED => __('coupon.fields.type_fixed'), 35 | self::TYPE_EACH => __('coupon.fields.type_each'), 36 | ]; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | window._ = require('lodash'); 2 | 3 | /** 4 | * We'll load the axios HTTP library which allows us to easily issue requests 5 | * to our Laravel back-end. This library automatically handles sending the 6 | * CSRF token as a header based on the value of the "XSRF" token cookie. 7 | */ 8 | 9 | window.axios = require('axios'); 10 | 11 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 12 | 13 | /** 14 | * Echo exposes an expressive API for subscribing to channels and listening 15 | * for events that are broadcast by Laravel. Echo and event broadcasting 16 | * allows your team to easily build robust real-time web applications. 17 | */ 18 | 19 | // import Echo from 'laravel-echo'; 20 | 21 | // window.Pusher = require('pusher-js'); 22 | 23 | // window.Echo = new Echo({ 24 | // broadcaster: 'pusher', 25 | // key: process.env.MIX_PUSHER_APP_KEY, 26 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER, 27 | // forceTLS: true 28 | // }); 29 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/pay.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Pay' => '支付通道', 6 | 'pay' => '支付通道', 7 | ], 8 | 'fields' => [ 9 | 'merchant_id' => '商户 ID', 10 | 'merchant_key' => '商户 KEY', 11 | 'merchant_pem' => '商户密钥', 12 | 'merchant_key_64' => '商户密钥(Base64)', 13 | 'pay_check' => '支付标识', 14 | 'pay_client' => '支付场景', 15 | 'pay_fee' => '通道费率', 16 | 'pay_handleroute' => '支付处理模块', 17 | 'pay_method' => '支付方式', 18 | 'pay_name' => '支付名称', 19 | 'china_only' => '仅允许中国大陆下单', 20 | 'is_open' => '是否启用', 21 | 'enable' => '是否启用', 22 | 'method_jump' => '跳转', 23 | 'method_scan' => '扫码', 24 | 'pay_client_pc' => '电脑PC', 25 | 'pay_client_mobile' => '手机', 26 | 'pay_client_all' => '通用', 27 | ], 28 | 'options' => [ 29 | ], 30 | 'helps' =>[ 31 | 'pay_fee' => '单位百分比。如填写0.38,则代表0.38%的通道手续费,下单时价格会自动加上这笔手续费。' 32 | ] 33 | ]; 34 | -------------------------------------------------------------------------------- /app/Providers/ConfigServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(ConfigManager::class, function ($app) { 13 | return new ConfigManager(); 14 | }); 15 | 16 | $this->app->alias(ConfigManager::class, 'config.manager'); 17 | } 18 | 19 | public function boot() 20 | { 21 | if (!function_exists('config_get')) { 22 | function config_get(string $key, $default = null) 23 | { 24 | return app('config.manager')->get($key, $default); 25 | } 26 | } 27 | 28 | if (!function_exists('config_set')) { 29 | function config_set(string $key, $value): void 30 | { 31 | app('config.manager')->set($key, $value); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/Rules/SearchPwd.php: -------------------------------------------------------------------------------- 1 | {let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /app/Events/GoodsDeleted.php: -------------------------------------------------------------------------------- 1 | goods = $goods; 28 | } 29 | 30 | /** 31 | * Get the channels the event should broadcast on. 32 | * 33 | * @return \Illuminate\Broadcasting\Channel|array 34 | */ 35 | public function broadcastOn() 36 | { 37 | return new PrivateChannel('channel-name'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Events/OrderUpdated.php: -------------------------------------------------------------------------------- 1 | order = $order; 28 | } 29 | 30 | /** 31 | * Get the channels the event should broadcast on. 32 | * 33 | * @return \Illuminate\Broadcasting\Channel|array 34 | */ 35 | public function broadcastOn() 36 | { 37 | return new PrivateChannel('channel-name'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Helpers/CdnHelper.php: -------------------------------------------------------------------------------- 1 | cdn_url; 18 | 19 | // 如果没有配置CDN URL,直接返回原URL 20 | if (empty($cdnUrl)) { 21 | return $url; 22 | } 23 | 24 | // 移除CDN URL末尾的斜杠 25 | $cdnUrl = rtrim($cdnUrl, '/'); 26 | 27 | // 如果是绝对URL且是本站资源,替换域名部分 28 | if (str_starts_with($url, request()->getSchemeAndHttpHost())) { 29 | return str_replace(request()->getSchemeAndHttpHost(), $cdnUrl, $url); 30 | } 31 | 32 | // 如果是相对URL,添加CDN域名 33 | if (str_starts_with($url, '/')) { 34 | return $cdnUrl . $url; 35 | } 36 | 37 | // 其他情况直接返回原URL 38 | return $url; 39 | } 40 | } -------------------------------------------------------------------------------- /resources/views/themes/morpho/views/layouts/seo.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @include('morpho::layouts._header') 5 | @if(isset($gd_keywords)) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | @endif 14 | 15 | 16 |
17 | @include('morpho::layouts._nav') 18 | @yield('content') 19 | @include('morpho::layouts._footer') 20 | 21 | @include('morpho::layouts._script') 22 | @include('morpho::layouts._cart_dropdown') 23 | @section('js') 24 | @show 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 28 | // ->hourly(); 29 | } 30 | 31 | /** 32 | * Register the commands for the application. 33 | * 34 | * @return void 35 | */ 36 | protected function commands() 37 | { 38 | $this->load(__DIR__.'/Commands'); 39 | 40 | require base_path('routes/console.php'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "npm run development -- --watch", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "axios": "^0.19", 14 | "cross-env": "^7.0", 15 | "laravel-mix": "^5.0.1", 16 | "lodash": "^4.17.19", 17 | "resolve-url-loader": "^3.1.0", 18 | "sass": "^1.15.2", 19 | "sass-loader": "^8.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Events/GoodsGroupDeleted.php: -------------------------------------------------------------------------------- 1 | goodsGroup = $goodsGroup; 28 | } 29 | 30 | /** 31 | * Get the channels the event should broadcast on. 32 | * 33 | * @return \Illuminate\Broadcasting\Channel|array 34 | */ 35 | public function broadcastOn() 36 | { 37 | return new PrivateChannel('channel-name'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Models/OrderItem.php: -------------------------------------------------------------------------------- 1 | 'decimal:2', 26 | 'subtotal' => 'decimal:2', 27 | 'quantity' => 'integer', 28 | 'type' => 'integer', 29 | ]; 30 | 31 | public function order(): BelongsTo 32 | { 33 | return $this->belongsTo(Order::class); 34 | } 35 | 36 | public function goods(): BelongsTo 37 | { 38 | return $this->belongsTo(Goods::class); 39 | } 40 | 41 | public function goodsSub(): BelongsTo 42 | { 43 | return $this->belongsTo(GoodsSub::class, 'sub_id'); 44 | } 45 | } -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | ], 22 | 23 | 'postmark' => [ 24 | 'token' => env('POSTMARK_TOKEN'), 25 | ], 26 | 27 | 'ses' => [ 28 | 'key' => env('AWS_ACCESS_KEY_ID'), 29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 31 | ], 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /app/Models/GoodsSub.php: -------------------------------------------------------------------------------- 1 | belongsTo(Goods::class); 25 | } 26 | 27 | /** 28 | * 自动发货自动计算库存 29 | * 30 | * @author outtime 31 | * @copyright outtime 32 | * @link https://outti.me 33 | */ 34 | public function getStockAttribute() 35 | { 36 | 37 | if ($this->goods->type == self::AUTOMATIC_DELIVERY) { 38 | return Carmis::where('sub_id', $this->id) 39 | ->where('status', Carmis::STATUS_UNSOLD) 40 | ->count(); 41 | } 42 | return $this->attributes['stock']; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 assimon/dujiaoka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /app/Admin/Pages/Login.php: -------------------------------------------------------------------------------- 1 | schema([ 16 | TextInput::make('username') 17 | ->label('用户名') 18 | ->required() 19 | ->autocomplete('username'), 20 | 21 | $this->getPasswordFormComponent(), 22 | $this->getRememberFormComponent(), 23 | ]) 24 | ->statePath('data'); 25 | } 26 | 27 | protected function getCredentialsFromFormData(array $data): array 28 | { 29 | return [ 30 | 'username' => $data['username'], 31 | 'password' => $data['password'], 32 | ]; 33 | } 34 | 35 | protected function throwFailureValidationException(): never 36 | { 37 | throw ValidationException::withMessages([ 38 | 'data.username' => '登录信息有误。', 39 | ]); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/order.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Order' => '訂單', 6 | 'order' => '訂單', 7 | ], 8 | 'fields' => [ 9 | 'actual_price' => '實際支付價格', 10 | 'buy_amount' => '購買數量', 11 | 'buy_ip' => '購買者下單IP地址', 12 | 'coupon_discount_price' => '折扣碼折扣價格', 13 | 'coupon_id' => '折扣碼', 14 | 'email' => '下單信箱', 15 | 'goods_id' => '所屬商品', 16 | 'goods_price' => '商品單價', 17 | 'info' => '訂單詳情', 18 | 'order_sn' => '訂單號', 19 | 'pay_id' => '支付通道', 20 | 'status' => '訂單狀態', 21 | 'search_pwd' => '查詢密碼', 22 | 'title' => '訂單名稱', 23 | 'total_price' => '訂單總價', 24 | 'trade_no' => '第三方支付訂單號', 25 | 'type' => '訂單類型', 26 | 'wholesale_discount_price' => '批發價折扣', 27 | 'status_wait_pay' => '待支付', 28 | 'status_pending' => '待處理', 29 | 'status_processing' => '處理中', 30 | 'status_completed' => '已完成', 31 | 'status_failure' => '失敗', 32 | 'status_abnormal' => '異常', 33 | 'status_expired' => '已逾期', 34 | 'order_created' => '訂單建立時間', 35 | 'order_detail' => '訂單詳情', 36 | ], 37 | 'options' => [ 38 | ], 39 | ]; 40 | -------------------------------------------------------------------------------- /app/Admin/Resources/Users/Pages/ListUsers.php: -------------------------------------------------------------------------------- 1 | Tab::make('全部用户'), 27 | 'active' => Tab::make('正常用户') 28 | ->modifyQueryUsing(fn (Builder $query) => $query->where('status', User::STATUS_ACTIVE)), 29 | 'disabled' => Tab::make('禁用用户') 30 | ->modifyQueryUsing(fn (Builder $query) => $query->where('status', User::STATUS_DISABLED)), 31 | 'vip' => Tab::make('VIP用户') 32 | ->modifyQueryUsing(fn (Builder $query) => $query->whereHas('level', fn($q) => $q->where('min_spent', '>', 0))), 33 | ]; 34 | } 35 | } -------------------------------------------------------------------------------- /resources/lang/zh_CN/remote-server.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright outtime 7 | * @link https://outti.me 8 | */ 9 | 10 | return [ 11 | "types" => [ 12 | "http_server" => "HTTP请求", 13 | "rcon_command" => "RCON执行", 14 | "sql_execute" => "Mysql执行" 15 | ], 16 | 'labels' => [ 17 | 'RemoteServer' => '回调服务器', 18 | 'remote_server' => '回调服务器', 19 | ], 20 | "fields" => [ 21 | "name" => "服务器名称", 22 | "type" => "回调类型", 23 | "url" => "URL地址", 24 | "host" => "主机地址", 25 | "port" => "端口", 26 | "username" => "用户名", 27 | "password" => "密码", 28 | "database" => "数据库名", 29 | "command" => "执行命令", 30 | "headers" => "请求头", 31 | "body" => "请求体", 32 | "is_active" => "是否启用", 33 | "description" => "描述", 34 | ], 35 | "helps" => [ 36 | "command" => "要执行的命令,支持变量替换", 37 | "headers" => "HTTP请求头,格式:key: value", 38 | "body" => "HTTP请求体,支持JSON格式", 39 | "type" => "您可以向您的应用开发者了解应该选择何种回调服务器", 40 | ], 41 | "you_can_use" => "你可以在执行的命令中使用如下变量:" 42 | ]; -------------------------------------------------------------------------------- /resources/lang/zh_TW/remote-server.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright outtime 7 | * @link https://outti.me 8 | */ 9 | 10 | return [ 11 | "types" => [ 12 | "http_server" => "HTTP請求", 13 | "rcon_command" => "RCON執行", 14 | "sql_execute" => "Mysql執行" 15 | ], 16 | 'labels' => [ 17 | 'RemoteServer' => '回調服務器', 18 | 'remote_server' => '回調服務器', 19 | ], 20 | "fields" => [ 21 | "name" => "服務器名稱", 22 | "type" => "回調類型", 23 | "url" => "URL地址", 24 | "host" => "主機地址", 25 | "port" => "端口", 26 | "username" => "用戶名", 27 | "password" => "密碼", 28 | "database" => "數據庫名", 29 | "command" => "執行命令", 30 | "headers" => "請求頭", 31 | "body" => "請求體", 32 | "is_active" => "是否啟用", 33 | "description" => "描述", 34 | ], 35 | "helps" => [ 36 | "command" => "要執行的命令,支持變量替換", 37 | "headers" => "HTTP請求頭,格式:key: value", 38 | "body" => "HTTP請求體,支持JSON格式", 39 | "type" => "您可以向您的應用開發者了解應該選擇何種回調服務器", 40 | ], 41 | "you_can_use" => "你可以在執行的命令中使用如下變量:" 42 | ]; -------------------------------------------------------------------------------- /resources/lang/zh_CN/menu.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'index' => '主页', 6 | 'admin' => '系统', 7 | 'users' => '管理员', 8 | 'roles' => '角色', 9 | 'permission' => '权限', 10 | 'menu' => '菜单', 11 | 'operation_log' => '操作日志', 12 | 'helpers' => '开发工具', 13 | 'extensions' => '扩展', 14 | 'scaffold' => '代码生成器', 15 | 'icons' => '图标', 16 | 'goods_manage' => '商品管理', 17 | 'goods' => '商品列表', 18 | 'goods_group' => '商品分类', 19 | 'carmis_manage' => '卡密管理', 20 | 'carmis' => '卡密列表', 21 | 'import_carmis' => '导入卡密', 22 | 'coupon_manage' => '优惠管理', 23 | 'coupon' => '优惠码列表', 24 | 'article_manage' => '文章管理', 25 | 'configuration' => '配置', 26 | 'email_template_configuration' => '邮件模板配置', 27 | 'pay_configuration' => '支付配置', 28 | 'order_manage' => '销售数据', 29 | 'order' => '订单列表', 30 | 'system_setting' => '系统设置', 31 | 'email_test' => '邮件测试', 32 | 'remote_server' => '回调服务器', 33 | 'theme_setting' => '主题设置', 34 | ], 35 | ]; 36 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | ./tests/Unit 9 | 10 | 11 | 12 | ./tests/Feature 13 | 14 | 15 | 16 | 17 | ./app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/Jobs/CouponBack.php: -------------------------------------------------------------------------------- 1 | order = $order; 40 | } 41 | 42 | /** 43 | * Execute the job. 44 | * 45 | * @return void 46 | */ 47 | public function handle() 48 | { 49 | // 如果订单有使用优惠码 50 | if ($this->order->coupon_id) { 51 | // 优惠码次数+1 52 | app('App\Services\Coupons')->retIncrByID($this->order->coupon_id); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Settings/SystemSettings.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'id' => 'ID', 6 | 'name' => '名称', 7 | 'username' => '用户名', 8 | 'email' => '邮箱', 9 | 'http_path' => 'HTTP路径', 10 | 'password' => '密码', 11 | 'password_confirmation' => '确认密码', 12 | 'created_at' => '创建时间', 13 | 'updated_at' => '更新时间', 14 | 'permissions' => '权限', 15 | 'slug' => '标识', 16 | 'user' => '用户', 17 | 'order' => '排序', 18 | 'ip' => 'IP', 19 | 'method' => '方法', 20 | 'uri' => 'URI', 21 | 'roles' => '角色', 22 | 'path' => '路径', 23 | 'input' => '输入', 24 | 'type' => '类型', 25 | ], 26 | 'labels' => [ 27 | 'list' => '列表', 28 | 'edit' => '编辑', 29 | 'detail' => '详细', 30 | 'create' => '创建', 31 | 'root' => '顶级', 32 | 'scaffold' => '代码生成器', 33 | ], 34 | 'options' => [ 35 | // 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/luna.php: -------------------------------------------------------------------------------- 1 | '选择分类', 7 | 'home_choice_goods' => '选择商品', 8 | 'order_search_m' => '查单', 9 | 'goods_num' => '商品数量', 10 | 'goods_disc_1' => '满', 11 | 'goods_disc_2' => '个 单价', 12 | 'goods_disc_3' => '元/件', 13 | 'goods_surplus' => '剩余', 14 | 'goods_unit' => '件', 15 | 16 | 'buy_goods_msg' => '商品详情', 17 | 'buy_num' => '购买数量', 18 | 'buy_email' => '邮        箱', 19 | 'buy_email_tips' => '填写你的邮箱', 20 | 'buy_disc' => '优  惠  码', 21 | 'buy_disc_tips' => '填写你的优惠码', 22 | 'buy_pass' => '查询密码', 23 | 'buy_pass_tips' => '请填写订单的查询密码', 24 | 'buy_code' => '验  证  码', 25 | 'buy_code_tips' => '请输入验证码', 26 | 'buy_loading_verification' => '正在加载验证码...', 27 | 'buy_order_m' => '手机下单', 28 | 29 | 'query_no_order' => '没有找到订单信息。', 30 | 'query_no_order_tips' => '或许可以检查核对下输入的订单号,邮箱,或者查询密码是否正确哦!!', 31 | 32 | 'order_number' => '订单编号', 33 | 'email' => '订单邮箱', 34 | 'order_status' => '订单状态', 35 | 36 | 37 | 'least_one' => '购买数量不能低于1件', 38 | 'exceeds' => '购买数量不能多于库存数量', 39 | 'exceeds_limit' => '购买数量不能多于限购数量', 40 | 'mobile_order' => '扫描二维码 手机下单' 41 | ]; 42 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/luna.php: -------------------------------------------------------------------------------- 1 | '選取分類', 7 | 'home_choice_goods' => '選取商品', 8 | 'order_search_m' => '查單', 9 | 'goods_num' => '商品數量', 10 | 'goods_disc_1' => '滿', 11 | 'goods_disc_2' => '個 單價', 12 | 'goods_disc_3' => '元/件', 13 | 'goods_surplus' => '剩余', 14 | 'goods_unit' => '件', 15 | 16 | 'buy_goods_msg' => '商品詳情', 17 | 'buy_num' => '購買數量', 18 | 'buy_email' => '信        箱', 19 | 'buy_email_tips' => '填寫你的信箱', 20 | 'buy_disc' => '折  扣  碼', 21 | 'buy_disc_tips' => '填寫你的折扣碼', 22 | 'buy_pass' => '查詢密碼', 23 | 'buy_pass_tips' => '請填寫訂單的查詢密碼', 24 | 'buy_code' => '驗  證  碼', 25 | 'buy_code_tips' => '請輸入驗證碼', 26 | 'buy_loading_verification' => '正在加載驗證碼...', 27 | 'buy_order_m' => '行動電話下單', 28 | 29 | 'query_no_order' => '沒有找到訂單資訊。', 30 | 'query_no_order_tips' => '或許可以檢查核實下輸入的訂單號,信箱,或者查詢密碼是否正確哦!!', 31 | 32 | 'order_number' => '訂單編號', 33 | 'email' => '訂單信箱', 34 | 'order_status' => '訂單狀態', 35 | 36 | 37 | 'least_one' => '購買數量不能低於1件', 38 | 'exceeds' => '購買數量不能多於庫存數量', 39 | 'exceeds_limit' => '購買數量不能多於限購數量', 40 | 'mobile_order' => '掃描QRcode 行動電話下單' 41 | ]; 42 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 22 | SendEmailVerificationNotification::class, 23 | ], 24 | GoodsGroupDeleted::class => [ 25 | \App\Listeners\GoodsGroupDeleted::class, 26 | ], 27 | GoodsDeleted::class => [ 28 | \App\Listeners\GoodsDeleted::class, 29 | ], 30 | OrderUpdated::class => [ 31 | \App\Listeners\OrderUpdated::class, 32 | ], 33 | ]; 34 | 35 | /** 36 | * Register any events for your application. 37 | * 38 | * @return void 39 | */ 40 | public function boot() 41 | { 42 | parent::boot(); 43 | // 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/order.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Order' => '订单', 6 | 'order' => '订单', 7 | ], 8 | 'fields' => [ 9 | 'actual_price' => '实际支付价格', 10 | 'buy_amount' => '购买数量', 11 | 'buy_ip' => '购买者下单IP地址', 12 | 'coupon_discount_price' => '优惠码优惠价格', 13 | 'coupon_id' => '优惠码', 14 | 'preselection' => '自选卡密', 15 | 'email' => '下单邮箱', 16 | 'goods_id' => '所属商品', 17 | 'sub_id' => '所属规格', 18 | 'goods_price' => '商品单价', 19 | 'info' => '订单详情', 20 | 'order_id' => '订单ID', 21 | 'order_sn' => '订单号', 22 | 'pay_id' => '支付通道', 23 | 'status' => '订单状态', 24 | 'search_pwd' => '查询密码', 25 | 'title' => '订单名称', 26 | 'total_price' => '订单总价', 27 | 'trade_no' => '第三方支付订单号', 28 | 'type' => '订单类型', 29 | 'wholesale_discount_price' => '批发价优惠', 30 | 'status_wait_pay' => '待支付', 31 | 'status_pending' => '待处理', 32 | 'status_processing' => '处理中', 33 | 'status_completed' => '已完成', 34 | 'status_failure' => '失败', 35 | 'status_abnormal' => '异常', 36 | 'status_expired' => '已过期', 37 | 'order_created' => '订单创建时间', 38 | 'order_detail' => '订单详情', 39 | ], 40 | 'options' => [ 41 | ], 42 | ]; 43 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/global.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'id' => 'ID', 6 | 'name' => '名稱', 7 | 'username' => '用戶名', 8 | 'email' => '信箱', 9 | 'http_path' => 'HTTP路徑', 10 | 'password' => '密碼', 11 | 'password_confirmation' => '確認密碼', 12 | 'created_at' => '建立時間', 13 | 'updated_at' => '更新時間', 14 | 'permissions' => '權限', 15 | 'slug' => '標示', 16 | 'user' => '用戶', 17 | 'order' => '排序', 18 | 'ip' => 'IP', 19 | 'method' => '方法', 20 | 'uri' => 'URI', 21 | 'roles' => '角色', 22 | 'path' => '路徑', 23 | 'input' => '輸入', 24 | 'type' => '类型', 25 | ], 26 | 'labels' => [ 27 | 'list' => '列表', 28 | 'edit' => '編輯', 29 | 'detail' => '詳細', 30 | 'create' => '創建', 31 | 'root' => 'root', 32 | 'scaffold' => '代碼生成器', 33 | ], 34 | 35 | 'options' => [ 36 | 'permissions' => [ 37 | 38 | ], 39 | ], 40 | 41 | ]; 42 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/carmis.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Carmis' => '卡密', 6 | 'carmis' => '卡密', 7 | ], 8 | 'fields' => [ 9 | 'goods_id' => '所属商品', 10 | 'status' => '状态', 11 | 'carmi' => '卡密内容', 12 | 'info' => '卡密说明', 13 | 'info_preg' => '卡密说明正则/分隔符', 14 | 'status_unsold' => '未售出', 15 | 'status_sold' => '已售出', 16 | 'is_loop' => '循环卡密', 17 | 'yes' => '是', 18 | 'no' => '否', 19 | 'import_carmis' => '导入卡密', 20 | 'carmis_list' => '卡密列表', 21 | 'carmis_txt' => '卡密文本', 22 | 'are_you_import_sure' => '确定要导入卡密吗?', 23 | 'remove_duplication' => '是否去重', 24 | 'sub_id' => '对应规格' 25 | ], 26 | 'options' => [ 27 | 'non_sub' => '默认规格' 28 | ], 29 | 'helps' => [ 30 | 'carmis_list' => '一行一个,回车分隔。请勿导入单个文本长度过大的卡密,容易导致内存溢出。如果卡密过大建议修改商品为人工处理', 31 | 'carmis_info' => '若商品支持自选,卡密信息将会展示在购买页供顾客自行选择', 32 | 'info_preg' => '支持正则表达式(优先判定)或用分隔符来筛选卡密。
正则表达式将会将符合条件的第一个字符串作为卡密信息。
分隔符将会使用最后一个字符串作为卡密信息,如c1---c2---c3,使用---作为分隔符,c3会作为卡密信息。', 33 | ], 34 | 'rule_messages' => [ 35 | 'carmis_list_and_carmis_txt_can_not_be_empty' => '请填写需要导入的卡密或选择需要上传的卡密文件', 36 | 'import_carmis_success' => '导入卡密成功!' 37 | ] 38 | ]; 39 | -------------------------------------------------------------------------------- /database/factories/OrderFactory.php: -------------------------------------------------------------------------------- 1 | define(\App\Models\Order::class, function (Faker $faker) { 10 | return [ 11 | 'order_sn' => strtoupper(Str::random(12)), 12 | 'goods_id' => rand(1, 3), 13 | 'coupon_id' => rand(1, 3), 14 | 'title' => $faker->words(3, true), 15 | 'type' => rand(1,2), 16 | 'goods_price' => $faker->randomFloat(2, 10, 100), 17 | 'buy_amount' => rand(1, 10), 18 | 'coupon_discount_price' => $faker->randomFloat(2, 0, 100), 19 | 'wholesale_discount_price' => $faker->randomFloat(2, 0, 100), 20 | 'total_price' => $faker->randomFloat(2, 10, 100), 21 | 'actual_price' => $faker->randomFloat(2, 10, 100), 22 | 'search_pwd' => $faker->password(6, 10), 23 | 'email' => $faker->email, 24 | 'info' => $faker->words(3, true), 25 | 'pay_id' => rand(1, 20), 26 | 'buy_ip' => $faker->ipv4, 27 | 'trade_no' => strtoupper(Str::random(12)), 28 | 'status' => rand(1, 5), 29 | 'created_at' => $faker->dateTimeBetween('-7 days', 'now', 'PRC'), 30 | 'updated_at' => $faker->dateTimeBetween('-7 days', 'now', 'PRC'), 31 | ]; 32 | }); 33 | -------------------------------------------------------------------------------- /app/Services/Coupons.php: -------------------------------------------------------------------------------- 1 | whereHas('goods', function ($query) use ($goodsID) { 26 | $query->where('goods_id', $goodsID); 27 | })->where('is_open', Coupon::STATUS_OPEN)->where('coupon', $coupon)->first(); 28 | return $coupon; 29 | } 30 | 31 | /** 32 | * 设置优惠券使用次数 -1 33 | * @param string $coupon 34 | * @return int 35 | * 36 | */ 37 | public function retDecr(string $coupon) 38 | { 39 | return Coupon::query() 40 | ->where('coupon', $coupon) 41 | ->decrement('ret', 1); 42 | } 43 | 44 | /** 45 | * 设置优惠券次数+1 46 | * 47 | * @param int $id 48 | * @return int 49 | * 50 | */ 51 | public function retIncrByID(int $id) 52 | { 53 | return Coupon::query()->where('id', $id)->increment('ret', 1); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/goods.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Goods' => '商品', 6 | 'goods' => '商品', 7 | ], 8 | 'fields' => [ 9 | 'sell_price' => '售價', 10 | 'group_id' => '所屬分類', 11 | 'api_hook' => '回調事件', 12 | 'buy_prompt' => '購買提示', 13 | 'description' => '商品描述', 14 | 'gd_name' => '商品名稱', 15 | 'gd_description' => '商品描述', 16 | 'gd_keywords' => '商品關鍵字', 17 | 'in_stock' => '庫存', 18 | 'ord' => '排序權重', 19 | 'other_ipu_cnf' => '其他輸入框配置', 20 | 'picture' => '商品圖片', 21 | 'sales_volume' => '銷量', 22 | 'type' => '商品類型', 23 | 'buy_limit_num' => '限製單次購買最大數量', 24 | 'wholesale_price_cnf' => '批發價配置', 25 | 'automatic_delivery' => '自動發貨', 26 | 'manual_processing' => '人工處理', 27 | 'is_open' => '是否上架', 28 | 'coupon_id' => '可用折扣碼' 29 | ], 30 | 'options' => [ 31 | ], 32 | 'helps' => [ 33 | 'picture' => '可不上傳,為預設圖片', 34 | 'in_stock' => '當商品類型為"人工處理"時,手動填寫的庫存數量才會生效。"自動發貨"類型的商品系統會自動識別庫存數量', 35 | 'buy_limit_num' => '防止惡意刷庫存,0為不限製客戶單次下單最大數量', 36 | 'other_ipu_cnf' => '格式為[唯一標識(英文)=輸入框名字=是否必填],例如:填寫 line_account=Line賬戶=true 表示產品詳情頁會新增一個 [Line賬戶] 輸入框,客戶可在其中輸入 [Line賬戶],true 為必填,false 為選填。(一行一個)', 37 | 'wholesale_price_cnf' => '例如:填寫 5=3 表示客戶購買 5 件或以上時,每件價格為 3 元。一行一個', 38 | 39 | ] 40 | ]; 41 | -------------------------------------------------------------------------------- /app/Models/Carmis.php: -------------------------------------------------------------------------------- 1 | __('carmis.fields.status_unsold'), 40 | self::STATUS_SOLD => __('carmis.fields.status_sold') 41 | ]; 42 | } 43 | 44 | /** 45 | * 关联商品规格 46 | * 47 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 48 | */ 49 | public function goodsSub() 50 | { 51 | return $this->belongsTo(GoodsSub::class, 'sub_id'); 52 | } 53 | 54 | /** 55 | * 关联商品(通过规格) 56 | * 57 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 58 | */ 59 | public function goods() 60 | { 61 | return $this->belongsTo(Goods::class, 'goods_id')->withDefault(function () { 62 | return $this->goodsSub?->goods; 63 | }); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/PaymentGateways/Contracts/PaymentDriverInterface.php: -------------------------------------------------------------------------------- 1 | where('goods_id', $goodsID) 27 | ->where('sub_id', $sub_id) 28 | ->where('status', Carmis::STATUS_UNSOLD) 29 | ->take($byAmount) 30 | ->get(); 31 | return $carmis ? $carmis->toArray() : null; 32 | } 33 | 34 | /** 35 | * 通过卡密ID获得卡密 36 | * 37 | * @param int $id 卡密id 38 | * @return string|null 39 | * 40 | * @author outtime 41 | * @copyright outtime 42 | * @link https://outti.me 43 | */ 44 | public function getCarmiById(int $id){ 45 | return Carmis::find($id); 46 | } 47 | 48 | /** 49 | * 通过id集合设置卡密已售出 50 | * 51 | * @param array $ids 卡密id集合 52 | * @return bool 53 | * 54 | */ 55 | public function soldByIDS(array $ids): bool 56 | { 57 | return Carmis::query()->whereIn('id', $ids)->where('is_loop', 0)->update(['status' => Carmis::STATUS_SOLD]); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/Http/Controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | with('page_title', $pageTitle); 28 | } 29 | 30 | /** 31 | * 错误提示 32 | * 33 | * @param string $content 提示内容 34 | * @param string $jumpUri 跳转url 35 | * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View 36 | * 37 | */ 38 | protected function err(string $content, $jumpUri = '') 39 | { 40 | $theme = current_theme(); 41 | return view("{$theme}::errors/error", [ 42 | 'title' => __('dujiaoka.error_title'), 43 | 'content' => $content, 44 | 'url' => $jumpUri 45 | ])->with('page_title', __('dujiaoka.error_title')); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/Services/ThemeService.php: -------------------------------------------------------------------------------- 1 | currentTheme = cfg('template', 'morpho'); 15 | $this->registerViews(); 16 | } 17 | 18 | public function getCurrentTheme(): string 19 | { 20 | return $this->currentTheme; 21 | } 22 | 23 | /** 24 | * 注册主题视图路径 25 | */ 26 | protected function registerViews(): void 27 | { 28 | $themePath = resource_path("views/themes/{$this->currentTheme}/views"); 29 | 30 | if (is_dir($themePath)) { 31 | View::addNamespace($this->currentTheme, $themePath); 32 | } 33 | } 34 | 35 | /** 36 | * 获取主题配置值 37 | */ 38 | public function getConfig(string $key, $default = null) 39 | { 40 | return Cache::get("theme.{$this->currentTheme}.{$key}", $default); 41 | } 42 | 43 | /** 44 | * 设置主题配置值 45 | */ 46 | public function setConfig(string $key, $value): void 47 | { 48 | Cache::put("theme.{$this->currentTheme}.{$key}", $value); 49 | } 50 | 51 | /** 52 | * 获取主题资源URL 53 | */ 54 | public function asset(string $path): string 55 | { 56 | $url = "/assets/{$this->currentTheme}/{$path}"; 57 | return \App\Helpers\CdnHelper::asset($url); 58 | } 59 | } -------------------------------------------------------------------------------- /app/Models/AdminUser.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | protected $fillable = [ 29 | 'name', 30 | 'username', 31 | 'password', 32 | ]; 33 | 34 | /** 35 | * The attributes that should be hidden for serialization. 36 | * 37 | * @var array 38 | */ 39 | protected $hidden = [ 40 | 'password', 41 | 'remember_token', 42 | ]; 43 | 44 | /** 45 | * Get the attributes that should be cast. 46 | * 47 | * @return array 48 | */ 49 | protected function casts(): array 50 | { 51 | return [ 52 | 'password' => 'hashed', 53 | ]; 54 | } 55 | 56 | /** 57 | * Determine if the user can access the Filament admin panel. 58 | */ 59 | public function canAccessPanel(Panel $panel): bool 60 | { 61 | return true; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | , \Psr\Log\LogLevel::*> 10 | */ 11 | protected $levels = [ 12 | // 13 | ]; 14 | /** 15 | * A list of the exception types that are not reported. 16 | * 17 | * @var array> 18 | */ 19 | protected $dontReport = [ 20 | // 21 | ]; 22 | /** 23 | * A list of the inputs that are never flashed to the session on validation exceptions. 24 | * 25 | * @var array 26 | */ 27 | protected $dontFlash = [ 28 | 'current_password', 29 | 'password', 30 | 'password_confirmation', 31 | ]; 32 | /** 33 | * Render an exception into an HTTP response. 34 | * 35 | * @param \Illuminate\Http\Request $request 36 | * @param \Throwable $exception 37 | * @return \Symfony\Component\HttpFoundation\Response 38 | * 39 | * @throws \Throwable 40 | */ 41 | public function render($request, Throwable $exception) { 42 | if ($exception instanceof AppException) { 43 | $layout = cfg('template', 'layui'); 44 | $tplPath = $layout . '/errors/error'; 45 | return view($tplPath, ['title' => __('dujiaoka.error_title'), 'content' => $exception->getMessage(), 'url' => ""]); 46 | } 47 | return parent::render($request, $exception); 48 | } 49 | } -------------------------------------------------------------------------------- /app/Services/Payment.php: -------------------------------------------------------------------------------- 1 | whereIn('pay_client', [$payClient, Pay::CLIENT_ALL]) 26 | ->where('enable', Pay::ENABLED) 27 | ->get(); 28 | return $payGateway ? $payGateway->toArray() : null; 29 | } 30 | 31 | /** 32 | * 通过支付标识获得支付配置 33 | * 34 | * @param string $check 支付标识 35 | * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null 36 | * 37 | */ 38 | public function detailByCheck(string $check) 39 | { 40 | $gateway = Pay::query() 41 | ->where('pay_check', $check) 42 | ->where('enable', Pay::ENABLED) 43 | ->first(); 44 | return $gateway; 45 | } 46 | 47 | /** 48 | * 通过id查询支付网关 49 | * 50 | * @param int $id 支付网关id 51 | * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null 52 | * 53 | */ 54 | public function detail(int $id) 55 | { 56 | $gateway = Pay::query() 57 | ->where('id', $id) 58 | ->where('enable', Pay::ENABLED) 59 | ->first(); 60 | return $gateway; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/Http/Controllers/Home/ArticleController.php: -------------------------------------------------------------------------------- 1 | get() 13 | ->map(function ($article) { 14 | $article->summary = $article->getSummary(); 15 | return $article; 16 | }); 17 | $articles_sort = $articles->groupBy('category'); 18 | $categories = $articles_sort->keys(); 19 | 20 | return $this->render('static_pages/article', [ 21 | 'articles' => $articles_sort, 22 | 'category' => $categories 23 | ], __('dujiaoka.page-title.article')); 24 | } 25 | 26 | public function show($link) { 27 | 28 | // 根据 $link 查询文章内容 29 | $article = Articles::with(['goods' => function($query) { 30 | $query->where('is_open', true)->select('goods.id', 'goods.gd_name', 'goods.gd_description', 'goods.picture'); 31 | }])->where('link', $link)->first(); 32 | 33 | if (!$article) { 34 | abort(404); 35 | } 36 | 37 | $title = $article->title; 38 | $content = $article->content; 39 | $relatedGoods = $article->goods; 40 | 41 | return $this->render('static_pages/article', [ 42 | 'title' => $title, 43 | 'content' => $content, 44 | 'relatedGoods' => $relatedGoods 45 | ], 46 | $title." | ". __('dujiaoka.page-title.article')); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /app/Jobs/OrderExpired.php: -------------------------------------------------------------------------------- 1 | orderSN = $orderSN; 47 | } 48 | 49 | /** 50 | * Execute the job. 51 | * 52 | * @return void 53 | */ 54 | public function handle() 55 | { 56 | // 如果x分钟后还没支付就算过期 57 | $order = app('App\Services\Orders')->detailOrderSN($this->orderSN); 58 | if ($order && $order->status == Order::STATUS_WAIT_PAY) { 59 | $stockMode = cfg('stock_mode', 2); 60 | // 如果是下单即减库存模式,释放锁定的库存 61 | if ($stockMode == 1) { 62 | CacheManager::unlockStock($this->orderSN); 63 | } 64 | 65 | app('App\Services\Orders')->expiredOrderSN($this->orderSN); 66 | // 回退优惠券 67 | CouponBack::dispatch($order); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/Http/Middleware/Challenge.php: -------------------------------------------------------------------------------- 1 | route()->getAction(); 26 | $controller = $routeAction['controller'] ?? null; 27 | if($controller && in_array(explode('@', $controller)[0], $this->whiteClass)) 28 | return $next($request); 29 | 30 | if(cfg('is_cn_challenge') == BaseModel::STATUS_CLOSE) 31 | return $next($request); 32 | 33 | $status = session('challenge'); 34 | if($status === "pass") 35 | return $next($request); 36 | 37 | if(isset($_REQUEST['_challenge'])){ 38 | if (substr(sha1($_REQUEST['_challenge']), -4) == $status){ 39 | session(['challenge' => 'pass']); 40 | return $next($request); 41 | } 42 | } 43 | 44 | $isoCode = get_ip_country($request->ip()); 45 | if($isoCode != 'CN'){ 46 | session(['challenge' => 'pass']); 47 | return $next($request); 48 | } 49 | $challenge = substr(sha1(rand()), -4); 50 | session(['challenge' => $challenge]); 51 | return response()->view('common/challenge',['code' => $challenge]); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Bcrypt Options 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify the configuration options that should be used when 26 | | passwords are hashed using the Bcrypt algorithm. This will allow you 27 | | to control the amount of time it takes to hash the given password. 28 | | 29 | */ 30 | 31 | 'bcrypt' => [ 32 | 'rounds' => env('BCRYPT_ROUNDS', 10), 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Argon Options 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Here you may specify the configuration options that should be used when 41 | | passwords are hashed using the Argon algorithm. These will allow you 42 | | to control the amount of time it takes to hash the given password. 43 | | 44 | */ 45 | 46 | 'argon' => [ 47 | 'memory' => 1024, 48 | 'threads' => 2, 49 | 'time' => 2, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /resources/views/themes/morpho/views/layouts/_footer.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 | 17 |
18 |

19 | © {{ date('Y') }} {{ app(\App\Settings\ShopSettings::class)->title }} All rights reserved. 20 |
Powered by 21 | 独角数卡. 22 | Theme by 23 | @riniba 24 |

25 |

26 |

27 |
28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'useTLS' => true, 41 | ], 42 | ], 43 | 44 | 'redis' => [ 45 | 'driver' => 'redis', 46 | 'connection' => 'default', 47 | ], 48 | 49 | 'log' => [ 50 | 'driver' => 'log', 51 | ], 52 | 53 | 'null' => [ 54 | 'driver' => 'null', 55 | ], 56 | 57 | ], 58 | 59 | ]; 60 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class); 50 | $response = $kernel->handle( 51 | $request = Request::capture() 52 | )->send(); 53 | 54 | $kernel->terminate($request, $response); -------------------------------------------------------------------------------- /resources/lang/zh_CN/goods.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'Goods' => '商品', 6 | 'goods' => '商品', 7 | 'soldout' => '已售完' 8 | ], 9 | 'fields' => [ 10 | 'price' => '售价', 11 | 'preselection' => '自选加价', 12 | 'group_id' => '所属分类', 13 | 'api_hook' => '回调事件', 14 | 'buy_prompt' => '购买提示', 15 | 'description' => '商品描述', 16 | 'gd_name' => '商品名称', 17 | 'gd_description' => '商品描述', 18 | 'gd_keywords' => '商品关键字', 19 | 'goods_sub' => '商品规格', 20 | 'stock' => '库存', 21 | 'ord' => '排序权重', 22 | 'other_ipu_cnf' => '其他输入框配置', 23 | 'picture' => '商品图片', 24 | 'picture_url' => '商品图片URL', 25 | 'sales_volume' => '销量', 26 | 'type' => '发货类型', 27 | 'buy_limit_num' => '限制单次购买最大数量', 28 | 'wholesale_price_cnf' => '批发价配置', 29 | 'automatic_delivery' => '自动发货', 30 | 'manual_processing' => '人工处理', 31 | 'automatic_processing' => '自动处理', 32 | 'is_open' => '是否上架', 33 | 'is_sub' => '商品类型', 34 | 'coupon_id' => '可用优惠码', 35 | 'payment_limit' => '限制支付方式' 36 | ], 37 | 'options' => [ 38 | 'is_sub' => '多规格商品', 39 | 'not_sub' => '单规格商品' 40 | ], 41 | 'helps' => [ 42 | 'picture' => '可不上传,为默认图片', 43 | 'picture_url' => '输入站外图片链接,将自动替换商品图片。', 44 | 'in_stock' => '"自动发货"类型的商品会自动计算库存卡密,其他类型需要主动设置库存。', 45 | 'buy_limit_num' => '防止恶意刷库存,0为不限制客户单次下单最大数量', 46 | 'other_ipu_cnf' => '格式为唯一标识(英文)=输入框名字=是否必填,例如:填写 qq_account=QQ账号=true 表示产品详情页会新增一个 QQ账号 输入框。true 为必填,false 为选填。(一行一个)', 47 | 'wholesale_price_cnf' => '例如:填写 5=3 表示客户购买 5 件或以上时,每件价格为 3 元;批发价会覆盖多规格商品价格。(一行一个)', 48 | 'payment_limit' => '仅允许使用这些支付方式购买此商品,若为空,则支持全部已启用的的支付方式', 49 | 'preselection' => '自动发货的商品支持在下单时预先选择想要的卡密,填写一个价格则代表开启自选加价。需在卡密处完成设置。' 50 | ] 51 | ]; 52 | -------------------------------------------------------------------------------- /app/Models/Pay.php: -------------------------------------------------------------------------------- 1 | __('pay.fields.method_jump'), 33 | self::METHOD_SCAN => __('pay.fields.method_scan'), 34 | ]; 35 | } 36 | 37 | public static function getClientMap() 38 | { 39 | return [ 40 | self::CLIENT_PC => __('pay.fields.pay_client_pc'), 41 | self::CLIENT_MOBILE => __('pay.fields.pay_client_mobile'), 42 | self::CLIENT_ALL => __('pay.fields.pay_client_all'), 43 | ]; 44 | } 45 | 46 | // 作用域:获取启用的支付方式 47 | public function scopeEnabled($query) 48 | { 49 | return $query->where('enable', self::ENABLED); 50 | } 51 | 52 | // 作用域:根据客户端类型筛选 53 | public function scopeForClient($query, $client) 54 | { 55 | return $query->whereIn('pay_client', [$client, self::CLIENT_ALL]); 56 | } 57 | 58 | protected static function boot() 59 | { 60 | parent::boot(); 61 | 62 | static::updated(function ($pay) { 63 | CacheManager::forgetPayMethod($pay->id); 64 | CacheManager::forgetPayMethods(); 65 | }); 66 | 67 | static::deleted(function ($pay) { 68 | CacheManager::forgetPayMethod($pay->id); 69 | CacheManager::forgetPayMethods(); 70 | }); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /resources/lang/zh_TW/system-setting.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright assimon 7 | * @link http://utf8.hk/ 8 | */ 9 | 10 | return [ 11 | 'labels' => [ 12 | 'SystemSetting' => '系統設定', 13 | 'system_setting' => '系統設定', 14 | 'base_setting' => '基本設定', 15 | 'mail_setting' => '信箱服務', 16 | 'order_push_setting' => '訂單推送配置', 17 | 'geetest' => '極驗驗證', 18 | ], 19 | 20 | 'fields' => [ 21 | 'title' => '網站標題', 22 | 'text_logo' => '文字LOGO', 23 | 'img_logo' => '圖片LOGO', 24 | 'keywords' => '網站關鍵詞', 25 | 'description' => '網站描述', 26 | 'notice' => '站點公告', 27 | 'footer' => '頁尾自訂代碼', 28 | 'manage_email' => '管理員信箱', 29 | 'is_open_anti_red' => '是否開啟Wechat/QQ防紅', 30 | 'is_open_img_code' => '是否開啟圖形驗證碼', 31 | 'is_open_search_pwd' => '是否開啟查詢密碼', 32 | 'is_open_server_jiang' => '是否開啟server醬', 33 | 'server_jiang_token' => 'server醬通訊token', 34 | 'is_open_telegram_push' => '是否開啟Telegram推送', 35 | 'telegram_userid' => 'Telegram用戶id', 36 | 'telegram_bot_token' => 'Telegram通訊token', 37 | 'template' => '站點模板', 38 | 'language' => '站點語言', 39 | 'order_expire_time' => '訂單逾期時間(分鐘)', 40 | 41 | 'driver' => '信箱驅動', 42 | 'host' => 'smtp伺服器地址', 43 | 'port' => '通訊埠', 44 | 'username' => '賬戶', 45 | 'password' => '密碼', 46 | 'encryption' => '協議', 47 | 'from_address' => '發件地址', 48 | 'from_name' => '發件名稱', 49 | 50 | 'geetest_id' => '極驗id', 51 | 'geetest_key' => '極驗key', 52 | 'is_open_geetest' => '是否開啟極驗', 53 | ], 54 | 'options' => [ 55 | ], 56 | 'rule_messages' => [ 57 | 'save_system_setting_success' => '系統配置套用成功!', 58 | 'change_reboot_php_worker' => '修改部分配置需要重新啓動[supervisor]或php進程管理工具才會生效,例如信箱服務,server醬等。' 59 | ] 60 | ]; 61 | -------------------------------------------------------------------------------- /app/Jobs/ServerJiang.php: -------------------------------------------------------------------------------- 1 | order = $order; 43 | } 44 | 45 | /** 46 | * Execute the job. 47 | * 48 | * @return void 49 | */ 50 | public function handle() 51 | { 52 | $postdata = http_build_query([ 53 | 'text' => __('dujiaoka.prompt.new_order_push') . ":{$this->order['ord_title']}", 54 | 'desp' => " 55 | - ". __('order.fields.title') .":{$this->order->title} 56 | - ". __('order.fields.order_sn') .":{$this->order->order_sn} 57 | - ". __('order.fields.email') .":{$this->order->email} 58 | - ". __('order.fields.actual_price') .":{$this->order->actual_price} 59 | " 60 | ]); 61 | $opts = [ 62 | 'http' => [ 63 | 'method' => 'POST', 64 | 'header' => 'Content-type: application/x-www-form-urlencoded', 65 | 'content' => $postdata 66 | ] 67 | ]; 68 | $context = stream_context_create($opts); 69 | $apiToken = cfg('server_jiang_token'); 70 | file_get_contents('https://sctapi.ftqq.com/' . $apiToken . '.send', false, $context); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 46 | 47 | $this->mapWebRoutes(); 48 | 49 | // 50 | } 51 | 52 | /** 53 | * Define the "web" routes for the application. 54 | * 55 | * These routes all receive session state, CSRF protection, etc. 56 | * 57 | * @return void 58 | */ 59 | protected function mapWebRoutes() 60 | { 61 | Route::middleware('web') 62 | ->namespace($this->namespace) 63 | ->group(base_path('routes/web.php')); 64 | } 65 | 66 | /** 67 | * Define the "api" routes for the application. 68 | * 69 | * These routes are typically stateless. 70 | * 71 | * @return void 72 | */ 73 | protected function mapApiRoutes() 74 | { 75 | Route::prefix('api') 76 | ->middleware('api') 77 | ->namespace($this->namespace) 78 | ->group(base_path('routes/api.php')); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "outtimes/dujiaoka", 3 | "type": "project", 4 | "description": "The Laravel Framework.", 5 | "keywords": ["framework", "laravel"], 6 | "license": "MIT", 7 | "require": { 8 | "php": "^8.2", 9 | "bezhansalleh/filament-shield": "^3.3", 10 | "filament/filament": "^3.2", 11 | "filament/spatie-laravel-settings-plugin": "^3.3", 12 | "geoip2/geoip2": "^2.13", 13 | "jenssegers/agent": "^2.6", 14 | "laravel/framework": "^12.0", 15 | "laravel/tinker": "^2.8", 16 | "mews/captcha": "^3.0", 17 | "mgcodeur/laravel-currency-converter": "^1.0", 18 | "overtrue/pinyin": "^5.3", 19 | "paypal/paypal-server-sdk": "^0.6", 20 | "simplesoftwareio/simple-qrcode": "^4.0", 21 | "stripe/stripe-php": "^7.84", 22 | "xhat/payjs-laravel": "^1.6", 23 | "yansongda/pay": "^3.2" 24 | }, 25 | "require-dev": { 26 | "spatie/laravel-ignition": "^2.0", 27 | "fakerphp/faker": "^1.23", 28 | "mockery/mockery": "^1.6", 29 | "nunomaduro/collision": "^8.0", 30 | "phpunit/phpunit": "^10.5|^11.0" 31 | }, 32 | "config": { 33 | "optimize-autoloader": true, 34 | "preferred-install": "dist", 35 | "sort-packages": true, 36 | "platform-check": false 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "dont-discover": [] 41 | } 42 | }, 43 | "autoload": { 44 | "psr-4": { 45 | "App\\": "app/" 46 | }, 47 | "classmap": [ 48 | "database/seeds", 49 | "database/factories" 50 | ], 51 | "files": [ 52 | "app/Helpers/functions.php" 53 | ] 54 | }, 55 | "autoload-dev": { 56 | "psr-4": { 57 | "Tests\\": "tests/" 58 | } 59 | }, 60 | "minimum-stability": "dev", 61 | "prefer-stable": true, 62 | "scripts": { 63 | "post-autoload-dump": [ 64 | "@php artisan filament:upgrade" 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/assets/morpho/js/scroll-to-top.js: -------------------------------------------------------------------------------- 1 | // 控制置顶按钮显示/隐藏和进度动画 2 | document.addEventListener('DOMContentLoaded', function() { 3 | const scrollTopBtn = document.querySelector('.btn-scroll-top'); 4 | if (!scrollTopBtn) return; 5 | 6 | const svgRect = scrollTopBtn.querySelector('svg rect'); 7 | const dashArray = 155.201; // SVG圆角矩形周长 8 | 9 | // 初始隐藏 10 | scrollTopBtn.style.opacity = '0'; 11 | scrollTopBtn.style.visibility = 'hidden'; 12 | scrollTopBtn.style.transform = 'translateX(100%)'; 13 | scrollTopBtn.style.transition = 'all 0.3s ease'; 14 | 15 | if (svgRect) { 16 | svgRect.style.strokeDasharray = dashArray; 17 | svgRect.style.strokeDashoffset = dashArray; 18 | } 19 | 20 | function updateScrollProgress() { 21 | const scrollTop = window.scrollY; 22 | const docHeight = document.documentElement.scrollHeight - window.innerHeight; 23 | const scrollPercent = scrollTop / docHeight; 24 | 25 | // 显示/隐藏按钮 26 | if (scrollTop > 300) { 27 | scrollTopBtn.style.opacity = '1'; 28 | scrollTopBtn.style.visibility = 'visible'; 29 | scrollTopBtn.style.transform = 'translateX(0)'; 30 | } else { 31 | scrollTopBtn.style.opacity = '0'; 32 | scrollTopBtn.style.visibility = 'hidden'; 33 | scrollTopBtn.style.transform = 'translateX(100%)'; 34 | } 35 | 36 | // 更新进度圆环 37 | if (svgRect && scrollTop > 300) { 38 | const offset = dashArray - (scrollPercent * dashArray); 39 | svgRect.style.strokeDashoffset = offset; 40 | } 41 | } 42 | 43 | // 节流函数 44 | let ticking = false; 45 | function requestTick() { 46 | if (!ticking) { 47 | requestAnimationFrame(updateScrollProgress); 48 | ticking = true; 49 | setTimeout(() => ticking = false, 16); 50 | } 51 | } 52 | 53 | window.addEventListener('scroll', requestTick); 54 | 55 | // 平滑滚动到顶部 56 | scrollTopBtn.addEventListener('click', function(e) { 57 | e.preventDefault(); 58 | window.scrollTo({ 59 | top: 0, 60 | behavior: 'smooth' 61 | }); 62 | }); 63 | }); -------------------------------------------------------------------------------- /app/Providers/PaymentServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(PaymentManager::class, function ($app) { 21 | return new PaymentManager(); 22 | }); 23 | 24 | // 注册支付管理器别名 25 | $this->app->alias(PaymentManager::class, 'payment.manager'); 26 | } 27 | 28 | /** 29 | * 启动服务 30 | */ 31 | public function boot() 32 | { 33 | $this->registerPaymentRoutes(); 34 | $this->registerPaymentDrivers(); 35 | } 36 | 37 | /** 38 | * 注册支付路由 39 | */ 40 | protected function registerPaymentRoutes() 41 | { 42 | Route::macro('paymentRoutes', function () { 43 | $paymentManager = app(PaymentManager::class); 44 | 45 | foreach ($paymentManager->getRegisteredDrivers() as $driver) { 46 | Route::group([ 47 | 'prefix' => "pay/{$driver}", 48 | 'middleware' => ['dujiaoka.pay_gate_way'] 49 | ], function () use ($driver) { 50 | Route::get('{payway}/{orderSN}', 'UnifiedPaymentController@gateway') 51 | ->name("payment.{$driver}.gateway"); 52 | 53 | Route::post('notify_url', 'UnifiedPaymentController@notify') 54 | ->name("payment.{$driver}.notify"); 55 | 56 | Route::get('return_url', 'UnifiedPaymentController@returnUrl') 57 | ->name("payment.{$driver}.return"); 58 | }); 59 | } 60 | }); 61 | } 62 | 63 | /** 64 | * 注册默认支付驱动 65 | */ 66 | protected function registerPaymentDrivers() 67 | { 68 | $paymentManager = app(PaymentManager::class); 69 | 70 | // 可以在这里手动注册额外的驱动 71 | // $paymentManager->registerDriver('custom_payment', CustomPaymentDriver::class); 72 | } 73 | } -------------------------------------------------------------------------------- /app/Jobs/ApiHook.php: -------------------------------------------------------------------------------- 1 | order = $order; 50 | $this->goodsService = app('App\Services\Shop'); 51 | } 52 | 53 | /** 54 | * Execute the job. 55 | * 56 | * @return void 57 | */ 58 | public function handle() 59 | { 60 | $goodInfo = $this->goodsService->detail($this->order->goods_id); 61 | // 判断是否有配置支付回调 62 | if(empty($goodInfo->api_hook)){ 63 | return; 64 | } 65 | $postdata = [ 66 | 'title' => $this->order->title, 67 | 'order_sn' => $this->order->order_sn, 68 | 'email' => $this->order->email, 69 | 'actual_price' => $this->order->actual_price, 70 | 'order_info' => $this->order->info, 71 | 'good_id' => $goodInfo->id, 72 | 'gd_name' => $goodInfo->gd_name 73 | 74 | ]; 75 | 76 | 77 | $opts = [ 78 | 'http' => [ 79 | 'method' => 'POST', 80 | 'header' => 'Content-type: application/json', 81 | 'content' => json_encode($postdata,JSON_UNESCAPED_UNICODE) 82 | ] 83 | ]; 84 | $context = stream_context_create($opts); 85 | file_get_contents($goodInfo->api_hook, false, $context); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(Shop::class); 27 | $this->app->singleton(Payment::class); 28 | $this->app->singleton(Cards::class); 29 | $this->app->singleton(Orders::class); 30 | $this->app->singleton(Coupons::class); 31 | $this->app->singleton(OrderProcess::class); 32 | $this->app->singleton(Email::class); 33 | $this->app->singleton(Validator::class); 34 | $this->app->singleton(CacheManager::class); 35 | $this->app->singleton('Jenssegers\Agent', function () { 36 | return $this->app->make(Agent::class); 37 | }); 38 | 39 | $this->app->singleton('App\\Services\\ConfigService', function ($app) { 40 | return new \App\Services\ConfigService(); 41 | }); 42 | 43 | $this->app->singleton('App\\Services\\ThemeService'); 44 | } 45 | 46 | /** 47 | * Bootstrap any application services. 48 | * 49 | * @return void 50 | */ 51 | public function boot() 52 | { 53 | // 动态设置语言文件中的货币符号 54 | $this->app->booted(function () { 55 | $currency = shop_cfg('currency', 'cny'); 56 | $symbols = [ 57 | 'cny' => '¥', 58 | 'usd' => '$', 59 | ]; 60 | $symbol = $symbols[$currency] ?? '¥'; 61 | 62 | // 设置所有语言文件的货币符号 63 | app('translator')->addLines(['dujiaoka.money_symbol' => $symbol], 'zh_CN'); 64 | app('translator')->addLines(['dujiaoka.money_symbol' => $symbol], 'zh_TW'); 65 | app('translator')->addLines(['dujiaoka.money_symbol' => $symbol], 'en'); 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/Services/ConfigService.php: -------------------------------------------------------------------------------- 1 | loaded) { 16 | $this->loadConfig(); 17 | } 18 | 19 | return data_get($this->config, $key, $default); 20 | } 21 | 22 | public function set(string $key, $value): void 23 | { 24 | data_set($this->config, $key, $value); 25 | $this->saveConfig(); 26 | } 27 | 28 | public function all(): array 29 | { 30 | if (!$this->loaded) { 31 | $this->loadConfig(); 32 | } 33 | 34 | return $this->config; 35 | } 36 | 37 | protected function loadConfig(): void 38 | { 39 | $this->config = Cache::get('system-setting', []); 40 | 41 | // Redis 没数据时从数据库读取一次 42 | if (empty($this->config)) { 43 | try { 44 | $settings = DB::table('settings')->get(); 45 | $config = []; 46 | 47 | foreach ($settings as $setting) { 48 | $payload = json_decode($setting->payload, true); 49 | // 构建嵌套结构:config[group][name] = payload 50 | if (!isset($config[$setting->group])) { 51 | $config[$setting->group] = []; 52 | } 53 | $config[$setting->group][$setting->name] = $payload; 54 | } 55 | 56 | $this->config = $config; 57 | if (!empty($config)) { 58 | Cache::put('system-setting', $config); 59 | } 60 | } catch (\Exception $e) { 61 | $this->config = []; 62 | } 63 | } 64 | 65 | $this->loaded = true; 66 | } 67 | 68 | protected function saveConfig(): void 69 | { 70 | Cache::put('system-setting', $this->config); 71 | } 72 | 73 | public function refresh(): void 74 | { 75 | Cache::forget('system-setting'); 76 | $this->loaded = false; 77 | $this->config = []; 78 | } 79 | } -------------------------------------------------------------------------------- /resources/views/vendor/geetest/geetest.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

loading...

5 | 6 | @define use Illuminate\Support\Facades\Config 7 | 51 | 56 | -------------------------------------------------------------------------------- /app/Jobs/MailSend.php: -------------------------------------------------------------------------------- 1 | to = $to; 47 | $this->title = $title; 48 | $this->content = $content; 49 | } 50 | 51 | /** 52 | * Execute the job. 53 | * 54 | * @return void 55 | */ 56 | public function handle() 57 | { 58 | $body = $this->content; 59 | $title = $this->title; 60 | $sysConfig = cache('system-setting'); 61 | $mailConfig = [ 62 | 'driver' => $sysConfig['driver'] ?? 'smtp', 63 | 'host' => $sysConfig['host'] ?? '', 64 | 'port' => $sysConfig['port'] ?? '465', 65 | 'username' => $sysConfig['username'] ?? '', 66 | 'from' => [ 67 | 'address' => $sysConfig['from_address'] ?? '', 68 | 'name' => $sysConfig['from_name'] ?? '独角发卡' 69 | ], 70 | 'password' => $sysConfig['password'] ?? '', 71 | 'encryption' => $sysConfig['encryption'] ?? '' 72 | ]; 73 | $to = $this->to; 74 | // 覆盖 mail 配置 75 | config([ 76 | 'mail' => array_merge(config('mail'), $mailConfig) 77 | ]); 78 | // 重新注册驱动 79 | (new MailServiceProvider(app()))->register(); 80 | Mail::send(['html' => 'email.mail'], ['body' => $body], function ($message) use ($to, $title){ 81 | $message->to($to)->subject($title); 82 | }); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/Services/Email.php: -------------------------------------------------------------------------------- 1 | '购买收据', 21 | 'manual_send_manage_mail' => '新订单通知', 22 | 'failed_order' => '订单失败', 23 | 'completed_order' => '订单完成', 24 | 'pending_order' => '订单待处理' 25 | ]; 26 | 27 | public function getTemplate(string $token): Emailtpl 28 | { 29 | return cache()->remember("email_{$token}", 86400, function () use ($token) { 30 | $dbTemplate = Emailtpl::where('tpl_token', $token)->first(); 31 | 32 | if (isset(self::$templates[$token])) { 33 | $template = $this->loadTemplate($token); 34 | if ($dbTemplate) { 35 | $template->tpl_name = $dbTemplate->tpl_name; 36 | } 37 | return $template; 38 | } 39 | 40 | return $dbTemplate; 41 | }); 42 | } 43 | 44 | private function loadTemplate(string $token): Emailtpl 45 | { 46 | $path = resource_path("email-templates/{$token}.html"); 47 | $content = file_exists($path) ? file_get_contents($path) : '

模板不存在

'; 48 | 49 | $template = new Emailtpl(); 50 | $template->tpl_name = self::$templates[$token]; 51 | $template->tpl_content = $content; 52 | $template->tpl_token = $token; 53 | 54 | return $template; 55 | } 56 | 57 | public static function getTemplates(): array 58 | { 59 | return self::$templates; 60 | } 61 | 62 | public function parse(string $template, Order $order): string 63 | { 64 | $context = EmailVariableResolver::createContext($order); 65 | return EmailVariableResolver::resolve($template, $context); 66 | } 67 | 68 | 69 | public function getEmail(string $token, Order $order): array 70 | { 71 | $template = $this->getTemplate($token); 72 | 73 | if (!$template) { 74 | throw new \Exception("模板 {$token} 不存在"); 75 | } 76 | 77 | return [ 78 | 'title' => $this->parse($template->tpl_name, $order), 79 | 'content' => $this->parse($template->tpl_content, $order), 80 | ]; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/system-setting.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright assimon 7 | * @link http://utf8.hk/ 8 | */ 9 | 10 | return [ 11 | 'labels' => [ 12 | 'SystemSetting' => '系统设置', 13 | 'system_setting' => '系统设置', 14 | 'base_setting' => '基本设置', 15 | 'mail_setting' => '邮件服务', 16 | 'order_push_setting' => '订单推送配置', 17 | 'order_setting' => '下单设置', 18 | 'geetest' => '极验验证', 19 | ], 20 | 21 | 'fields' => [ 22 | 'title' => '网站标题', 23 | 'text_logo' => '文字LOGO', 24 | 'img_logo' => '图片LOGO', 25 | 'keywords' => '网站关键词', 26 | 'description' => '网站描述', 27 | 'notice' => '站点公告', 28 | 'footer' => '页脚自定义代码', 29 | 'manage_email' => '管理员邮箱', 30 | 'is_open_anti_red' => '是否开启微信/QQ防红', 31 | 'is_open_search_pwd' => '是否开启查询密码', 32 | 'is_open_google_translate' => '是否开启google翻译', 33 | 34 | 'is_open_img_code' => '是否开启图形验证码', 35 | 'is_cn_challenge' => '中国大陆访问需要人机验证', 36 | 'order_expire_time' => '订单过期时间(分钟)', 37 | 'order_ip_limits' => '单IP同时待支付订单数量', 38 | 39 | 'is_open_server_jiang' => '是否开启server酱', 40 | 'server_jiang_token' => 'server酱通讯token', 41 | 'is_open_telegram_push' => '是否开启Telegram推送', 42 | 'telegram_userid' => 'Telegram用户id', 43 | 'telegram_bot_token' => 'Telegram通讯token', 44 | 'is_open_bark_push' => '是否开启Bark推送', 45 | 'is_open_bark_push_url' => '是否推送订单URL', 46 | 'bark_server' => 'Bark服务器', 47 | 'bark_token' => 'Bark通讯Token', 48 | 'is_open_qywxbot_push' => '是否开启企业微信Bot推送', 49 | 'qywxbot_key' => '企业微信Bot通讯Key', 50 | 51 | 'template' => '站点模板', 52 | 'language' => '站点语言', 53 | 'currency' => '站点货币', 54 | 55 | 'driver' => '邮件驱动', 56 | 'host' => 'smtp服务器地址', 57 | 'port' => '端口', 58 | 'username' => '账号', 59 | 'password' => '密码', 60 | 'encryption' => '协议', 61 | 'from_address' => '发件地址', 62 | 'from_name' => '发件名称', 63 | 64 | 'geetest_id' => '极验id', 65 | 'geetest_key' => '极验key', 66 | 'is_open_geetest' => '是否开启极验', 67 | ], 68 | 'options' => [ 69 | ], 70 | 'rule_messages' => [ 71 | 'save_system_setting_success' => '系统配置保存成功!', 72 | 'change_reboot_php_worker' => '修改部分配置需要重启[supervisor]或php进程管理工具才会生效,例如邮件服务,server酱等。' 73 | ] 74 | ]; 75 | -------------------------------------------------------------------------------- /app/Jobs/TelegramPush.php: -------------------------------------------------------------------------------- 1 | order = $order; 52 | $this->goodsService = app('App\Services\Shop'); 53 | } 54 | 55 | /** 56 | * Execute the job. 57 | * 58 | * @return void 59 | */ 60 | public function handle() 61 | { 62 | $goodInfo = $this->goodsService->detail($this->order->goods_id); 63 | $formatText = '*'. __('dujiaoka.prompt.new_order_push').'('.$this->order->actual_price.'元)*%0A' 64 | . __('order.fields.order_id') .': `'.$this->order->id.'`%0A' 65 | . __('order.fields.order_sn') .': `'.$this->order->order_sn.'`%0A' 66 | . __('order.fields.pay_id') .': `'.$this->order->pay->pay_name.'`%0A' 67 | . __('order.fields.title') .': '.$this->order->title.'%0A' 68 | . __('order.fields.actual_price') .': '.$this->order->actual_price.'%0A' 69 | . __('order.fields.email') .': `'.$this->order->email.'`%0A' 70 | . __('goods.fields.gd_name') .': `'.$goodInfo->gd_name.'`%0A' 71 | . __('goods.fields.stock') .': `'.$goodInfo->stock.'`%0A' 72 | . __('order.fields.order_created') .': '.$this->order->created_at; 73 | $client = new Client([ 74 | 'timeout' => 30, 75 | 'proxy'=> '' 76 | ]); 77 | $apiUrl = 'https://api.telegram.org/bot' . cfg('telegram_bot_token') . 78 | '/sendMessage?chat_id=' . cfg('telegram_userid') . '&parse_mode=Markdown&text='.$formatText; 79 | $client->post($apiUrl); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/Models/Articles.php: -------------------------------------------------------------------------------- 1 | link) && !empty($article->title)) { 28 | $article->link = static::generateSlug($article->title); 29 | } 30 | }); 31 | 32 | static::updating(function ($article) { 33 | if (empty($article->link) && !empty($article->title)) { 34 | $article->link = static::generateSlug($article->title); 35 | } 36 | }); 37 | } 38 | 39 | public static function generateSlug($title) 40 | { 41 | $slug = Pinyin::permalink($title, '-'); 42 | 43 | if (empty($slug)) { 44 | $slug = preg_replace('/[^a-zA-Z0-9\s]/', '', $title); 45 | $slug = str_replace(' ', '-', trim($slug)); 46 | $slug = strtolower($slug); 47 | 48 | if (empty($slug)) { 49 | $slug = 'article-' . time(); 50 | } 51 | } 52 | 53 | // 确保唯一性 54 | $originalSlug = $slug; 55 | $counter = 1; 56 | while (static::where('link', $slug)->exists()) { 57 | $slug = $originalSlug . '-' . $counter; 58 | $counter++; 59 | } 60 | 61 | return $slug; 62 | } 63 | 64 | /** 65 | * 关联分类 66 | */ 67 | public function category() 68 | { 69 | return $this->belongsTo(ArticleCategory::class, 'category_id'); 70 | } 71 | 72 | /** 73 | * 关联商品 74 | */ 75 | public function goods() 76 | { 77 | return $this->belongsToMany(Goods::class, 'article_goods', 'article_id', 'goods_id') 78 | ->withTimestamps() 79 | ->withPivot('sort') 80 | ->orderBy('pivot_sort', 'desc'); 81 | } 82 | 83 | public function getSummary() 84 | { 85 | //简单地输出摘要 86 | $summary = substr($this->content, 0, 200); 87 | return strip_tags($summary); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/Admin/Widgets/SalesOverviewWidget.php: -------------------------------------------------------------------------------- 1 | startOfDay(); 22 | $todayEnd = $now->copy()->endOfDay(); 23 | $todayOrders = Order::whereBetween('created_at', [$todayStart, $todayEnd]) 24 | ->where('status', 4) // 已完成 25 | ->count(); 26 | $todayRevenue = Order::whereBetween('created_at', [$todayStart, $todayEnd]) 27 | ->where('status', 4) 28 | ->sum('actual_price'); 29 | 30 | // 本周数据 31 | $weekStart = $now->copy()->startOfWeek(); 32 | $weekEnd = $now->copy()->endOfWeek(); 33 | $weekOrders = Order::whereBetween('created_at', [$weekStart, $weekEnd]) 34 | ->where('status', 4) 35 | ->count(); 36 | $weekRevenue = Order::whereBetween('created_at', [$weekStart, $weekEnd]) 37 | ->where('status', 4) 38 | ->sum('actual_price'); 39 | 40 | // 本月数据 41 | $monthStart = $now->copy()->startOfMonth(); 42 | $monthEnd = $now->copy()->endOfMonth(); 43 | $monthOrders = Order::whereBetween('created_at', [$monthStart, $monthEnd]) 44 | ->where('status', 4) 45 | ->count(); 46 | $monthRevenue = Order::whereBetween('created_at', [$monthStart, $monthEnd]) 47 | ->where('status', 4) 48 | ->sum('actual_price'); 49 | 50 | return [ 51 | Stat::make('今日销售', '¥' . number_format($todayRevenue, 2)) 52 | ->description('订单数: ' . $todayOrders) 53 | ->icon('heroicon-m-currency-dollar') 54 | ->color('success'), 55 | 56 | Stat::make('本周销售', '¥' . number_format($weekRevenue, 2)) 57 | ->description('订单数: ' . $weekOrders) 58 | ->icon('heroicon-m-chart-bar') 59 | ->color('info'), 60 | 61 | Stat::make('本月销售', '¥' . number_format($monthRevenue, 2)) 62 | ->description('订单数: ' . $monthOrders) 63 | ->icon('heroicon-m-chart-pie') 64 | ->color('warning'), 65 | ]; 66 | } 67 | } -------------------------------------------------------------------------------- /app/Jobs/BarkPush.php: -------------------------------------------------------------------------------- 1 | order = $order; 53 | $this->goodsService = app('App\Services\Shop'); 54 | } 55 | 56 | /** 57 | * Execute the job. 58 | * 59 | * @return void 60 | */ 61 | public function handle() 62 | { 63 | $goodInfo = $this->goodsService->detail($this->order->goods_id); 64 | $client = new Client(); 65 | $apiUrl = cfg('bark_server') .'/'. cfg('bark_token'); 66 | $params = [ 67 | "title" => __('dujiaoka.prompt.new_order_push').'('.$this->order->actual_price.'元)', 68 | "body" => __('order.fields.order_id') .': '.$this->order->id."\n" 69 | . __('order.fields.order_sn') .': '.$this->order->order_sn."\n" 70 | . __('order.fields.pay_id') .': '.$this->order->pay->pay_name."\n" 71 | . __('order.fields.title') .': '.$this->order->title."\n" 72 | . __('order.fields.actual_price') .': '.$this->order->actual_price."\n" 73 | . __('order.fields.email') .': '.$this->order->email."\n" 74 | . __('goods.fields.gd_name') .': '.$goodInfo->gd_name."\n" 75 | . __('goods.fields.stock') .': '.$goodInfo->stock."\n" 76 | . __('order.fields.order_created') .': '.$this->order->created_at, 77 | "icon"=>url('assets/common/images/default.jpg'), 78 | "level"=>"timeSensitive", 79 | "group"=>cfg('text_logo', '独角数卡') 80 | ]; 81 | if (cfg('is_open_bark_push_url', 0) == BaseModel::STATUS_OPEN) { 82 | $params["url"] = url('detail-order-sn/'.$this->order->order_sn); 83 | } 84 | $client->post($apiUrl,['form_params' => $params, 'verify' => false]); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /public/assets/common/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/Config/ConfigManager.php: -------------------------------------------------------------------------------- 1 | cachePrefix . $key; 16 | 17 | return Cache::remember($cacheKey, $this->cacheTime, function () use ($key, $default) { 18 | return $this->resolveConfig($key, $default); 19 | }); 20 | } 21 | 22 | public function set(string $key, $value): void 23 | { 24 | $this->configs[$key] = $value; 25 | Cache::put($this->cachePrefix . $key, $value, $this->cacheTime); 26 | } 27 | 28 | public function forget(string $key): void 29 | { 30 | unset($this->configs[$key]); 31 | Cache::forget($this->cachePrefix . $key); 32 | } 33 | 34 | public function flush(): void 35 | { 36 | $this->configs = []; 37 | Cache::flush(); 38 | } 39 | 40 | protected function resolveConfig(string $key, $default) 41 | { 42 | if (isset($this->configs[$key])) { 43 | return $this->configs[$key]; 44 | } 45 | 46 | $parts = explode('.', $key); 47 | $type = $parts[0]; 48 | 49 | return match ($type) { 50 | 'system' => $this->getSystemConfig($key, $default), 51 | 'theme' => $this->getThemeConfig($key, $default), 52 | 'payment' => $this->getPaymentConfig($key, $default), 53 | default => $default, 54 | }; 55 | } 56 | 57 | protected function getSystemConfig(string $key, $default) 58 | { 59 | $systemConfig = Cache::get('system-setting', []); 60 | $configKey = str_replace('system.', '', $key); 61 | 62 | return $systemConfig[$configKey] ?? $default; 63 | } 64 | 65 | protected function getThemeConfig(string $key, $default) 66 | { 67 | $parts = explode('.', $key); 68 | $theme = $parts[1] ?? app('theme.manager')->getActiveTheme(); 69 | $configKey = implode('.', array_slice($parts, 2)); 70 | 71 | $themeConfig = Cache::get("theme-{$theme}-config", []); 72 | 73 | return data_get($themeConfig, $configKey, $default); 74 | } 75 | 76 | protected function getPaymentConfig(string $key, $default) 77 | { 78 | $paymentConfig = Cache::get('payment-config', []); 79 | $configKey = str_replace('payment.', '', $key); 80 | 81 | return $paymentConfig[$configKey] ?? $default; 82 | } 83 | } -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DRIVER', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Cloud Filesystem Disk 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Many applications store files both locally and in the cloud. For this 24 | | reason, you may specify a default "cloud" driver here. This driver 25 | | will be bound as the Cloud disk implementation in the container. 26 | | 27 | */ 28 | 29 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Filesystem Disks 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here you may configure as many filesystem "disks" as you wish, and you 37 | | may even configure multiple disks of the same driver. Defaults have 38 | | been setup for each driver as an example of the required options. 39 | | 40 | | Supported Drivers: "local", "ftp", "sftp", "s3" 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'public' => [ 52 | 'driver' => 'local', 53 | 'root' => storage_path('app/public'), 54 | 'url' => env('APP_URL').'/storage', 55 | 'visibility' => 'public', 56 | ], 57 | 58 | 's3' => [ 59 | 'driver' => 's3', 60 | 'key' => env('AWS_ACCESS_KEY_ID'), 61 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 62 | 'region' => env('AWS_DEFAULT_REGION'), 63 | 'bucket' => env('AWS_BUCKET'), 64 | 'url' => env('AWS_URL'), 65 | 'endpoint' => env('AWS_ENDPOINT'), 66 | ], 67 | 68 | 'admin' => [ 69 | 'driver' => 'local', 70 | 'root' => public_path('uploads'), 71 | 'visibility' => 'public', 72 | 'url' => env('APP_URL').'/uploads', 73 | ], 74 | ], 75 | 76 | ]; 77 | -------------------------------------------------------------------------------- /app/Http/Controllers/Home/CartController.php: -------------------------------------------------------------------------------- 1 | render('static_pages/cart', [], '购物车'); 17 | } 18 | 19 | public function validateItem(Request $request) 20 | { 21 | $params = $request->only(['goods_id', 'sub_id', 'quantity']); 22 | $qty = (int) ($params['quantity'] ?? 1); 23 | 24 | $goods = cache()->remember("goods_with_sub_{$params['goods_id']}", 21600, function () use ($params) { 25 | return Goods::with('goods_sub')->find($params['goods_id']); 26 | }); 27 | 28 | if (!$goods?->is_open) { 29 | return $this->fail('商品不存在或已下架'); 30 | } 31 | 32 | $sub = $goods->goods_sub()->find($params['sub_id']); 33 | if (!$sub) { 34 | return $this->fail('商品规格不存在'); 35 | } 36 | 37 | $stock = $goods->type == Goods::AUTOMATIC_DELIVERY 38 | ? Carmis::where('sub_id', $params['sub_id'])->where('status', 1)->count() 39 | : $sub->stock; 40 | 41 | if ($qty > $stock) { 42 | return $this->fail("库存不足,当前库存:{$stock}"); 43 | } 44 | 45 | if ($goods->buy_limit_num > 0 && $qty > $goods->buy_limit_num) { 46 | return $this->fail("超出限购数量:{$goods->buy_limit_num}"); 47 | } 48 | 49 | $enabledPays = cache()->remember('enabled_pay_methods', 43200, function () { 50 | return Pay::enabled()->get(); 51 | }); 52 | $payways = empty($goods->payment_limit) 53 | ? $enabledPays->toArray() 54 | : $enabledPays->whereIn('id', $goods->payment_limit)->values()->toArray(); 55 | 56 | return $this->success([ 57 | 'goods_id' => $goods->id, 58 | 'sub_id' => $sub->id, 59 | 'name' => "{$goods->gd_name} [{$sub->name}]", 60 | 'price' => $sub->price, 61 | 'image' => pictureUrl($goods->picture), 62 | 'stock' => $stock, 63 | 'max_quantity' => min($stock, $goods->buy_limit_num ?: $stock), 64 | 'payways' => $payways 65 | ]); 66 | } 67 | 68 | private function success($data = []) 69 | { 70 | return response()->json(['success' => true, 'data' => $data]); 71 | } 72 | 73 | private function fail($message) 74 | { 75 | return response()->json(['success' => false, 'message' => $message]); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /app/Http/Controllers/InstallController.php: -------------------------------------------------------------------------------- 1 | validate([ 30 | 'db_host' => 'required|string', 31 | 'db_port' => 'required|integer', 32 | 'db_database' => 'required|string', 33 | 'db_username' => 'required|string', 34 | 'db_password' => 'nullable|string', 35 | 'redis_host' => 'required|string', 36 | 'redis_port' => 'required|integer', 37 | 'redis_password' => 'nullable|string', 38 | 'title' => 'required|string', 39 | 'app_url' => 'required|url', 40 | 'admin_path' => 'required|string|regex:/^\/[a-zA-Z0-9_-]+$/', 41 | ]); 42 | 43 | // 测试数据库连接 44 | $dbTestResult = Installer::testDatabase([ 45 | 'host' => $validated['db_host'], 46 | 'port' => $validated['db_port'], 47 | 'database' => $validated['db_database'], 48 | 'username' => $validated['db_username'], 49 | 'password' => $validated['db_password'] ?? '', 50 | ]); 51 | 52 | if ($dbTestResult !== true) { 53 | $errorMessage = '数据库连接失败: ' . ($dbTestResult['error'] ?? '请检查配置'); 54 | return response()->json(['error' => $errorMessage], 400); 55 | } 56 | 57 | // 测试Redis连接 58 | if (!Installer::testRedis([ 59 | 'host' => $validated['redis_host'], 60 | 'port' => $validated['redis_port'], 61 | 'password' => $validated['redis_password'] ?? '', 62 | ])) { 63 | return response()->json(['error' => 'Redis连接失败,请检查配置'], 400); 64 | } 65 | $validated['cache_driver'] = 'redis'; 66 | $validated['queue_connection'] = 'redis'; 67 | 68 | // 执行安装 69 | $result = Installer::install($validated); 70 | 71 | if ($result === 'success') { 72 | return response()->json(['success' => true, 'message' => '安装成功!']); 73 | } 74 | 75 | return response()->json(['error' => $result], 500); 76 | } 77 | } --------------------------------------------------------------------------------