0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kintone のレコードを A4 の帳票にして印刷する:CSS の @page と、値の整え方

0
Posted at

kintone のレコードを紙で出したい、という要望はよくあります。ブラウザーの印刷をそのまま使うと、メニューやサイドバーまで印刷され、1件が何枚にも分かれます。

この記事では、作業日報や KY 活動記録を A4 縦の帳票にして印刷するプラグインのコードで、次の4つを見ます。PDF のライブラリーは使わず、HTML と CSS とブラウザーの印刷だけで作っています。

  1. 帳票を画面の上に重ね、印刷では帳票だけを出す CSS
  2. フィールドの値を、帳票に載せる文字に整える
  3. リッチエディターの HTML から文字だけを取り出す
  4. A4 の1枚に収まらない帳票を、印刷の前に知らせる

コードは2026年11月8日に kintone の開発環境で動かして確かめています。画面の写真は、その環境の架空のデータです。

A4縦の作業日報の帳票。日付・工事名・天候、開始の日時、作業内容、作業員の表、金額、備考の欄が並ぶ
印刷(PDF)の1ページ目の上の部分。開始は UTC の値を日本時間に直し、金額は桁区切りと単位つき、備考はリッチエディターから文字だけを取り出している

1. 帳票だけを印刷する CSS

ボタンを押すと、body の直下に #gbd-report を足し、body に gbd-printing のクラスを付けます。画面では、帳票を全面に重ねて表示します(kintone/daily-report/src/css/desktop.css)。

