Pārlūkot izejas kodu

feat: 分销二维码

xiansin 2 gadi atpakaļ
vecāks
revīzija
2f74d1b099

+ 130 - 0
mini/pages/share/components/Qrcode.vue

xqd
@@ -0,0 +1,130 @@
+<template>
+  <view class="qrcode-modal" :class="{show: show}">
+    <view class="content">
+      <view class="qrcode-modal-content dir-top-wrap cross-center">
+        <view class="head-img">
+          <u-image
+            width="150rpx"
+            height="150rpx"
+            :src="userInfo.avatar"
+            shape="circle"
+          />
+        </view>
+        <view class="nickname">{{ userInfo.nickname }}</view>
+        <u-line class="u-line" border-style="dashed" length="90%" />
+        <view class="qrcode">
+          <u-image
+            width="320rpx"
+            height="320rpx"
+            :src="userInfo.share_qrcode"
+          />
+        </view>
+      </view>
+      <u-button
+        shape="circle"
+        hover-class="none"
+        :custom-style="{
+          background:'linear-gradient(270deg, #6EEBE8 0%, #FF74B9 100%)',
+          boxShadow: '0 0 20rpx rgba(13,239,250,.3)',
+          borderColor:'none',
+          color: '#ffffff',
+          marginTop: '50rpx'}"
+        @click="handleSave"
+      >保存</u-button>
+      <u-button
+        shape="circle"
+        hover-class="none"
+        :custom-style="{
+          borderColor:'none',
+          marginTop: '30rpx'}"
+        @click="handleCancel"
+      >取消</u-button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+export default {
+  name: 'Qrcode',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {}
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  methods: {
+    handleSave() {
+      console.log('-->data', this.userInfo.share_qrcode)
+      this.$util.saveImage(this.userInfo.share_qrcode).then(res => {
+        this.$emit('update:show', false)
+      })
+    },
+    handleCancel() {
+      this.$emit('update:show', false)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.qrcode-modal{
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 750rpx;
+  height: 100vh;
+  background: $bg-color;
+  z-index: 999;
+  display: none;
+  animation: show-modal linear .5s;
+  &.show{
+    display: block;
+  }
+  .content{
+    position: absolute;
+    width: 600rpx;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%,-50%);
+    .qrcode-modal-content{
+      background: #1B203C;
+      position: relative;
+      width: 100%;
+      height: 50vh;
+      background: url("/static/image/share-qrcode-bg.png") no-repeat center;
+      background-size: 100% 100%;
+      .head-img{
+        position: absolute;
+        top: -75rpx;
+      }
+      .nickname{
+        position: absolute;
+        color: #ffffff;
+        top: 15%;
+        font-size: 34rpx;
+      }
+      .u-line{
+        position: absolute;
+        top: 28.55%;
+      }
+      .qrcode{
+        position: absolute;
+        top: 35%;
+      }
+    }
+  }
+}
+@keyframes show-modal {
+  0%{opacity: 0;}
+  100%{opacity: 1;}
+}
+</style>

+ 32 - 2
mini/pages/share/index.vue

xqd xqd xqd
@@ -51,12 +51,16 @@
         <u-icon name="arrow-right" :color="$colors.infoColor" bold />
       </button>
     </view>
+    <!-- QRCODE 弹窗   -->
+    <qrcode :show.sync="qrcodeModal.show" />
   </view>
 </template>
 
 <script>
 import { mapState } from 'vuex'
+import Qrcode from './components/Qrcode'
 export default {
+  components: { Qrcode },
   data() {
     return {
       menus: [
@@ -64,8 +68,14 @@ export default {
         { icon: '/static/image/share/income.png', name: '分享佣金', href: '/pages/share/income' },
         { icon: '/static/image/share/order.png', name: '分享订单', href: '/pages/share/order' },
         { icon: '/static/image/share/detail.png', name: '提现明细', href: '/pages/share/withdrawDetail' },
-        { icon: '/static/image/share/team.png', name: '我的团队', href: '/pages/share/team' }
-      ]
+        { icon: '/static/image/share/team.png', name: '我的团队', href: '/pages/share/team' },
+        // #ifdef MP-TOUTIAO | MP-WEIXIN
+        { icon: '/static/image/share/qrcode.png', name: '分享二维码', type: 'share_qrcode' }
+        // #endif
+      ],
+      qrcodeModal: {
+        show: false
+      }
     }
   },
   computed: {
@@ -88,9 +98,29 @@ export default {
         })
       }
       // #endif
+      if (menu.type === 'share_qrcode') {
+        this.handleGenerateQrcode()
+      }
       if (menu.href) {
         this.$u.route(menu.href)
       }
+    },
+    handleGenerateQrcode() {
+      if (this.userInfo.share_qrcode) {
+        this.qrcodeModal.show = true
+        return
+      }
+      this.$loading()
+      this.$api.share.generateQrcode().then(res => {
+        this.$hideLoading()
+        // 获取用户信息
+        this.$api.user.info().then(res => {
+          this.$store.dispatch('user/info', res.data)
+          this.qrcodeModal.show = true
+        })
+      }).catch(() => {
+        this.$hideLoading()
+      })
     }
   }
 }

