Преглед на файлове

客户关联-日志显示修改

Zhouzhendong преди 5 години
родител
ревизия
92b674b6a7

+ 12 - 1
app/Http/Controllers/CustomerBaseController.php

@@ -4,9 +4,11 @@ namespace App\Http\Controllers;
 
 use App\Components\SyncResponse;
 use App\Customer;
+use App\CustomerLog;
 use App\CustomerTag;
 use App\Owner;
 use App\Services\LogService;
+use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Http\Request;
 use Illuminate\Http\Response;
 use Illuminate\Support\Facades\Gate;
@@ -24,7 +26,10 @@ class CustomerBaseController extends Controller
     public function index(Request $request)
     {
         $this->gate("客户-查询");
-        $customers = app('CustomerService')->paginate($request->input(),["owners.contracts.files","tags:id,name"]);
+        $customers = app('CustomerService')->paginate($request->input(),["owners.contracts.files","tags:id,name","customerLogs"=>function($query){
+            /** @var Builder $query */
+            $query->with(["status:id,name,created_at","user:id,name"])->orderByDesc('id');
+        }]);
         $owners = app("OwnerService")->getIntersectPermitting(['id','name','customer_id']);
         $tags = CustomerTag::query()->get(["id","name"]);
         return response()->view('customer.customer.index',compact("customers","owners","tags"));
@@ -158,4 +163,10 @@ class CustomerBaseController extends Controller
         $customer->load("tags");
         return ["success"=>true,"data"=>$customer->tags];
     }
+
+    public function destroyLog(Request $request)
+    {
+        CustomerLog::destroy($request->input("id"));
+        return ["success"=>true];
+    }
 }

+ 15 - 7
app/Http/Controllers/PriceModelController.php

@@ -2,16 +2,17 @@
 
 namespace App\Http\Controllers;
 
+use App\Components\AsyncResponse;
 use App\Imports\ExpressImport;
 use App\Imports\OwnerPriceDirectLogisticDetailImport;
 use App\Imports\OwnerPriceLogisticDetailImport;
 use App\Owner;
-use App\OwnerOutStorageRule;
 use App\OwnerPriceDirectLogistic;
 use App\OwnerPriceExpress;
 use App\OwnerPriceExpressProvince;
 use App\OwnerPriceLogistic;
 use App\OwnerPriceOperation;
+use App\OwnerPriceOperationItemOut;
 use App\Services\common\ExportService;
 use App\Services\LogService;
 use App\Services\OwnerOutStorageRuleService;
@@ -26,6 +27,7 @@ use Maatwebsite\Excel\Facades\Excel;
 
 class PriceModelController extends Controller
 {
+    use AsyncResponse;
     public function storageIndex(Request $request)
     {
         if(!Gate::allows('计费模型-仓储')){ return redirect('denied');  }
@@ -187,7 +189,7 @@ class PriceModelController extends Controller
         $row = app('OwnerOutStorageRuleService')->update(["id"=>$id],["feature"=>$feature]);
         if ($row != 1)return ["success"=>false,"data"=>"影响了“".$row."”行"];
         LogService::log(__METHOD__,"计费模型-修改出库特征",json_encode($request->input()));
-        OwnerOutStorageRule::$features = $result["map"];
+        OwnerPriceOperationItemOut::$features = $result["map"];
         $rule = app('OwnerOutStorageRuleService')->find($id)->append("featureFormat");
         return ["success"=>true,"data"=>["featureFormat"=>$rule->featureFormat,"feature"=>$feature]];
     }
@@ -208,7 +210,8 @@ class PriceModelController extends Controller
             }
         }
         if (count($stack) > 0)return ["success"=>false,"data"=>"组标记错误,起始与结束标记必须对应"];
-        return ["success"=>true,"data"=>$feature];
+        if ($request->has("isFormat"))$this->success(["feature"=>$feature,"featureFormat"=>app("FeatureService")->formatFeature($result["map"], $feature)]);
+        $this->success($feature);
     }
 
     public function operationDestroy($id)
@@ -434,11 +437,16 @@ class PriceModelController extends Controller
         ini_set('max_execution_time',2500);
         ini_set('memory_limit','1526M');
         $fileSuffix = ucwords($fileSuffix);
-        /** @var OwnerPriceExpress $model */
-        $model = app('OwnerPriceExpressService')->find($request->input("id"),["details"]);
-        Excel::import(new ExpressImport($model),$request->file('file')->path(),null,$fileSuffix);
-        if (Cache::has('express'))return Cache::pull('express');
 
+        if (!$request->has("id")){
+            Excel::import(new ExpressImport(null),$request->file('file')->path(),null,$fileSuffix);
+        }else{
+            /** @var OwnerPriceExpress $model */
+            $model = app('OwnerPriceExpressService')->find($request->input("id"),["details"]);
+            Excel::import(new ExpressImport($model),$request->file('file')->path(),null,$fileSuffix);
+        }
+
+        if (Cache::has('express'))return Cache::pull('express');
         return ["success"=>false,"data"=>"导入发生错误,数据无响应"];
     }
 

+ 36 - 4
app/Imports/ExpressImport.php

@@ -28,10 +28,6 @@ class ExpressImport implements ToCollection,WithHeadingRow
     */
     public function collection(Collection $collection)
     {
-        if (!$this->express){
-            Cache::put("express",["success"=>false, "data"=>"不存在父级"],86400);
-            return false;
-        }
         $row = $collection->first();
         $header = [
             "省","首重价格","续重价格"
@@ -50,6 +46,10 @@ class ExpressImport implements ToCollection,WithHeadingRow
             $map[$province->name] = $province->id;
         }
 
+        if (!$this->express){
+            return $this->readonly($collection,$map);
+        }
+
         //已存在的计费
         $existDetails = [];
         foreach ($this->express->details as $detail){
@@ -105,4 +105,36 @@ class ExpressImport implements ToCollection,WithHeadingRow
         Cache::put("express",["success"=>true,"data"=>$this->express->details,"errors"=>$errors],86400);
         return true;
     }
+
+    private function readonly(Collection $collection, array $map)
+    {
+        $errors = [];
+        $data = [];
+        foreach ($collection as $index => $item){
+            if (!isset($map[$item["省"]])){
+                foreach ($map as $name=>$id){
+                    if (mb_strpos($item["省"],$name) !== false)$item["省"] = $name;
+                }
+                if (!isset($map[$item["省"]])){
+                    $errors[] = "第“".($index+2)."”行未知省份";
+                    continue;
+                }
+            }
+            if (!is_numeric($item["首重价格"]) || $item["首重价格"] <= 0){
+                $errors[] = "第“".($index+2)."”行非法首重价格";
+                continue;
+            }
+            if (!is_numeric($item["续重价格"]) || $item["续重价格"] <= 0){
+                $errors[] = "第“".($index+2)."”行非法续重价格";
+                continue;
+            }
+            $data[] = [
+                "province_id" => $map[$item["省"]],
+                "initial_weight_price" => $item["首重价格"],
+                "additional_weight_price" => $item["续重价格"],
+            ];
+        }
+        Cache::put("express",["success"=>true,"data"=>$data,"errors"=>$errors],86400);
+        return true;
+    }
 }

+ 4 - 0
app/Providers/AppServiceProvider.php

@@ -10,6 +10,8 @@ use App\Services\CommodityService;
 use App\Services\common\BatchUpdateService;
 use App\Services\CommodityBarcodeService;
 use App\Services\common\DataHandlerService;
+use App\Services\CustomerLogService;
+use App\Services\CustomerLogStatusService;
 use App\Services\CustomerService;
 use App\Services\DepositoryService;
 use App\Services\FeatureService;
@@ -192,6 +194,8 @@ class AppServiceProvider extends ServiceProvider
         app()->singleton('WarehouseService',WarehouseService::class);
         app()->singleton('WaybillFinancialService',WaybillFinancialService::class);
         app()->singleton('WeighExceptedService',WeighExceptedService::class);
+        app()->singleton('CustomerLogService',CustomerLogService::class);
+        app()->singleton('CustomerLogStatusService',CustomerLogStatusService::class);
     }
 
 

+ 27 - 1
resources/sass/text.scss

@@ -65,7 +65,33 @@
     overflow: hidden;
     white-space: nowrap;
     text-overflow: ellipsis;