#gbd-report{position:fixed;inset:0;z-index:100000;overflow:auto;background:#d8d8d8;color:#111;font-family:sans-serif;-webkit-print-color-adjust:exact;print-color-adjust:exact}
.gbd-sheet{box-sizing:border-box;width:210mm;min-height:297mm;margin:12px auto;padding:10mm;background:#fff}

1枚の帳票(.gbd-sheet)は幅 210mm、高さ 297mm。CSS の mm で書くと、画面でも紙と同じ比率で見えます。print-color-adjust:exact は、見出しの灰色の背景を印刷でも消さないための指定です。

印刷のときの指定はこうです。

@media print{
  @page{size:A4 portrait;margin:10mm}
  body.gbd-printing{height:auto!important;overflow:visible!important;background:#fff!important}
  body.gbd-printing>*:not(#gbd-report){display:none!important}
  /* 写真台帳プラグインと同じアプリに入れても、帳票が隠れないようにする */
  html body.gbd-printing>div#gbd-report{display:block!important;position:static;overflow:visible;background:#fff}
  .gbd-bar{display:none!important}
  .gbd-sheet{width:auto;min-height:0;margin:0;padding:0;outline:0!important;break-after:page}
  .gbd-sheet:last-child{break-after:auto}
}
  • body.gbd-printing>*:not(#gbd-report) で、kintone の画面(帳票以外の body の子)をすべて隠します。
  • 画面では position:fixed だった帳票を、印刷では position:static に戻します。固定のままだと、画面からはみ出した2ページ目以降がうまく印刷されないことがあるためです。
  • @page の余白を 10mm にしたので、印刷では .gbd-sheet の余白(padding)を0にします。画面の padding:10mm と同じ見た目になります。
  • break-after:page で、1件ごとに改ページします。最後の1枚だけは外して、白紙が1枚増えないようにします。
  • 表の行と署名欄には break-inside:avoid を付け、行の途中でページが切れないようにしています。

開発環境で100件の帳票を PDF にすると、すべて A4 縦(210×297mm)で、白紙のページはありませんでした(1枚に収まらない1件が3ページになり、合わせて102ページ)。

コメントにあるとおり、同じアプリに入れる別のプラグイン(写真台帳)も、印刷では body>*:not(#gbk-ledger){display:none!important} で「自分以外」を隠します。そのままでは日報の帳票まで消えるので、html body.gbd-printing>div#gbd-report のように詳しさ(詳細度)を上げた !important で、自分の帳票を出し直しています。

2. フィールドの値を帳票の文字に整える

REST API やイベントで届く値は、画面の表示とは違う形です。そのまま印刷すると読みにくいので、型ごとに整えます(kintone/daily-report/src/js/desktop.js)。

  function display(field, property) {
    if (!field || field.value === null || field.value === undefined || field.value === '') return '';
    var value = field.value;
    var prop = property || {};
    switch (field.type) {
      case 'DATETIME':
      case 'CREATED_TIME':
      case 'UPDATED_TIME':
        return localTime(value);
      case 'RICH_TEXT':
        return richText(value);
      case 'NUMBER':
      case 'CALC':
        if (field.type === 'CALC' && prop.format && prop.format !== 'NUMBER' && prop.format !== 'NUMBER_DIGIT') return String(value);
        var number = (prop.digit === true || prop.digit === 'true' || prop.format === 'NUMBER_DIGIT') ? withDigits(value) : String(value);
        if (!prop.unit) return number;
        return prop.unitPosition === 'BEFORE' ? prop.unit + number : number + prop.unit;
      default:
        break;
    }
    if (Array.isArray(value)) return value.map(function(v) { return (v && typeof v === 'object') ? (v.name || v.code || '') : String(v); }).join('、');
    if (typeof value === 'object') return value.name || value.code || '';
    return String(value);
  }

property は、/k/v1/app/form/fields.json で取ったフィールドの設定です。値だけでは分からない「桁区切りを出すか」「単位は何か、前か後ろか」をここから読みます。

  • 日時:値は UTC の 2026-11-02T23:30:00Z の形で届きます。日本時間では翌日の 08:30 なので、端末の時刻に直します。
  // 日時はUTCで届くので、端末の時刻に直して「2026-11-02 08:30」にする
  function localTime(value) {
    var date = new Date(value);
    if (isNaN(date.getTime())) return value;
    return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes());
  }
  • 数値・計算:桁区切りは、整数の部分にだけ付けます。小数の部分に付けないよう、. で分けてから正規表現をかけます。
  function withDigits(value) {
    var parts = String(value).split('.');
    if (!/^-?\d+$/.test(parts[0])) return String(value);
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    return parts.join('.');
  }
  • 計算フィールドで表示形式が日付や時間のものは、数として扱わず、そのまま出します。
  • ユーザー選択・チェックボックスなど:値が配列なので、name(ユーザーなど)か文字列を「、」でつなぎます。

3. リッチエディターから文字だけを取り出す

リッチエディターの値は HTML です。innerHTML で帳票に入れると、書式が崩れるだけでなく、スクリプトが動くおそれもあります。そこで、描画しない文書として読み、文字と改行だけを取り出します。

  var BLOCKS = {P: 1, DIV: 1, LI: 1, TR: 1, H1: 1, H2: 1, H3: 1, H4: 1, H5: 1, H6: 1, BLOCKQUOTE: 1, PRE: 1};
  // (中略)
  // リッチエディターのHTMLは、描画しない文書で読んで文字だけ取り出す
  function richText(html) {
    var doc = new DOMParser().parseFromString(String(html), 'text/html');
    var out = '';
    (function walk(node) {
      for (var child = node.firstChild; child; child = child.nextSibling) {
        if (child.nodeType === 3) {
          out += child.nodeValue;
        } else if (child.nodeType === 1) {
          if (child.nodeName === 'BR') {
            out += '\n';
          } else if (child.nodeName !== 'SCRIPT' && child.nodeName !== 'STYLE') {
            walk(child);
            if (BLOCKS[child.nodeName] && out.slice(-1) !== '\n') out += '\n';
          }
        }
      }
    })(doc.body);
    return out.replace(/\n+$/, '');
  }
  • DOMParser で作った文書は画面に出ないので、中の <img onerror=...> なども動きません。開発環境で <img src="x" onerror=...> と <script> を入れた値を帳票にしても、どちらも動かず、段落と <br> と箇条書きの区切りが改行になりました。
  • <br> と、段落などのブロックの終わりを改行にします。textContent だけだと段落の区切りが消えて1行につながるので、木をたどって改行を足しています。
  • 帳票のセルには textContent で入れ、CSS の white-space:pre-wrap で改行を見せます。

帳票の部品はすべて document.createElement と textContent で作っていて、HTML の文字列を組み立てていません。レコードの値に < が入っていても、そのまま文字として出ます。

4. 1枚に収まらない帳票を知らせる

本文が長いと、1件が2枚にまたがります。印刷してから気づくと紙がむだになるので、画面に出した時点で数えます。

    // A4の印刷範囲(余白10mmを除いた高さ277mm)を超えた帳票を数える
    var probe = element('div', 'gbd-probe');
    root.appendChild(probe);
    var limit = probe.getBoundingClientRect().height;
    root.removeChild(probe);
    var over = 0;
    Array.prototype.forEach.call(root.querySelectorAll('.gbd-inner'), function(inner) {
      if (limit && inner.getBoundingClientRect().height > limit + 1) {
        inner.parentNode.classList.add('gbd-over');
        over++;
      }
    });
.gbd-probe{height:277mm;width:1px;position:absolute;visibility:hidden}
.gbd-sheet.gbd-over{outline:3px solid #c62828}

297mm から上下の余白 10mm ずつを引いた 277mm の見えない箱を置き、その高さをピクセルで測ります。mm とピクセルの換算を自分で書かずに、ブラウザーに測らせるのがこつです。中身(.gbd-inner)がそれより高い帳票に赤い枠を付け、上のバーに「1枚に収まらない帳票 ○件(赤枠。印刷すると2枚以上になります)」と出します。赤枠は @media print で消すので、紙には出ません。

帳票の画面。上のバーに「1枚に収まらない帳票 1件(赤枠。印刷すると2枚以上になります)」と出て、本文の長い帳票に赤い枠が付いている

5. 押した時点の内容で作る

詳細画面のボタンは、画面を開いたときの event.record ではなく、押した時点の内容を使います。

        // 押した時点の内容を使う。取れなければ画面を開いたときの内容
        var current = kintone.app.record.get();
        var record = (current && current.record) || event.record;

まとめ

  • 帳票は body の直下に重ね、印刷では :not() で帳票以外を隠す。@page で A4 と余白を決める。
  • 値はフィールドの設定(桁区切り・単位)と型を見て整える。日時は UTC から端末の時刻へ。
  • リッチエディターは DOMParser で読み、文字と改行だけを取る。
  • 1枚に収まるかは、277mm の箱をブラウザーに測らせて比べる。

ソースと配布先

この記事のコードは kintone プラグイン「作業日報・KY記録プリント」(MIT License)の一部です。入れ方と設定:https://genbakoe.com/kintone/daily-report/?utm_source=qiita&utm_medium=referral&utm_campaign=kintone-plugin-a4-print / ソース:https://github.com/Genbacoe/kintone-plugins/tree/main/daily-report/src

※ kintone はサイボウズ株式会社の登録商標です。このプラグインと記事は、サイボウズ株式会社の公式のものではありません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?