در این آموزش قصد داریم یک پنل سفارشی برای وردپرس درست کنیم که قراره با استفاده از Vue.js یکسری تنظیمات رو که با فرم به مدیر نشون داده میشه، درون دیتابیس ذخیره کنیم.

ساخت پلاگین وردپرسی

در اولین قدم ما به یک پلاگین تازه نیاز داریم. برای این کار یک پوشه در مسیر wp-content\plugins میسازیم. درون پوشه ای که ساختیم یک فایل index.php میسازیم و کد زیر رو برای کانفیگ پلاگینمون درونش قرار میدم:

<?php
/**
 * Plugin Name:  Vue Admin Setting Panel
 * Description:  Save Plugin or Theme Settings with using Vue.js
 * Version:      1.0
 * Author:       Mohsen Jahani
 * Author URI:   https://scriptestan.ir/
 */

بعد از این کار به پنل مدیریت میریم و افزونه رو فعال میکنیم. فعلا اینجا متوقف میشیم تا نیازمندی های اپ Vuejs مون رو تامین کنیم.

راه اندازی یک اپ Vuejs

ابتدا یک پوشه به اسم app درون پوشه ی پلاگینمون ایجاد میکنیم.

یک پوشه دیگه داخل پوشه app با نام src میسازیم که این پوشه قراره محتویات سورس پروژه Vue مون باشه و فایل های زیر رو درونش ایجاد میکنیم:

فایل main.js با محتویات:

import Vue from 'vue'
import App from './App.vue'

new Vue({
	el: '#app-vue-admin-setting-panel',
	render: h => h(App)
})

فایل App.vue با محتویات:

<template>
    <div id="app" class="wrap">
        <h1>Welcome to Vue WordPress Admin Panel</h1>
        <p>scriptestan.ir</p>
    </div>
</template>

<script lang="ts">
    import { Component, Vue } from 'vue-property-decorator';
    @Component({})
    export default class App extends Vue {
        created(){}
    }
</script>

<style lang="scss" scoped>
    h1 {
        color: red;
    }
</style>

همینطور که در دو فایل بالا میبینید، در فایل main.js ما اپمون که همون فایل App.vue هست رو با ساخت یک کلاس Vue در المنتی با آی دی app-vue-admin-setting-panel رندر میکنیم. که حالا اینکه این المنت کجاست در ادامه بهش اشاره میکنیم. (همچنین فعلن این اپ قراره فقط متن خوش آمدید رو به ما نشون بده)

خب برای اینکه ما پروژمون رو که همون پوشه src هست کامپایل کنیم و به صورت یک بسته (bundle.js) دربیاریم باید از وب پک و یکسری کتاب خونه دیگه کمک بگیریم.

برمیگردیم به پوشه app و درونش یک فایل با نام package.json ایجاد کرده و محتویات زیر رو درونش قرار میدیم:

{
  "name": "wordpress_vue_admin_panel",
  "version": "1.0.0",
  "description": "Setting Panel for WordPress with using vue.js",
  "scripts": {
    "build-and-watch": "cross-env NODE_ENV=production webpack --progress --hide-modules --watch",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
  },
  "keywords": [
    "Vue.js", "Wordpress", "Panel", "Scriptestan", "TypeScript"
  ],
  "author": "Mohsen Jahani",
  "license": "ISC",
  "dependencies": {
    "vue": "^2.6.12"
  },
  "devDependencies": {
    "@babel/core": "~7.2",
    "@babel/preset-env": "~7.3",
    "@babel/plugin-syntax-dynamic-import": "~7.2",
    "@babel/polyfill": "~7.2",
    "babel-core": "^6.26.3",
    "babel-eslint": "~10.0",
    "babel-loader": "^8.1.0",
    "cross-env": "^7.0.2",
    "css-loader": "^4.2.1",
    "file-loader": "^6.0.0",
    "mini-css-extract-plugin": "^0.10.0",
    "node-sass": "^4.14.1",
    "sass-loader": "^9.0.3",
    "terser-webpack-plugin": "^4.1.0",
    "vue-loader": "^15.9.3",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.12",
    "vue-class-component": "^7.2.3",
    "vue-property-decorator": "^8.4.2",
    "ts-loader": "^7.0.5",
    "typescript": "^3.9.5",
    "webpack": "^4.44.1",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  }
}