-    pointer-events: none
+    pointer-events: none;
+}
+.text-overflow-replace-100{
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    pointer-events: none;
+    max-width: 100px;
+}
+.text-overflow-replace-200{
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    pointer-events: none;
+    max-width: 200px;
+}
+//文本溢出换行展示
+.text-overflow-warp{
+    white-space: normal;
+}
+.text-overflow-warp-100{
+    max-width: 100px;
+    white-space: normal;
+}
+.text-overflow-warp-200{
+    max-width: 200px;
+    white-space: normal;
 }
 //输入框font标签图
 .form-font{

+ 13 - 32
resources/views/customer/customer/_customerLog.blade.php

@@ -1,43 +1,24 @@
 <div class="modal fade" tabindex="-1" role="dialog" id="modal">
-    <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
+    <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
         <div class="modal-content">
             <div class="modal-header">
-                <button type="button" class="btn btn-outline-info pull-left" @click="addLog()">新增</button>
+                <h4 class="modal-title text-primary">新增日志信息</h4>
                 <button type="button" class="close" data-dismiss="modal">&times;</button>
             </div>
             <div class="modal-body">
-                <table class="table table-sm table-hover table-striped" v-if="index !== ''">
-                    <tr>
-                        <th>状态</th>
-                        <th>操作者</th>
-                        <th>说明</th>
-                        <th>操作时间</th>
-                    </tr>
-                    <tr v-for="(log,i) in customerLogs[customers[index]['id']]">
-                        <td>
-                            <select id="logStatusName" v-if="!log.id" class="form-control form-control-sm" :id="'logStatus-'+log.id">
-                                <option v-for="logStatus in logStatuses" :value="logStatus.id">@{{ logStatus.name }}</option>
-                            </select>
-                            <label v-else for="logStatusName">
-                                @{{ log.status.name }}
-                            </label>
-                        </td>
-                        <td>@{{ log.user.name }}</td>
-                        <td style="max-width: 100px" class="cursor-pointer" @mouseenter="textClass($event,true)" @mouseleave="textClass($event,false)">
-                            <div class="text-overflow-replace" v-if="currentLogIndex !== i" @click="edit(i,log.user)">
-                                @{{ log.description }}
-                            </div>
-                            <div v-if="currentLogIndex === i && i===0">
-                                <textarea class="form-control form-control-sm" v-text="log.description" :id="'description-'+log.id"></textarea>
-                            </div>
-                        </td>
-                        <td>@{{ log.created_at }}</td>
-                    </tr>
-                </table>
+                <div class="row">
+                    <label for="status" class="col-3">客户状态</label>
+                    <select id="status" class="form-control form-control-sm col-5">
+                        <option v-for="status in logStatuses" :value="status.id">@{{ status.name }}</option>
+                    </select>
+                </div>
+                <div class="row mt-2">
+                    <label for="description" class="col-3">说明</label>
+                    <textarea id="description" class="form-control form-control-sm col-7"></textarea>
+                </div>
             </div>
             <div class="modal-footer">
-                <button type="button" class="btn btn-success" v-if="currentLogIndex !== ''" @click="editLog()">保存</button>
-                <button type="button" class="btn btn-danger" v-if="currentLogIndex !== ''" @click="closeEditLog()">取消</button>
+                <button type="button" class="btn btn-success" @click="addLog()">提交</button>
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
             </div>
         </div>

+ 103 - 76
resources/views/customer/customer/index.blade.php

@@ -25,7 +25,18 @@
                     <th>联系人</th>
                     <th>联系电话</th>
                     <th>公司备注</th>
-                    <th>日志</th>
+                    <th class="text-center" style="min-width: 700px">
+                        <div class="row">
+                            <label class="col-12">日志</label>
+                        </div>
+                        <div class="row text-secondary">
+                            <label class="col-2">状态</label>
+                            <label class="col-2">操作者</label>
+                            <label class="col-4">说明</label>
+                            <label class="col-3">操作时间</label>
+                            <label class="col-1"></label>
+                        </div>
+                    </th>
                     <th>合同</th>
                     <th>创建时间</th>
                     <th>标签</th>
@@ -40,7 +51,33 @@
                     <td>@{{ customer.contact_man }}</td>
                     <td>@{{ customer.phone }}</td>
                     <td>@{{ customer.remark }}</td>
-                    <td><span class="cursor-pointer" @click="showCustomerLog(customer.id,i)">查看</span></td>
+                    <td>
+                        <div class="row text-center mb-0" v-if="customer.customer_logs.length > 0">
+                            <div class="col-2">@{{ customer.customer_logs[0].status ? customer.customer_logs[0].status.name : '' }}</div>
+                            <div class="col-2">@{{ customer.customer_logs[0].user ? customer.customer_logs[0].user.name : '' }}</div>
+                            <label v-if="editCustomer == customer.id">
+                                <textarea @blur="editLog(i,$event)" class="form-control form-control-sm">@{{ customer.customer_logs[0].description }}</textarea>
+                            </label>
+                            <div v-else @click="edit(i,customer.customer_logs[0].user)" @mouseenter="textClass($event,true)" @mouseleave="textClass($event,false)" class="col-4">
+                                <div class="text-overflow-replace cursor-pointer">@{{ customer.customer_logs[0].description }}</div>
+                            </div>
+                            <div class="col-3">@{{ customer.customer_logs[0].created_at }}</div>
+                            @can("客户-编辑")<div class="col-1 font-weight-bold text-danger h4 cursor-pointer" @click="delLog(i,0)">&times;</div>@endcan
+                        </div>
+                        <div class="row text-center mb-0 up" v-for="(log,j) in customer.customer_logs" v-if="j!==0" :id="'log-'+customer.id">
+                            <div class="col-2">@{{ log.status ? log.status.name : '' }}</div>
+                            <div class="col-2">@{{ log.user ? log.user.name : '' }}</div>
+                            <div @mouseenter="textClass($event,true)" @mouseleave="textClass($event,false)" class="col-4"><div class="text-overflow-replace cursor-pointer">@{{ log.description }}</div></div>
+                            <div class="col-3">@{{ log.created_at }}</div>
+                            @can("客户-编辑")<div class="col-1 font-weight-bold text-danger h4 cursor-pointer" @click="delLog(i,j)">&times;</div>@endcan
+                        </div>
+                        <div class="row" @click="showLog(i)">
+                            <label class="text-center mt-0 p-0 cursor-pointer offset-5">
+                                <span class="fa" :class="customer.isShowLog ? 'fa-angle-double-down' : 'fa-angle-double-right'"></span>
+                                &nbsp;<span v-if="customer.isShowLog">收起</span><span v-else>展开</span>&nbsp;@{{ customer.customer_logs.length }} 条日志
+                            </label>
+                        </div>
+                    </td>
                     <td v-if="customer.contractQuantity>0" class="cursor-pointer" @click="showContract(customer.id)"><span class="fa" :class="contractIds['_'+customer.id] === true ? 'fa-angle-double-down' : 'fa-angle-double-right'"></span>@{{ customer.contractQuantity }}份合同</td>
                     <td v-else>#</td>
                     <td>@{{ customer.created_at }}</td>
@@ -60,6 +97,7 @@
                     </td>
                     <td>
                         @can("客户-编辑")
+                            <button class="btn btn-sm btn-outline-info" @click="showCustomerLog(i)">新建日志</button>
                             <button class="btn btn-sm btn-outline-success" @click="addContract(i)">新建合同</button>
                             <button v-if="!tagIndexes[i]" class="btn btn-sm btn-info text-white" @click="setTag(i)">设定标签</button>
                             <button class="btn btn-sm btn-outline-primary" @click="relatedOwner(i)">关联项目</button>
@@ -82,8 +120,7 @@
                 customers :  {!!  $customers->toJson() !!}['data'],
                 customerLogs : [],
                 index:'',
-                currentLogIndex:"",
-                logStatuses:[],
+                logStatuses:null,
                 contractIds:[],
                 customerOwners:[],
                 files:[],
@@ -100,9 +137,12 @@
                     {name:"{{$tag->id}}",value:"{{$tag->name}}"},
                     @endforeach
                 ],
