Quellcode durchsuchen

退货录入添加调用usb摄像头拍照上传图片工能

haozi vor 4 Jahren
Ursprung
Commit
00c38637e6

+ 68 - 77
app/Http/Controllers/RejectedBillItemController.php

@@ -2,14 +2,13 @@
 
 namespace App\Http\Controllers;
 
-use App\Commodity;
+
+use App\Components\AsyncResponse;
 use App\Events\InformWMSReceivedEvent;
-use App\OrderIssue;
 use App\Owner;
 use App\RejectedBill;
 use App\RejectedBillItem;
-use App\Services\CommodityService;
-use App\Services\RejectedBillService;
+use App\UploadFile;
 use App\WMSReflectReceive;
 use App\WMSReflectReceiveSku;
 use Illuminate\Http\Request;
@@ -17,40 +16,12 @@ use Illuminate\Support\Collection;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Gate;
 use Illuminate\Support\Facades\Validator;
-use Zttp\Zttp;
+use Intervention\Image\Facades\Image;
+use Ramsey\Uuid\Uuid;
 
 class RejectedBillItemController extends Controller
 {
-    /**
-     * Display a listing of the resource.
-     *
-     * @return \Illuminate\Http\Response
-     */
-    public function index()
-    {
-        //
-    }
-
-    /**
-     * Show the form for creating a new resource.
-     *
-     * @return \Illuminate\Http\Response
-     */
-    public function create()
-    {
-        //
-    }
-
-    /**
-     * Store a newly created resource in storage.
-     *
-     * @param  \Illuminate\Http\Request  $request
-     * @return \Illuminate\Http\Response
-     */
-    public function store(Request $request)
-    {
-        //
-    }
+    use AsyncResponse;
 
     public function apiStore(Request $request)
     {
@@ -349,8 +320,8 @@ class RejectedBillItemController extends Controller
         if(!$request->input('id_rejected_bill')){
             return ['success'=>'false','failure_info'=>'表头id不能为空'];
         }
-        $rejectedBillItems = RejectedBillItem::where('id_rejected_bill',$request->input('id_rejected_bill'))->get();
-        return ['success'=>'true','items'=>$rejectedBillItems->toArray()];
+        $rejectedBillItems = RejectedBillItem::query()->with(['uploadFiles'])->where('id_rejected_bill',$request->input('id_rejected_bill'))->get();
+        return ['success'=>'true','items'=>$rejectedBillItems];
     }
     public function apiDelete(Request $request)
     {
@@ -369,48 +340,68 @@ class RejectedBillItemController extends Controller
         app('LogService')->log(__METHOD__,__FUNCTION__,json_encode($rejectedBillItem),Auth::user()['id']);
         return ['success'=>'true','id'=>$request->input('id')];
     }
-    /**
-     * Display the specified resource.
-     *
-     * @param  \App\RejectedBillItem  $rejectedBillItem
-     * @return \Illuminate\Http\Response
-     */
-    public function show(RejectedBillItem $rejectedBillItem)
-    {
-        //
-    }
 
-    /**
-     * Show the form for editing the specified resource.
-     *
-     * @param  \App\RejectedBillItem  $rejectedBillItem
-     * @return \Illuminate\Http\Response
-     */
-    public function edit(RejectedBillItem $rejectedBillItem)
+    public function apiUpload(Request $request)
     {
-        //
-    }
-
-    /**
-     * Update the specified resource in storage.
-     *
-     * @param  \Illuminate\Http\Request  $request
-     * @param  \App\RejectedBillItem  $rejectedBillItem
-     * @return \Illuminate\Http\Response
-     */
-    public function update(Request $request, RejectedBillItem $rejectedBillItem)
-    {
-        //
+        $files=$request->file("files");
+        if (!$files)$this->error("未传递照片");
+        $id=$request->input('id');
+        $rejectedBillItem=RejectedBillItem::query()->find($id);
+        if (!$rejectedBillItem)$this->error("未找到该退货详情!");
+        $res = [];
+        foreach ($files as $file){
+            if (!$file->isValid()){
+                return ['success'=>false,'error'=>"找不到照片!"];
+            }
+            $tmpFile = $file->getRealPath();
+            if (! is_uploaded_file($tmpFile)) {
+                return ['success'=>false,'error'=>"文件错误!"];
+            }
+            $fileExtension=$file->getClientOriginalExtension();
+
+            // 5.存储, 生成一个随机文件名
+            $fileName = date('ymd').'-'.Uuid::uuid1();//thumbnail common bulky
+            $thumbnailName=storage_path('app/public/files/'.$fileName.'-thumbnail.'.$fileExtension);
+            $commonName=storage_path('app/public/files/'.$fileName.'-common.'.$fileExtension);
+            $bulkyName=storage_path('app/public/files/'.$fileName.'-bulky.'.$fileExtension);
+            $result=move_uploaded_file ($tmpFile ,$bulkyName);
+            if ($result){
+                $img=Image::make($bulkyName);
+                if ($img->height() > $img->width())
+                    $img->heighten(250)->save($commonName);
+                else $img->widen(250)->save($commonName);
+                $img->heighten(28)->save($thumbnailName);
+                /** @var UploadFile|\stdClass $uploadFile */
+                $uploadFile=new UploadFile([
+                    "table_name"=>"rejected_bill_items",
+                    "table_id"=>$id,
+                    "url"=>'/files/'.$fileName,
+                    "type"=>$fileExtension,
+                ]);
+                if ($uploadFile->save())
+                    app('LogService')->log(__CLASS__,'退货详情图片上传',json_encode($request),Auth::user()['id']);
+                $res[] = $uploadFile;
+            }else $this->error("图片存储失败,检查服务器状态");
+        }
+        $this->success($res);
     }
-
-    /**
-     * Remove the specified resource from storage.
-     *
-     * @param  \App\RejectedBillItem  $rejectedBillItem
-     * @return \Illuminate\Http\Response
-     */
-    public function destroy(RejectedBillItem $rejectedBillItem)
-    {
-        //
+    //删除照片
+    public function apiDeleteImg(Request $request){
+        $id=$request->input("id");
+        $url=$request->input("url");
+        $query=UploadFile::query()->where('table_name','rejected_bill_items');
+        if ($url)$query = $query->where('table_id',$id)->where("url",$request->input("url"));
+        else $query = $query->whereIn('table_id',$request->input("id"));
+        foreach ($query->get() as $uploadFile){
+            $bulky=storage_path('app/public/'.$uploadFile->url.'-bulky.'.$uploadFile->type);
+            $common=storage_path('app/public/'.$uploadFile->url.'-common.'.$uploadFile->type);
+            $thumbnail=storage_path('app/public/'.$uploadFile->url.'-thumbnail.'.$uploadFile->type);
+            if (file_exists($bulky) && file_exists($common) && file_exists($thumbnail)){
+                unlink($bulky);unlink($common);unlink($thumbnail);
+            }
+        }
+        $query->delete();
+        app('LogService')->log(__METHOD__,'退货商品详情图片删除',json_encode($request),Auth::user()['id']);
+        $this->success();
     }
 }

+ 3 - 0
app/RejectedBillItem.php

@@ -30,6 +30,9 @@ class RejectedBillItem extends Model
     public function quality(){
         return $this->belongsTo(QualityLabel::class, 'id_quality_label', 'id');
     }
+    public function uploadFiles(){
+        return $this->hasMany(UploadFile::class,'table_id','id')->where('table_name','rejected_bill_items');
+    }
     public function wmsReflectSku(){
         $bill=$this->rejectedBill()->first();
         $wmsReflectReceive=$bill->wmsReflectReceive()->first();

+ 1 - 1
app/Services/RejectedService.php

@@ -24,7 +24,7 @@ class RejectedService
     private function  conditionQuery(array $param)
     {
         $user = Auth::user();
-        $rejectedBills = RejectedBill::query()->with('user','owner', 'logistic', 'items.quality','orderIssueRejectedBill:logistic_number_return')
+        $rejectedBills = RejectedBill::query()->with('user','owner', 'logistic', 'items.quality', 'items.uploadFiles','orderIssueRejectedBill:logistic_number_return')
             ->orderBy('rejected_bills.id', 'desc')
             ->whereIn('rejected_bills.id_owner', $user ? (app('UserService')->getPermittingOwnerIds($user) ?? []) : []);
         $columnQueryRules = [

+ 184 - 13
resources/views/rejected/create.blade.php

@@ -269,17 +269,18 @@
                         <div class="list-group">
                             <table class="table table-sm table-striped table-warning table-hover table-bordered">
                                 <tr>
-                                    <th>序号</th>
-                                    <th>商品条码</th>
-                                    <th>商品名称</th>
-                                    <th>数量</th>
-                                    <th>是否正品</th>
-                                    <th>生产日期</th>
-                                    <th>效期</th>
-                                    <th>批次号</th>
-                                    <th>备注</th>
+                                    <th class="text-center">序号</th>
+                                    <th class="text-center">商品条码</th>
+                                    <th class="text-center">商品名称</th>
+                                    <th class="text-center">数量</th>
+                                    <th class="text-center">是否正品</th>
+                                    <th class="text-center">生产日期</th>
+                                    <th class="text-center">效期</th>
+                                    <th class="text-center">批次号</th>
+                                    <th class="text-center">备注</th>
+                                    <th class="text-center">照片</th>
                                     @can('退货管理-删除')
-                                        <th></th>@endcan
+                                        <th class="text-center">操作</th>@endcan
                                 </tr>
                                 <tr :class="[item.isEditing?'bg-warning':'']"
                                     v-for="(item,i) in items" :data-id="item.id" @click="editItem">
@@ -292,10 +293,19 @@
                                     <td>@{{item.validity_at}}</td>
                                     <td>@{{item.batch_number}}</td>
                                     <td>@{{item.remark}}</td>
+                                    <td>
+                                        <div align="center" @mouseleave="removeCommonImg('common_img_'+item.id)" @mouseenter="commonImg('img_'+item.id,item.upload_files)">
+                                            <button class="btn btn-outline-secondary" @click="takePhoto(item.id,i)">拍照上传</button>
+                                            <div :id="'img_'+item.id">
+                                                <img v-for="uploadFile in item.upload_files"  :src="imgPrefix+uploadFile.url+'-thumbnail.'+uploadFile.type">
+                                            </div>
+                                        </div>
+                                    </td>
                                     @can('退货管理-删除')
                                         <td>
                                             <button class="btn btn-outline-danger" @click="deleteItem">删</button>
-                                        </td>@endcan
+                                        </td>
+                                    @endcan
                                 </tr>
                             </table>
                         </div>
@@ -449,6 +459,7 @@
         </div>
         <div id="drag" class="col-4 p-0 " style="position:fixed;bottom: 30px;left: 10px;">
             <video id="photo" width="50%" height="auto" controls></video>
+            <canvas id='canvas'width='400' height='300' hidden></canvas>
         </div>
     </div>
 @endsection
@@ -478,16 +489,75 @@
                 if (left >pageWidth)left=pageWidth;
                 if (top <0)top=0;
                 if (top >pageHeight)top=pageHeight;
-
                 drag.style.left = left + "px";
                 drag.style.top = top + "px";
             }
         })
+        let MediaUtils = {
+            /**
+             * 获取用户媒体设备(处理兼容的问题)
+             * @param videoEnable {boolean} - 是否启用摄像头
+             * @param audioEnable {boolean} - 是否启用麦克风
+             * @param callback {Function} - 处理回调
+             */
+            getUserMedia: function (videoEnable, audioEnable, callback) {
+                navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia
+                    || navigator.msGetUserMedia || window.getUserMedia;
+                let constraints = {video: videoEnable, audio: audioEnable};
+                if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+                    navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
+                        callback(false, stream);
+                    })['catch'](function (err) {
+                        callback(err);
+                    });
+                } else if (navigator.getUserMedia) {
+                    navigator.getUserMedia(constraints, function (stream) {
+                        callback(false, stream);
+                    }, function (err) {
+                        callback(err);
+                    });
+                } else {
+                    callback(new Error('Not support userMedia'));
+                }
+            },
+            /**
+             * 关闭媒体流
+             * @param stream {MediaStream} - 需要关闭的流
+             */
+            closeStream: function (stream) {
+                if (typeof stream.stop === 'function') {
+                    stream.stop();
+                } else {
+                    let trackList = [stream.getAudioTracks(), stream.getVideoTracks()];
 
+                    for (let i = 0; i < trackList.length; i++) {
+                        let tracks = trackList[i];
+                        if (tracks && tracks.length > 0) {
+                            for (let j = 0; j < tracks.length; j++) {
+                                let track = tracks[j];
+                                if (typeof track.stop === 'function') {
+                                    track.stop();
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        };
 
         let vueList = new Vue({
             el: "#editPanel",
             data: {
+                imgPrefix:"{{asset("/storage")}}",
+                // 用于存放 MediaRecorder 对象和音频Track,关闭录制和关闭媒体设备需要用到
+                recorder:'',
+                mediaStream:'',
+                // 用于存放录制后的音频文件对象和录制结束回调
+                recorderFile:'',
+                stopRecordCallback:'',
+                // 用于存放是否开启了视频录制
+                videoEnabled:false,
+
                 status: {
                     billCreating: true,
                     billEditing: false,
@@ -1201,6 +1271,107 @@
                             tempTip.show('没有找到对应的数据,可手动填写 ');
                         });
                 },
+                // 开启摄像头
+                enableCamera(enableVideo) {
+                    this.videoEnabled = enableVideo;
+                    MediaUtils.getUserMedia(enableVideo, false, function (err, stream) {
+                        if (err) {
+                            throw err;
+                        } else {
+                            let photo=document.getElementById('photo');
+                            photo.srcObject = stream;
+                            photo.play();
+                        }
+                    });
+                },
+                // 关闭摄像头
+                closeCamera() {
+                    let stream = document.getElementById('photo').srcObject;
+                    let tracks = stream.getTracks();
+                    tracks.forEach(function(track) {
+                        track.stop();
+                    });
+                    document.getElementById('photo').srcObject = null;
+                },
+                //拍照
+                takePhoto(id,index){
+                    this.enableCamera(true);
+                    setTimeout(()=>{
+                        let photo=document.getElementById('photo');
+                        let canvas=document.getElementById('canvas');
+                        //绘制canvas图形
+                        canvas.getContext('2d').drawImage(photo, 0, 0, 400, 300);
+                        let img = document.getElementById('canvas').toDataURL();
+                        // 这里的img就是得到的图片
+                        this.closeCamera();
+                        let blob=this.dataUrlToBlob(img);
+                        this.submitFile(blob,id,index);
+                        // photo.style.display='none';
+                    },2000)
+                },
+                dataUrlToBlob(imgDataUrl) {
+                    let imgUrl = window.atob(imgDataUrl.split(',')[1])
+                    let ab = new ArrayBuffer(imgUrl.length)
+                    let ia = new Uint8Array(ab)
+                    for (let i = 0; i < imgUrl.length; i++) {
+                        ia[i] = imgUrl.charCodeAt(i)
+                    }
+                    return new Blob([ab],{type:'image/jpeg'})
+                },
+                submitFile(blob,id,index){
+                    window.tempTip.setDuration(3000);
+                    let formData=new FormData();
+                    formData.append('files[]', blob,'rejected_bill_item'+id+'.jpg')
+                    formData.append("id",id);
+                    window.tempTip.postBasicRequest('{{url('apiLocal/rejectedBillItem/apiUpload')}}',formData,res=>{
+                        if (this.items[index].upload_files.length===0) this.$set(this.items[index],'upload_files',res);
+                        else this.$set(this.items[index],'upload_files',this.items[index].upload_files.concat(res));
+                        return "上传成功";
+                    },false,true);
+                },
+                removeCommonImg(id){
+                    $('#'+id).remove();
+                },
+                commonImg(id,uploadFiles){
+                    let div = "";
+                    let isBtn = '@can('运输管理-运单-图片删除') true @endcan ';
+                    for(let i=0;i<uploadFiles.length;i++){
+                        let btn = isBtn ? "<button type='button' class='btn btn-sm btn-danger' onclick='vueList.btnDeleteImg(this)' data-url='"+uploadFiles[i].url+"' value='"+id+"' style='position: relative;float: right;margin-top: -30px;' >删除</button>" : "";
+                        let href = this.imgPrefix+uploadFiles[i].url+'-bulky.'+uploadFiles[i].type;
+                        let src = this.imgPrefix+uploadFiles[i].url+'-common.'+uploadFiles[i].type;
+                        div += "<div><a target='_blank' href='"+href+"'><img alt='#' src='"+src+"' style='position: relative;' ></a>"+btn+"</div>"
+                    }
+                    $('#'+id).after(
+                        "<div id=\"common_"+id+"\" style='position: absolute;padding-bottom: 2px;z-index: 99'>" +
+                        "<div style='position:absolute;left: -50px' class='overflow-y-scrollbar-200'>"+div+
+                        "</div></div>");
+                },
+                btnDeleteImg(e){
+                    let idstr = $(e).val();
+                    let id =  idstr.substr( idstr.indexOf('_')+1);
+                    let url = e.getAttribute("data-url");
+                    if (!confirm('确定要删除所选图片吗?'))return;
+                    this.destroyImg(id,url);
+                },
+                destroyImg(id,url = null){
+                    window.tempTip.postBasicRequest('{{url('apiLocal/rejectedBillItem/apiDeleteImg')}}',{id:id,url:url},()=>{
+                        if (url){
+                            this.items.some((item,i)=>{
+                                if (item.id==id){
+                                    item.upload_files.some((file,j)=>{
+                                        if (file.url === url){this.$delete(this.items[i].upload_files,j);return true;}
+                                    });
+                                    return true;
+                                }
+                            });
+                        }else{
+                            this.items.forEach((item,i)=>{
+                                if (id.includes(item.id))this.$set(this.items[i],'upload_files',[]);
+                            });
+                        }
+                        return "删除成功";
+                    });
+                },
 
             },
             filters: {
@@ -1211,7 +1382,7 @@
                     if (!value) return '';
                     return value.substr(5, 11);
                 }
-            }
+            },
         });
     </script>
 @endsection

+ 230 - 27
resources/views/rejected/edit.blade.php

@@ -4,12 +4,12 @@
 @section('content')
 
     <div id="nav2">
-        @component('rejected.menu')
-            @can('退货管理-录入')
-                <li class="nav-item">
-                    <a class="nav-link" href="javascript:;" :class="{active:isActive('edit',3)}">修改</a>
-                </li> @endcan
-        @endcomponent
+{{--        @component('rejected.menu')--}}
+{{--            @can('退货管理-录入')--}}
+{{--                <li class="nav-item">--}}
+{{--                    <a class="nav-link" href="javascript:;" :class="{active:isActive('edit',3)}">修改</a>--}}
+{{--                </li> @endcan--}}
+{{--        @endcomponent--}}
     </div>
     <div class="container-fluid d-none" id="editPanel">
         <div class="card mb-2">
@@ -28,17 +28,17 @@
                         <div class="list-group" style="max-height: 415px;overflow-y: scroll">
                             <table class="table table-sm table-striped table-info table-hover table-bordered">
                                 <tr>
-                                    <th>创建时间</th>
-                                    <th>客户名称</th>
-                                    <th>退回单号</th>
-                                    <th>退回公司</th>
-                                    <th>姓名</th>
-                                    <th>电话</th>
-                                    <th>到付费用</th>
-                                    <th>订单号</th>
-                                    <th>原单号</th>
-                                    <th>是否入库</th>
-                                    <th>备注</th>
+                                    <th class="text-center">创建时间</th>
+                                    <th class="text-center">客户名称</th>
+                                    <th class="text-center">退回单号</th>
+                                    <th class="text-center">退回公司</th>
+                                    <th class="text-center">姓名</th>
+                                    <th class="text-center">电话</th>
+                                    <th class="text-center">到付费用</th>
+                                    <th class="text-center">订单号</th>
+                                    <th class="text-center">原单号</th>
+                                    <th class="text-center">是否入库</th>
+                                    <th class="text-center">备注</th>
                                 </tr>
                                 <tr :class="[rejectedBill.isEditing?'bg-info':'']"
                                     :data-id="rejectedBill.id" @click="editBill">
@@ -231,16 +231,17 @@
                         <div class="list-group">
                             <table class="table table-sm table-striped table-warning table-hover table-bordered">
                                 <tr>
-                                    <th>序号</th>
-                                    <th>商品条码</th>
-                                    <th>商品名称</th>
-                                    <th>数量</th>
-                                    <th>是否正品</th>
-                                    <th>生产日期</th>
-                                    <th>效期</th>
-                                    <th>批次号</th>
-                                    <th>备注</th>
-                                    @can('退货管理-删除')<th></th>@endcan
+                                    <th class="text-center">序号</th>
+                                    <th class="text-center">商品条码</th>
+                                    <th class="text-center">商品名称</th>
+                                    <th class="text-center">数量</th>
+                                    <th class="text-center">是否正品</th>
+                                    <th class="text-center">生产日期</th>
+                                    <th class="text-center">效期</th>
+                                    <th class="text-center">批次号</th>
+                                    <th class="text-center">备注</th>
+                                    <th class="text-center">照片</th>
+                                    @can('退货管理-删除')<th class="text-center">操作</th>@endcan
                                 </tr>
                                 <tr :class="[item.isEditing?'bg-warning':'']"
                                     v-for="(item,i) in items" :data-id="item.id" @click="editItem">
@@ -253,6 +254,14 @@
                                     <td>@{{item.validity_at}}</td>
                                     <td>@{{item.batch_number}}</td>
                                     <td>@{{item.remark}}</td>
+                                    <td>
+                                        <div align="center" @mouseleave="removeCommonImg('common_img_'+item.id)" @mouseenter="commonImg('img_'+item.id,item.upload_files)">
+                                            <button class="btn btn-outline-secondary" @click="takePhoto(item.id,i)">拍照上传</button>
+                                            <div :id="'img_'+item.id">
+                                                <img v-for="uploadFile in item.upload_files"  :src="imgPrefix+uploadFile.url+'-thumbnail.'+uploadFile.type">
+                                            </div>
+                                        </div>
+                                    </td>
                                     @can('退货管理-删除')<td><button class="btn btn-outline-danger" @click="deleteItem">删</button></td>@endcan
                                 </tr>
                             </table>
@@ -382,14 +391,107 @@
                 </div>
             </div>
         </div>
+        <div id="drag" class="col-4 p-0 " style="position:fixed;bottom: 30px;left: 10px;">
+            <video id="photo" width="50%" height="auto" controls></video>
+            <canvas id='canvas'width='400' height='300' hidden></canvas>
+        </div>
     </div>
 @endsection
 
 @section('lastScript')
     <script>
+        //鼠标拖动 drag
+        $(function() {
+            let posX,posY;
+            let drag = document.getElementById("drag");
+            drag.onmousedown = function(e) {
+                posX = event.x - drag.offsetLeft; //获得横坐标x
+                posY = event.y - drag.offsetTop; //获得纵坐标y
+                document.onmousemove = mousemove; //调用函数,只要一直按着按钮就能一直调用
+            }
+            document.onmouseup = function() {
+                document.onmousemove = null; //鼠标举起,停止
+            }
+            function mousemove(ev) {
+                if (ev == null) ev = window.event;
+                let left= (ev.clientX - posX);
+                let top= (ev.clientY - posY);
+                let pageWidth=document.body.scrollWidth;//网页正文宽度
+                let pageHeight=window.screen.availHeight;//屏幕工作区高度
+                //限定拖动区域
+                if (left <0)left=0;
+                if (left >pageWidth)left=pageWidth;
+                if (top <0)top=0;
+                if (top >pageHeight)top=pageHeight;
+                drag.style.left = left + "px";
+                drag.style.top = top + "px";
+            }
+        })
+        let MediaUtils = {
+            /**
+             * 获取用户媒体设备(处理兼容的问题)
+             * @param videoEnable {boolean} - 是否启用摄像头
+             * @param audioEnable {boolean} - 是否启用麦克风
+             * @param callback {Function} - 处理回调
+             */
+            getUserMedia: function (videoEnable, audioEnable, callback) {
+                navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia
+                    || navigator.msGetUserMedia || window.getUserMedia;
+                let constraints = {video: videoEnable, audio: audioEnable};
+                if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+                    navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
+                        callback(false, stream);
+                    })['catch'](function (err) {
+                        callback(err);
+                    });
+                } else if (navigator.getUserMedia) {
+                    navigator.getUserMedia(constraints, function (stream) {
+                        callback(false, stream);
+                    }, function (err) {
+                        callback(err);
+                    });
+                } else {
+                    callback(new Error('Not support userMedia'));
+                }
+            },
+            /**
+             * 关闭媒体流
+             * @param stream {MediaStream} - 需要关闭的流
+             */
+            closeStream: function (stream) {
+                if (typeof stream.stop === 'function') {
+                    stream.stop();
+                } else {
+                    let trackList = [stream.getAudioTracks(), stream.getVideoTracks()];
+
+                    for (let i = 0; i < trackList.length; i++) {
+                        let tracks = trackList[i];
+                        if (tracks && tracks.length > 0) {
+                            for (let j = 0; j < tracks.length; j++) {
+                                let track = tracks[j];
+                                if (typeof track.stop === 'function') {
+                                    track.stop();
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        };
+
         let vueList=new Vue({
             el: "#editPanel",
             data:{
+                imgPrefix:"{{asset("/storage")}}",
+                // 用于存放 MediaRecorder 对象和音频Track,关闭录制和关闭媒体设备需要用到
+                recorder:'',
+                mediaStream:'',
+                // 用于存放录制后的音频文件对象和录制结束回调
+                recorderFile:'',
+                stopRecordCallback:'',
+                // 用于存放是否开启了视频录制
+                videoEnabled:false,
+
                 status: {billCreating:true,billEditing:false,itemCreating:true,itemEditing:false
                     ,editingBill:null,editingBillId:'',editingItem:null,editingItemId:''
                     ,inputtingId_owner:'',endAndPackCommitEdited:false,lockingBillPanel:false,lockingIsLoadedInput:false,
@@ -922,6 +1024,107 @@
                         }
                     });
                 },
+                // 开启摄像头
+                enableCamera(enableVideo) {
+                    this.videoEnabled = enableVideo;
+                    MediaUtils.getUserMedia(enableVideo, false, function (err, stream) {
+                        if (err) {
+                            throw err;
+                        } else {
+                            let photo=document.getElementById('photo');
+                            photo.srcObject = stream;
+                            photo.play();
+                        }
+                    });
+                },
+                // 关闭摄像头
+                closeCamera() {
+                    let stream = document.getElementById('photo').srcObject;
+                    let tracks = stream.getTracks();
+                    tracks.forEach(function(track) {
+                        track.stop();
+                    });
+                    document.getElementById('photo').srcObject = null;
+                },
+                //拍照
+                takePhoto(id,index){
+                    this.enableCamera(true);
+                    setTimeout(()=>{
+                        let photo=document.getElementById('photo');
+                        let canvas=document.getElementById('canvas');
+                        //绘制canvas图形
+                        canvas.getContext('2d').drawImage(photo, 0, 0, 400, 300);
+                        let img = document.getElementById('canvas').toDataURL();
+                        // 这里的img就是得到的图片
+                        this.closeCamera();
+                        let blob=this.dataUrlToBlob(img);
+                        this.submitFile(blob,id,index);
+                        // photo.style.display='none';
+                    },2000)
+                },
+                dataUrlToBlob(imgDataUrl) {
+                    let imgUrl = window.atob(imgDataUrl.split(',')[1])
+                    let ab = new ArrayBuffer(imgUrl.length)
+                    let ia = new Uint8Array(ab)
+                    for (let i = 0; i < imgUrl.length; i++) {
+                        ia[i] = imgUrl.charCodeAt(i)
+                    }
+                    return new Blob([ab],{type:'image/jpeg'})
+                },
+                submitFile(blob,id,index){
+                    window.tempTip.setDuration(3000);
+                    let formData=new FormData();
+                    formData.append('files[]', blob,'rejected_bill_item'+id+'.jpg')
+                    formData.append("id",id);
+                    window.tempTip.postBasicRequest('{{url('apiLocal/rejectedBillItem/apiUpload')}}',formData,res=>{
+                        if (this.items[index].upload_files.length===0) this.$set(this.items[index],'upload_files',res);
+                        else this.$set(this.items[index],'upload_files',this.items[index].upload_files.concat(res));
+                        return "上传成功";
+                    },false,true);
+                },
+                removeCommonImg(id){
+                    $('#'+id).remove();
+                },
+                commonImg(id,uploadFiles){
+                    let div = "";
+                    let isBtn = '@can('运输管理-运单-图片删除') true @endcan ';
+                    for(let i=0;i<uploadFiles.length;i++){
+                        let btn = isBtn ? "<button type='button' class='btn btn-sm btn-danger' onclick='vueList.btnDeleteImg(this)' data-url='"+uploadFiles[i].url+"' value='"+id+"' style='position: relative;float: right;margin-top: -30px;' >删除</button>" : "";
+                        let href = this.imgPrefix+uploadFiles[i].url+'-bulky.'+uploadFiles[i].type;
+                        let src = this.imgPrefix+uploadFiles[i].url+'-common.'+uploadFiles[i].type;
+                        div += "<div><a target='_blank' href='"+href+"'><img alt='#' src='"+src+"' style='position: relative;' ></a>"+btn+"</div>"
+                    }
+                    $('#'+id).after(
+                        "<div id=\"common_"+id+"\" style='position: absolute;padding-bottom: 2px;z-index: 99'>" +
+                        "<div style='position:absolute;left: -50px' class='overflow-y-scrollbar-200'>"+div+
+                        "</div></div>");
+                },
+                btnDeleteImg(e){
+                    let idstr = $(e).val();
+                    let id =  idstr.substr( idstr.indexOf('_')+1);
+                    let url = e.getAttribute("data-url");
+                    if (!confirm('确定要删除所选图片吗?'))return;
+                    this.destroyImg(id,url);
+                },
+                destroyImg(id,url = null){
+                    window.tempTip.postBasicRequest('{{url('apiLocal/rejectedBillItem/apiDeleteImg')}}',{id:id,url:url},()=>{
+                        if (url){
+                            this.items.some((item,i)=>{
+                                if (item.id==id){
+                                    item.upload_files.some((file,j)=>{
+                                        if (file.url === url){this.$delete(this.items[i].upload_files,j);return true;}
+                                    });
+                                    return true;
+                                }
+                            });
+                        }else{
+                            this.items.forEach((item,i)=>{
+                                if (id.includes(item.id))this.$set(this.items[i],'upload_files',[]);
+                            });
+                        }
+                        return "删除成功";
+                    });
+                },
             },
             filters:{
                 isLoaded:function (value) {

+ 32 - 7
resources/views/rejected/search/general.blade.php

@@ -88,20 +88,27 @@
                         @can('退货管理-查询-客户定义-爱奇艺')<td class="text-muted"><span>@{{rejectedBill.common_01}}</span></td>@endcan
                         @cannot('退货管理-客户不可见')<td class="text-muted">@{{rejectedBill.common_02}}</td>@endcannot
                         <td class="text-muted"><span>@{{rejectedBill.goods_amount}}</span></td>
-                        <td style="min-width: 900px">
-                            <div class="w-100" :class="rejectedBill.items.length>1 ? 'up' : ''" :id="'rejected-'+rejectedBill.id">
-                                <div class="row" v-for="item in rejectedBill.items">
+                        <td style="min-width: 1000px">
+                            <div class="w-100 " :class="rejectedBill.items.length>1 ? 'up' : ''" :id="'rejected-'+rejectedBill.id" >
+                                <div class="row" v-for="(item,i) in rejectedBill.items">
                                     <div class="col-2 border border-1" style="overflow-x: hidden">
                                         <div class="w-100 text-overflow-warp-200 warp-min-200">@{{item.barcode_goods}}</div>
                                     </div>
-                                    <div class="col-2 border border-1" style="overflow-x: hidden">
+                                    <div class="col-1 border border-1" style="overflow-x: hidden">
                                         <div class="w-100 text-overflow-warp-200 warp-min-200">@{{item.name_goods}}</div>
                                     </div>
                                     <div class="col-1 border border-1">@{{item.amount}}</div>
                                     <div class="col-1 border border-1">@{{item.quality_label}}</div>
-                                    <div class="col-2 border border-1">@{{item.batch_number}}</div>
+                                    <div class="col-1 border border-1">@{{item.batch_number}}</div>
                                     <div class="col-1 border border-1">@{{item.made_at}}</div>
                                     <div class="col-1 border border-1">@{{item.validity_at}}</div>
+                                    <div class="col-2 border border-1">
+                                        <div align="center" @mouseleave="removeCommonImg('common_img_'+item.id)" @mouseenter="commonImg('img_'+item.id,item.upload_files)">
+                                            <div :id="'img_'+item.id">
+                                                <img v-for="uploadFile in item.upload_files"  :src="imgPrefix+uploadFile.url+'-thumbnail.'+uploadFile.type">
+                                            </div>
+                                        </div>
+                                    </div>
                                     <div class="col-2 border border-1">@{{item.remark}}</div>
                                 </div>
                             </div>
@@ -180,6 +187,7 @@
         let vue = new Vue({
             el:"#list",
             data:{
+                imgPrefix:"{{asset("/storage")}}",
                 rejectedBills:rejectedBills,
                 owners:[
                         @foreach($owners as $owner)
@@ -249,12 +257,13 @@
                     {name:'goods_amount',value: '商品总数'},
                     {name:"goods",type:"multi",title:"商品信息",rows:[
                             {value:"商品条码",col:"2"},
-                            {value:"商品名称",col:"2"},
+                            {value:"商品名称",col:"1"},
                             {value:"数量",col:"1"},
                             {value:"质量",col:"1"},
-                            {value:"批次号",col:"2"},
+                            {value:"批次号",col:"1"},
                             {value:"产期",col:"1"},
                             {value:"效期",col:"1"},
+                            {value:"照片",col:"2"},
                             {value:"备注",col:"2"},
                         ]},
                     {name:'remark',value: '退单备注'},
@@ -273,6 +282,22 @@
                 });
             },
             methods:{
+                removeCommonImg(id){
+                    $('#'+id).remove();
+                },
+                commonImg(id,uploadFiles){
+                    let div = "";
+                    let isBtn = '@can('运输管理-运单-图片删除') true @endcan ';
+                    for(let i=0;i<uploadFiles.length;i++){
+                        let href = this.imgPrefix+uploadFiles[i].url+'-bulky.'+uploadFiles[i].type;
+                        let src = this.imgPrefix+uploadFiles[i].url+'-common.'+uploadFiles[i].type;
+                        div += "<div><a target='_blank' href='"+href+"'><img alt='#' src='"+src+"' style='position: relative;' ></a>"+"</div>"
+                    }
+                    $('#'+id).after(
+                        "<div id=\"common_"+id+"\" style='position: relative;padding-bottom: 2px;z-index: 99'>" +
+                        "<div style='position:relative;left: -50px' class='overflow-y-scrollbar-400'>"+div+
+                        "</div></div>");
+                },
                 cancelCheck() {
                     let url = '{{url("apiLocal/rejected/cancelCheck")}}';
                     let data = {ids: checkData};

+ 1 - 1
resources/views/rejected/videoTest.blade.php

@@ -186,7 +186,7 @@
                     let url = URL.createObjectURL(recorderFile);
                     // 创建隐藏的可下载链接
                     let eleLink = document.createElement('a');
-                    eleLink.download = new Date().getFullYear()+'download.webm';
+                    eleLink.download = new Date().getFullYear()+'download.mp4';
                     // 字符内容转变成blob地址
                     eleLink.href = url
                     // 触发点击

+ 1 - 0
resources/views/transport/waybill/index.blade.php

@@ -1023,6 +1023,7 @@
                 },
                 submitFile(e,index){
                     let files=e.target.files;
+                    console.log(files)
                     window.tempTip.setDuration(3000);
                     if (files.length===0){window.tempTip.show("未选定图片!");return;}
                     let formData=new FormData();

+ 2 - 0
routes/apiLocal.php

@@ -11,6 +11,8 @@ Route::post('rejectedBillItem/reviseOwner', 'RejectedBillItemController@apiRevis
 Route::post('rejectedBillItem/update', 'RejectedBillItemController@apiUpdate');
 Route::post('rejectedBillItem/apiGet', 'RejectedBillItemController@apiGet');
 Route::post('rejectedBillItem/apiDelete', 'RejectedBillItemController@apiDelete');
+Route::post('rejectedBillItem/apiUpload', 'RejectedBillItemController@apiUpload');
+Route::post('rejectedBillItem/apiDeleteImg', 'RejectedBillItemController@apiDeleteImg');
 Route::post('rejectedBillItem/packConfirm', 'RejectedBillItemController@apiPackConfirm');
 Route::post('rejectedBillItem/packDestroy', 'RejectedBillItemController@apiPackDestroy');
 Route::post('rejectedBill/store', 'RejectedBillController@apiStore');