بعد از این کار وارد ترمینال در همین پوشه میشیم و دستور npm install رو اجرا می کنیم تا تمام پکیج های مورد نیازمون دانلود و نصب بشه. (Node.js رو باید نصب داشته باشید)

قبل از اینکه بتونیم از وب پک برای کامپایل سورسمون استفاده کنیم باید تنظیمش کنیم!

یک فایل به اسم webpack.config.js میسازیم و محتویات زیر رو درونش قرار میدیم:

const path = require('path')
const webpack = require('webpack')
const { VueLoaderPlugin } = require('vue-loader')
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const _root = path.resolve(__dirname, '..');

const root = function (args) {
	args = Array.prototype.slice.call(arguments, 0);

	return path.join.apply(path, [ _root ].concat(args));
};


module.exports = {
	entry: './src/main.js',
	output: {
		path: path.resolve(__dirname, './dist'),
		publicPath: '/dist/',
		filename: 'build.js'
	},
	module: {
		rules: [
			{
				test: /\.vue$/,
				loader: 'vue-loader',
				options: {
					esModule: true,
					loaders: {
						'scss': [
							'vue-style-loader',
							'css-loader',
							'sass-loader'
						],
							'sass': [
							'vue-style-loader',
							'css-loader',
							'sass-loader?indentedSyntax'
						]
					}
				}
			},
			{
				test: /\.ts$/,
				loader: 'ts-loader',
				options: {
					appendTsSuffixTo: [/\.vue$/],
					transpileOnly: true
				}
			},
			{
				test: /\.js$/,
				loader: 'babel-loader',
				include: [ root('src') ]
			},
			{
				test: /\.(png|jpg|gif|svg)$/,
					loader: 'file-loader',
				options: {
					name: '[name].[ext]?[hash]'
				}
			},
			{
				test: /\.(s*)[a|c]ss$/,
				use: [
					"vue-style-loader",
					MiniCssExtractPlugin.loader,
					"css-loader",
					"sass-loader"
				]
			}
	]
	},
	resolve: {
			alias: {
				'vue$': 'vue/dist/vue.esm.js',
				'@': root('src')
			},
			extensions: ['*', '.ts', '.js', '.vue', '.json']
		},
	devServer: {
			historyApiFallback: true,
				noInfo: true,
				overlay: true
		},
	performance: {
			hints: false
		},
	devtool: '#eval-source-map',

	plugins: [
		new VueLoaderPlugin(),
		new MiniCssExtractPlugin({
			filename: "[name].css"
		})
	]
}

if (process.env.NODE_ENV === 'production') {
	module.exports.devtool = '#source-map'
	module.exports.optimization = {
		minimize: true,
		minimizer: [new TerserPlugin()],
	};
	// http://vue-loader.vuejs.org/en/workflow/production.html
	module.exports.plugins = (module.exports.plugins || []).concat([
		new webpack.DefinePlugin({
			'process.env': {
				NODE_ENV: '"production"'
			}
		}),

		new webpack.LoaderOptionsPlugin({
			minimize: true
		})
	])
}

کاری که وب پک با تنظیمات بالا میکنه اینه که میاد فایل src/main.js رو که نقطه شروع اپمون هست رو با استفاده از کتابخونه های babel، vue-loader، ts-loader و … کامپایل و درون یک فایل تکی در مسیر dist/bundle.js قرار میده. همچنین همه استایل هایی که در پروژمون باشه رو در فایل dist/main.css قرار میده.

اضافه کردن منوی پنل

