【QR】Google Apps Scriptを用いたQRコード登録システムについて【GAS】
追記:8/29 更新しました。
参考サイト様
https://www.webprofessional.jp/create-qr-code-reader-mobile-website/
1:目的
社内では現在在庫管理+棚卸業務が目視確認&メモによるもので効率が悪い。そこでGoogle apps script(以下gas)を用いて javascript、html、cssによるwebアプリケーションのフォームを作成、qrコードにてスマホで業務を可能にし効率化を図る。
2:Gasにおけるのシートの準備
シート作成します。以下3つ。ボタンは作らなくてもOKです。
ホームシート
検索データを蓄積するシートです。
ボタンは各追加機能です。要望があれば追記します。
QRコードシート
在庫品の情報を管理するシートです。ここでQRコードを生成します。
QRコードを生成するための計算式は以下の通りです。
=image("http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=" & "生成したいセル番号")
ここでは連番のセル(A列)としています。
履歴
こちらで棚卸したデータを蓄積します。
3:プログラム詳細
以下、プログラム詳細です。
各プログラムごとファイルを追加、名前を変更してください。
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<?!= HtmlService.createHtmlOutputFromFile('css').getContent(); ?>
</head>
<body>
<h2 class = "title">㈱テスト 棚卸システム</h2>
<div class="outer">
<div class="inner">
<input type=text id = "QRtext" size=25 placeholder="タップでQR読込→" class=qrcode-text readonly="readonly"><label class=qrcode-text-btn><input type=file accept="image/*" capture=environment onclick="return showQRIntro();" onchange="openQRCamera(this);" tabindex=-1></label>
<!--<input type=button value="" disabled>-->
<?!= HtmlService.createHtmlOutputFromFile('js2').getContent(); ?>
<script src="https://rawgit.com/sitepoint-editors/jsqrcode/master/src/qr_packed.js"></script>
<button class="btn-gradation" onclick="changeMessage()"> 読込 </button>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<?!= HtmlService.createHtmlOutputFromFile('js').getContent(); ?>
<br>
<h3 class = Hyooudai1 >品番</h3>
<br>
<input type="text" id="hinban" class = "inputBox" size=33 readonly="readonly">
<br>
<h3 class = Hyooudai1 >品名</h3>
<br>
<input type="text" id="hinmei" class = "inputBox" size=33 readonly="readonly">
<br>
<h3 class = Hyooudai1 >品目</h3>
<br>
<input type="text" id="hinmoku" class = "inputBox" size=33 readonly="readonly">
<br>
<h3 class = Hyooudai1 >数量</h3>
<br>
<input type="tel" name="sample" class = "inputBox" id="suuryou" size=33 maxlength="6"/>
<br>
<h3 class = Hyooudai1 >備考</h3>
<br>
<input type="text" id="bikou" class = "inputBox" size=33 >
<br>
<br>
<button class="btn-gradation" onclick="InputSpredSheet()"> 登録 </button>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<?!= HtmlService.createHtmlOutputFromFile('js').getContent(); ?>
</div>
</div>
</body>
</html>
css.html
<style>
.outer{
text-align: center;
}
.inner{
display: inline-block;
text-align: left;
}
.Hyooudai1{
line-height: 0.0;
}
.title {
font-size: 20px;
font-family: "YuGothic","Yu Gothic","Meiryo","ヒラギノ角ゴ","sans-serif";
}
h2 {
padding: 1rem 3rem;
-webkit-transform: skew(-15deg);
transform: skew(-15deg);
color: #fff;
background-image: -webkit-gradient(linear, left top, right top, from(#209cff), to(#68e0cf));
background-image: -webkit-linear-gradient(left, #209cff 0%, #68e0cf 100%);
background-image: linear-gradient(to right, #209cff 0%, #68e0cf 100%);
}
h3 {
color: #505050;/*文字色*/
padding: 0.5em;/*文字周りの余白*/
display: inline-block;/*おまじない*/
line-height: 1.3;/*行高*/
background: #dbebf8;/*背景色*/
vertical-align: middle;
border-radius: 25px 0px 0px 25px;/*左側の角を丸く*/
}
h3:before {
content: '●';
color: white;
margin-right: 8px;
}
*:not(br){
line-height: 1;
/* ...その他リセット項目... */
}
/* ここから下がボタンのCSS */
.btn-gradation {
display: inline-block;
text-align: left;
background-image: linear-gradient(#eaeaea 0%, #c6c6c6 100%);
box-shadow: inset 0 2px 0 rgba(255,255,255,0.5), 0 2px 2px rgba(0, 0, 0, 0.19);
color: #000000;
font-size: 16px;
text-decoration: none;
font-weight: bold;
text-shadow: 1px 1px 1px rgba(255, 255, 255, 1);
padding: 12px 24px;
border-radius: 4px;
font-size: 16px;
}
.btn-gradation:hover {
opacity: 0.8;
}
.inputBox{
width: 360px;
padding: 5px 8px;
border-radius: 6px;
border-top: 1px solid #aaa;
border-left: 1px solid #aaa;
border-right: 2px solid #aaa;
border-bottom: 2px solid #aaa;
background-image: none;
background-color: #ddd;
font-size: 16px;
}
body, input {font-size:14pt}
input, label {vertical-align:middle}
.qrcode-text {padding-right:1.7em; margin-right:0}
.qrcode-text-btn {display:inline-block; background:url(//dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/07/1499401426qr_icon.svg) 50% 50% no-repeat; height:1em; width:1.7em; margin-left:-1.7em; cursor:pointer}
.qrcode-text-btn > input[type=file] {position:absolute; overflow:hidden; width:1px; height:1px; opacity:0}
</style>
js.html
<script>
function changeMessage() {
const QRtextbox = document.getElementById("QRtext");
const QRvalue = QRtextbox.value;
google.script.run.withSuccessHandler(function(data){
var csvArray = data.split(',');
document.getElementById("hinban").value = csvArray[0];
document.getElementById("hinmei").value = csvArray[1];
document.getElementById("hinmoku").value = csvArray[2];
}).SheetSarch(QRvalue);
}
function InputSpredSheet(){
const QRtextbox = document.getElementById("QRtext");
const QRvalue = QRtextbox.value;
var hinbanTextbox = document.getElementById("hinban").value;
var hinmeiTextbox = document.getElementById("hinmei").value;
var hinmokuTextbox = document.getElementById("hinmoku").value;
var suuryouTextbox = document.getElementById("suuryou").value;
var bikouTextbox = document.getElementById("bikou").value;
var useragent = window.navigator.userAgent;
var dt = new Date();
var y = dt.getFullYear();
var m = ("00" + (dt.getMonth()+1)).slice(-2);
var d = ("00" + dt.getDate()).slice(-2);
var resultTodat = y + "/" + m + "/" + d;
if (hinbanTextbox == "" || hinmeiTextbox == "" || hinmokuTextbox == "") {
alert("品番品名品目が読み取れていません。QRコードを再読みしてください。");
return
}
if (suuryouTextbox == "") {
alert("数量が入力されていません");
return
}
google.script.run.withSuccessHandler(function(){
alert("登録しました。")
document.getElementById("hinban").value = "";
document.getElementById("hinmei").value = "";
document.getElementById("hinmoku").value = "";
document.getElementById("suuryou").value = "";
document.getElementById("bikou").value = "";
}).SheetSet(QRvalue,hinbanTextbox,hinmeiTextbox,hinmokuTextbox,suuryouTextbox,bikouTextbox,useragent,resultTodat);
}
</script>
js2.html (QR認識のためのJS)
<script>
function openQRCamera(node) {
var reader = new FileReader();
reader.onload = function() {
node.value = "";
qrcode.callback = function(res) {
if(res instanceof Error) {
alert("エラーが発生しました。読み取りずらい環境にいるかブレ等が原因の可能性があります。再度読み取ってください。");
} else {
node.parentNode.previousElementSibling.value = res;
document.getElementById("hinban").value = "";
document.getElementById("hinmei").value = "";
document.getElementById("hinmoku").value = "";
}
};
qrcode.decode(reader.result);
};
reader.readAsDataURL(node.files[0]);
}
function showQRIntro() {
return confirm("QRコードを読み取るためにカメラを起動します。");
}
</script>
main.gs
function doGet() {
var htmlOutput = HtmlService.createTemplateFromFile("index").evaluate();
htmlOutput
.setTitle('㈱テスト 棚卸システム')
.addMetaTag('viewport', 'width=device-width, initial-scale=1')
return htmlOutput;
}
function SheetSarch(data){
var spreadsheet = SpreadsheetApp.openById("自分のスプレットシートのID");
var sheet = spreadsheet.getSheetByName("QRコード");
var col = 1
let LastNumber
LastNumber = findRow(sheet,data,col)
//. 指定するセルの範囲(A1)を取得
var rangeHinban = sheet.getRange("B"+LastNumber);
var rangeHinmei = sheet.getRange("C"+LastNumber);
var rangeHinmoku = sheet.getRange("D"+LastNumber);
//. 値を取得する
var valueHinban = rangeHinban.getValue();
var valueHinmei = rangeHinmei.getValue();
var valueHinmoku = rangeHinmoku.getValue();
return valueHinban+","+valueHinmei+","+valueHinmoku;
}
function SheetSet(QRvalue,hinbanTextbox,hinmeiTextbox,hinmokuTextbox,suuryouTextbox,bikouTextbox,useragent,resultTodat){
var spreadsheet = SpreadsheetApp.openById("自分のスプレットシートのID");
var sheet = spreadsheet.getSheetByName("履歴");
var lRow = sheet.getLastRow();
lRow = lRow + 1
sheet.getRange("A"+lRow).setValue(resultTodat);
sheet.getRange("B"+lRow).setValue(QRvalue);
sheet.getRange("C"+lRow).setValue(hinbanTextbox);
sheet.getRange("D"+lRow).setValue(hinmeiTextbox);
sheet.getRange("E"+lRow).setValue(hinmokuTextbox);
sheet.getRange("F"+lRow).setValue(suuryouTextbox);
sheet.getRange("G"+lRow).setValue(bikouTextbox);
sheet.getRange("H"+lRow).setValue(useragent);
}
function findRow(sheet,val,col){
var dat = sheet.getDataRange().getValues(); //受け取ったシートのデータを二次元配列に取得
for(var i=1;i<dat.length;i++){
if(dat[i][col-1] === val){
return i+1;
}
}
return 0;
}
特に悩んだところ
google.script.run.withSuccessHandler について
スプレットシートにはhtml内のjava scriptには直接アクセスできません。
そこで、google.script.run.withSuccessHandler を使用し、main,gs内の関数にjavascriptからアクセスすることでスプレットシート内のデータにアクセスできます。以下解説
//js.html
google.script.run.withSuccessHandler(function(){ //function内の()の中はSheetSetの戻り値。
alert("登録しました。")
document.getElementById("hinban").value = "";//以下html内のinputのデータを消去
document.getElementById("hinmei").value = "";
document.getElementById("hinmoku").value = "";
document.getElementById("suuryou").value = "";
document.getElementById("bikou").value = "";
}).SheetSet(QRvalue,hinbanTextbox,hinmeiTextbox,hinmokuTextbox,suuryouTextbox,bikouTextbox,useragent,resultTodat);
//function内の処理をする前に、main.gsのSheetSet関数の処理を行い、シートにアクセスする
}
//main.gs
//js.htmlから受け取ったデータをセル内に入力する関数
function SheetSet(QRvalue,hinbanTextbox,hinmeiTextbox,hinmokuTextbox,suuryouTextbox,bikouTextbox,useragent,resultTodat){
var spreadsheet = SpreadsheetApp.openById("自分のスプレットシートのID");
var sheet = spreadsheet.getSheetByName("履歴");
var lRow = sheet.getLastRow();
lRow = lRow + 1
sheet.getRange("A"+lRow).setValue(resultTodat);
sheet.getRange("B"+lRow).setValue(QRvalue);
sheet.getRange("C"+lRow).setValue(hinbanTextbox);
sheet.getRange("D"+lRow).setValue(hinmeiTextbox);
sheet.getRange("E"+lRow).setValue(hinmokuTextbox);
sheet.getRange("F"+lRow).setValue(suuryouTextbox);
sheet.getRange("G"+lRow).setValue(bikouTextbox);
sheet.getRange("H"+lRow).setValue(useragent);
}
ほか、わからないところがあれば随時解説します。
4:出力結果
スマートフォンにてデプロイ先のURLにアクセスすると以下のような画面になります。
(使用環境ブラウザ:safari)
QRコードマークをタップするとカメラアプリが自動で起動します。
読み込みボタンを押すとシート内から連番の品物を探し出します。
そこで数量を入力すると以下のように"履歴"シートに自動で書き込まれます。
5:結果と今後
わからないところが多々あったが結果的にはシステムを作成できた。
今後の展望としてQRコードの読み取りの精度を向上させる方法を模索したい。
以上。
この記事が気に入ったらサポートをしてみませんか?