街头程序员:从在线POS Web应用到本地打印机的打印收据
#javascript #网络开发人员 #react #php

在我告诉您更多有关如何创建使用PHP的WebSocket服务器以提供在线Web应用程序和本地热线打印机之间的通信访问的信息之前。

捐赠者:

GitHub赞助商:

上一个故事...


你好朋克!我是自由泳者,许多关于 如何从在线Web应用到本地打印机“ Thermal”?。通常在POS应用中使用的热打印机,听起来像Whaaaaat ....(只是开玩笑,什么都没有!)

How To Do That

对不起...自恋(在我的脑海中),但是的,(健康顺便说一句)。所以, 我的收据是什么?

收据(要求)

在很久以前的AM PHP开发人员中,然后我与JavaScript(并爱上了它)有 Backstreet 关系(尝试使其像黑客一样),现在我是Playgrammer。 /p>

框架

  • React JS我的年轻未婚夫
  • Workerman我的老太太,可以使用Easy!
  • 创建WebSocket服务器

他们两个都像文档一样。

当很好时,很好。当不好的时候,总比没有好。 - 迪克·布兰登

依赖项

设备

  • 您的计算机 /笔记本电脑(不!)< / li>
  • Abiaoqian与ESCPOS-PHP Package1111111111

其他要求

  • 咖啡 - 必需!
  • 雪茄 - 必需!
  • 您非常讨厌的音乐! - 必需!

打开门

Tok Tok Tok ...请打开门。打开不寻常的文本编辑器和开放终端窗口,然后创建您的全新React应用程序! (仅供参考:我不会告诉您如何创建React全新React应用程序,只需阅读他们的文档即可!)

创建全新的React应用程序后,然后使用Composer安装react-use-websocket(这是错误的方法!)。

这只是我在项目中使用的(f)示例,所以不要以不同的方式询问。刚刚以您的想象力写。

创建打印机设置存储

// Filename: src/db/index.js

import Dexie from 'dexie';

export const db = new Dexie('YOUR-STORAGE-NAME');

db.version(1).stores({
    printer_settings: '++id, app_name, printer_name, interface, font_type, ch_line_1, ch_line_2, ch_line_3, cf_line_1, cf_line_2, cf_line_3, cl_operator, cl_time, cl_trx_number, cl_costumer_name, cl_discount, cl_total_discount, cl_total, cl_tax, cl_member, cl_paid, cl_return, cl_loan, cl_saving, cl_tempo, line_feed_each_in_items, column_width, more_new_line, pull_cash_drawer, template'
});

创建打印机设置页面

创建打印机设置页面以允许用户设置自己的语言以及收据的外观。

打印机设置页面包含类似Dexie存储定义的字段。

// Filename: src/constants/index.js
// NOTE:
// - cl = Custom Language
// - ch = Custom Header
// - cf = Custom Footer

export const DEFAULT_PRINTER_SETTING = {
    app_name: 'Your Application Name',
    printer_name: '/dev/usb/lp0',
    interface: 'linux-usb',
    font_type: 'C',
    ch_line_1: 'Your Header Line 1',
    ch_line_2: 'Your Header Line 2',
    ch_line_3: 'Your Header Line 3',
    cf_line_1: 'Your Footer Line 1',
    cf_line_2: 'Your Footer Line 2',
    cf_line_3: 'Your Footer Line 3',
    cl_operator: 'Operator Name',
    cl_time: 'Time',
    cl_trx_number: 'Transaction Number',
    cl_customer_name: 'Customer Name',
    cl_discount: 'Discount',
    cl_total_discount: 'Total Discount',
    cl_total: 'Total',
    cl_tax: 'Tax',
    cl_member: 'Member',
    cl_paid: 'Paid',
    cl_return: 'Return',
    cl_debt: 'Debt',
    cl_saving: 'Saving',
    cl_due_date: 'Due date',
    line_feed_each_in_items: 15,
    column_width: 40,
    more_new_line: 1,
    pull_cash_drawer: 'no',
    template: 'epson'
}

const interfaceOptions = [
    { value: 'cpus', key: 'CPUS' },
    { value: 'ethernet', key: 'Ethernet' },
    { value: 'linux-usb', key: 'Linux USB' },
    { value: 'smb', key: 'SMB' },
    { value: 'windows-usb', key: 'Windows USB' },
    { value: 'windows-lpt', key: 'Windows LPT' },
]

