2021年5月9日 星期日

MacOS 無法啟動Mysql/MariaDB

上一篇 MacOS php 升級 7.2→7.4 做完後,發現Mysql/MariaDB重啟時會出現問題,查了一下網路找到這篇,照著做後就能夠正常的啟動嘍!

先停止Mysql/MariaDB

mysql.server stop

因為我的原本啟動就有問題,所以DB本來就沒在啟動的狀態…

接著我們找到資料夾 /usr/local/var/mysql,然後將 ib_logfile0 & ib_logfile1 這兩個檔案刪除,再次啟動DB。

mysql.server start

成功!


MacOS php 升級 7.2→7.4

 MacOS升級至Big Sur後發現brew update無法使用了。

查了網路上很多文章建議用brew update-reset來更新,但我下了指令後卻出現了其他問題,

==> Fetching /usr/local/Homebrew...
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
...

後來查了一下才發現要重装xcode command line

xcode-select --install

再執行

brew update-reset
brew list

接著就能安裝php7.4了

brew install php@7.4

結果出現錯誤

Removing: /usr/local/Cellar/nginx/1.17.9... (25 files, 2MB)
Error: Directory not empty @ dir_s_rmdir - /usr/local/Cellar/nginx/1.17.9

先執行

sudo rm -R /usr/local/Cellar/nginx/1.17.9

之後

brew cleanup

我們就可以開始指定使用php7.4了

sudo nginx -s reload
brew unlink php@7.2
brew link php@7.4
brew services start php@7.4
export PATH="/usr/local/opt/php@7.4/bin:$PATH"
export PATH="/usr/local/opt/php@7.4/sbin:$PATH"

最後我們在檢查一下php的版本

php -v

大功告成!


2021年5月1日 星期六

PHP laravel-admin 加入查看購物車鈕

上篇在這 PHP laravel-admin 載入商品至購物車內

選購完商品後我們要有一個可以觀看並修改購物數量及輸入相關折扣(客戶要求)的購物清單畫面,並在總價下方增加一個加總欄位。

根據 PHP laravel-admin 購物車清單 ,在addCart中加入查看購物車的按鈕,藍色那行。

protected function addCart() {
    $this->iScript();
    return '
    <div class="btn-group pull-right" style="margin-right: 10px;">
    <a class="btn btn-md btn-success" title="購物車"><i class="fa fa-shopping-cart"></i><span class="hidden-xs"> 購物車</span></a>
    <button type="button" class="btn btn-md btn-success dropdown-toggle" data-toggle="dropdown">
        <span class="caret"></span>
        <span class="sr-only">Toggle Dropdown</span>
    </button>
    <ul id="isale" class="dropdown-menu" role="menu" style="min-width:300px">
        <li id="total"><a class="btn btn-md btn-danger" style="color:#FFF" title="查看購物車" href="../carts?saleid='.$saleid.'"><i class="fa fa-shopping-basket"></i><span class="hidden-xs"> 查看購物車</span></a></li>
    </ul>
</div>';
}

請先建立好cart的controller跟model,然後打開controller。

進入查看購物車頁面後可以編輯購買的商品數量及設定相關折扣,我們直接在grid頁面使用行內編輯的功能進行商品的數量及扣折編輯。

※每個editable在form裡面一定要有一個相對應的欄位,不然怎麼儲存都會無效哦,特別是後來新增的欄位別忘了也加要到form中。

protected function grid()
{
    Admin::js('../vendor/laravel-admin/AdminLTE/dist/js/cart.js');
    $this->iScript();
    $grid = new Grid(new Cart());
    $grid->model()->where('sale_id', '=', request('saleid'));
    $grid->disableActions();
    $grid->disableCreateButton();
    $grid->disableFilter();
    $grid->tools(function ($tools) {
        $tools->append($this->prev(request('saleid')));
        $tools->append($this->createBill(request('saleid')));
    });

    $grid->number('序號');
    $grid->rows(function($row, $number){
        $row->column('number', $number + 1);
    });
    $grid->column('product.name', __('商品/療程名稱'));
    $grid->column('unit', __('單價'))->editable();
    $grid->column('sale_amount', __('數量'))->editable();
    $grid->column('discount', __('折扣'))->editable();
    $grid->column('total', __('總價'))->totalRow();

    return $grid;
}

總價我們採用js即時計算並更新至資料庫中,先在單價、數量、折扣加上異動時觸發的js。

