Vue.jsとPHPを使用して、メールフォームを作ってみます。
基本的な機能としては、
- いたずら防止のために簡単にtokenが一致すれば送信できる
- 項目は、名前・電話番号・メールアドレス・メディアを入力できます。
- 名前・電話番号・メールアドレスが空または正確でない場合、バリデーション表示
- ファイルはドラッグアンドドロップでアップロードで複数送信できます。
- 送信ボタンは名前・電話番号・メールアドレスが正確であれば表示
- 送信ボタンをクリックすると、送信中を表示
- 送信が成功するとフォームを非表示にして、お問い合わせありがとうございます。の文言表示。失敗すると失敗しましたの表示。
- ファイルを後から×ボタンをクリックで削除可能
プレビュー
1、初期画面
2,すべて入力した後の画面
3、送信中の画面
4、送信成功
入力側PHP
<?php
unset($_SESSION['csrf_token']);
session_start();
$toke_byte = openssl_random_pseudo_bytes(16);
$csrf_token = bin2hex($toke_byte);
$_SESSION['csrf_token'] = $csrf_token;
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>メールフォーム</title>
<script src="https://unpkg.com/vue@2.5.17"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<style>
</style>
</head>
<body>
<div id="app">
<h2>お問い合わせ</h2>
<Mail-Form></Mail-Form>
</div>
</body>
</html>
JavaScript
var MailForm = {
data: function () {
return {
name: '',
tel: '',
email: '',
result: '',
param: {},
params: {},
isEnter: false,
files: [],
sendClick:false
}
},
template: `<div>
<form class="contact-form" method="post" action="">
<table>
<tbody>
<tr>
<th>お名前</th>
<td>
<input v-model="name" placeholder="例)山本 太郎">
<p class="error">{{ errors.name }}</p>
</td>
</tr>
<tr>
<th>電話番号</th>
<td>
<input v-model="tel" placeholder="例)08012345678" maxlength="11">
<p class="error">{{ errors.tel }}</p>
</td>
</tr>
<tr>
<th>メールアドレス</th>
<td>
<input v-model="email" placeholder="例)info@example.co.jp">
<p class="error">{{ errors.email }}</p>
</td>
</tr>
</tbody>
</table>
<div
class="drop_area"
@dragenter="dragEnter"
@dragleave="dragLeave"
@dragover.prevent
@drop.prevent="dropFile($event)"
:class="{enter: isEnter}"
>
アップロードエリア
</div>
<div>
<ul>
<li v-for="(file, index) in files":key="index">{{ file.name }}<span @click="FileClear(index)" class="batsu">×</span></li>
</ul>
</div>
<button v-if="valid" type="button" @click="submit">メール送信</button>
<p v-show="sendClick">送信中。。。</p>
</form>
<div v-if="result" class="contact-result">{{ result }}</div>
</div>`,//コンポーネントの要素は一つでなければならない為、divで囲って一つにしています。
computed: {
chackName: function () {
if (!this.name) {
return '名前を入力してください';
}
return '';
},
checkTel: function () {
if (!this.tel) {
return '電話番号を入力してください';
} else if (!this.validTel(this.tel)) {
return '電話番号が正しくありません。';
}
return '';
},
chackEmail: function () {
if (!this.email) {
return 'メールアドレスを入力してください';
} else if (!this.validEmail(this.email)) {
return 'メールアドレスを正しくありません。';
}
return '';
},
errors: function () {
const errors = {
'name': this.chackName,
'tel': this.checkTel,
'email': this.chackEmail,
};
for (var key in errors) {
if (errors[key] === '' || errors[key] === null || errors[key] === undefined) {
delete errors[key];
}
}
return errors;
},
valid: function () {
return !Object.keys(this.errors).length;
}
},
methods: {
submit: async function () {
this.sendClick =true;
const result = await this.send();
valid =false;
if (result === '送信完了') {
this.name = '';
this.tel = '';
this.email = '';
this.files = [];
document.querySelector('.contact-form').style.display = 'none';
this.result = 'お問い合わせありがとうございました。';
}
},
send: async function () {
const url = '【送信側PHPのリンク】';
const token = "<?php echo $csrf_token; ?>";
this.param = {
'name': this.name,
'tel': this.tel,
'email': this.email,
'files': this.files
}
console.log(this.files);
this.params = {
'param': this.param,
'token': token
}
return await axios
.post(url, this.params)
.then(function (res) {
console.log(res.data);
return res.data;
})
.catch(function (error) {
console.log(error);
});
},
validTel: function (tel) {
const re = /^(0[5-9]0[0-9]{8}|0[1-9][1-9][0-9]{7})$/;
return re.test(tel);
},
validEmail: function (email) {
const re = /^[a-zA-Z0-9_+-]+(.[a-zA-Z0-9_+-]+)*@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$/;
return re.test(email);
},
dropFile(event) {
const files = event.target.files ? event.target.files : event.dataTransfer.files;
const reader = new FileReader();
let name = '';
name = files[0].name;
const fileData = {
'name': name
}
reader.onload = (event) => {
preview = event.target.result;
fileData['filePath'] = preview;
};
reader.readAsDataURL(files[0]);
this.files.push(fileData);
this.isEnter = false;
},
dragEnter() {
this.isEnter = true;
},
dragLeave() {
this.isEnter = false;
},
FileClear(index){
this.files.splice(index,1);
}
}
}
new Vue({
el: '#app',
components: {
MailForm: MailForm
}
});
CSS
*{
padding: 0;
margin: 0;
list-style: none;
}
html,
body {
height: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
h2{
text-align: center;
margin-bottom: 50px;
}
.drop_area {
color: gray;
font-weight: bold;
font-size: 1.2em;
display: flex;
justify-content: center;
align-items: center;
width: 500px;
height: 300px;
border: 5px solid gray;
border-radius: 15px;
margin-bottom: 50px;
}
.error {
color: red;
}
button {
appearance: none;
border: 0;
border-radius: 5px;
background: #4676D7;
color: #fff;
padding: 8px 16px;
font-size: 16px;
cursor: pointer;
}
input {
width: 100%;
padding: 10px 15px;
font-size: 16px;
border-radius: 3px;
border: 2px solid #ddd;
box-sizing: border-box;
margin-top: 15px;
}
.contact-form{
text-align: center;
}
.contact-form table{
margin-bottom: 50px;
}
.contact-form th {
text-align: left;
padding-top: 13px;
padding-right: 15px;
}
.batsu{
cursor: pointer;
}
送信側PHP
<?php
mb_language("Japanese");
mb_internal_encoding("UTF-8");
$to = '【送り先のメールアドレス】';
$title = '【タイトル】';
$from = '【送信元メールアドレス】';
//ヘッダー設定
$header = "Content-Type: multipart/mixed;boundary=\"__BOUNDARY__\"\n";
$header .= "Return-Path: " . $to . " \n";
$header .= "From: " . $from ." \n";
$header .= "Sender: " . $from ." \n";
$header .= "Reply-To: " . $to . " \n";
//アクセスを許可するオリジンを指定する場合、指定したいURLを設定
$header .= "Access-Control-Allow-Origin: *";
$json = file_get_contents("php://input");
$inputs = json_decode($json, true);
$bodys = $inputs["param"];
//本文
$content = "--__BOUNDARY__\n";
$content .= "Content-Type: text/plain; charset=\"ISO-2022-JP\"\n\n";
$content .= "名前:". $bodys["name"] ."さんからのお問い合わせ". "\n";
$content .= "電話番号:". $bodys["tel"] . "\n";
$content .= "メールアドレス:". $bodys["email"] . "\n";
$content .= "--__BOUNDARY__\n";
foreach($bodys["files"] as $file){
$content .= "Content-Type: application/octet-stream; name=\"{$file['name']}\"\n";
$content .= "Content-Disposition: attachment; filename=\"{$file['name']}\"\n";
$content .= "Content-Transfer-Encoding: base64\n";
$content .= "\n";
$content .= chunk_split(base64_encode(file_get_contents($file['filePath'])));
$content .= "--__BOUNDARY__\n";
}
session_start();
if (isset($inputs["token"]) && $inputs["token"] === $_SESSION['csrf_token']) {
if (mb_send_mail($to, $title, $content, $header)) {
echo "送信完了";
} else {
echo "送信に失敗しました。";
}
} else {
echo "不正なリクエストです";
}
?>