どうも、こんにちは。
「天下一品」を略すと「下品」になるのかな、と心配する緒方です。
ご機嫌いかがでしょうか。
常連さんとの会話中…
緒方「いらっしゃいませー。あ、お久しぶりです」
顧客「こんにちはー。実は最近私もハンドメイド始めたんだよね。だから浅草橋に来る機会が増えちゃって」
緒方「浅草橋は良い資材屋さん多いですからね」
顧客「そう。あと他の作品見て勉強しようと思って」
最近またハンドメイドを始めた人が増えたみたい。
コロナ禍でおうち時間が余った時にもハンドメイド人口が増えたけど、最近ではインフルエンサーの影響で、若い女の子の間で編み物がちょっとしたブームらしい。
たしかに、うちも最近布小物がよく売れてる気がする。
顧客「それでさminne登録してみたんだけど、私のなかなか見てもらえなくてねぇ…」
緒方「minneは登録者多いですもんね」
顧客「本当はネットでも販売したいんだけど、自分でホームページ作ったりするのは出来ないから。やっぱりデザフェスやマルシェにコツコツ出るしかないみたい」
なるほど、ネット通販は買い手側だけじゃなくて売り手側にも需要があるんだな。
うちも通販できたら良いんだけどな。ちょっとminneを調べてみるか。
おぉ、92万人か。いっぱいいるんだなぁ…。
トップページのデザインも見やすくて綺麗だなぁ。
そして作品がピックアップされて、その作品にアクセスが集中するわけか。
でもこれって、92万分の1でランダムにピックアップされてるとしたら
自分の作品が掲載される確率ってかなり低いな…。
何かしらのアルゴリズムでもあるんだろうか。
お客様が言ってた「なかなか見てもらえない」ってのはこの点だろう。
あと「写真を撮るのが苦手」とか「発送するのが大変」とか「見知らぬ人とのDMが怖い」とか言ってたな…。
そうか!これを全部うちで引き受ければいいんだな。
(ぼくのかんがえたさいきょうのサイト)
コミュニティを小規模にすれば自分の作品を見てもらえるし、発送や写真やクレームをこっちで全部引き受ければ既存の通販サイトと差別化できるから、面白いかもしれない。
ただ、一人でできる仕事量には限界があるから、プログラム化して自分自身の負担を減らす必要があるね。
よし!そうと決まったらminneを参考にして、ECサイトをハンドメイドしてみよう!
まずは上から検索窓をつけてみるか。
function.phpをいじって、ウィジェットに検索窓を出します。
//ヘッダーロゴの下に出力をする//
add_filter(‘wp_header_logo_after_open’, ‘add_header_contents’);
function add_header_contents() {
echo ‘<div class=”header-top-row”>’;
// 左側(検索ウィジェット)
echo ‘<div class=”header-search-box”>’;
dynamic_sidebar(‘add-header-contents’);
echo ‘</div>’;
}
// アナウンス枠エリアウィジェット
if (function_exists(‘register_sidebar’)) {
register_sidebar(array(
‘name’ => ‘ヘッダー領域追加コンテンツ’,
‘id’ => ‘add-header-contents’,
‘description’ => ‘ヘッダー領域に追加コンテンツを表示するウィジェットです。’,
‘before_widget’ => ‘<div class=”add-header-contents”>’,
‘after_widget’ => ‘</div>’,
));
}
次にjavascriptを設定。
function init() {
var px_change = 100;
window.addEventListener(‘scroll’, function(e){
if ( $(window).scrollTop() > px_change ) {
$(“#header-container”).addClass(“header-small”);
} else if ( $(“#header-container”).hasClass(“header-small”) ) {
$(“#header-container”).removeClass(“header-small”);
}
});
}
window.onload = init();
最後にcssでデザインの調整。
.header-top-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
margin-top: 10px;
}
/* 左:検索フォーム */
.header-search-box {
flex: 1;
}
すると、
こんな感じ。
おぉ、ちょっとminneっぽくなった気がする!
次はスライド(カルーセル)かぁ。
うちはレンタルボックスだから、作品別のピックアップと棚ごとのピックアップが必要になるなぁ。
こんな感じかな。
スライドは少しゆっくりにしよう。
ついでに直近の納品には「New」が自動でつくようにしてみようかな。
////カルーセルを1枚ずつスライドさせる
//Slickの読み込み
if (!function_exists(‘wp_enqueue_slick’)) :
function wp_enqueue_slick() {
if (function_exists(‘is_carousel_visible’) && is_carousel_visible()) {
wp_enqueue_style(‘slick-theme-style’, get_template_directory_uri() . ‘/plugins/slick/slick-theme.css’);
wp_enqueue_script(‘slick-js’, get_template_directory_uri() . ‘/plugins/slick/slick.min.js’, array(‘jquery’), false, true);
$autoplay = (function_exists(‘is_carousel_autoplay_enable’) && is_carousel_autoplay_enable()) ? ‘autoplay: true,’ : ”;
$autoplaySpeed = (function_exists(‘get_carousel_autoplay_interval’)) ? intval(get_carousel_autoplay_interval()) * 1000 : 3000;
$data = ‘
(function($){
$(“.carousel-content”).slick({
dots: true,
‘ . $autoplay . ‘
autoplaySpeed: ‘ . $autoplaySpeed . ‘,
infinite: true,
slidesToShow: 5,
slidesToScroll: 1,
responsive: [
{ breakpoint: 1240, settings: { slidesToShow: 5, slidesToScroll: 1 } },
{ breakpoint: 1023, settings: { slidesToShow: 5, slidesToScroll: 1 } },
{ breakpoint: 834, settings: { slidesToShow: 2, slidesToScroll: 1 } },
{ breakpoint: 480, settings: { slidesToShow: 2, slidesToScroll: 1 } }
]
});
})(jQuery);
‘;
wp_add_inline_script(‘slick-js’, $data, ‘after’);
}
}
add_action(‘wp_enqueue_scripts’, ‘wp_enqueue_slick’);
endif;
//サムネイルにNew
function add_new_mark_to_thumbnail($html, $post_id, $post_thumbnail_id, $size, $attr) {
// 投稿の公開日を取得
$post_date = get_the_time(‘U’, $post_id);
$now = current_time(‘timestamp’);
// 7日(7 * 24 * 60 * 60秒)以内なら「New!」を表示
$days = 5 * 24 * 60 * 60;
if (($now – $post_date) < $days) {
$html .= ‘<div class=”new-badge”>New!</div>’;
}
return $html;
}
add_filter(‘post_thumbnail_html’, ‘add_new_mark_to_thumbnail’, 10, 5);
おぉ!minne感出てきた!
でもトップページからアクセスした時、更新情報がないと見辛いよな。
よしそれも作ろう。
// 「What’s New」ショートコードを追加(通常投稿のみ対象)
function whats_new_generator() {
$args = array(
‘post_type’ => ‘post’,
‘posts_per_page’ => 10, // 更新状況を10件表示
‘orderby’ => ‘date’,
‘order’ => ‘DESC’,
);
$query = new WP_Query($args);
if (!$query->have_posts()) {
return ‘<p style=”color:red; text-align: center;”>エラー: 投稿が見つかりません。</p>’;
}
$output = ‘<div class=”whats-new-container”>’;
$output .= ‘<div class=”whats-new-title”>What\’s New!</div>’; // タイトルを枠の外に出す
$output .= ‘<div class=”whats-new-box”><div class=”whats-new-scroll”><ul class=”whats-new-list”>’;
while ($query->have_posts()) {
$query->the_post();
$formatted_date = date(‘Y-m-d’, get_post_time(‘U’, true)); // 確実に日付を取得
$output .= ‘<li><a href=”‘ . get_permalink() . ‘” class=”post-title”>’ . get_the_title() . ‘</a> <span class=”post-date”>’ . $formatted_date . ‘</span></li>’;
}
$output .= ‘</ul></div></div></div>’;
wp_reset_postdata();
return $output;
}
add_shortcode(‘whats_new’, ‘whats_new_generator’);
せっかくなのでデザインもうちのサイトっぽくcssで修正。
/************************************
** what’s new
************************************/
.whats-new-container {
text-align: center;
margin-bottom: 40px;
}
.whats-new-title {
font-size: 24px;
font-weight: bold;
color: #ff604b;
margin-bottom: 5px;
}
.whats-new-box {
width: 100%;
max-width: 600px;
margin: 0 auto;
border: 2px solid #ddd;
border-color: #ffc04c;
border-radius: 10px;
padding: 10px;
background: #f9f3ea;
text-align: center;
position: relative;
}
.whats-new-list {
list-style-type: none;
padding: 5px 0;
margin: 0;
}
.whats-new-list li {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 5px 0;
font-size: 13px;
border-bottom: 1px dashed #bea375;
}
.whats-new-list li:last-child {
border-bottom: none;
}
.post-title {
text-decoration: none;
color: #6b3500;
font-size: 16px;
font-weight: bold;
}
.post-date {
color: gray;
font-size: 12px;
}
/* スクロール可能にする */
.whats-new-scroll {
max-height: 250px;
overflow-y: auto;
}
よし、良い感じだね。オリジナリティが出てきた。
あんまり凝りすぎるとキリがないから、この辺にしよう。
さて、本題のネットショップ機能に取り掛かろう。
ショップカート(買い物カゴ)を作るかなぁ。
phpMyAdminを使って情報をデータベース化して、カートに追加できるようにしよ。
<?php
/* Template Name: Cart Page */
session_start();
get_header();
if (!isset($_SESSION[‘cart’])) {
$_SESSION[‘cart’] = [];
}
// 別のデータベースに接続
$db_host = ‘x’;
$db_name = ‘x’;
$db_user = ‘x’;
$db_password = ‘x’;
try {
$dsn = “mysql:host=$db_host;dbname=$db_name;charset=utf8mb4”;
$pdo = new PDO($dsn, $db_user, $db_password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die(“データベース接続エラー: ” . $e->getMessage());
}
if ($_SERVER[“REQUEST_METHOD”] == “POST”) {
$product_id = isset($_POST[‘product_id’]) ? intval($_POST[‘product_id’]) : 0;
$quantity = isset($_POST[‘quantity’]) ? intval($_POST[‘quantity’]) : 1;
$price = isset($_POST[‘price’]) ? floatval($_POST[‘price’]) : 0.0;
$product_name = isset($_POST[‘product_name’]) ? htmlspecialchars($_POST[‘product_name’], ENT_QUOTES, ‘UTF-8’) : ”;
if ($product_id <= 0 || $quantity <= 0 || $quantity > 100) {
die(“不正な入力です。”);
}
$found = false;
foreach ($_SESSION[‘cart’] as &$item) {
if ($item[‘id’] == $product_id) {
$item[‘quantity’] += $quantity;
$found = true;
break;
}
}
if (!$found) {
$_SESSION[‘cart’][] = [
‘id’ => $product_id,
‘name’ => $product_name,
‘price’ => $price,
‘quantity’ => $quantity
];
}
header(“Location: /cart”);
exit();
}
// 商品削除処理(ID指定で削除)
if (isset($_GET[‘remove’])) {
$remove_id = intval($_GET[‘remove’]);
foreach ($_SESSION[‘cart’] as $key => $item) {
if ($item[‘id’] == $remove_id) {
unset($_SESSION[‘cart’][$key]);
break;
}
}
header(“Location: /cart”);
exit();
}
// カート表示
if (empty($_SESSION[‘cart’])) {
echo “<p>カートは空です。</p>”;
} else {
echo “<table border=’1′>”;
echo “<tr><th>商品名</th><th>価格</th><th>数量</th><th>小計</th><th>削除</th></tr>”;
$total = 0;
foreach ($_SESSION[‘cart’] as $key => $item) {
$subtotal = $item[‘price’] * $item[‘quantity’];
$total += $subtotal;
echo “<tr>”;
echo “<td>” . htmlspecialchars($item[‘name’], ENT_QUOTES, ‘UTF-8’) . “</td>”;
echo “<td>” . htmlspecialchars(number_format($item[‘price’]), ENT_QUOTES, ‘UTF-8’) . ” 円</td>”;
echo “<td>” . htmlspecialchars($item[‘quantity’], ENT_QUOTES, ‘UTF-8’) . “</td>”;
echo “<td>” . htmlspecialchars(number_format($subtotal), ENT_QUOTES, ‘UTF-8′) . ” 円</td>”;
echo “<td><a href=’?remove=” . htmlspecialchars($item[‘id’], ENT_QUOTES, ‘UTF-8’) . “‘>削除</a></td>”;
echo “</tr>”;
}
echo “</table>”;
echo “<p>合計金額: <strong>” . number_format($total) . ” 円</strong></p>”;
echo “<p><a href=’/’>買い物を続ける</a></p>”;
echo “<p><a href=’/checkout’>購入手続きへ</a></p>”;
}
get_footer(); ?>
んー、おかしいなぁ…。
カートページからなぜかリダイレクトしないんだよな。原因がわからん。
このあと2週間ほど色々試したのですが、結局解決しなかったのでショップカートは既存の無料プラグイン「WooCommerce」を使うことにした。
さて、普通のネットショップならこのまま使っても問題ないんだけど、うちはレンタルボックスだからサイト用に改造しなくてはならない。
というわけで、まずカスタム投稿用のプラグインをインストール。
そしてfunction.phpに追加で記入します。
これで作家ページをブラウザから見るとこんな具合に。
(すみません。今ちょうど書いてる最中に売れたのでlilyさんのページを使わせてもらっています)
さらに、
作品個別ページではこんな感じに。
作家名はカテゴリ、ジャンルは作品タグとリンクするようになっています。
スクロールすると詳細情報。
そしてリストタブをクリックすると、
カスタムフィールドの情報がそのままテーブルに掲載されます。
そして、管理画面からこれを非公開にすると、
リストの売切一覧に移動するシステムになっています(このプログラムがバグばっかり出て本当に厄介だった)
さて、これを一つ一つ手入力すると時間がかかるので、
googleスプレッドシートからそのまま反映されるように設定します。
まずは納品書記入フォーマットタブを作ります。
関数を使ってデータがcsvインポート用のタブに反映するように設定します。
(このcsvデータはエアレジにそのまま転用できるので一石二鳥です!)
それぞれ見出しのカスタムフィールドと情報がリンクするように設定します。
で、このプラグイン「csvインポータ」をインストールして、
スプレッドシートの情報をアップ。すると、
こんな感じに情報をまとめてアップすることができるようになりました。
在庫情報もここで簡単に変更できるようになっています。
あとは細々した設定を。
// 作品ページカスタマイズ
function change_related_products_text( $translated_text, $text, $domain ) {
if ( $text === ‘Related products’ && $domain === ‘woocommerce’ ) {
$translated_text = ‘▼こちらもおすすめ!▼’;
}
return $translated_text;
}
add_filter( ‘gettext’, ‘change_related_products_text’, 10, 3 );
// カスタムフィールドと標準価格に同数を自動入力
function sync_price_with_custom_field($post_id) {
// 商品ページ以外では実行しない
if (get_post_type($post_id) !== ‘product’) {
return;
}
// price_intaxのカスタムフィールドの値を取得
$custom_price = get_post_meta($post_id, ‘price_intax’, true);
// 値が空でない場合、WooCommerceの価格フィールドに設定
if (!empty($custom_price)) {
update_post_meta($post_id, ‘_regular_price’, $custom_price); // 通常価格
update_post_meta($post_id, ‘_price’, $custom_price); // 販売価格
}
}
add_action(‘save_post’, ‘sync_price_with_custom_field’);
// 関連商品の「カートに追加」ボタンと商品名を非表示にする
function customize_woocommerce_related_products() {
?>
<style>
.related .product .button,
.related .product h2.woocommerce-loop-product__title {
display: none !important;
}
</style>
<?php
}
// 作品名のH2からH4へ変更
function change_product_title_tag() {
remove_action(‘woocommerce_shop_loop_item_title’, ‘woocommerce_template_loop_product_title’, 10);
add_action(‘woocommerce_shop_loop_item_title’, function() {
echo ‘<h4 class=”woocommerce-loop-product__title”>’ . get_the_title() . ‘</h4>’;
}, 10);
}
add_action(‘init’, ‘change_product_title_tag’);
// googleにリッチリザルトを認識させない
add_filter(‘woocommerce_structured_data_product’, ‘__return_empty_array’);
add_action(‘wp_head’, function() {
remove_action(‘wp_footer’, array(WC()->structured_data, ‘generate_json_ld’), 10);
}, 1);
このコードで閲覧者が好きそうな他の作品をおすすめします!
ちなみに
この赤丸のシェアボタンをクリックすれば、
ページごとシェアすることもできますよ。
ぜひ自分の作品や気に入った作品をシェアしてみてください!
さて、実際に買い物カゴに入れてみましょう。
作品に複数のタイプがある場合は、タイプを選択します。
下にスクロールすると追加情報が出てきますので、そこから番号を選んでくださいね。
買い物カゴに入れると他にも選んでいた作品の小計と、送料が含まれた合計が表示されます。
あとは購入手続きに進んでいただければ、買えちゃいます!!
「手数料や固定費がかからないレンタルボックス兼ECサイト」がようやくできました。
あー…大変だった。
でもこのシステムなら全部緒方一人で対応できそうです。
当店へ作品を納品してもらえればこちらでECサイトと連携しますので、ぜひお気軽にどうぞ!
もちろん写真撮影や郵送などの作家様へのお手間はありません。
納品楽しみにお待ちしてます!あと爆買いもお待ちしてますね!