BIN
mini/static/image/share-qrcode-bg.png


BIN
mini/static/image/share/qrcode.png


+ 55 - 1
mini/utils/index.js

xqd
@@ -63,9 +63,63 @@ const tranNumber = (num, point = 2) => {
   }
 }
 
+const saveImage = url => {
+  return new Promise((resolve, reject) => {
+    uni.downloadFile({
+      url: url,
+      // #ifdef MP-TOUTIAO
+      header: {
+        "content-type": "application/json",
+      },
+      // #endif
+      success: (res) => {
+        if (res.statusCode === 200) {
+          console.log('下载成功');
+          uni.authorize({
+            // #ifdef MP-WEIXIN
+            scope: 'scope.writePhotosAlbum',
+            // #endif
+            // #ifdef MP-TOUTIAO
+            scope: "scope.album",
+            // #endif
+            success() {
+              uni.saveImageToPhotosAlbum({
+                filePath: res.tempFilePath,
+                success: function(red) {
+                  uni.$u.toast(`保存成功`)
+                  //uni.$u.toast(`保存路径:${red.savedFilePath}`)
+                  resolve()
+                },
+                fail: function(err) {
+                  console.log('-->save error',err)
+                  uni.$u.toast(`保存失败`)
+                  reject()
+                }
+              });
+            },
+            fail: err => {
+              console.log('-->authorize fail',err)
+              uni.$u.toast(`授权失败`+JSON.stringify(err))
+              reject()
+            }
+          })
+        }else{
+          uni.$u.toast(`保存失败`)
+          reject()
+        }
+      },
+      fail: err => {
+        uni.$u.toast(`保存失败`+JSON.stringify(err))
+        reject()
+      }
+    });
+  })
+}
+
 export default {
   copyText,
   checkOS,
   shareMessage,
-  tranNumber
+  tranNumber,
+  saveImage
 }

+ 19 - 0
server/app/Helper/ByteDance.php

xqd xqd
@@ -173,6 +173,21 @@ class ByteDance extends BaseUniPlatform
         ]);
     }
 
+    /**
+     * @return array|mixed
+     * @throws \Exception
+     */
+    public function generateQrcode()
+    {
+        $userId = \user()->id;
+        return $this->post($this->API::CREATE_QRCODE, [
+            'appname'      => 'douyin',
+            'access_token' => $this->accessToken,
+            'path'         =>urlencode('/pages/index/index?'."{user_id:{$userId}}"),
+            'width'        => 600,
+        ]);
+    }
+
     /**
      * 接口请求
      *
@@ -193,6 +208,10 @@ class ByteDance extends BaseUniPlatform
 
             $stringBody = (string)$res->getBody();
             $res = json_decode($stringBody, true);
+            // 生成二维码接口是直接放回 buffer的
+            if(empty($res) && $uri == $this->API::CREATE_QRCODE){
+                return [$stringBody];
+            }
 
             if(isset($res['err_no']) && !empty($res['err_no'])){
                 throw new \Exception("请求字节跳动API接口错误,错误码:{$res['err_no']},错误信息:{$res['err_tips']}");

+ 1 - 0
server/app/Helper/UniPlatform/BaseAPI.php

xqd
@@ -7,4 +7,5 @@ abstract class BaseAPI
     const LOGIN = '';
     const CREATE_ORDER = '';
     const ORDER_PUSH = '';
+    const CREATE_QRCODE = '';
 }

+ 6 - 0
server/app/Helper/UniPlatform/Bytedance/ByteDanceAPI.php

xqd
@@ -32,4 +32,10 @@ final class ByteDanceAPI extends BaseAPI
      * @url https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/order/order-sync/
      */
     const ORDER_PUSH = PAY_URL.'/order/v2/push';