const fontTypeOptions = [
    { value: 'A', key: 'Font Type A' },
    { value: 'B', key: 'Font Type B' },
    { value: 'C', key: 'Font Type C' },
]

创建向用户提供输入设置的表单。您可以使用Formik或您知道要在React中创建表单的东西创建。

在我的(f)情况下,我使用Formik并使DEFAULT_PRINTER_SETTING稳定为form initialValues,然后使用interfaceOptionsfontTypeOptions以形式创建下拉选项。

创建表格提交处理程序

您可以在上面看到,形式需要提交以在Dexie存储中的存储设置。

// Filename: src/pages/PrinterSetting.js
import { db } from '../db'
import { DEFAULT_PRINTER_SETTING } from '../constants'

const formOnSubmit = async (values) => {
    try {
        db.printer_settings.count(count => {
            if (count > 0) {
                db.printer_settings.update(1, values).then(updated => {
                    if (updated)
                        console.log("Printer setting was updated!");
                    else
                        console.warn("Nothing was updated - there were no update changed");
                });
            } else {
                db.printer_settings.add(DEFAULT_PRINTER_SETTING).then(() => {
                    console.log("New setting just created using default setting");
                }).catch(error => {
                    console.error(error)
                })
            }
        })
    } catch (error) {
        console.error(error)
    }
}

创建打印机设置页面后,不要忘记喝咖啡并发射雪茄,播放下一首您讨厌大声的歌曲!

创建POS上下文

使生活变得轻松...在React中创建pos ContextProvider。将其放置并包装您的应用程序。

...For What?

我们需要制作在整个应用中共享的WebSocket客户端:

  • 共享Websocket连接状态
  • 创建指标
  • 将消息发送到WebSocket服务器
// Filename: src/contexts/POSContext.js
import useWebSocket from 'react-use-websocket'
import { db } from '../db'
import { DEFAULT_PRINTER_SETTING } from '../constants'

const POSContext = createContext()

const SOCKET_URL = 'ws://<your-websocket-address:port>'

const generateAsyncUrlGetter = (url, timeout = 2000) => () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(url);
        }, timeout);
    });
};

const POSProvider = ({ children }) => {
    const [currentSocketUrl, setCurrentSocketUrl] = useState(SOCKET_URL)
    const [printerSettings, setprinterSettings] = useState()

    const { sendMessage, sendJsonMessage, readyState } = useWebSocket(
        currentSocketUrl,
        {
            share: true,
            shouldReconnect: () => true,
            reconnectAttempts: 3,
            reconnectInterval: 3000,
            onError: (event) => {
                alert.show('WebSocket trying to connect to sever but failed!', {
                    type: 'error',
                    position: 'bottom right',
                    timeout: 3000
                })
            },
            onOpen: (event) => {
                alert.show('WebSocket connection establised!', {
                    type: 'success',
                    position: 'bottom right',
                    timeout: 3000
                })
            },
            onClose: (event) => {
                alert.show('WebSocket connection is closed!', {
                    type: 'warning',
                    position: 'bottom right',
                    timeout: 3000
                })
            },
            onReconnectStop: () => 6
        }
    )

    const readyStateString = {
        0: 'CONNECTING',
        1: 'OPEN',
        2: 'CLOSING',
        3: 'CLOSED',
    }[readyState]

    const reconnectWebSocket = useCallback(() => {
        if (readyState === 3) {
            setCurrentSocketUrl(generateAsyncUrlGetter(SOCKET_URL))
        } else {
            setCurrentSocketUrl(null)
        }
    }, [setCurrentSocketUrl, readyState])

    const initData = useCallback(async () => {
        if (readyState === 1) {
            db.printer_settings.count(count => {
                if (count > 0) {
                    setprinterSettings(first(settings))
                    console.log('Setting is exists')
                } else {
                    setprinterSettings(DEFAULT_PRINTER_SETTING)
                    console.log('Setting is not exists!')
                }
            })
        } else {
            console.log('Setting is not exists!')
            setprinterSettings(DEFAULT_PRINTER_SETTING)
        }
    }, [readyState])

    useEffect(() => {
        if (readyState === 1) initData()
    }, [readyState, initData])

    return (
        <KasirContext.Provider value={{
            sendMessage,
            sendJsonMessage,
            reconnectWebSocket,
            readyStateString,
            printerSettings
        }}>
            {children}
        </KasirContext.Provider>
    )
}

