├── api ├── public │ ├── favicon.ico │ ├── robots.txt │ ├── .htaccess │ ├── web.config │ └── index.php ├── resources │ ├── sass │ │ └── app.scss │ ├── js │ │ ├── app.js │ │ └── bootstrap.js │ └── lang │ │ ├── en │ │ ├── pagination.php │ │ ├── auth.php │ │ └── passwords.php │ │ └── zh-CN │ │ ├── base.php │ │ └── user.php ├── bootstrap │ ├── cache │ │ └── .gitignore │ └── app.php ├── storage │ ├── logs │ │ └── .gitignore │ ├── app │ │ ├── public │ │ │ └── .gitignore │ │ └── .gitignore │ └── framework │ │ ├── sessions │ │ └── .gitignore │ │ ├── testing │ │ └── .gitignore │ │ ├── views │ │ └── .gitignore │ │ ├── cache │ │ ├── data │ │ │ └── .gitignore │ │ └── .gitignore │ │ └── .gitignore ├── database │ ├── .gitignore │ ├── seeds │ │ └── DatabaseSeeder.php │ └── factories │ │ └── UserFactory.php ├── .gitattributes ├── tests │ ├── TestCase.php │ ├── Unit │ │ ├── ExampleTest.php │ │ └── HashTest.php │ ├── Feature │ │ ├── ExampleTest.php │ │ └── User │ │ │ └── RegisterTest.php │ └── CreatesApplication.php ├── .gitignore ├── .editorconfig ├── .styleci.yml ├── readme.md ├── app │ ├── Events │ │ └── NewOrderRecordEvent.php │ ├── Http │ │ ├── Middleware │ │ │ ├── EncryptCookies.php │ │ │ ├── CheckForMaintenanceMode.php │ │ │ ├── TrimStrings.php │ │ │ ├── TrustProxies.php │ │ │ ├── Authenticate.php │ │ │ ├── VerifyCsrfToken.php │ │ │ ├── LangDetect.php │ │ │ ├── RedirectIfAuthenticated.php │ │ │ └── JwtAuthMiddleware.php │ │ ├── Requests │ │ │ ├── BasePageListPost.php │ │ │ ├── Backend │ │ │ │ ├── ImageUploadPost.php │ │ │ │ ├── UserEditPost.php │ │ │ │ ├── BannerListPost.php │ │ │ │ ├── UserListPost.php │ │ │ │ ├── WithdrawListPost.php │ │ │ │ ├── RegisterAdminPost.php │ │ │ │ ├── TradeListPost.php │ │ │ │ ├── OrderListPost.php │ │ │ │ ├── AdminUserEditPost.php │ │ │ │ ├── EditPasswordPost.php │ │ │ │ ├── OrderEditPost.php │ │ │ │ ├── BannerEditPost.php │ │ │ │ └── OrderCreatePost.php │ │ │ ├── WithdrawCreatePost.php │ │ │ ├── RegisterPost.php │ │ │ ├── OrderListPost.php │ │ │ ├── EditPasswordPost.php │ │ │ └── EditProfilePost.php │ │ └── Controllers │ │ │ ├── Backend │ │ │ ├── UploadController.php │ │ │ ├── TradeController.php │ │ │ └── DashboardController.php │ │ │ ├── Controller.php │ │ │ └── Frontend │ │ │ ├── BannerController.php │ │ │ └── OrderController.php │ ├── Services │ │ ├── BaseService.php │ │ ├── Frontend │ │ │ ├── BannerService.php │ │ │ ├── OrderService.php │ │ │ └── UserService.php │ │ └── Backend │ │ │ ├── BannerService.php │ │ │ ├── TradeService.php │ │ │ └── AdminUserService.php │ ├── Providers │ │ ├── BroadcastServiceProvider.php │ │ ├── AuthServiceProvider.php │ │ ├── AppServiceProvider.php │ │ ├── EventServiceProvider.php │ │ └── RouteServiceProvider.php │ ├── Enum │ │ ├── UserEnum.php │ │ ├── BannerEnum.php │ │ ├── CodeEnum.php │ │ ├── OrderEnum.php │ │ └── CommonEnum.php │ ├── Repositories │ │ ├── Contracts │ │ │ └── BannerRepositoryInterface.php │ │ └── BannerRepository.php │ ├── User.php │ ├── Console │ │ └── Kernel.php │ ├── Exceptions │ │ ├── Handler.php │ │ └── MyExceptionHandler.php │ ├── Listeners │ │ ├── QueryListener.php │ │ └── NewOrderRecordListener.php │ ├── Models │ │ ├── FinanceTradeModel.php │ │ ├── FindListTrait.php │ │ ├── FinanceWithdrawModel.php │ │ ├── OrderInfoModel.php │ │ ├── OrderGoodsModel.php │ │ ├── UserBalanceModel.php │ │ ├── ValidateBaseModel.php │ │ ├── AdminUserModel.php │ │ └── BannerModel.php │ └── Utils │ │ └── UtilHelper.php ├── routes │ ├── channels.php │ ├── api.php │ ├── console.php │ ├── web.php │ └── backend.php ├── webpack.mix.js ├── server.php ├── config │ ├── services.php │ ├── view.php │ ├── hashing.php │ ├── broadcasting.php │ ├── filesystems.php │ ├── queue.php │ └── logging.php ├── .env.production ├── .env.local ├── phpunit.xml ├── composer.json └── artisan ├── frontend ├── .browserslistrc ├── cypress.json ├── .env.production ├── .env.development ├── tests │ ├── unit │ │ ├── .eslintrc.js │ │ ├── reg.test.js │ │ ├── login.test.js │ │ ├── banner.js │ │ ├── helloword.test.js │ │ └── home.js │ └── e2e │ │ ├── .eslintrc.js │ │ ├── specs │ │ └── test.js │ │ ├── support │ │ ├── index.js │ │ └── commands.js │ │ └── plugins │ │ └── index.js ├── src │ ├── assets │ │ ├── bg.jpg │ │ ├── logo.png │ │ ├── avatar.png │ │ ├── avatar_0.png │ │ └── avatar_1.png │ ├── app.less │ ├── App.vue │ ├── store │ │ └── index.js │ ├── main.js │ ├── views │ │ ├── Layout.vue │ │ ├── Banner.vue │ │ ├── Home.vue │ │ ├── user │ │ │ ├── Trade.vue │ │ │ ├── WithdrawList.vue │ │ │ └── Order.vue │ │ ├── Login.vue │ │ └── Reg.vue │ ├── router │ │ └── index.js │ └── components │ │ └── HelloWorld.vue ├── public │ ├── favicon.ico │ └── index.html ├── babel.config.js ├── jest.config.js.bak ├── .gitignore ├── README.md ├── webpack.config.js ├── .eslintrc.js └── package.json ├── .DS_Store ├── screenshots ├── banners.png ├── orders.png ├── profile.png ├── signin.png ├── signup.png ├── admin-user.png ├── homepage.png ├── admin-order.png ├── admin-banners.png ├── admin-banner-edit.png └── admin-order-create.png ├── backend ├── .env.development ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ ├── bg.jpg │ │ ├── logo.png │ │ ├── avatar.png │ │ ├── avatar_0.png │ │ └── avatar_1.png │ ├── App.vue │ ├── store │ │ └── index.js │ ├── main.js │ ├── router │ │ └── index.js │ ├── app.less │ ├── views │ │ ├── Dashboard.vue │ │ ├── Login.vue │ │ └── Reg.vue │ └── components │ │ └── HelloWorld.vue ├── babel.config.js ├── .gitignore ├── README.md └── package.json ├── .gitignore └── SECURITY.md /api/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/resources/sass/app.scss: -------------------------------------------------------------------------------- 1 | // 2 | -------------------------------------------------------------------------------- /api/bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /api/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /api/resources/js/app.js: -------------------------------------------------------------------------------- 1 | require('./bootstrap'); 2 | -------------------------------------------------------------------------------- /api/storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /api/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | *.sqlite-journal 3 | -------------------------------------------------------------------------------- /api/storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /api/storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /api/storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /api/storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /api/storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /api/storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/.DS_Store -------------------------------------------------------------------------------- /api/storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /frontend/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_BASE_URL="http://api.edu.com" 3 | -------------------------------------------------------------------------------- /frontend/.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_API_HOST="http://www.edu.com/api" 3 | -------------------------------------------------------------------------------- /frontend/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | } 5 | } -------------------------------------------------------------------------------- /screenshots/banners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/banners.png -------------------------------------------------------------------------------- /screenshots/orders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/orders.png -------------------------------------------------------------------------------- /screenshots/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/profile.png -------------------------------------------------------------------------------- /screenshots/signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/signin.png -------------------------------------------------------------------------------- /screenshots/signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/signup.png -------------------------------------------------------------------------------- /backend/.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_API_HOST="http://admin.dev.com/api" 3 | 4 | -------------------------------------------------------------------------------- /backend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/backend/public/favicon.ico -------------------------------------------------------------------------------- /backend/src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/backend/src/assets/bg.jpg -------------------------------------------------------------------------------- /frontend/src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/frontend/src/assets/bg.jpg -------------------------------------------------------------------------------- /screenshots/admin-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/admin-user.png -------------------------------------------------------------------------------- /screenshots/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/homepage.png -------------------------------------------------------------------------------- /backend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/backend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /screenshots/admin-order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/admin-order.png -------------------------------------------------------------------------------- /backend/src/assets/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/backend/src/assets/avatar.png -------------------------------------------------------------------------------- /backend/src/assets/avatar_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/backend/src/assets/avatar_0.png -------------------------------------------------------------------------------- /backend/src/assets/avatar_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/backend/src/assets/avatar_1.png -------------------------------------------------------------------------------- /frontend/src/assets/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/frontend/src/assets/avatar.png -------------------------------------------------------------------------------- /screenshots/admin-banners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/admin-banners.png -------------------------------------------------------------------------------- /backend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/assets/avatar_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/frontend/src/assets/avatar_0.png -------------------------------------------------------------------------------- /frontend/src/assets/avatar_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/frontend/src/assets/avatar_1.png -------------------------------------------------------------------------------- /screenshots/admin-banner-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/admin-banner-edit.png -------------------------------------------------------------------------------- /screenshots/admin-order-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imbin/affiliate/HEAD/screenshots/admin-order-create.png -------------------------------------------------------------------------------- /api/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /api/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 9 | -------------------------------------------------------------------------------- /frontend/jest.config.js.bak: -------------------------------------------------------------------------------- 1 | const {defaults} = require('jest-config'); 2 | module.exports = { 3 | // ... 4 | moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'], 5 | // ... 6 | }; -------------------------------------------------------------------------------- /api/tests/TestCase.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /frontend/tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /api/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /api/.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | enabled: 4 | - alpha_ordered_imports 5 | disabled: 6 | - length_ordered_imports 7 | - unused_use 8 | finder: 9 | not-name: 10 | - index.php 11 | - server.php 12 | js: 13 | finder: 14 | not-name: 15 | - webpack.mix.js 16 | css: true 17 | -------------------------------------------------------------------------------- /api/readme.md: -------------------------------------------------------------------------------- 1 | ## 订单产生流程 2 | 3 | 1. 前台领取素材链接 4 | 1. 消费者点击素材链接,下单购买商品 5 | 1. 联盟平台收到成交订单信息 6 | 1. 计算订单佣金 7 | 1. 完成订单收录 8 | 9 | ## 提现流程 10 | 1. 订单付款时间加上审核周期(30天)后,订单有效(已付款、已发货),发放佣金到联盟客账号余额中,记录一笔佣金收入 11 | 1. 联盟客操作申请余额提现 12 | 1. 后台审核提现申请,不符合的情况驳回并注明原因 13 | 1. 审核通过的提现申请,后台打款后,标记提现成功,记录一笔联盟客财务支出 14 | 15 | ## 测试账号 16 | 17 | test / test 18 | 19 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | /__tests__ -------------------------------------------------------------------------------- /api/app/Events/NewOrderRecordEvent.php: -------------------------------------------------------------------------------- 1 | orderId = $orderId; 17 | } 18 | } -------------------------------------------------------------------------------- /frontend/tests/unit/reg.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { shallowMount } from '@vue/test-utils' 3 | import Page from '@/views/Reg.vue' 4 | 5 | describe('页面测试', () => { 6 | it('页面正常渲染', () => { 7 | const msg = '会员注册' 8 | const wrapper = shallowMount(Page) 9 | expect(wrapper.text()).to.include(msg) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /api/database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(UsersTableSeeder::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/tests/unit/login.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { shallowMount } from '@vue/test-utils' 3 | import Page from '@/views/Login.vue' 4 | 5 | describe('页面测试', () => { 6 | it('页面正常渲染', () => { 7 | const msg = '会员登录' 8 | const wrapper = shallowMount(Page) 9 | expect(wrapper.text()).to.include(msg) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /frontend/tests/unit/banner.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { shallowMount } from '@vue/test-utils' 3 | import Page from '@/components/BannerList.vue' 4 | 5 | describe('组件测试', () => { 6 | it('正常渲染', () => { 7 | const wrapper = shallowMount(Page, { 8 | pagination:true, perPage: 12 9 | }) 10 | expect(wrapper.classes('el-card')).toBe(true) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /api/app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /api/app/Services/BaseService.php: -------------------------------------------------------------------------------- 1 | make(static::class); 21 | } 22 | } -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # frontend 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # frontend 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const nodeExternals = require('webpack-node-externals') 2 | 3 | module.exports = { 4 | // ... 5 | externals: [nodeExternals()], 6 | 7 | devtool: 'inline-cheap-module-source-map', 8 | output: { 9 | // ... 10 | // 在源码表中使用绝对路径 (对于在 IDE 中调试时很重要) 11 | devtoolModuleFilenameTemplate: '[absolute-resource-path]', 12 | devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]' 13 | } 14 | } -------------------------------------------------------------------------------- /frontend/src/app.less: -------------------------------------------------------------------------------- 1 | @header-color: #1976D2; 2 | @white: #ffffff; 3 | @bg-color: #EEF5F9; 4 | 5 | body { 6 | margin: 0; 7 | padding: 0; 8 | background-color: @white; 9 | font-size: 14px; 10 | } 11 | h1 { 12 | font-weight: 300; 13 | } 14 | 15 | .clearfix:before, 16 | .clearfix:after { 17 | display: table; 18 | content: ""; 19 | } 20 | 21 | .clearfix:after { 22 | clear: both 23 | } 24 | 25 | .el-main { 26 | padding-top: 0; 27 | } -------------------------------------------------------------------------------- /api/app/Http/Middleware/CheckForMaintenanceMode.php: -------------------------------------------------------------------------------- 1 | { 6 | it('renders props.msg when passed', () => { 7 | const msg = 'new message' 8 | const wrapper = shallowMount(HelloWorld, { 9 | propsData: { msg } 10 | }) 11 | expect(wrapper.text()).to.include(msg) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /api/tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/tests/Feature/User/RegisterTest.php: -------------------------------------------------------------------------------- 1 | findByUserName('test'); 20 | $this->assertIsObject($model); 21 | } 22 | } -------------------------------------------------------------------------------- /frontend/tests/unit/home.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { shallowMount } from '@vue/test-utils' 3 | import Page from '@/views/Home.vue' 4 | 5 | describe('页面测试', () => { 6 | it('页面正常渲染', () => { 7 | const msg = '加入联盟' 8 | const wrapper = shallowMount(Page) 9 | expect(wrapper.text()).to.include(msg) 10 | }), 11 | 12 | it('素材列表正常渲染', () => { 13 | const wrapper = shallowMount(Page) 14 | expect(wrapper.classes('el-card')).toBe(true) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /backend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex); 5 | 6 | const store = new Vuex.Store({ 7 | state: { 8 | loading: false, 9 | }, 10 | mutations: { 11 | LOADING: (state, loading) => { 12 | state.loading = loading; 13 | } 14 | }, 15 | actions: { 16 | Loading({ commit }, loading) { 17 | commit('LOADING', loading); 18 | } 19 | } 20 | }); 21 | 22 | export default store -------------------------------------------------------------------------------- /api/tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | '未操作', 20 | self::STATUS_PENDING => '待审核', 21 | self::STATUS_PASS => '审核通过', 22 | self::STATUS_REJECT => '驳回', 23 | ]; 24 | } -------------------------------------------------------------------------------- /api/app/Repositories/Contracts/BannerRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 16 | }); 17 | -------------------------------------------------------------------------------- /api/app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 17 | return $request->user(); 18 | }); 19 | -------------------------------------------------------------------------------- /api/app/Enum/BannerEnum.php: -------------------------------------------------------------------------------- 1 | '已上架', 19 | self::STATUS_OFF => '已下架', 20 | ]; 21 | 22 | const RETURN_TYPE_AMOUNT = 1; 23 | const RETURN_TYPE_PERCENT = 2; 24 | const RETURN_TYPE_TEXT_LIST = [ 25 | self::RETURN_TYPE_AMOUNT => '金额', 26 | self::RETURN_TYPE_PERCENT => '百分比', 27 | ]; 28 | } -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 15 | -------------------------------------------------------------------------------- /backend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | backend 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /api/resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /api/routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 18 | })->describe('Display an inspiring quote'); 19 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | frontend 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /api/app/Http/Middleware/LangDetect.php: -------------------------------------------------------------------------------- 1 | setLocale(config('app.locale')); 25 | return $next($request); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | "no-console": "off" 14 | }, 15 | parserOptions: { 16 | parser: 'babel-eslint' 17 | }, 18 | overrides: [ 19 | { 20 | files: [ 21 | '**/__tests__/*.{j,t}s?(x)' 22 | ], 23 | env: { 24 | mocha: true 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /api/app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/home'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /api/app/Http/Requests/BasePageListPost.php: -------------------------------------------------------------------------------- 1 | 'required|integer|min:1', 22 | 'perPage' => 'required|integer|min:5|max:100', 23 | ]; 24 | } 25 | 26 | public function attributes() 27 | { 28 | return [ 29 | 'page' => '页码', 30 | 'perPage' => '每页数量' 31 | ]; 32 | } 33 | } -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /api/app/Enum/CodeEnum.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 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/ImageUploadPost.php: -------------------------------------------------------------------------------- 1 | 'required|image|max:'.(5 * 1024),//5M 24 | ]; 25 | } 26 | 27 | public function attributes() 28 | { 29 | return [ 30 | 'image' => '图片文件', 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /api/resources/lang/zh-CN/base.php: -------------------------------------------------------------------------------- 1 | '登录已过期', 23 | 'server_error' => '服务器错误', 24 | 'invalid_parameter' => '参数错误', 25 | 'success' => 'Success', 26 | 27 | ]; -------------------------------------------------------------------------------- /api/app/Http/Requests/WithdrawCreatePost.php: -------------------------------------------------------------------------------- 1 | 'required|in:1,2', 28 | 'card' => 'required', 29 | 'name' => 'required', 30 | 'amount' => 'required|numeric|min:100', 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /api/app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind( 20 | BannerRepositoryInterface::class, 21 | BannerRepository::class // 使用这个具体类 22 | ); 23 | } 24 | 25 | /** 26 | * Bootstrap any application services. 27 | * 28 | * @return void 29 | */ 30 | public function boot() 31 | { 32 | // 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/UserEditPost.php: -------------------------------------------------------------------------------- 1 | 'required|min:4|max:20|regex:/^([a-zA-Z0-9\.\-_\/\+=\.\~\!@#\$\%\^\&\*\(\)\[\]\{\}]){4,20}$/', 25 | ]; 26 | } 27 | 28 | public function attributes() 29 | { 30 | return [ 31 | 'password' => '登录密码', 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api/resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 17 | 'sent' => 'We have e-mailed your password reset link!', 18 | 'token' => 'This password reset token is invalid.', 19 | 'user' => "We can't find a user with that e-mail address.", 20 | 'throttled' => 'Please wait before retrying.', 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /api/app/Http/Requests/RegisterPost.php: -------------------------------------------------------------------------------- 1 | 'required|min:1|max:20|regex:/^([a-zA-Z0-9]){4,20}$/', 27 | 'password' => 'required|min:4|max:20|regex:/^([a-zA-Z0-9\.\-_\/\+=\.\~\!@#\$\%\^\&\*\(\)\[\]\{\}]){4,20}/', 28 | 'passwordTwice' => 'required|same:password', 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /api/resources/lang/zh-CN/user.php: -------------------------------------------------------------------------------- 1 | '账号或密码错误', 23 | 'status_pending' => '账号审核中', 24 | 'name_exists' => '账号已存在', 25 | 'email_exists' => 'Email已存在', 26 | 'mobile_exists' => '手机号已存在', 27 | 28 | 'invalid_old_pwd' => '旧密码不正确', 29 | 30 | ]; -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/BannerListPost.php: -------------------------------------------------------------------------------- 1 | 'nullable|integer|in:1,2', 26 | 'title' => 'nullable|min:1|max:50', 27 | ]; 28 | } 29 | 30 | public function attributes() 31 | { 32 | return [ 33 | 'title' => '标题', 34 | 'status' => '上架状态', 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /api/app/User.php: -------------------------------------------------------------------------------- 1 | 'datetime', 38 | ]; 39 | } 40 | -------------------------------------------------------------------------------- /api/app/Http/Controllers/Backend/UploadController.php: -------------------------------------------------------------------------------- 1 | file('image'), $fileName.'.'.$post->file('image')->extension() 23 | ); 24 | 25 | $url = Storage::url($path); 26 | return $this->jsonSuccess([ 27 | 'file_url' => $url, 28 | 'thumb_url' => UtilHelper::thumbUrl( $url) 29 | ]); 30 | } 31 | } -------------------------------------------------------------------------------- /frontend/tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex); 5 | 6 | const store = new Vuex.Store({ 7 | state: { 8 | loading: false, 9 | userLogined: false, 10 | userName: null, 11 | }, 12 | mutations: { 13 | LOADING: (state, loading) => { 14 | state.loading = loading; 15 | }, 16 | LOGINED: (state, data) => { 17 | state.userLogined = data.logined; 18 | state.userName = data.userName; 19 | }, 20 | USERINFO: (state, data) => { 21 | state.userInfo = data; 22 | }, 23 | }, 24 | actions: { 25 | Loading({ commit }, loading) { 26 | commit('LOADING', loading); 27 | }, 28 | Logined({ commit }, data) { 29 | commit('LOGINED', data); 30 | }, 31 | UserInfo({ commit }, data) { 32 | commit('USERINFO', data); 33 | }, 34 | } 35 | }); 36 | 37 | export default store -------------------------------------------------------------------------------- /api/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 | // encrypted: true 28 | // }); 29 | -------------------------------------------------------------------------------- /frontend/tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | // if you need a custom webpack configuration you can uncomment the following import 4 | // and then use the `file:preprocessor` event 5 | // as explained in the cypress docs 6 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples 7 | 8 | /* eslint-disable import/no-extraneous-dependencies, global-require, arrow-body-style */ 9 | // const webpack = require('@cypress/webpack-preprocessor') 10 | 11 | module.exports = (on, config) => { 12 | // on('file:preprocessor', webpack({ 13 | // webpackOptions: require('@vue/cli-service/webpack.config'), 14 | // watchOptions: {} 15 | // })) 16 | 17 | return Object.assign({}, config, { 18 | fixturesFolder: 'tests/e2e/fixtures', 19 | integrationFolder: 'tests/e2e/specs', 20 | screenshotsFolder: 'tests/e2e/screenshots', 21 | videosFolder: 'tests/e2e/videos', 22 | supportFile: 'tests/e2e/support/index.js' 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /api/database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(User::class, function (Faker $faker) { 20 | return [ 21 | 'name' => $faker->name, 22 | 'email' => $faker->unique()->safeEmail, 23 | 'email_verified_at' => now(), 24 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 25 | 'remember_token' => Str::random(10), 26 | ]; 27 | }); 28 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/UserListPost.php: -------------------------------------------------------------------------------- 1 | 'nullable|integer|min:1', 28 | 'userName' => 'nullable|min:1|max:20|regex:/^([a-zA-Z0-9]){1,20}$/', 29 | 'nickName' => 'nullable|min:1|max:20', 30 | ]; 31 | } 32 | 33 | public function attributes() 34 | { 35 | return [ 36 | 'userName' => '登录名', 37 | 'nickName' => '昵称', 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /api/app/Http/Requests/OrderListPost.php: -------------------------------------------------------------------------------- 1 | 'nullable|integer|in:'.implode(',', [ 26 | OrderEnum::STATUS_PENDING, 27 | OrderEnum::STATUS_CANCELLED, 28 | OrderEnum::STATUS_PAYED, 29 | OrderEnum::STATUS_DELIVERED, 30 | ]), 31 | 'order_sn' => 'nullable|min:1|max:64', 32 | ]; 33 | } 34 | 35 | public function attributes() 36 | { 37 | return [ 38 | 'order_sn' => '订单号', 39 | 'status' => '订单状态', 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /api/app/Enum/OrderEnum.php: -------------------------------------------------------------------------------- 1 | '未付款', 22 | self::STATUS_CANCELLED => '已取消', 23 | self::STATUS_PAYED => '已付款', 24 | self::STATUS_DELIVERED => '已发货', 25 | ]; 26 | 27 | //佣金状态: 1=未发放、2=已发放、3=不发放 28 | const COMMISSION_STATUS_PENDING = 1;//未发放 29 | const COMMISSION_STATUS_GRANTED = 2;//已发放 30 | const COMMISSION_STATUS_NOT = 3;//不发放 31 | 32 | const COMMISSION_STATUS_TEXT_LIST = [ 33 | self::COMMISSION_STATUS_PENDING => '未发放', 34 | self::COMMISSION_STATUS_GRANTED => '已发放', 35 | self::COMMISSION_STATUS_NOT => '不发放', 36 | ]; 37 | 38 | //佣金发放周期,默认30天 39 | const ORDER_AUDIT_DAYS = 30; 40 | 41 | 42 | } -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/WithdrawListPost.php: -------------------------------------------------------------------------------- 1 | 'nullable', 27 | 'status' => 'nullable|in:'.implode(',', [ 28 | CommonEnum::WITHDRAW_STATUS_PENDING, 29 | CommonEnum::WITHDRAW_STATUS_COMPLETE, 30 | CommonEnum::WITHDRAW_STATUS_REJECT, 31 | ]), 32 | ]; 33 | } 34 | 35 | public function attributes() 36 | { 37 | return [ 38 | 'sn' => '提现单号', 39 | 'status' => '提现状态', 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/RegisterAdminPost.php: -------------------------------------------------------------------------------- 1 | 'required|min:1|max:20|regex:/^([a-zA-Z0-9]){4,20}$/', 28 | 'realName' => 'required|min:1|max:20', 29 | 'password' => 'required|min:4|max:20|regex:/^([a-zA-Z0-9\.\-_\/\+=\.\~\!@#\$\%\^\&\*\(\)\[\]\{\}]){4,20}$/', 30 | ]; 31 | } 32 | 33 | public function attributes() 34 | { 35 | return [ 36 | 'userName' => '登录名', 37 | 'realName' => '姓名', 38 | 'password' => '登录密码' 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/TradeListPost.php: -------------------------------------------------------------------------------- 1 | 'nullable', 28 | 'user_id' => 'nullable|integer', 29 | 'type' => 'nullable|in:'.implode(',', [ 30 | CommonEnum::TRADE_TYPE_INCOME, 31 | CommonEnum::TRADE_TYPE_EXPEND, 32 | ]), 33 | ]; 34 | } 35 | 36 | public function attributes() 37 | { 38 | return [ 39 | 'sn' => '交易单号', 40 | 'type' => '交易类型', 41 | 'user_id' => '用户ID', 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/OrderListPost.php: -------------------------------------------------------------------------------- 1 | 'nullable|array', 26 | 'status.*' => 'nullable|integer|in:'.implode(',', [ 27 | OrderEnum::STATUS_PENDING, 28 | OrderEnum::STATUS_CANCELLED, 29 | OrderEnum::STATUS_PAYED, 30 | OrderEnum::STATUS_DELIVERED, 31 | ]), 32 | 'order_sn' => 'nullable|min:1|max:64', 33 | ]; 34 | } 35 | 36 | public function attributes() 37 | { 38 | return [ 39 | 'order_sn' => '订单号', 40 | 'status' => '订单状态', 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/AdminUserEditPost.php: -------------------------------------------------------------------------------- 1 | 'required|min:1|max:20|regex:/^([a-zA-Z0-9]){4,20}$/', 27 | 'realName' => 'required|min:1|max:20', 28 | 'password' => 'nullable|min:4|max:20|regex:/^([a-zA-Z0-9\.\-_\/\+=\.\~\!@#\$\%\^\&\*\(\)\[\]\{\}]){4,20}$/', 29 | ]; 30 | } 31 | 32 | public function attributes() 33 | { 34 | return [ 35 | 'userName' => '登录名', 36 | 'realName' => '姓名', 37 | 'password' => '登录密码', 38 | 'passwordTwice' => '确认密码' 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /api/.env.production: -------------------------------------------------------------------------------- 1 | APP_NAME=Api 2 | APP_ENV=production 3 | APP_KEY=base64:fNCI2lR70WHsOrHv7XG8oAJyVVWaKLd1Qp3ZyXmBTzo= 4 | APP_DEBUG=true 5 | APP_URL=http://api.dev.com 6 | APP_TIMEZONE=PRC 7 | 8 | LOG_CHANNEL=stack 9 | 10 | DB_CONNECTION=mysql 11 | DB_HOST=127.0.0.1 12 | DB_PORT=3306 13 | DB_DATABASE=aff 14 | DB_USERNAME=root 15 | DB_PASSWORD=123456 16 | DB_TIMEZONE=+08:00 17 | 18 | BROADCAST_DRIVER=log 19 | CACHE_DRIVER=file 20 | QUEUE_CONNECTION=sync 21 | SESSION_DRIVER=file 22 | SESSION_LIFETIME=120 23 | 24 | REDIS_HOST=127.0.0.1 25 | REDIS_PASSWORD=null 26 | REDIS_PORT=6379 27 | 28 | MAIL_DRIVER=smtp 29 | MAIL_HOST=smtp.mailtrap.io 30 | MAIL_PORT=2525 31 | MAIL_USERNAME=null 32 | MAIL_PASSWORD=null 33 | MAIL_ENCRYPTION=null 34 | 35 | AWS_ACCESS_KEY_ID= 36 | AWS_SECRET_ACCESS_KEY= 37 | AWS_DEFAULT_REGION=us-east-1 38 | AWS_BUCKET= 39 | 40 | PUSHER_APP_ID= 41 | PUSHER_APP_KEY= 42 | PUSHER_APP_SECRET= 43 | PUSHER_APP_CLUSTER=mt1 44 | 45 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 46 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 47 | 48 | JWT_SECRET=VAwvgqaII1G6jaq2GCwJOQYjEYz3S7QeFpF3VZRzXnJ2Rg5tW2dY4B1mYfyQis8u 49 | //minutes 50 | JWT_TTL=120 51 | 52 | HOST_WEB=api.dev.com 53 | HOST_ADMIN=admin.dev.com -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import App from './App.vue' 4 | import ElementUI from 'element-ui' 5 | import router from './router' 6 | import store from './store' 7 | import { post, fetch, patch, put } from './util/http' 8 | import Validator from 'vue-validator' 9 | // import 'font-awesome/css/font-awesome.min.css' 10 | import 'element-ui/lib/theme-chalk/index.css' 11 | import '@/app.less' 12 | 13 | Vue.use(ElementUI); 14 | Vue.use(Validator) 15 | 16 | Vue.config.productionTip = false 17 | 18 | Vue.prototype.$http = axios; 19 | 20 | Vue.prototype.$post = post; 21 | Vue.prototype.$fetch = fetch; 22 | Vue.prototype.$patch = patch; 23 | Vue.prototype.$put = put; 24 | 25 | // 全局导航钩子 26 | router.beforeEach((to, from, next) => { 27 | if (to.meta.requireAuth) { 28 | let token = window.localStorage.getItem('token'); 29 | if (!token) { 30 | next({ 31 | path: '/login', 32 | query: { redirect: to.fullPath } // 将跳转的路由path作为参数,登录成功后跳转到该路由 33 | }); 34 | return; 35 | } 36 | } 37 | next(); 38 | }) 39 | 40 | new Vue({ 41 | render: h => h(App), 42 | store, 43 | router, 44 | }).$mount('#app') 45 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /api/.env.local: -------------------------------------------------------------------------------- 1 | APP_NAME=Api 2 | APP_ENV=local 3 | APP_KEY=base64:apTD4AN77+ee6DdWZ5sOUTtYaO9FNLFX5Dk/7K4hMes= 4 | APP_DEBUG=true 5 | APP_URL=http://api.edu.com 6 | APP_TIMEZONE=PRC 7 | APP_LOCALE=zh-CN 8 | 9 | LOG_CHANNEL=stack 10 | 11 | DB_CONNECTION=mysql 12 | DB_HOST=mysql 13 | DB_PORT=3306 14 | DB_DATABASE=aff 15 | DB_USERNAME=root 16 | DB_PASSWORD=root 17 | DB_TIMEZONE=+08:00 18 | 19 | BROADCAST_DRIVER=log 20 | CACHE_DRIVER=file 21 | QUEUE_CONNECTION=sync 22 | SESSION_DRIVER=file 23 | SESSION_LIFETIME=120 24 | 25 | REDIS_HOST=127.0.0.1 26 | REDIS_PASSWORD=null 27 | REDIS_PORT=6379 28 | 29 | MAIL_DRIVER=smtp 30 | MAIL_HOST=smtp.mailtrap.io 31 | MAIL_PORT=2525 32 | MAIL_USERNAME=null 33 | MAIL_PASSWORD=null 34 | MAIL_ENCRYPTION=null 35 | 36 | AWS_ACCESS_KEY_ID= 37 | AWS_SECRET_ACCESS_KEY= 38 | AWS_DEFAULT_REGION=us-east-1 39 | AWS_BUCKET= 40 | 41 | PUSHER_APP_ID= 42 | PUSHER_APP_KEY= 43 | PUSHER_APP_SECRET= 44 | PUSHER_APP_CLUSTER=mt1 45 | 46 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 47 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 48 | 49 | JWT_SECRET=VAwvgqaII1G6jaq2GCwJOQYjEYz3S7QeFpF3VZRzXnJ2Rg5tW2dY4B1mYfyQis8u 50 | 51 | JWT_TTL=120 52 | 53 | HOST_WEB=www.dev.com 54 | HOST_ADMIN=admin.dev.com 55 | 56 | FILESYSTEM_DRIVER=public 57 | -------------------------------------------------------------------------------- /api/app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | { 29 | if (to.meta.requireAuth === undefined || to.meta.requireAuth) { 30 | let token = window.localStorage.getItem('token'); 31 | if (!token) { 32 | next({ 33 | path: '/login', 34 | query: { redirect: to.fullPath } // 将跳转的路由path作为参数,登录成功后跳转到该路由 35 | }); 36 | return; 37 | } 38 | } 39 | next(); 40 | }) 41 | 42 | new Vue({ 43 | render: h => h(App), 44 | store, 45 | router, 46 | }).$mount('#app') 47 | -------------------------------------------------------------------------------- /api/app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | json([ 24 | 'code' => 0, 25 | 'msg' => __('base.success'), 26 | 'data' => $data 27 | ]); 28 | } 29 | 30 | /** 31 | * 32 | * 33 | * @param int $code 34 | * @param string $msg 35 | * @param string|array|object $data 36 | * 37 | * @return \Illuminate\Http\JsonResponse 38 | */ 39 | public function jsonFail(int $code, string $msg, $data = null) 40 | { 41 | return response()->json([ 42 | 'code' => $code, 43 | 'msg' => $msg, 44 | 'data' => $data 45 | ]); 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "test:unit": "vue-cli-service test:unit", 9 | "test:e2e": "vue-cli-service test:e2e", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.21.2", 14 | "core-js": "^3.1.2", 15 | "less": "^3.10.3", 16 | "less-loader": "^5.0.0", 17 | "vue": "^2.6.10", 18 | "vue-validator": "^3.0.0-alpha.2" 19 | }, 20 | "devDependencies": { 21 | "element-ui": "^2.12.0", 22 | "expect": "^24.9.0", 23 | "vue-router": "^3.1.3", 24 | "vuex": "^3.1.1", 25 | "@vue/cli-plugin-babel": "^4.0.0", 26 | "@vue/cli-plugin-e2e-cypress": "^4.0.0", 27 | "@vue/cli-plugin-eslint": "^4.0.0", 28 | "@vue/cli-plugin-router": "^4.0.0", 29 | "@vue/cli-plugin-unit-mocha": "^4.0.0", 30 | "@vue/cli-plugin-vuex": "^4.0.0", 31 | "@vue/cli-service": "^4.0.0", 32 | "@vue/test-utils": "1.0.0-beta.29", 33 | "babel-eslint": "^10.0.3", 34 | "chai": "^4.1.2", 35 | "eslint": "^5.16.0", 36 | "eslint-plugin-vue": "^5.0.0", 37 | "less": "^3.0.4", 38 | "less-loader": "^5.0.0", 39 | "vue-template-compiler": "^2.6.10" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /api/app/Enum/CommonEnum.php: -------------------------------------------------------------------------------- 1 | '否', 18 | self::IS_DELETED_YES => '是', 19 | ]; 20 | 21 | const TRADE_TYPE_INCOME = 1;//收入 22 | const TRADE_TYPE_EXPEND = 2;//支出 23 | 24 | const FINANCE_TRADE_TYPE_TEXT_LIST = [ 25 | self::TRADE_TYPE_INCOME => '收入', 26 | self::TRADE_TYPE_EXPEND => '支出', 27 | ]; 28 | 29 | //提现状态:1=未审核、2=驳回、3=提现成功 30 | const WITHDRAW_STATUS_PENDING = 1; 31 | const WITHDRAW_STATUS_REJECT = 2; 32 | const WITHDRAW_STATUS_COMPLETE = 3; 33 | 34 | const WITHDRAW_STATUS_TEXT_LIST = [ 35 | self::WITHDRAW_STATUS_PENDING => '待审核', 36 | self::WITHDRAW_STATUS_REJECT => '驳回', 37 | self::WITHDRAW_STATUS_COMPLETE => '提现成功', 38 | ]; 39 | 40 | //提现方式:1=银行卡,2=支付宝 41 | const WITHDRAW_WAY_BANK = 1; 42 | const WITHDRAW_WAY_ALIPAY = 2; 43 | 44 | const WITHDRAW_WAY_TEXT_LIST = [ 45 | self::WITHDRAW_WAY_BANK => '银行卡', 46 | self::WITHDRAW_WAY_ALIPAY => '支付宝', 47 | ]; 48 | 49 | const WITHDRAW_SN_PREFIX = 'WD'; 50 | } -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.21.2", 12 | "core-js": "^3.1.2", 13 | "less": "^3.10.3", 14 | "less-loader": "^5.0.0", 15 | "vue": "^2.6.10" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^4.0.0", 19 | "@vue/cli-plugin-eslint": "^4.0.0", 20 | "@vue/cli-service": "^4.0.0", 21 | "babel-eslint": "^10.0.1", 22 | "element-ui": "^2.12.0", 23 | "eslint": "^5.16.0", 24 | "eslint-plugin-vue": "^5.0.0", 25 | "vue-router": "^3.1.3", 26 | "vue-template-compiler": "^2.6.10", 27 | "vuex": "^3.1.1" 28 | }, 29 | "eslintConfig": { 30 | "root": true, 31 | "env": { 32 | "node": true 33 | }, 34 | "extends": [ 35 | "plugin:vue/essential", 36 | "eslint:recommended" 37 | ], 38 | "rules": { 39 | "no-console":"off" 40 | }, 41 | "parserOptions": { 42 | "parser": "babel-eslint" 43 | } 44 | }, 45 | "postcss": { 46 | "plugins": { 47 | "autoprefixer": {} 48 | } 49 | }, 50 | "browserslist": [ 51 | "> 1%", 52 | "last 2 versions" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/EditPasswordPost.php: -------------------------------------------------------------------------------- 1 | 'required|min:1|max:20', 29 | 'passwordOld' => 'nullable|min:4|max:20|regex:/^([a-zA-Z0-9\.\-_\/\+=\.\~\!@#\$\%\^\&\*\(\)\[\]\{\}]){4,20}$/', 30 | 'passwordNew' => 'required_with:passwordOld|min:4|max:20|regex:/^([a-zA-Z0-9\.\-_\/\+=\.\~\!@#\$\%\^\&\*\(\)\[\]\{\}]){4,20}$/', 31 | 'passwordTwice' => 'required_with:passwordNew|same:passwordNew', 32 | ]; 33 | } 34 | 35 | public function attributes() 36 | { 37 | return [ 38 | 'realName' => '姓名', 39 | 'passwordOld' => '旧密码', 40 | 'passwordNew' => '新密码', 41 | 'passwordTwice' => '确认新密码', 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /api/app/Services/Frontend/BannerService.php: -------------------------------------------------------------------------------- 1 | findById( $id); 28 | } 29 | /** 30 | * 31 | * @param BasePageListPost $post 32 | * @param int $totalRows 33 | * 34 | * @return BannerModel[] 35 | */ 36 | public function findListByPage(BasePageListPost $post, int &$totalRows) 37 | { 38 | $where = [ 39 | ['status', '=', BannerEnum::STATUS_ON] 40 | ]; 41 | if ($post->get('title')) { 42 | $where[] = ['title', 'like', '%'.addcslashes( $post->get('title'), '%').'%']; 43 | } 44 | // if ($post->status) { 45 | // $where[] = ['status', '=', $post->status]; 46 | // } 47 | $list = BannerModel::singleton()->findListByPage( $where, $post->page, $post->perPage, $totalRows); 48 | 49 | return $list; 50 | } 51 | } -------------------------------------------------------------------------------- /api/tests/Unit/HashTest.php: -------------------------------------------------------------------------------- 1 | assertGreaterThan(0, strlen($hash)); 26 | $a = UtilHelper::validPassword($pwd, $hash); 27 | $this->assertTrue($a); 28 | $a1 = UtilHelper::validPassword($pwd, $h1); 29 | $this->assertTrue($a1); 30 | 31 | $p2= '$2y$04$hq14gU.ZbqugQpbOdCIQ5.e/5Gbp0pM0786b.EXQf2yHu.pc8RCHK'; 32 | $a2 = UtilHelper::validPassword($pwd, $p2); 33 | $this->assertTrue($a2); 34 | 35 | } 36 | 37 | public function testSn() 38 | { 39 | $length = 20; 40 | $sn = UtilHelper::generateSn( CommonEnum::WITHDRAW_SN_PREFIX, $length); 41 | echo $sn, PHP_EOL; 42 | $this->assertEquals( strlen( $sn), 20); 43 | $nowUtc = UtilHelper::logTime(); 44 | var_dump($nowUtc); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/app/Listeners/QueryListener.php: -------------------------------------------------------------------------------- 1 | bindings; 31 | foreach ($params as $index => $param) { 32 | if ($param instanceof DateTime) { 33 | $params[$index] = $param->format('Y-m-d H:i:s'); 34 | } 35 | } 36 | 37 | $params = array_map('addslashes', $params); 38 | 39 | if (strpos($event->sql, 'INSERT INTO') !== false && empty($event->bindings)) { 40 | Log::info($event->sql); 41 | } else { 42 | $sql = str_replace("%", "##", $event->sql); 43 | $sql = str_replace("?", "'%s'", $sql); 44 | 45 | array_unshift($params, $sql); 46 | $sql = call_user_func_array('sprintf', $params); 47 | $sql = str_replace("##", "%", $sql); 48 | Log::info($sql); 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /api/app/Http/Requests/EditPasswordPost.php: -------------------------------------------------------------------------------- 1 | 'required|min:4|max:20|regex:/^([a-zA-Z0-9\.\-_\/\+=\.\~\!@#\$\%\^\&\*\(\)\[\]\{\}]){4,20}$/', 37 | 'passwordNew' => 'required|min:4|max:20|regex:/^([a-zA-Z0-9\.\-_\/\+=\.\~\!@#\$\%\^\&\*\(\)\[\]\{\}]){4,20}$/', 38 | 'passwordTwice' => 'required|same:passwordNew', 39 | ]; 40 | } 41 | 42 | public function attributes() 43 | { 44 | return [ 45 | 'passwordOld' => '旧密码', 46 | 'passwordNew' => '新密码', 47 | 'passwordTwice' => '确认新密码', 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /api/app/Http/Controllers/Frontend/BannerController.php: -------------------------------------------------------------------------------- 1 | bannerService = $bannerService; 24 | } 25 | public function actionList(BasePageListPost $post) 26 | { 27 | $totalRows = 0; 28 | $list = $this->bannerService->findListByPage( $post, $totalRows); 29 | $ret = []; 30 | foreach ($list as $item) { 31 | $ret[] = [ 32 | 'id' => $item->id, 33 | 'title' => $item->title, 34 | 'sku' => $item->sku, 35 | 'price' => $item->price, 36 | 'return_text' => $item->formatReturnType(), 37 | 'redirect_url' => $item->redirect_url, 38 | 'thumb_url' => UtilHelper::thumbUrl( $item->pic_url), 39 | ]; 40 | } 41 | return $this->jsonSuccess([ 42 | 'list' => $ret, 43 | 'total' => $totalRows, 44 | ]); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /frontend/src/views/Layout.vue: -------------------------------------------------------------------------------- 1 | 19 | 44 | 54 | -------------------------------------------------------------------------------- /api/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/Unit 16 | 17 | 18 | 19 | ./tests/Feature 20 | 21 | 22 | 23 | 24 | ./app 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /api/app/Http/Requests/EditProfilePost.php: -------------------------------------------------------------------------------- 1 | 'nullable|email', 40 | 'gender' => 'nullable|integer|in:0,1,2', 41 | 'mobile' => 'nullable|numeric|regex:/^1(\d{10})$/i', 42 | 'birthday' => 'nullable|date|date_format:Y-m-d', 43 | ]; 44 | } 45 | /** 46 | * Get custom attributes for validator errors. 47 | * 48 | * @return array 49 | */ 50 | public function attributes() 51 | { 52 | return [ 53 | 'mobile' => '手机号', 54 | 'birthday' => '生日', 55 | 'gender' => '性别', 56 | ]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /frontend/src/views/Banner.vue: -------------------------------------------------------------------------------- 1 | 11 | 58 | 79 | -------------------------------------------------------------------------------- /api/app/Models/FinanceTradeModel.php: -------------------------------------------------------------------------------- 1 | 'required|max:255', 37 | * @return array 38 | */ 39 | public function getRules() 40 | { 41 | return [ 42 | //'merchant_id' => 'required|integer', 43 | //'title' => 'required|max:255', 44 | ]; 45 | } 46 | 47 | /** 48 | * 49 | * @param $sn 50 | * 51 | * @return $this 52 | */ 53 | public function findBySn(string $sn) 54 | { 55 | return static::query()->where('business_sn', '=', $sn)->first(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /api/app/Models/FindListTrait.php: -------------------------------------------------------------------------------- 1 | find($id); 23 | } 24 | 25 | /** 26 | * 27 | * @param array $id 28 | * 29 | * @return $this[] 30 | */ 31 | public function findListById(array $id) 32 | { 33 | return static::query()->whereIn('id', $id)->get()->all(); 34 | } 35 | 36 | /** 37 | * 分页获取多个实例(Model数组) 38 | * 39 | * @param array $where 40 | * @param int $page 41 | * @param int $perPage 42 | * @param int $totalRows 返回查询总数 43 | * 44 | * @return static[] 45 | */ 46 | public function findListByPage(array $where, $page, $perPage, &$totalRows, $orderByDesc = 'id' ) 47 | { 48 | $query = static::query(); 49 | $query->where($where)->forPage( $page, $perPage)->orderByDesc($orderByDesc); 50 | 51 | $list = $query->get()->all(); 52 | $totalRows = $query->toBase()->getCountForPagination(); 53 | return $list; 54 | } 55 | 56 | /** 57 | * @return $this 58 | */ 59 | public static function singleton() 60 | { 61 | return app()->make(static::class); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /api/app/Http/Controllers/Backend/TradeController.php: -------------------------------------------------------------------------------- 1 | tradeService = $tradeService; 28 | } 29 | /** 30 | * 列表 31 | * 32 | * @param TradeListPost $post 33 | * 34 | * @return \Illuminate\Http\JsonResponse 35 | */ 36 | public function actionList(TradeListPost $post) 37 | { 38 | $totalRows = 0; 39 | $list = $this->tradeService->findListByPage($post, $totalRows); 40 | 41 | foreach ($list as $item) { 42 | $item->setAttribute( 'type_text', CommonEnum::FINANCE_TRADE_TYPE_TEXT_LIST[$item->type]); 43 | } 44 | 45 | return $this->jsonSuccess([ 46 | 'list' => $list, 47 | 'page' => $post->page, 48 | 'perPage' => $post->perPage, 49 | 'totalRows' => $totalRows 50 | ]); 51 | } 52 | 53 | 54 | 55 | } -------------------------------------------------------------------------------- /api/app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 21 | SendEmailVerificationNotification::class, 22 | ], 23 | ]; 24 | 25 | /** 26 | * Register any events for your application. 27 | * 28 | * @return void 29 | */ 30 | public function boot() 31 | { 32 | parent::boot(); 33 | 34 | // 35 | } 36 | /** 37 | * Get the events and handlers. 38 | * 39 | * @return array 40 | */ 41 | public function listens() 42 | { 43 | //SQL调试 44 | if (app()->isLocal() || app()->environment() == 'dev') { 45 | $this->listen[ QueryExecuted::class ] = [ 46 | \App\Listeners\QueryListener::class, 47 | ]; 48 | } 49 | //新订单录入事件 50 | $this->listen[ NewOrderRecordEvent::class ] = [ 51 | \App\Listeners\NewOrderRecordListener::class, 52 | ]; 53 | return $this->listen; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/OrderEditPost.php: -------------------------------------------------------------------------------- 1 | 'required|integer|in:'.implode(',', [ 31 | OrderEnum::STATUS_PENDING, 32 | OrderEnum::STATUS_CANCELLED, 33 | OrderEnum::STATUS_PAYED, 34 | OrderEnum::STATUS_DELIVERED, 35 | ]), 36 | 'order_time' => 'required|date|date_format:Y-m-d H:i:s', 37 | 'pay_time' => 'nullable|date|date_format:Y-m-d H:i:s', 38 | 'deliver_time' => 'nullable|date|date_format:Y-m-d H:i:s', 39 | ]; 40 | } 41 | 42 | public function attributes() 43 | { 44 | return [ 45 | 'order_status' => '订单状态', 46 | 'order_time' => '下单日期', 47 | 'pay_time' => '支付日期', 48 | 'deliver_time' => '发货日期', 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /api/app/Models/FinanceWithdrawModel.php: -------------------------------------------------------------------------------- 1 | 'required|max:255', 42 | * @return array 43 | */ 44 | public function getRules() 45 | { 46 | return [ 47 | //'merchant_id' => 'required|integer', 48 | //'title' => 'required|max:255', 49 | ]; 50 | } 51 | 52 | /** 53 | * 54 | * @param string $sn 55 | * 56 | * @return $this 57 | */ 58 | public function findBySn(string $sn) 59 | { 60 | return static::query()->where('sn', '=', $sn)->first(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /api/app/Models/OrderInfoModel.php: -------------------------------------------------------------------------------- 1 | 'required|max:255', 40 | * @return array 41 | */ 42 | public function getRules() 43 | { 44 | return [ 45 | //'merchant_id' => 'required|integer', 46 | //'title' => 'required|max:255', 47 | ]; 48 | } 49 | 50 | /** 51 | * 52 | * @param $orderSn 53 | * 54 | * @return $this 55 | */ 56 | public function findBySn($orderSn) 57 | { 58 | return static::query()->where('order_sn', '=', $orderSn)->first(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /backend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | import Login from '@/views/Login.vue' 5 | import Layout from '@/views/Layout.vue' 6 | import BannerList from '@/views/BannerList.vue' 7 | import WithdrawList from '@/views/WithdrawList.vue' 8 | import OrderList from '@/views/OrderList.vue' 9 | import OrderGrant from '@/views/OrderGrant.vue' 10 | import OrderAdd from '@/views/OrderAdd.vue' 11 | import UserList from '@/views/UserList.vue' 12 | import Dashboard from '@/views/Dashboard.vue' 13 | import AdminList from '@/views/AdminList.vue' 14 | import AdminProfile from '@/views/AdminProfile.vue' 15 | import TradeList from '@/views/TradeList.vue' 16 | 17 | Vue.use(Router); 18 | 19 | const routes = [ 20 | { path: '/', component: Login, meta:{requireAuth:false} }, 21 | { path: '/login', component: Login, meta:{requireAuth:false} }, 22 | { 23 | path: '*', 24 | name: 'default', 25 | component: Layout, 26 | children: [ 27 | { path: '/admin', component: AdminList }, 28 | { path: '/admin/profile', component: AdminProfile }, 29 | { path: '/banner', component: BannerList }, 30 | { path: '/dashboard', component: Dashboard }, 31 | { path: '/user', component: UserList }, 32 | { path: '/user/trade', component: TradeList }, 33 | { path: '/order', component: OrderList }, 34 | { path: '/order/add', component: OrderAdd }, 35 | { path: '/order/grant', component: OrderGrant }, 36 | { path: '/withdraw', component: WithdrawList }, 37 | ] 38 | } 39 | ]; 40 | 41 | const router = new Router({ routes: routes });//mode: 'history', 42 | 43 | export default router 44 | -------------------------------------------------------------------------------- /backend/src/app.less: -------------------------------------------------------------------------------- 1 | @header-color: #1976D2; 2 | @white: #ffffff; 3 | @bg-color: #EEF5F9; 4 | 5 | body, html { 6 | margin: 0; 7 | padding: 0; 8 | height: 100%; 9 | background-color: @white; 10 | } 11 | h1 { 12 | font-weight: 300; 13 | } 14 | 15 | .clearfix:before, 16 | .clearfix:after { 17 | display: table; 18 | content: ""; 19 | } 20 | 21 | .clearfix:after { 22 | clear: both 23 | } 24 | 25 | .el-main { 26 | padding-top: 0; 27 | } 28 | 29 | .el-header { 30 | background-color: @header-color; 31 | color: @white !important; 32 | line-height: 60px; 33 | padding: 0; 34 | .logo { 35 | width: 400px; 36 | float: left; 37 | h1 { 38 | line-height: 60px; 39 | margin: 0; 40 | padding: 0; 41 | padding-left: 15px; 42 | text-align: center; 43 | } 44 | } 45 | .header-nav { 46 | display: inline-block; 47 | float: right; 48 | margin: 0; 49 | 50 | .el-submenu__title { 51 | i { 52 | color: @white; 53 | } 54 | border-bottom-color: @header-color !important; 55 | } 56 | 57 | .el-menu[role='menu'] { 58 | background-color: @white !important; 59 | 60 | .el-menu-item { 61 | background-color: @white !important; 62 | color: #000 !important; 63 | } 64 | 65 | i { 66 | color: #000 !important; 67 | } 68 | } 69 | } 70 | } 71 | 72 | .el-aside { 73 | background-color: @white; 74 | line-height: 200px; 75 | 76 | .el-menu[role='menubar'] { 77 | min-height: 100% !important; 78 | } 79 | } 80 | 81 | .breadcrumb { 82 | margin-bottom: 20px; 83 | h3 { 84 | margin: 0; 85 | padding: 0; 86 | } 87 | 88 | .el-breadcrumb { 89 | float: right; 90 | line-height: 25px; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /api/app/Models/OrderGoodsModel.php: -------------------------------------------------------------------------------- 1 | 'required|max:255', 40 | * @return array 41 | */ 42 | public function getRules() 43 | { 44 | return [ 45 | //'merchant_id' => 'required|integer', 46 | //'title' => 'required|max:255', 47 | ]; 48 | } 49 | 50 | /** 51 | * 获取指定订单的商品列表 52 | * 53 | * @param int $orderId 54 | * 55 | * @return $this[] 56 | */ 57 | public function findListByOrderId($orderId) 58 | { 59 | if (!is_array($orderId)) { 60 | $orderId = [$orderId]; 61 | } 62 | return static::query()->whereIn('order_id', $orderId)->get()->all(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /api/app/Models/UserBalanceModel.php: -------------------------------------------------------------------------------- 1 | 'required|max:255', 37 | * @return array 38 | */ 39 | public function getRules() 40 | { 41 | return [ 42 | //'merchant_id' => 'required|integer', 43 | //'title' => 'required|max:255', 44 | ]; 45 | } 46 | 47 | /** 48 | * 49 | * @param int $userId 50 | * 51 | * @return bool 52 | */ 53 | public function createRow(int $userId) 54 | { 55 | return static::query()->insert(['user_id' => $userId]); 56 | } 57 | 58 | /** 59 | * 60 | * @param int $userId 61 | * 62 | * @return $this 63 | */ 64 | public function findBalance(int $userId) 65 | { 66 | return static::query()->where('user_id', '=', $userId)->firstOrCreate(['user_id' => $userId]); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /api/routes/web.php: -------------------------------------------------------------------------------- 1 | json([ 18 | 'code' => 0, 19 | 'msg' => 'Unknown request.' 20 | ]); 21 | }); 22 | 23 | //不用检查登录的 24 | Route::group(['prefix' => '/api', 'domain' => env('HOST_WEB'), 'namespace' => 'Frontend'], function () { 25 | Route::post('login', 'UserController@actionLogin'); 26 | Route::post('register', 'UserController@actionRegister'); 27 | 28 | //banner 29 | Route::get('banners', 'BannerController@actionList'); 30 | }); 31 | 32 | //用户中心要检查登录的 33 | Route::group(['prefix' => '/api/user', 'domain' => env('HOST_WEB'), 'middleware' => 'jwt:frontend', 'namespace' => 'Frontend'], function () { 34 | Route::get('profile', 'UserController@actionGetProfile'); 35 | Route::put('edit-profile', 'UserController@actionEditProfile'); 36 | Route::put('edit-pwd', 'UserController@actionEditPwd'); 37 | 38 | //订单 39 | Route::get('orders', 'OrderController@actionList'); 40 | //收支明细 41 | Route::get('trades', 'FinanceController@actionTradeList'); 42 | //查余额 43 | Route::get('balance', 'FinanceController@actionBalance'); 44 | //提现申请 45 | Route::post('withdraws', 'FinanceController@actionWithdrawCreate'); 46 | //提现列表 47 | Route::get('withdraws', 'FinanceController@actionWithdrawList'); 48 | }); 49 | -------------------------------------------------------------------------------- /api/app/Services/Frontend/OrderService.php: -------------------------------------------------------------------------------- 1 | findById( $id); 29 | } 30 | 31 | /** 32 | * 33 | * @param string $sn 34 | * 35 | * @return OrderInfoModel 36 | */ 37 | public function findByOrderSn(string $sn) 38 | { 39 | return OrderInfoModel::singleton()->findBySn( $sn); 40 | } 41 | /** 42 | * 43 | * @param int|array $id 44 | * 45 | * @return OrderGoodsModel[] 46 | */ 47 | public function findGoodsListById($id) 48 | { 49 | return OrderGoodsModel::singleton()->findListByOrderId( $id); 50 | } 51 | /** 52 | * 53 | * @param OrderListPost $post 54 | * @param int $totalRows 55 | * 56 | * @return OrderInfoModel[] 57 | */ 58 | public function findListByPage(OrderListPost $post, int &$totalRows) 59 | { 60 | $where = []; 61 | if ($post->order_sn) { 62 | $where[] = ['order_sn', '=', $post->order_sn]; 63 | } 64 | if ($post->status) { 65 | $where[] = ['order_status', '=', $post->status]; 66 | } 67 | $list = OrderInfoModel::singleton()->findListByPage( $where, $post->page, $post->perPage, $totalRows); 68 | 69 | return $list; 70 | } 71 | } -------------------------------------------------------------------------------- /api/app/Http/Middleware/JwtAuthMiddleware.php: -------------------------------------------------------------------------------- 1 | $code, 30 | 'msg' => __('base.invalid_token'), 31 | 'data' => null 32 | ]; 33 | try { 34 | Log::debug('init jwt auth'); 35 | if (!JWTAuth::parseToken()->check()) { 36 | Log::error('parseToken'); 37 | return response()->json($result); 38 | } 39 | 40 | if (!auth($guard)->check()) { 41 | Log::debug('auth guard check'); 42 | return response()->json($result); 43 | } 44 | Log::debug('passed jwt auth'); 45 | 46 | // 设置用户信息 47 | Auth::setUser(auth($guard)->user()); 48 | 49 | } catch (\Exception $e) { 50 | $code = CodeEnum::BASE_INVALID_TOKEN; 51 | $result = [ 52 | 'code' => $code, 53 | 'msg' => __('base.server_error').$e->getMessage(), 54 | 'data' => null 55 | ]; 56 | return response()->json($result); 57 | } 58 | return $next($request); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/BannerEditPost.php: -------------------------------------------------------------------------------- 1 | 'required|integer|in:1,2', 34 | 'title' => 'required|min:1|max:64', 35 | 'redirect_url' => 'required|min:1|max:128', 36 | 'pic_url' => 'required|min:1|max:128', 37 | 'sku' => 'required|max:20', 38 | 'price' => 'required|max:20', 39 | 'return_type' => 'required|numeric|in:'.BannerEnum::RETURN_TYPE_AMOUNT.','.BannerEnum::RETURN_TYPE_PERCENT, 40 | 'return_value' => 'required|numeric|min:0.01', 41 | ]; 42 | } 43 | 44 | public function attributes() 45 | { 46 | return [ 47 | 'title' => '标题', 48 | 'pic_url' => '图片URL', 49 | 'redirect_url' => '跳转 URL', 50 | 'status' => '上架状态', 51 | 'sku' => 'SKU', 52 | 'price' => '售价', 53 | 'return_type' => '返佣方式', 54 | 'return_value' => '返佣金额/比例', 55 | ]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /backend/src/views/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 26 | 29 | 68 | -------------------------------------------------------------------------------- /api/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/laravel", 3 | "type": "project", 4 | "description": "The Laravel Framework.", 5 | "keywords": [ 6 | "framework", 7 | "laravel" 8 | ], 9 | "license": "MIT", 10 | "require": { 11 | "php": "^7.2", 12 | "fideloper/proxy": "^4.0", 13 | "intervention/image": "^2.5", 14 | "laravel/framework": "^6.2", 15 | "laravel/tinker": "^1.0", 16 | "tymon/jwt-auth": "1.*@rc" 17 | }, 18 | "require-dev": { 19 | "facade/ignition": "^1.4", 20 | "fzaninotto/faker": "^1.4", 21 | "mockery/mockery": "^1.0", 22 | "nunomaduro/collision": "^3.0", 23 | "phpunit/phpunit": "^8.0" 24 | }, 25 | "config": { 26 | "optimize-autoloader": true, 27 | "preferred-install": "dist", 28 | "sort-packages": true 29 | }, 30 | "extra": { 31 | "laravel": { 32 | "dont-discover": [] 33 | } 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "App\\": "app/" 38 | }, 39 | "classmap": [ 40 | "database/seeds", 41 | "database/factories" 42 | ] 43 | }, 44 | "autoload-dev": { 45 | "psr-4": { 46 | "Tests\\": "tests/" 47 | } 48 | }, 49 | "minimum-stability": "dev", 50 | "prefer-stable": true, 51 | "scripts": { 52 | "post-autoload-dump": [ 53 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 54 | "@php artisan package:discover --ansi" 55 | ], 56 | "post-root-package-install": [ 57 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 58 | ], 59 | "post-create-project-cmd": [ 60 | "@php artisan key:generate --ansi" 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | import Login from '@/views/Login.vue' 5 | import Layout from '@/views/Layout.vue' 6 | import Banner from '@/views/Banner.vue' 7 | import Home from '@/views/Home.vue' 8 | import Reg from '@/views/Reg.vue' 9 | import UserLayout from '@/views/UserLayout.vue' 10 | import UserProfile from '@/views/user/Profile.vue' 11 | import UserOrder from '@/views/user/Order.vue' 12 | import UserWithdraw from '@/views/user/Withdraw.vue' 13 | import UserTrade from '@/views/user/Trade.vue' 14 | import UserWithdrawList from '@/views/user/WithdrawList.vue' 15 | 16 | // const Layout = resolve => { require(['@/views/Layout.vue'], resolve) }; 17 | 18 | Vue.use(Router); 19 | 20 | const routes = [ 21 | { 22 | path: '/user', 23 | name: 'user', 24 | component: UserLayout, 25 | children: [ 26 | {path: 'profile', component: UserProfile, meta: { requireAuth: true }}, 27 | {path: 'order', component: UserOrder, meta: { requireAuth: true }}, 28 | {path: 'trade', component: UserTrade, meta: { requireAuth: true }}, 29 | {path: 'withdraw', component: UserWithdraw, meta: { requireAuth: true }}, 30 | {path: 'withdraw/list', component: UserWithdrawList, meta: { requireAuth: true }}, 31 | ] 32 | }, 33 | { 34 | path: '*', 35 | name: 'default', 36 | component: Layout, 37 | children: [ 38 | { path: '/banner', component: Banner, meta: { requireAuth: false } }, 39 | { path: '/login', component: Login, meta: { requireAuth: false } }, 40 | { path: '/reg', component: Reg, meta: { requireAuth: false } }, 41 | { path: '/', component: Home, meta: { requireAuth: false } }, 42 | ] 43 | } 44 | ]; 45 | 46 | const router = new Router({ routes: routes });//mode: 'history', 47 | 48 | export default router 49 | -------------------------------------------------------------------------------- /api/app/Models/ValidateBaseModel.php: -------------------------------------------------------------------------------- 1 | fillable($fillable); 28 | 29 | parent::__construct($attributes); 30 | } 31 | 32 | /** 33 | * Description:获取验证规则 34 | * 35 | * 'title' => 'required|unique:posts|max:255', 36 | 'body' => 'required' 37 | * @return mixed 38 | */ 39 | public function getRules() 40 | { 41 | return []; 42 | } 43 | 44 | /** @var array */ 45 | protected $failed; 46 | /** @var MessageBag */ 47 | protected $messageBag; 48 | /** 49 | * Description: 验证数据是否符合格式要求,在入库前调用判断 50 | * @return bool true=验证通过,false=验证失败 51 | */ 52 | public function validate() 53 | { 54 | $validator = Validator::make($this->getAttributes(), $this->getRules()); 55 | $ret = $validator->fails(); 56 | if ($ret) { 57 | //存储错误消息 58 | $this->messageBag = $validator->getMessageBag(); 59 | $this->failed = $validator->failed(); 60 | } 61 | return !$ret; 62 | } 63 | 64 | /** 65 | * 66 | * @return MessageBag 67 | */ 68 | public function getValidateError() 69 | { 70 | return $this->messageBag; 71 | } 72 | /** 73 | * 74 | * @return array 75 | */ 76 | public function getFailed() 77 | { 78 | return $this->failed; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /api/app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 39 | 40 | $this->mapWebRoutes(); 41 | $this->mapBackendRoutes(); 42 | 43 | // 44 | } 45 | 46 | /** 47 | * Define the "web" routes for the application. 48 | * 49 | * These routes all receive session state, CSRF protection, etc. 50 | * 51 | * @return void 52 | */ 53 | protected function mapWebRoutes() 54 | { 55 | Route::namespace($this->namespace) 56 | ->group(base_path('routes/web.php')); 57 | } 58 | 59 | protected function mapBackendRoutes() 60 | { 61 | Route::namespace($this->namespace) 62 | ->group(base_path('routes/backend.php')); 63 | } 64 | 65 | /** 66 | * Define the "api" routes for the application. 67 | * 68 | * These routes are typically stateless. 69 | * 70 | * @return void 71 | */ 72 | protected function mapApiRoutes() 73 | { 74 | Route::prefix('api') 75 | ->namespace($this->namespace) 76 | ->group(base_path('routes/api.php')); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /api/app/Http/Controllers/Frontend/OrderController.php: -------------------------------------------------------------------------------- 1 | orderService = $orderService; 25 | } 26 | /** 27 | * 列表 28 | * 29 | * @param OrderListPost $post 30 | * 31 | * @return \Illuminate\Http\JsonResponse 32 | */ 33 | public function actionList(OrderListPost $post) 34 | { 35 | $totalRows = 0; 36 | $list = $this->orderService->findListByPage($post, $totalRows); 37 | 38 | if (count($list)) { 39 | $idList = array_column($list, 'id'); 40 | $tmpList = $this->orderService->findGoodsListById( $idList); 41 | $orderGoodsList = []; 42 | foreach ($tmpList as $goods) { 43 | $orderGoodsList[$goods->order_id][] = $goods; 44 | } 45 | foreach ( $list as $item ) { 46 | if (isset( $orderGoodsList[$item->id])) { 47 | $item->setAttribute( 'goods_list', $orderGoodsList[ $item->id ] ); 48 | } else { 49 | $item->setAttribute( 'goods_list', [] ); 50 | } 51 | $item->statusText = OrderEnum::STATUS_TEXT_LIST[ $item->order_status ] ?? ''; 52 | $item->commissionStatusText = OrderEnum::COMMISSION_STATUS_TEXT_LIST[ $item->commission_status ] ?? ''; 53 | } 54 | } 55 | 56 | return $this->jsonSuccess([ 57 | 'list' => $list, 58 | 'page' => $post->page, 59 | 'perPage' => $post->perPage, 60 | 'totalRows' => $totalRows 61 | ]); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /api/app/Http/Controllers/Backend/DashboardController.php: -------------------------------------------------------------------------------- 1 | userService = $userService; 18 | } 19 | public function actionIndex() 20 | { 21 | /* 22 | 昨日新增注册联盟客:10个 23 | 今日新增注册联盟客:5个 24 | 本月新增注册联盟客:10个 25 | 截止昨日联盟客总数:100个 26 | 27 | 昨日新增有效订单:10单 28 | 本月订单销售:10000元 29 | 本月提现佣金:10元 30 | 审核中的提现单:10单 31 | * */ 32 | //昨日 33 | $yesterday = strtotime('-1 day'); 34 | $startTime = date('Y-m-d', $yesterday).' 00:00:00'; 35 | $endTime = date('Y-m-d', $yesterday).' 23:59:59'; 36 | $yesterdayUsers = $this->userService->countByRegTime( $startTime, $endTime); 37 | 38 | //总数,截止昨日 39 | $totalUsers = $this->userService->countByRegTime( '', $endTime); 40 | 41 | //今日 42 | $startTime = date('Y-m-d').' 00:00:00'; 43 | $endTime = date('Y-m-d').' 23:59:59'; 44 | $todayUsers = $this->userService->countByRegTime( $startTime, $endTime); 45 | 46 | //本月新增 47 | $startTime = date('Y-m-1').' 00:00:00'; 48 | $endTime = date('Y-m-d', $yesterday).' 23:59:59'; 49 | $monthUsers = $this->userService->countByRegTime( $startTime, $endTime); 50 | 51 | 52 | return $this->jsonSuccess([ 53 | 'users' => [ 54 | 'yesterday' => $yesterdayUsers, 55 | 'today' => $todayUsers, 56 | 'month' => $monthUsers, 57 | 'total' => $totalUsers, 58 | ], 59 | 'orders' => [ 60 | //昨日新增有效订单 61 | 'yesterday' => 0, 62 | //本月订单销售 63 | 'monthSale' => 0, 64 | //本月提现佣金 65 | 'monthCommission' => 0, 66 | //审核中的提现单 67 | 'withdrawPending' => 0 68 | ] 69 | ]); 70 | } 71 | } -------------------------------------------------------------------------------- /frontend/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 27 | 60 | 80 | -------------------------------------------------------------------------------- /backend/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /frontend/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /api/app/Repositories/BannerRepository.php: -------------------------------------------------------------------------------- 1 | model = $model; 17 | } 18 | public function deleteRow($id) 19 | { 20 | return BannerModel::query()->where('id', $id)->delete(); 21 | } 22 | 23 | /** 24 | * @param BannerEditPost $post 25 | * @return bool 26 | */ 27 | public function createRow(BannerEditPost $post) 28 | { 29 | $model = new BannerModel(); 30 | $model->title = $post->title; 31 | $model->status = $post->status; 32 | $model->redirect_url = $post->redirect_url; 33 | $model->pic_url = $post->pic_url; 34 | $model->sku = $post->sku; 35 | $model->price = $post->price; 36 | $model->return_type = $post->return_type; 37 | $model->return_value = $post->return_value; 38 | return $model->save(); 39 | } 40 | 41 | /** 42 | * @param int $id 43 | * @return BannerModel|null 44 | */ 45 | public function findById(int $id) 46 | { 47 | return $this->model->findById($id); 48 | } 49 | 50 | /** 51 | * @param int $sku 52 | * 53 | * @return BannerModel 54 | */ 55 | public function findBySku(int $sku) 56 | { 57 | return $this->model->findBySku( $sku); 58 | } 59 | /** 60 | * @param BannerListPost $post 61 | * @param int $totalRows 62 | * 63 | * @return BannerModel[] 64 | */ 65 | public function findListByPage(BannerListPost $post, int &$totalRows) 66 | { 67 | $where = []; 68 | if ($post->title) { 69 | $where[] = ['title', 'like', '%'.addcslashes( $post->title, '%').'%']; 70 | } 71 | if ($post->status) { 72 | $where[] = ['status', '=', $post->status]; 73 | } 74 | $list = $this->model->findListByPage( $where, $post->page, $post->perPage, $totalRows); 75 | 76 | return $list; 77 | } 78 | } -------------------------------------------------------------------------------- /api/app/Services/Backend/BannerService.php: -------------------------------------------------------------------------------- 1 | bannerRepository = $bannerRepository; 24 | } 25 | public function createRow(BannerEditPost $post) 26 | { 27 | return $this->bannerRepository->createRow($post); 28 | } 29 | 30 | /** 31 | * 32 | * @param $id 33 | * 34 | * @return int 35 | */ 36 | public function deleteRow($id) 37 | { 38 | return $this->bannerRepository->deleteRow($id); 39 | } 40 | public function editRow(BannerModel $model, BannerEditPost $post) 41 | { 42 | $model->title = $post->title; 43 | $model->sku = $post->sku; 44 | $model->price = $post->price; 45 | $model->return_type = $post->return_type; 46 | $model->return_value = $post->return_value; 47 | $model->status = $post->status; 48 | $model->redirect_url = $post->redirect_url; 49 | $model->pic_url = $post->pic_url; 50 | 51 | return $model->save(); 52 | } 53 | /** 54 | * 55 | * @param int $id 56 | * 57 | * @return BannerModel 58 | */ 59 | public function findById(int $id) 60 | { 61 | return $this->bannerRepository->findById( $id); 62 | } 63 | 64 | /** 65 | * 66 | * @param int $sku 67 | * 68 | * @return BannerModel 69 | */ 70 | public function findBySku(int $sku) 71 | { 72 | return $this->bannerRepository->findBySku( $sku ); 73 | } 74 | /** 75 | * 76 | * @param BannerListPost $post 77 | * @param int $totalRows 78 | * 79 | * @return BannerModel[] 80 | */ 81 | public function findListByPage(BannerListPost $post, int &$totalRows) 82 | { 83 | return $this->bannerRepository->findListByPage( $post, $totalRows); 84 | } 85 | } -------------------------------------------------------------------------------- /api/app/Models/AdminUserModel.php: -------------------------------------------------------------------------------- 1 | 'required|max:255', 44 | * @return array 45 | */ 46 | public function getRules() 47 | { 48 | return [ 49 | //'merchant_id' => 'required|integer', 50 | //'title' => 'required|max:255', 51 | ]; 52 | } 53 | 54 | 55 | /** 56 | * Get the identifier that will be stored in the subject claim of the JWT. 57 | * 58 | * @return mixed 59 | */ 60 | public function getJWTIdentifier() 61 | { 62 | return $this->getKey(); 63 | } 64 | 65 | /** 66 | * Return a key value array, containing any custom claims to be added to the JWT. 67 | * 68 | * @return array 69 | */ 70 | public function getJWTCustomClaims() 71 | { 72 | return [ 73 | 'id' => $this->id, 74 | 'ip' => UtilHelper::getIp(), 75 | ]; 76 | } 77 | /** 78 | * 79 | * 80 | * @param $userName 81 | * 82 | * @return $this 83 | */ 84 | public function findByUserName(string $userName) 85 | { 86 | return static::query()->where(['user_name'=>$userName])->orderByDesc( 'id')->first(); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /api/app/Models/BannerModel.php: -------------------------------------------------------------------------------- 1 | 'required|max:255', 51 | * @return array 52 | */ 53 | public function getRules() 54 | { 55 | return [ 56 | //'merchant_id' => 'required|integer', 57 | //'title' => 'required|max:255', 58 | ]; 59 | } 60 | 61 | public function formatReturnType() 62 | { 63 | return $this->return_type == BannerEnum::RETURN_TYPE_AMOUNT ? '¥'.$this->return_value : $this->return_value.'%'; 64 | } 65 | 66 | public function isReturnAmount() 67 | { 68 | return $this->return_type == BannerEnum::RETURN_TYPE_AMOUNT; 69 | } 70 | public function isReturnPercent() 71 | { 72 | return $this->return_type == BannerEnum::RETURN_TYPE_PERCENT; 73 | } 74 | 75 | /** 76 | * 77 | * @param int $sku 78 | * 79 | * @return static 80 | */ 81 | public function findBySku(int $sku) 82 | { 83 | return static::query()->where('sku','=', $sku)->first(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /frontend/src/views/user/Trade.vue: -------------------------------------------------------------------------------- 1 | 43 | 46 | 91 | -------------------------------------------------------------------------------- /api/app/Http/Requests/Backend/OrderCreatePost.php: -------------------------------------------------------------------------------- 1 | 'required|integer|min:1', 39 | 'order_sn' => 'required|min:1|max:64', 40 | 'order_status' => 'required|integer|in:'.implode(',', [ 41 | OrderEnum::STATUS_PENDING, 42 | OrderEnum::STATUS_CANCELLED, 43 | OrderEnum::STATUS_PAYED, 44 | OrderEnum::STATUS_DELIVERED, 45 | ]), 46 | 'order_time' => 'required|date|date_format:Y-m-d H:i:s', 47 | 'pay_time' => 'nullable|date|date_format:Y-m-d H:i:s', 48 | 'deliver_time' => 'nullable|date|date_format:Y-m-d H:i:s', 49 | 'goods_list' => 'required|array', 50 | 'goods_list.*.sku' => 'required|integer|min:1', 51 | 'goods_list.*.sku_quantity' => 'required|integer|min:1', 52 | 'goods_list.*.sku_price' => 'required|numeric|min:0', 53 | ]; 54 | } 55 | 56 | public function attributes() 57 | { 58 | return [ 59 | 'order_sn' => '订单号', 60 | 'order_status' => '订单状态', 61 | 'order_time' => '下单时间', 62 | 'pay_time' => '付款时间', 63 | 'deliver_time' => '发货时间', 64 | 'goods_list' => '商品列表', 65 | 'goods_list.*.sku' => 'SKU', 66 | 'goods_list.*.sku_quantity' => '商品数量', 67 | 'goods_list.*.sku_price' => '售价', 68 | ]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /api/app/Exceptions/MyExceptionHandler.php: -------------------------------------------------------------------------------- 1 | getMessage(); 26 | } else { 27 | $msg = __('base.server_error'); 28 | } 29 | $responseData = [ 30 | 'code' => is_numeric( $code) ? intval( $code) : $code, 31 | 'msg' => $msg, 32 | 'data' => $this->convertExceptionToArray($e) 33 | ]; 34 | return new JsonResponse( 35 | $responseData, 36 | 200, 37 | [], 38 | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES 39 | ); 40 | } 41 | 42 | 43 | /** 44 | * 覆写父类的方法 45 | * Convert the given exception to an array. 46 | * 47 | * @param \Exception $e 48 | * @return array 49 | */ 50 | protected function convertExceptionToArray(Exception $e) 51 | { 52 | return config('app.debug') ? [ 53 | 'message' => $e->getMessage(), 54 | 'exception' => get_class($e), 55 | 'file' => $e->getFile(), 56 | 'line' => $e->getLine(), 57 | ] : []; 58 | } 59 | 60 | /** 61 | * 覆写父类的方法 62 | * Convert a validation exception into a JSON response. 63 | * 64 | * @param \Illuminate\Http\Request $request 65 | * @param \Illuminate\Validation\ValidationException $exception 66 | * @return \Illuminate\Http\JsonResponse 67 | */ 68 | protected function invalidJson($request, ValidationException $exception) 69 | { 70 | $errorCode = CodeEnum::BASE_INVALID_PARAMETER; 71 | $errorMessage = $exception->validator->errors()->first(); 72 | return response()->json([ 73 | 'code' => $errorCode, 74 | 'msg' => $errorMessage, 75 | 'data'=> null 76 | ]); 77 | // return parent::invalidJson( $request, $exception); 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /frontend/src/views/user/WithdrawList.vue: -------------------------------------------------------------------------------- 1 | 50 | 53 | 98 | -------------------------------------------------------------------------------- /frontend/src/views/user/Order.vue: -------------------------------------------------------------------------------- 1 | 50 | 53 | 98 | -------------------------------------------------------------------------------- /api/app/Listeners/NewOrderRecordListener.php: -------------------------------------------------------------------------------- 1 | orderService = $orderService; 33 | $this->bannerService = $bannerService; 34 | } 35 | 36 | /** 37 | * Handle the event. 38 | * 39 | * @param NewOrderRecordEvent $event 40 | * @return void 41 | * @throws \Exception 42 | */ 43 | public function handle(NewOrderRecordEvent $event) 44 | { 45 | $orderModel = $this->orderService->findById( $event->orderId ); 46 | if (! $orderModel) { 47 | throw new \Exception('订单不存在, orderId='. $event->orderId, CodeEnum::BASE_SERVER_ERROR); 48 | } 49 | 50 | $orderGoodsList = $this->orderService->findGoodsListById( $event->orderId); 51 | foreach ($orderGoodsList as $goodsItem) { 52 | //小计 53 | $goodsItem->subtotal = bcmul($goodsItem->sku_price, $goodsItem->sku_quantity, 2); 54 | //默认 1% 55 | $goodsItem->sku_commission = bcmul(0.01, $goodsItem->subtotal, 2); 56 | $banner = $this->bannerService->findBySku( $goodsItem->sku ); 57 | if ($banner) { 58 | //存在 59 | if ($banner->isReturnAmount()) { 60 | //固定金额 61 | $goodsItem->sku_commission = bcmul($banner->return_value, $goodsItem->sku_quantity, 2); 62 | } elseif ($banner->isReturnPercent()) { 63 | //百分比 64 | $goodsItem->sku_commission = bcmul(($banner->return_value / 100), $goodsItem->subtotal, 2); 65 | } 66 | } 67 | $goodsItem->save(); 68 | $orderModel->commission = bcadd($orderModel->commission, $goodsItem->sku_commission, 2); 69 | $orderModel->order_amount = bcadd($orderModel->order_amount, $goodsItem->subtotal, 2); 70 | } 71 | $orderModel->save(); 72 | Log::info( '订单佣金计算完成, orderSn:'.$orderModel->order_sn.', commission:'.$orderModel->commission ); 73 | } 74 | } -------------------------------------------------------------------------------- /api/public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | define('LARAVEL_START', microtime(true)); 11 | 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Register The Auto Loader 15 | |-------------------------------------------------------------------------- 16 | | 17 | | Composer provides a convenient, automatically generated class loader for 18 | | our application. We just need to utilize it! We'll simply require it 19 | | into the script here so that we don't have to worry about manual 20 | | loading any of our classes later on. It feels great to relax. 21 | | 22 | */ 23 | 24 | require __DIR__.'/../vendor/autoload.php'; 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Turn On The Lights 29 | |-------------------------------------------------------------------------- 30 | | 31 | | We need to illuminate PHP development, so let us turn on the lights. 32 | | This bootstraps the framework and gets it ready for use, then it 33 | | will load up this application so that we can run it and send 34 | | the responses back to the browser and delight our users. 35 | | 36 | */ 37 | $env = env('APP_ENV', 'local'); 38 | //header('Access-Control-Allow-Origin: *'); 39 | header( 'Access-Control-Allow-Origin: http://localhost:8080' ); 40 | //if ($env === 'local') { 41 | // header( 'Access-Control-Allow-Origin: http://localhost:8080' ); 42 | //} else { 43 | // header('Access-Control-Allow-Origin: *'); 44 | //} 45 | header('Access-Control-Allow-Headers:Origin, Content-Type, Authorization, Cookie, Accept'); 46 | header('Access-Control-Allow-Methods: POST,GET,HEAD,OPTIONS,PUT,DELETE,PATCH'); 47 | header('Access-Control-Allow-Credentials: true'); 48 | 49 | if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { 50 | exit(); 51 | } 52 | 53 | $app = require_once __DIR__.'/../bootstrap/app.php'; 54 | 55 | /* 56 | |-------------------------------------------------------------------------- 57 | | Run The Application 58 | |-------------------------------------------------------------------------- 59 | | 60 | | Once we have the application, we can handle the incoming request 61 | | through the kernel, and send the associated response back to 62 | | the client's browser allowing them to enjoy the creative 63 | | and wonderful application we have prepared for them. 64 | | 65 | */ 66 | 67 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 68 | 69 | $response = $kernel->handle( 70 | $request = Illuminate\Http\Request::capture() 71 | ); 72 | 73 | $response->send(); 74 | 75 | $kernel->terminate($request, $response); 76 | -------------------------------------------------------------------------------- /api/app/Services/Backend/TradeService.php: -------------------------------------------------------------------------------- 1 | findById( $id); 31 | } 32 | 33 | /** 34 | * 35 | * @param string $sn 36 | * 37 | * @return FinanceTradeModel 38 | */ 39 | public function findBySn(string $sn) 40 | { 41 | return FinanceTradeModel::singleton()->findBySn( $sn); 42 | } 43 | 44 | /** 45 | * 46 | * @param int $userId 47 | * @param string $sn 48 | * @param float $amount 49 | * @param int $type 50 | * @param string $remark 51 | * @return bool 52 | */ 53 | public function createRow(int $userId, string $sn, float $amount, int $type, string $remark) 54 | { 55 | $model = new FinanceTradeModel(); 56 | $model->user_id = $userId; 57 | $model->business_sn = $sn; 58 | $model->amount = $amount; 59 | $model->type = $type; 60 | $model->remark = $remark; 61 | return $model->save(); 62 | } 63 | 64 | /** 65 | * 66 | * @param TradeListPost $post 67 | * @param int $totalRows 68 | * 69 | * @return FinanceTradeModel[] 70 | */ 71 | public function findListByPage(TradeListPost $post, int &$totalRows) 72 | { 73 | $where = []; 74 | if ($post->user_id) { 75 | $where[] = ['ft.user_id', '=', $post->user_id]; 76 | } 77 | if ($post->sn) { 78 | $where[] = ['ft.business_sn', '=', $post->sn]; 79 | } 80 | if ($post->type) { 81 | $where[] = ['ft.type', '=', $post->type]; 82 | } 83 | $query = FinanceTradeModel::query()->from(FinanceTradeModel::singleton()->getTable().' as ft'); 84 | $query->leftjoin(UsersModel::singleton()->getTable().' as u', 'u.id', '=', 'ft.user_id'); 85 | $query->select('ft.*', 'u.user_name'); 86 | $list = $query->where($where)->forPage( $post->page, $post->perPage)->orderByDesc( 'ft.id')->get()->all(); 87 | 88 | $totalRows = $query->toBase()->getCountForPagination(); 89 | 90 | return $list; 91 | } 92 | } -------------------------------------------------------------------------------- /api/app/Utils/UtilHelper.php: -------------------------------------------------------------------------------- 1 | format($format); 94 | } 95 | } -------------------------------------------------------------------------------- /api/routes/backend.php: -------------------------------------------------------------------------------- 1 | '/api', 'domain' => env('HOST_ADMIN'), 'namespace' => 'Backend'], function () { 16 | Route::post('login', 'AdminUserController@actionLogin'); 17 | 18 | }); 19 | //管理员 20 | Route::group(['prefix' => '/api', 'domain' => env('HOST_ADMIN'), 'middleware' => 'jwt:backend', 'namespace' => 'Backend'], function () { 21 | //上传图片 22 | Route::post('upload/img', 'UploadController@actionImage'); 23 | 24 | //管理员 25 | Route::post('admin', 'AdminUserController@actionCreate'); 26 | //修改自己的资料 27 | Route::put('admin/pwd', 'AdminUserController@actionEditPwd'); 28 | //管理员列表 29 | Route::post('admin/list', 'AdminUserController@actionList'); 30 | //删除管理员 31 | Route::delete('admin/{id}', 'AdminUserController@actionDelete')->where(['id' => '[0-9]+']); 32 | //修改管理员 33 | Route::put('admin/edit/{id}', 'AdminUserController@actionEdit')->where(['id' => '[0-9]+']); 34 | 35 | //联盟客列表 36 | Route::post('users/list', 'UserController@actionList'); 37 | //操作联盟客状态:审核通过、驳回、禁用、启用 38 | Route::patch('users/{id}', 'UserController@actionStatus')->where(['id' => '[0-9]+']); 39 | //修改联盟客 40 | Route::put('users/edit/{id}', 'UserController@actionEdit')->where(['id' => '[0-9]+']); 41 | 42 | //后台首页 43 | Route::get('dashboard', 'DashboardController@actionIndex'); 44 | 45 | //素材Banner(CRUD) 46 | Route::get('banners', 'BannerController@actionList'); 47 | Route::post('banners', 'BannerController@actionCreate'); 48 | Route::post('banners/{id}', 'BannerController@actionEdit')->where(['id' => '[0-9]+']); 49 | Route::delete('banners/{id}', 'BannerController@actionDelete')->where(['id' => '[0-9]+']); 50 | 51 | //订单列表 52 | Route::get('orders', 'OrderController@actionList'); 53 | //录入订单 54 | Route::post('orders', 'OrderController@actionCreate'); 55 | //录入订单 56 | Route::post('orders/{id}', 'OrderController@actionEdit')->where(['id' => '[0-9]+']); 57 | //发放佣金 58 | Route::put('orders/grant/{id}', 'OrderController@actionGrant')->where(['id' => '[0-9]+']); 59 | 60 | //提现列表 61 | Route::get('withdraws', 'WithdrawController@actionList'); 62 | //提现审核 63 | Route::put('withdraws/{id}', 'WithdrawController@actionUpdateStatus')->where(['id' => '[0-9]+']); 64 | //联盟客收支明细 65 | Route::get('trades', 'TradeController@actionList'); 66 | }); 67 | 68 | -------------------------------------------------------------------------------- /frontend/src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 22 | 25 | 75 | -------------------------------------------------------------------------------- /api/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 | 'permissions' => [ 50 | 'file' => [ 51 | 'public' => 0664, 52 | 'private' => 0600, 53 | ], 54 | 'dir' => [ 55 | 'public' => 0775, 56 | 'private' => 0700, 57 | ], 58 | ], 59 | ], 60 | 61 | 'public' => [ 62 | 'driver' => 'local', 63 | 'root' => storage_path('app/public'), 64 | 'url' => env('APP_URL').'/storage', 65 | 'visibility' => 'public', 66 | 'permissions' => [ 67 | 'file' => [ 68 | 'public' => 0664, 69 | 'private' => 0600, 70 | ], 71 | 'dir' => [ 72 | 'public' => 0775, 73 | 'private' => 0700, 74 | ], 75 | ], 76 | ], 77 | 78 | 's3' => [ 79 | 'driver' => 's3', 80 | 'key' => env('AWS_ACCESS_KEY_ID'), 81 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 82 | 'region' => env('AWS_DEFAULT_REGION'), 83 | 'bucket' => env('AWS_BUCKET'), 84 | 'url' => env('AWS_URL'), 85 | ], 86 | 87 | ], 88 | 89 | ]; 90 | -------------------------------------------------------------------------------- /api/config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'sync'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection information for each server that 24 | | is used by your application. A default configuration has been added 25 | | for each back-end shipped with Laravel. You are free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'retry_after' => 90, 49 | 'block_for' => 0, 50 | ], 51 | 52 | 'sqs' => [ 53 | 'driver' => 'sqs', 54 | 'key' => env('AWS_ACCESS_KEY_ID'), 55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 56 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 57 | 'queue' => env('SQS_QUEUE', 'your-queue-name'), 58 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 59 | ], 60 | 61 | 'redis' => [ 62 | 'driver' => 'redis', 63 | 'connection' => 'default', 64 | 'queue' => env('REDIS_QUEUE', 'default'), 65 | 'retry_after' => 90, 66 | 'block_for' => null, 67 | ], 68 | 69 | ], 70 | 71 | /* 72 | |-------------------------------------------------------------------------- 73 | | Failed Queue Jobs 74 | |-------------------------------------------------------------------------- 75 | | 76 | | These options configure the behavior of failed queue job logging so you 77 | | can control which database and table are used to store the jobs that 78 | | have failed. You may change them to any database / table you wish. 79 | | 80 | */ 81 | 82 | 'failed' => [ 83 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database'), 84 | 'database' => env('DB_CONNECTION', 'mysql'), 85 | 'table' => 'failed_jobs', 86 | ], 87 | 88 | ]; 89 | -------------------------------------------------------------------------------- /api/app/Services/Backend/AdminUserService.php: -------------------------------------------------------------------------------- 1 | forPage( $post->page, $post->perPage)->get()->all(); 33 | if (count($list)) { 34 | $totalRows = AdminUserModel::query()->count(); 35 | } 36 | return $list; 37 | } 38 | 39 | public function editRow(AdminUserModel $model, AdminUserEditPost $post) 40 | { 41 | $model->user_name = $post->userName; 42 | $model->real_name = $post->realName; 43 | if ($post->password) { 44 | $model->passwd = UtilHelper::hashPassword( $post->password); 45 | } 46 | return $model->save(); 47 | } 48 | 49 | /** 50 | * 51 | * @param $id 52 | * 53 | * @return int 54 | */ 55 | public function deleteRow($id) 56 | { 57 | return AdminUserModel::query()->where('id', $id)->delete(); 58 | } 59 | 60 | /** 61 | * 62 | * 63 | * @param $userName 64 | * 65 | * @return AdminUserModel|null 66 | */ 67 | public function login($userName) 68 | { 69 | $model = $this->findByUserName( $userName); 70 | if (!$model) { 71 | return null; 72 | } 73 | return $model; 74 | } 75 | 76 | /** 77 | * 78 | * @param int $id 79 | * 80 | * @return AdminUserModel 81 | */ 82 | public function findById(int $id) 83 | { 84 | return AdminUserModel::singleton()->findById( $id); 85 | } 86 | public function findByUserName($userName) 87 | { 88 | return AdminUserModel::singleton()->findByUserName( $userName); 89 | } 90 | 91 | public function registerNew(RegisterAdminPost $post) 92 | { 93 | $model = new AdminUserModel(); 94 | $model->user_name = $post->userName; 95 | $model->real_name = $post->realName; 96 | $model->passwd = UtilHelper::hashPassword( $post->password); 97 | return $model->save(); 98 | } 99 | 100 | public function editPassword(EditPasswordPost $post, AdminUserModel $model) 101 | { 102 | if ($post->passwordNew && $post->passwordOld) { 103 | $model->passwd = UtilHelper::hashPassword( $post->passwordNew ); 104 | } 105 | 106 | $model->real_name = $post->realName; 107 | 108 | return $model->save(); 109 | } 110 | } -------------------------------------------------------------------------------- /frontend/src/views/Reg.vue: -------------------------------------------------------------------------------- 1 | 26 | 29 | 86 | -------------------------------------------------------------------------------- /backend/src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 29 | 32 | 89 | -------------------------------------------------------------------------------- /api/config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'stack'), 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Log Channels 25 | |-------------------------------------------------------------------------- 26 | | 27 | | Here you may configure the log channels for your application. Out of 28 | | the box, Laravel uses the Monolog PHP logging library. This gives 29 | | you a variety of powerful log handlers / formatters to utilize. 30 | | 31 | | Available Drivers: "single", "daily", "slack", "syslog", 32 | | "errorlog", "monolog", 33 | | "custom", "stack" 34 | | 35 | */ 36 | 37 | 'channels' => [ 38 | 'stack' => [ 39 | 'driver' => 'stack', 40 | 'channels' => ['daily'], 41 | 'ignore_exceptions' => false, 42 | ], 43 | 44 | 'single' => [ 45 | 'driver' => 'single', 46 | 'path' => storage_path('logs/laravel.log'), 47 | 'level' => 'debug', 48 | ], 49 | 50 | 'daily' => [ 51 | 'driver' => 'daily', 52 | 'path' => storage_path('logs/laravel.log'), 53 | 'level' => 'debug', 54 | 'days' => 14, 55 | ], 56 | 57 | 'slack' => [ 58 | 'driver' => 'slack', 59 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 60 | 'username' => 'Laravel Log', 61 | 'emoji' => ':boom:', 62 | 'level' => 'critical', 63 | ], 64 | 65 | 'papertrail' => [ 66 | 'driver' => 'monolog', 67 | 'level' => 'debug', 68 | 'handler' => SyslogUdpHandler::class, 69 | 'handler_with' => [ 70 | 'host' => env('PAPERTRAIL_URL'), 71 | 'port' => env('PAPERTRAIL_PORT'), 72 | ], 73 | ], 74 | 75 | 'stderr' => [ 76 | 'driver' => 'monolog', 77 | 'handler' => StreamHandler::class, 78 | 'formatter' => env('LOG_STDERR_FORMATTER'), 79 | 'with' => [ 80 | 'stream' => 'php://stderr', 81 | ], 82 | ], 83 | 84 | 'syslog' => [ 85 | 'driver' => 'syslog', 86 | 'level' => 'debug', 87 | ], 88 | 89 | 'errorlog' => [ 90 | 'driver' => 'errorlog', 91 | 'level' => 'debug', 92 | ], 93 | 94 | 'null' => [ 95 | 'driver' => 'monolog', 96 | 'handler' => NullHandler::class, 97 | ], 98 | ], 99 | 100 | ]; 101 | -------------------------------------------------------------------------------- /api/app/Services/Frontend/UserService.php: -------------------------------------------------------------------------------- 1 | findByUserName( $userName); 35 | if (!$model) { 36 | $model = $this->findByMobile( $userName); 37 | if (!$model) { 38 | $model = $this->findByEmail( $userName); 39 | if (!$model) { 40 | return null; 41 | } 42 | } 43 | } 44 | $model->incrementLoginCount(); 45 | return $model; 46 | } 47 | 48 | 49 | public function findByMobile($userName) 50 | { 51 | if ( is_numeric( $userName ) && intval( $userName ) > 0 ) { 52 | $model = UsersModel::singleton()->findByMobile( $userName ); 53 | if ( $model ) { 54 | return $model; 55 | } 56 | } 57 | 58 | return null; 59 | } 60 | public function findByEmail($userName) 61 | { 62 | $model = UsersModel::singleton()->findByEmail( $userName); 63 | if ($model) { 64 | return $model; 65 | } 66 | return null; 67 | } 68 | 69 | public function findByUserName($userName) 70 | { 71 | return UsersModel::singleton()->findByUserName( $userName); 72 | } 73 | 74 | public function registerNew(RegisterPost $post) 75 | { 76 | DB::beginTransaction(); 77 | try { 78 | $model = new UsersModel(); 79 | $model->user_name = $post->userName; 80 | $model->passwd = UtilHelper::hashPassword( $post->password ); 81 | $model->status = UserEnum::STATUS_PENDING; 82 | 83 | $model->save(); 84 | 85 | UserBalanceModel::singleton()->createRow($model->id); 86 | 87 | return true; 88 | } catch (\Throwable $throwable) { 89 | DB::rollBack(); 90 | return false; 91 | } 92 | } 93 | 94 | public function editProfile(EditProfilePost $post, UsersModel $model) 95 | { 96 | if ($post->birthday) $model->birthday = $post->birthday; 97 | if ($post->gender) $model->gender = $post->gender; 98 | if ($post->email) $model->email = $post->email; 99 | if ($post->mobile) $model->mobile = $post->mobile; 100 | return $model->save(); 101 | 102 | } 103 | 104 | public function editPassword(EditPasswordPost $post, UsersModel $model) 105 | { 106 | $model->passwd = UtilHelper::hashPassword( $post->passwordNew); 107 | return $model->save(); 108 | } 109 | } -------------------------------------------------------------------------------- /backend/src/views/Reg.vue: -------------------------------------------------------------------------------- 1 | 26 | 29 | 84 | --------------------------------------------------------------------------------