+                editCustomer:"",
+                addCustomerLogIndex:"",
             },
             mounted(){
                 this._formatOwner();
+                $(".up").slideUp();
                 $('#container').removeClass('d-none');
                 this.rendering();
                 let data=[
@@ -120,6 +160,46 @@
                 }).init();
             },
             methods:{
+                textClass(event,isOver){
+                    event = event.target.children[0];
+                    if (isOver) event.className = "text-overflow-warp";
+                    else event.className = "cursor-pointer text-overflow-replace";
+                },
+                editLog(customer_index,event){
+                    let description = event.target.value;
+                    if (description ===this.customers[customer_index].customer_logs[0].description){
+                        this.editCustomer = "";
+                        return;
+                    }
+                    let url="{{url('customer/customer/editLog')}}";
+                    let params = {id:this.customers[customer_index].customer_logs[0].id,description:description};
+                    window.tempTip.confirm("确定要提交该条日志的修改信息吗?",()=>{
+                        window.tempTip.postBasicRequest(url,params,(res)=> {
+                            this.customers[customer_index].customer_logs[0].description = description;
+                            this.$forceUpdate();
+                            return "修改说明成功!";
+                        });
+                    });
+                },
+                showLog(index){
+                    if(this.customers[index].isShowLog){
+                        this.customers[index].isShowLog = false;
+                        $("#log-"+this.customers[index].id).slideUp();
+                    } else{
+                        this.customers[index].isShowLog = true;
+                        $("#log-"+this.customers[index].id).slideDown();
+                    }
+                    this.$forceUpdate();
+                },
+                delLog(customer_index,log_index){
+                    let id = this.customers[customer_index].customer_logs[log_index].id;
+                    window.tempTip.confirm("确定要删除该条日志吗?",()=>{
+                        window.tempTip.postBasicRequest("{{url('customer/customer/destroyLog')}}",{id:id},res=>{
+                            this.$delete(this.customers[customer_index].customer_logs,log_index);
+                            return "已成功删除该条日志记录";
+                        });
+                    })
+                },
                 addTag(index){
                     let val = $("#sel-"+index).selectpicker('val');
                     let url = "{{url('customer/customer/addTag')}}";
@@ -144,14 +224,7 @@
                         });
                     }
                     this.$forceUpdate();
-                    /*if (this.tags.length<1){
-                        window.tempTip.postBasicRequest("{{--{{url('customer/customer/customerTag/get')}}--}}",{},(res)=>{
-                            this.tags = res;
-                        });
-                    }*/
-                    //setTimeout(()=>{
-                        $(".selectpicker").selectpicker('refresh').selectpicker('val',defaults);
-                    //},500);
+                    $(".selectpicker").selectpicker('refresh').selectpicker('val',defaults);
                 },
                 _formatOwner(){
                     let ownerIds=[];
@@ -227,77 +300,31 @@
                         })
                     })
                 },
