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());
    ...
}

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


2020年7月23日 星期四

php artisan make小筆記

建立migration
php artisan make:migration create_users_table

編輯結構內容
public function up()
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('airline');
            $table->timestamps();
        });
    }

執行migration
php artisan migrate

mysql to seed

建立model並指定目錄,無指定皆會建立在預設目錄app底下
php artisan make:model /Models/User

建立controller並配合指定目錄下之model,這裡弄了好久,因為一直不成功,想說app目錄中是顯示小寫,就把指令裡的App改為app,原來要大寫才行。
php artisan admin:make UserController --model='App\Models\User'


2020年6月20日 星期六

laravel-admin碎碎念

#20210428 更新
終於讓我找到怎麼在form底下抓取input當前輸入的值了!!!!!
這超重要,對於要寫小複雜rules的我幫助很大。
request()->all()['input_name'];

遇到網站開不起來時...
  1. 先檢查是不是DB連線有問題,連線語法。
    mysql.server start
  2. config改完,.env檔(目錄下方的隱藏檔案)也要重新檢查。
  3. config改完後先執行「php artisan config:cache」再執行「php artisan serve」。

如何建立一個共用函數

欄位輸出前如何加入前置作業,例如:將時間戳記轉成日期格式後輸出至text

台灣身分證檢查
安裝完後,使用方法如下
$form->text('tw_id','身分證')->attribute('maxlength', 10)->rules('tw_id');
錯誤訊息則是在resources/lang/zh-TW找到validation.php,檔案開啟後在最下方加上
'tw_id' => ':attribute 格式錯誤。',
這樣就可以嘍。

checkbox的使用需要在model加上array2string及string2array

擴展laravel-admin裡的admin_users欄位。


2020年4月15日 星期三

Laravel+Laravue for Mac OS

今天要紀錄的主要是Laravue的部份,遇到了好多問題才能順利on起整個模版,拍拍自己,還好堅持住了,沒有放棄不用它。

[laravel安裝]
主要參考來源:https://ithelp.ithome.com.tw/articles/10216204
次要參考來源:https://ray247k.blogspot.com/2018/03/laravel-mac.html

上一篇提到參考了某篇文章最後決定安裝php7.1就是來自第2個參考來源,雖然不知道現在還是不是有一樣的問題,但我怕麻煩,能少一事就少一事。

安裝composer
curl -sS https://getcomposer.org/installer | php 

使用 Composer 下載 Laravel installer
composer global require "laravel/installer"

把$HOME/.composer/vendor/bin目錄放置於環境變數 $PATH裡,這樣系統才能夠找到並正確執行laravel這個指令
export PATH="$PATH:$HOME/.composer/vendor/bin"

然後就能夠建立Laravel的專案了,不過這邊我就不做了,因為下一步要clone laravue的專案下來,直接使用這套模版。

[laravue安裝]
Laravue是一個laravel+vue.js的後台管理系統,因為對vue.js有興趣,找了一陣子才找到這套模版,往後也有打算用在客戶的後台管理系統。
參考來源:https://doc.laravue.dev/guide/#getting-started

因為這套系統會使用node.js,我們要先把node.js安裝起來,我們是透過home-brew來安裝,執行命令前要先確保你的系統已經安裝過home-brow嘍(安裝方式可以參考上一篇文章)
brew install node 

裝完後如果要檢查版本這樣做就行了
node -v
npm -v

接著切換到我要Clone的目錄底下
cd /my/path

下載模版並切換到該專案目錄下
composer create-project tuandm/laravue
cd laravue

接下來都是舉node.js有關的指令
npm install
npm run dev
在執行這兩行指令時我有跳出錯誤,這錯誤我找了超多文章才終於找到真正的解決之道,在這邊也放上來供大家參考。
參考來源:https://npm.community/t/npm-err-cb-never-called-macos/6101
!!錯誤訊息:npm ERR! cb() never called!
這錯誤訊息似乎是與權限有關係,解法如下,我們只要輸入下列指令後再執行上方的指令就能夠完成了。
sudo chown -R $(whoami) ~/.npm

在執行Migration前請我做了幾件事,一個參考了程式中的dbname,先在mariaDB中將DB建立好,然後到.env中修改DB連線,然後我才順利將seed餵進我的DB裡...不然一直跳出錯誤真的很煩呀!
php artisan migrate --seed