+
+    /**
+     * 生成二维码
+     * @url https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/qr-code/create-qr-code
+     */
+    const CREATE_QRCODE =  PAY_URL.'/qrcode';
 }

+ 6 - 2
server/app/Helper/UniPlatform/Kuaishou/KuaishouAPI.php

xqd xqd
@@ -8,8 +8,6 @@ define('PAY_URL','https://open.kuaishou.com/openapi/mp/developer');
 
 final class KuaishouAPI extends BaseAPI
 {
-
-
     /**
      * 获取 ACCESS_TOKEN
      * @url https://mp.kuaishou.com/docs/develop/server/getAccessToken.html
@@ -27,4 +25,10 @@ final class KuaishouAPI extends BaseAPI
      * @url https://mp.kuaishou.com/docs/develop/server/epay/interfaceDefinition.html
      */
     const CREATE_ORDER = PAY_URL.'/epay/create_order';
+
+    /**
+     * 生成二维码
+     * @url https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/qr-code/create-qr-code
+     */
+    const CREATE_QRCODE =  '';
 }

+ 1 - 0
server/app/Helper/UniPlatform/Wechat/WechatAPI.php

xqd
@@ -10,4 +10,5 @@ final class WechatAPI extends BaseAPI
     const ACCESS_TOKEN = '';
     const LOGIN = '';
     const CREATE_ORDER = '';
+    const CREATE_QRCODE = '';
 }

+ 29 - 0
server/app/Helper/function.php

xqd
@@ -340,3 +340,32 @@ if(!function_exists('user')){
     }
 }
 
+if (!function_exists('save2Oss')) {
+    /**
+     * @create       JianJia.Zhou<z656123456@gmail.com>
+     * @param string $path
+     * @return string
+     * @throws Exception
+     */
+    function save2Oss(string $path) : string
+    {
+        if(config('app.env') !== 'production') {
+            //return Storage::url($path);
+        }
+        try {
+            $endpoint = 'http://'.env('ALI_OSS_BUCKET').'.'.env('ALI_OSS_ENDPOINT');
+            $ossClient = new \OSS\OssClient(env('ALI_OSS_ACCESS_ID'), env('ALI_OSS_ACCESS_KEY'), $endpoint, true);
+            $filename = explode('/',$path);
+            $filename = array_pop($filename);
+            $object = 'qrcode/'.$filename;
+            $ossClient->uploadFile(env('ALI_OSS_BUCKET'), $object, $path);
+            unlink($path);
+            return $endpoint.'/'.$object;
+        } catch (\OSS\Core\OssException $e) {
+            dd($e->getMessage());
+            //throw new Exception( $e->getMessage());
+        }
+    }
+}
+
+

+ 42 - 3
server/app/Http/Controllers/V1/Share/ShareController.php

xqd xqd
@@ -10,10 +10,12 @@ namespace App\Http\Controllers\V1\Share;
 use App\Http\Controllers\V1\Controller;
 use App\Models\ShareConfig;
 use App\Models\UserWithdraw;
+use App\Services\Api\ErrorMsgServive;
+use Illuminate\Http\JsonResponse;
 
 class ShareController extends Controller
 {
-    public function income()
+    public function income(): JsonResponse
     {
         // 已提现
         $been = UserWithdraw::where('user_id', \user()->id)
@@ -29,15 +31,52 @@ class ShareController extends Controller
         ]);
     }
 
-    public function tips()
+    public function tips(): JsonResponse
     {
         $config = ShareConfig::first();
         return $this->success($config->withdraw_desc);
     }
 
-    public function setting()
+    public function setting(): JsonResponse
     {
         $config = ShareConfig::first();
         return $this->success($config);
     }