protected function iScript() {
    $script = <<<EOT
    //數量
    $('.column-sale_amount').change(function () {
        saleinfo_list_edittable($(this), 'sale_amount');
    });
    //單價
    $('.column-unit').change(function () {
        cart_list_edittable($(this), 'unit_price');
    });
    //折扣
    $('.column-discount').change(function () {
        cart_list_edittable($(this), 'discount_price');
    });
EOT;
    Admin::script($script);
}

在頁面多增加兩個按鈕,一個是繼續購物、一個是前往結帳。

protected function prev($saleid) {
    return '
<a class="btn btn-md btn-success" title="繼續選購商品" href="../products?saleid='.$saleid.'"><i class="fa fa-cart-plus"></i><span class="hidden-xs"> 繼續選購商品</span></a>
';
}

protected function createBill($saleid) {
    return '
<a class="btn btn-md btn-danger" title="結帳" href="../bills/create?sale_id='.$saleid.'&type=1"><i class="fa fa-usd"></i><span class="hidden-xs"> 結帳</span></a>
';
}
cart.js主要是做即時的總價計算並將結果儲存至資料表中。
function cart_list_edittable(obj, name) {
    var amount, price, discount;
    var ele = obj.closest('tr');
    var url = '../carts/';
    switch (name) {
        case 'sale_amount':
            amount = obj.find('.form-control').val();
            price = parseInt(ele.children('.column-unit').text());
            discount = parseInt(ele.children('.column-discount').text());
        break;
        case 'unit':
            price = obj.find('.form-control').val();
            amount = parseInt(ele.children('.column-sale_amount').text());
            discount = parseInt(ele.children('.column-discount').text());
        break;
        case 'discount':
            discount = obj.find('.form-control').val();
            amount = parseInt(ele.children('.column-sale_amount').text());
            price = parseInt(ele.children('.column-unit').text());
        break;

    }
    /* 呼叫editable() begin */
    var pk = obj.children('a').data('pk');
    var sum = amount*price-discount;
    $.ajax({
        method: 'POST',
        url: url+pk,
        data: {name: 'total', value: sum, pk: pk, _token: $.admin.token, _editable: 1, _method: 'PUT'}
    });
    /* 呼叫editable() end */
    ele.children('.column-total').text(sum);
    var total = 0;
    $('.table tbody .column-total').each(function (i, val) {
        total += parseInt($(this).text());
    });
    $('.table tfoot .column-total').text(total);    
}

好嘍,查看購物車內容完成!

下一篇我們要來介接結帳。


2021年4月20日 星期二

PHP laravel-admin 載入商品至購物車內

#20210501 更新

接續上一篇 PHP laravel-admin 刪除購物車內的商品

已經在結帳頁面了才發現少選了商品因此必須要回上一頁重新選擇商品,這時的購物車應該要載入我們原先已經選擇的商品,做起來跟上一篇的刪除購物車內容差不多,只是從刪除商品改為列出商品清單。

首先新增一個action。

php artisan admin:action Product\\CartInfo

內容如下:

use App\Models\Cart;
use App\Models\Product;

public function handle(Request $request)
{
    // $request ...
    $carts = Cart::with('product')->whereHas('product', function ($query) use ($request) 
    {
        $query->where('sale_id', '=', $request->get('_saleid'));
    })->get();
    return $this->response()->success($carts);
}

然後在iScript中新增下列內容,綠色的內容就是刪除商品的javascript,跟上一篇一樣要在此時一併加入,不然刪除鍵會沒作用哦。

var process = new Promise(function (resolve,reject) {
    var data = { _token: $.admin.token, _action: 'App_Admin_Actions_Product_CartInfo', _saleid:urlParams.get('saleid')};
    $.ajax({
        method: 'POST',
        url: '../_handle_action_',
        data: data,
        success: function (response) {
            $('#isale li').each(function (i,v){
                if ($(this).attr('id') != 'total') $(this).remove();
            });
            var result = JSON.parse(response.toastr.content);
            if (result.length > 0) {
                $.each(result, function (index, element) {
                    var p = element.product;
                    $('#total').before('<li id="p'+p.id+'" data-_key="'+p.id+'"><p>'+ p.name +'<a class="text-danger pull-right fa fa-trash-o href-hand"></a></p></li>');
                    $('#p'+p.id).off('click').on('click', function() {
                        var data = $(this).data();
                        var key = $(this).data('_key');
                        var target = $(this);
                        Object.assign(data, []);
                        var process = new Promise(function (resolve,reject) {
                            Object.assign(data, {
                                _token: $.admin.token,
                                _action: 'App_Admin_Actions_Product_DeleteCart',
                                _productid: key,
                                _saleid:urlParams.get('saleid')
                            });
                            $.ajax({
                                method: 'POST',
                                url: '../_handle_action_',
                                data: data,
                                success: function (data) {
                                    $('#p'+key).remove();
                                    resolve([data, target]);
                                },
                                error:function(request){
                                    //reject(request);
                                    reject('商品刪除失敗');
                                }
                            });
                        });
                        process.then(actionResolver).catch(actionCatcher);
                    });
                });
            }
        },
        error:function(request){
            $('#total').before('讀取商品時發生錯誤!');
        }
    });
});
process.then(actionResolver).catch(actionCatcher);