export { KasirContext, KasirProvider }

喝咖啡和烟!更大的音量!

Dance More

创建指示按钮

此按钮显示客户端和服务器之间的Websocket连接。此按钮还可以触发连接时重新连接到服务器。

// Filename: src/components/IndicatorButton.js

import React, { useContext } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { POSContext } from '../contexts/POSContext';

function IndicatorButton() {
    const posCtx = useContext(POSContext)

    return (
        <button
            className='btn btn-indicator'
            onClick={() => posCtx.reconnectWebSocket()}
        >
            <FontAwesomeIcon icon="fa-solid fa-satellite-dish" />{' '}
            Printer Server is:{' '}
            <span
                className={`${posCtx.readyStateString === 'CONNECTING' ? 'text-info' : (posCtx.readyStateString === 'OPEN') ? 'text-success' : (posCtx.readyStateString === 'CLOSED') ? 'text-secondary' : 'text-danger'}`}
            >
                {posCtx.readyStateString}
            </span>
        </button>
    )
}

export default IndicatorButton

您可以将其放在代码中的某个地方。

现在! WebSocket客户端正在运行,但连接已关闭3次尝试将其重新连接到WebSocket服务器。

那么接下来呢?!

创建WebSocket服务器

使用workerman创建WebSocket服务器就像魔术。

  1. printer-server目录中创建新的php index.php文件
  2. Visit Workerman Repository
  3. 使用npm(愚蠢!)在printer-server目录中安装workermanescpos-php包装
  4. 复制和粘贴A simple websocket example
  5. 去您的巴士终端运行php index.php start

就是这样,别无其他!而且您无法使用该代码进行任何打印呼叫。这很容易吧?

WTF! Are you kidding me?!
wtf!你在开玩笑吧?! (听起来很棒,谢谢)

不要生气我!请开玩笑!