برمیگردیم به فایل index.php تا برای وردپرسمون یک منوی مدیریتی تازه بسازیم. برای این کار این کد را اضافه میکنیم:

const PLUGIN_SLUG_NAME = 'vue-admin-setting-panel';

function my_vue_panel_page() {
    ?>
    <div id="app-vue-admin-setting-panel"></div>
    <?php
    wp_enqueue_script( PLUGIN_SLUG_NAME, plugin_dir_url( __FILE__ ). 'app/dist/build.js', array(), time(), true );
    wp_enqueue_style( PLUGIN_SLUG_NAME, plugin_dir_url( __FILE__ ). 'app/dist/main.css', array(), time());
}

function add_menu_item() {
    add_menu_page("Vue.js Admin Panel", "Vue Admin Panel", "manage_options",
        PLUGIN_SLUG_NAME, "my_vue_panel_page", 'dashicons-screenoptions', 99999);
}
add_action("admin_menu", "add_menu_item");

همینطور که میبینید با استفاده از اکشن admin_menu و تابع add_menu_page میتونیم یک منو به پنل مدیریتی وردپرس اضافه کنیم. تابع my_vue_panel_page همون خروجی هست که در منوی پنلمون نمایش داده میشه که من درونش المنتی با آی دی app-vue-admin-setting-panel گذاشتم تا اپ Vue مون درونش رندر بشه و با استفاده از توابع wp_enqueue استایل و اسکریپت کامپایل شده اپمون رو درون این صفحه اضافه میکنم. خب قبل از این که وارد پنل بشیم و خروجی اپمون رو ببینیم باید قبلش یکبار اپ رو کامپایل کنیم تا فایل build.js و main.css ساخته بشه برای اینکار ترمینال رو در پوشه app باز میکنیم و این دستور رو اجرا میکنیم:

npm run build

این یک دستور اجرای npm هست که اسکریپت build درون فایل package.json رو فراخونی میکنه. همچنین برای اینکه بعد از هر تغییر به طور خودکار دستور بیلد اجرا بشه از دستور npm run build-and-watch استفاده میکنیم. (توجه داشته باشید که برای اجرای درست بیلد، باید حتمن فایل های .babelrc و tsconfig.json و shims-vue.d.ts درون پوشه اپ و پروژه باشند، این فایل ها تنظیمات و کانقیگ بابل و تایپ اسکریپت هستند، این فایل ها رو میتونین از مخزن پروژه که درون گیتهاب قرار میدم دریافت کنید – لینک در پایین مقاله)

بعد از اجرای دستور صبر میکنیم مراحل کامپایل به طور کامل انجام شود و بعد وارد پنل وردپرس میشیم و شاهد همچین چیزی خواهیم بود:

نمونه پنل وردپرس با vue.js

تا اینجا بیس کارمون انجام شده و میتونیم حالا اپ رو توسعه بدیم هرجوری که میخوایم !

ذخیره داده در دیتابیس

الان قراره فرمی بسازیم که شامل متن، چکباکس، رادیو باتن و … هست و میخوایم اون هارو درون دیتابیس ذخیره کنیم.

قبل از اینکه فرممون رو بسازیم میخوام که مدل داده ای که قراره توی دیتابیس ذخیره بشه رو به صورت یک کلاس بسازیم. برای اینکار درون سورس پروژمون یک فایل میسازیم با نام Settings.ts و درونش قرار میدیم:

export class Settings {
    public title: string = '';
    public description: string = '';
    public checklist: any = [];
    public color: string = '';
}

این مدلی هست که ما قراره درون دیتابیس ذخیرش کنیم که یکسری متغیر نمونه هست و شما میتونین تغییرش بدین.

حالا بر میگردیم به فایل App.vue و این کلاس رو به طور سراسری تعریف میکنم.

متدی هم برای لیستی از رنگ ها میسازم تا درون فرم ازش استفاده کنیم:

<script lang="ts">
    import { Component, Vue } from 'vue-property-decorator';
    import { Settings } from "./model/Settings";

    @Component({})
    export default class App extends Vue {
        settings: Settings = new Settings();

        created(){}

        save(){}

        get colors() {
            return [
                {label:'red', val:'#f11c2d'},
                {label:'blue', val:'#2465dd'},
                {label:'green', val:'#58d02c'},
                {label:'yellow', val:'#ffbb14'},
                {label:'gray', val:'#a0a0a0'},
            ];
        }
    }
</script>

در بخش template فرممون رو اضافه میکنیم و از مدلمون استفاده میکنیم:

<template>
    <div id="app" class="wrap">
        <h1>Vue.js Admin Panel For WP</h1>

        <h3>Texts</h3>
        <label class="text">
            <span>Title</span>
            <input v-model="settings.title" type="text" placeholder="Title" class="regular-text" />
        </label>

        <label class="text">
            <span>Description</span>
            <textarea v-model="settings.description" placeholder="Description" cols="40" rows="5"></textarea>
        </label>


        <h3>Check list</h3>
        <label v-for="n in 4">
            <input type="checkbox" :value="n" v-model="settings.checklist" />
            <span>Item {{ n }}</span>
        </label>

        <h3>Select</h3>
        <label class="select">
            <span>Color</span>
            <select v-model="settings.color">
                <option value=''>Select a color</option>
                <option v-for="color in colors" :value='color.val'>{{ color.label }}</option>
            </select>
        </label>

        <div v-if="settings.color!==''" class="colored-box" :style="{'background-color':settings.color}"></div>

        <button @click="save" class="button-primary">Save Settings</button>

        <br><br>
        <pre dir="ltr">{{ settings }}</pre>
    </div>
</template>

همچنین کمی استایل اضافه میکنیم:

<style lang="scss" scoped>
    .wrap {
        padding-top: 20px;

        h3 {
            margin-top: 30px;
        }

        label {
            display: block;
            margin-bottom: 10px;
            &amp;.text, &amp;.select {
                margin-bottom: 15px;
                span {
                    display: block;
                    font-weight: 400;
                    margin-bottom: 2px;
                }
            }
        }

        .colored-box {
            width: 200px;
            height: 50px;
            margin-bottom: 15px;
            border-radius: 5px;
        }
    }
</style>

فرممون تقریبن آمادست و فقط کافیه برای دکمه save متدی ایجاد کنیم تا تغییرات را به صورت ای جکس Ajax درون دیتابیس ذخیره کند. قبل از اینکه این متد رو اینجا پیاده کنیم باید یک وب سرویس برای این کار ایجاد کنیم.

برمیگردیم به فایل index.php و کد زیر رو اضافه میکنیم:

/**
 * Rest api for saving setting
 * @param $request
 * @return mixed
 */
function save_settings_func( $request ) {
    $user = wp_get_current_user();
    if (is_super_admin( $user->ID )) {
        return update_option(PLUGIN_SLUG_NAME, $request->get_json_params());
    } else {
        return new WP_Error('not_allowed', null, array('status' => 403,));
    }
}
add_action( 'rest_api_init', function () {
    register_rest_route( PLUGIN_SLUG_NAME, '/save', array(
        'methods' => 'POST',
        'callback' => 'save_settings_func',
    ) );
} );

در کد بالا ما با استفاده از register_rest_route یک وب سرویس اختصاصی به وردپرسمون اضافه میکنیم که در زمان فراخوانی متد save_settings_func صدا میزنه.

در متد save_settings_func ما قبل اینکه کاری انجام بدیم کاربر رو احراز هویت میکنیم تا حتمن ادمین باشد. و اگر بود داده های JSON Body درون رکوئست رو با متد update_option با شناسه ای که درون PLUGIN_SLUG_NAME تعریف کردیم درون دیتابیس ذخیره میکنیم. (متد update_option کارش اینه که یک داده رو بر اساس یک شناسه خاص درون دیتابیس بسازه یا بروزرسانی کنه)