好了,完成。


2021年3月22日 星期一

PHP laravel-admin 刪除購物車內的商品

#20210420 更新

本篇是接續 PHP laravel-admin 購物車清單 的文章。

上一篇介紹點了選購的按鈕後會把商品加到購物車中,這一篇就是介紹如何刪除購物車的物品且不需要刷新頁面,就是利用ajax。

先新增一個action。

php artisan admin:action Product\\DeleteCart --name="刪除購物車"

DeleteCart.php的內容。

use App\Models\Cart;
use App\Models\Product;

public function handle(Request $request)
{
    // $request ...
    $product_name = Product::find($request->get('_product'))->name;
    $cart = Cart::where('sale_id', $request->get('_saleid'))->where('product_id', $request->get('_productid'));
    $cart->forceDelete();
    return $this->response()->success($product_name.'已刪除');
}

新增iScript裡的內容。 此方法無法刪除購物車內之商品

$('.delete-cart').off('click').on('click', function() {
    var data = $(this).data();
    var key = $(this).data('_key');
    var target = $(this);
    Object.assign(data, []);
    
    var process = new Promise(function (resolve,reject) {
        
        Object.assign(data, {
            _token: $.admin.token,
            _action: 'App_Admin_Actions_Product_DeleteCart',
            _productid: key,
            _saleid:urlParams.get('saleid'),
        });
    
        $.ajax({
            method: 'POST',
            url: '../_handle_action_',
            data: data,
            success: function (data) {
                $('#p'+key).remove();
                resolve([data, target]);
            },
            error:function(request){
                //reject(request); debug用
                reject('商品刪除失敗');
            }
        });
    });
    process.then(actionResolver).catch(actionCatcher);
});

因購物車的商品項目是使用ajax渲染上去的,無法連動上述的javascript,因此我們需要在渲染時一併加上javascript,修改一下iScript中的btn-add-to-cart,藍字是本次新增的部份。