现在使用此代码更新WebSocket Server脚本!我向你保证...(我的头声音:$^u@%g@yt@#..... .....)

但是,在触摸WebSocket Server脚本之前,您需要知道 打印机服务器需要哪些数据?

上午印度尼西亚人,如果我的源代码包含任何与英语混合的印尼语言,请不要生气。您可以翻译印度尼西亚>使用翻译器的英语或之后,您可以学习更多印尼语言(Hahaha Just Kinding!接下来!)

有效载荷该Websocket服务器(需要打印机服务器)

{
    "trx": {
        "nama_kasir": "Umam",
        "no_transaksi": "nomor-transaksi",
        "trx": "only-number-trx",
        "nama_pelanggan": "nama-pelanggan",
        "tgl_transaksi": "tanggal-transaksi-format-yyyy-mm-dd",
        "diskon": "jumlah-diskon",
        "total_transaksi": "total-transaksi",
        "uang_diterima": "uang-diterima",
        "uang_kembali": "uang-kembali",
        "jenis_pembayaran": "jenis-pembayaran",
        "jatuh_tempo": "tanggal-jatuh-tempo",
        "uang_titip": "jumlah-uang-titip",
        "ekspedisi": null,
        "ongkir": null,
        "pajak": "0",
        "potongan_member": "0"
    },
    "text_kembalian": 0,
    "belanja": [
        {
            "barang_nama": "MIE SEDAP SOTO",
            "harga": "0",
            "jumlah": "50 Dus",
            "sub_total": "5125000",
            "initial_code": "BRG-605A5B07C3EE5",
            "no_transaksi": "nomor-transaksi"
        },
        {
            "barang_nama": "MIE SEDAP GORENG",
            "harga": "107000",
            "jumlah": "38 Dus",
            "sub_total": "4066000",
            "initial_code": "BRG-605A5B01530C4",
            "no_transaksi": "nomor-transaksi"
        },
        {
            "barang_nama": "PATI SG",
            "harga": "0",
            "jumlah": "100 Sak",
            "sub_total": "25450000",
            "initial_code": "BRG-625678D10EC80",
            "no_transaksi": "nomor-transaksi"
        }
    ],
    "alamat_gudang": "Jl. AHMAD YANI - DESA SAMBIROTO - TAYU - PATI <br>\r\nDepan Masjid Baitussalam <br>\r\n0853 - 2622 - 5333 / 0856 - 4165 - 5202",
    "app_name": "CV. Kalimasodo Agriculture",
    "nama_lengkap": "Umam",
    "from": "posclient",
    "command": "posclient",
    "printer_name": "EPSON TM-U220 Receipt",
    "printer_settings": {
        "printer_name": "/dev/usb/lp0",
        "interface": "linux-usb",
        "font_type": "A",
        "custom_print_header": [
            "Your Company Name",
            "Your Subheading for Receipt",
            "Your Third text on header"
        ],
        "custom_print_footer": [
            "Your Company Name",
            "Your Subheading for Receipt",
            "Your Third text on footer"
        ],
        "custom_language": {
            "operator": "Operator",
            "time": "Time",
            "trx_number": "TRX Number",
            "costumer_name": "Costumer",
            "discount": "Discount",
            "total_discount": "Total Disc" ,
            "total": "Total",
            "tax": "Tax",
            "member": "Member",
            "paid": "Paid",
            "return": "Return",
            "loan": "Loan",
            "saving": "Saving",
            "tempo": "Tempo"
        },
        "line_feed_each_in_items": 1,
        "column_width": 48,
        "more_new_line": 1,
        "pull_cash_drawer": false
    }
}

您可以使用该模式按您的要求格式化该请求有效载荷。

创建WebSocket打印机服务器

以下是index.php中的所有代码

<?php

use Workerman\Worker;
use Mike42\Escpos\Printer;
use Mike42\Escpos\PrintConnectors\DummyPrintConnector;
use Mike42\Escpos\PrintConnectors\CupsPrintConnector;
use Mike42\Escpos\PrintConnectors\NetworkPrintConnector;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
use Mike42\Escpos\PrintConnectors\WindowsPrintConnector;
use Mike42\Escpos\CapabilityProfile;

require_once __DIR__ . '/vendor/autoload.php';

// Create a Websocket server
$ws_worker = new Worker('websocket://0.0.0.0:2112');

// Emitted when new connection come
$ws_worker->onConnect = function ($connection) {
    echo "New connection\n";
};

// Emitted when data received
$ws_worker->onMessage = function ($connection, $payload) {
    // Send hello $data
    $data = json_decode($payload, TRUE);
    $options = $data['printer_settings'];

    echo '> metadata: ' . $data['from'] . PHP_EOL;
    sendingMessage($connection, createMessage('info', 'metadata: ' . $data['from']));
    echo '> activer_printer: ' . $data['printer_name'] . PHP_EOL;
    sendingMessage($connection, createMessage('info', 'active_printer: ' . $data['printer_name']));
    echo '< echo', "\n";
    print_r($options);

    if ($data['from'] === 'posclient') {
        print_reciept($data, $options);
    } else if ($data['from'] === 'testprinter') {
    }
};

// Emitted when connection closed
$ws_worker->onClose = function ($connection) {
    echo "Connection closed\n";
};

// Run worker
Worker::runAll();

function print_reciept($arrData, $options = null)
{
    // Printer Connector
    $allowed_print_interfaces = ['cpus', 'ethernet', 'linux-usb', 'smb', 'windows-usb', 'windows-lpt'];
    if (in_array($options['interface'], $allowed_print_interfaces)) {
        switch ($options['interface']) {
            case 'cpus':
                $connector = new CupsPrintConnector($options['printer_name']);
                break;
            case 'ethernet':
                $connector = new NetworkPrintConnector($options['printer_name'], 9100);
                break;
            case 'linux-usb':
                $connector = new FilePrintConnector($options['printer_name']);
                break;
            case 'smb':
            case 'windows-usb':
            case 'windows-lpt':
            default:
                $connector = new WindowsPrintConnector($options['printer_name']);
                break;
        }
    } else {
        // Make sure you load a Star print connector or you may get gibberish.
        $connector = new DummyPrintConnector();
        $profile = CapabilityProfile::load("TSP600");
    }

    // Font Type
    $selectPrinterFont = [
        'A' => Printer::FONT_A,
        'B' => Printer::FONT_B,
        'C' => Printer::FONT_C
    ];

    if ($options['template'] === 'epson') {
        $fontSettings = [
            'header_nota' => $selectPrinterFont['B'],
            'sub_header_nota' => $selectPrinterFont['B'],
            'detail_operator' => $selectPrinterFont['C'],
            'detail_balanja' => $selectPrinterFont['C'],
            'sub_promo' => $selectPrinterFont['B'],
            'footer' => $selectPrinterFont['C'],
            'sub_footer' => $selectPrinterFont['B']
        ];
    } else {
        $fontSettings = [
            'header_nota' => $selectPrinterFont['C'],
            'sub_header_nota' => $selectPrinterFont['C'],
            'detail_operator' => $selectPrinterFont['C'],
            'detail_balanja' => $selectPrinterFont['C'],
            'sub_promo' => $selectPrinterFont['C'],
            'footer' => $selectPrinterFont['C'],
            'sub_footer' => $selectPrinterFont['C']
        ];
    }

    echo "Line feed each item: " . $options['line_feed_each_in_items'];

    $printer = new Printer($connector);
    $column_width = ($options['template'] === 'epson') ? 40 : 48;
    $divider    = str_repeat("-", $column_width) . "\n";
    $optnl = ($options['template'] === 'epson') ? 0 : $options['more_new_line'];
    $more_new_line = str_repeat("\n", $optnl);

    // Membuat header nota
    $printer->initialize();
    $printer->selectPrintMode(Printer::MODE_DOUBLE_HEIGHT); // Setting teks menjadi lebih besar
    $printer->setJustification(Printer::JUSTIFY_CENTER);    // Setting teks menjadi rata tengah
    $printer->setFont($fontSettings['header_nota']);
    $printer->setEmphasis(true);
    $printer->text($arrData['app_name'] . "\n");
    $printer->setEmphasis(false);
    $printer->feed(1);

    // Membuat sub header nota
    $printer->initialize();
    $printer->setJustification(Printer::JUSTIFY_CENTER); // Setting teks menjadi rata tengah
    $printer->setFont($fontSettings['sub_header_nota']);
    $printer->setEmphasis(true);
    if (count($options['custom_print_header']) > 0) {
        for ($i = 0; $i < count($options['custom_print_header']); $i++) {
            $header_text = str_replace('\r\n', "", $options['custom_print_header'][$i]);
            $header_text = str_replace('<br>', PHP_EOL, $header_text);
            $printer->text(strtoupper($header_text) . "\n");
        }
    } else {
        $printer->text("MELAYANI SEPENUH HATI\n");
        $printer->text(strtoupper(str_replace("<br>", "", $arrData['alamat_gudang'])) . "\n");
        $printer->text("Buka Jam 07:30 - 16:30\n");
    }
    $printer->setEmphasis(false);
    $printer->feed(1);

    // Membuat detail operator
    $printer->initialize();
    $printer->setLineSpacing(20);
    $printer->setFont($fontSettings['detail_operator']);
    $printer->setEmphasis(true);
    $printer->text($divider);
    $printer->text(buatBarisSejajar($options['custom_language']['operator'] . " : ", $arrData['nama_lengkap'] . $more_new_line, $column_width));
    $printer->text(buatBarisSejajar($options['custom_language']['time'] . " : ", $arrData['trx']['tgl_transaksi'] . ' ' . date('H:i:s')  . $more_new_line, $column_width));
    $printer->text(buatBarisSejajar($options['custom_language']['trx_number'] . " : ", $arrData['trx']['trx'] . $more_new_line, $column_width));
    $printer->text(buatBarisSejajar($options['custom_language']['costumer_name'] . " : ", $arrData['trx']['nama_pelanggan'] . $more_new_line, $column_width));
    $printer->text($divider);
    $printer->setEmphasis(false);
    $printer->feed(1);

    // Print Detail Belanja
    $printer->initialize();
    $printer->setLineSpacing(20);
    $printer->setFont($fontSettings['detail_balanja']);
    $printer->setEmphasis(true);
    $no    = 1;
    foreach ($arrData['belanja'] as $b) {
        $printer->text(buatBarisContent($b['barang_nama'], idr_format($b['sub_total'], false), $options['template']));
        $printer->text('@ ' . idr_format($b['harga'], true) . " x " . $b['jumlah'] . "\n");
        $printer->feed($options['line_feed_each_in_items']);
        $no++;
    }
    $printer->setEmphasis(false);
    $printer->feed(1);

    if (count($arrData['promo']) > 0) {
        // Membuat sub promo
        $printer->initialize();
        $printer->setLineSpacing(20);
        $printer->feed(1);
        $printer->setJustification(Printer::JUSTIFY_CENTER); // Setting teks menjadi rata tengah
        $printer->setFont($fontSettings['sub_promo']);
        $printer->setEmphasis(true);
        $printer->text($divider);
        for ($i = 0; $i < count($arrData['promo']); $i++) {
            $printer->text(wordwrap($arrData['promo'][$i], 35, "\n") . "\n\n");
        }
        $printer->setEmphasis(false);
        $printer->feed(3);
    }

    // Print Footer
    $printer->initialize();
    $printer->setLineSpacing(20);
    $printer->setFont($fontSettings['footer']);
    $printer->setEmphasis(true);
    $printer->text($divider);

    // Total Belanja
    if ($arrData['trx']['sebelum_diskon'] != $arrData['trx']['total_transaksi']) {
        $printer->text(buatBarisSejajar('Total Belanja', idr_format($arrData['trx']['sebelum_diskon']), $column_width) . $more_new_line);
    }

    // Total Pajak
    if ($arrData['trx']['pajak'] != 0) {
        $printer->text(buatBarisSejajar($options['custom_language']['tax'], idr_format($arrData['trx']['pajak']), $column_width) . $more_new_line);
    }
    // Total Diskon
    if ($arrData['trx']['diskon'] != 0) {
        $printer->text(buatBarisSejajar($options['custom_language']['discount'], idr_format($arrData['trx']['diskon']), $column_width) . $more_new_line);
    }
    // Total Potongan Member
    if ($arrData['trx']['potongan_member'] != 0) {
        $printer->text(buatBarisSejajar($options['custom_language']['member'], $arrData['trx']['potongan_member'], $column_width) . $more_new_line);
    }

    $printer->text(buatBarisSejajar($options['custom_language']['total'], idr_format($arrData['trx']['total_transaksi']), $column_width) . $more_new_line);

    // Total Bayar
    $bayar = ($arrData['trx']['uang_diterima'] != 0) ? number_format($arrData['trx']['uang_diterima']) : '-';
    $printer->text(buatBarisSejajar($options['custom_language']['paid'], $bayar, $column_width) . $more_new_line);

    $text_kembalian = ($arrData['trx']['jenis_pembayaran'] != 'Non Tunai') ? number_format($arrData['trx']['uang_kembali']) : '-';

    $printer->text(buatBarisSejajar($options['custom_language']['return'], $text_kembalian, $column_width) . $more_new_line);
    // End Total Kembalian
    if ($arrData['trx']['jenis_pembayaran'] == 'Non Tunai') {
        $printer->text(buatBarisSejajar($options['custom_language']['tempo'], date('d/m/Y', strtotime($arrData['trx']['jatuh_tempo'])), $column_width) . $more_new_line);
        $printer->text(buatBarisSejajar($options['custom_language']['saving'], number_format($arrData['trx']['uang_titip']), $column_width) . $more_new_line);
        $printer->text(buatBarisSejajar($options['custom_language']['loan'], number_format($arrData['trx']['total_transaksi'] - $arrData['trx']['uang_titip']), $column_width) . $more_new_line);
    }
    $printer->setEmphasis(false);
    $printer->feed(1);

    // Membuat sub footer nota
    $printer->initialize();
    // $printer->setLineSpacing(20);
    $printer->feed(1);
    $printer->setJustification(Printer::JUSTIFY_CENTER); // Setting teks menjadi rata tengah
    $printer->setFont($fontSettings['sub_footer']);
    $printer->setEmphasis(true);
    if (count($options['custom_print_footer']) > 0) {
        $printer->text("Terimakasih sudah berbelanja di\n");
        for ($i = 0; $i < count($options['custom_print_footer']); $i++) {
            $printer->text($options['custom_print_footer'][$i] . "\n");
        }
    } else {
        $printer->text("Terimakasih sudah berbelanja di\n");
        $printer->text("CV. Kalimosodo Angriculture\n");
        $printer->text("Barang yang sudah dibeli tidak bisa\n");
        $printer->text("dikembalikan.\n");
    }
    $printer->setEmphasis(false);
    $printer->feed(3);

    $printer->cut();

    // Get the data out as a string
    // $data = $connector -> getData();
    // echo $data . PHP_EOL;

    /* Pulse */
    if ($options['pull_cash_drawer']) {
        $printer->pulse();
    }

    // Close the printer when done.
    $printer->close();
}

function sendingMessage($connection, $message)
{
    $connection->send($message);
}

function createMessage($type = 'info', $message)
{
    $timestamps = date('Y/m/d H:i:s');
    $log = strtoupper($type);
    return "[$timestamps][$log] $message";
}

function buatBarisSejajar($kolom1, $kolom2, $x)
{
    $divider    = str_repeat("-", $x);
    $full_width = strlen($divider);
    $half_width = $full_width / 2;

    $rawText = str_pad($kolom1, $half_width);
    $rawText .= str_pad($kolom2, $half_width, " ", STR_PAD_LEFT);
    return $rawText . "\n";
}

function buatBarisContent($kolom1, $kolom2, $template)
{
    $paperLength   = ($template === 'epson') ? 40 : 48;
    $divider       = str_repeat("-", $paperLength);
    $full_width    = strlen($divider);
    $half_width    = $full_width / 2;
    $total_x1 = ($template === 'epson') ? 18 : 22;
    $total_x2 = ($template === 'epson') ? 21 : 25;
    $lebar_kolom_1 = $total_x1; // asale 18
    $lebar_kolom_2 = $total_x2; // asale 21

    // Melakukan wordwrap(), jadi jika karakter teks melebihi lebar kolom, ditambahkan \n
    $kolom1 = wordwrap($kolom1, $lebar_kolom_1, "\n", true);
    $kolom2 = wordwrap($kolom2, $lebar_kolom_2, "\n", true);

    // Merubah hasil wordwrap menjadi array, kolom yang memiliki 2 index array berarti memiliki 2 baris (kena wordwrap)
    $kolom1Array = explode("\n", $kolom1);
    $kolom2Array = explode("\n", $kolom2);

    // Mengambil jumlah baris terbanyak dari kolom-kolom untuk dijadikan titik akhir perulangan
    $jmlBarisTerbanyak = max(count($kolom1Array), count($kolom2Array));

    // Mendeklarasikan variabel untuk menampung kolom yang sudah di edit
    $hasilBaris = array();

    // Melakukan perulangan setiap baris (yang dibentuk wordwrap), untuk menggabungkan setiap kolom menjadi 1 baris
    for ($i = 0; $i < $jmlBarisTerbanyak; $i++) {

        // memberikan spasi di setiap cell berdasarkan lebar kolom yang ditentukan,
        $hasilKolom1 = str_pad((isset($kolom1Array[$i]) ? $kolom1Array[$i] : ""), $lebar_kolom_1);
        $hasilKolom2 = str_pad((isset($kolom2Array[$i]) ? $kolom2Array[$i] : ""), $lebar_kolom_2, " ", STR_PAD_LEFT);

        // Menggabungkan kolom tersebut menjadi 1 baris dan ditampung ke variabel hasil (ada 1 spasi disetiap kolom)
        $hasilBaris[] = $hasilKolom1 . " " . $hasilKolom2;
    }

    // Hasil yang berupa array, disatukan kembali menjadi string dan tambahkan \n disetiap barisnya.
    return implode("\n", $hasilBaris) . "\n";
}

function idr_format($number, $prefix = false, $decimal_number = 0, $decimal_point = ',', $thousand_point = '.')
{
    return ($prefix == false) ? number_format($number, $decimal_number, $decimal_point, $thousand_point) : 'Rp. ' . number_format($number, $decimal_number, $decimal_point, $thousand_point);
}

要运行上述代码,我们需要在命令行中使用以下命令,并使用此命令:

➜ php index.php start
Workerman[index.php] start in DEBUG mode
------------------------------------------- WORKERMAN --------------------------------------------
Workerman version:4.1.4          PHP version:7.4.33           Event-Loop:\Workerman\Events\Select
-------------------------------------------- WORKERS ---------------------------------------------
proto   user            worker          listen                      processes    status           
tcp     darkterminal    none            websocket://0.0.0.0:2112    1             [OK]            
--------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.

如果您想要更有效的东西,并且不要重复上述命令,请使用supervisordsystemdnssm(Windows用户的NSSM - the Non-Sucking Service Manager


如果您发现此博客很有用,并向世界的另一面开放了主意,并想给我一杯咖啡或成为我的Github帐户上的赞助商:

Buy Me ☕ Become Sponsor ❤

对于赞助商/捐助者,您的名字将包含在即将发布的文章中,因此请确保从您的开发,Twitter,Github或您拥有的任何其他社交媒体帐户中提供您的用户名。

Ask Me Anything 💬