اینکه چطور احراز هویت انجام میشود باید این مستند را ببینید: https://developer.wordpress.org/rest-api/using-the-rest-api/authentication

قبل از نوشتن متد ذخیره سازی، باید به روشی داده هایی که درون دیتابیس ذخیره شده رو به فرم تزریق کنیم. برای اینکار در فایل index.php پلاگینمون تغییراتی را در متد my_vue_panel_page ایجاد میکنیم:

function my_vue_panel_page() {
    $setting_data_option = get_option( PLUGIN_SLUG_NAME );
    ?>
    <script type="text/javascript">
        const vue_wp_api_url = "<?php echo get_site_url().'/wp-json/'.PLUGIN_SLUG_NAME.'/save' ?>";
        const vue_wp_settings_data = <?php
                if($setting_data_option){
                    echo json_encode($setting_data_option);
                }else {
                    echo '{}';
                }
            ?>;
    </script>
    <div id="app-vue-admin-setting-panel"></div>
    <?php

    wp_enqueue_script( PLUGIN_SLUG_NAME, plugin_dir_url( __FILE__ ). 'app/dist/build.js', array(), time(), true );
    wp_enqueue_style( PLUGIN_SLUG_NAME, plugin_dir_url( __FILE__ ). 'app/dist/main.css', array(), time());
}

همینطور که میبینید تگ اسکریپتی تعریف کردم و مقدار داده ای که درون دیتابیس ذخیره کردیم رو با متد get_option گرفتم و به صورت جی سون درون یک متغیر قرار دادم. همچنین آدرس URL وب سرویسمون رو هم درون یک متغیر قرار دادم تا در ایجاد رکوئست ازش استفاده کنیم.

برمیگردیم به فایل App.vue تا از داده های که درون اسکریپت قرار دادیم استفاده کنیم. اول برای ترکیب کردن مقدار داده ذخیره شده و داده فرممون درون متد created اپ به این صورت عمل میکنیم:

export default class App extends Vue {
    settings: Settings = new Settings();

    created(){
        // @ts-ignore
        this.settings = {...this.settings, ...vue_wp_settings_data};
    }
    
    save(){ //... }

    //...
}

و متد save را به این صورت پیاده میکنیم:

save(){
    // @ts-ignore
    jQuery.ajax({
        type: 'POST',
        // @ts-ignore
        url: vue_wp_api_url,
        contentType: 'application/json',
        data: JSON.stringify(this.settings),
        dataType: 'json',
        beforeSend: function ( xhr ) {
            // @ts-ignore
            xhr.setRequestHeader('X-WP-Nonce', wpApiSettings.nonce);
        },
        success: (data) => {
            alert("successfully saved!");
        },
        error:() => {
            alert("failed");
        },
    });
}

بدلیل اینکه کتابخانه جی کوئری به صورت پیشفرض در پنل وردپرس وجود دارد ما هم از جی کوئری برای ایجاد رکوئست Ajax استفاده میکنیم. مقدار nonce که در هدر رکوئست قرار دادیم همان داده ای است که وردپرس به کمک آن، کاربر را احراز هویت میکند. (در مستندی که لینکش را قرار دادم بهش اشاره شده است)

کارمون تموم شد. حالا میتونیم از این پنل استفاده کنیم یا چیز های دیگه ای بهش اضافه کنیم. تصویر نهایی پنل:

پنل مدیریتی وردپرس با Vue.js

در صورتی که آموزش براتون مفید واقع شد، ممنون خواهم شد که به اشتراک بگذارید.

برای دیدن و دریافت سورس کامل پروژه به گیتهاب مراجعه کنید (در سورس کامل نمایش لودینگ در زمان ذخیره رو هم اضافه کردم) :

https://github.com/mohsenjahani/Vue-Wordpress-Admin-Page

یک برنامه نویس علاقمند به تکنولوژی
سه‌شنبه 1 سپتامبر 2020