$('.btn-add-to-cart').off('click').on('click', function() {
    var data = $(this).data();
    var key = $(this).data('_key');
    var target = $(this);
    Object.assign(data, {"_model":"App_Models_Product"});
        var process = new Promise(function (resolve,reject) {
        Object.assign(data, {
            _token: $.admin.token,
            _action: 'App_Admin_Actions_Product_AddCart',
            _saleid: urlParams.get('saleid'),
        });
        $.ajax({
            method: 'POST',
            url: '../_handle_action_',
            data: data,
            success: function (data) {
                var p = data.toastr.content;
                if (p != '重複選購') 
                {
                    $('#total').before('<li id="p'+key+'><p>'+p+'<i class="text-danger pull-right fa fa-trash-o href-hand delete-cart" data-_key="'+key+'"></i></p></li>');
                    $('#p'+key).off('click').on('click', function() {
                        var data = $(this).data();
                        var key = $(this).data('_key');
                        var target = $(this);
                        Object.assign(data, []);
                        var process = new Promise(function (resolve,reject) {
                            Object.assign(data, {
                                _token: $.admin.token,
                                _action: 'App_Admin_Actions_Product_DeleteCart',
                                _productid: key,
                                _saleid:urlParams.get('saleid')
                            });
                            $.ajax({
                                method: 'POST',
                                url: '../_handle_action_',
                                data: data,
                                success: function (data) {
                                    $('#p'+key).remove();
                                    resolve([data, target]);
                                },
                                error:function(request){
                                    //reject(request);
                                    reject('商品刪除失敗');
                                }
                            });
                        });
                        process.then(actionResolver).catch(actionCatcher);
                    });
                }
                resolve([data, target]);
}, error:function(request){ //reject(request); debug用 reject('選購失敗'); } }); }); process.then(actionResolver).catch(actionCatcher); });

經過這次的調整就能正常刪除商品了。


2021年3月19日 星期五

PHP laravel-admin 購物車清單

#20210501 更新

這是延續上篇 PHP laravel-admin grid客製化自行定義 擴充同個頁面的功能。

選完商品後,希望有個功能可以觀看目前買了哪些東西而且是在不跳頁的情況下,所以就擴充了這支程式。

考慮了很久,我決定要把觀看購物車的按鈕放在tools中,所以我們先在grid的部份加上tools,這邊會把上一篇中$this->iScript()的部份移到新增的function中,所以我們先把原本grid裡的$this->iScript()移除。

$this->iScript();
$grid->tools(function ($tools) {
    $tools->append($this->addCart());
});

因為購物車清單需要顯示商品名稱,我們把app/Admin/Actions/Product/AddCart.php改一下。

use App\Models\Product;    
public function handle(Model $model, Request $request)
{
    $exist = Cart::where('sale_id', $request->get('_saleid'))->where('product_id', $this->getKey())->count(); 
    if ($exist < 1) {
        $product_name = Product::find($this->getKey())->name;
        $cart = new Cart();
        $cart->sale_id = $request->get('_saleid');
$cart->product_id = $this->getKey();
$cart->sale_amount = 1;
$cart->save();
        return $this->response()->success($product_name); } else {         return $this->response()->warning('重複選購');
}
}

然後我們來寫addCart的部份吧!

protected function addCart() {
    $this->iScript();
    return '
    <div class="btn-group pull-right" style="margin-right: 10px;">
    <a class="btn btn-md btn-success" title="購物車"><i class="fa fa-shopping-cart"></i><span class="hidden-xs"> 購物車</span></a>
    <button type="button" class="btn btn-md btn-success dropdown-toggle" data-toggle="dropdown">
        <span class="caret"></span>
        <span class="sr-only">Toggle Dropdown</span>
    </button>
    <ul id="isale" class="dropdown-menu" role="menu">
    </ul>
</div>';
}

修改一下iScript中的btn-add-to-cart,藍字是本次新增的部份。

$('.btn-add-to-cart').off('click').on('click', function() {
    var data = $(this).data();
    var key = $(this).data('_key');
    var target = $(this);
    Object.assign(data, {"_model":"App_Models_Product"});
        var process = new Promise(function (resolve,reject) {
        Object.assign(data, {
            _token: $.admin.token,
            _action: 'App_Admin_Actions_Product_AddCart',
            _saleid: urlParams.get('saleid'),
        });
        $.ajax({
            method: 'POST',
            url: '../_handle_action_',
            data: data,
            success: function (data) {
                var p = data.toastr.content;
                if (p != '重複選購') 
                {
                    $('#total').before('<li id="p'+key+'><p>'+p+'<i class="text-danger pull-right fa fa-trash-o href-hand delete-cart" data-_key="'+key+'"></i></p></li>');
                }
                resolve([data, target]);
}, error:function(request){ //reject(request); debug用 reject('選購失敗'); } }); }); process.then(actionResolver).catch(actionCatcher); });

這裡有新增了幾組style,可以參考著增加到自己的css檔案中。

.dropdown-menu li p {
    margin: 5px 10px;
    border-bottom: #ccc dashed 1px;
}
.href-hand {
    cursor: pointer;
}

這麼一來選購商品時即可在上方的購物車按鈕中查看商品了,之後會寫一篇關於刪除商品及查詢或商品換頁時自動帶入商品清單到購物車中。


2021年3月17日 星期三

PHP laravel-admin grid客製化自行定義

基本設定可參考此連結:https://laravel-admin.org/docs/zh/1.x/model-grid-custom-actions

從版本1.7.3開始grid的操作就變成下拉選項,但我並不需要編輯、顯示、刪除等功能,反而是要增加一個按鈕可以操作ajax,而且我還需要在ajax.data多傳一個額外的參數進去,所以決定模仿$actions->add(new Replicate)的方式,自己另外寫一個。

首先我們先下個指令增加RowAction的操作,我們可以將資料夾名稱對應所使用的Controller Name,例如我現在新增的RowAction是要對應ProductController,那麼路徑就設成Product\\XXXX,以後管理起檔案來才方便。

php artisan admin:action Product\\AddCart --grid-row --name="購物車"

如果是第一次新增,app/Admin會多增加一個叫Actions的資料夾,底下就是我們剛剛新增的Product/AddCart.php。

接著我們開始撰寫程式,我這次是想要在grid裡新增一個button,可以讓我直接將商品丟入購物車中。

<?php

namespace App\Admin\Actions\Product;

use App\Models\Cart;
use Encore\Admin\Actions\RowAction;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Encore\Admin\Facades\Admin;

class AddCart extends RowAction
{
    public $name = '購物車';
    
    public function handle(Model $model, Request $request)
    {
        // $model ...
        $cart = new Cart();
        $cart->sale_id = $request->get('_saleid');
        $cart->product_id = $this->getKey();
        $cart->sale_amount = 1;
        ...
        $cart->save();
        return $this->response()->success('選購成功');
    }

}

然後找到要使用此RowAction的ProductController.php,因為我們沒有要直接使用$actions->add(new Replicate),所以需要自行編寫javascript,其實也不難,直接copy $actions->add(new Replicate)所產生的javascript文字來改寫就好。

提供一下我寫的grid column部份

$grid->column('id','操作')->display(function ($id) {
    return "<a class='btn btn-s btn-success btn-add-to-cart' data-_key='{$id}'><i class='fa fa-cart-plus'></i> 選購</a>";
});

data-_key一定要照著這麼寫哦,不然AddCart.php裡的$this->getKey()會取不到值。

接著我們來產生javascript吧!在Controller底下增加一個function,因為最底下有個process.then(actionResolver).catch(actionCatcher),所以我們必須要補上actionResolver跟actionCatcher的定義,因為我有個值是在URL上,所以寫了一個取params的javascript,然後我增加了一個ajax.data→_saleid: urlParams.get('saleid'),如果使用自行定義是沒辦法增加data的,又或許可以?我沒找到相關的範例就是了。

use Encore\Admin\Admin;
    
    protected function iScript() {
        $script = <<<EOT
        var actionResolver = function (data) {

            var response = data[0];
            var target   = data[1];
                
            if (typeof response !== 'object') {
                return $.admin.swal({type: 'error', title: 'Oops!'});
            }
            
            var then = function (then) {
                if (then.action == 'refresh') {
                    $.admin.reload();
                }
                
                if (then.action == 'download') {
                    window.open(then.value, '_blank');
                }
                
                if (then.action == 'redirect') {
                    $.admin.redirect(then.value);
                }
                
                if (then.action == 'location') {
                    window.location = then.value;
                }
            };
            
            if (typeof response.html === 'string') {
                target.html(response.html);
            }

            if (typeof response.swal === 'object') {
                $.admin.swal(response.swal);
            }
            
            if (typeof response.toastr === 'object' && response.toastr.type) {
                $.admin.toastr[response.toastr.type](response.toastr.content, '', response.toastr.options);
            }
            
            if (response.then) {
              then(response.then);
            }
        };
        
        var actionCatcher = function (request) {
            if (request && typeof request.responseJSON === 'object') {
                $.admin.toastr.error(request.responseJSON.message, '', {positionClass:"toast-bottom-center", timeOut: 10000}).css("width","500px")
            }
        };
        var urlParams = new URLSearchParams(window.location.search);
        $('.btn-add-to-cart').off('click').on('click', function() {

            var data = $(this).data();
            var target = $(this);
            Object.assign(data, {"_model":"App_Models_Product"});
            
                    var process = new Promise(function (resolve,reject) {
                
                Object.assign(data, {
                    _token: $.admin.token,
                    _action: 'App_Admin_Actions_ProductSale_AddCart',
                    _saleid: urlParams.get('saleid'),
                });
            
                $.ajax({
                    method: 'POST',
                    url: '../_handle_action_',
                    data: data,
                    success: function (data) {
                        resolve([data, target]);
                    },
                    error:function(request){
                        //reject(request); debug用
                        reject('選購失敗');
                    }
                });
            });
    
            process.then(actionResolver).catch(actionCatcher);
        });
EOT;
        Admin::script($script);
    }

完成後,我們回到grid裡加上剛剛寫的script function。

protected function grid()
{
    $this->iScript();
    $grid = new Grid(new Product());
    ...
}

好了,完成了,下篇要介紹在同個頁面上可以查看目前選購商品的按鈕。