你以為這樣終於能順利執行系統了嗎?才沒有!!就在我想說這次總能順利登入系統時…又出現了一個error。
參考文章:https://github.com/tymondesigns/jwt-auth/issues/1729
!!錯誤訊息:\JWT\Signer\Hmac::doVerify() must be an instance of Lcobucci\JWT\Signer\Key
很抱歉,我並沒有把完整的錯誤訊息留下,這是找解法時人家的標題內容,如果你的錯誤訊息也有部份長的一樣,大概都會是相同的解法啦,我稍為了解了一下,這東西要你產生一個key出來才能使用,所以我們只要執行下列指令產出一個key值就能解決了。
php artisan jwt:secret

執行專案
php artisan serve

看到自己終於能夠順利登入模版,眼淚差點沒掉下來,好了!接下來就是要慢慢研究這模版怎麼使用要怎麼改,祝大家好運,我也是。

哦對了,今天在指令處用了不一樣的樣式,其實是copy官方的命令時忘了先去掉樣式就直接貼在日誌上,然後發現好漂亮哦,於是將錯就錯就用哩,呵呵呵。


PHP+Nginx+MariaDB for Mac OS

安裝時參考了很多篇文章,當參考文章的某個步驟寫的太複雜時就會再去找別篇文章參考,反正步驟看起越簡單的就越得我青睞,最後東拼西湊的終於完成安裝,在這裡記錄一下過程以供日後參考。

[安裝home-brew]
參考來源:https://codertw.com/資料庫/18645/
安裝xcode命令列工具
xcode-select --install

檢查當前環境是否最新符合brew執行
brew doctor

home-brew的安裝
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

[安裝 Nginx伺服器]
brew install nginx

安裝好後執行下面幾條命了測試一下
# 啟動 nginx服務
sudo nginx
# 重新載入配置|重啟|停止|退出 nginx
nginx -s reload|reopen|stop|quit
#測試配置是否有語法錯誤
nginx -t

啟動後可以在瀏覽器上輸入http://localhost:8080/看看是不是能看到Nginx的首頁哦。

[開機自啟動nginx服務設定]
參考來源:https://juejin.im/post/5c8fb28a6fb9a07103548318
sudo brew services start nginx

[安裝php及php-fpm]
參考來源:https://tecadmin.net/install-php-macos/
因為看了某篇文章(忘了把來源留下),決定這次要安裝php7.1版本,所以找了可以指定版本的語法的來安裝。

Install PHP 7.1
curl -s http://php-osx.liip.ch/install.sh | bash -s 7.1

Verify PHP Installation
export PATH=/usr/local/php5/bin:$PATH 

至於php-fpm在安裝php時即會一併被安裝,因此不需要另外下安裝指令。

啟動php-fpm
sudo php-fpm

[修改nginx.conf及php-fpm.conf]
參考來源:https://www.jianshu.com/p/e6a9dc091296

下方會另外說明如何修改nginx的預設路徑。

修改php-fpm檔案,先執行複製指令
sudo cp /private/etc/php-fpm.conf.default /private/etc/php-fpm.conf

到/private/etc/目錄下找到php-fpm.conf檔打開來找到error_log,內容改成下列內容
error_log = /usr/local/var/log/php-fpm.log

修改nginx檔案,找到/usr/local/etc/nginx/目錄下的nginx.conf檔,因為不想每次都要打:8080,決定將端口改為80port,找到listen 8080後改成80
listen       80;

在location /的地方加上index.php
location / {
root  html;
index  index.html index.htm index.php;
}

找到location ~ \.php$,修改fastcgi_param成下列內容
fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;

號外!!我不想使用預設路徑(/usr/local/var/www/)該怎麼做呢?調整nginx.conf就對哩,找到剛剛的location /及location ~ \.php$,將root  html改成
root   /my/path;

接著到/private/etc/php-fpm.d/底下找到www.conf.default改成www.conf。

如果沒有調整root路徑請在/usr/local/var/www/目錄下新建index.php,如果有請在剛剛填上的路徑資料夾中建立index.php檔,檔案內容為
<?php
echo phpinfo();
?>

重啟nginx及php-fpm的服務
sudo php-fpm restart
sudo nginx -s reload

在瀏覽器中輸入http://localhost可以看到phpinfo的內容就代表安裝成功嘍。

[安裝MariaDB]
參考來源:https://hoyangtsai.github.io/posts/2015/12/09/mac-using-homebrew-install-mariadb/

我得承認MariaDB的安裝過程我是真真切切的忘的差不多了…還忘了當初root密碼是設定什麼?到底什麼時候設定過的?所以大家照著上面的網址做唄,怕誤導了大家就不另外說明了…

另外,提供一下萬一真的忘了root密碼可以參考這篇文章哦。
https://blog.goodjack.tw/2018/02/mariadb-root.html

下一篇會說明Laravel及Laravue(Laravel+Vue)的安裝過程哦,這才是我最想紀錄的。