+
+    public function generateQrcode(): JsonResponse
+    {
+        $user = \user();
+        if(!$user->scene_code || !$user->share_qrcode){
+            try {
+                $app = $this->getUniFactory($user->info->platform);
+                $user->scene_code = md5(uniqid().$user->id);
+                if($user->info->platform == 1){ // 抖音
+                    $response = $app->generateQrcode();
+                    $filename = public_path('static/qrcode_')."{$user->scene_code}.png";
+                    file_put_contents($filename, $response[0]);
+                    $user->share_qrcode = save2Oss($filename);
+                }elseif($user->info->platform == 2){ // 快手
+                    return $this->error('当前平台不支持');
+                }elseif($user->info->platform == 3){ // 微信
+                    $response = $app->mini()->app_code->getUnlimit($user->scene_code, [
+                        'page'  => '/pages/index/index',
+                        'width' => 600,
+                    ]);
+                    if(!empty($response['errcode'])){
+                        throw new \Exception($response['errmsg']);
+                    }
+                    if ($response instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
+                        $filename = $response->saveAs(public_path('assets/qrcode'), "{$user->scene_code}.png");
+                        $user->share_qrcode = save2Oss($filename);
+                    }
+                }
+                $user->save();
+            }catch (\Exception $ex){
+                ErrorMsgServive::write($ex, \request()->url());
+                return $this->error('生成二维码失败');
+            }
+        }
+
+        return $this->success();
+    }
 }

+ 8 - 3
server/app/Http/Controllers/V1/UserController.php

xqd
@@ -94,10 +94,15 @@ class UserController extends Controller
     public function bind($id)
     {
         $user = \user();
-        $parent = User::find($id);
+        if(is_numeric($id)){
+            $user = \user();
+            $parent = User::find($id);
+        }else{ // 微信扫场景码
+            $parent = User::where('scene_code', $id)->first();
+        }
         // 没有绑定上级 且上级存在其实分销商 且不是自己
-        if(!$user->parent_id && $parent && $parent->is_share && $user->parent_id != $user->id){
-            $user->parent_id = $id;
+        if(!$user->parent_id && $parent && $parent->is_share && $parent->id != $user->id){
+            $user->parent_id = $parent->id;
             $user->become_child_at = Carbon::now()->toDateTimeString();
 
             // 之前不是分销商 自动成为分销商

+ 3 - 1
server/app/Models/User.php

xqd xqd
@@ -3,6 +3,7 @@
 namespace App\Models;
 
 use App\Casts\DefaultAvatar;
+use App\Casts\HttpToHttps;
 use Carbon\Carbon;
 use Dcat\Admin\Traits\HasDateTimeFormatter;
 use Illuminate\Database\Eloquent\SoftDeletes;
@@ -107,7 +108,8 @@ class User extends Authenticatable implements JWTSubject
     protected $casts = [
         'total_income' => 'decimal:2',
         'income' => 'decimal:2',
-        'avatar' => DefaultAvatar::class
+        'avatar' => DefaultAvatar::class,
+        'share_qrcode' => HttpToHttps::class,
     ];
 
     public function getJWTIdentifier()

+ 1 - 0
server/composer.json

xqd
@@ -8,6 +8,7 @@
         "php": "^7.3|^8.0",
         "ext-json": "*",
         "alibabacloud/iot": "^1.8",
+        "aliyuncs/oss-sdk-php": "^2.6",
         "api-ecosystem-for-laravel/dingo-api": "^v3.1.1",
         "dcat/easy-excel": "^1.0",
         "dcat/laravel-admin": "2.*",

+ 1 - 1
server/composer.lock

xqd
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "2d2e458a1da59e616f87d59326bd802c",
+    "content-hash": "9bfbb41140574065e42cf9039aadf913",
     "packages": [
         {
             "name": "adbario/php-dot-notation",

+ 1 - 1
server/routes/api.php

xqd
@@ -175,7 +175,7 @@ $api->version('v1', ['namespace' => 'App\Http\Controllers\V1'], function ($api)
             $api->get('income', 'ShareController@income'); // 分销明细
             $api->get('tips', 'ShareController@tips'); // 用户需知
             $api->get('setting', 'ShareController@setting'); // 设置
-            $api->get('generate/qrcode', 'ShareController@generateQrcode'); // 生成二维码
+            $api->post('generate/qrcode', 'ShareController@generateQrcode'); // 生成二维码
         });
 
         // 短剧