-                showCustomerLog(id,index){
-                    this.index = index;
-                    if (this.customerLogs[id]){
-                        $("#modal").modal('show');
-                        return;
-                    }
-                    let url="{{url('customer/customer/getLog')}}";
-                    let params = {customer_id:id};
-                    window.tempTip.postBasicRequest(url,params,(res)=> {
-                        this.customerLogs[id] = res;
-                        this.$forceUpdate();
-                        $("#modal").modal('show');
-                    });
-                },
-                textClass(event,isOver){
-                    event = event.target.children[0];
-                    if (isOver){event.classList.remove("text-overflow-replace");}
-                    else {event.classList.add("text-overflow-replace");}
+                showCustomerLog(index){
+                    if (!this.logStatuses)window.tempTip.postBasicRequest("{{url('customer/customer/getLogStatus')}}",{},(res)=>{
+                        this.logStatuses = res;
+                    },true);
+                    this.addCustomerLogIndex = index;
+                    $("#modal").modal('show');
                 },
                 edit(index,user){
+                    if (!user)return;
                     let id = "{{\Illuminate\Support\Facades\Auth::id()}}";
                     if (id != user.id) return;
-                    if (index === 0)this.currentLogIndex=index;
-                    else this.currentLogIndex='';
-                },
-                closeEditLog(){
-                    this.currentLogIndex = '';
-                    if (this.customerLogs[this.customers[this.index]['id']][0].id === ''){
-                        this.$delete(this.customerLogs[this.customers[this.index]['id']],0);
-                    }
-                },
-                editLog(){
-                    let log = this.customerLogs[this.customers[this.index]['id']][this.currentLogIndex];
-                    if (log.id === ''){
-                        let description = $("#description-"+log.id)[0].value;
-                        let logStatus = $("#logStatus-"+log.id)[0].value;
-                        let url="{{url('customer/customer/storeLog')}}";
-                        let params = {customer_id:this.customers[this.index]['id'],description:description,customer_log_status_id:logStatus};
-                        window.tempTip.postBasicRequest(url,params,(res)=>{
-                            this.customerLogs[this.customers[this.index]['id']][0] = res;
-                            this.currentLogIndex = '';
-                            this.$forceUpdate();
-                            return "成功创建客户日志";
-                        },true);
-                        return;
-                    }
-                    let url="{{url('customer/customer/editLog')}}";
-                    let description = $("#description-"+log.id)[0].value;
-                    let params = {id:log.id,description:description};
-                    window.tempTip.postBasicRequest(url,params,(res)=> {
-                        this.customerLogs[this.customers[this.index]['id']][this.currentLogIndex].description = description;
-                        this.currentLogIndex = '';
-                        this.$forceUpdate();
-                        return "修改说明成功!";
-                    },true);
+                    this.editCustomer = this.customers[index].id;
                 },
                 addLog(){
-                    if (this.logStatuses.length < 1)window.tempTip.postBasicRequest("{{url('customer/customer/getLogStatus')}}",{},(res)=>{
-                        this.logStatuses = res;
+                    let description = $("#description")[0].value;
+                    let logStatus = $("#status")[0].value;
+                    let url="{{url('customer/customer/storeLog')}}";
+                    let params = {customer_id:this.customers[this.addCustomerLogIndex].id,description:description,customer_log_status_id:logStatus};
+                    window.tempTip.postBasicRequest(url,params,(res)=>{
+                        this.customers[this.addCustomerLogIndex].customer_logs.unshift(res);
+                        console.log(this.customers[this.addCustomerLogIndex].customer_logs);
+                        this.$forceUpdate();
+                        $("#modal").modal('hide');
+                        return "成功创建客户日志";
                     },true);
-                    let logs = this.customerLogs[this.customers[this.index]['id']];
-                    if (logs.length > 0 && logs[0].id === "")return;
-                    logs.unshift({
-                        id:"",
-                        status:{name:""},
-                        user:{name:"{{\Illuminate\Support\Facades\Auth::user()->name}}"},
-                        description:"",
-                        created_at:"",
-                    });
-                    this.currentLogIndex = 0;
-                    this.$forceUpdate();
                 },
                 showContract(id){
                     if (this.contractIds['_'+id] &&  this.contractIds['_'+id]===true){

+ 0 - 55
resources/views/customer/project/_three.blade.php

@@ -1,55 +0,0 @@
-<div class="row">
-    <div class="col-6">
-        <div class="card">
-            <div class="card-header bg-light-info">
-                <span class="pull-left font-weight-bold cursor-pointer" @click="show('storage')"><span class="fa fa-cubes"></span>&nbsp;仓储</span>
-            </div>
-            <div class="card-body" id="storage">
-                <table class="table table-sm">
-                    <tr>
-                        <th>计费类型</th>
-                        <th>用仓类型</th>
-                        <th>起租面积</th>
-                        <th>单价</th>
-                        <th>单位</th>
-                        <th>减免类型</th>
-                        <th>减免值</th>
-                    </tr>
-                    <tr v-for="item in selectedModel.storage">
-                        <td>@{{ item.counting_type }}</td>
-                        <td>@{{ item.using_type }}</td>
-                        <td>@{{ item.minimum_area }}</td>
-                        <td>@{{ item.price }}</td>
-                        <td>@{{ item.discount_type }}</td>
-                        <td>@{{ item.discount_value }}</td>
-                        <td>@{{ poolMapping.units[item.unit_id] }}</td>
-                    </tr>
-                </table>
-            </div>
-        </div>
-    </div>
-    <div class="col-6">
-        <div class="card">
-            <div class="card-header bg-light-info">
-                <button type="button" class="btn mr-1" :class="type == 'storage' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('storage')">仓储</button>
-                <button type="button" class="btn mr-1" :class="type == 'operation' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('operation')">作业</button>
-                <button type="button" class="btn mr-1" :class="type == 'express' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('express')">快递</button>
-                <button type="button" class="btn mr-1" :class="type == 'logistic' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('logistic')">物流</button>
-                <button type="button" class="btn mr-1" :class="type == 'directLogistic' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('directLogistic')">直发</button>
-            </div>
-            <div class="card-body">
-                <div v-if="type == 'storage'">
-                    @include("customer.project._storage")
-                </div>
-                <div v-show="type == 'operation'">
-                    @include("customer.project._operation")
-                    @include("customer.project._addFeature")
-                </div>
-                <div class="row mt-3" v-if="base=='three'">
-                    <div class="col-3"></div>
-                    <button type="button" class="btn btn-success ml-1 col-6" @click="saveModel()">保存</button>
-                </div>
-            </div>
-        </div>
-    </div>
-</div>

+ 298 - 42
resources/views/customer/project/create.blade.php

@@ -56,13 +56,13 @@
             <form method="POST" action="{{url('customer/project/store')}}" class="mt-5">
                 @csrf
                 <div v-if="base == 'one'">
-                    @include("customer.project._one")
+                    @include("customer.project.part._one")
                 </div>
                 <div v-if="base == 'two'">
-                    @include("customer.project._two")
+                    @include("customer.project.part._two")
                 </div>
                 <div v-if="base == 'three'">
-                    @include("customer.project._three")
+                    @include("customer.project.part._three")
                 </div>
                 <div class="row mt-3">
                     <div class="pull-right offset-7">
@@ -82,7 +82,7 @@
         new Vue({
             el:"#container",
             data:{
-                base:"one",
+                base:"three",
                 owner : {
                     id:"{{old('id') ?? ($owner->id ?? '') }}",
                     name : "{{old('name') ?? ($owner->name ?? '')}}",
@@ -161,6 +161,12 @@
                             {strategy:"特征"},
                         ],
                     },
+                    express:{
+                        name:"",
+                        initial_weight:"",
+                        additional_weight:"",
+                        items:[],
+                    },
                 },
                 pool:{//基础数据选择池,以方便异步懒加载而非即时加载 例:units,owners等
                     counting_type:[
@@ -172,17 +178,24 @@
                     discount_type:[
                         "无减免","按单减免","固定减免"
                     ],
+                    feature_type:['商品名称','订单类型','承运商','店铺类型'],
+                    logic : ['包含','不包含','等于'],
                 },
                 poolMapping:{//基础数据选择池的映射对象 供展示使用
 
                 },
                 selectedModel:{//已选定的计费模型
                     storage:[],
+                    operation:[],
+                    express:{},
                 },
                 thisOperationItemIndex:-1,//当前选中的作业费子项下标,用以唤起特征模态框
-                oldFeature:"",//上一个特征,如果与当前特征一致则无需再次转换特征
+                operationItems:{},//控制作业费子项的渐入展开
+                importError:[],//导入时的错误数据原因
+                isShowError:false,//是否展开导入错误信息
             },
             mounted(){
+                $('[data-toggle="tooltip"]').tooltip();
                 if (this.errors.length===0 && this.owner.id){
                     this.display = true;
                 }
@@ -210,6 +223,9 @@
                         case "operation":
                             this._loadOperation();
                             break;
+                        case "express":
+                            this._loadExpress();
+                            break;
                     }
                     this.type = type;
                 },
@@ -251,12 +267,6 @@
                         this.errors["owner_group_id"] = ["必须选择项目小组"];
                         this.$forceUpdate();
                     }
-/*                    this._requestRequest({
-                        customer_id:this.owner.customer_id,
-                        owner_group_id:this.owner.owner_group_id,
-                        tax_rate:this.owner.tax_rate,
-                        waring_line_on:this.owner.waring_line_on,
-                    },"three")*/
                 },
                 //上一步
                 back(){
@@ -290,6 +300,10 @@
                 _loadOperation(){
                     if (!this.pool.units)this._getUnits();
                 },
+                //加载快递
+                _loadExpress(){
+                    if (!this.pool.provinces)this._getProvinces();
+                },
                 //获取单位
                 _getUnits(){
                     let url = "{{url('maintenance/unit/getUnits')}}";
@@ -303,12 +317,31 @@
                         this.$forceUpdate();
                     });
                 },
+                //获取省份
+                _getProvinces(){
+                    let url = "{{url('maintenance/province/get')}}";
+                    window.tempTip.postBasicRequest(url,{},res=>{
+                        this.pool.provinces = res;
+                        let mapping = [];
+                        res.forEach(province=>{
+                            mapping[province.id] = province.name;
+                        });
+                        this.poolMapping.provinces = mapping;
+                        this.$forceUpdate();
+                    });
+                },
                 //保存模型
                 saveModel(){
                     switch (this.type) {
                         case "storage":
                             this._verifyStorage();
                             break;
+                        case "operation":
+                            this._verifyOperation();
+                            break;
+                        case "express":
+                            this._verifyExpress();
+                            break;
                     }
                 },
                 _verifyStorage(){
@@ -344,6 +377,89 @@
                     };
                     this.errors = [];
                 },
+                _verifyOperation() {
+                    if (this.selectedModel.operation.length>0){
+                        this.selectedModel.operation.forEach(operation=>{
+                            if (operation.operation_type === this.model.operation.operation_type && operation.strategy === this.model.operation.strategy){
+                                this.errors["operation_type"] = ["已存在同类型的"+operation.operation_type+"作业计费模型"];
+                                this.$forceUpdate();
+                                return;
+                            }
+                        });
+                    }
+                    if (!this.model.operation.name){
+                        this.errors["name"] = ["名称不得为空"];
+                        this.$forceUpdate();
+                        return;
+                    }
+                    if ((this.model.operation.operation_type === '出库' && this._verifyOperationItem(0)) || this._verifyOperationItem(1))return;
+                    if (this.model.operation.items.length>2){
+                        for (let i=2;i<this.model.operation.items.length;i++){
+                            if (this._verifyOperationItem(i))return;
+                        }
+                    }
+                    if (this.model.operation.operation_type === '入库')this.$delete(this.model.operation.items,0);//入库时干掉起步子项
+                    this.selectedModel.operation.push(this.model.operation);
+                    setTimeout(()=>{
+                        this._renderingOperationItem(this.selectedModel.operation.length-1);
+                    },100);
+                    this.model.operation = {
+                        operation_type:"入库",
+                        strategy:"默认",
+                        name:"",
+                        feature:"",
+                        items : [
+                            {strategy:"起步"},
+                            {strategy:"默认"},
+                            {strategy:"特征"},
+                        ],
+                    };
+                    this.errors = [];
+                },
+                _verifyOperationItem(itemIndex){//验证作业费子项信息完整
+                    let obj = this.model.operation.items[itemIndex];
+                    let sign = false;
+                    if (!obj.amount){
+                        this.errors['items.'+itemIndex+'.amount'] = ["数量不得为空"];
+                        sign = true;
+                    }
+                    if (!obj.unit_id){
+                        this.errors['items.'+itemIndex+'.unit_id'] = ["必须选择单位"];
+                        sign = true;
+                    }
+                    if (!obj.unit_price){
+                        this.errors['items.'+itemIndex+'.unit_price'] = ["单价不得为空"];
+                    }
+                    if (sign)this.$forceUpdate();
+                    return sign;
+                },
+                _verifyExpress(){
+                    let error = {};
+                    if (!this.model.express.name)error.name = ["名称不得为空"];
+                    if (!this.model.express.initial_weight)error.initial_weight = ["首重不得为空"];
+                    if (!this.model.express.additional_weight)error.additional_weight = ["续重不得为空"];
+                    if (this.model.express.items.length>0){
+                        this.model.express.items.forEach((item,index)=>{
+                            if (!item.province_id)error["item."+index+".province_id"] = ["不存在"];
+                            if (!item.initial_weight_price)error["item."+index+".initial_weight_price"] = ["不存在"];
+                            if (!item.additional_weight_price)error["item."+index+".additional_weight_price"] = ["不存在"];
+                        });
+                    }
+                    if (JSON.stringify(error) !== "{}"){
+                        this.errors = error;
+                        this.$forceUpdate();
+                        return;
+                    }
+                    this.selectedModel.express = this.model.express;
+                    this.model.express = {
+                        name:"",
+                        initial_weight:"",
+                        additional_weight:"",
+                        items:[],
+                    };
+                    this.errors = [];
+                    this.importError = [];
+                },
                 //增加作业费特征子项
                 addOperationItem(){
                     this.model.operation.items.push({
@@ -355,45 +471,185 @@
                     this.$delete(this.model.operation.items,index);
                 },
                 //显示特征选择modal
-                showAddFeatureModal(index,feature){
-                    if (!feature){
-                        this.model.operation.items[index].features = [{
-                            "strategyGroupStartSign": false,
-                            "calculation" : "",
-                            "type" : "",
-                            "id" : "",
-                            "logic" : "",
-                            "describe" : "",
-                            "strategyGroupEndSign" : false,
-                        }];
-                        this.thisOperationItemIndex = index;
-                        $("#addFeatureModal").modal("show");
-                        return;
-                    }
-                    if (this.oldFeature === feature) {
-                        this.thisOperationItemIndex = index;
-                        $("#addFeatureModal").modal("show");
-                        return;
+                showAddFeatureModal(index){
+                    if (index === -1){
+                        if (!this.model.operation.feature){
+                            this.model.operation.features = this._createFeature();
+                            this.thisOperationItemIndex = index;
+                            this.$forceUpdate();
+                            $("#addFeatureModal").modal("show");
+                            return;
+                        }
+                        if (this.model.operation.features) {
+                            this.thisOperationItemIndex = index;
+                            $("#addFeatureModal").modal("show");
+                            return;
+                        }
+                    }else{
+                        if (!this.model.operation.items[index].feature){
+                            this.model.operation.items[index].features = this._createFeature();
+                            this.thisOperationItemIndex = index;
+                            $("#addFeatureModal").modal("show");
+                            return;
+                        }
+                        if (this.model.operation.items[index].features) {
+                            this.thisOperationItemIndex = index;
+                            $("#addFeatureModal").modal("show");
+                            return;
+                        }
                     }
                     let url = "{{url('maintenance/priceModel/operation/getFeatures')}}";
+                    let feature = index===-1 ? this.model.operation.feature : this.model.operation.items[index].feature;
                     window.tempTip.postBasicRequest(url,{feature:feature},res=>{
-                        this.features = res.data.data;
-                        this.oldFeature = feature;
-                        if (!this.features || this.features.length === 0){
-                            this.features = [{
-                                "strategyGroupStartSign": false,
-                                "calculation" : "",
-                                "type" : "",
-                                "id" : "",  //特征ID
-                                "logic" : "",  //特征逻辑
-                                "describe" : "",  //特征信息
-                                "strategyGroupEndSign" : false,
-                            }];
+                        if (!res.data.data || res.data.data.length === 0){
+                            res.data.data = this._createFeature();
                         }
+                        if (index === -1) this.model.operation.features = res.data.data;
+                        else this.model.operation.items[index].features = res.data.data;
                         this.thisOperationItemIndex = index;
                         $("#addFeatureModal").modal("show");
                     });
                 },
+                //增加特征
+                addFeature(){
+                    let obj = {
+                        "strategyGroupStartSign": false,
+                        "calculation" : "",
+                        "type" : "",
+                        "id" : "",
+                        "logic" : "",
+                        "describe" : "",
+                        "strategyGroupEndSign" : false,
+                    };
+                    if (this.thisOperationItemIndex === -1)this.model.operation.features.push(obj);
+                    else this.model.operation.items[this.thisOperationItemIndex].features.push(obj);
+                    this.$forceUpdate();
+                },
+                _createFeature(){
+                    return [{
+                        "strategyGroupStartSign": false,
+                        "calculation" : "",
+                        "type" : "",
+                        "id" : "",
+                        "logic" : "",
+                        "describe" : "",
+                        "strategyGroupEndSign" : false,
+                    }];
+                },
+                //删除特征
+                delFeature(index) {
+                    if (this.thisOperationItemIndex === -1)this.$delete(this.model.operation.features,index);
+                    else this.$delete(this.model.operation.items[this.thisOperationItemIndex].features,index);
+                    this.$forceUpdate();
+                },
+                //提交特征更新现有
+                submitFeature(){
+                    let url = "{{url('maintenance/priceModel/operation/getFeature')}}";
+                    let features = this.thisOperationItemIndex === -1 ? this.model.operation.features : this.model.operation.items[this.thisOperationItemIndex].features;
+                    window.tempTip.postBasicRequest(url,{features:features,isFormat:true},res=>{
+                        if (this.thisOperationItemIndex === -1){
+                            this.model.operation.feature = res.feature;
+                            this.model.operation.featureFormat = res.featureFormat;
+                        } else {
+                            this.model.operation.items[this.thisOperationItemIndex].feature = res.feature;
+                            this.model.operation.items[this.thisOperationItemIndex].featureFormat = res.featureFormat;
+                            this.$forceUpdate();
+                        }
+                        $("#addFeatureModal").modal("hide");
+                        return "已更新特征";
+                    },true);
+                },
+                //渲染作业费子项
+                _renderingOperationItem(index){
+                    let domId = "operation-"+index;
+                    let trId = "operation-tr-"+index;
+                    let itemId = "operation-item-"+index;
+                    let html = "<tr class='d-none' id='"+trId+"'><td></td><td colspan='5'>"+
+                        "<div id='"+itemId+"'><table class='table table-sm'>"+
+                        "<th>子策略</th><th>数量</th><th>单位</th><th>单价</th><th>特征</th></th>";
+                    this.selectedModel.operation[index].items.forEach(item=> {
+                        html = this._createOperationItemList(html,item.strategy,item.amount,this.poolMapping.units[item.unit_id],item.unit_price,item.featureFormat ? item.featureFormat : '');
+                    });
+                    html += "</table></div></td></tr>";
+                    $("#"+domId).after(html);
+                    $("#"+itemId).slideUp();
+                },
+                _createOperationItemList(html,strategy,amount,unit,unit_price,feature){
+                    html += "<tr><td>"+strategy+"</td><td>"+amount+"</td><td>"+unit+"</td><td>"+unit_price+"</td><td><div class='text-overflow-warp-100'>"+feature+"</div></td></tr>";
+                    return html;
+                },
+                //移入移出时更改长文本显示效果
+                textClass(event,isOver){
+                    event = event.target.children[0];
+                    if (isOver) event.className = "text-overflow-warp-100";
+                    else event.className = "cursor-pointer text-overflow-replace-100";
+                },
+                //展开子策略
+                showOperationItem(index){
+                    let trId = "operation-tr-"+index;
+                    let itemId = "operation-item-"+index;
+                    if (this.operationItems['_'+index] &&  this.operationItems['_'+index]===true){
+                        this.operationItems['_'+index] = false;
+                        $("#"+itemId).slideUp(undefined,function () {
+                            $("#"+trId).addClass("d-none");
+                        });
+                    }else {
+                        $("#"+trId).removeClass("d-none");
+                        this.operationItems['_'+index] = true;
+                        $("#"+itemId).slideDown();
+                    }
+                    this.$forceUpdate();
+                },
+                //新增快递子项
+                addExpressItem(){
+                    this.model.express.items.unshift({
+                        province_id : "",
+                        initial_weight_price:"",
+                        additional_weight_price:"",
+                    });
+                },
+                //删除快递子项
+                delExpressItem(index){
+                    this.$delete(this.model.express.items,index);
+                },
+                //选择文件
+                selectFile(id){
+                    this.importError = [];
+                    $("#"+id).click();
+                },
+                //导入快递子项
+                importExpress(e){
+                    let file=e.target.files[0];
+                    if (!file){
+                        tempTip.setDuration(3000);
+                        tempTip.show("未选择文件");
+                        return;
+                    }
+                    let formData = new FormData();
+                    formData.append("file",file);
+                    axios.post('{{url('maintenance/priceModel/express/import')}}',formData,{
+                        'Content-Type':'multipart/form-data'
+                    }).then(res=>{
+                            if (res.data.success) {
+                                res.data.data.forEach(data=>{
+                                    let unique = this.model.express.items.every(item=>{
+                                        if (data.province_id === item.province_id)return false;
+                                        return true;
+                                    });
+                                    if (unique)this.model.express.items.push(data);
+                                });
+                                this.importError = res.data.errors;
+                                tempTip.setDuration(3000);
+                                tempTip.showSuccess("导入成功!");
+                                return;
+                            }
+                            tempTip.setDuration(3000);
+                            tempTip.show(res.data.data);
+                        }).catch(err=> {
+                        tempTip.setDuration(3000);
+                        tempTip.show("网络错误:"+err);
+                    })
+                }
             },
         });
     </script>

+ 8 - 5
resources/views/customer/project/_addFeature.blade.php → resources/views/customer/project/part/_addFeature.blade.php

@@ -11,9 +11,11 @@
                         <label class="col-4">特征内容</label>
                         <label class="col-1"></label>
                     </div>
-                    <div class="row" v-for="(feature,i) in features">
+                    <div class="row" v-for="(feature,i) in (thisOperationItemIndex===-1 ? model.operation.features : (model.operation.items[thisOperationItemIndex] ? model.operation.items[thisOperationItemIndex].features : []))">
                         <div class="col-1">
-                            <span class="fa fa-minus-square pull-right mt-1" v-if="features.length > 1" style="cursor: pointer" @click="delFeature(i)"></span>
+                            <span class="fa fa-minus-square pull-right mt-1"
+                                  v-if="(thisOperationItemIndex===-1 ? model.operation.features : (model.operation.items[thisOperationItemIndex] ? model.operation.items[thisOperationItemIndex].features : [])).length > 1"
+                                  style="cursor: pointer" @click="delFeature(i)"></span>
                         </div>
                         <label class="col-2">
                             <select class="form-control form-control-sm" v-if="i != 0" v-model="feature.calculation">
@@ -23,19 +25,20 @@
                         </label>
                         <label class="col-2">
                             <select class="form-control form-control-sm" v-model="feature.type">
-                                <option v-for="t in type" :value="t" v-if="(thisIndex == '-1' && t != '商品名称') || (thisIndex != '-1' && t=='商品名称')">@{{ t }}</option>
+                                <option v-for="t in pool.feature_type" :value="t">@{{ t }}</option>
                             </select>
                         </label>
                         <label class="col-2">
                             <select class="form-control form-control-sm" v-model="feature.logic">
-                                <option v-for="l in logic" :value="l" v-if="thisIndex == '-1' || l != '不包含'">@{{ l }}</option>
+                                <option v-for="l in pool.logic" :value="l">@{{ l }}</option>
                             </select>
                         </label>
                         <label class="col-4">
                             <input class="form-control form-control-sm" v-model="feature.describe">
                         </label>
                         <div class="col-1">
-                            <span v-if="i == (features.length-1)" class="fa fa-plus-square pull-right mt-1" style="cursor: pointer" @click="addFeature()"></span>
+                            <span v-if="i == ((thisOperationItemIndex===-1 ? model.operation.features : (model.operation.items[thisOperationItemIndex] ? model.operation.items[thisOperationItemIndex].features : [])).length-1)"
+                                  class="fa fa-plus-square pull-right mt-1" style="cursor: pointer" @click="addFeature()"></span>
                         </div>
                     </div>
                 </div>

+ 1 - 0
resources/views/customer/project/part/_directLogistic.blade.php

@@ -0,0 +1 @@
+2

+ 57 - 0
resources/views/customer/project/part/_express.blade.php

@@ -0,0 +1,57 @@
+<div class="row">
+    <label class="col-3"><b class="text-danger">* </b>价格名称</label>
+    <label class="col-7"><input type="text" class="form-control"
+           v-model="model.express.name" :class="errors.name ? 'is-invalid' : ''"></label>
+</div>
+<div class="row mt-3">
+    <label class="col-3"><b class="text-danger">* </b>首重值(KG)</label>
+    <label class="col-7"><input type="number" min="0" step="0.01" class="form-control"
+           v-model="model.express.initial_weight" :class="errors.initial_weight ? 'is-invalid' : ''"></label>
+</div>
+<div class="row mt-3">
+    <label class="col-3"><b class="text-danger">* </b>续重值(KG)</label>
+    <label class="col-7"><input type="number" min="0" step="0.01" class="form-control"
+           v-model="model.express.additional_weight" :class="errors.additional_weight ? 'is-invalid' : ''"></label>
+</div>
+<div class="row mt-3">
+    <label class="col-3">详情</label>
+    <div class="col-9">
+        <div class="w-100 form-inline">
+            <input id="expressFile" type="file" class="d-none" accept=".csv, .xlsx, .xls" @change="importExpress($event)"/>
+            <button type="button" class="btn btn-sm btn-outline-info w-25" @click="addExpressItem()">新增</button>
+            <button type="button" class="btn btn-sm btn-outline-primary w-25 ml-2" @click="selectFile('expressFile')">导入</button>
+            <h5><span class="ml-0 fa fa-question-circle-o cursor-pointer" data-toggle="tooltip" data-placement="top" title="导入与保存时自动过滤重复数据"></span></h5>
+        </div>
+        <div class="w-100 text-center mb-1 mt-1" v-if="importError.length > 0">
+            <button type="button" class="btn btn-sm btn-danger mb-1" @click="isShowError = true" v-if="!isShowError">@{{ importError.length }}条错误,点击展开</button>
+            <button type="button" class="btn btn-sm btn-dark mb-1" @click="isShowError = false" v-else>收起错误展示</button>
+            <div v-if="isShowError" class="container-fluid text-danger font-weight-bolder">
+                <div class="row text-left">
+                    <div class="col-6" v-for="error in importError">@{{ error }}</div>
+                </div>
+            </div>
+        </div>
+        <div class="row font-weight-bold" v-if="model.express.items.length>0">
+            <label class="col-3">省</label>
+            <label class="col-3">首重价格</label>
+            <label class="col-3">续重价格</label>
+            <label class="col-3"></label>
+        </div>
+        <div class="row" v-for="(item,i) in model.express.items">
+            <label class="col-3">
+                <select class="form-control form-control-sm" v-model="item.province_id" :class="errors['item.'+i+'.province_id'] ? 'is-invalid' : ''">
+                    <option v-for="province in pool.provinces" :value="province.id">@{{ province.name }}</option>
+                </select>
+            </label>
+            <label class="col-3">
+                <input type="number" step="0.01" class="form-control form-control-sm" v-model="item.initial_weight_price" :class="errors['item.'+i+'.initial_weight_price'] ? 'is-invalid' : ''">
+            </label>
+            <label class="col-3">
+                <input type="number" step="0.01" class="form-control form-control-sm" v-model="item.additional_weight_price" :class="errors['item.'+i+'.additional_weight_price'] ? 'is-invalid' : ''">
+            </label>
+            <label class="col-3 cursor-pointer h3 font-weight-bold text-danger" @click="delExpressItem(i)">
+                &times;
+            </label>
+        </div>
+    </div>
+</div>

+ 1 - 0
resources/views/customer/project/part/_logistic.blade.php

@@ -0,0 +1 @@
+1

+ 0 - 0
resources/views/customer/project/_one.blade.php → resources/views/customer/project/part/_one.blade.php


+ 53 - 52
resources/views/customer/project/_operation.blade.php → resources/views/customer/project/part/_operation.blade.php

@@ -37,33 +37,33 @@
             <div class="row">
                 <label class="col-3">子策略:</label>
                 <label class="col-5"><select disabled v-model="model.operation.items[0].strategy" class="form-control">
-                        <option>起步</option>
-                    </select></label>
+                    <option>起步</option>
+                </select></label>
             </div>
             <div class="row mt-2">
                 <label class="col-3">起步数</label>
-                <label class="col-5"><input id="amount" type="number" :class="errors['items.0.amount'] ? 'is-invalid' : ''"
+                <label class="col-5 mb-0"><input id="amount" type="number" :class="errors['items.0.amount'] ? 'is-invalid' : ''"
                        v-model="model.operation.items[0].amount" class="form-control" step="1"></label>
-                <span class="invalid-feedback mt-0 offset-2" role="alert" v-if="errors['items.0.amount']">
-                    <strong>@{{ errors['items.0.amount'][0] }}</strong>
-                </span>
+            </div>
+            <div class="row mt-0" v-if="errors['items.0.amount']">
+                <div class="offset-3"><small class="text-danger font-weight-bold ml-3">@{{ errors['items.0.amount'][0] }}</small></div>
             </div>
             <div class="row mt-2">
                 <label class="col-3">单位</label>
-                <label class="col-5"><select v-model="model.operation.items[0].unit_id" class="form-control" :class="errors['items.0.unit_id'] ? 'is-invalid' : ''">
+                <label class="col-5 mb-0"><select v-model="model.operation.items[0].unit_id" class="form-control" :class="errors['items.0.unit_id'] ? 'is-invalid' : ''">
                         <option v-for="unit in pool.units" :value="unit.id">@{{ unit.name }}</option>
                 </select></label>
-                <span class="invalid-feedback mt-0 offset-2" role="alert" v-if="errors['items.0.unit_id']">
-                    <strong>@{{ errors['items.0.unit_id'][0] }}</strong>
-                </span>
+            </div>
+            <div class="row mt-0" v-if="errors['items.0.unit_id']">
+                <div class="offset-3"><small class="text-danger font-weight-bold ml-3">@{{ errors['items.0.unit_id'][0] }}</small></div>
             </div>
             <div class="row mt-2">
                 <label class="col-3">单价</label>
-                <label class="col-5"><input type="number" min="0" step="0.001" class="form-control" v-model="model.operation.items[0].unit_price"
+                <label class="col-5 mb-0"><input type="number" min="0" step="0.001" class="form-control" v-model="model.operation.items[0].unit_price"
                            :class="errors['items.0.unit_price'] ? 'is-invalid' : ''"></label>
-                <span class="invalid-feedback mt-0 offset-2" role="alert" v-if="errors['items.0.unit_price']">
-                    <strong>@{{ errors['items.0.unit_price'][0] }}</strong>
-                </span>
+            </div>
+            <div class="row mt-0" v-if="errors['items.0.unit_price']">
+                <div class="offset-3"><small class="text-danger font-weight-bold ml-3">@{{ errors['items.0.unit_price'][0] }}</small></div>
             </div>
         </div>
     </div>
@@ -75,36 +75,37 @@
         </div>
         <div class="card-body">
             <div class="row">
+                <label class="col-3">子策略:</label>
+                <label class="col-5"><select disabled v-model="model.operation.items[1].strategy" class=" form-control">
+                    <option>默认</option>
+                </select></label>
+            </div>
+            <div class="row mt-2">
                 <label class="col-3">数量</label>
-                <label class="col-5"><input id="amount" type="number" :class="errors['items.1.amount'] ? 'is-invalid' : ''"
-                                            v-model="model.operation.items[1].amount" class="form-control" step="1">
+                <label class="col-5 mb-0"><input id="amount" type="number" :class="errors['items.1.amount'] ? 'is-invalid' : ''"
+                       v-model="model.operation.items[1].amount" class="form-control" step="1">
                 </label>
-                <span class="invalid-feedback mt-0 offset-2" role="alert" v-if="errors['items.1.amount']">
-                    <strong>@{{ errors['items.1.amount'][0] }}</strong>
-                </span>
+                <div class="col-4"></div>
+            </div>
+            <div class="row mt-0" v-if="errors['items.1.amount']">
+                <div class="offset-3"><small class="text-danger font-weight-bold ml-3">@{{ errors['items.1.amount'][0] }}</small></div>
             </div>
             <div class="row mt-2">
                 <label class="col-3">单位</label>
-                <label class="col-5"><select v-model="model.operation.items[1].unit_id" class="form-control" :class="errors['items.1.unit_id'] ? 'is-invalid' : ''">
-                        <option v-for="unit in pool.units" :value="unit.id">@{{ unit.name }}</option>
-                    </select></label>
-                <span class="invalid-feedback mt-0 offset-2" role="alert" v-if="errors['items.1.unit_id']">
-                     <strong>@{{ errors['items.1.unit_id'][0] }}</strong>
-                </span>
+                <label class="col-5 mb-0"><select v-model="model.operation.items[1].unit_id" class="form-control" :class="errors['items.1.unit_id'] ? 'is-invalid' : ''">
+                    <option v-for="unit in pool.units" :value="unit.id">@{{ unit.name }}</option>
+                </select></label>
+            </div>
+            <div class="row mt-0" v-if="errors['items.1.unit_id']">
+                <div class="offset-3"><small class="text-danger font-weight-bold ml-3">@{{ errors['items.1.unit_id'][0] }}</small></div>
             </div>
             <div class="row mt-2">
                 <label class="col-3">单价</label>
-                <label class="col-5"><input type="number" min="0" step="0.001" class="form-control" v-model="model.operation.items[1].unit_price"
+                <label class="col-5 mb-0"><input type="number" min="0" step="0.001" class="form-control" v-model="model.operation.items[1].unit_price"
                            :class="errors['items.1.unit_price'] ? 'is-invalid' : ''"></label>
-                <span class="invalid-feedback mt-0 offset-2" role="alert" v-if="errors['items.1.unit_price']">
-                    <strong>@{{ errors['items.1.unit_price'][0] }}</strong>
-                </span>
             </div>
-            <div class="row mt-2">
-                <label class="col-3">子策略:</label>
-                <label class="col-5"><select disabled v-model="model.operation.items[1].strategy" class=" form-control">
-                    <option>默认</option>
-                </select></label>
+            <div class="row mt-0" v-if="errors['items.1.unit_price']">
+                <div class="offset-3"><small class="text-danger font-weight-bold ml-3">@{{ errors['items.1.unit_price'][0] }}</small></div>
             </div>
         </div>
     </div>
@@ -117,40 +118,40 @@
         </div>
         <div class="card-body">
             <div class="row">
+                <label class="col-3">子策略</label>
+                <label class="col-5"><select disabled v-model="item.strategy" class="form-control">
+                    <option>特征</option>
+                </select></label>
+            </div>
+            <div class="row mt-2">
                 <label class="col-3">数量</label>
-                <label class="col-5"><input type="number" step="1" :class="errors['items.'+i+'.amount'] ? 'is-invalid' : ''" v-model="item.amount" class="form-control"></label>
-                <span class="invalid-feedback mt-0 offset-2" role="alert" v-if="errors['items.'+i+'.amount']">
-                    <strong>@{{ errors['items.'+i+'.amount'][0] }}</strong>
-                </span>
+                <label class="col-5 mb-0"><input type="number" step="1" :class="errors['items.'+i+'.amount'] ? 'is-invalid' : ''" v-model="item.amount" class="form-control"></label>
+            </div>
+            <div class="row mt-0" v-if="errors['items.'+i+'.amount']">
+                <div class="offset-3"><small class="text-danger font-weight-bold ml-3">@{{ errors['items.'+i+'.amount'][0] }}</small></div>
             </div>
             <div class="row mt-2">
                 <label class="col-3">单位</label>
-                <label class="col-5"><select v-model="item.unit_id" class="form-control" :class="errors['items.'+i+'.unit_id'] ? 'is-invalid' : ''">
+                <label class="col-5 mb-0"><select v-model="item.unit_id" class="form-control" :class="errors['items.'+i+'.unit_id'] ? 'is-invalid' : ''">
                     <option v-for="unit in pool.units" :value="unit.id">@{{ unit.name }}</option>
                 </select></label>
-                <span class="invalid-feedback mt-0 offset-2" role="alert" v-if="errors['items.'+i+'.unit_id']">
-                    <strong>@{{ errors['items.'+i+'.unit_id'][0] }}</strong>
-                </span>
+            </div>
+            <div class="row mt-0" v-if="errors['items.'+i+'.unit_id']">
+                <div class="offset-3"><small class="text-danger font-weight-bold ml-3">@{{ errors['items.'+i+'.unit_id'][0] }}</small></div>
             </div>
             <div class="row mt-2">
                 <label class="col-3">单价</label>
-                <label class="col-5"><input type="number" min="0" step="0.001" class="form-control" v-model="item.unit_price"
+                <label class="col-5 mb-0"><input type="number" min="0" step="0.001" class="form-control" v-model="item.unit_price"
                        :class="errors['items.'+i+'.unit_price'] ? 'is-invalid' : ''"></label>
-                <span class="invalid-feedback mt-0 offset-2" role="alert" v-if="errors['items.'+i+'.unit_price']">
-                    <strong>@{{ errors['items.'+i+'.unit_price'][0] }}</strong>
-                </span>
             </div>
-            <div class="row mt-2">
-                <label class="col-3">子策略</label>
-                <label class="col-5"><select disabled v-model="item.strategy" class="form-control">
-                    <option>特征</option>
-                </select></label>
+            <div class="row mt-0" v-if="errors['items.'+i+'.unit_price']">
+                <div class="offset-3"><small class="text-danger font-weight-bold ml-3">@{{ errors['items.'+i+'.unit_price'][0] }}</small></div>
             </div>
             <div class="row mt-2">
                 <label class="col-3">特征:</label>
                 <label class="col-5">
                     <label v-if="item.feature">@{{ item.feature }}</label><br>
-                    <button type="button" class="btn btn-dark ml-2" @click="showAddFeatureModal(i,item.feature)">调整特征</button>
+                    <button type="button" class="btn btn-dark ml-2" @click="showAddFeatureModal(i)">调整特征</button>
                 </label>
             </div>
         </div>

+ 0 - 0
resources/views/customer/project/_storage.blade.php → resources/views/customer/project/part/_storage.blade.php


+ 170 - 0
resources/views/customer/project/part/_three.blade.php

@@ -0,0 +1,170 @@
+<div class="row">
+    <div class="col-6">
+        <div class="card">
+            <div class="card-header bg-light-info">
+                <span class="pull-left font-weight-bold cursor-pointer" @click="show('storage')"><span class="fa fa-cubes"></span>&nbsp;仓储</span>
+            </div>
+            <div class="card-body" id="storage">
+                <table class="table table-sm">
+                    <tr>
+                        <th>计费类型</th>
+                        <th>用仓类型</th>
+                        <th>起租面积</th>
+                        <th>单价</th>
+                        <th>单位</th>
+                        <th>减免类型</th>
+                        <th>减免值</th>
+                    </tr>
+                    <tr v-for="item in selectedModel.storage">
+                        <td>@{{ item.counting_type }}</td>
+                        <td>@{{ item.using_type }}</td>
+                        <td>@{{ item.minimum_area }}</td>
+                        <td>@{{ item.price }}</td>
+                        <td>@{{ item.discount_type }}</td>
+                        <td>@{{ item.discount_value }}</td>
+                        <td>@{{ poolMapping.units[item.unit_id] }}</td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+        <div class="card">
+            <div class="card-header bg-light-info">
+                <span class="pull-left font-weight-bold cursor-pointer" @click="show('operation')"><span class="fa fa-suitcase"></span>&nbsp;作业</span>
+            </div>
+            <div class="card-body" id="operation">
+                <table class="table table-sm">
+                    <tr>
+                        <th>操作类型</th>
+                        <th>计费策略</th>
+                        <th>名称</th>
+                        <th>子策略</th>
+                        <th>特征</th>
+                        <th>备注</th>
+                    </tr>
+                    <tr v-for="(operation,i) in selectedModel.operation" :id="'operation-'+i">
+                        <td>@{{ operation.operation_type }}</td>
+                        <td>@{{ operation.strategy }}</td>
+                        <td>@{{ operation.name }}</td>
+                        <td class="cursor-pointer" @click="showOperationItem(i)"><span class="fa" :class="operationItems['_'+i] === true ? 'fa-angle-double-down' : 'fa-angle-double-right'"></span>@{{ operation.items.length }}条策略</td>
+                        <td @mouseenter="textClass($event,true)" @mouseleave="textClass($event,false)"><div class="cursor-pointer text-overflow-replace-100">@{{ operation.featureFormat }}</div></td>
+                        <td @mouseenter="textClass($event,true)" @mouseleave="textClass($event,false)"><div class="cursor-pointer text-overflow-replace-100">@{{ operation.remark }}</div></td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+        <div class="card">
+            <div class="card-header bg-light-info">
+                <span class="pull-left font-weight-bold cursor-pointer" @click="show('express')"><span class="fa fa-cube"></span>&nbsp;快递</span>
+            </div>
+            <div class="card-body" id="express">
+                <div class="row">
+                    <label class="col-4">名称:<b>@{{ selectedModel.express.name }}</b></label>
+                    <label class="col-4">首重值(KG):<b>@{{ selectedModel.express.initial_weight }}</b></label>
+                    <label class="col-4">续重值(KG):<b>@{{ selectedModel.express.additional_weight }}</b></label>
+                </div>
+                <div class="row">
+                    <label class="text-primary col-2">详情</label>
+                    <table class="table table-sm col-10">
+                        <tr>
+                            <th>省份</th>
+                            <th>首重价格</th>
+                            <th>续重价格</th>
+                        </tr>
+                        <tr v-for="(item,i) in selectedModel.express.items">
+                            <td>@{{ poolMapping.provinces[item.province_id] }}</td>
+                            <td>@{{ item.initial_weight_price }}</td>
+                            <td>@{{ item.additional_weight_price }}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+        <div class="card">
+            <div class="card-header bg-light-info">
+                <span class="pull-left font-weight-bold cursor-pointer" @click="show('logistic')"><span class="fa fa-truck"></span>&nbsp;物流</span>
+            </div>
+            <div class="card-body" id="logistic">
+                <div class="row">
+                    <label class="col-4">名称:<b>@{{ selectedModel.express.name }}</b></label>
+                    <label class="col-4">首重值(KG):<b>@{{ selectedModel.express.initial_weight }}</b></label>
+                    <label class="col-4">续重值(KG):<b>@{{ selectedModel.express.additional_weight }}</b></label>
+                </div>
+                <div class="row">
+                    <label class="text-primary col-2">详情</label>
+                    <table class="table table-sm col-10">
+                        <tr>
+                            <th>省份</th>
+                            <th>首重价格</th>
+                            <th>续重价格</th>
+                        </tr>
+                        <tr v-for="(item,i) in selectedModel.express.items">
+                            <td>@{{ poolMapping.provinces[item.province_id] }}</td>
+                            <td>@{{ item.initial_weight_price }}</td>
+                            <td>@{{ item.additional_weight_price }}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+        <div class="card">
+            <div class="card-header bg-light-info">
+                <span class="pull-left font-weight-bold cursor-pointer" @click="show('directLogistic')"><span class="fa fa-rocket"></span>&nbsp;直发</span>
+            </div>
+            <div class="card-body" id="directLogistic">
+                <div class="row">
+                    <label class="col-4">名称:<b>@{{ selectedModel.express.name }}</b></label>
+                    <label class="col-4">首重值(KG):<b>@{{ selectedModel.express.initial_weight }}</b></label>
+                    <label class="col-4">续重值(KG):<b>@{{ selectedModel.express.additional_weight }}</b></label>
+                </div>
+                <div class="row">
+                    <label class="text-primary col-2">详情</label>
+                    <table class="table table-sm col-10">
+                        <tr>
+                            <th>省份</th>
+                            <th>首重价格</th>
+                            <th>续重价格</th>
+                        </tr>
+                        <tr v-for="(item,i) in selectedModel.express.items">
+                            <td>@{{ poolMapping.provinces[item.province_id] }}</td>
+                            <td>@{{ item.initial_weight_price }}</td>
+                            <td>@{{ item.additional_weight_price }}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="col-6">
+        <div class="card">
+            <div class="card-header bg-light-info">
+                <button type="button" class="btn mr-1" :class="type == 'storage' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('storage')">仓储</button>
+                <button type="button" class="btn mr-1" :class="type == 'operation' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('operation')">作业</button>
+                <button type="button" class="btn mr-1" :class="type == 'express' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('express')">快递</button>
+                <button type="button" class="btn mr-1" :class="type == 'logistic' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('logistic')">物流</button>
+                <button type="button" class="btn mr-1" :class="type == 'directLogistic' ? 'btn-primary text-white' : 'btn-outline-primary'" @click="switchType('directLogistic')">直发</button>
+            </div>
+            <div class="card-body">
+                <div v-if="type == 'storage'">
+                    @include("customer.project.part._storage")
+                </div>
+                <div v-show="type == 'operation'">
+                    @include("customer.project.part._operation")
+                    @include("customer.project.part._addFeature")
+                </div>
+                <div v-show="type == 'express'">
+                    @include("customer.project.part._express")
+                </div>
+                <div v-show="type == 'logistic'">
+                    @include("customer.project.part._logistic")
+                </div>
+                <div v-show="type == 'directLogistic'">
+                    @include("customer.project.part._directLogistic")
+                </div>
+                <div class="row mt-3" v-if="base=='three'">
+                    <div class="col-3"></div>
+                    <button type="button" class="btn btn-success ml-1 col-6" @click="saveModel()">保存</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 0 - 0
resources/views/customer/project/_two.blade.php → resources/views/customer/project/part/_two.blade.php


+ 1 - 0
routes/web.php

@@ -607,6 +607,7 @@ Route::group(['prefix'=>'customer'],function(){
         Route::post('storeLog', 'CustomerLogController@store');
         Route::post('relatedOwner', 'CustomerBaseController@relatedOwner');
         Route::post('addTag', 'CustomerBaseController@addTag');
+        Route::post('destroyLog', 'CustomerBaseController@destroyLog');
         Route::group(['prefix' => 'customerLogStatus'], function () {
             Route::post('save', 'CustomerLogStatusController@save');
             Route::post('destroy', 'CustomerLogStatusController@destroy');