問題描述
如何在我博客的前端顯示 WordPress 用户註冊表單 (出現在 「www.mywebsite.com/wp-register.php」 頁面中的表單)?
我已經定製了註冊表。但是不知道如何在前端頁面中調用該表單。任何支持都將是非常有幫助的。
提前致謝。 🙂
最佳解決方案
該過程涉及兩個步驟:
- 顯示前端形式
- 保存提交的數據
有三種不同的方法來到我的頭腦來展示前端:
- 使用 built-in 註冊表單,編輯樣式等使其更多”frontend like”
- 使用 WordPress 頁面/帖子,並使用短代碼顯示錶單
- 使用不與任何頁面/帖子相關聯的專用模板,但由特定網址調用
對於這個答案,我會用後者。原因是:
- 使用 built-in 註冊表單可以是一個好主意,深度定製可以很難使用 built-in 形式,如果還想自定義表單字段,疼痛增加
- 使用一個 WordPress 頁面與一個短代碼組合,不是那麼可靠,而且我認為,shorcodes 不應該用於功能,只是為了格式化等
1:構建 URL
我們所有人都知道,WordPress 網站的默認註冊表通常是垃圾郵件發送者的目標。使用自定義網址是解決這個問題的一個幫助。另外我還要使用一個變量 url,即註冊表單不應該總是相同的,這樣可以使垃圾郵件發送者的生活變得更加困難。訣竅是使用 nonce 在 URL 中完成的:
/**
* Generate dynamic registration url
*/
function custom_registration_url() {
$nonce = urlencode( wp_create_nonce( 'registration_url' ) );
return home_url( $nonce );
}
/**
* Generate dynamic registration link
*/
function custom_registration_link() {
$format = '<a href="%s">%s</a>';
printf(
$format,
custom_registration_url(), __( 'Register', 'custom_reg_form' )
);
}
使用這個功能很容易在模板中顯示註冊表單的鏈接,即使它是動態的。
2:識別 Custom_RegCustom_Reg 類的 URL,第一個存根
現在我們需要識別 url 。對於傾訴我會開始寫一個類,這將在後面的答案中完成:
<?php
// don't save, just a stub
namespace Custom_Reg;
class Custom_Reg {
function checkUrl() {
$url_part = $this->getUrl();
$nonce = urlencode( wp_create_nonce( 'registration_url' ) );
if ( ( $url_part === $nonce ) ) {
// do nothing if registration is not allowed or user logged
if ( is_user_logged_in() || ! get_option('users_can_register') ) {
wp_safe_redirect( home_url() );
exit();
}
return TRUE;
}
}
protected function getUrl() {
$home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' );
$relative = trim(str_replace($home_path, '', esc_url(add_query_arg(array()))), '/');
$parts = explode( '/', $relative );
if ( ! empty( $parts ) && ! isset( $parts[1] ) ) {
return $parts[0];
}
}
}
該函數查看 home_url()後的 url 的第一部分,如果它與我們的隨機數匹配,則返回 TRUE 。此功能將用於檢查我們的請求並執行所需的操作以顯示我們的表單。
3:Custom_RegForm 類
我現在將編寫一個類,它將負責生成表單標記。我還將使用它來存儲應該用於顯示錶單的模板文件路徑的屬性。
<?php
// file: Form.php
namespace Custom_Reg;
class Form {
protected $fields;
protected $verb = 'POST';
protected $template;
protected $form;
public function __construct() {
$this->fields = new ArrayIterator();
}
public function create() {
do_action( 'custom_reg_form_create', $this );
$form = $this->open();
$it = $this->getFields();
$it->rewind();
while( $it->valid() ) {
$field = $it->current();
if ( ! $field instanceof FieldInterface ) {
throw new DomainException( "Invalid field" );
}
$form .= $field->create() . PHP_EOL;
$it->next();
}
do_action( 'custom_reg_form_after_fields', $this );
$form .= $this->close();
$this->form = $form;
add_action( 'custom_registration_form', array( $this, 'output' ), 0 );
}
public function output() {
unset( $GLOBALS['wp_filters']['custom_registration_form'] );
if ( ! empty( $this->form ) ) {
echo $this->form;
}
}
public function getTemplate() {
return $this->template;
}
public function setTemplate( $template ) {
if ( ! is_string( $template ) ) {
throw new InvalidArgumentException( "Invalid template" );
}
$this->template = $template;
}
public function addField( FieldInterface $field ) {
$hook = 'custom_reg_form_create';
if ( did_action( $hook ) && current_filter() !== $hook ) {
throw new BadMethodCallException( "Add fields before {$hook} is fired" );
}
$this->getFields()->append( $field );
}
public function getFields() {
return $this->fields;
}
public function getVerb() {
return $this->verb;
}
public function setVerb( $verb ) {
if ( ! is_string( $verb) ) {
throw new InvalidArgumentException( "Invalid verb" );
}
$verb = strtoupper($verb);
if ( in_array($verb, array( 'GET', 'POST' ) ) ) $this->verb = $verb;
}
protected function open() {
$out = sprintf( '<form id="custom_reg_form" method="%s">', $this->verb ) . PHP_EOL;
$nonce = '<input type="hidden" name="_n" value="%s" />';
$out .= sprintf( $nonce, wp_create_nonce( 'custom_reg_form_nonce' ) ) . PHP_EOL;
$identity = '<input type="hidden" name="custom_reg_form" value="%s" />';
$out .= sprintf( $identity, __CLASS__ ) . PHP_EOL;
return $out;
}
protected function close() {
$submit = __('Register', 'custom_reg_form');
$out = sprintf( '<input type="submit" value="%s" />', $submit );
$out .= '</form>';
return $out;
}
}
類生成表單標記循環所有添加的字段,每個字段都調用 create 方法。每個字段必須是 Custom_RegFieldInterface 的實例。添加一個附加的隱藏字段用於隨機驗證。表單方法默認為’POST’,但可以使用 setVerb 方法設置為’GET’ 。創建後,標記保存在 output()方法回顯的 $form 對象屬性中,掛接到'custom_registration_form'鈎子中:在表單模板中,只需調用 do_action( 'custom_registration_form' )即可輸出表單。
4:默認模板
正如我所説,表單的模板可以很容易地覆蓋,但是我們需要一個基本的模板作為後備。我會在這裏寫一個非常粗略的模板,比一個真正的模板更多的概念證明。
<?php
// file: default_form_template.php
get_header();
global $custom_reg_form_done, $custom_reg_form_error;
if ( isset( $custom_reg_form_done ) && $custom_reg_form_done ) {
echo '<p class="success">';
_e(
'Thank you, your registration was submitted, check your email.',
'custom_reg_form'
);
echo '</p>';
} else {
if ( $custom_reg_form_error ) {
echo '<p class="error">' . $custom_reg_form_error . '</p>';
}
do_action( 'custom_registration_form' );
}
get_footer();
5:Custom_RegFieldInterface 接口
每個字段應該是一個實現以下接口的對象
<?php
// file: FieldInterface.php
namespace Custom_Reg;
interface FieldInterface {
/**
* Return the field id, used to name the request value and for the 'name' param of
* html input field
*/
public function getId();
/**
* Return the filter constant that must be used with
* filter_input so get the value from request
*/
public function getFilter();
/**
* Return true if the used value passed as argument should be accepted, false if not
*/
public function isValid( $value = NULL );
/**
* Return true if field is required, false if not
*/
public function isRequired();
/**
* Return the field input markup. The 'name' param must be output
* according to getId()
*/
public function create( $value = '');
}
我認為評論解釋了實現這個界面應該做什麼類。
6:添加一些字段
現在我們需要一些領域。我們可以創建一個名為’fields.php’ 的文件,我們定義了這些字段類:
<?php
// file: fields.php
namespace Custom_Reg;
abstract class BaseField implements FieldInterface {
protected function getType() {
return isset( $this->type ) ? $this->type : 'text';
}
protected function getClass() {
$type = $this->getType();
if ( ! empty($type) ) return "{$type}-field";
}
public function getFilter() {
return FILTER_SANITIZE_STRING;
}
public function isRequired() {
return isset( $this->required ) ? $this->required : FALSE;
}
public function isValid( $value = NULL ) {
if ( $this->isRequired() ) {
return $value != '';
}
return TRUE;
}
public function create( $value = '' ) {
$label = '<p><label>' . $this->getLabel() . '</label>';
$format = '<input type="%s" name="%s" value="%s" class="%s"%s /></p>';
$required = $this->isRequired() ? ' required' : '';
return $label . sprintf(
$format,
$this->getType(), $this->getId(), $value, $this->getClass(), $required
);
}
abstract function getLabel();
}
class FullName extends BaseField {
protected $required = TRUE;
public function getID() {
return 'fullname';
}
public function getLabel() {
return __( 'Full Name', 'custom_reg_form' );
}
}
class Login extends BaseField {
protected $required = TRUE;
public function getID() {
return 'login';
}
public function getLabel() {
return __( 'Username', 'custom_reg_form' );
}
}
class Email extends BaseField {
protected $type = 'email';
public function getID() {
return 'email';
}
public function getLabel() {
return __( 'Email', 'custom_reg_form' );
}
public function isValid( $value = NULL ) {
return ! empty( $value ) && filter_var( $value, FILTER_VALIDATE_EMAIL );
}
}
class Country extends BaseField {
protected $required = FALSE;
public function getID() {
return 'country';
}
public function getLabel() {
return __( 'Country', 'custom_reg_form' );
}
}
我使用基類來定義默認接口實現,但是可以添加非常自定義的字段直接實現接口或擴展基類並覆蓋一些方法。
在這一點上,我們有一切顯示窗體,現在我們需要一些驗證和保存字段的東西。
7:Custom_RegSaver 類
<?php
// file: Saver.php
namespace Custom_Reg;
class Saver {
protected $fields;
protected $user = array( 'user_login' => NULL, 'user_email' => NULL );
protected $meta = array();
protected $error;
public function setFields( ArrayIterator $fields ) {
$this->fields = $fields;
}
/**
* validate all the fields
*/
public function validate() {
// if registration is not allowed return false
if ( ! get_option('users_can_register') ) return FALSE;
// if no fields are setted return FALSE
if ( ! $this->getFields() instanceof ArrayIterator ) return FALSE;
// first check nonce
$nonce = $this->getValue( '_n' );
if ( $nonce !== wp_create_nonce( 'custom_reg_form_nonce' ) ) return FALSE;
// then check all fields
$it = $this->getFields();
while( $it->valid() ) {
$field = $it->current();
$key = $field->getID();
if ( ! $field instanceof FieldInterface ) {
throw new DomainException( "Invalid field" );
}
$value = $this->getValue( $key, $field->getFilter() );
if ( $field->isRequired() && empty($value) ) {
$this->error = sprintf( __('%s is required', 'custom_reg_form' ), $key );
return FALSE;
}
if ( ! $field->isValid( $value ) ) {
$this->error = sprintf( __('%s is not valid', 'custom_reg_form' ), $key );
return FALSE;
}
if ( in_array( "user_{$key}", array_keys($this->user) ) ) {
$this->user["user_{$key}"] = $value;
} else {
$this->meta[$key] = $value;
}
$it->next();
}
return TRUE;
}
/**
* Save the user using core register_new_user that handle username and email check
* and also sending email to new user
* in addition save all other custom data in user meta
*
* @see register_new_user()
*/
public function save() {
// if registration is not allowed return false
if ( ! get_option('users_can_register') ) return FALSE;
// check mandatory fields
if ( ! isset($this->user['user_login']) || ! isset($this->user['user_email']) ) {
return false;
}
$user = register_new_user( $this->user['user_login'], $this->user['user_email'] );
if ( is_numeric($user) ) {
if ( ! update_user_meta( $user, 'custom_data', $this->meta ) ) {
wp_delete_user($user);
return FALSE;
}
return TRUE;
} elseif ( is_wp_error( $user ) ) {
$this->error = $user->get_error_message();
}
return FALSE;
}
public function getValue( $var, $filter = FILTER_SANITIZE_STRING ) {
if ( ! is_string($var) ) {
throw new InvalidArgumentException( "Invalid value" );
}
$method = strtoupper( filter_input( INPUT_SERVER, 'REQUEST_METHOD' ) );
$type = $method === 'GET' ? INPUT_GET : INPUT_POST;
$val = filter_input( $type, $var, $filter );
return $val;
}
public function getFields() {
return $this->fields;
}
public function getErrorMessage() {
return $this->error;
}
}
該類有兩個主要方法,一個 (validate) 循環字段,驗證它們並將好的數據保存到數組中,第二個 (save) 保存數據庫中的所有數據,並通過電子郵件將密碼發送給新用户。
8:使用定義的類:完成 Custom_Reg 類
現在我們可以在 Custom_Reg 類上再次工作,添加對象定義的”glues” 的方法,使它們工作
<?php
// file Custom_Reg.php
namespace Custom_Reg;
class Custom_Reg {
protected $form;
protected $saver;
function __construct( Form $form, Saver $saver ) {
$this->form = $form;
$this->saver = $saver;
}
/**
* Check if the url to recognize is the one for the registration form page
*/
function checkUrl() {
$url_part = $this->getUrl();
$nonce = urlencode( wp_create_nonce( 'registration_url' ) );
if ( ( $url_part === $nonce ) ) {
// do nothing if registration is not allowed or user logged
if ( is_user_logged_in() || ! get_option('users_can_register') ) {
wp_safe_redirect( home_url() );
exit();
}
return TRUE;
}
}
/**
* Init the form, if submitted validate and save, if not just display it
*/
function init() {
if ( $this->checkUrl() !== TRUE ) return;
do_action( 'custom_reg_form_init', $this->form );
if ( $this->isSubmitted() ) {
$this->save();
}
// don't need to create form if already saved
if ( ! isset( $custom_reg_form_done ) || ! $custom_reg_form_done ) {
$this->form->create();
}
load_template( $this->getTemplate() );
exit();
}
protected function save() {
global $custom_reg_form_error;
$this->saver->setFields( $this->form->getFields() );
if ( $this->saver->validate() === TRUE ) { // validate?
if ( $this->saver->save() ) { // saved?
global $custom_reg_form_done;
$custom_reg_form_done = TRUE;
} else { // saving error
$err = $this->saver->getErrorMessage();
$custom_reg_form_error = $err ? : __( 'Error on save.', 'custom_reg_form' );
}
} else { // validation error
$custom_reg_form_error = $this->saver->getErrorMessage();
}
}
protected function isSubmitted() {
$type = $this->form->getVerb() === 'GET' ? INPUT_GET : INPUT_POST;
$sub = filter_input( $type, 'custom_reg_form', FILTER_SANITIZE_STRING );
return ( ! empty( $sub ) && $sub === get_class( $this->form ) );
}
protected function getTemplate() {
$base = $this->form->getTemplate() ? : FALSE;
$template = FALSE;
$default = dirname( __FILE__ ) . '/default_form_template.php';
if ( ! empty( $base ) ) {
$template = locate_template( $base );
}
return $template ? : $default;
}
protected function getUrl() {
$home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' );
$relative = trim( str_replace( $home_path, '', add_query_arg( array() ) ), '/' );
$parts = explode( '/', $relative );
if ( ! empty( $parts ) && ! isset( $parts[1] ) ) {
return $parts[0];
}
}
}
該類的構造函數接受 Form 和 Saver 之一的實例。
init()方法 (使用 checkUrl()) 查看 home_url()後的 url 的第一部分,如果與正確的隨機數匹配,則檢查表單是否已經提交,如果使用 Saver 對象,則驗證並保存用户數據,否則只打印表單。
init()方法也觸發動作鈎'custom_reg_form_init'傳遞表單實例作為參數:該鈎子應該用於添加字段,設置自定義模板以及自定義表單方法。
9:把東西放在一起
現在我們需要編寫主要的插件文件,我們可以在這裏
- 需要所有的文件
- 加載文本域
- 啓動整個過程使用實例化
Custom_Reg類,並使用合理的早期鈎來調用init()方法 - 使用’custom_reg_form_init’ 添加字段來形成類
所以:
<?php
/**
* Plugin Name: Custom Registration Form
* Description: Just a rough plugin example to answer a WPSE question
* Plugin URI: https://wordpress.stackexchange.com/questions/10309/
* Author: G. M.
* Author URI: https://wordpress.stackexchange.com/users/35541/g-m
*
*/
if ( is_admin() ) return; // this plugin is all about frontend
load_plugin_textdomain(
'custom_reg_form',
FALSE,
plugin_dir_path( __FILE__ ) . 'langs'
);
require_once plugin_dir_path( __FILE__ ) . 'FieldInterface.php';
require_once plugin_dir_path( __FILE__ ) . 'fields.php';
require_once plugin_dir_path( __FILE__ ) . 'Form.php';
require_once plugin_dir_path( __FILE__ ) . 'Saver.php';
require_once plugin_dir_path( __FILE__ ) . 'CustomReg.php';
/**
* Generate dynamic registration url
*/
function custom_registration_url() {
$nonce = urlencode( wp_create_nonce( 'registration_url' ) );
return home_url( $nonce );
}
/**
* Generate dynamic registration link
*/
function custom_registration_link() {
$format = '<a href="%s">%s</a>';
printf(
$format,
custom_registration_url(), __( 'Register', 'custom_reg_form' )
);
}
/**
* Setup, show and save the form
*/
add_action( 'wp_loaded', function() {
try {
$form = new Custom_RegForm;
$saver = new Custom_RegSaver;
$custom_reg = new Custom_RegCustom_Reg( $form, $saver );
$custom_reg->init();
} catch ( Exception $e ) {
if ( defined('WP_DEBUG') && WP_DEBUG ) {
$msg = 'Exception on ' . __FUNCTION__;
$msg .= ', Type: ' . get_class( $e ) . ', Message: ';
$msg .= $e->getMessage() ? : 'Unknown error';
error_log( $msg );
}
wp_safe_redirect( home_url() );
}
}, 0 );
/**
* Add fields to form
*/
add_action( 'custom_reg_form_init', function( $form ) {
$classes = array(
'Custom_RegFullName',
'Custom_RegLogin',
'Custom_RegEmail',
'Custom_RegCountry'
);
foreach ( $classes as $class ) {
$form->addField( new $class );
}
}, 1 );
10:缺少任務
現在永遠是完美的。我們只需要自定義模板,可能在我們的主題中添加一個自定義模板文件。
我們可以通過這種方式將特定的樣式和腳本添加到自定義註冊頁面
add_action( 'wp_enqueue_scripts', function() {
// if not on custom registration form do nothing
if ( did_action('custom_reg_form_init') ) {
wp_enqueue_style( ... );
wp_enqueue_script( ... );
}
});
使用這種方法,我們可以排隊一些 js 腳本來處理客户端驗證,例如 this one 。使腳本工作所需的標記可以輕鬆地處理編輯 Custom_RegBaseField 類。
如果我們要自定義註冊郵件,我們可以使用 standard method 並將定製數據保存在 meta 上,我們可以在電子郵件中使用它們。
我們可能要實現的最後一個任務是阻止請求默認註冊表單,如下所示:
add_action( 'login_form_register', function() { exit(); } );
所有文件都可以在 Gist here 中找到。
次佳解決方案
TLDR; 將以下表單放入您的主題中,name 和 id 屬性很重要:
<form action="<?php echo site_url('wp-login.php?action=register', 'login_post') ?>" method="post">
<input type="text" name="user_login" value="Username" id="user_login" class="input" />
<input type="text" name="user_email" value="E-Mail" id="user_email" class="input" />
<?php do_action('register_form'); ?>
<input type="submit" value="Register" id="register" />
</form>
我在 Making a fancy WordPress Register Form from scratch 上發現了一個很好的 Tutsplus 文章。這花了相當多的時間在樣式的形式,但以下相當簡單的部分在所需的 wordpress 代碼:
Step 4. WordPress
There is nothing fancy here; we only require two WordPress snippets, hidden within the wp-login.php file.
The first snippet:
<?php echo site_url('wp-login.php?action=register', 'login_post') ?>And:
<?php do_action('register_form'); ?>
編輯:我從文章中添加了額外的最後一位,以説明放置上述代碼片段的位置 – 它只是一個表單,所以它可以在任何頁面模板或側邊欄中進行,或者從中創建一個短代碼。重要的部分是 form,其中包含以上代碼段和重要的必填字段。
The final code should look like so:
<div style="display:none"> <!-- Registration --> <div id="register-form"> <div> <h1>Register your Account</h1> <span>Sign Up with us and Enjoy!</span> </div> <form action="<?php echo site_url('wp-login.php?action=register', 'login_post') ?>" method="post"> <input type="text" value="Username" id="user_login" /> <input type="text" value="E-Mail" id="user_email" /> <?php do_action('register_form'); ?> <input type="submit" value="Register" id="register" /> <hr /> <p>A password will be e-mailed to you.</p> </form> </div> </div><!-- /Registration -->Please note that it’s really important, and necessary, to have
user_loginas anameand as anidattribute in your text input; the same is true for the email input. Otherwise, it won’t work.And with that, we’re done!
第三種解決方案
這個 Article 提供了一個關於如何創建自己的前端註冊/登錄/恢復密碼錶單的 greate 教程。
或者如果您正在尋找一個插件,那麼我以前使用過這些插件,可以推薦它們:
第四種方案
前段時間我做了一個網站,正在前端顯示一個定製的註冊表單。這個網站不再活着,但這裏是一些截圖。
以下是我遵循的步驟:
1) 激活所有訪問者通過設置> 請求新帳户的可能性。一般> 會員選項。註冊頁面現在出現在 URL /wp-login.php?action=register
2) 自定義註冊表單,使其看起來像您的網站 front-end 。這更棘手,取決於您使用的主題。
這是一個例子二十三:
// include theme scripts and styles on the login/registration page
add_action('login_enqueue_scripts', 'twentythirteen_scripts_styles');
// remove admin style on the login/registration page
add_filter( 'style_loader_tag', 'user16975_remove_admin_css', 10, 2);
function user16975_remove_admin_css($tag, $handle){
if ( did_action('login_init')
&& ($handle == 'wp-admin' || $handle == 'buttons' || $handle == 'colors-fresh'))
return "";
else return $tag;
}
// display front-end header and footer on the login/registration page
add_action('login_footer', 'user16975_integrate_login');
function user16975_integrate_login(){
?><div id="page" class="hfeed site">
<header id="masthead" class="site-header" role="banner">
<a class="home-link" href="<?php%20echo%20esc_url(%20home_url(%20'/'%20)%20);%20?>" title="<?php echo esc_attr( get_bloginfo( 'name', 'display' ) ); ?>" rel="home">
<h1 class="site-title"><?php bloginfo( 'name' ); ?></h1>
<h2 class="site-description"><?php bloginfo( 'description' ); ?></h2>
</a>
<div id="navbar" class="navbar">
<nav id="site-navigation" class="navigation main-navigation" role="navigation">
<h3 class="menu-toggle"><?php _e( 'Menu', 'twentythirteen' ); ?></h3>
<a class="screen-reader-text skip-link" href="#content" title="<?php esc_attr_e( 'Skip to content', 'twentythirteen' ); ?>"><?php _e( 'Skip to content', 'twentythirteen' ); ?></a>
<?php wp_nav_menu( array( 'theme_location' => 'primary', 'menu_class' => 'nav-menu' ) ); ?>
<?php get_search_form(); ?>
</nav><!-- #site-navigation -->
</div><!-- #navbar -->
</header><!-- #masthead -->
<div id="main" class="site-main">
<?php get_footer(); ?>
<script>
// move the login form into the page main content area
jQuery('#main').append(jQuery('#login'));
</script>
<?php
}
然後修改主題樣式表,使表單按需要顯示。
3) 您可以通過調整顯示的消息進一步修改表單:
add_filter('login_message', 'user16975_login_message');
function user16975_login_message($message){
if(strpos($message, 'register') !== false){
$message = 'custom register message';
} else {
$message = 'custom login message';
}
return $message;
}
add_action('login_form', 'user16975_login_message2');
function user16975_login_message2(){
echo 'another custom login message';
}
add_action('register_form', 'user16975_tweak_form');
function user16975_tweak_form(){
echo 'another custom register message';
}
4) 如果您需要 front-end 註冊表,您可能不希望註冊用户在 log-in 時看到後端。
add_filter('user_has_cap', 'user16975_refine_role', 10, 3);
function user16975_refine_role($allcaps, $cap, $args){
global $pagenow;
$user = wp_get_current_user();
if($user->ID != 0 && $user->roles[0] == 'subscriber' && is_admin()){
// deny access to WP backend
$allcaps['read'] = false;
}
return $allcaps;
}
add_action('admin_page_access_denied', 'user16975_redirect_dashbord');
function user16975_redirect_dashbord(){
wp_redirect(home_url());
die();
}
有很多步驟,但結果是在這裏!
參考文獻
注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。


