首次提交

unocss
chris 10 months ago
commit 2d417b1472

5
.gitignore vendored

@ -0,0 +1,5 @@
node_modules/
Android-SDK*/
uni_modules/
unpackage/

@ -0,0 +1,20 @@
{
"version" : "1.0",
"configurations" : [
{
"openVueDevtools" : true,
"playground" : "standard",
"type" : "uni-app:app-android"
},
{
"app-plus" : {
"launchtype" : "local"
},
"type" : "uniCloud"
},
{
"openVueDevtools" : true,
"type" : "uni-app:app-ios"
}
]
}

@ -0,0 +1,63 @@
<script setup>
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
import { onBeforeUnmount } from 'vue'
// #ifdef APP
import { useSystemStore } from '@/store/system.js';
const systemStore = useSystemStore()
// #endif
onLaunch(() => {
console.log('App Launch')
// #ifdef APP
const appInfo = uni.getAppBaseInfo()
systemStore.setAppInfo(appInfo)
// #endif
})
onShow(() => {
console.log('App Show')
})
onHide(() => {
console.log('App Hide')
})
onBeforeUnmount(() => {
uni.removeStorage({ key: 'uId' })
})
// export default {
// onLaunch: function() {
// console.log('App Launch')
// },
// onShow: function() {
// console.log('App Show')
// // #ifdef APP
// checkUpdate().then(res => {
// console.log('', res)
// }).catch(error => {
// console.log("", error)
// })
// // #endif
// const appInfo = uni.getAppBaseInfo()
// console.log(appInfo)
// systemStore.setAppInfo(appInfo)
// },
// onHide: function() {
// console.log('App Hide')
// },
// beforeUnmount () {
// uni.removeStorage({ key: 'uId' })
// }
// }
</script>
<style>
#app {
min-height: 100vh;
background-color: #efeff4;
/* background-color: #000; */
/* height: auto; */
}
</style>

@ -0,0 +1,43 @@
import request from '@/utils/network/request';
/**
* 登录接口
* @param {Object} params
*/
export function login (params) {
return request({
url: '/api/user.ashx?act=studentlogin',
method: 'GET',
data: params
})
}
/**
* 登出接口
*/
export function logout () {
return request({
url: '',
method: 'GET'
})
}
export function getImgData(url) {
return new Promise((resolve, reject) => {
uni.request({
url,
method: 'GET',
responseType: 'arraybuffer',
success: (e) => {
const imgData = uni.arrayBufferToBase64(e.data)
const imgBase64 = 'data:image/png;base64,' + imgData
// resolve(e.data)
resolve(imgBase64)
},
fail: (error) => {
console.log(error)
reject(error)
}
})
})
}

@ -0,0 +1,39 @@
import request from '@/utils/network/request';
/**
* 获取考试列表
* @param {Object} data
* { studentid: 学生id, status: 考试状态进行中1 已结束 2 }
*/
export function getExamList(data, config = {}) {
return request({
url: '/api/user.ashx?act=studentexamlist',
method: 'GET',
data
}, config)
}
/**
* 获取考试详情
* @param {Object} data
* { examid: 考试id studentid 学生id }
*/
export function getExamInfo(data, config = {}) {
return request({
url: '/api/user.ashx?act=studentexaminfo',
method: 'GET',
data
})
}
/**
* 上传考试图片
*/
export function uploadExamImg(data, config = {}) {
const { userId, examId, file } = data;
return request({
url: `/api/user.ashx?act=subexaminfo&studentid=${userId}&examid=${examId}`,
method: 'POST',
data: file
}, config)
}

@ -0,0 +1,27 @@
import request from '@/utils/network/request.js';
/**
* 获取试题列表
* @param {Object} data
* { subjectid: 科目id gradeid 年纪id }
*/
export function getQuestionList (data, config = {}) {
return request({
url: '/api/user.ashx?act=questionlist',
method: 'GET',
data
}, config)
}
/**
* 获取试题列表
* @param {Object} data
* { questionid: 试题id }
*/
export function getQuestionInfo (data, config = {}) {
return request({
url: '/api/user.ashx?act=questioninfo',
method: 'GET',
data
}, config)
}

@ -0,0 +1,42 @@
import request from '@/utils/network/request';
/**
* 获取学校列表
*/
export function getSchoolList() {
return request({
url: '/api/user.ashx?act=getschoollist',
method: 'GET'
})
}
/**
* 获取年纪列表
*/
export function getGradeList () {
return request({
url: '/api/user.ashx?act=getgradelist',
method: 'GET'
})
}
/**
* 获取科目列表
*/
export function getSubjectList () {
return request({
url: '/api/user.ashx?act=getsubjectlist',
method: 'GET'
})
}
/**
* 获取知识点
*/
export function getKnowledgeList (data) {
return request({
url: '/api/user.ashx?act=getknowledgelist',
method: 'GET',
data
})
}

@ -0,0 +1,39 @@
import request from '@/utils/network/request';
/**
* 获取作业列表
* @param {Object} data
* { studentid: 学生id, status: 作业状态已提交1 未提交 2 }
*/
export function getTaskList(data, config = {}) {
return request({
url: '/api/user.ashx?act=studenthomeworklist',
method: 'GET',
data
}, config)
}
/**
* 获取作业详情
* @param {Object} data
* { taskid: 考试id studentid 学生id }
*/
export function getTaskInfo(data, config = {}) {
return request({
url: '/api/user.ashx?act=studenthomeworkinfo',
method: 'GET',
data
})
}
/**
* 上传作业图片
*/
export function uploadTaskImg(data, config = {}) {
const { userId, workId, file } = data;
return request({
url: `/api/user.ashx?act=subworkinfo&studentid=${userId}&workid=${workId}`,
method: 'POST',
data: file
}, config)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

@ -0,0 +1,4 @@
@import './common/config.scss';
@import './common/function.scss';
@import './common/mixins.scss';
@import './common/setting.scss';

@ -0,0 +1,4 @@
$namespace: 'ch';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';

@ -0,0 +1,43 @@
@function selectorToString ($selector) {
$selector: inspect($selector);
$selector: str-slice($selector, 2, -2);
@return $selector;
}
// --
@function containsModifier($selector) {
$selector: selectorToString($selector);
@if (str-index($selector, $modifier-separator)) {
@return true;
} @else {
@return false;
}
}
// .is
@function containWhenFlag($selector) {
$selector: selectorToString($selector);
@if (str-index($selector, '.' + $state-prefix)) {
@return true;
} @else {
@return false;
}
}
//
@function containPseudoClass($selector) {
$selector: selectorToString($selector);
@if (str-index($selector, ':')) {
@return true;
} @else {
@return false;
}
}
// -- .is
@function hitAllSpecialNestRule($selector) {
@return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}

@ -0,0 +1,51 @@
//
@mixin b($block) {
$B: $namespace + '-' + $block !global;
.#{$B} {
@content;
}
}
//
@mixin e($element) {
$E: $element !global;
$selector: &;
$currentSelector: '';
@each $unit in $element {
$currentSelector: #{$currentSelector + '.' + $B + $element-separator + $unit + ','}
}
@if hitAllSpecialNestRule($selector) {
@at-root {
#{$selector} {
#{$currentSelector} {
@content;
}
}
}
} @else {
@at-root {
#{$currentSelector} {
@content;
}
}
}
}
//
@mixin m($modifier) {
$selector: &;
$currentSelector: '';
@each $unit in $modifier {
$currentSelector: #{$currentSelector + $selector + $modifier-separator + $unit + ','}
}
@at-root {
#{$currentSelector} {
@content;
}
}
}

@ -0,0 +1 @@
$main-width: 1400px;

@ -0,0 +1,5 @@
@import './content/base.scss';
@import './content/atoms.scss';
@import './content/layout.scss';
@import './content/module.scss';
@import './content/cover.scss';

@ -0,0 +1,48 @@
@for $i from 1 through 10 {
$pd: $i*5;
$mg: $i*5;
.pd-#{$pd} {
padding: $pd + px;
}
.pd-t-#{$pd} {
padding-top: $pd + px;
}
.pd-b-#{$pd} {
padding-bottom: $pd + px;
}
.pd-l-#{$pd} {
padding-left: $pd + px;
}
.pd-r-#{$pd} {
padding-right: $pd + px;
}
.mg-#{$mg} {
margin: $mg + px;
}
.mg-t-#{$mg} {
margin-top: $mg + px;
}
.mg-b-#{$mg} {
margin-bottom: $mg + px;
}
.mg-l-#{$mg} {
margin-left: $mg + px;
}
.mg-r-#{$mg} {
margin-right: $mg + px;
}
}
.ptr {
cursor: pointer;
}

@ -0,0 +1,2 @@
@import './base/reset.scss';
@import './base/typography.scss';

@ -0,0 +1,67 @@
*,
*::before,
*::after {
box-sizing: border-box;
}
// a {
// text-decoration: none;
// color : inherit;
// cursor : pointer;
// }
// button {
// background-color: transparent;
// color : inherit;
// border-width : 0;
// padding : 0;
// cursor : pointer;
// }
// figure {
// margin: 0;
// }
// input::-moz-focus-inner {
// border : 0;
// padding: 0;
// margin : 0;
// }
// ul,
// ol,
// dd {
// margin : 0;
// padding : 0;
// list-style: none;
// }
h1,
h2,
h3,
h4,
h5,
h6 {
margin : 0;
font-size : inherit;
font-weight: inherit;
}
p {
margin: 0;
}
// cite {
// font-style: normal;
// }
// fieldset {
// border-width: 0;
// padding : 0;
// margin : 0;
// }
body {
margin: 0;
font-size: $uni-font-size-base;
}

@ -0,0 +1,64 @@
// :root {
// --vt-c-white: #ffffff;
// --vt-c-white-soft: #f8f8f8;
// --vt-c-white-mute: #f2f2f2;
// --vt-c-black: #181818;
// --vt-c-black-soft: #222222;
// --vt-c-black-mute: #282828;
// --vt-c-indigo: #2c3e50;
// --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
// --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
// --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
// --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
// --vt-c-text-light-1: var(--vt-c-indigo);
// --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
// --vt-c-text-dark-1: var(--vt-c-white);
// --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
// }
// /* semantic color variables for this project */
// :root {
// --color-background: var(--vt-c-white);
// --color-background-soft: var(--vt-c-white-soft);
// --color-background-mute: var(--vt-c-white-mute);
// --color-border: var(--vt-c-divider-light-2);
// --color-border-hover: var(--vt-c-divider-light-1);
// --color-heading: var(--vt-c-text-light-1);
// --color-text: var(--vt-c-text-light-1);
// --section-gap: 160px;
// }
// @media (prefers-color-scheme: dark) {
// :root {
// --color-background: var(--vt-c-black);
// --color-background-soft: var(--vt-c-black-soft);
// --color-background-mute: var(--vt-c-black-mute);
// --color-border: var(--vt-c-divider-dark-2);
// --color-border-hover: var(--vt-c-divider-dark-1);
// --color-heading: var(--vt-c-text-dark-1);
// --color-text: var(--vt-c-text-dark-2);
// }
// }
// body {
// min-height: 100vh;
// color: var(--color-text);
// background: var(--color-background);
// transition: color 0.5s, background-color 0.5s;
// line-height: 1.5;
// font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
// Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
// font-size: 14px;
// text-rendering: optimizeLegibility;
// -webkit-font-smoothing: antialiased;
// -moz-osx-font-smoothing: grayscale;
// }

@ -0,0 +1,13 @@
@import './cover/button.scss';
@import './cover/input.scss';
@import './cover/form.scss';
@import './cover/section.scss';
@import './cover/list.scss';
// @import './cover/nav-bar.scss';
@import './cover/segmented-control.scss';
@import './cover/select.scss';
@import './cover/base.scss';
@import './cover/check-list.scss';
@import './cover/checkbox.scss';
@import './cover/pagination.scss';
@import './cover/load-more.scss';

@ -0,0 +1,4 @@
text {
font-size: $uni-font-size-base;
line-height: $uni-font-size-base * 1.2;
}

@ -0,0 +1,9 @@
uni-button {
font-size: $uni-font-size-base!important;
// height: 66px;
// line-height: 66px!important;
}
uni-button[size="mini"] {
font-size: $uni-font-size-sm!important;
}

@ -0,0 +1,10 @@
.checklist-box {
&.is--tag {
padding: 20px 35px!important;
}
}
.checklist-text {
font-size: $uni-font-size-base!important;
line-height: $uni-font-size-base!important;
}

@ -0,0 +1,10 @@
.uni-checkbox-input {
height: 34px!important;
width: 34px!important;
svg {
font-size: 40px!important;
width: 34px!important;
height: 34px!important;
}
}

@ -0,0 +1,9 @@
.uni-forms-item {
&.is-direction-left {
margin-bottom: 28px!important;
}
.uni-forms-item__error {
font-size: 22px!important;
}
}

@ -0,0 +1,16 @@
$input-font-size: 34px;
.uni-easyinput__content-input {
height: 68px!important;
font-size: $input-font-size!important;
}
.uni-easyinput__placeholder-class {
font-size: $uni-font-size-base!important;
}
.uni-easyinput {
.uni-icons {
font-size: $input-font-size!important;
}
}

@ -0,0 +1,20 @@
.uni-list-item__container {
padding: 20rpx 26rpx!important;
padding-left: 26rpx!important;
}
.uni-list-item__content-title {
font-size: $uni-font-size-base!important;
}
.uni-list-item__content-note {
font-size: $uni-font-size-sm!important;
}
.uni-icon-wrapper {
font-size: $uni-font-size-sm!important;
}
.uni-list-item__extra-text {
font-size: $uni-font-size-base!important;
}

@ -0,0 +1,7 @@
.uni-load-more {
height: $uni-font-size-mini * 3!important;
}
.uni-load-more__text {
font-size: $uni-font-size-mini!important;
}

@ -0,0 +1,10 @@
.uni-pagination__total {
font-size: $uni-font-size-mini!important;
}
.uni-pagination__btn,
.uni-pagination__child-btn,
.uni-pagination__num {
font-size: $uni-font-size-mini!important;
line-height: $uni-font-size-mini * 1.5!important;
}

@ -0,0 +1,22 @@
// .uni-section-header {
// padding: 20rpx 16rpx !important;
// }
// .uni-section__content-title {
// font-size: 24rpx!important;
// }
.uni-section__content-title {
font-size: $uni-font-size-base!important;
}
.uni-section-header__decoration {
&.line {
height: $uni-font-size-base - 8px!important;
margin-right: 12px!important;
}
}
.uni-section-header {
padding: 20px!important;
}

@ -0,0 +1,8 @@
.segmented-control {
height: 56px!important;
.segmented-control__text {
line-height: 46px;
font-size: $uni-font-size-base!important;
}
}

@ -0,0 +1,22 @@
.uni-select__input-placeholder,
.uni-select__selector-item,
.uni-select__input-text{
font-size: $uni-font-size-base!important;
}
.uni-select__input-box,
.uni-select {
height: 66px!important;
}
.uni-select__selector-item {
line-height: 55px!important;
}
.uni-select {
background-color: #fff;
.uni-icons {
font-size: 32px!important;
}
}

@ -0,0 +1 @@
@import './layout/flex.scss';

@ -0,0 +1,73 @@
@include b('flex') {
display: flex;
@include m('row') {
flex-direction: row;
}
@include m('row-reverse') {
flex-direction: row-reverse;
}
@include m('column') {
flex-direction: column;
}
@include m('column-reverse') {
flex-direction: column-reverse;
}
@include m('wrap') {
flex-wrap: wrap;
}
@include m('wrap-reverse') {
flex-wrap: wrap-reverse;
}
@each $a in ['between', 'around', 'center', 'start', 'end '] {
@if $a == 'between' or $a == 'around' {
@include m('justify-#{$a}') {
justify-content: space-#{$a};
}
} @else {
@include m('justify-#{$a}') {
justify-content: #{$a};
}
}
}
@each $a in ['stretch', 'center', 'start', 'end ', 'baseline'] {
@if $a == 'start' or $a == 'end' {
@include m('items-#{$a}') {
align-items: flex-#{$a};
}
} @else {
@include m('items-#{$a}') {
align-items: #{$a};
}
}
}
@each $a in ['stretch', 'center', 'start', 'end ', 'between', 'around'] {
@if $a == 'start' or $a == 'end' {
@include m('align-#{$a}') {
align-content: flex-#{$a};
}
} @else if $a == 'between' or $a == 'around'{
@include m('align-#{$a}') {
align-content: space-#{$a};
}
} @else {
@include m('align-#{$a}') {
align-content: #{$a};
}
}
}
@for $i from 1 through 24 {
@include m(#{$i}) {
flex: #{$i};
}
}
}

@ -0,0 +1,3 @@
@import './module/form.scss';
@import './module/avatar.scss';
@import './module/ep-footer.scss'

@ -0,0 +1,42 @@
$boxSize-small: 80px;
$boxSize-middle: 120px;
$boxSize-large: 150px;
@include b('avatar-uploader') {
@include e('box') {
width: $boxSize-middle;
height: $boxSize-middle;
overflow: hidden;
border-radius: 4px;
border: 1px dashed var(--el-border-color-dark);
background-color: var(--el-fill-color-lighter);
display: flex;
justify-content: center;
align-items: center;
@include m('middle') {
width: $boxSize-middle;
height: $boxSize-middle;
}
@include m('small') {
width: $boxSize-small;
height: $boxSize-small;
}
@include m('large') {
width: $boxSize-large;
height: $boxSize-large;
}
}
@include e('img') {
max-width: 100%;
max-height: 100%;
}
@include e('icon') {
font-size: 20px;
color: var(--el-text-color-secondary )
}
}

@ -0,0 +1,38 @@
.ep-footer {
background-color: black;
}
.ep-footer__list {
color: #fff;
font-size: 12px;
padding: 20px 0;
display: flex;
margin-bottom: 20px;
}
.ep-footer__module {
margin-top: 30px;
margin-bottom: 30px;
+ .ep-footer__module {
margin-left: 80px;
}
}
.ep-footer__title {
font-weight: bold;
margin-bottom: 10px;
font-size: 14px;
}
.ep-footer__item {
line-height: 22px;
display: flex;
flex-direction: column;
}
.ep-footer__copyright {
color: #fff;
line-height: 40px;
font-size: 12px;
text-align: center;
}

@ -0,0 +1,8 @@
$explainFontSize: 12px;
.form-item-explain {
font-size: $explainFontSize;
color: var(--el-color-info-dark-2);
line-height: 1.5;
margin-top: $explainFontSize * 1.4;
}

@ -0,0 +1,2 @@
@import './common.scss';
@import './content.scss';

BIN
components/.DS_Store vendored

Binary file not shown.

@ -0,0 +1,21 @@
<template>
<view class="ch-flex-item" :class="flexCls">
<slot></slot>
</view>
</template>
<script setup>
import { computed, defineProps } from 'vue';
const props = defineProps([
'flex'
])
const flexCls = computed(() => {
return props.flex ? `ch-flex--${props.flex}` : ''
})
</script>
<style lang="scss" scoped>
@import './ch-flex-item.scss'
</style>

@ -0,0 +1,30 @@
<template>
<view class="ch-flex" :class="flexClassList">
<slot></slot>
</view>
</template>
<script setup>
import { computed, defineProps } from 'vue'
const props = defineProps([
'direction',
'wrap',
'justify',
'items',
'align'
])
const flexClassList = computed(() => {
const direction = props.direction ? `ch-flex--${props.direction}` : '';
const wrap = props.wrap ? `ch-flex--${props.wrap}` : '';
const justify = props.justify ? `ch-flex--justify-${props.justify}` : '';
const items = props.items ? `ch-flex--items-${props.items}` : '';
const align = props.align ? `ch-flex--algin-${props.align}` : '';
return Array.from(new Set([direction, wrap, justify, items, align])).join(' ');
})
</script>
<style lang="scss" scoped>
@import './ch-flex.scss';
</style>

@ -0,0 +1,46 @@
.img-item,
.select-btn {
height: 300rpx;
width: 300rpx;
border-radius: 5rpx;
overflow: hidden;
border: 1px solid $uni-border-color;
margin: 10px;
}
.img-item {
position: relative;
+ .img-item {
margin-left: 10px;
}
image {
height: 100%;
width: 100%;
pointer-events: none;
}
.del-btn {
position: absolute;
top: 7px;
right: 2px;
z-index: 99;
}
}
.img-list {
display: flex;
flex-wrap: wrap;
}
.select-btn {
margin-left: 10px;
display: flex;
justify-content: center;
align-items: center;
.uni-icons {
color: $uni-border-color!important;
}
}

@ -0,0 +1,152 @@
<template>
<view class="ch-image-uploader">
<ch-flex class="img-list" justify="center" items="center" @click.native="handleSelect">
<block v-for="item in imageList" key="item.name">
<view class="img-item" data-type="img">
<uni-icons type="clear" class="del-btn" size="50" v-if="showClose" data-type="delete" @click="delImg(item.name)"></uni-icons>
<image :src="item.path" mode="aspectFit"></image>
</view>
</block>
<view class="select-btn" data-type="btn" v-if="showBtn">
<uni-icons type="plusempty" data-type="add" size="70"/>
</view>
</ch-flex>
</view>
</template>
<script setup name="ch-image-uploader">
import { ref, defineProps, reactive, toRefs, watchEffect, watch, computed, defineEmits, defineExpose } from 'vue';
const emit = defineEmits(['update:modelValue'])
defineExpose({
upload
})
const props = defineProps({
limit: {
type: Number,
default: 8
},
extList: {
type: Array,
default: () => ['png', 'jepg', 'jpg']
},
sourceType: {
type: Array,
default: () => ['album', 'camera']
},
showClose: {
type: Boolean,
default: true
},
url: {
type: String,
default: ''
},
modelValue: {
type: Array,
default: () => []
}
})
const data = reactive({
imageList: []
})
const { imageList } = toRefs(data);
const showBtn = computed(() => {
return (props.limit <= 1 && imageList.value.length <= 0)
|| (props.limit > 1 && imageList.value.length <= props.limit - 1)
})
watchEffect(() => {
imageList.value = props.modelValue;
})
function handleSelect (e) {
const type = e.target.dataset.type || false;
if (!type || (type === 'img' && props.limit > 1)) return;
if (type === 'delete') {
delImg();
return;
}
uni.chooseImage({
count: props.limit,
extension: props.extList,
sourceType: props.sourceType,
success: (res) => {
if (props.limit > 1) {
const newImageList = filterImage(res.tempFiles)
imageList.value = [...imageList.value, ...newImageList]
} else {
imageList.value = res.tempFiles
}
emit('update:modelValue', imageList.value)
},
fail: (res) => {
uni.showToast({
icon: 'error',
title: '选择失败'
})
}
})
}
//
function upload () {
const files = formatImageFiles(imageList.value);
console.log(files)
return new Promise((resolve, reject) => {
uni.uploadFile({
url: props.url,
files,
name: 'task-image',
success: (uploadFileRes) => {
uni.showToast({
title: '上传成功'
})
resolve(uploadFileRes)
},
fail: (error) => {
uni.showToast({
icon: 'fail',
title: '上传失败'
})
reject(error)
console.log('上传失败', error)
}
})
})
}
function delImg (delName) {
imageList.value = imageList.value.filter(item => item.name !== delName)
}
// image
function formatImageFiles (fileList) {
return imageList.value.map(item => {
return {
name: item.name,
size: item.size,
uri: item.path,
file: item
}
})
}
//
function filterImage (images) {
return images.filter(item => {
const result = imageList.value.find(img => img.name === item.name );
return !result;
})
}
</script>
<style lang="scss" scoped>
@import './ch-image-uploader.scss';
</style>

@ -0,0 +1,26 @@
button {
line-height: 1.7;
}
.nav-title {
font-size: $uni-font-size-lg;
align-self: center;
margin-left: 20px;
font-weight: bold;
}
.uni-navbar {
position: relative;
}
.uni-navbar::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 1px;
background-color: #fff;
background: linear-gradient(90deg, #000, #fff, #000);
z-index: 99;
}

@ -0,0 +1,41 @@
<template>
<uni-nav-bar :height="height" fixed statusBar="true" :rightWidth="rWidth" :border="false" :dark="true">
<block v-slot:left>
<button type="primary" @click="clickBack">
<uni-icons type="undo-filled" color="#fff" size="30"></uni-icons>
</button>
</block>
<view class="nav-title" v-if="props.title">{{props.title}}</view>
<slot v-else></slot>
<block v-slot:right>
<slot name="right"></slot>
</block>
</uni-nav-bar>
</template>
<script setup name="ChNavBar">
import { reactive, defineProps, computed } from 'vue';
import { back } from '@/useModules/useNavigate.js'
const props = defineProps([
'title',
'height',
'backFn',
'rightWidth'
])
const data = reactive({
})
const rWidth = computed(() => `${props.rightWidth || 110}rpx`)
function clickBack () {
const fn = props.backFn || back;
fn();
}
</script>
<style lang="scss" scoped>
@import './ch-nav-bar.scss';
</style>

@ -0,0 +1,11 @@
.ch-nav-btn {
background-color: $uni-color-primary;
font-size: $uni-font-size-sm;
line-height: 1.8;
border-radius: 8px;
padding: 0 12rpx;
&:active {
background-color: transparentize($uni-color-primary, 0.3);
}
}

@ -0,0 +1,21 @@
<template>
<view class="ch-nav-btn">
<uni-icons v-if="props.showIcon || props.icon" :type="props.icon" :size="props.iconSize" :color="props.color"/>
<slot/>
</view>
</template>
<script setup name="ChNavBtn">
import { defineProps } from 'vue';
const props = defineProps([
'icon',
'iconSize',
'showIcon',
'color'
])
</script>
<style lang="scss" scoped>
@import 'ch-nav-btn.scss';
</style>

@ -0,0 +1,279 @@
class Bluetooth {
constructor() {
this.isOpenBle = false;
this.deviceId = "";
this.serviceId = "";
this.writeId = "";
this.notifyId = "";
this.openBluetoothAdapter();
}
showToast(title) {
uni.showToast({
title: title,
icon: 'none',
'duration': 2000
});
}
//初始化蓝牙模块,检查蓝牙是否已打开
openBluetoothAdapter() {
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: res => {
this.isOpenBle = true;
resolve(res);
},
fail: err => {
setTimeout(function() {
uni.showToast({
title:"请打开蓝牙和定位功能",
icon:"none"
})
}, 1000);
reject(err);
},
});
});
}
//搜索周边蓝牙设备
startBluetoothDevicesDiscovery() {
if (!this.isOpenBle) {
this.showToast(`请打开蓝牙和定位功能`)
return;
}
let self = this;
uni.showLoading({
title: '正在搜索蓝牙设备'
})
return new Promise((resolve, reject) => {
setTimeout(() => {
uni.startBluetoothDevicesDiscovery({
success: res => {
resolve(res)
},
fail: res => {
self.showToast(`搜索设备失败` + JSON.stringify(err));
reject(err);
}
})
}, 300);
});
}
//停止搜索周报蓝牙设备
stopBluetoothDevicesDiscovery() {
let self = this;
return new Promise((resolve, reject) => {
uni.stopBluetoothDevicesDiscovery({
success: e => {
uni.hideLoading();
},
fail: e => {
uni.hideLoading();
self.showToast('停止搜索失败,请重试');
}
})
});
}
//连接设备
createBLEConnection() {
let deviceId = this.deviceId;
let self = this;
return new Promise((resolve, reject) => {
uni.createBLEConnection({
deviceId,
success: (res) => {
console.log("成功连接设备" + JSON.stringify(res));
resolve(res)
},
fail: err => {
self.showToast(`设备连接失败` + JSON.stringify(err));
reject(err);
}
})
});
}
//获取蓝牙设备所有服务(service)
getBLEDeviceServices() {
let _serviceList = [];
let deviceId = this.deviceId;
let self = this;
return new Promise((resolve, reject) => {
setTimeout(() => {
uni.getBLEDeviceServices({
deviceId,
success: res => {
//console.log('获取服务成功');
//console.log(res.services);
for (let service of res.services) {
if (service.isPrimary) {
_serviceList.push(service);
}
}
if(res.services.length<=0){
self.showToast('成功获取服务,但没有可用服务');
}
resolve(_serviceList)
},
fail: err => {
//设备已连接,但未能获取设备服务,也有可能连接处于断开状态
self.showToast('未获取蓝牙设备相关服务');
reject(err);
},
})
}, 2000);
});
}
//获取蓝牙设备某个服务中所有特征值(characteristic)
getBLEDeviceCharacteristics() {
let deviceId = this.deviceId;
let serviceId = this.serviceId;
let self = this;
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: res => {
for (let _obj of res.characteristics) {
//获取notify
if (_obj.properties.notify) {
self.notifyId = _obj.uuid;
uni.setStorageSync('notifyId', self.notifyId);
}
//获取writeId
if (_obj.properties.write) {
self.writeId = _obj.uuid;
uni.setStorageSync('writeId', self.writeId);
}
}
let result = {
'notifyId': self.notifyId,
'writeId': self.writeId
};
//成功获取到设备的服务特征值,可以测试设备功能了
//self.showToast('已获取打印机服务,请测试打印功能')
resolve(result)
},
fail: err => {
//已连接设备,但未能获取设备服务
self.showToast('未能获取设备相关服务,请重试')
reject(err);
}
})
});
}
//断开蓝牙链接
closeBLEConnection() {
let deviceId = this.deviceId;
uni.closeBLEConnection({
deviceId,
success(res) {
console.log('蓝牙连接已断开')
}
})
}
//启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值
notifyBLECharacteristicValue() {
let deviceId = this.deviceId;
let serviceId = this.serviceId;
let characteristicId = this.notifyId;
//特征值变化时,异步通知提示
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
deviceId,
serviceId,
characteristicId,
success(res) {
//监听低功耗蓝牙设备的特征值变化事件。必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification
uni.onBLECharacteristicValueChange(function(res) {
});
},
fail(res) {
console.log('notifyBLECharacteristicValueChange failed:' + res.errMsg);
}
});
}
//向低功耗蓝牙设备特征值中写入二进制数据。注意:必须设备的特征值支持 write 才可以成功调用
writeBLECharacteristicValue(buffer) {
let deviceId = this.deviceId;
let serviceId = this.serviceId;
let characteristicId = this.writeId;
//console.log("当前连接蓝牙设备服务信息: " + JSON.stringify(this));
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value: buffer,
success(res) {
//console.log('message发送成功', JSON.stringify(res));
resolve(res);
},
fail(err) {
console.log('message发送失败', JSON.stringify(err));
reject(err);
}
});
});
}
//关闭蓝牙连接,想要连接要重新启动
closeBluetoothAdapter() {
uni.closeBluetoothAdapter({
success: res => {
//console.log(res)
}
});
}
//若APP在之前已有搜索过某个蓝牙设备并成功建立连接可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需进行搜索操作。
reconnect() {
(async () => {
try {
this.deviceId = this.deviceId || uni.getStorageSync("deviceId");//设备id
this.serviceId = this.serviceId || uni.getStorageSync("serviceId");//服务id
this.notifyId = this.notifyId || uni.getStorageSync("notifyId");//
this.writeId = this.writeId || uni.getStorageSync("writeId");//写入二进制数据 特征值id
//连接蓝牙设备
await this.createBLEConnection();
//获取蓝牙设备服务
//await this.getBLEDeviceServices();
uni.hideLoading()
if(!this.serviceId || this.serviceId == ''){
//this.showToast('打印服务已断开,请到开发设置重新搜索蓝牙设备')
uni.showModal({
title: '打印机断开提示',
content: '打印服务已断开,请到开发设置重新搜索蓝牙设备'
});
}else if(!this.writeId || this.writeId == ''){
//重新获取蓝牙设备服务特征值
await this.getBLEDeviceCharacteristics();
this.showToast('成功连接打印机')
}else {
this.showToast('成功连接打印机')
}
} catch (err) {
uni.hideLoading()
console.log("err: " + JSON.stringify(err));
}
})();
}
}
export default Bluetooth;

@ -0,0 +1,290 @@
<!-- 蓝牙和位置都需要打开-->
<template>
<view class="content">
<button type="primary" @tap="startBluetoothDeviceDiscovery"></button>
<button type="warn" @tap="stopBluetoothDevicesDiscovery"></button>
<scroll-view class="device_list" scroll-y="true" show-scrollbar="true">
<radio-group>
<view v-for="(item,index) in devicesList" :key="index" class="device_item" v-if="item.name.length>0">
<view style="font-size: 32rpx; color: #333;">
<radio :value="item.deviceId" @tap="select_deviceId(item)" />{{item.name }}
</view>
<view style="font-size: 20rpx">信号强度: {{item.RSSI}}dBm ({{Math.max(100+item.RSSI,0)}}%)</view>
<view style="font-size: 20rpx">设备名称: {{item.deviceId}}</view>
<!-- <view style="font-size: 20rpx">可用服务数量: {{item.advertisServiceUUIDs.length || 0}}</view> -->
<radio-group v-if="deviceId===item.deviceId">
<view v-for="(service,service_index) in serviceList" :key="service_index" style="font-size: 20rpx">
<radio style="transform:scale(0.7)" :value="service.uuid" @tap="select_service(service)" />{{service.uuid }}
</view>
</radio-group>
</view>
</radio-group>
</scroll-view>
<view style="text-align: center;margin-top: 10px;">
<view>当前连接设备:{{deviceId}}</view>
<view>服务:{{serviceId}}</view>
<!-- <view>设备特征值notifyId:{{notifyId}}</view> -->
<view>writeId:{{writeId}}</view>
<button type="primary" @click="reconnect()"></button>
</view>
<button type="primary" style="margin-top: 10px;" @tap="pickUpOnce"></button>
</view>
</template>
<script>
import PrinterJobs from './printerjobs.js'
import printerUtil from './printerutil.js'
import Bluetooth from './bluetooth.js'
let bluetooth = new Bluetooth();
export default {
components: {},
data() {
return {
isOpenBle: false, //false
devicesList: [], //
serviceList: [], //
deviceId: "", //
serviceId:"",//
notifyId:"",//
writeId:"",//
}
},
//
onUnload() {
console.log('关闭蓝牙连接')
bluetooth.closeBLEConnection();
bluetooth.closeBluetoothAdapter();
},
onLoad() {
this.deviceId = uni.getStorageSync("deviceId") || "暂无信息";
this.serviceId = uni.getStorageSync("serviceId") || "暂无信息";
this.notifyId = uni.getStorageSync("notifyId") || "暂无信息";
this.writeId = uni.getStorageSync("writeId") || "暂无信息";
bluetooth.openBluetoothAdapter();
},
methods: {
reconnect(){
uni.showLoading({
mask: true,
title: '正在连接设备'+this.deviceId
})
bluetooth.reconnect();
},
//
startBluetoothDeviceDiscovery() {
uni.showLoading({
title: '蓝牙搜索中'
})
let self = this;
self.devicesList = [];
setTimeout(() => {
uni.startBluetoothDevicesDiscovery({
success: res => {
uni.onBluetoothDeviceFound(devices => {
//console.log(": " + JSON.stringify(devices));
//,devicesList,
if (!self.devicesList.some(item => {
return item.deviceId === devices.devices[0].deviceId
})) {
self.devicesList.push(devices.devices[0])
}
});
},
fail: res => {
uni.hideLoading();
uni.showToast({
title:`搜索设备失败` + JSON.stringify(err),
icon:"none"
})
}
})
}, 200)
},
//
stopBluetoothDevicesDiscovery() {
uni.hideLoading();
bluetooth.stopBluetoothDevicesDiscovery();
},
//
async select_deviceId(item) {
this.deviceId = item.deviceId;
bluetooth.deviceId = item.deviceId;
uni.setStorageSync('deviceId', bluetooth.deviceId);
this.serviceList = [];
try {
//1.
await bluetooth.createBLEConnection();
uni.showLoading({
title: '正在获取蓝牙设备服务'
})
//2.
await bluetooth.getBLEDeviceServices().then(res=>{
uni.hideLoading()
this.serviceList = res;
});
} catch (e) {
uni.hideLoading()
console.log("e: " + JSON.stringify(e));
}
},
//
async select_service(res) {
let self = this;
bluetooth.serviceId = res.uuid;
uni.setStorageSync('serviceId', res.uuid);
try {
let result = await bluetooth.getBLEDeviceCharacteristics();
setTimeout(function() {
uni.showToast({
title:"已连接打印机,请测试打印",
icon:"none"
})
}, 500);
} catch (e) {
console.log("e: " + JSON.stringify(e));
}
},
//
pickUpOnce() {
uni.showToast({
title:"正在为您打印...",
mask:true,
icon:"none",
duration:2000
})
bluetooth.notifyBLECharacteristicValue();
let self = this;
setTimeout(() => {
self.writeBLECharacteristicValue();
}, 500);
},
//
async writeBLECharacteristicValue() {
// chengjn1314()
// chengjn1314()
// chengjn1314()
//
let printerJobs = new PrinterJobs();
printerJobs
.setSize(1, 1)
.print(printerUtil.fillAround('加油小票'))
.print('')
.setAlign('lt')
.print('油品名称92#汽油')
.print('用户卡号00000259')
.print('加油数量48.07升')
.print('油品价格8.05元')
.print('加油金额387元')
.print('结算方式:用户卡')
.print('卡余额413.87元')
.print('单位名称:供电局')
.print('油站名称:桂通石油桥头加油站')
.print('加油时间2022-10-13 113459')
.print('')
.print(printerUtil.fillAround('谢谢惠顾'))
.println()
;
let buffer = printerJobs.buffer();
this.printbuffs(buffer);
},
printbuffs(buffer) {
// 1.
// 2.20
//
const maxChunk = 20;
const delay = 20;
for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) {
let subPackage = buffer.slice(i, i + maxChunk <= length ? (i + maxChunk) : length);
setTimeout(this.printbuff, j * delay, subPackage);
}
},
printbuff(buffer) {
bluetooth.writeBLECharacteristicValue(buffer);
},
}
}
</script>
<style>
.content {}
page {
color: #333;
}
button {
margin: 10upx;
}
.devices_summary {
margin-top: 5rpx;
padding: 20rpx;
font-size: 30rpx;
}
.device_list {
margin: 5rpx 20rpx 5rpx 20rpx;
border: 1rpx solid #ddd;
border-radius: 10rpx;
background-color: #FdFdFd;
min-height: 0rpx;
max-height: 400rpx;
width: 700rpx;
}
.device_item {
border-bottom: 1rpx solid #ddd;
padding: 20rpx;
color: #666;
}
.device_item_hover {
background-color: rgba(0, 0, 0, .1);
}
.connected_info {
position: fixed;
bottom: 0;
width: 100%;
background-color: #F0F0F0;
padding: 10px;
padding-bottom: 20px;
margin-bottom: env(safe-area-inset-bottom);
font-size: 14px;
min-height: 100px;
box-shadow: 0px 0px 3px 0px;
}
.connected_info .operation {
position: absolute;
display: inline-block;
right: 30px;
}
</style>

@ -0,0 +1,193 @@
/**
* 修改自https://github.com/song940/node-escpos/blob/master/commands.js
* ESC/POS _ (Constants)
*/
var _ = {
LF: [0x0a],
FS: [0x1c],
FF: [0x0c],
GS: [0x1d],
DLE: [0x10],
EOT: [0x04],
NUL: [0x00],
ESC: [0x1b],
EOL: '\n',
};
/**
* [FEED_CONTROL_SEQUENCES Feed control sequences]
* @type {Object}
*/
_.FEED_CONTROL_SEQUENCES = {
CTL_LF: [0x0a], // Print and line feed
CTL_GLF: [0x4a, 0x00], // Print and feed paper (without spaces between lines)
CTL_FF: [0x0c], // Form feed
CTL_CR: [0x0d], // Carriage return
CTL_HT: [0x09], // Horizontal tab
CTL_VT: [0x0b], // Vertical tab
};
_.CHARACTER_SPACING = {
CS_DEFAULT: [0x1b, 0x20, 0x00],
CS_SET: [0x1b, 0x20]
};
_.LINE_SPACING = {
LS_DEFAULT: [0x1b, 0x32],
LS_SET: [0x1b, 0x33]
};
/**
* [HARDWARE Printer hardware]
* @type {Object}
*/
_.HARDWARE = {
HW_INIT: [0x1b, 0x40], // Clear data in buffer and reset modes
HW_SELECT: [0x1b, 0x3d, 0x01], // Printer select
HW_RESET: [0x1b, 0x3f, 0x0a, 0x00], // Reset printer hardware
};
/**
* [CASH_DRAWER Cash Drawer]
* @type {Object}
*/
_.CASH_DRAWER = {
CD_KICK_2: [0x1b, 0x70, 0x00], // Sends a pulse to pin 2 []
CD_KICK_5: [0x1b, 0x70, 0x01], // Sends a pulse to pin 5 []
};
/**
* [MARGINS Margins sizes]
* @type {Object}
*/
_.MARGINS = {
BOTTOM: [0x1b, 0x4f], // Fix bottom size
LEFT: [0x1b, 0x6c], // Fix left size
RIGHT: [0x1b, 0x51], // Fix right size
};
/**
* [PAPER Paper]
* @type {Object}
*/
_.PAPER = {
PAPER_FULL_CUT: [0x1d, 0x56, 0x00], // Full cut paper
PAPER_PART_CUT: [0x1d, 0x56, 0x01], // Partial cut paper
PAPER_CUT_A: [0x1d, 0x56, 0x41], // Partial cut paper
PAPER_CUT_B: [0x1d, 0x56, 0x42], // Partial cut paper
};
/**
* [TEXT_FORMAT Text format]
* @type {Object}
*/
_.TEXT_FORMAT = {
TXT_NORMAL: [0x1b, 0x21, 0x00], // Normal text
TXT_2HEIGHT: [0x1b, 0x21, 0x10], // Double height text
TXT_2WIDTH: [0x1b, 0x21, 0x20], // Double width text
TXT_4SQUARE: [0x1b, 0x21, 0x30], // Double width & height text
TXT_UNDERL_OFF: [0x1b, 0x2d, 0x00], // Underline font OFF
TXT_UNDERL_ON: [0x1b, 0x2d, 0x01], // Underline font 1-dot ON
TXT_UNDERL2_ON: [0x1b, 0x2d, 0x02], // Underline font 2-dot ON
TXT_BOLD_OFF: [0x1b, 0x45, 0x00], // Bold font OFF
TXT_BOLD_ON: [0x1b, 0x45, 0x01], // Bold font ON
TXT_ITALIC_OFF: [0x1b, 0x35], // Italic font ON
TXT_ITALIC_ON: [0x1b, 0x34], // Italic font ON
TXT_FONT_A: [0x1b, 0x4d, 0x00], // Font type A
TXT_FONT_B: [0x1b, 0x4d, 0x01], // Font type B
TXT_FONT_C: [0x1b, 0x4d, 0x02], // Font type C
TXT_ALIGN_LT: [0x1b, 0x61, 0x00], // Left justification
TXT_ALIGN_CT: [0x1b, 0x61, 0x01], // Centering
TXT_ALIGN_RT: [0x1b, 0x61, 0x02], // Right justification
};
/**
* [BARCODE_FORMAT Barcode format]
* @type {Object}
*/
_.BARCODE_FORMAT = {
BARCODE_TXT_OFF: [0x1d, 0x48, 0x00], // HRI barcode chars OFF
BARCODE_TXT_ABV: [0x1d, 0x48, 0x01], // HRI barcode chars above
BARCODE_TXT_BLW: [0x1d, 0x48, 0x02], // HRI barcode chars below
BARCODE_TXT_BTH: [0x1d, 0x48, 0x03], // HRI barcode chars both above and below
BARCODE_FONT_A: [0x1d, 0x66, 0x00], // Font type A for HRI barcode chars
BARCODE_FONT_B: [0x1d, 0x66, 0x01], // Font type B for HRI barcode chars
BARCODE_HEIGHT: function (height) { // Barcode Height [1-255]
return [0x1d, 0x68, height];
},
BARCODE_WIDTH: function (width) { // Barcode Width [2-6]
return [0x1d, 0x77, width];
},
BARCODE_HEIGHT_DEFAULT: [0x1d, 0x68, 0x64], // Barcode height default:100
BARCODE_WIDTH_DEFAULT: [0x1d, 0x77, 0x01], // Barcode width default:1
BARCODE_UPC_A: [0x1d, 0x6b, 0x00], // Barcode type UPC-A
BARCODE_UPC_E: [0x1d, 0x6b, 0x01], // Barcode type UPC-E
BARCODE_EAN13: [0x1d, 0x6b, 0x02], // Barcode type EAN13
BARCODE_EAN8: [0x1d, 0x6b, 0x03], // Barcode type EAN8
BARCODE_CODE39: [0x1d, 0x6b, 0x04], // Barcode type CODE39
BARCODE_ITF: [0x1d, 0x6b, 0x05], // Barcode type ITF
BARCODE_NW7: [0x1d, 0x6b, 0x06], // Barcode type NW7
BARCODE_CODE93: [0x1d, 0x6b, 0x48], // Barcode type CODE93
BARCODE_CODE128: [0x1d, 0x6b, 0x49], // Barcode type CODE128
};
/**
* [IMAGE_FORMAT Image format]
* @type {Object}
*/
_.IMAGE_FORMAT = {
S_RASTER_N: [0x1d, 0x76, 0x30, 0x00], // Set raster image normal size
S_RASTER_2W: [0x1d, 0x76, 0x30, 0x01], // Set raster image double width
S_RASTER_2H: [0x1d, 0x76, 0x30, 0x02], // Set raster image double height
S_RASTER_Q: [0x1d, 0x76, 0x30, 0x03], // Set raster image quadruple
};
/**
* [BITMAP_FORMAT description]
* @type {Object}
*/
_.BITMAP_FORMAT = {
BITMAP_S8: [0x1b, 0x2a, 0x00],
BITMAP_D8: [0x1b, 0x2a, 0x01],
BITMAP_S24: [0x1b, 0x2a, 0x20],
BITMAP_D24: [0x1b, 0x2a, 0x21]
};
/**
* [GSV0_FORMAT description]
* @type {Object}
*/
_.GSV0_FORMAT = {
GSV0_NORMAL: [0x1d, 0x76, 0x30, 0x00],
GSV0_DW: [0x1d, 0x76, 0x30, 0x01],
GSV0_DH: [0x1d, 0x76, 0x30, 0x02],
GSV0_DWDH: [0x1d, 0x76, 0x30, 0x03]
};
/**
* [BEEP description]
* @type {string}
*/
_.BEEP = [0x1b, 0x42]; // Printer Buzzer pre hex
/**
* [COLOR description]
* @type {Object}
*/
_.COLOR = {
0: [0x1b, 0x72, 0x00], // black
1: [0x1b, 0x72, 0x01] // red
};
/**
* [exports description]
* @type {[type]}
*/
module.exports = _;

File diff suppressed because one or more lines are too long

@ -0,0 +1,161 @@
const commands = require('./commands');
const gbk = require('./gbk');
const printerJobs = function() {
this._queue = Array.from(commands.HARDWARE.HW_INIT);
this._enqueue = function(cmd) {
this._queue.push.apply(this._queue, cmd);
}
};
/**
* 增加打印内容
* @param {string} content 文字内容
*/
printerJobs.prototype.text = function(content) {
if (content) {
let uint8Array = gbk.encode(content);
let encoded = Array.from(uint8Array);
this._enqueue(encoded);
}
return this;
};
/**
* 打印文字
* @param {string} content 文字内容
*/
printerJobs.prototype.print = function(content) {
this.text(content);
this._enqueue(commands.LF);
return this;
};
/**
* 打印文字并换行
* @param {string} content 文字内容
*/
printerJobs.prototype.println = function(content = '') {
return this.print(content + commands.EOL);
};
/**
* 设置对齐方式
* @param {string} align 对齐方式 LT/CT/RT
*/
printerJobs.prototype.setAlign = function(align) {
this._enqueue(commands.TEXT_FORMAT['TXT_ALIGN_' + align.toUpperCase()]);
return this;
};
/**
* 设置字体
* @param {string} family A/B/C
*/
printerJobs.prototype.setFont = function(family) {
this._enqueue(commands.TEXT_FORMAT['TXT_FONT_' + family.toUpperCase()]);
return this;
};
/**
* 设定字体尺寸
* @param {number} width 字体宽度 1~2
* @param {number} height 字体高度 1~2
*/
printerJobs.prototype.setSize = function(width, height) {
if (2 >= width && 2 >= height) {
this._enqueue(commands.TEXT_FORMAT.TXT_NORMAL);
if (2 === width && 2 === height) {
this._enqueue(commands.TEXT_FORMAT.TXT_4SQUARE);
} else if (1 === width && 2 === height) {
this._enqueue(commands.TEXT_FORMAT.TXT_2HEIGHT);
} else if (2 === width && 1 === height) {
this._enqueue(commands.TEXT_FORMAT.TXT_2WIDTH);
}
}
return this;
};
/**
* 设定字体是否加粗
* @param {boolean} bold
*/
printerJobs.prototype.setBold = function(bold) {
if (typeof bold !== 'boolean') {
bold = true;
}
this._enqueue(bold ? commands.TEXT_FORMAT.TXT_BOLD_ON : commands.TEXT_FORMAT.TXT_BOLD_OFF);
return this;
};
/**
* 设定是否开启下划线
* @param {boolean} underline
*/
printerJobs.prototype.setUnderline = function(underline) {
if (typeof underline !== 'boolean') {
underline = true;
}
this._enqueue(underline ? commands.TEXT_FORMAT.TXT_UNDERL_ON : commands.TEXT_FORMAT.TXT_UNDERL_OFF);
return this;
};
/**
* 设置行间距为 n 点行,默认值行间距是 30
* @param {number} n 0n255
*/
printerJobs.prototype.setLineSpacing = function(n) {
if (n === undefined || n === null) {
this._enqueue(commands.LINE_SPACING.LS_DEFAULT);
} else {
this._enqueue(commands.LINE_SPACING.LS_SET);
this._enqueue([n]);
}
return this;
};
/**
* 打印空行
* @param {number} n
*/
printerJobs.prototype.lineFeed = function(n = 1) {
return this.print(new Array(n).fill(commands.EOL).join(''));
};
/**
* 设置字体颜色需要打印机支持
* @param {number} color - 0 默认颜色黑色 1 红色
*/
printerJobs.prototype.setColor = function(color) {
this._enqueue(commands.COLOR[color === 1 ? 1 : 0]);
return this;
};
/**
* https://support.loyverse.com/hardware/printers/use-the-beeper-in-a-escpos-printers
* 蜂鸣警报需要打印机支持
* @param {number} n 蜂鸣次数,1-9
* @param {number} t 蜂鸣长短,1-9
*/
printerJobs.prototype.beep = function(n, t) {
this._enqueue(commands.BEEP);
this._enqueue([n, t]);
return this;
};
/**
* 清空任务
*/
printerJobs.prototype.clear = function() {
this._queue = Array.from(commands.HARDWARE.HW_INIT);
return this;
};
/**
* 返回ArrayBuffer
*/
printerJobs.prototype.buffer = function() {
return new Uint8Array(this._queue).buffer;
};
module.exports = printerJobs;

@ -0,0 +1,91 @@
// 打印机纸宽58mm页的宽度384字符宽度为1每行最多盛放32个字符
const PAGE_WIDTH = 384;
const MAX_CHAR_COUNT_EACH_LINE = 32;
/**
* @param str
* @returns {boolean} str是否全是中文
*/
function isChinese(str) {
return /^[\u4e00-\u9fa5]$/.test(str);
}
/**
* 返回字符串宽度(1个中文=2个英文字符)
* @param str
* @returns {number}
*/
function getStringWidth(str) {
let width = 0;
for (let i = 0, len = str.length; i < len; i++) {
width += isChinese(str.charAt(i)) ? 2 : 1;
}
return width;
}
/**
* 同一行输出str1, str2str1居左, str2居右
* @param {string} str1 内容1
* @param {string} str2 内容2
* @param {number} fontWidth 字符宽度 1/2
* @param {string} fillWith str1 str2之间的填充字符
*
*/
function inline(str1, str2, fillWith = ' ', fontWidth = 1) {
const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
// 需要填充的字符数量
let fillCount = lineWidth - (getStringWidth(str1) + getStringWidth(str2)) % lineWidth;
let fillStr = new Array(fillCount).fill(fillWith.charAt(0)).join('');
return str1 + fillStr + str2;
}
/**
* 用字符填充一整行
* @param {string} fillWith 填充字符
* @param {number} fontWidth 字符宽度 1/2
*/
function fillLine(fillWith = '-', fontWidth = 1) {
const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
return new Array(lineWidth).fill(fillWith.charAt(0)).join('');
}
/**
* 文字内容居中左右用字符填充
* @param {string} str 文字内容
* @param {number} fontWidth 字符宽度 1/2
* @param {string} fillWith str1 str2之间的填充字符
*/
function fillAround(str, fillWith = '-', fontWidth = 1) {
const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
let strWidth = getStringWidth(str);
// 内容已经超过一行了,没必要填充
if (strWidth >= lineWidth) {
return str;
}
// 需要填充的字符数量
let fillCount = lineWidth - strWidth;
// 左侧填充的字符数量
let leftCount = Math.round(fillCount / 2);
// 两侧的填充字符,需要考虑左边需要填充,右边不需要填充的情况
let fillStr = new Array(leftCount).fill(fillWith.charAt(0)).join('');
return fillStr + str + fillStr.substr(0, fillCount - leftCount);
}
// ArrayBuffer转16进度字符串示例
function ab2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join(',')
}
module.exports = {
inline: inline,
fillLine: fillLine,
fillAround: fillAround,
ab2hex:ab2hex,
};

@ -0,0 +1,6 @@
#### 已测试 芯烨、汉印、济强、佳博等多个品牌多款机型打印机,均能正常打印(含二维码)
#### 本插件为参考学习打印示例支持小程序和app但不同品牌打印机对打印指令支持不一样插件无法做到通用我的小程序已集成esccpcltspl指令打印
#### 您可以到我的小程序中的体验测试打印功能,如有打印二维码需求和打印你想要的模板,可咨询定制
#### 扫码进入微信小程序(选择 手机功能 --> 蓝牙打印机),快来试试呗!
#### 如果您遇到打印问题或者其他方面需求可添加微信chengjn1314(宁哥),欢迎骚扰~
![avatar](https://mp-a9f90fc5-628e-43f8-a370-00b89368841b.cdn.bspapp.com/cloudstorage/16ff40bb-d352-4f46-a9ed-2ef0406b19c0.jpg)

@ -0,0 +1,66 @@
const formatTime = date => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}
const formatNumber = n => {
n = n.toString()
return n[1] ? n : '0' + n
}
//4合1
function convert4to1(res) {
let arr = [];
for (let i = 0; i < res.length; i++) {
if (i % 4 == 0) {
let rule = 0.29900 * res[i] + 0.58700 * res[i + 1] + 0.11400 * res[i + 2];
if (rule > 200) {
res[i] = 0;
} else {
res[i] = 1;
}
arr.push(res[i]);
}
}
return arr;
}
//8合1
function convert8to1(arr) {
let data = [];
for (let k = 0; k < arr.length; k += 8) {
let temp = arr[k] * 128 + arr[k + 1] * 64 + arr[k + 2] * 32 + arr[k + 3] * 16 + arr[k + 4] * 8 + arr[k + 5] * 4 +
arr[k + 6] * 2 + arr[k + 7] * 1
data.push(temp);
}
return data;
}
//我的图片宽度是240那么拼接的指令就是[29, 118, 48, 0, 30, 0, 240, 0]
//我的图片宽度是160那么拼接的指令就是[29, 118, 48, 0, 20, 0, 160, 0]
//补充一点打印非二维码的图片宽度一定要是24的倍数不然打印也会出现乱码
function toArrayBuffer(res) {
let arr = convert4to1(res.data);
let data = convert8to1(arr);
let cmds = [].concat([27, 97, 1], [29, 118, 48, 0, 30, 0, 240, 0], data, [27, 74, 3], [27, 64]);
return new Uint8Array(cmds).buffer;
}
function zip_image(res) {
let arr = convert4to1(res.data);
let data = convert8to1(arr);
return data;
}
module.exports = {
formatTime: formatTime,
toArrayBuffer: toArrayBuffer,
zip_image: zip_image,
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,7 @@
import ChNavBar from '@/components/ch-nav-bar/ch-nav-bar.vue'
import ChNavBtn from '@/components/ch-nav-btn/ch-nav-btn.vue'
export default function registerComponents (app) {
app.component(ChNavBar.name, ChNavBar);
app.component(ChNavBtn.name, ChNavBtn);
}

@ -0,0 +1,218 @@
<template>
<view>
<button @click="searchBle"></button>
<view style="margin-top: 30upx;" :key="index" v-for="(item,index) in devices">
<button style="width: 400upx; color: #0081FF;" @click="onConn(item)">{{item.name}}</button>
</view>
<button style="margin-top: 100upx;" @click="senBleLabel()"></button>
<button style="margin-top: 100upx;" @click="jumpOld()">BLE</button>
<textarea auto-height placeholder-style="color:#F76260" placeholder="请输入票据信息" v-model="piaojuText" />
<button style="margin-top: 100upx;" @click="senBleLabel2()"></button>
<canvas :style="{width: canvasWidth +'px', height: canvasHeight +'px'}" canvas-id="firstCanvas" id="firstCanvas" class="firstCanvas" />
</view>
</template>
<script>
import tsc from '@/static/libs/tsc.js'
import esc from '@/static/libs/esc.js'
import bluetoothTool from '@/static/libs/BluetoothTool.js'
export default {
data() {
return {
devices: [],
currDev: null,
connId: '',
piaojuText:'',
tableDomId: '',
tableImgPath: '',
canvasWidth: 800,
canvasHeight: 600,
msg: ''
}
},
watch:{
msg(){
uni.showToast({
title: this.msg
})
}
},
onReady() {
this.renderCanvas()
},
mounted() {
//#ifdef APP-PLUS
//
bluetoothTool.init({
listenBTStatusCallback: (state)=> {
if(state == 'STATE_ON') {
let lastBleAddress = uni.getStorageSync('lastBleAddress')
if(lastBleAddress) {
uni.showLoading({
title: '正在连接...'
})
console.log(lastBleAddress)
bluetoothTool.connDevice(lastBleAddress,(result)=>{
uni.hideLoading()
uni.showToast({
title: result?'连接成功!':'连接失败...'
});
})
}
}
},
discoveryDeviceCallback: this.onDevice,
discoveryFinishedCallback: function() {
that.msg = "搜索完成";
},
readDataCallback: function(dataByteArr) {
//
/* if(that.receiveDataArr.length >= 200) {
that.receiveDataArr = [];
}
that.receiveDataArr.push.apply(that.receiveDataArr, dataByteArr); */
},
connExceptionCallback: function(e) {
console.log(e);
that.msg = "设备连接失败";
}
});
//#endif
},
methods: {
destroyed: function() {
console.log("destroyed----------")
if (this.connId != '') {
uni.closeBLEConnection({
deviceId: this.connId,
success(res) {
console.log(res)
}
})
}
},
searchBle() {
var that = this
console.log("initBule")
// 使openBluetoothAdapter
uni.openBluetoothAdapter({
success(res) {
this.devices = []
console.log("打开 蓝牙模块,开始搜索模式...")
console.log(res)
bluetoothTool.discoveryNewDevice();
//that.onDevice()
}
})
},
onDevice(newDevice){
console.log("监听寻找到新设备的事件---------------")
console.log(newDevice)
if(newDevice.name && newDevice.name != 'null') {
this.devices.push({
name: newDevice.name,
address: newDevice.address
})
}
},
stopFindBule() {
console.log("停止搜寻附近的蓝牙外围设备---------------")
uni.stopBluetoothDevicesDiscovery({
success(res) {
console.log(res)
}
})
},
onConn(item) {
console.log("连接蓝牙---------------" + item.address)
bluetoothTool.connDevice(item.address,(result)=>{
if(result) {
uni.setStorageSync('lastBleAddress', item.address)
}
console.log('连接结果:',result)
});
},
senBleLabel() {
//
uni.canvasGetImageData({
canvasId: 'firstCanvas',
x: 0,
y: 0,
width: this.canvasWidth,
height: this.canvasHeight,
success: (res)=> {
uni.hideLoading()
var command = tsc.jpPrinter.createNew()
command.init()
command.setSize(80, 60)
command.setGap(2)
command.setCls()
command.setText(50, 10, "TSS24.BF2", 1, 1, "打印测试")
command.setQR(50, 50, "L", 5, "A", "https://www.baidu.com")
command.setBitmap(200, 0, 0, res)
command.setBox(100, 100, 700, 500, 1)
command.setPagePrint()
let data = command.getData()
bluetoothTool.sendByteData(data)
console.log('发送完毕')
},
fail: function(res) {
console.log(res)
}
})
},
senBleLabel2(){
//
var command = esc.jpPrinter.createNew()
command.init()
command.setText(this.piaojuText);
command.setPrintAndFeedRow(1)
bluetoothTool.sendData(command.getRawData())
},
renderCanvas() {
let filePath = '../../static/xtHead.jpg'
let that = this
const firstCanvas = uni.createCanvasContext('firstCanvas', this);
let scla = 1
uni.getImageInfo({
src: filePath,
success(res) {
console.log(res.width + "--" + res.height)
that.canvasWidth = res.width * scla
that.canvasHeight = res.height * scla
console.log(res, that.canvasWidth, that.canvasHeight)
firstCanvas.drawImage(filePath, 0, 0, that.canvasWidth, that.canvasHeight);
firstCanvas.draw();
that.$nextTick(() => { //
})
},
fail(res) {
console.log(res)
}
})
},
jumpOld () {
uni.navigateTo({
url: './miniAppPrint'
})
}
}
}
</script>
<style>
</style>

@ -0,0 +1,452 @@
/**
* html 5+ 串口蓝牙操作
* 2021.04.23 uni-app版本
* @auth boolTrue
*/
/**
* 初始化参数
*/
//#ifdef APP-PLUS
let BluetoothAdapter = plus.android.importClass("android.bluetooth.BluetoothAdapter");
let Intent = plus.android.importClass("android.content.Intent");
let IntentFilter = plus.android.importClass("android.content.IntentFilter");
let BluetoothDevice = plus.android.importClass("android.bluetooth.BluetoothDevice");
let UUID = plus.android.importClass("java.util.UUID");
let Toast = plus.android.importClass("android.widget.Toast");
//连接串口设备的 UUID
let MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
let invoke = plus.android.invoke;
let btAdapter = BluetoothAdapter.getDefaultAdapter();
let activity = plus.android.runtimeMainActivity();
let btSocket = null;
let btInStream = null;
let btOutStream = null;
let setIntervalId = 0;
let btFindReceiver = null; //蓝牙搜索广播接收器
let btStatusReceiver = null; //蓝牙状态监听广播
//#endif
/**
* 构造对象
*/
var blueToothTool = {
state : {
bluetoothEnable: false, //蓝牙是否开启
bluetoothState: "", //当前蓝牙状态
discoveryDeviceState: false, //是否正在搜索蓝牙设备
readThreadState: false, //数据读取线程状态
},
options : {
/**
* 监听蓝牙状态回调
* @param {String} state
*/
listenBTStatusCallback: function(state) {},
/**
* 搜索到新的蓝牙设备回调
* @param {Device} newDevice
*/
discoveryDeviceCallback: function(newDevice) {},
/**
* 蓝牙搜索完成回调
*/
discoveryFinishedCallback: function() {},
/**
* 接收到数据回调
* @param {Array} dataByteArr
*/
readDataCallback: function(dataByteArr) {},
/**
* 蓝牙连接中断回调
* @param {Exception} e
*/
connExceptionCallback: function(e) {}
},
init(setOptions) {
Object.assign(this.options, setOptions);
this.state.bluetoothEnable = this.getBluetoothStatus();
this.listenBluetoothStatus();
},
shortToast(msg) {
Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show();
},
/**
* 是否支持蓝牙
* @return {boolean}
*/
isSupportBluetooth() {
if(btAdapter != null) {
return true;
}
return false;
},
/**
* 获取蓝牙的状态
* @return {boolean} 是否已开启
*/
getBluetoothStatus() {
if(btAdapter != null) {
return btAdapter.isEnabled();
}
return false;
},
/**
* 打开蓝牙
* @param activity
* @param requestCode
*/
turnOnBluetooth() {
if(btAdapter == null) {
shortToast("没有蓝牙");
return;
}
if(!btAdapter.isEnabled()) {
if(activity == null) {
shortToast("未获取到activity");
return;
} else {
let intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
let requestCode = 1;
activity.startActivityForResult(intent, requestCode);
return;
}
} else {
shortToast("蓝牙已经打开");
}
},
/**
* 关闭蓝牙
*/
turnOffBluetooth() {
if(btAdapter != null && btAdapter.isEnabled()) {
btAdapter.disable();
}
if(btFindReceiver != null) {
try {
activity.unregisterReceiver(btFindReceiver);
} catch(e) {
}
btFindReceiver = null;
}
this.state.bluetoothEnable = false;
this.cancelDiscovery();
closeBtSocket();
if(btAdapter != null && btAdapter.isEnabled()) {
btAdapter.disable();
shortToast("蓝牙关闭成功");
} else {
shortToast("蓝牙已经关闭");
}
},
/**
* 获取已经配对的设备
* @return {Array} connetedDevices
*/
getPairedDevices() {
let pairedDevices = [];
//蓝牙连接android原生对象是一个set集合
let pairedDevicesAndroid = null;
if(btAdapter != null && btAdapter.isEnabled()) {
pairedDevicesAndroid = btAdapter.getBondedDevices();
} else {
shortToast("蓝牙未开启");
}
if(!pairedDevicesAndroid) {
return pairedDevices;
}
//遍历连接设备的set集合转换为js数组
let it = invoke(pairedDevicesAndroid, "iterator");
while(invoke(it, "hasNext")) {
let device = invoke(it, "next");
pairedDevices.push({
"name": invoke(device, "getName"),
"address": invoke(device, "getAddress")
});
}
return pairedDevices;
},
/**
* 发现设备
*/
discoveryNewDevice() {
if(btFindReceiver != null) {
try {
activity.unregisterReceiver(btFindReceiver);
} catch(e) {
console.error(e);
}
btFindReceiver = null;
this.cancelDiscovery();
}
let Build = plus.android.importClass("android.os.Build");
//6.0以后的如果需要利用本机查找周围的wifi和蓝牙设备, 申请权限
if(Build.VERSION.SDK_INT >= 6.0){
}
let options = this.options
btFindReceiver = plus.android.implements("io.dcloud.android.content.BroadcastReceiver", {
"onReceive": function(context, intent) {
plus.android.importClass(context);
plus.android.importClass(intent);
let action = intent.getAction();
if(BluetoothDevice.ACTION_FOUND == action) { // 找到设备
let device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
let newDevice = {
"name": plus.android.invoke(device, "getName"),
"address": plus.android.invoke(device, "getAddress")
}
options.discoveryDeviceCallback && options.discoveryDeviceCallback(newDevice);
}
if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED == action) { // 搜索完成
cancelDiscovery();
options.discoveryFinishedCallback && options.discoveryFinishedCallback();
}
}
});
let filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
activity.registerReceiver(btFindReceiver, filter);
btAdapter.startDiscovery(); //开启搜索
this.state.discoveryDeviceState = true;
},
/**
* 蓝牙状态监听
* @param {Activity} activity
*/
listenBluetoothStatus() {
if(btStatusReceiver != null) {
try {
activity.unregisterReceiver(btStatusReceiver);
} catch(e) {
console.error(e);
}
btStatusReceiver = null;
}
btStatusReceiver = plus.android.implements("io.dcloud.android.content.BroadcastReceiver", {
"onReceive": (context, intent)=> {
plus.android.importClass(context);
plus.android.importClass(intent);
let action = intent.getAction();
switch(action) {
case BluetoothAdapter.ACTION_STATE_CHANGED:
let blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
let stateStr = "";
switch(blueState) {
case BluetoothAdapter.STATE_TURNING_ON:
stateStr = "STATE_TURNING_ON";
break;
case BluetoothAdapter.STATE_ON:
this.state.bluetoothEnable = true;
stateStr = "STATE_ON";
break;
case BluetoothAdapter.STATE_TURNING_OFF:
stateStr = "STATE_TURNING_OFF";
break;
case BluetoothAdapter.STATE_OFF:
stateStr = "STATE_OFF";
this.state.bluetoothEnable = false;
break;
}
this.state.bluetoothState = stateStr;
this.options.listenBTStatusCallback && this.options.listenBTStatusCallback(stateStr);
break;
}
}
});
let filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
activity.registerReceiver(btStatusReceiver, filter);
// 首次连接 状态回调
if(this.state.bluetoothEnable) {
this.options.listenBTStatusCallback && this.options.listenBTStatusCallback('STATE_ON');
}
},
/**
* 根据蓝牙地址连接设备
* @param {Stirng} address
* @return {Boolean}
*/
connDevice(address, callback) {
let InputStream = plus.android.importClass("java.io.InputStream");
let OutputStream = plus.android.importClass("java.io.OutputStream");
let BluetoothSocket = plus.android.importClass("android.bluetooth.BluetoothSocket");
this.cancelDiscovery();
if(btSocket != null) {
this.closeBtSocket();
}
this.state.readThreadState = false;
try {
let device = invoke(btAdapter, "getRemoteDevice", address);
btSocket = invoke(device, "createRfcommSocketToServiceRecord", MY_UUID);
} catch(e) {
console.error(e);
shortToast("连接失败获取Socket失败");
callback(false)
return false;
}
try {
invoke(btSocket, "connect");
this.readData(); //读数据
this.shortToast("连接成功");
callback(true)
} catch(e) {
console.error(e);
this.shortToast("连接失败");
callback(false)
try {
btSocket.close();
btSocket = null;
} catch(e1) {
console.error(e1);
}
return false;
}
return true;
},
/**
* 断开连接设备
* @param {Object} address
* @return {Boolean}
*/
disConnDevice() {
if(btSocket != null) {
this.closeBtSocket();
}
this.state.readThreadState = false;
this.shortToast("断开连接成功");
},
/**
* 断开连接设备
* @param {Object} address
* @return {Boolean}
*/
closeBtSocket() {
this.state.readThreadState = false;
if(!btSocket) {
return;
}
try {
btSocket.close();
} catch(e) {
console.error(e);
btSocket = null;
}
},
/**
* 取消发现
*/
cancelDiscovery() {
if(btAdapter.isDiscovering()) {
btAdapter.cancelDiscovery();
}
if(btFindReceiver != null) {
activity.unregisterReceiver(btFindReceiver);
btFindReceiver = null;
}
this.state.discoveryDeviceState = false;
},
/**
* 读取数据
* @param {Object} activity
* @param {Function} callback
* @return {Boolean}
*/
readData() {
if(!btSocket) {
this.shortToast("请先连接蓝牙设备!");
return false;
}
try {
btInStream = invoke(btSocket, "getInputStream");
btOutStream = invoke(btSocket, "getOutputStream");
} catch(e) {
console.error(e);
this.shortToast("创建输入输出流失败!");
this.closeBtSocket();
return false;
}
this.read();
this.state.readThreadState = true;
return true;
},
/**
* 模拟java多线程读取数据
*/
read() {
let setTimeCount = 0;
clearInterval(setIntervalId);
setIntervalId = setInterval(()=> {
setTimeCount++;
if(this.state.readThreadState) {
let t = new Date().getTime();
//心跳检测
if(setTimeCount % 20 == 0) {
try {
btOutStream.write([0b00]);
} catch(e) {
this.state.readThreadState = false;
this.options.connExceptionCallback && this.options.connExceptionCallback(e);
}
}
let dataArr = [];
while(invoke(btInStream, "available") !== 0) {
let data = invoke(btInStream, "read");
dataArr.push(data);
let ct = new Date().getTime();
if(ct - t > 20) {
break;
}
}
if(dataArr.length > 0) {
this.options.readDataCallback && this.options.readDataCallback(dataArr);
}
}
}, 40);
},
/**
* 发送数据
* @param {String} dataStr
* @return {Boolean}
*/
sendData(dataStr) {
if(!btOutStream) {
this.shortToast("创建输出流失败!");
return;
}
let bytes = invoke(dataStr, 'getBytes', 'gbk');
try {
btOutStream.write(bytes);
} catch(e) {
return false;
}
return true;
},
sendByteData(byteData) {
if(!btOutStream) {
this.shortToast("创建输出流失败!");
return;
}
try {
btOutStream.write(byteData);
} catch(e) {
return false;
}
return true;
}
}
module.exports = blueToothTool

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -0,0 +1,313 @@
var encode = require("./encoding.js")
var jpPrinter = {
createNew: function() {
var jpPrinter = {};
var data = [];
var bar = ["UPC-A", "UPC-E", "EAN13", "EAN8", "CODE39", "ITF", "CODABAR", "CODE93", "CODE128"];
jpPrinter.name = "账单模式";
jpPrinter.init = function() { //初始化打印机
data.push(27)
data.push(64)
};
jpPrinter.setText = function(content) { //设置文本内容
var code = new encode.TextEncoder(
'gb18030', {
NONSTANDARD_allowLegacyEncoding: true
}).encode(content)
for (var i = 0; i < code.length; ++i) {
data.push(code[i])
}
}
jpPrinter.setBarcodeWidth = function(width) { //设置条码宽度
data.push(29)
data.push(119)
if (width > 6) {
width = 6;
}
if (width < 2) {
width = 1;
}
data.push(width)
}
jpPrinter.setBarcodeHeight = function(height) { //设置条码高度
data.push(29)
data.push(104)
data.push(height)
}
jpPrinter.setBarcodeContent = function(t, content) {
var ty = 73;
data.push(29)
data.push(107)
switch (t) {
case bar[0]:
ty = 65;
break;
case bar[1]:
ty = 66;
break;
case bar[2]:
ty = 67;
break;
case bar[3]:
ty = 68;
break;
case bar[4]:
ty = 69;
break;
case bar[5]:
ty = 70;
break;
case bar[6]:
ty = 71;
break;
case bar[7]:
ty = 72;
break;
case bar[8]:
ty = 73;
break;
}
data.push(ty)
}
jpPrinter.setSelectSizeOfModuleForQRCode = function(n) { //设置二维码大小
data.push(29)
data.push(40)
data.push(107)
data.push(3)
data.push(0)
data.push(49)
data.push(67)
if (n > 15) {
n = 15
}
if (n < 1) {
n = 1
}
data.push(n)
}
jpPrinter.setSelectErrorCorrectionLevelForQRCode = function(n) { //设置纠错等级
/*
n 功能 纠错能力
48 选择纠错等级 L 7
49 选择纠错等级 M 15
50 选择纠错等级 Q 25
51 选择纠错等级 H 30
*/
data.push(29)
data.push(40)
data.push(107)
data.push(3)
data.push(0)
data.push(49)
data.push(69)
data.push(n)
}
jpPrinter.setStoreQRCodeData = function(content) { //设置二维码内容
var code = new encode.TextEncoder(
'gb18030', {
NONSTANDARD_allowLegacyEncoding: true
}).encode(content)
data.push(29)
data.push(40)
data.push(107)
data.push(parseInt((code.length + 3) % 256))
data.push(parseInt((code.length + 3) / 256))
data.push(49)
data.push(80)
data.push(48)
for (var i = 0; i < code.length; ++i) {
data.push(code[i])
}
}
jpPrinter.setPrintQRCode = function() { //打印二维码
data.push(29)
data.push(40)
data.push(107)
data.push(3)
data.push(0)
data.push(49)
data.push(81)
data.push(48)
}
jpPrinter.setHorTab = function() { //移动打印位置到下一个水平定位点的位置
data.push(9)
}
jpPrinter.setAbsolutePrintPosition = function(where) { //设置绝对打印位置
data.push(27)
data.push(36)
data.push(parseInt(where % 256))
data.push(parseInt(where / 256))
}
jpPrinter.setRelativePrintPositon = function(where) { //设置相对横向打印位置
data.push(27)
data.push(92)
data.push(parseInt(where % 256))
data.push(parseInt(where / 256))
}
jpPrinter.setSelectJustification = function(which) { //对齐方式
/*
0, 48 左对齐
1, 49 中间对齐
2, 50 右对齐
*/
data.push(27)
data.push(97)
data.push(which)
}
jpPrinter.setLeftMargin = function(n) { //设置左边距
data.push(29)
data.push(76)
data.push(parseInt(n % 256))
data.push(parseInt(n / 256))
}
jpPrinter.setPrintingAreaWidth = function(width) { //设置打印区域宽度
data.push(29)
data.push(87)
data.push(parseInt(width % 256))
data.push(parseInt(width / 256))
}
jpPrinter.setSound = function(n, t) { //设置蜂鸣器
data.push(27)
data.push(66)
if (n < 0) {
n = 1;
} else if (n > 9) {
n = 9;
}
if (t < 0) {
t = 1;
} else if (t > 9) {
t = 9;
}
data.push(n)
data.push(t)
}
jpPrinter.setBitmap = function(res) { //参数,画布的参数
console.log(res)
var width = parseInt((res.width + 7) / 8 * 8 / 8)
var height = res.height;
var time = 1;
var temp = res.data.length - width * 32;
var point_list = []
console.log(width + "--" + height)
data.push(29)
data.push(118)
data.push(48)
data.push(0)
data.push((parseInt((res.width + 7) / 8) * 8) / 8)
data.push(0)
data.push(parseInt(res.height % 256))
data.push(parseInt(res.height / 256))
console.log(res.data.length)
console.log("temp=" + temp)
for (var i = 0; i < height; ++i) {
for (var j = 0; j < width; ++j) {
for (var k = 0; k < 32; k += 4) {
var po = {}
if (res.data[temp] == 0 && res.data[temp + 1] == 0 && res.data[temp + 2] == 0 && res
.data[temp + 3] == 0) {
po.point = 0;
} else {
po.point = 1;
}
point_list.push(po)
temp += 4
}
}
time++
temp = res.data.length - width * 32 * time
}
for (var i = 0; i < point_list.length; i += 8) {
var p = point_list[i].point * 128 + point_list[i + 1].point * 64 + point_list[i + 2].point *
32 + point_list[i + 3].point * 16 + point_list[i + 4].point * 8 + point_list[i + 5]
.point * 4 + point_list[i + 6].point * 2 + point_list[i + 7].point
data.push(p)
}
}
jpPrinter.setPrint = function() { //打印并换行
data.push(10)
}
jpPrinter.setPrintAndFeed = function(feed) { //打印并走纸feed个单位
data.push(27)
data.push(74)
data.push(feed)
}
jpPrinter.setPrintAndFeedRow = function(row) { //打印并走纸row行
data.push(27)
data.push(100)
data.push(row)
}
jpPrinter.getData = function() { //获取打印数据
return data;
};
return jpPrinter;
},
Query: function() {
var queryStatus = {};
var buf;
var dateView;
queryStatus.getRealtimeStatusTransmission = function(n) { //查询打印机实时状态
/*
n = 1传送打印机状态
n = 2传送脱机状态
n = 3传送错误状态
n = 4传送纸传感器状态
*/
buf = new ArrayBuffer(3)
dateView = new DataView(buf)
dateView.setUint8(0, 16)
dateView.setUint8(1, 4)
dateView.setUint8(2, n)
queryStatus.query(buf)
}
queryStatus.query = function(buf) {
wx.writeBLECharacteristicValue({
deviceId: app.BLEInformation.deviceId,
serviceId: app.BLEInformation.writeServiceId,
characteristicId: app.BLEInformation.writeCharaterId,
value: buf,
success: function(res) {
},
complete: function(res) {
console.log(res)
buf = null
dateView = null;
}
})
}
return queryStatus;
}
};
module.exports.jpPrinter = jpPrinter;

@ -0,0 +1,388 @@
/**
* tsc 命令打印工具类
* 2021.04.26 uni-app版本
* @auth boolTrue
*/
var encode = require("./encoding.js");
var jpPrinter = {
createNew: function() {
var jpPrinter = {};
var data = "";
var command = []
var rawCommand = ''
jpPrinter.name = "标签模式";
jpPrinter.init = function() {};
jpPrinter.addCommand = function(content) { //将指令转成数组装起
//#ifdef MP-WEIXIN
var code = new encode.TextEncoder(
'gb18030', {
NONSTANDARD_allowLegacyEncoding: true
}).encode(content)
for (var i = 0; i < code.length; ++i) {
command.push(code[i])
}
//#endif
//#ifdef APP-PLUS
let byteCommand = plus.android.invoke(content, 'getBytes', 'gb18030');
for (var i = 0; i < byteCommand.length; ++i) {
command.push(byteCommand[i])
}
//#endif
//console.log('command--:',command)
rawCommand += content
}
function intToByte(i) {
// 此处关键 -- android是java平台 byte数值范围是 [-128, 127]
// 因为java平台的byte类型是有符号的 最高位表示符号,所以数值范围固定
// 而图片计算出来的是数值是 0 -255 属于int类型
// 所以把int 转换成byte类型
//#ifdef APP-PLUS
var b = i & 0xFF;
var c = 0;
if (b >= 128) {
c = b % 128;
c = -1 * (128 - c);
} else {
c = b;
}
return c
//#endif
// 而微信小程序不需要因为小程序api接收的是 无符号8位
//#ifdef MP-WEIXIN
return i
//#endif
}
jpPrinter.setSize = function(pageWidght, pageHeight) { //设置页面大小
data = "SIZE " + pageWidght.toString() + " mm" + "," + pageHeight.toString() + " mm" + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setSpeed = function(printSpeed) { //设置打印机速度
data = "SPEED " + printSpeed.toString() + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setDensity = function(printDensity) { //设置打印机浓度
data = "DENSITY " + printDensity.toString() + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setGap = function(printGap) { //传感器
data = "GAP " + printGap.toString() + " mm\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setCountry = function(country) { //选择国际字符集
/*
001:USA
002:French
003:Latin America
034:Spanish
039:Italian
044:United Kingdom
046:Swedish
047:Norwegian
049:German
*/
data = "COUNTRY " + country + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setCodepage = function(codepage) { //选择国际代码页
/*
8-bit codepage 字符集代表
437:United States
850:Multilingual
852:Slavic
860:Portuguese
863:Canadian/French
865:Nordic
Windows code page
1250:Central Europe
1252:Latin I
1253:Greek
1254:Turkish
以下代码页仅限于 12×24 dot 英数字体
WestEurope:WestEurope
Greek:Greek
Hebrew:Hebrew
EastEurope:EastEurope
Iran:Iran
IranII:IranII
Latvian:Latvian
Arabic:Arabic
Vietnam:Vietnam
Uygur:Uygur
Thai:Thai
1252:Latin I
1257:WPC1257
1251:WPC1251
866:Cyrillic
858:PC858
747:PC747
864:PC864
1001:PC100
*/
data = "CODEPAGE " + codepage + "\r\n";
jpPrinter.addCommand(data)
}
jpPrinter.setCls = function() { //清除打印机缓存
data = "CLS" + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setFeed = function(feed) { //将纸向前推出n
data = "FEED " + feed + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setBackFeed = function(backup) { //将纸向后回拉n
data = "BACKFEED " + backup + "\r\n";
jpPrinter.addCommand(data)
}
jpPrinter.setDirection = function(direction) { //设置打印方向,参考编程手册
data = "DIRECTION " + direction + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setReference = function(x, y) { //设置坐标原点,与打印方向有关
data = "REFERENCE " + x + "," + y + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setFromfeed = function() { //根据Size进一张标签纸
data = "FORMFEED \r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setHome = function() { //根据Size找到下一张标签纸的位置
data = "HOME \r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setSound = function(level, interval) { //控制蜂鸣器
data = "SOUND " + level + "," + interval + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setLimitfeed = function(limit) { // 检测垂直间距
data = "LIMITFEED " + limit + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setBar = function(x, y, width, height) { //绘制线条
data = "BAR " + x + "," + y + "," + width + "," + height + "\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setBox = function(x_start, y_start, x_end, y_end, thickness) { //绘制方框
data = "BOX " + x_start + "," + y_start + "," + x_end + "," + y_end + "," + thickness + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setErase = function(x_start, y_start, x_width, y_height) { //清除指定区域的数据
data = "ERASE " + x_start + "," + y_start + "," + x_width + "," + y_height + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setReverse = function(x_start, y_start, x_width, y_height) { //将指定的区域反相打印
data = "REVERSE " + x_start + "," + y_start + "," + x_width + "," + y_height + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setText = function(x, y, font, x_, y_, str) { //打印文字
data = "TEXT " + x + "," + y + ",\"" + font + "\"," + 0 + "," + x_ + "," + y_ + "," + "\"" +
str + "\"\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setQR = function(x, y, level, width, mode, content) { //打印二维码
data = "QRCODE " + x + "," + y + "," + level + "," + width + "," + mode + "," + 0 + ",\"" +
content + "\"\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setBar = function(x, y, codetype, height, readable, narrow, wide, content) { //打印条形码
data = "BARCODE " + x + "," + y + ",\"" + codetype + "\"," + height + "," + readable + "," + 0 +
"," + narrow + "," + wide + ",\"" + content + "\"\r\n"
jpPrinter.addCommand(data)
};
// 固定灰度阈值128以上的都看作白色
jpPrinter.setBitmap = function(x, y, mode, res) { //添加图片res为画布参数
var width = parseInt((res.width) / 8 * 8 / 8)
var height = res.height
var imgWidth = res.width
var time = 1;
var temp = res.data.length - width * 32;
var pointList = []
var resultData = []
console.log(width + "--" + height)
data = "BITMAP " + x + "," + y + "," + width + "," + height + "," + mode + ","
jpPrinter.addCommand(data)
//console.log(res.data)
console.log('---以上是原始数据---')
//for循环顺序不要错了外层遍历高度内层遍历宽度因为横向每8个像素点组成一个字节
for (var y = 0; y < height; y++) {
for (var x = 0; x < imgWidth; x++) {
let r = res.data[(y * imgWidth + x) * 4];
let g = res.data[(y * imgWidth + x) * 4 + 1];
let b = res.data[(y * imgWidth + x) * 4 + 2];
let a = res.data[(y * imgWidth + x) * 4 + 3]
//console.log(`当前${y}行${x}列像素,rgba值:(${r},${g},${b},${a})`)
// 像素灰度值
let grayColor = r * 0.299 + g * 0.587 + b * 0.114
//灰度值大于128位
//1不打印, 0打印 参考佳博标签打印机编程手册tspl
if (grayColor > 128) {
pointList.push(1)
} else {
pointList.push(0)
}
}
}
//console.log(pointList)
for (var i = 0; i < pointList.length; i += 8) {
var p = pointList[i] * 128 + pointList[i + 1] * 64 + pointList[i + 2] * 32 + pointList[i +
3] * 16 + pointList[i + 4] * 8 + pointList[i + 5] * 4 + pointList[i + 6] * 2 +
pointList[i + 7]
resultData.push(p)
}
console.log('最终数据:')
//console.log(resultData)
for (var i = 0; i < resultData.length; ++i) {
command.push(intToByte(resultData[i]))
}
}
jpPrinter.setBitmap2 = function(x, y, mode, res) { //添加图片res为画布参数
var w = res.width
var width = parseInt((res.width + 7) / 8 * 8 / 8)
var height = res.height;
console.log(width + "--" + height)
data = "BITMAP " + x + "," + y + "," + width + "," + height + "," + mode + ","
jpPrinter.addCommand(data)
var r = []
var bits = new Uint8Array(height * width);
for (y = 0; y < height; y++) {
for (x = 0; x < w; x++) {
let r = res.data[(y * w + x) * 4];
let g = res.data[(y * w + x) * 4 + 1];
let b = res.data[(y * w + x) * 4 + 2];
let a = res.data[(y * w + x) * 4 + 3]
var color = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) <<
0);
if ((color & 0xFF) > 128) {
bits[parseInt(y * width + x / 8)] |= (0x80 >> (x % 8));
}
}
}
for (var i = 0; i < bits.length; i++) {
//command.push((~bits[i]) & 0xFF);
command.push(intToByte(bits[i]));
//r.push((~bits[i]) & 0xFF);
}
}
// 平均灰度阈值(先计算平均灰度,然后大于平均灰度的都算作白色)
jpPrinter.setBitmap3 = function(x, y, mode, res) { //添加图片res为画布参数
var width = parseInt((res.width) / 8 * 8 / 8)
var height = res.height
var imgWidth = res.width
var time = 1;
var temp = res.data.length - width * 32;
var pointList = []
var resultData = []
console.log(width + "--" + height)
data = "BITMAP " + x + "," + y + "," + width + "," + height + "," + mode + ","
jpPrinter.addCommand(data)
//console.log(res.data)
console.log('---以上是原始数据---')
let sumRed = 0,
sumGreen = 0,
sumBlue = 0;
let total = height * imgWidth;
let pix = res.data;
for (var i = 0; i < pix.length; i += 4) {
sumRed += pix[i]
sumGreen += pix[i + 1]
sumBlue += pix[i + 2]
}
let avgRed = parseInt(sumRed / total);
let avgGreen = parseInt(sumGreen / total);
let avgBlue = parseInt(sumBlue / total);
let avgGrayColor = avgRed * 0.299 + avgGreen * 0.587 + avgBlue * 0.114
//for循环顺序不要错了外层遍历高度内层遍历宽度因为横向每8个像素点组成一个字节
for (var y = 0; y < height; y++) {
for (var x = 0; x < imgWidth; x++) {
let r = res.data[(y * imgWidth + x) * 4];
let g = res.data[(y * imgWidth + x) * 4 + 1];
let b = res.data[(y * imgWidth + x) * 4 + 2];
let a = res.data[(y * imgWidth + x) * 4 + 3]
// 像素灰度值
let grayColor = r * 0.299 + g * 0.587 + b * 0.114
//灰度值大于128位
//1不打印, 0打印 参考佳博标签打印机编程手册tspl
if (grayColor > avgGrayColor) {
pointList.push(1)
} else {
pointList.push(0)
}
}
}
//console.log(pointList)
for (var i = 0; i < pointList.length; i += 8) {
var p = pointList[i] * 128 + pointList[i + 1] * 64 + pointList[i + 2] * 32 + pointList[i +
3] * 16 + pointList[i + 4] * 8 + pointList[i + 5] * 4 + pointList[i + 6] * 2 +
pointList[i + 7]
resultData.push(p)
}
console.log('最终数据:')
//console.log(resultData)
for (var i = 0; i < resultData.length; ++i) {
command.push(intToByte(resultData[i]))
}
}
jpPrinter.RawCommand = function(data) {
jpPrinter.addCommand(data)
}
jpPrinter.setPagePrint = function() { //打印页面
data = "PRINT 1,1\r\n"
jpPrinter.addCommand(data)
};
//获取打印数据
jpPrinter.getData = function() {
return command;
};
jpPrinter.getRawData = function() {
return rawCommand;
};
jpPrinter.clearCommand = function() {
rawCommand = ''
};
return jpPrinter;
}
};
module.exports.jpPrinter = jpPrinter;

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@ -0,0 +1,341 @@
<template>
<view>
<button @click="searchBle"></button>
<view style="margin-top: 30upx;" :key="index" v-for="(item,index) in devices">
<button style="width: 400upx; color: #0081FF;" @click="onConn(item)">{{item.name}}</button>
</view>
<button style="margin-top: 100upx;" @click="senBleLabel()"></button>
<textarea auto-height placeholder-style="color:#F76260" placeholder="请输入票据信息" v-model="piaojuText" />
<button style="margin-top: 100upx;" @click="senBleLabel2()"></button>
<canvas :style="{width: canvasWidth +'px', height: canvasHeight +'px'}" canvas-id="firstCanvas" id="firstCanvas" class="firstCanvas" />
</view>
</template>
<script>
import tsc from '@/static/libs/tsc.js'
import esc from '@/static/libs/esc.js'
export default {
data() {
return {
devices: [],
currDev: null,
connId: '',
piaojuText:'',
tableDomId: '',
tableImgPath: '',
canvasWidth: 800,
canvasHeight: 600,
msg: ''
}
},
watch:{
msg(){
uni.showToast({
title: this.msg
})
}
},
onReady() {
this.renderFinish()
},
methods: {
destroyed: function() {
console.log("destroyed----------")
if (this.connId != '') {
uni.closeBLEConnection({
deviceId: this.connId,
success(res) {
console.log(res)
}
})
}
},
searchBle() {
var that = this
console.log("initBule")
uni.openBluetoothAdapter({
success(res) {
console.log("打开 蓝牙模块")
console.log(res)
that.onDevice()
uni.getBluetoothAdapterState({
success: function(res) {
console.log(res)
if (res.available) {
if (res.discovering) {
that.stopFindBule()
}
//
//
console.log("开始搜寻附近的蓝牙外围设备")
uni.startBluetoothDevicesDiscovery({
success(res) {
console.log(res)
}
})
} else {
console.log('本机蓝牙不可用')
}
},
})
}
})
},
onDevice(){
console.log("监听寻找到新设备的事件---------------")
var that = this
//
uni.onBluetoothDeviceFound(function(devices) {
console.log('--------------new-----------------------'+JSON.stringify(devices))
var re = JSON.parse(JSON.stringify(devices))
console.log(re.devices[0].name + " " + re.devices[0].deviceId)
let name = re.devices[0].name
if (name != "未知设备") {
let deviceId = re.devices[0].deviceId
that.devices.push({
name: name,
deviceId: deviceId,
services: []
})
}
})
},
stopFindBule() {
console.log("停止搜寻附近的蓝牙外围设备---------------")
uni.stopBluetoothDevicesDiscovery({
success(res) {
console.log(res)
}
})
},
onConn(item) {
var that = this
console.log("连接蓝牙---------------" + item.deviceId)
let deviceId = item.deviceId
uni.createBLEConnection({
deviceId: deviceId,
complete(res) {
if (res.errMsg == "createBLEConnection:ok") {
console.log("连接蓝牙-[" + item.name + "]--成功")
that.connId = deviceId;
that.currDev = item
setTimeout(function() {
that.getBLEServices(deviceId)
}, 2000)
} else {
console.log(res)
}
//
that.stopFindBule()
},
})
},
getBLEServices(_deviceId) {
var that = this;
let deviceId = _deviceId
console.log("获取蓝牙设备所有服务(service)。---------------")
uni.getBLEDeviceServices({
// deviceId createBLEConnection
deviceId: deviceId,
complete(res) {
console.log(res)
let serviceId = ""
for (var s = 0; s < res.services.length; s++) {
console.log(res.services[s].uuid)
let serviceId = res.services[s].uuid
uni.getBLEDeviceCharacteristics({
// deviceId createBLEConnection
deviceId: deviceId,
// serviceId getBLEDeviceServices
serviceId: serviceId,
success(res) {
var re = JSON.parse(JSON.stringify(res))
console.log('deviceId = [' + deviceId + '] serviceId = [' + serviceId + ']')
for (var c = 0; c < re.characteristics.length; c++) {
if (re.characteristics[c].properties.write == true) {
let uuid = re.characteristics[c].uuid
console.log(' deviceId = [' + deviceId + '] serviceId = [' + serviceId + '] characteristics=[' +
uuid)
for (var index in that.devices) {
if (that.devices[index].deviceId == deviceId) {
that.devices[index].services.push({
serviceId: serviceId,
characteristicId: uuid
})
break
}
}
console.log(JSON.stringify(that.devices))
}
}
}
})
}
},
fail(res) {
console.log(res)
},
})
},
senBlData(deviceId, serviceId, characteristicId,uint8Array) {
console.log('************deviceId = [' + deviceId + '] serviceId = [' + serviceId + '] characteristics=[' +characteristicId+ "]")
var uint8Buf = Array.from(uint8Array);
function split_array(datas,size){
var result = {};
var j = 0
if(datas.length < size) {
size = datas.length
}
for (var i = 0; i < datas.length; i += size) {
result[j] = datas.slice(i, i + size)
j++
}
//result[j] = datas
console.log(result)
return result
}
var sendloop = split_array(uint8Buf, 20);
// console.log(sendloop.length)
function realWriteData(sendloop, i) {
var data = sendloop[i]
if(typeof(data) == "undefined"){
return
}
//console.log("" + i + ""+data)
var buffer = new ArrayBuffer(data.length)
var dataView = new DataView(buffer)
for (var j = 0; j < data.length; j++) {
dataView.setUint8(j, data[j]);
}
uni.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value: buffer,
success(res) {
console.log('发送成功',i)
setTimeout(()=>{
realWriteData(sendloop, i + 1);
},100)
//realWriteData(sendloop, i + 1);
},
fail(e) {
console.log('发送数据失败')
console.log(e)
}
})
}
var i = 0;
realWriteData(sendloop, i);
},
senBleLabel() {
//
uni.canvasGetImageData({
canvasId: 'firstCanvas',
x: 0,
y: 0,
width: this.canvasWidth,
height: this.canvasHeight,
success: (res)=> {
uni.hideLoading()
let deviceId = this.currDev.deviceId;
let serviceId = this.currDev.services[0].serviceId;
let characteristicId = this.currDev.services[0].characteristicId;
var command = tsc.jpPrinter.createNew()
command.init()
command.setSize(80, 60)
//command.setBox(0,0,800,600,1)
command.setGap(2)
command.setCls()
command.setText(50, 10, "TSS24.BF2", 1, 1, "打印测试")
command.setQR(50, 50, "L", 5, "A", "https://www.baidu.com")
command.setBitmap(100, 0, 0, res)
//command.setBitmap2(100, 100, 0,res)
command.setBox(5, 5, 795, 595, 1)
command.setPagePrint()
console.log('command.getData()')
//console.log(command.getData())
this.senBlData(deviceId, serviceId, characteristicId,command.getData())
},
fail: function(res) {
console.log(res)
}
})
},
senBleLabel2(){
//
let deviceId = this.currDev.deviceId;
let serviceId = this.currDev.services[0].serviceId;
let characteristicId = this.currDev.services[0].characteristicId;
var command = esc.jpPrinter.createNew()
command.init()
command.setText(this.piaojuText);
command.setPrintAndFeedRow(1)
this.senBlData(deviceId, serviceId, characteristicId,command.getData())
},
renderFinish() {
let filePath = '../../static/logo.png'
let that = this
const firstCanvas = uni.createCanvasContext('firstCanvas', this);
let scla = 1
uni.getImageInfo({
src: filePath,
success(res) {
that.canvasWidth = res.width * scla
that.canvasHeight = res.height * scla
console.log(res, that.canvasWidth, that.canvasHeight)
firstCanvas.drawImage(filePath, 0, 0, that.canvasWidth, that.canvasHeight);
firstCanvas.draw();
that.$nextTick(() => { //
})
},
fail(res) {
console.log(res)
}
})
}
}
}
</script>
<style>
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

@ -0,0 +1,64 @@
<template>
<ch-flex class="question-item" justify="center" items="center">
<checkbox ref="checkRef" :value="item.questionid.toString()" />
<uni-card @click="checkQues(item.questionid)">
<view class="question__sort-content">{{ item.questioncontent }}</view>
<view class="question__info">
<text>{{ item.gradename }}</text>
<text>{{ item.addtime }}</text>
<text>{{ item.subjectname }}</text>
</view>
</uni-card>
</ch-flex>
</template>
<script setup>
import { defineProps, defineEmits, watch, ref, computed, onMounted } from 'vue';
const props = defineProps([
'item'
]);
const emit = defineEmits(['clickItem', 'change'])
const checkValue = ref(null);
const checkStatus = ref(false);
const checkRef = ref(null)
onMounted(() => {
console.log(checkRef)
})
watch(checkValue, (newValue) => {
console.log('change')
emit('change', newValue)
})
function checkQues(id) {
emit('clickItem', id)
}
</script>
<style scoped lang="scss">
$lineHeight: $uni-font-size-base * 1.5;
.question__sort-content {
font-size: $uni-font-size-base;
line-height: $lineHeight;
height: $lineHeight;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 16rpx;
}
.question__info {
> text {
font-size: $uni-font-size-mini;
color: $uni-secondary-color;
+ text {
margin-left: 16rpx;
}
}
}
</style>

@ -0,0 +1,134 @@
<template>
<view class="content">
<radio-group class="radio" @change="radioChange">
<label class="label"
v-for="(item, index) in [{value:'tspl',name:'标签模式'},{value:'cpcl',name:'面单模式'},{value:'esc',name:'票据模式'},{value:'0',name:'其它'}]"
:key="index">
<radio :value="item.value" :checked="item.value === state.current"/>
{{ item.name }}
</label>
</radio-group>
<view class="btn">
<button @click="onPrintClick" type="primary">打印</button>
<button @click="getBluetooth" type="primary">搜索</button>
</view>
<view class="list">
<view class="title">设备列表</view>
<scroll-view scroll-y="true" class="scroll-Y">
<button v-for="item in state.deviceData" :key="item.deviceId" class="button" type="primary"
@click="onBtnClick(item.deviceId)" :plain="item.deviceId===state.deviceId">{{ item.name }}
</button>
</scroll-view>
</view>
</view>
</template>
<script setup>
import {
reactive,
onMounted,
onUnmounted
} from 'vue';
import Printer from './printer';
import {commands} from './command';
const state = reactive({
current: 'tspl',
deviceData: [
{deviceId:0,name:'deviceId'},
{deviceId:1,name:'deviceId'}
],
data: [
{
id: "12345",
width: 100,
height: 100,
title1: 'title_1',
title2: 'title_2',
title3: 'title_3'
},
{
id: "123456",
width: 100,
height: 100,
title1: 'title_4',
title2: 'title_5',
title3: 'title_6'
},
{
id: "123456789",
width: 100,
height: 100,
title1: 'title_7',
title2: 'title_8',
title3: 'title_9'
}
],
deviceId: ''
});
onMounted(() => {
Printer.init();
});
onUnmounted(() => {
Printer.closeBluetoothAdapter();
});
const radioChange = (e) => {
state.current = e.detail.value;
}
const getBluetooth = () => {
Printer.getBluetooth(function (res) {
state.deviceData = res.devices;
});
}
const onPrintClick = () => {
const stringData = [];
state.data.forEach(item => {
stringData.push(commands(state.current, item));
})
Printer.printer(stringData, () => {
uni.showToast({
title: '打印成功',
duration: 2000,
});
})
}
const onBtnClick = (deviceId) => {
state.deviceId = deviceId;
Printer.connectDevice(deviceId);
}
</script>
<style scoped>
.content {
padding-top: 50rpx;
font-size: 14rpx;
.radio {
display: flex;
justify-content: space-around;
}
.btn {
display: flex;
margin: 30rpx;
}
.list {
.title {
padding: 30rpx;
}
.scroll-Y {
height: calc(100vh - 500rpx);
border: 1px solid #4400ff;
.button {
margin: 30rpx;
}
}
}
}
</style>

@ -0,0 +1,50 @@
// 对应的指令 请到手册查询
const esc = (item) => {
}
const cpcl = (item) => {
let string = '';
//模板字符串 自带换行符 \n (也可以用字符串拼接 每行指令结尾 + \n)
// 字符串拼接:! 0 200 200 210 1\n TEXT 0 0 250 0 test\n PRINT\n
string += `! 0 ${item.width} ${item.height} 210 1
TEXT 0 0 50 0 标题1${item.title1} ${item.title2}
TEXT 0 0 50 40 标题2${item.title2} 标题3${item.title3}
VB QR 50 60 M 2 U 6
${item.id}
ENDQR
PRINT
`;
return string;
}
const tspl = (item) => {
let string = '';
//模板字符串 自带换行符 \n (也可以用字符串拼接 每行指令结尾 + \n)
// 字符串拼接SIZE 100mm, 100mm\n TEXT 250,0,"TSS24.BF2",0,1,1,"test"\n PRINT 1\n
string += `SIZE ${item.width}mm, ${item.height}mm
GAP 3mm, 0mm
DIRECTION 1
CLS
TEXT 50,0,"TSS24.BF2",0,1,1,"标题1${item.title1} ${item.title2}"
TEXT 50,40,"TSS24.BF2",0,1,1,"标题2${item.title2} 标题3${item.title3}"
QRCODE 50,60,H,6,A,0,"${item.id}"
PRINT 1
`;
return string;
}
const commands = (type, item) => {
switch (type) {
case 'esc':
return esc(item);
case 'cpcl':
return cpcl(item);
case 'tspl':
return tspl(item);
default:
return 'HELLO WORLD';
}
}
export {
commands
}

@ -0,0 +1,330 @@
// import './encoding.js'; // 如需要 中文乱码问题 new TextEncoder
const printInteraction = {
device_id: null,// 打印设备id
service_id: '',
write_id: '',
notify_id: '',
connFlag: false,
services: [],
stringData: [],
callback: function () {},
ab2hex: (buffer) => {//ArrayBuffer转16进度字符串示例
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('')
},
// 获取蓝牙状态是否开启 准备
getBluetooth: (callback) => {
const _this = printInteraction;
uni.getBluetoothAdapterState({
success(res) {
if (res.available) { // 蓝牙适配器是否可用
callback && _this.startSearch(callback)
} else {
uni.showToast({
title: '本机蓝牙不可用',
duration: 2000,
icon: 'none'
});
}
},
fail(err) {
uni.showToast({
title: '本机蓝牙不可用',
duration: 2000,
icon: 'none'
});
}
});
},
// 初始化蓝牙
init: () => {
const _this = printInteraction;
uni.openBluetoothAdapter({
success(res) {
uni.getBluetoothAdapterState({
success(res) {
if (res.available) { // 蓝牙适配器是否可用
if (res.discovering) { // 蓝牙适配器是否处于搜索状态
// 立即停止搜索 节约电
_this.stopSearch();
}
}
}
});
},
fail(err) {
setTimeout(function () {
uni.showToast({
title: '蓝牙打开失败,请打开蓝牙',
duration: 2000,
icon: 'none'
});
}, 200)
}
})
},
// 搜索附近蓝牙,并获取蓝牙列表
startSearch: (callback) => {
const _this = printInteraction;
uni.showLoading({
title: '正在搜索...'
});
uni.startBluetoothDevicesDiscovery({ // 开始搜索蓝牙设备
allowDuplicatesKey: true,
powerLevel: 'high',
success(res) {
setTimeout(function () {
uni.getBluetoothDevices({ // 获取蓝牙列表
success(re) {
callback(re)
},
fail(err) {
uni.showToast({
title: '获取设备失败',
duration: 2000,
icon: 'error'
});
}
});
_this.stopSearch();
uni.hideLoading();
}, 5000);
},
fail(err) {
uni.showToast({
title: '搜索失败',
duration: 2000,
icon: 'error'
});
}
})
},
// 连接蓝牙设备
connectDevice: (device_id) => {
const _this = printInteraction;
if (_this.device_id) {
// 对刚才的连接设备进行断开
_this.breakConnction();
}
if (device_id) {
uni.showLoading({
title: '正在连接'
});
uni.createBLEConnection({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId:device_id,
success(res) {
_this.device_id = device_id;
uni.showToast({
title: '连接成功',
duration: 2000,
icon: 'error'
});
// 安卓设备 需要等待1.5秒之后
_this.getDeviceService();
},
fail(err) {
uni.showToast({
title: `连接失败!${err.code}`,
duration: 2000,
icon: 'none'
});
},
complete() {
uni.hideLoading();
}
});
} else {
uni.showToast({
title: '请选择蓝牙设备',
duration: 2000,
icon: 'error'
});
}
},
// 获取蓝牙设备所有服务
getDeviceService: () => {
const _this = printInteraction;
uni.getBLEDeviceServices({
deviceId: _this.device_id,
success(res) {
_this.services = res.services;
res.services.some(item => {
if (item.isPrimary) {
_this.service_id = item.uuid;
return true;
}
});
_this.getDeviceCharacter();
}
})
},
// 获取连接设备服务的所有特征值
getDeviceCharacter: () => {
const _this = printInteraction;
uni.getBLEDeviceCharacteristics({
deviceId: _this.device_id,
serviceId: _this.service_id,
success(res) {
let notify_id, write_id, read_id;
res.characteristics.forEach(item => {
if (item.properties.notify) {
notify_id = item.uuid;
}
if (item.properties.write) {
write_id = item.uuid;
}
if (item.properties.read) {
read_id = item.uuid;
}
});
if (notify_id != null && write_id != null) {
_this.notify_id = notify_id;
_this.write_id = write_id;
_this.read_id = read_id;
if (_this.connFlag) { // 发送失败进来自动再次点击 | 或手动点击按钮重新打印
_this.printer(_this.stringData, _this.callback);
}
}
},
fail(err) {
// _this.getDeviceService(); // 再次获取服务uuid可能导致死循环 需要进行判断)
uni.showToast({
title: err.code || '没有找到特征值',
duration: 2000,
icon: 'error'
});
}
})
},
// 字符串转为ArrayBuffer
stringToArrayBuffer: (str) => {
const len = str.length * 2; // 一个中文占 二个字节, 其它字母 占二个字节
const buf = new ArrayBuffer(len);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
// console.log('1',str.charCodeAt(i)); // 对特殊字符不支持 如表情emoji,麻将图案等
// console.log('2',str.codePointAt(i)); // 对特殊字符可以支持
bufView[i] = str.charCodeAt(i);
}
return buf;
},
// 开始打印
printer: (stringData, callback) => {
const _this = printInteraction;
if (!_this.device_id) {
uni.showToast({
title: "请选择蓝牙设备",
duration: 2000,
icon: 'error'
});
return;
}
uni.showLoading({
title: '正在打印'
});
_this.connFlag = true;
_this.stringData = stringData;
_this.callback = callback;
let buffer = null;
stringData.forEach(item => {
_this._writeBLECharacteristicValue(_this.stringToArrayBuffer(item));
});
},
_writeBLECharacteristicValue: (buffer) => {
const _this = printInteraction;
// 字节长度小于等于20 只调用一次
if (buffer.byteLength <= 20) {
uni.writeBLECharacteristicValue({
deviceId: _this.device_id,
serviceId: _this.service_id,
characteristicId: _this.notify_id,
value: buffer,
writeType: "write", //iOS 优先 write安卓优先 writeNoResponse
success(res) {
_this.connFlag = false;
_this.callback();
},
fail(err) {
// 换一个uuid获取特征值重新写入 (或者 手动去点击打印,可能需要连续点击多次)
if (_this.services.length) {
_this.services.splice(0, 1);
_this.services.some(item => {
if (item.isPrimary) {
_this.service_id = item.uuid;
return true;
}
});
_this.getDeviceCharacter();
}
},
complete() {
uni.hideLoading();
}
})
} else {
// 进行分包 最大20字节长度
const newBuffer = buffer.slice(20);
const writeBuffer = buffer.slice(0, 20);
uni.writeBLECharacteristicValue({
deviceId: _this.device_id,
serviceId: _this.service_id,
characteristicId: _this.notify_id,
value: writeBuffer,
writeType: "write", //iOS 优先 write安卓优先 writeNoResponse
success(res) {
// console.log('递归发送成功 继续发送');
_this._writeBLECharacteristicValue(newBuffer);
},
fail(err) {
// console.log('递归发送失败', err.code);
// 一旦发生失败,就要重新发送一遍,(另有需求断点发送 可保存newBuffer变量
uni.hideLoading();
if (_this.services.length) {
_this.services.splice(0, 1);
_this.services.some(item => {
if (item.isPrimary) {
_this.service_id = item.uuid;
return true;
}
});
_this.getDeviceCharacter();
}
}
})
}
},
// 停止搜索
stopSearch: () => {
uni.stopBluetoothDevicesDiscovery({
success(res) {}
})
},
// 关闭蓝牙适配器
closeBluetoothAdapter: () => {
uni.closeBluetoothAdapter({
success(res) {
console.log('关闭蓝牙适配器')
}
})
},
// 断开连接
breakConnction: () => {
const _this = printInteraction;
if (_this.device_id) {
uni.closeBLEConnection({
deviceId: _this.device_id,
success() {
_this.connFlag = false;
}
});
}
},
}
export default printInteraction;

@ -0,0 +1,185 @@
自定义指令手册
更新历史
日期 版本 作者 描述说明
Mingkeo 2018/10/08 10:10
2018/10/08 1.0.0
最后一次修改时间
使用指南
指令:
向打印机下发的指令内容,文档中的指令有 16 进制 byte 和字符串两种形式,
请注意区分
返回值:
指令为查询指令,打印机的返回数据
参数值:
指令为设置指令,打印机的设置参数
使用场景:
打印机调用指令的场景、效果以及注意事项等
适用模式:
目前我司打印机共有三种模式分别为票据模式ESC、标签模式TSC
面单模式CPCL。如果没有特殊声明16 进制 byte 指令格式适用于票据模式,
字符串指令适用于标签模式。此处标明的适用模式是指在当前模式下该指令有
效。
兼容性:
大部分指令仅对特定型号有效,此处描述该指令的兼容性相关信息
切换工作模式
指令1F 1B 1F FC 01 02 03 n
n 0x33 TSC (标签模式)
0x55 ESC (票据模式)
0x44 CPCL (面单模式)
使用场景:该指令用于切换打印机的工作模式,切换成功后,打印机蜂鸣器响一
声,并重启打印机。该指令同样也可以用于令打印机重启。
适用模式:票据模式、标签模式、面单模式
兼容性:多种模式的打印机
设置 USB 端口的 PID 和 VID
指令1F 1B 1F 24 04 06 07 vidH vidL pidH pidL
适用模式:票据模式
指令SET USB_VID_PID vidstring pidstring
适用模式:标签模式
vidH VID 高字节
vidL VID 低字节
pidH PID 高字节
pidL PID 低字节
vidstring VID 字符串
pidstring PID 字符串
2 佳博集团 智汇网络
参数值: VID:6868 PID:0500
VID:8866 PID:0100
VID:0471 PID:0055
使用场景:
该指令用于设置打印机 USB 接口的 PID 与 VID请在参数值指定的三组 PID 与
VID 内修改,以免引起连接问题。以 VID:6868 PID:0500 为例
票据模式(以 16 进制 byte 发送)
1F 1B 1F 24 04 06 07 68 68 05 00
标签模式(以 string 类型发送)
SET USB_VID_PID 0x6868 0x0500\r\n
兼容性:自检页中包含 USB VID PID 信息的打印机
查询电池电量
指令1F 1B 1F A8 10 11 12 13 14 15 77
返回值: 0x31低电量
0x32中电量
0x33高电量
0x35正在充电
使用场景:
用于查询打印机电池电量,与打印机电量指示灯一一对应
佳博集团 智汇网络 3
适用模式:票据模式
兼容性:带电池的移动打印机
设置默认字体放大倍数
指令1F 1B 1F B3 02 03 04 n1 n2 n3 n4
n1 字符宽度
n2 字符高度
n3 汉字宽度
n4 汉字高度
参数值: 0x00默认
0x01翻倍
使用场景:
设置字体默认放大倍数后,重启打印机不失效
适用模式:票据模式
兼容性:票据模式通用
打印自检页
指令1F 1B 1F 93 10 11 12 15 16 17 10 00
适用模式:票据模式
指令:SELFTEST
适用模式:标签模式
4 佳博集团 智汇网络
设置蓝牙名称前缀
指令1F 1B 1F B0 02 03 04 n C0 C1…Cn
n 表示字符串长度,不大于 9
C0-Cn 字符对应 ASCII 码
使用场景:
不支持中文名称。设置成功后,打印机会自动重启并打印蓝牙信息,蓝牙名称为
XXXX+MAC 地址后四位
适用模式:票据模式、标签模式、面单模式
兼容性:带蓝牙模块的打印机
设置蓝牙 PIN 码
指令1F 1B 1F B1 02 03 04 C0 C1 C2 C3
C0 0-9 对应 ASCII 码
C1 0-9 对应 ASCII 码
C2 0-9 对应 ASCII 码
C3 0-9 对应 ASCII 码
使用场景:
设置成功后,打印机蜂鸣器会响一声
适用模式:票据模式、标签模式、面单模式
兼容性:带蓝牙模块的打印机
佳博集团 智汇网络 5
设置网口参数
指令描述 指令格式 参数说明
n1-n4 为 IP 地址00-FF
设置 IP 1F 1B 1F 91 00 49 50 n1-n4 n1-n6 为 MAC 地址00-FF
n1-n4 为子网掩码00-FF
设置 MAC 地址 1F 1B 1F 91 00 49 44 n1-n6 n1-n4 为网关地址00-FF
设置子网掩码 1F 1B 1F 91 00 49 45 n1-n4
设置网关地址 1F 1B 1F 91 00 49 46 n1-n4
使用场景:
设置成功后,打印机蜂鸣器会响一声
适用模式:票据模式
兼容性:带网口的打印机
6 佳博集团 智汇网络

@ -0,0 +1,977 @@
GP-M320
目录
前言.................................................................................................................................................................. 3
“!” 命令.................................................................................................................................................... 4
PRINT 命令......................................................................................................................................................5
POPRINT 命令...............................................................................................................................................6
FORM 命令.................................................................................................................................................... 7
ABORT 命令...................................................................................................................................................8
PREFEED 命令............................................................................................................................................... 9
SETMAG 命令..............................................................................................................................................10
CONTRAST 命令.......................................................................................................................................11
SPEED 命令.................................................................................................................................................. 12
UNDERLINE 命令........................................................................................................................................13
SETBOLD 命令.............................................................................................................................................14
TEXT 命令..................................................................................................................................................... 15
LINE 命令......................................................................................................................................................17
INVERSE-LINE 命令................................................................................................................................... 18
BOX 命令...................................................................................................................................................... 20
TONE 命令................................................................................................................................................... 21
PW 命令........................................................................................................................................................22
BARCODE-TEXT 命令................................................................................................................................ 23
BARCODE 命令........................................................................................................................................... 24
QRCODE 命令............................................................................................................................................. 25
BEEP 命令..................................................................................................................................................... 28
JOURNAL 命令............................................................................................................................................29
GAP-SENSE 和 BAR-SENSE 命令........................................................................................................ 30
COMPRESSED-GRAPHICS 命令..............................................................................................................31
使用范例........................................................................................................................................................ 32
第2页
前言
本手册详细介绍了 CPCL 语言中的各种命令,通过这些命令,编程人员可以利用移动打印机中
的内置文本、图形、条码打印和通信等功能。文中通篇使用了以下符号约定:
{ } 必填项
[ ] 可选项
( ) 缩写命令
{ } 文字项
空格字符用于分隔命令行中的各个字段。
许多命令都随附了命令使用示例。在每个示例中的“Input”一词后面显示的是命令集后接打
印机处理这些命令后生成的打印输出结果“Output”示例。
备注:每一条语句 必须以回车换行符结尾,(0X0D X0A)
第3页
“!” 命令
格式:
<!> {offset} <200> <200> {height} {qty}
其中:
<!>:使用“!”作为控制会话的起始字符。
{offset}:整个标签的横向偏置。此值可以使所有域以指定的单位数量进行横向偏置。
<200>:横向分辨率(以点/英寸为单位)。
<200>:纵向分辨率(以点/英寸为单位)。
{height}:标签的最大高度。
标签 最大高度的计算方法是,先测出从第 1 个黑条(或标签间隙)底部到下
一个黑条(或标签间隙)顶部之间的距离。然后从中减去 1/16 英寸1.5 毫
米),所得结果即最大高度。(以点为单位时:对于 203 d.p.i 打印机,减去 12
点;对于 306 d.p.i. 打印机,减去 18 点)
{qty}:要打印的标签数量。最大值 = 1024。
打印机命令示例 输出:
输入:
!0 200 200 210 1
TEXT 4 0 30 40 Hello World
FORM
PRINT
第4页
PRINT 命令
PRINT 命令作为整个命令集的结束命令,将会启动文件打印。在任何情况下(行式打印模
式除外),这项命令都必须是最后一条命令。执行 PRINT 命令时,打印机将从控制会话中退
出。确保使用回车和换行字符结束此项及所有命
令。
格式:
{command}
其中:
{command}PRINT
第5页
POPRINT 命令
POPRINT 命令:旋转 180 度后,执行 PRINT 命令内容
PRINT 命令内容
PRINT 命令作为整个命令集的结束命令,将会启动文件打印。在任何情况下(行式打印模
式除外),这项命令都必须是最后一条命令。执行 PRINT 命令时,打印机将从控制会话中退
出。确保使用回车和换行字符结束此项及所有命
令。
格式:
{command}
其中:
{command}PRINT
第6页
FORM 命令
FORM 命令可以指示打印机在一页打印结束后切换至下一页顶部。
格式:
{command}
其中:
{command}FORM
在下例中,打印机将在打印标签后执行换页操作。有关设置执行 FORM 命令时打印机操
作的信息,请参阅设计收据和列表一节中的 SETFF设置换页命令。
示例
输入:
!0 200 200 3 1
IN-CENTIMETERS
CENTER
TEXT 4 1 0 5 Hello World
FORM
PRINT
第7页
ABORT 命令
ABORT 命令可以在不打印的情况下终止当前的控制会话。
格式:
{command}
其中:
{command}ABOR
第8页
PREFEED 命令
PREFEED 命令指示打印机在打印之前将介质向前移动指定长度。
备注:如果使用负值指定预送纸量,则该值不得超过标签高度。这样做会导致打印机陷入不间断的循
环中,并禁止与打印机的进一步交互,直至关闭电源重新开机为止。
格式:
{command} {length}
其中:
{command}PREFEED
{length}:打印机在打印之前将介质向前移动的单位长度。
PREFEED 命令示例
下例将打印机设置为在打印之前向前移动 40 点行。
输入:
!0 200 200 210 1
PREFEED 40
TEXT 7 0 0 20 PREFEED EXAMPLE
FORM
PRINT
第9页
SETMAG 命令
SETMAG 命令可将常驻字体放大指定的放大倍数。
格式:
{command} {w} {h}
其中:
{command}SETMAG
{w}:字体的宽度放大倍数。有效放大倍数为 1 到 10。
{h}:字体的高度放大倍数。有效放大倍数为 1 到 10。
备注SETMAG 命令在标签打印后仍保持有效。这意味着要打印的下一标签将使用最近设置的
SETMAG 值。
要取消 SETMAG 值并使打印机可以使用默认字体大小,请使用 SETMAG 命令,且放大倍数为 0,0。
SETMAG 命令示例 输出:
输入:
!0 200 200 210 1
CENTER
SETMAG 1 1
TEXT 0 0 0 10 Font 0-0 at SETMAG 1 1
SETMAG 1 2
TEXT 0 0 0 40 Font 0-0 at SETMAG 1 2
SETMAG 2 1
TEXT 0 0 0 80 Font 0-0 at SETMAG 2 1
SETMAG 2 2
TEXT 0 0 0 110 Font 0-0 at SETMAG 2 2
SETMAG 2 4
TEXT 0 0 0 145 Font 0-0 at SETMAG 2 4
; Restore default font sizes
SETMAG 0 0
FORM
PRINT
第 10 页
CONTRAST 命令
CONTRAST 命令用于指定整个标签的打印黑度。最亮的打印输出为对比度级别 0。最暗
的对比度级别为 3。
打印机在开机时的默认对比度级别为 0。必须为每个标签文件指定对比度级别。
备注:为了最大限度地提高打印效率,请始终使用尽可能低的对比度级别。
CONTRAST 命令,打印机(浓度)黑度默认根据 M320 机器显示面板设置的浓度为准。
格式:
{command} {level}
其中:
{command}CONTRAST
{level}:对比度级别。
0 = 默认值
1= 中
2= 暗
3 = 非常暗
第 11 页
SPEED 命令
此命令用于设置电机的最高速度级别。每一款打印机型号都设置了最低和最高极限速度。
SPEED 命令可以在 0 到 5 的范围内选择速度级别0 表示最低速度。为每一款打印机型号
设置的最高速度仅可在理想条件下达到。
电池或供电电压、材料厚度、打印黑度、是否使用贴标机、是否使用剥离器以及标签长度
等诸多因素均会影响最大极限打印速度。
警告:在练习此命令时,用户将会覆盖标签打印的出厂设定速度,这可能会导致打印质量下降。如果
使用当前 SPEED 设置影响到打印质量,则应降低打印速度。
SPEED 命令,打印机速度 默认根据 M320 机器显示面板设置的速度为准。
格式:
{command} {speed level}
其中:
{command}SPEED
{speed level}:一个介于 0 到 5 之间的数字0 表示最低速度。
SPEED 命令示例
输入:
!0 200 200 150 1
SPEED 4
TEXT 5 0 0 20 PRINTS AT SPEED 4
FORM
PRINT
第 12 页
UNDERLINE 命令
UNDERLINE 命令用于给文本加下划线。仅当所使用的字体支持下划线时,此命令才会起
作用。如果所使用的字体不支持 UNDERLINE则将忽略此命令。
以下字体支持 UNDERLINE
GBUNSN24.CPF
GBUNSN24.CPF
格式:
{command} {mode}
{command}UNDERLINE
{mode}:从下面选择一项
"on"
"off"
UNDERLINE 示例
输入 (UNDERLINE.LBL) 输出:
!0 200 200 200 1
ENCODING GB18030
UNDERLINE ON
TEXT GBUNSG24.CPF 0 20 30 Underlined tu
UNDERLINE OFF
TEXT GBUNSG24.CPF 0 20 80 Normal tu
ENCODING ASCII
UNDERLINE OFF
PRINT
第 13 页
SETBOLD 命令
SETBOLD 命令可使文本加粗并且稍微加宽。SETBOLD 命令会采用一个操作数来设置文本变
黑的程度。
格式:
!U1 SETBOLD {value}
其中,{value} 是介于 0 到 5 之间的偏移量。
备注:{value} 将采用 通过单位命令设置的单位。
默认单位设置以点为单位。203 点 = 1 英寸)
如果单位为英寸,则偏移值的范围为 0-0.0246 英寸。
如果单位为厘米,则偏移值的范围为 0-0.0625 厘米。
如果单位为毫米,则偏移值的范围为 0-0.625 毫米。
完成后,请务必发出“!U1 SETBOLD 0”命令以禁用粗体格式。
SET BOLD 命令示例
输入:
!U1 SETBOLD 2
This text is in bold !U1 SETBOLD 0
but this text is normal.
输出:
This text is in bold but this text is normal.
第 14 页
TEXT 命令
TEXT 命令用于在标签上添加文本。这项命令及其各衍生命令可以控制使用的具体字体号
和大小、标签上文本的位置以及文本的方向。标准常驻字体能够以 90 度的增量旋转,如下例
所示。
格式: 效果
{command} {font} {size} {x} {y} {data}
其中: 横向打印文本。
{command}:从下面选择一项: 逆时针旋转 90 度,纵向打印文本。
{command} ( 同 VTEXT )。
逆时针旋转 80 度,反转打印文本。
TEXT或 T 逆时针旋转 270 度,纵向打印文本。
VTEXT或 VT
TEXT90或 T90
TEXT180或 T180
TEXT270或 T270
{font}:字体名称/编号。
- 1 ==> 8×12英文字体
- 55 ==> 16×16GB18030
- 4 ==> 32×32GB18030
- 其他默认 ==> 24×24GB2312
{size}:字体的大小标识。
- 有效字体大小范围是 0 至 7。
{x}:横向起始位置。
{y}:纵向起始位置。
{data}:要打印的文本。
第 15 页
示例 输出:
输入:
!0 200 200 210 1
TEXT 4 0 200 100 TEXT
TEXT90 4 0 200 100 T90
TEXT180 4 0 200 100 T180
TEXT270 4 0 200 100 T270
FORM
PRINT
第 16 页
LINE 命令
使用 LINE 命令可以绘制任何长度、宽度和角度方向的线条。
格式:
{command} {x 0 } {y 0 } {x 1 } {y 1 } {width}
其中:
{command} 从下面选择一项:
LINE或 L打印线条。
{x 0 }:左上角的 X 坐标。
{y 0 }:左上角的 Y 坐标。
{x 1 }:以下项的 X 坐标:
- 水平轴的右上角。
- 垂直轴的左下角。
{y 1 }:以下项的 Y 坐标:
- 水平轴的右上角。
- 垂直轴的左下角。
{width}:线条的单位宽度
示例 输出:
输入:
!0 200 200 210 1
LINE 0 0 200 0 1
LINE 0 0 200 200 2
LINE 0 0 0 200 3
FORM
PRINT
备注:输出中显示的文本坐标仅用于说明之目的。
第 17 页
INVERSE-LINE 命令
INVERSE-LINE 命令的语法与 LINE 命令相同。位于 INVERSE-LINE 命令所定义区域内
的以前创建的对象的黑色区域将重绘为白色,白色区域将重绘为黑色。这些对象可以包括文本、
条码和/或图形(包括下载的 .pcx 文件。INVERSE-LINE 对在其之后创建的对象不起作用,
即使这些对象位于该命令的覆盖区域内也是如此。
在示例 INVERSE2.LBL 中,在 INVERSE-LINE 命令之后创建的文本字段部分仍然为黑
色,因此不可见,即使被放置在 INVERSE-LINE 区域内也是如此。
格式:
{command} {x 0 } {y 0 } {x 1 } {y 1 } {width}
其中:
{command}:从下面选择一项:
INVERSE-LINE或 IL在现有字段上方打印一个线条以反转图像。
{x 0 }:在上角的 X 坐标。
{y 0 }:在上角的 Y 坐标。
{x 1 }:以下项的 X 坐标:
- 水平轴的右上角。
- 垂直轴的左下角。
{y 1 }:以下项的 Y 坐标:
- 水平轴的右上角。
- 垂直轴的左下角。
{width}:反转线的单位宽度。
INVERSE-LINE 命令示例 输出 1
输入 1
!0 200 200 210 1
CENTER
TEXT 4 0 0 45 SAVE
TEXT 4 0 0 95 MORE
INVERSE-LINE 0 45 145 45 45
INVERSE-LINE 0 95 145 95 45
FORM
PRINT
第 18 页
输入 2 输出 2
!0 200 200 210 1
T 4 2 30 20 $123.45
T 4 2 30 70 $678.90
IL 25 40 350 40 90
T 4 2 30 120 $432.10
FORM
PRINT
第 19 页
BOX 命令
用户可以使用 BOX 命令生成具有指定线条宽度的矩形。
格式:
{command} {x 0 } {y 0 } {x 1 } {y 1 } {width}
其中:
{command}BOX
{x 0 }:左上角的 X 坐标。
{y 0 }:左上角的 Y 坐标。
{x 1 }:右下角的 X 坐标。
{y 1 }:右下角的 Y 坐标。
{width}:形成矩形框的线条的单位宽度。
BOX 命令示例 输出:
输入:
!0 200 200 210 1
BOX 0 0 200 200 1
FORM
PRINT
备注:输出中显示的文本坐标仅用于说明之目的。
第 20 页
TONE 命令
TONE 命令可用于替代 CONTRAST 命令来指定所有标签的打印黑度。最亮的打印输出为
色调级别 -99。最暗的色调级别为 200。打印机在开机时的默认色调级别为 0。色调级别设置
在更改前对所有打印任务保持有效。TONE 和 CONTRAST 命令不能彼此组合使用。
格式:
{command} {level}
其中:
{command}TONE
{level} 选择介于 -99 到 200 之间的值。
对比度级别和色调级别的等价等效关系:
对比度 0 = 色调 0 对比度 1 = 色调 100
对比度 2 = 色调 200 对比度 3 = 无等效色调
备注:在使用 Zebra Technologies 生产的无衬纸介质时,建议将 TONE 值设置为 25 以获得最
佳打印效果。
TONE 命令 M320 暂时不支持。
第 21 页
PW 命令
打印机假定页面宽度为打印机的完整宽度。打印会话的最大高度由页面宽度和可用打印内
存决定。如果页面宽度小于打印机的完整宽度,则用户可以通过指定页面宽度来增加最大页面
高度。
备注:此命令应在打印会话开始时发出。
格式:
{command} {width}
其中:
{command}:从下面选择一项:
PAGE-WIDTH或 PW 指定页面宽度。
{width}:页面的单位宽度。
PAGE-WIDTH 示例 输出 1
输入 1
!UTILITIES
PW 300
PRINT
在打印此文本时,标签内存宽度设置为 300 点
PAGE-WIDTH 示例(续) 输出 2
输入 2
!UTILITIES
SETLP 7 0 15
PW 200
PRINT
在打印此文本时,标签内存宽度设置为 200 点
第 22 页
BARCODE-TEXT 命令
条码的必要。文本位于条码下方的中间位置。
使用 BARCODE-TEXT OFF或 BT OFF可以禁用它。
格式:
{command} {font number} {font size} {offset}
其中:
{command}BARCODE-TEXT或 BT
{font number}:注释条码时要使用的字体号。
{font size}:注释条码时要使用的字体大小。
{offset}:文本距离条码的单位偏移量。
BARCODE-TEXT 示例 输出:
输入:
!0 200 200 400 1
JOURNAL
CENTER
; Annotate bar codes using font 7 size 0
; and offset 5 dots from the bar code.
BARCODE-TEXT 7 0 5
BARCODE 128 1 1 50 0 20 123456789
VBARCODE 128 1 1 50 40 400 112233445
BARCODE-TEXT OFF
FORM
PRINT
第 23 页
BARCODE 命令
BARCODE 命令能够以指定的宽度和高度纵向和横向打印条码。
标准条码
格式:
{command} {type} {width} {ratio} {height} {x} {y} {data}
其中:
{command}:从下面选择一项:
BARCODE或 B横向打印条码。
VBARCODE或 VB 纵向打印条码。
{type}:从下表中选择:
符号 用法
UPC-A UPCA、UPCA2、UPCA5
UPC-E UPCE、UPCE2、UPCE5
EAN/JAN-13 EAN13、EAN132、EAN135
EAN/JAN-8 EAN8、EAN82、EAN 85
Code 39 39、39C、F39、F39C
Code 93/Ext.93 93
Interleaved 2 of 5 I2OF5
Interleaved 2 of 5带 checksum I2OF5C
German Post Code I2OF5G
Code 128自动 128
UCC EAN 128 UCCEAN128
Codabar CODABAR、CODABAR16
MSI/Plessy MSI、MSI10、MSI1010、MSI1110
Postnet POSTNET
FIM FIM
备注:条码数据必须在 {data} 部分提供,且应位于新的行字符序列之前。否则,打印机可能会将下一
条命令识别为条码数据,因而生成错误条码,并导致下一条命令的执行错误。
第 24 页
QRCODE 命令
格式:
{command} {type} {x} {y} [M n] [U n]
{data}
<ENDQR>
其中:
{command}:从下面选择一项:
BARCODE或 B横向打印条码。
VBARCODE或 VB纵向打印条码。
{type}QR
{x}:横向起始位置。
{y}:纵向起始位置。
[M n]QR Code 规范编号。选项是 1 或 2。QR Code Model 1 是原始规范,而 QR
Code Model 2 则是该符号的经过增强后的形式。Model 2 提供了附加功能,而
且可以自动与 Model 1 进行区分。Model 2 为推荐规范,是默认值。
[U n]:模块的单位宽度/单位高度。
范围是 1 至 32。默认值为 6。
{data}:提供生成 QR Code 所需的信息。请参见下面的示例。
{data} 除了包含实际的输入数据字符串外,还包含一些模式选择符号。输入数据类型可以
由打印机软件自动识别,也可以通过手动方式设置。模式选择符号和实际数据之间
有一个分隔符(逗号)。
用于自动选择数据类型的数据字段格式:
<Error Correction Level><Mask No.><Data Input Mode (should be “A”)>,<Data
Character String>
纠错级别应为以下符号之一:
H - 极高可靠性级别H 级);
Q - 高可靠性级别Q 级);
M - 标准级别M 级);
L - 高密度级别L 级)。
掩码号可能会省略,也可能具有一个值(介于 0 至 8 之间):
无 - 软件自动选择掩码;
0 至 7 使用带有相应编号0 至 7的掩码
8 - 无掩码。
用于手动选择数据类型的数据字段格式包含字符模式符号,采用如下格式:
<Error Correction Level><Mask No.><Data Input Mode (should be “M”)>,
第 25 页
<Character Mode 1><Data Character String 1>, <Character Mode 2><Data
Character String 2>,< : >< : >,<Character Mode n><Data Character String n>
字符模式符号:
N 数字;
A - 字母数字;
Bxxxx 二进制,二进制模式包含由 2 字节 BCD 代码表示的数据字符的数量 (xxxx)。
K Kanji
不同的数据字段(带有对应的字符模式符号)由逗号分隔。
如果输入模式设置为“自动”,则无法设置 0x80 至 0x9F 和 0xe0 至 0xFF 的二进制
代码。
<ENDQR>:终止 QR Code。
数据字段格式设置示例
示例 1
Error Correction Level:Standard level <M>
Mask No.:<None>
Input mode:Automatic setting <A>
Data:QR Code
The {data} field presentation for generating a QR code under the conditions
above:
MA,QR Code
示例 2
Error Correction Level:Ultra high reliability level <H>
Mask No.:<0>
Input mode:Manual setting <M>
Character Mode:Numeric mode <N>
Data:0123456789012345
The {data} field presentation:
H0M,N0123456789012345
示例 3
Error Correction Level:Standard level <M>
Mask:<None> (Automatic selection)
Input mode:Manual setting <M>
Character Mode:Alphanumeric mode <A>
Data:AC-42
第 26 页
The {data} field presentation:
MM,AAC-42
示例 4
Error Correction Level:High density level <L>
Mask No.:Automatic setting <None>
Input mode:Manual setting <M>
Character Mode:Alphanumeric <A>
Data:QR code
Character Mode:Numeric <N>
Data:0123456789012345
Character Mode:Alphanumeric <A>
Data:QRCODE
Character Mode:Binary <B>
Data: qrcode
The {data} field presentation:
LM,AQRcode,N0123456789012345,AQRCODE,B0006qrcode
备注BARCODE-TEXT 命令不能用于 QR Code。对于任何所需的可人工识读文本必须使用 TEXT
命令单独输入,如下例所示。
QR Code 示例 输出:
输入:
备注:可人工识读的文本不包含在 QR 代码输
!0 200 200 500 1 出结果中。
B QR 10 100 M 2 U 10
MA,QR code ABC123
ENDQR
T 4 0 10 400 QR code ABC123
FORM
PRINT
第 27 页
BEEP 命令
此命令用于指示打印机让蜂鸣器发出给定时间长度的声音。未配备蜂鸣器的打印机将忽略
此命令。
格式:
{command} {beep _ length}
其中:
{command}BEEP
{beep _ length}:蜂鸣持续时间,以 1/8 秒为单位递增指定。
BEEP 命令示例
此示例指示打印机蜂鸣两秒钟16 x .125 秒 = 2 秒)
输入:
!0 200 200 210 1
CENTER
TEXT 5 0 0 10 beeps for two seconds
BEEP 16
FORM
PRINT
第 28 页
JOURNAL 命令
默认情况下如果在打印周期期间LABEL 模式)发现明显标记(介质背面的黑色水平条),
则打印机会检查介质对齐情况是否正确。必要时,可以使用 JOURNAL 命令禁用自动校正功能。
用户程序负责在 JOURNAL 模式下进行检查并确保有纸。有关检查缺纸条件的详细信息,请参
阅状态询问命令。
格式:
{command}
其中:
{command}JOURNAL
第 29 页
GAP-SENSE 和 BAR-SENSE 命令
这两项命令用于指示打印机应采用的页顶检测方式。如果未指定任何命令,则打印机默认
使用 BAR-SENSE。未配备间隙传感器的打印机将尝试伪间隙感应。
格式:
{command}
其中:
{command}:从下面选择一项:
GAP-SENSE # (0-255)
BAR-SENSE # (0-255)
Gap Sense 和 Bar Sense 命令可后跟数字以调整感应度。这对来自 Zebra 以外的供应
商的间隙感应介质十分有用。
GAP-SENSE 命令示例:
下例将打印机配置为间隙感应。此外,它还指定页顶到间隙的距离为零。
输入:
!UTILITIES
GAP-SENSE
SET-TOF 0
PRINT
第 30 页
COMPRESSED-GRAPHICS 命令
可以使用图形命令打印位映射图形。扩展图形数据使用 ASCII 十六进制字符来表示(参见
示例)。通过对十六进制数据的等效二进制字符使用 COMPRESSED-GRAPHICS 命令,可以
将数据大小减半。如果使用 CG对于每 8 位图形数据,将会发送一个 8 位字符。如果使用
EG将使用两个字符16 位)来传输 8 位图形数据,因此 EG 的效率会减半。 但是由于该
数据是字符数据,因此比二进制数据更容易处理和传输。
格式:
{command} {width} {height} {x} {y} {data}
其中:(目前只支持 CG 命令)
{command}:从下面选择一项:
EXPANDED-GRAPHICS或 EG横向打印扩展图形。
VEXPANDED-GRAPHICS或 VEG纵向打印扩展图形。
COMPRESSED-GRAPHICS或 CG横向打印压缩图形。
VCOMPRESSED-GRAPHICS或 VCG纵向打印压缩图形。
{width}:图像的宽度(以字节为单位)。
{height}:图像的高度(以点为单位)。
{x}:横向起始位置。
{y}:纵向起始位置。
{data}:图形数据。
图形命令示例 输出:
输入:
!0 200 200 210 1
EG 2 16 90 45 F0F0F0F0F0F0F0F00F0F0F0F0F0F0F0F
F0F0F0F0F0F0F0F00F0F0F0F0F0F0F0F
FORM
PRINT
备注:图形输出已被放大。实际大小为所示输出的四分之一。
第 31 页
使用范例
! 0 200 200 1600 1
PAGE - WIDTH 560
LINE 0 100 560 100 2
LINE 0 260 560 260 2
LINE 0 350 560 350 2
LINE 0 430 560 430 2
LINE 0 540 560 540 2
LINE 0 640 560 640 2
LINE 0 775 560 775 2
LINE 0 915 560 915 2
LINE 0 995 560 995 2
LINE 0 1095 560 1095 2
LINE 0 1135 560 1135 2
LINE 0 1190 560 1190 2
LINE 0 1290 560 1290 2
LINE 0 1400 560 1400 2
LINE 0 1450 560 1450 2
LINE 0 1500 560 1500 2
LINE 300 350 300 430 2
LINE 40 430 40 775 2
LINE 280 640 280 775 2
LINE 280 915 280 1095 2
LINE 200 1095 200 1188 2
LINE 320 1095 320 1188 2
LINE 440 1095 440 1188 2
LINE 280 1188 280 1400 2
LINE 112 1400 112 1500 2
LINE 224 1400 224 1500 2
LINE 336 1400 336 1500 2
LINE 448 1400 448 1500 2
LINE 280 1500 280 1600 2
BARCODE 128 3 3 110 20 110 000000000000001
TEXT 4 0 50 220 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
TEXT 4 3 10 270 01 02 03 (测试打印)
第 32 页
TEXT 4 0 10 360 浙江杭州南星桥公司
TEXT 4 0 310 360 2017/11/11
TEXT 2 0 0 460 收
TEXT 2 0 0 485 件
TEXT 2 0 0 560 寄
TEXT 2 0 0 585 件
SETBOLD 1
TEXT 2 0 50 440 火先生 13800001111
TEXT 2 0 50 505 浙江省 杭州市 西湖区 文三路伟星大厦 9D
SETBOLD 0
TEXT 2 0 50 550 风先生 15812345678
TEXT 2 0 50 600 上海市 黄浦区 南京西路 231 号人民公园
TEXT 2 0 0 670 服
TEXT 2 0 0 695 务
TEXT 2 0 50 650 内容品名:
TEXT 55 0 50 680 计费重量:
TEXT 55 0 50 705 声明价值:
TEXT 55 0 50 730 代收金额:
TEXT 2 0 290 650 签收人/签收时间
TEXT 2 0 500 745 已验视
TEXT 2 0 160 650 服饰
第 33 页
第 34 页
BARCODE 128 1 1 40 10 925 000000000000001
TEXT 2 0 25 969 000000000000001
TEXT 2 0 290 925 订单号:
TEXT 55 0 10 1005 收件方信息:
TEXT 55 0 10 1025 火先生 13800001111
TEXT 55 0 10 1045 浙江省 杭州市 西湖区
TEXT 55 0 10 1065 文三路伟星大厦 9D
TEXT 55 0 290 1005 寄件方信息:
TEXT 55 0 290 1025 风先生 15812345678
TEXT 55 0 290 1045 上海市 黄浦区
TEXT 55 0 290 1065 南京西路 231 号人民公园
TEXT 55 0 10 1110 内容品名
TEXT 55 0 210 1110 计费重量(kg)
TEXT 55 0 330 1110 声明价值(¥)
TEXT 55 0 450 1110 代收金额(¥)
TEXT 2 0 10 1140 服饰
BARCODE 128 1 1 60 10 1200 000000000000001
TEXT 2 0 15 1265 000000000000001
TEXT 55 0 10 1300 收件方信息:
TEXT 55 0 10 1320 火先生 13800001111
TEXT 55 0 10 1340 浙江省 杭州市 西湖区
TEXT 55 0 10 1360 文三路伟星大厦 9D
TEXT 55 0 290 1300 收件方信息:
TEXT 55 0 290 1320 风先生 15812345678
TEXT 55 0 290 1340 上海市 黄浦区
TEXT 55 0 290 1360 南京西路 231 号人民公园
TEXT 55 0 10 1415 内容品名
TEXT 55 0 122 1415 计费重量(kg)
TEXT 55 0 234 1415 声明价值(¥)
TEXT 55 0 346 1415 代收金额(¥)
TEXT 55 0 458 1415 现付运费(¥)
TEXT 2 0 10 1460 服饰
TEXT 55 0 10 1510 打印时间
TEXT 55 0 290 1510 快递员签名/签名时间
TEXT 55 0 10 1540 打印时间2017/11/11 11:02:42
第 35 页
TEXT 55 0 400 1560 月 日
PRINT
第 36 页
! 0 200 200 1248 1
LINE 0 0 608 0 2
SETMAG 2 2
T 8 1 200 13
SETMAG 1 1
LINE 0 70 608 70 2
SETMAG 2 3
CONTRAST 3
T 8 1 0 90 x 省-xx 市
CONTRAST 0
LINE 0 187 608 187 2
SETMAG 2 2
SETMAG 1 1
LINE 0 265 608 265 2
BARCODE-TEXT 7 0 5
BARCODE 128 3 1 140 60 270 01508482741451
BARCODE-TEXT OFF
LINE 0 440 608 440 2
T 8 1 50 443 收件人签字:
T 8 1 50 485 单号01508482741451
BARCODE QR 450 445 M 2 U 5
MA,01508482741451
ENDQR
LINE 0 512 445 512 2
LINE 445 440 445 555 2
LINE 0 555 608 555 2
LINE 0 561 608 561 2
SETMAG 2 2
T 8 1 210 573 01508482741451
SETMAG 1 1
LINE 0 631 608 631 2
CONTRAST 3
T 8 1 50 636 张三加 34 13765879011
T 8 1 50 666 上海市 上海市 青浦区
T 8 1 50 696 香花桥街道外青松公路 254 号
第 37 页
CONTRAST 0
LINE 0 776 608 776 2
T 8 1 50 781 测试 01 13765478976
T 8 1 50 806 安徽省 蚌埠市 固镇县
T 8 1 50 831 回顾的卡刚到家啊 009
LINE 0 865 608 865 2
LINE 40 631 40 865 2
T 8 1 5 656 收
T 8 1 5 716 件
T 8 1 5 791 寄
T 8 1 5 821 件
BARCODE-TEXT 7 0 5
BARCODE 128 2 1 90 5 905 01508482741451
BARCODE-TEXT OFF
T 8 1 0 1054 已验视
LINE 0 1084 608 1084 2
LINE 368 865 368 1084 2
T 8 1 375 875 【测试测试】
LINE 0 1101 608 1101 2
CONTRAST 3
T 8 1 50 1105 张三加 34 13765879011
T 8 1 50 1127 上海市 上海市 青浦区
T 8 1 50 1149 香花桥街道外青松公路 254 号
LINE 0 1177 608 1177 2
CONTRAST 0
T 8 1 50 1182 测试 01
T 8 1 50 1207 13765478976
LINE 0 1245 608 1245 2
LINE 40 1101 40 1245 2
T 8 1 5 1110 收
T 8 1 5 1140 件
T 8 1 5 1182 寄
T 8 1 5 1212 件
LINE 324 1177 324 1245 2
T 8 1 334 1182 百世快递
第 38 页
T 8 1 334 1207 单号01508482741451
SPEED 0
PRINT
第 39 页

@ -0,0 +1,715 @@
CPCL 标签打印机
中文编程手册
珠海智汇网络设备有限公司
客服热线 400 811 0380
www.smarnet.cc
*本手册指令适用于支持 CPCL 指令打印机,部分指令仅支持特定型号
 更新历史
日期 版本 作者 描述说明
2018/03/01 1.0.0 Mingkeo 初始版本
2018/03/03 1.0.1 Mingkeo 添加代码范例
2018/03/23 1.0.2 Mingkeo 修正部分错误
2018/03/30 1.0.3 Mingkeo 增加 SET BOLD 指令,修正 TEXT 字体信息
文档最后一次修改时间 2018/03/30 13:55
使用指南
 文档字体规则
符号 描述
{} 必填项
[] 可选项
() 缩写命令
<> 文字项(字符串)
\r\n 回车和换行字符,用于每行结尾
空格字符用于分隔命令行中的各个字段
文档中的默认单位为 dot与毫米换算1 mm = 8 dots
 设计标签
以下代码内容为一个最简单标签的必备要素,以此为例,详解设计标签时必备的
内容和要点。
!0 200 200 210 1\r\n
TEXT 4 0 30 40 Hello World\r\n
PRINT\r\n
一张标签通常包含三个部分,即标签规格设定(蓝色部分)、标签内容设定(绿
色部分)和执行打印走纸指令(红色部分)。
标签规格设定包括标签尺寸、打印宽度
标签内容设定可以参考本文档卷标内容设计指令内容,本例中系打印文本。
执行打印指令用于打印出设计好的标签,在此指令发送后打印机才执行打印动
作。
需要特别注意,在每一条指令结尾需要加入换行符,即字符串“\r\n”或 16 进
制 0x0D 0x0A下文代码范例中不再赘述。
佳博集团 智汇网络 1
打印机命令
 标签初始化指令
标签文件通常以“!”字符作为开头后接“x”偏置参数、“x”和“y”轴分辨
率、标签长度以及要打印的标签数量。包含这些参数的行称为命令起始行。
任何情况下标签文件都是以命令起始行开头以“PRINT”命令结尾。用于构
建具体标签的命令置于这两项命令之间。
指令语法
<!> {offset} <200> <200> {height} {qty}
参数 说明
<!> 使用“!”作为控制会话的起始字符
{offset} 标签横向偏移量
<200> 横向 DPI
<200> 纵向 DPI
{height} 标签最大高度
标签最大高度的计算方法是,先测出从第 1 个黑条(或标签间隙)底部到下一
个黑条(或标签间隙)顶部之间的距离。然后从中减去 1/16 英寸1.5 毫米),
所得结果即最大高度。(以点为单位时:对于 203 DPI 打印机,减去 12 点;对
于 306 DPI 打印机,减去 18 点)
代码范例
! 0 200 200 210 1
TEXT 4 0 30 40 Hello World
PRINT
2 佳博集团 智汇网络
 PRINT 指令
PRINT 命令作为整个命令集的结束命令,将会启动文件打印。在任何情况下(行
式打印模式除外),这项命令都必须是最后一条命令。执行 PRINT 命令时,打
印机将从控制会话中退出。确保使用回车和换行字符结束此项及所有命令。
指令语法
{command}
参数 说明
{command} PRINT
 使用注释
注释可以添加在命令会话第一行和“PRINT”命令之间。
在文件中添加注释时,需要将“;”字符置入第一列,以此作为注释行的起始部
分。“;”字符与行末尾的所有其他文本都将被忽略。
指令语法
代码范例
! 0 200 200 55 1
; Center justify text
CENTER
; Print the words A COMMENT
TEXT 5 1 0 5 A COMMENT
; Print the label and go to top of next form
PRINT
佳博集团 智汇网络 3
文本指令
 TEXT 命令
TEXT 命令用于在标签上添加文本。这项命令及其各衍生命令可以控制使用的具
体字体号和大小、标签上文本的位置以及文本的方向。标准常驻字体能够以 90
度的增量旋转,如下例所示。
指令语法
{command} {font} {size} {x} {y} {data}
{command} 指令效果
TEXT或 T 横向打印文本。
VTEXT或 VT 逆时针旋转 90 度,纵向打印文本。
TEXT90或 T90 (同 VTEXT。
TEXT180或 T180 逆时针旋转 80 度,反转打印文本。
TEXT270或 T270 逆时针旋转 270 度,纵向打印文本。
参数 说明
{font} 字体名称/编号
{size} 忽略该参数,请输入任意数字
{x} 横向起始位置
{y} 纵向起始位置
{data} 要打印的文本
{font} 英文字体 中文字体
0 12*24 24*24 简体中文 GB18030
1 9*17 24*24 简体中文 GB18030
2 12*24 24*24 简体中文 GB18030
3 10*20 20*20 简体中文 GBK
4 16*32 32*32 简体中文 GBK
4 佳博集团 智汇网络
5 9*17 24*24 简体中文 GB18030
6 12*24
7 12*24 24*24 简体中文 GB18030
8 12*24 24*24 简体中文 GB18030
10 24*48 48*48 简体中文 GBK
11 8*16 24*24 简体中文 GB18030
13 12*24 24*24 繁体中文 BIG5
20 8*16 16*16 简体中文 GB18030
24 12*24 24*24 简体中文 GB18030
41 8*12
42 12*20
43 16*24
44 24*32
45 32*48
46 14*19
47 21*27
48 14*25
49 28*56
55 8*16 16*16 简体中文 GB18030
代码范例
! 0 200 200 210 1
TEXT 0 0 200 100 TEXT
TEXT90 0 0 200 100 T90
TEXT180 0 0 200 100 T180
TEXT270 0 0 200 100 T270
PRINT
 TEXT 串联命令CONCAT
使用文本串联,可以为字符串分配不同的字符样式,在同一文本行上使用统一间
距进行打印。
佳博集团 智汇网络 5
指令语法
{command} {x} {y}
{font} {size} {offset} {data}
......
{font} {size} {offset} {data}
<ENDCONCAT>
{command} 指令效果
CONCAT 横向串联
参数 说明
{x} 横向起始位置
{y} 纵向起始位置
{font} 字体名称/编号
{size} 忽略该参数,请输入任意数字
{offset} 文本相对起始位置的偏置单位值
{data} 要打印的文本
<ENDCONCAT> 终止文本串联
代码范例
! 0 200 200 210 1
CONCAT 75 75
225$
3 3 0 12
4 2 5 34
ENDCONCAT
PRINT
 SETMAG 命令
SETMAG 命令可将常驻字体放大指定的放大倍数
指令语法
6 佳博集团 智汇网络
{command} {w} {h}
参数 说明
{command} SETMAG
{w} 宽度放大倍数,有效放大倍数为 1 到 16
{h} 高度放大倍数,有效放大倍数为 1 到 16
SETMAG 命令在标签打印后仍保持有效。这意味着要打印的下一标签将使用最近
设置的 SETMAG 值。要取消 SETMAG 值并使打印机可以使用默认字体大小,请使
用 SETMAG 命令,且放大倍数为 0,0。
代码范例
! 0 200 200 210 1
CENTER
SETMAG 1 1
TEXT 0 0 0 10 Font 0-0 at SETMAG 1 1
SETMAG 1 2
TEXT 0 0 0 40 Font 0-0 at SETMAG 1 2
SETMAG 2 1
TEXT 0 0 0 80 Font 0-0 at SETMAG 2 1
SETMAG 2 2
TEXT 0 0 0 110 Font 0-0 at SETMAG 2 2
SETMAG 2 4
TEXT 0 0 0 145 Font 0-0 at SETMAG 2 4
; Restore default font sizes
SETMAG 0 0
PRINT
 SETBLOD 命令
SETBLOD 命令可将常驻字体加粗
指令语法
{command} {value}
佳博集团 智汇网络 7
参数 说明
{command} SETBLOD
{value} 0不加粗
1加粗
SETBLOD 命令在设定后保持有效。这意味着要打印的下一部分标签内容将使用最
近设置的 SETBLOD 值。要取消加粗请发送 SETBLOD 0
代码范例
! 0 200 200 210 1
CENTER
SETBOLD 1
TEXT 1 0 0 10 BOLD FONT
SETBOLD 0
TEXT 1 0 0 40 NORMAL FONT
PRINT
8 佳博集团 智汇网络
条码指令
 BARCODE 命令
BARCODE 命令能够以指定的宽度和高度纵向和横向打印条码。建议以理想宽窄比
和窄条宽度进行打印。
指令语法
{command} {type} {width} {ratio} {height} {x} {y} {data}
{command} 指令效果
BARCODE或 B 横向打印条码
VBARCODE或 VB 纵向打印条码
{tpye} 条码种类 理想宽窄比 理想窄点宽度
UPCA UPC-A 2:1 2
UPCE UPC-E 2:1 2
EAN13 EAN/JAN-13 2:1 2
EAN8 EAN/JAN-8 2:1 2
39 Code39 2.5:1 2
93 Code93/Ext.93 1.5:1 1
128 Code128 N/A 2
CODABAR Codabar 2.5:1 2
{ratio} 宽窄比
0 1.5:1
1 2.0:1
2 2.5:1
3 3.0:1
4 3.5:1
20 2.0:1
佳博集团 智汇网络 9
21 2.1:1
22 2.2:1
23 2.3:1
24 2.4:1
25 2.5:1
26 2.6:1
27 2.7:1
28 2.8:1
29 2.9:1
30 3.0:1
参数 说明
{width} 窄条的单位宽度
{ratio} 宽条与窄条的比率
{height} 条码的单位高度
{x} 横向起始位置
{y} 纵向起始位置
{data} 条码数据
代码范例
! 0 200 200 210 1
BARCODE 128 1 1 50 150 10 HORIZ.
TEXT 7 0 210 70 HORIZ.
VBARCODE 128 1 1 50 10 200 VERT.
VTEXT 7 0 70 140 VERT.
PRINT
 BARCODE-TEXT 命令
BARCODE-TEXT 命令用于通过创建条码时所用的相同数据来标记条码。这项命令
避免了使用单独文本命令注释条码的必要。文本位于条码下方的中间位置。
10 佳博集团 智汇网络
使用 BARCODE-TEXT OFF或 BT OFF可以禁用它。
指令语法
{command} {font number} {font size} {offset}
参数 说明
{command} BARCODE-TEXT或 BT
{font number} 注释条码时要使用的字体号
{font size} 忽略该参数,请输入任意数字
{offset} 文本距离条码的单位偏移量
代码范例
! 0 200 200 400 1
CENTER
BARCODE-TEXT 7 0 5
BARCODE 128 1 1 50 0 20 123456789
VBARCODE 128 1 1 50 40 400 112233445
BARCODE-TEXT OFF
PRINT
 QR Code
指令语法
{command} {type} {x} {y} [M n] [U n]
{data}
<ENDQR>
{command} 指令效果
BARCODE或 B 横向打印条码
VBARCODE或 VB 纵向打印条码
参数 说明
{type} QR
佳博集团 智汇网络 11
{x} 横向起始位置
{y} 纵向起始位置
[M n] QR Code 规范编号,1 或 2推荐为 2
[U n] 模块的单位宽度/单位高度
1-32默认为 6
<ENDQR> 终止 QR Code
{data}除了包含实际的输入数据字符串外,还包含一些模式选择符号。输入数据
类型可以由打印机软件自动识别,也可以通过手动方式设置。模式选择符号和实
际数据之间有一个分隔符(逗号)。
{date}格式如下:
<纠错等级><掩码><输入模式>,<所需生成二维码的数据>
{data} 含义及参数范围
<纠错等级> H - 极高可靠性级别H 级)
Q - 高可靠性级别Q 级)
M - 标准级别M 级)
L - 高密度级别L 级)
<掩码> 省略 - 软件自动选择掩码
0 至 7 使用带有相应编号0 至 7的掩码
8 - 无掩码
<输入模式> A - 自动
M - 手动(不支持)
<所需生成二维码的数据>
代码范例
! 0 200 200 500 1
CENTER
B QR 10 100 M 2 U 10
12 佳博集团 智汇网络
MA,QR code ABC123
ENDQR
T 4 0 10 400 QR code ABC123
PRINT
佳博集团 智汇网络 13
图形指令
 BOX 命令
用户可以使用 BOX 命令生成具有指定线条宽度的矩形。
指令语法
{command} {x0} {y0} {x1} {y1} {width}
参数 说明
{command} BOX
{x0} 左上角的 X 坐标
{y0} 左上角的 Y 坐标
{x1} 右下角的 X 坐标
{y1} 右下角的 Y 坐标
{width} 形成矩形框的线条的单位宽度
代码范例
! 0 200 200 210 1
BOX 0 0 200 200 1
PRINT
 LINE 命令
使用 LINE 命令可以绘制任何长度、宽度和角度方向的线条。
指令语法
{command} {x0} {y0} {x1} {y1} {width}
参数 说明
{command} LINE (或 L
{x0} 起始点的 X 坐标
14 佳博集团 智汇网络
{y0} 起始点的 Y 坐标
{x1} 终止点的 X 坐标
{y1} 终止点的 Y 坐标
{width} 线条的单位宽度
代码范例
! 0 200 200 210 1
LINE 0 0 200 0 1
LINE 0 0 200 200 2
LINE 0 0 0 200 3
PRINT
 INVERSE-LINE 命令
INVERSE-LINE 命令的语法与 LINE 命令相同。位于 INVERSE-LINE 命令所定义
区域内的以前创建的对象的黑色区域将重绘为白色,白色区域将重绘为黑色。这
些对象可以包括文本、条码和/或图形。INVERSE-LINE 对在其之后创建的对象不
起作用,即使这些对象位于该命令的覆盖区域内也是如此。
指令语法
{command} {x0} {y0} {x1} {y1} {width}
参数 说明
{command} INVERSE-LINE (或 IL
{x0} 起始点的 X 坐标
{y0} 起始点的 Y 坐标
{x1} 终止点的 X 坐标
{y1} 同 y0
{width} 反色区域高度
代码范例
! 0 200 200 210 1
T 4 2 30 20 $123.45
佳博集团 智汇网络 15
T 4 2 30 70 $678.90
INVERSE-LINE 25 40 350 40 90
T 4 2 30 120 $432.10
PRINT
 GRAPHICS 命令
可以使用图形命令打印位映射图形。扩展图形数据使用 ASCII 十六进制字符来
表示(参见示例)。通过对十六进制数据的等效二进制字符使用
COMPRESSED-GRAPHICS 命令,可以将数据大小减半。
如果使用 CG对于每 8 位图形数据,将会发送一个 8 位字符。如果使用 EG
将使用两个字符16 位)来传输 8 位图形数据,因此 EG 的效率会减半。但是
由于该数据是字符数据,因此比二进制数据更容易处理和传输。
如果传输一个 byte0xFF),CG 指令下直接发送 0xFF而 EG 下发送字符串 FF
即 16 进制 0x46 0x46
指令语法
{command} {width} {height} {x} {y} {data}
{command} 说明
EXPANDED-GRAPHICS或 EG 横向打印扩展图形
COMPRESSED-GRAPHICS或 CG 横向打印压缩图形
参数 说明
{width} 图像的宽度(以字节为单位)
{height} 图像的高度(以点为单位)
{x} 横向起始位置
{y} 纵向起始位置
{data} 图形数据,由上至下,由左至右
代码范例
! 0 200 200 210 1
16 佳博集团 智汇网络
EG 2 16 90 45 F0F0F0F0F0F0F0F00F0F0F0F0F0F0F0F
F0F0F0F0F0F0F0F00F0F0F0F0F0F0F0F
PRINT
范例中位图数据结构如下图所示,宽度为 2 个 byte高度为 16 个点
佳博集团 智汇网络 17
高级指令
 JUSTIFICATION 命令
使用对齐命令可以控制字段的对齐方式。默认情况下,打印机将左对齐所有字段。
对齐命令将对所有后续字段保持有效,直至指定了其他对齐命令。
指令语法
{command} [end]
参数 说明
{command} 从下面选择一项:
CENTER 居中对齐所有后续字段。
[end] LEFT 左对齐所有后续字段。
RIGHT 右对齐所有后续字段。
对齐的结束点。如果未输入参数,则对于横向打
印,对齐命令将使用打印头的宽度;而对于纵向
打印,对齐命令将使用零(页头)
代码范例
!0 200 200 210 1
CENTER 383
TEXT 4 0 0 75 C
LEFT
TEXT 4 0 0 75 L
RIGHT 383
TEXT 4 0 0 75 R
PRINT
18 佳博集团 智汇网络
 PAGE-WIDTH 命令
打印机假定页面宽度为打印机的完整宽度。打印会话的最大高度由页面宽度和可
用打印内存决定。如果页面宽度小于打印机的完整宽度,则用户可以通过指定页
面宽度来增加最大页面高度。
指令语法
{command} {width}
参数 说明
{command} PAGE-WIDTH或 PW
{width} 页面的单位宽度
代码范例
! 0 200 200 210 1
LEFT
PAGE-WIDTH 374
TEXT 4 0 0 0 测试文本 123abc
PRINT
! 0 200 200 210 1
LEFT
PAGE-WIDTH 574
TEXT 4 0 0 0 测试文本 123abc
PRINT
 SPEED 命令
此命令用于设置电机的最高速度级别。每一款打印机型号都设置了最低和最高极
限速度。SPEED 命令可以在 0 到 5 的范围内选择速度级别0 表示最低速度。
为每一款打印机型号设置的最高速度仅可在理想条件下达到。电池或供电电压、
材料厚度、打印黑度、是否使用贴标机、是否使用剥离器以及标签长度等诸多因
素均会影响最大极限打印速度。
佳博集团 智汇网络 19
指令语法
{command} {speed level}
参数 说明
{command} SPEED
{speed level} 0-5
代码范例
! 0 200 200 450 1
LEFT
SPEED 4
TEXT 5 0 0 20 PRINTS AT SPEED 4
PRINT
 BEEP 命令
此命令用于指示打印机让蜂鸣器发出给定时间长度的声音。未配备蜂鸣器的打印
机将忽略此命令。
指令语法
{command} {beep_length}
参数 说明
{command} BEEP
{beep_length} 蜂鸣持续时间,以 1/8 秒为单位递增
代码范例
! 0 200 200 210 1
CENTER
TEXT 5 0 0 10 beeps for two seconds
BEEP 16
PRINT
20 佳博集团 智汇网络
查询指令
 <ESC>1B 68
打印机返回一个 byte 的数据,根据返回值,判断打印机当前状态。
指令语法
16 进制1B 68
返回值16 进制) 打印机状态
00 正常
01 走纸或正在打印
02 缺纸
04 开盖有纸
06 开盖缺纸
返回值可转换为二进制按位bit进行观察判定0 位假1 为真
bit 打印机状态(值为 0 或 1
0 正在打印
1 缺纸
2 开盖
3 电量低
4 未定义
5 未定义
6 未定义
7 未定义
佳博集团 智汇网络 21

Binary file not shown.

@ -0,0 +1,312 @@
<template>
<view>
<button @click="discoveryPrinter"></button>
<button @click="stopDiscoveryPrinter"></button>
<button @click="offDevice"></button>
<view>选中的设备{{deviceId}}</view>
<view class="text-red">设备列表</view>
<radio-group @change="radioChange">
<label class="uni-list-cell uni-list-cell-pd" v-for="(item, index) in devices" :key="item.deviceId">
<view>
<radio :value="item.deviceId" :checked="deviceId === item.deviceId"/>{{item.name}}({{item.deviceId}})
</view>
</label>
</radio-group>
<view class="text-red">设备服务列表</view>
<radio-group @change="radioChange2">
<label class="uni-list-cell uni-list-cell-pd" v-for="(item, index) in serverList" :key="index">
<view>
<radio :value="item.uuid" />{{item.uuid}}
</view>
</label>
</radio-group>
<view class="text-red">特征值</view>
<radio-group @change="radioChange3">
<label class="uni-list-cell uni-list-cell-pd" v-for="(item, index) in characteristics" :key="index">
<view>
<radio :value="item.uuid" />{{item.uuid}}(write:{{item.properties.write}} notify:{{item.properties.notify}} indicate:{{item.properties.indicate}})
</view>
</label>
</radio-group>
<button @click="writeBLECharacteristicValue"></button>
</view>
</template>
<script>
import PrinterJobs from './print/printerjobs.js'
import printerUtil from './print/printerutil.js'
export default {
name: 'xun_bluetoothPrint_t',
data () {
return {
devices: [],
deviceId: '',
serverList: [],
serviceId: '',
characteristics: [],
characteristicId: ''
}
},
mounted () {
//
this.openBluetoothAdapter()
},
methods: {
openBluetoothAdapter () {
var _this = this
uni.openBluetoothAdapter({
complete (e) {
console.log(e);
if (!e.errCode) {
console.log('初始化完成')
} else if (e.errCode == 10001) {
uni.showToast({
icon: 'none',
title: '请打开手机蓝牙'
})
} else {
uni.showToast({
icon: 'none',
title: e.errMsg
})
}
}
})
},
//
discoveryPrinter () {
var _this = this
_this.devices = []
uni.startBluetoothDevicesDiscovery({
complete (e) {
// console.log(e)
if (e.errMsg == "startBluetoothDevicesDiscovery:ok") {
// ArrayBuffer16
uni.onBluetoothDeviceFound(devices => {
// console.log(devices)
_this.devices.push(devices.devices[0])
})
}
}
})
},
//
stopDiscoveryPrinter () {
uni.stopBluetoothDevicesDiscovery()
},
radioChange (e) {
console.log(e)
if(!e.detail.value) return;
this.deviceId = e.detail.value
this.serviceId = ''
this.serverList = []
this.characteristics = []
this.characteristicId = ''
//
this.connect()
},
radioChange2 (e) {
console.log(e)
this.serviceId = e.detail.value
this.characteristics = []
this.characteristicId = ''
//
this.getBLEDeviceCharacteristics()
},
radioChange3 (e) {
console.log(e)
this.characteristicId = e.detail.value
},
offDevice () {
uni.showLoading({
title: '正在断开连接...'
});
uni.closeBLEConnection({
deviceId: this.deviceId,
success: () => {
uni.showToast({
title: '已断开'
});
this.deviceId = ''
},
fail: (e) => {
console.log(`断开连接id:${this.deviceId}失败`, e)
uni.showToast({
title: '断开失败.'
});
},
complete: () => {
uni.hideLoading()
}
})
},
connect () {
var _this = this
uni.showLoading({
title: '正在连接...'
});
uni.createBLEConnection({
deviceId: _this.deviceId,
success (e) {
console.log('连接成功', e)
//(service)
setTimeout(() => {
_this.getBLEDeviceServices()
}, 500)
},
fail (e) {
console.log('连接失败', e)
uni.showToast({
icon: 'none',
title: '连接设备失败'
})
},
complete() {
uni.hideLoading()
}
})
},
//
getBLEDeviceServices(){
var _this = this
uni.getBLEDeviceServices({
// deviceId createBLEConnection
deviceId: _this.deviceId,
success:(res)=>{
console.log('device services:', res)
_this.serverList = res.services
// _this.serviceId = res.services[0].uuid;
console.log('serverId:', _this.serviceId)
}
})
},
getBLEDeviceCharacteristics () {
var _this = this
uni.getBLEDeviceCharacteristics({
// deviceId createBLEConnection
deviceId: _this.deviceId,
// serviceId getBLEDeviceServices
serviceId:_this.serviceId,
success:(res)=>{
console.log('getBLEDeviceCharacteristics', res)
_this.characteristics = res.characteristics
// _this.characteristicId = res.characteristics[0].uuid
},
fail:(res)=>{
console.log(res)
}
})
},
//
writeBLECharacteristicValue(){
if (!this.deviceId) {
uni.showToast({
icon: 'none',
title: '请选择设备'
})
return
}
if (!this.serviceId) {
uni.showToast({
icon: 'none',
title: '请选择设备服务'
})
return
}
if (!this.characteristicId) {
uni.showToast({
icon: 'none',
title: '请选择特征值'
})
return
}
let printerJobs = new PrinterJobs()
var arr = [{
title: '阿根廷车厘子',
number: 20,
price: '¥188.00'
}, {
title: '新鲜山竹',
number: 38,
price: '¥199.00'
}]
var arr2 = [{
title: '平台消暑费',
price: '9.99'
},{
title: '打包费',
price: '2.00',
}]
printerJobs
.print(`下单时间2021-07-25 15:30:23`)
.print(printerUtil.fillLine())
.print('备注')
.setSize(2, 2)
.setBold(true)
.print(`叫那位非常漂亮的小姐姐送来,其他人送来拒收`)
.setSize(1, 1)
.setBold(false)
.print(printerUtil.fillLine('*'))
.setAlign('lt')
.printArray(arr)
.print(printerUtil.fillAround('其它'))
.print(printerUtil.inline('平台随机立减', `-2.10`))
.print(printerUtil.inline('平台服务费', `-10.99`))
.printArray(arr2)
.print(printerUtil.fillLine())
.setAlign('rt')
.setSize(1, 2)
.setBold(true)
.print(`用户支付¥99.99`)
.print(printerUtil.fillLine())
let buffer = printerJobs.buffer();
console.log('buffer>>>',buffer)
this.printbuffs(buffer)
},
printbuffs(buffer) {
// 1.
// 2.20
//
const maxChunk = 20;
const delay = 20;
for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) {
let subPackage = buffer.slice(i, i + maxChunk <= length ? (i + maxChunk) : length);
setTimeout(this.printbuff, j * delay, subPackage);
}
},
printbuff(buffer) {
var _this = this
uni.writeBLECharacteristicValue({
// deviceId getBluetoothDevices onBluetoothDeviceFound
deviceId: _this.deviceId,
// serviceId getBLEDeviceServices
serviceId: _this.serviceId,
// characteristicId getBLEDeviceCharacteristics
characteristicId: _this.characteristicId,
// valueArrayBuffer
value: buffer,
success:(res)=> {
console.log('writeBLECharacteristicValue success', res.errMsg)
},
fail:(res)=> {
console.log('writeBLECharacteristicValue fail', res.errMsg)
},
complete (e) {
console.log('writeBLECharacteristicValue complete', e)
}
})
}
}
}
</script>
<style scoped>
* { font-size: 24rpx;}
button { font-size: 28rpx;}
.text-red { color: red; font-size: 28rpx;}
</style>

@ -0,0 +1,326 @@
class Bluetooth {
constructor() {
this.isOpenBle = false;
this.deviceId = "";
this.serviceId = "";
this.writeId = "";
this.notifyId = "";
this.initBluetooth();
}
showToast(title) {
uni.showToast({
title: title,
icon: 'none',
'duration': 2000
});
}
initBluetooth () {
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: res => {
this.isOpenBle = true;
// this.showToast("初始化蓝牙模块成功");
console.log("初始化蓝牙模块成功")
resolve(res);
},
fail: err => {
if (err.code == 10001 ) {
this.showToast('请先打开蓝牙')
} else {
// this.showToast(`初始化蓝牙模块失败` + JSON.stringify(err));
console.log(`初始化蓝牙模块失败` + JSON.stringify(err))
}
reject(err);
},
});
});
}
startBluetoothDevicesDiscovery(options) {
if (!this.isOpenBle) {
this.showToast(`初始化蓝牙模块失败`)
return;
}
const { uuids = [], isListen, callback } = options
let self = this;
const devices = [];
isListen && uni.onBluetoothDeviceFound(res => {
const newDevice = res.devices[0]
if (!newDevice.name) {
return false;
}
const isExist = devices.some(item => item.deviceId == newDevice.deviceId)
!isExist && devices.push(newDevice)
setTimeout(() => {
callback && callback(devices)
}, 1500)
})
return new Promise((resolve, reject) => {
setTimeout(() => {
uni.startBluetoothDevicesDiscovery({
services: uuids,
allowDuplicatesKey: false,
interval: 1000,
success: res => {
resolve(res)
},
fail: res => {
self.showToast(`搜索设备失败` + JSON.stringify(err));
reject(err);
},
complete: (e) => {
}
})
}, 300);
});
}
stopBluetoothDevicesDiscovery() {
let self = this;
return new Promise((resolve, reject) => {
uni.stopBluetoothDevicesDiscovery({
success: e => {
console.log('搜索已停止')
},
fail: e => {
uni.hideLoading();
self.showToast(`停止搜索蓝牙设备失败` + JSON.stringify(err));
}
})
});
}
createBLEConnection(deviceId) {
//设备deviceId
this.deviceId = deviceId;
let self = this;
uni.showLoading({
mask: true,
title: '设别连接中...'
})
console.log(this.deviceId);
return new Promise((resolve, reject) => {
uni.createBLEConnection({
deviceId,
success: (res) => {
console.log("res:createBLEConnection " + JSON.stringify(res));
resolve(res)
},
fail: err => {
self.showToast(`连接失败` + JSON.stringify(err));
this.showToast('连接失败')
reject(err);
},
complete() {
uni.hideLoading();
}
})
});
}
// 获取已发现的蓝牙设备
getBLEDevices () {
return new Promise((resolve, rejcet) => {
uni.getBluetoothDevices({
success(res) {
resolve(res)
},
fail(error) {
reject(error)
}
})
})
}
// 根据 uuid 获取处于已连接状态的设备
getConnectedBluetoothDevices (uuids) {
return new Promise((resolve, reject) => {
uni.getConnectedBluetoothDevices({
services: uuids,
success: (res) => {
resolve(res)
},
fail: (error) => {
reject(error)
}
})
})
}
//获取蓝牙设备所有服务(service)
getBLEDeviceServices() {
let _serviceList = [];
let deviceId = this.deviceId;
let self = this;
return new Promise((resolve, reject) => {
uni.getBLEDeviceServices({
deviceId,
success: res => {
console.log('before details services:', res.services )
for (let service of res.services) {
if (service.isPrimary) {
_serviceList.push(service);
}
}
uni.hideLoading();
console.log("_serviceList: " + JSON.stringify(_serviceList));
resolve(_serviceList)
},
fail: err => {
uni.hideLoading();
self.showToast(`获取设备Services` + JSON.stringify(err));
reject(err);
},
})
});
}
//获取蓝牙设备某个服务中所有特征值(characteristic)
getBLEDeviceCharacteristics(serviceId) {
let deviceId = this.deviceId;
this.serviceId = serviceId;
let self = this;
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: res => {
for (let _obj of res.characteristics) {
//获取notify
if (_obj.properties.notify) {
self.notifyId = _obj.uuid;
uni.setStorageSync('notifyId', self.notifyId);
}
//获取writeId
if (_obj.properties.write) {
self.writeId = _obj.uuid;
uni.setStorageSync('writeId', self.writeId);
}
}
//console.log("res:getBLEDeviceCharacteristics " + JSON.stringify(res));
let result = {
'notifyId': self.notifyId,
'writeId': self.writeId
};
// self.showToast(`获取服务中所有特征值OK,${JSON.stringify(result)}`);
resolve(result)
},
fail: err => {
self.showToast(`getBLEDeviceCharacteristics` + JSON.stringify(err));
reject(err);
}
})
});
}
//断开联链接
closeBLEConnection() {
let deviceId = this.deviceId;
console.log(deviceId);
uni.closeBLEConnection({
deviceId,
success(res) {
console.log(res)
},
fail(error) {
console.log('断开连接失败:', error)
}
})
}
notifyBLECharacteristicValue() {
let deviceId = this.deviceId;
let serviceId = this.serviceId;
let characteristicId = this.notifyId;
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
deviceId,
serviceId,
characteristicId,
success(res) {
uni.onBLECharacteristicValueChange(function(res) {
});
},
fail(res) {
console.log('notifyBLECharacteristicValueChange failed:' + res.errMsg);
}
});
}
writeBLECharacteristicValue(buffer, characteristicId) {
let deviceId = this.deviceId;
let serviceId = this.serviceId;
// let characteristicId = this.writeId;
this.characteristicId = characteristicId;
console.log("this: " + JSON.stringify(this));
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value: buffer,
// writeType: "writeNoResponse"
success(res) {
console.log('message发送成功', JSON.stringify(res));
resolve(res);
},
fail(err) {
console.log('message发送失败', JSON.stringify(err));
reject(err);
}
});
});
}
closeBluetoothAdapter() {
uni.closeBluetoothAdapter({
success: res => {
console.log(res)
}
});
}
//若APP在之前已有搜索过某个蓝牙设备并成功建立连接可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需进行搜索操作。
reconnect() {
(async () => {
try {
this.deviceId = this.deviceId || uni.getStorageSync("deviceId");
this.serviceId = this.serviceId || uni.getStorageSync("serviceId");
let result1 = await this.createBLEConnection();
console.log("createBLEConnection: " + JSON.stringify(result1));
let result2 = await this.getBLEDeviceServices();
console.log("getBLEDeviceServices: " + JSON.stringify(result2));
let result3 = await this.getBLEDeviceCharacteristics();
console.log("getBLEDeviceCharacteristics: " + JSON.stringify(result3));
// this.writeId = uni.getStorageSync("writeId");
// this.notifyId = uni.getStorageSync("notifyId");
} catch (err) {
console.log("err: " + JSON.stringify(err));
}
})();
}
}
export default Bluetooth;

@ -0,0 +1,193 @@
/**
* 修改自https://github.com/song940/node-escpos/blob/master/commands.js
* ESC/POS _ (Constants)
*/
var _ = {
LF: [0x0a],
FS: [0x1c],
FF: [0x0c],
GS: [0x1d],
DLE: [0x10],
EOT: [0x04],
NUL: [0x00],
ESC: [0x1b],
EOL: '\n',
};
/**
* [FEED_CONTROL_SEQUENCES Feed control sequences]
* @type {Object}
*/
_.FEED_CONTROL_SEQUENCES = {
CTL_LF: [0x0a], // Print and line feed
CTL_GLF: [0x4a, 0x00], // Print and feed paper (without spaces between lines)
CTL_FF: [0x0c], // Form feed
CTL_CR: [0x0d], // Carriage return
CTL_HT: [0x09], // Horizontal tab
CTL_VT: [0x0b], // Vertical tab
};
_.CHARACTER_SPACING = {
CS_DEFAULT: [0x1b, 0x20, 0x00],
CS_SET: [0x1b, 0x20]
};
_.LINE_SPACING = {
LS_DEFAULT: [0x1b, 0x32],
LS_SET: [0x1b, 0x33]
};
/**
* [HARDWARE Printer hardware]
* @type {Object}
*/
_.HARDWARE = {
HW_INIT: [0x1b, 0x40], // Clear data in buffer and reset modes
HW_SELECT: [0x1b, 0x3d, 0x01], // Printer select
HW_RESET: [0x1b, 0x3f, 0x0a, 0x00], // Reset printer hardware
};
/**
* [CASH_DRAWER Cash Drawer]
* @type {Object}
*/
_.CASH_DRAWER = {
CD_KICK_2: [0x1b, 0x70, 0x00], // Sends a pulse to pin 2 []
CD_KICK_5: [0x1b, 0x70, 0x01], // Sends a pulse to pin 5 []
};
/**
* [MARGINS Margins sizes]
* @type {Object}
*/
_.MARGINS = {
BOTTOM: [0x1b, 0x4f], // Fix bottom size
LEFT: [0x1b, 0x6c], // Fix left size
RIGHT: [0x1b, 0x51], // Fix right size
};
/**
* [PAPER Paper]
* @type {Object}
*/
_.PAPER = {
PAPER_FULL_CUT: [0x1d, 0x56, 0x00], // Full cut paper
PAPER_PART_CUT: [0x1d, 0x56, 0x01], // Partial cut paper
PAPER_CUT_A: [0x1d, 0x56, 0x41], // Partial cut paper
PAPER_CUT_B: [0x1d, 0x56, 0x42], // Partial cut paper
};
/**
* [TEXT_FORMAT Text format]
* @type {Object}
*/
_.TEXT_FORMAT = {
TXT_NORMAL: [0x1b, 0x21, 0x00], // Normal text
TXT_2HEIGHT: [0x1b, 0x21, 0x10], // Double height text
TXT_2WIDTH: [0x1b, 0x21, 0x20], // Double width text
TXT_4SQUARE: [0x1b, 0x21, 0x30], // Double width & height text
TXT_UNDERL_OFF: [0x1b, 0x2d, 0x00], // Underline font OFF
TXT_UNDERL_ON: [0x1b, 0x2d, 0x01], // Underline font 1-dot ON
TXT_UNDERL2_ON: [0x1b, 0x2d, 0x02], // Underline font 2-dot ON
TXT_BOLD_OFF: [0x1b, 0x45, 0x00], // Bold font OFF
TXT_BOLD_ON: [0x1b, 0x45, 0x01], // Bold font ON
TXT_ITALIC_OFF: [0x1b, 0x35], // Italic font ON
TXT_ITALIC_ON: [0x1b, 0x34], // Italic font ON
TXT_FONT_A: [0x1b, 0x4d, 0x00], // Font type A
TXT_FONT_B: [0x1b, 0x4d, 0x01], // Font type B
TXT_FONT_C: [0x1b, 0x4d, 0x02], // Font type C
TXT_ALIGN_LT: [0x1b, 0x61, 0x00], // Left justification
TXT_ALIGN_CT: [0x1b, 0x61, 0x01], // Centering
TXT_ALIGN_RT: [0x1b, 0x61, 0x02], // Right justification
};
/**
* [BARCODE_FORMAT Barcode format]
* @type {Object}
*/
_.BARCODE_FORMAT = {
BARCODE_TXT_OFF: [0x1d, 0x48, 0x00], // HRI barcode chars OFF
BARCODE_TXT_ABV: [0x1d, 0x48, 0x01], // HRI barcode chars above
BARCODE_TXT_BLW: [0x1d, 0x48, 0x02], // HRI barcode chars below
BARCODE_TXT_BTH: [0x1d, 0x48, 0x03], // HRI barcode chars both above and below
BARCODE_FONT_A: [0x1d, 0x66, 0x00], // Font type A for HRI barcode chars
BARCODE_FONT_B: [0x1d, 0x66, 0x01], // Font type B for HRI barcode chars
BARCODE_HEIGHT: function (height) { // Barcode Height [1-255]
return [0x1d, 0x68, height];
},
BARCODE_WIDTH: function (width) { // Barcode Width [2-6]
return [0x1d, 0x77, width];
},
BARCODE_HEIGHT_DEFAULT: [0x1d, 0x68, 0x64], // Barcode height default:100
BARCODE_WIDTH_DEFAULT: [0x1d, 0x77, 0x01], // Barcode width default:1
BARCODE_UPC_A: [0x1d, 0x6b, 0x00], // Barcode type UPC-A
BARCODE_UPC_E: [0x1d, 0x6b, 0x01], // Barcode type UPC-E
BARCODE_EAN13: [0x1d, 0x6b, 0x02], // Barcode type EAN13
BARCODE_EAN8: [0x1d, 0x6b, 0x03], // Barcode type EAN8
BARCODE_CODE39: [0x1d, 0x6b, 0x04], // Barcode type CODE39
BARCODE_ITF: [0x1d, 0x6b, 0x05], // Barcode type ITF
BARCODE_NW7: [0x1d, 0x6b, 0x06], // Barcode type NW7
BARCODE_CODE93: [0x1d, 0x6b, 0x48], // Barcode type CODE93
BARCODE_CODE128: [0x1d, 0x6b, 0x49], // Barcode type CODE128
};
/**
* [IMAGE_FORMAT Image format]
* @type {Object}
*/
_.IMAGE_FORMAT = {
S_RASTER_N: [0x1d, 0x76, 0x30, 0x00], // Set raster image normal size
S_RASTER_2W: [0x1d, 0x76, 0x30, 0x01], // Set raster image double width
S_RASTER_2H: [0x1d, 0x76, 0x30, 0x02], // Set raster image double height
S_RASTER_Q: [0x1d, 0x76, 0x30, 0x03], // Set raster image quadruple
};
/**
* [BITMAP_FORMAT description]
* @type {Object}
*/
_.BITMAP_FORMAT = {
BITMAP_S8: [0x1b, 0x2a, 0x00],
BITMAP_D8: [0x1b, 0x2a, 0x01],
BITMAP_S24: [0x1b, 0x2a, 0x20],
BITMAP_D24: [0x1b, 0x2a, 0x21]
};
/**
* [GSV0_FORMAT description]
* @type {Object}
*/
_.GSV0_FORMAT = {
GSV0_NORMAL: [0x1d, 0x76, 0x30, 0x00],
GSV0_DW: [0x1d, 0x76, 0x30, 0x01],
GSV0_DH: [0x1d, 0x76, 0x30, 0x02],
GSV0_DWDH: [0x1d, 0x76, 0x30, 0x03]
};
/**
* [BEEP description]
* @type {string}
*/
_.BEEP = [0x1b, 0x42]; // Printer Buzzer pre hex
/**
* [COLOR description]
* @type {Object}
*/
_.COLOR = {
0: [0x1b, 0x72, 0x00], // black
1: [0x1b, 0x72, 0x01] // red
};
/**
* [exports description]
* @type {[type]}
*/
export default _;

File diff suppressed because one or more lines are too long

@ -0,0 +1,235 @@
// const commands = require('./commands');
// const gbk = require('./gbk');
// const printerUtil = require('./printerutil');
import commands from './commands';
import gbk from './gbk';
import printerUtil from './printerutil';
const printerJobs = function() {
this._queue = Array.from(commands.HARDWARE.HW_INIT);
this._enqueue = function(cmd) {
this._queue.push.apply(this._queue, cmd);
}
};
/**
* 增加打印内容
* @param {string} content 文字内容
*/
printerJobs.prototype.text = function(content) {
if (content) {
let uint8Array = gbk.encode(content);
let encoded = Array.from(uint8Array);
this._enqueue(encoded);
}
return this;
};
/**
* 打印文字
* @param {string} content 文字内容
*/
printerJobs.prototype.print = function(content) {
this.text(content);
this._enqueue(commands.LF);
return this;
};
printerJobs.prototype.printArray = function(array) {
array.forEach(v => {
if (v.number) {
this.text(printerUtil.inline(v.title + '*' + v.number, `${v.price}`))
} else {
this.text(printerUtil.inline(v.title, `${v.price}`))
}
this._enqueue(commands.LF);
})
return this;
};
printerJobs.prototype.printQrcode = function(content) {
if (content) {
const cmds = [].concat([27, 97, 1], [29, 118, 48, 0, 30, 0, 240, 0], content, [27, 74, 3], [27, 64]);
this._enqueue(cmds);
this._enqueue(commands.LF);
}
return this;
};
printerJobs.prototype.printImage = function(content) {
if (content) {
const cmds = [].concat([27, 97, 1], [29, 118, 48, 0, 30, 0, 240, 0], content, [27, 74, 3], [27, 64]);
this._enqueue(cmds);
this._enqueue(commands.LF);
}
return this;
}
printerJobs.prototype.printQrcodeByESC = function(content) {
let arrPrint = [];
let uint8Array = gbk.encode(content);
let encoded = Array.from(uint8Array);
//初始化打印机
arrPrint= arrPrint.concat([0x1B, 0x40]); //16进制
//居中对齐
arrPrint=arrPrint.concat([0x1B, 0x61, 0x01]); //16进制
//正文
/*
* QR Code 设置单元大小 格式 ASCII GS ( k pL pH 1 C n 十六进制 1D 28 6B 03
* 00 31 43 n 十进制 29 40 107 03 0 49 67 n 功能设置QR CODE 单元大小
* 说明·n 对应QR版本号 决定QR CODE的高度与宽度 · 1n 16(十六进制为0x01n 0x0f)
*/
//设置设置 QR Code 的单元大小为 n 点
arrPrint=arrPrint.concat([0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x43, 0x05]);
//设置错误纠正等级
arrPrint=arrPrint.concat([0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x45, 0x05]);
//传入数据的长度+3
arrPrint=arrPrint.concat([0x1d, 0x28, 0x6b, encoded.length+3, 0x00, 0x31, 0x50, 0x30]);
//二维码内容
arrPrint=arrPrint.concat(encoded);
//开始打印二维码
arrPrint=arrPrint.concat([0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x51, 0x30]);
//恢复居左对齐
arrPrint=arrPrint.concat([0x1B, 0x61, 0x00]); //16进制
this._enqueue(arrPrint);
this._enqueue(commands.LF);
return this;
};
/**
* 打印文字并换行
* @param {string} content 文字内容
*/
printerJobs.prototype.println = function(content = '') {
return this.print(content + commands.EOL);
};
/**
* 设置对齐方式
* @param {string} align 对齐方式 LT/CT/RT
*/
printerJobs.prototype.setAlign = function(align) {
this._enqueue(commands.TEXT_FORMAT['TXT_ALIGN_' + align.toUpperCase()]);
return this;
};
/**
* 设置字体
* @param {string} family A/B/C
*/
printerJobs.prototype.setFont = function(family) {
this._enqueue(commands.TEXT_FORMAT['TXT_FONT_' + family.toUpperCase()]);
return this;
};
/**
* 设定字体尺寸
* @param {number} width 字体宽度 1~2
* @param {number} height 字体高度 1~2
*/
printerJobs.prototype.setSize = function(width, height) {
if (2 >= width && 2 >= height) {
this._enqueue(commands.TEXT_FORMAT.TXT_NORMAL);
if (2 === width && 2 === height) {
this._enqueue(commands.TEXT_FORMAT.TXT_4SQUARE);
} else if (1 === width && 2 === height) {
this._enqueue(commands.TEXT_FORMAT.TXT_2HEIGHT);
} else if (2 === width && 1 === height) {
this._enqueue(commands.TEXT_FORMAT.TXT_2WIDTH);
}
}
return this;
};
/**
* 设定字体是否加粗
* @param {boolean} bold
*/
printerJobs.prototype.setBold = function(bold) {
if (typeof bold !== 'boolean') {
bold = true;
}
this._enqueue(bold ? commands.TEXT_FORMAT.TXT_BOLD_ON : commands.TEXT_FORMAT.TXT_BOLD_OFF);
return this;
};
/**
* 设定是否开启下划线
* @param {boolean} underline
*/
printerJobs.prototype.setUnderline = function(underline) {
if (typeof underline !== 'boolean') {
underline = true;
}
this._enqueue(underline ? commands.TEXT_FORMAT.TXT_UNDERL_ON : commands.TEXT_FORMAT.TXT_UNDERL_OFF);
return this;
};
/**
* 设置行间距为 n 点行,默认值行间距是 30
* @param {number} n 0n255
*/
printerJobs.prototype.setLineSpacing = function(n) {
if (n === undefined || n === null) {
this._enqueue(commands.LINE_SPACING.LS_DEFAULT);
} else {
this._enqueue(commands.LINE_SPACING.LS_SET);
this._enqueue([n]);
}
return this;
};
/**
* 打印空行
* @param {number} n
*/
printerJobs.prototype.lineFeed = function(n = 1) {
return this.print(new Array(n).fill(commands.EOL).join(''));
};
/**
* 设置字体颜色需要打印机支持
* @param {number} color - 0 默认颜色黑色 1 红色
*/
printerJobs.prototype.setColor = function(color) {
this._enqueue(commands.COLOR[color === 1 ? 1 : 0]);
return this;
};
/**
* https://support.loyverse.com/hardware/printers/use-the-beeper-in-a-escpos-printers
* 蜂鸣警报需要打印机支持
* @param {number} n 蜂鸣次数,1-9
* @param {number} t 蜂鸣长短,1-9
*/
printerJobs.prototype.beep = function(n, t) {
this._enqueue(commands.BEEP);
this._enqueue([n, t]);
return this;
};
/**
* 清空任务
*/
printerJobs.prototype.clear = function() {
this._queue = Array.from(commands.HARDWARE.HW_INIT);
return this;
};
/**
* 返回ArrayBuffer
*/
printerJobs.prototype.buffer = function() {
return new Uint8Array(this._queue).buffer;
};
export default printerJobs;

@ -0,0 +1,91 @@
// 打印机纸宽58mm页的宽度384字符宽度为1每行最多盛放32个字符
const PAGE_WIDTH = 384;
const MAX_CHAR_COUNT_EACH_LINE = 32;
/**
* @param str
* @returns {boolean} str是否全是中文
*/
function isChinese(str) {
return /^[\u4e00-\u9fa5]$/.test(str);
}
/**
* 返回字符串宽度(1个中文=2个英文字符)
* @param str
* @returns {number}
*/
function getStringWidth(str) {
let width = 0;
for (let i = 0, len = str.length; i < len; i++) {
width += isChinese(str.charAt(i)) ? 2 : 1;
}
return width;
}
/**
* 同一行输出str1, str2str1居左, str2居右
* @param {string} str1 内容1
* @param {string} str2 内容2
* @param {number} fontWidth 字符宽度 1/2
* @param {string} fillWith str1 str2之间的填充字符
*
*/
function inline(str1, str2, fillWith = ' ', fontWidth = 1) {
const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
// 需要填充的字符数量
let fillCount = lineWidth - (getStringWidth(str1) + getStringWidth(str2) + 2) % lineWidth;
let fillStr = new Array(fillCount).fill(fillWith.charAt(0)).join('');
return str1 + fillStr + str2;
}
/**
* 用字符填充一整行
* @param {string} fillWith 填充字符
* @param {number} fontWidth 字符宽度 1/2
*/
function fillLine(fillWith = '-', fontWidth = 1) {
const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
return new Array(lineWidth).fill(fillWith.charAt(0)).join('');
}
/**
* 文字内容居中左右用字符填充
* @param {string} str 文字内容
* @param {number} fontWidth 字符宽度 1/2
* @param {string} fillWith str1 str2之间的填充字符
*/
function fillAround(str, fillWith = '-', fontWidth = 1) {
const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
let strWidth = getStringWidth(str);
// 内容已经超过一行了,没必要填充
if (strWidth >= lineWidth) {
return str;
}
// 需要填充的字符数量
let fillCount = lineWidth - strWidth;
// 左侧填充的字符数量
let leftCount = Math.round(fillCount / 2);
// 两侧的填充字符,需要考虑左边需要填充,右边不需要填充的情况
let fillStr = new Array(leftCount).fill(fillWith.charAt(0)).join('');
return fillStr + str + fillStr.substr(0, fillCount - leftCount);
}
// ArrayBuffer转16进度字符串示例
function ab2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join(',')
}
export default {
inline: inline,
fillLine: fillLine,
fillAround: fillAround,
ab2hex:ab2hex,
};

@ -0,0 +1,66 @@
const formatTime = date => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}
const formatNumber = n => {
n = n.toString()
return n[1] ? n : '0' + n
}
//4合1
function convert4to1(res) {
let arr = [];
for (let i = 0; i < res.length; i++) {
if (i % 4 == 0) {
let rule = 0.29900 * res[i] + 0.58700 * res[i + 1] + 0.11400 * res[i + 2];
if (rule > 200) {
res[i] = 0;
} else {
res[i] = 1;
}
arr.push(res[i]);
}
}
return arr;
}
//8合1
function convert8to1(arr) {
let data = [];
for (let k = 0; k < arr.length; k += 8) {
let temp = arr[k] * 128 + arr[k + 1] * 64 + arr[k + 2] * 32 + arr[k + 3] * 16 + arr[k + 4] * 8 + arr[k + 5] * 4 +
arr[k + 6] * 2 + arr[k + 7] * 1
data.push(temp);
}
return data;
}
//我的图片宽度是240那么拼接的指令就是[29, 118, 48, 0, 30, 0, 240, 0]
//我的图片宽度是160那么拼接的指令就是[29, 118, 48, 0, 20, 0, 160, 0]
//补充一点打印非二维码的图片宽度一定要是24的倍数不然打印也会出现乱码
function toArrayBuffer(res) {
let arr = convert4to1(res.data);
let data = convert8to1(arr);
let cmds = [].concat([27, 97, 1], [29, 118, 48, 0, 30, 0, 240, 0], data, [27, 74, 3], [27, 64]);
return new Uint8Array(cmds).buffer;
}
function zip_image(res) {
let arr = convert4to1(res.data);
let data = convert8to1(arr);
return data;
}
export default {
formatTime: formatTime,
toArrayBuffer: toArrayBuffer,
zip_image: zip_image,
}

File diff suppressed because one or more lines are too long

Binary file not shown.

@ -0,0 +1,276 @@
<template>
<view>
<button @click="discoveryPrinter"></button>
<button @click="stopDiscoveryPrinter"></button>
<view>选中的设备{{deviceId}}</view>
<view class="text-red">设备列表</view>
<radio-group @change="radioChange">
<label class="uni-list-cell uni-list-cell-pd" v-for="(item, index) in devices" :key="index">
<view>
<radio :value="item.deviceId" />{{item.name}}({{item.deviceId}})
</view>
</label>
</radio-group>
<view class="text-red">设备服务列表</view>
<radio-group @change="radioChange2">
<label class="uni-list-cell uni-list-cell-pd" v-for="(item, index) in serverList" :key="index">
<view>
<radio :value="item.uuid" />{{item.uuid}}
</view>
</label>
</radio-group>
<view class="text-red">特征值</view>
<radio-group @change="radioChange3">
<label class="uni-list-cell uni-list-cell-pd" v-for="(item, index) in characteristics" :key="index">
<view>
<radio :value="item.uuid" />{{item.uuid}}(write:{{item.properties.write}} notify:{{item.properties.notify}} indicate:{{item.properties.indicate}})
</view>
</label>
</radio-group>
<button @click="writeBLECharacteristicValue"></button>
</view>
</template>
<script>
import PrinterJobs from './print/printerjobs.js'
import printerUtil from './print/printerutil.js'
export default {
name: 'xun_bluetoothPrint',
data () {
return {
devices: [],
deviceId: '',
serverList: [],
serviceId: '',
characteristics: [],
characteristicId: ''
}
},
mounted () {
//
this.openBluetoothAdapter()
},
methods: {
openBluetoothAdapter () {
var _this = this
uni.openBluetoothAdapter({
complete (e) {
console.log(e);
if (!e.errCode) {
console.log('初始化完成')
} else if (e.errCode == 10001) {
uni.showToast({
icon: 'none',
title: '请打开手机蓝牙'
})
} else {
uni.showToast({
icon: 'none',
title: e.errMsg
})
}
}
})
},
//
discoveryPrinter () {
var _this = this
_this.devices = []
uni.startBluetoothDevicesDiscovery({
complete (e) {
console.log(e)
if (e.errMsg == "startBluetoothDevicesDiscovery:ok") {
// ArrayBuffer16
uni.onBluetoothDeviceFound(devices => {
console.log(devices)
_this.devices.push(devices.devices[0])
})
}
}
})
},
//
stopDiscoveryPrinter () {
uni.stopBluetoothDevicesDiscovery()
},
radioChange (e) {
console.log(e)
this.deviceId = e.target.value
this.serviceId = ''
this.serverList = []
this.characteristics = []
this.characteristicId = ''
//
this.connect()
},
radioChange2 (e) {
console.log(e)
this.serviceId = e.target.value
this.characteristics = []
this.characteristicId = ''
//
this.getBLEDeviceCharacteristics()
},
radioChange3 (e) {
console.log(e)
this.characteristicId = e.target.value
},
connect () {
var _this = this
uni.createBLEConnection({
deviceId: _this.deviceId,
complete (e) {
if (!e.errCode) {
//(service)
_this.getBLEDeviceServices()
} else {
uni.showToast({
icon: 'none',
title: '连接设备失败'
})
}
}
})
},
getBLEDeviceServices(){
var _this = this
uni.getBLEDeviceServices({
// deviceId createBLEConnection
deviceId: _this.deviceId,
success:(res)=>{
console.log('device services:', res)
_this.serverList = res.services
// _this.serviceId = res.services[0].uuid;
console.log('serverId:', _this.serviceId)
}
})
},
getBLEDeviceCharacteristics () {
var _this = this
uni.getBLEDeviceCharacteristics({
// deviceId createBLEConnection
deviceId: _this.deviceId,
// serviceId getBLEDeviceServices
serviceId:_this.serviceId,
success:(res)=>{
console.log('getBLEDeviceCharacteristics', res)
_this.characteristics = res.characteristics
// _this.characteristicId = res.characteristics[0].uuid
},
fail:(res)=>{
console.log(res)
}
})
},
//
writeBLECharacteristicValue(){
if (!this.deviceId) {
uni.showToast({
icon: 'none',
title: '请选择设备'
})
return
}
if (!this.serviceId) {
uni.showToast({
icon: 'none',
title: '请选择设备服务'
})
return
}
if (!this.characteristicId) {
uni.showToast({
icon: 'none',
title: '请选择特征值'
})
return
}
let printerJobs = new PrinterJobs()
var arr = [{
title: '阿根廷车厘子',
number: 20,
price: '¥188.00'
}, {
title: '新鲜山竹',
number: 38,
price: '¥199.00'
}]
var arr2 = [{
title: '平台消暑费',
price: '9.99'
},{
title: '打包费',
price: '2.00',
}]
printerJobs
.print(`下单时间2021-07-25 15:30:23`)
.print(printerUtil.fillLine())
.print('备注')
.setSize(2, 2)
.setBold(true)
.print(`叫那位非常漂亮的小姐姐送来,其他人送来拒收`)
.setSize(1, 1)
.setBold(false)
.print(printerUtil.fillLine('*'))
.setAlign('lt')
.printArray(arr)
.print(printerUtil.fillAround('其它'))
.print(printerUtil.inline('平台随机立减', `-2.10`))
.print(printerUtil.inline('平台服务费', `-10.99`))
.printArray(arr2)
.print(printerUtil.fillLine())
.setAlign('rt')
.setSize(1, 2)
.setBold(true)
.print(`用户支付¥99.99`)
.print(printerUtil.fillLine())
let buffer = printerJobs.buffer();
console.log('buffer>>>',buffer)
this.printbuffs(buffer)
},
printbuffs(buffer) {
// 1.
// 2.20
//
const maxChunk = 20;
const delay = 20;
for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) {
let subPackage = buffer.slice(i, i + maxChunk <= length ? (i + maxChunk) : length);
setTimeout(this.printbuff, j * delay, subPackage);
}
},
printbuff(buffer) {
var _this = this
uni.writeBLECharacteristicValue({
// deviceId getBluetoothDevices onBluetoothDeviceFound
deviceId: _this.deviceId,
// serviceId getBLEDeviceServices
serviceId: _this.serviceId,
// characteristicId getBLEDeviceCharacteristics
characteristicId: _this.characteristicId,
// valueArrayBuffer
value: buffer,
success:(res)=> {
console.log('writeBLECharacteristicValue success', res.errMsg)
},
fail:(res)=> {
console.log('writeBLECharacteristicValue fail', res.errMsg)
},
complete (e) {
console.log('writeBLECharacteristicValue complete', e)
}
})
}
}
}
</script>
<style scoped>
* { font-size: 24rpx;}
button { font-size: 28rpx;}
.text-red { color: red; font-size: 28rpx;}
</style>

@ -0,0 +1,265 @@
class Bluetooth {
constructor() {
this.isOpenBle = false;
this.deviceId = "";
this.serviceId = "";
this.writeId = "";
this.notifyId = "";
this.openBluetoothAdapter();
}
showToast(title) {
uni.showToast({
title: title,
icon: 'none',
'duration': 2000
});
}
openBluetoothAdapter() {
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: res => {
this.isOpenBle = true;
this.showToast("初始化蓝牙模块成功");
resolve(res);
},
fail: err => {
this.showToast(`初始化蓝牙模块失败` + JSON.stringify(err));
reject(err);
},
});
});
}
startBluetoothDevicesDiscovery() {
if (!this.isOpenBle) {
this.showToast(`初始化蓝牙模块失败`)
return;
}
let self = this;
uni.showLoading({
title: '蓝牙搜索中'
})
return new Promise((resolve, reject) => {
setTimeout(() => {
uni.startBluetoothDevicesDiscovery({
success: res => {
resolve(res)
},
fail: res => {
self.showToast(`搜索设备失败` + JSON.stringify(err));
reject(err);
}
})
}, 300);
});
}
stopBluetoothDevicesDiscovery() {
let self = this;
return new Promise((resolve, reject) => {
uni.stopBluetoothDevicesDiscovery({
success: e => {
uni.hideLoading();
},
fail: e => {
uni.hideLoading();
self.showToast(`停止搜索蓝牙设备失败` + JSON.stringify(err));
}
})
});
}
createBLEConnection() {
//设备deviceId
let deviceId = this.deviceId;
let self = this;
uni.showLoading({
mask: true,
title: '设别连接中,请稍候...'
})
console.log(this.deviceId);
return new Promise((resolve, reject) => {
uni.createBLEConnection({
deviceId,
success: (res) => {
console.log("res:createBLEConnection " + JSON.stringify(res));
resolve(res)
},
fail: err => {
uni.hideLoading();
self.showToast(`停止搜索蓝牙设备失败` + JSON.stringify(err));
reject(err);
}
})
});
}
//获取蓝牙设备所有服务(service)
getBLEDeviceServices() {
let _serviceList = [];
let deviceId = this.deviceId;
let self = this;
return new Promise((resolve, reject) => {
setTimeout(() => {
uni.getBLEDeviceServices({
deviceId,
success: res => {
for (let service of res.services) {
if (service.isPrimary) {
_serviceList.push(service);
}
}
uni.hideLoading();
console.log("_serviceList: " + JSON.stringify(_serviceList));
resolve(_serviceList)
},
fail: err => {
uni.hideLoading();
self.showToast(`获取设备Services` + JSON.stringify(err));
reject(err);
},
})
}, 500);
});
}
//获取蓝牙设备某个服务中所有特征值(characteristic)
getBLEDeviceCharacteristics() {
let deviceId = this.deviceId;
let serviceId = this.serviceId;
let self = this;
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: res => {
for (let _obj of res.characteristics) {
//获取notify
if (_obj.properties.notify) {
self.notifyId = _obj.uuid;
uni.setStorageSync('notifyId', self.notifyId);
}
//获取writeId
if (_obj.properties.write) {
self.writeId = _obj.uuid;
uni.setStorageSync('writeId', self.writeId);
}
}
//console.log("res:getBLEDeviceCharacteristics " + JSON.stringify(res));
let result = {
'notifyId': self.notifyId,
'writeId': self.writeId
};
self.showToast(`获取服务中所有特征值OK,${JSON.stringify(result)}`);
resolve(result)
},
fail: err => {
self.showToast(`getBLEDeviceCharacteristics` + JSON.stringify(err));
reject(err);
}
})
});
}
//断开联链接
closeBLEConnection() {
let deviceId = this.deviceId;
uni.closeBLEConnection({
deviceId,
success(res) {
console.log(res)
}
})
}
notifyBLECharacteristicValue() {
let deviceId = this.deviceId;
let serviceId = this.serviceId;
let characteristicId = this.notifyId;
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
deviceId,
serviceId,
characteristicId,
success(res) {
uni.onBLECharacteristicValueChange(function(res) {
});
},
fail(res) {
console.log('notifyBLECharacteristicValueChange failed:' + res.errMsg);
}
});
}
writeBLECharacteristicValue(buffer) {
let deviceId = this.deviceId;
let serviceId = this.serviceId;
let characteristicId = this.writeId;
console.log("this: " + JSON.stringify(this));
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value: buffer,
success(res) {
console.log('message发送成功', JSON.stringify(res));
resolve(res);
},
fail(err) {
console.log('message发送失败', JSON.stringify(err));
reject(err);
}
});
});
}
closeBluetoothAdapter() {
uni.closeBluetoothAdapter({
success: res => {
console.log(res)
}
});
}
//若APP在之前已有搜索过某个蓝牙设备并成功建立连接可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需进行搜索操作。
reconnect() {
(async () => {
try {
this.deviceId = this.deviceId || uni.getStorageSync("deviceId");
this.serviceId = this.serviceId || uni.getStorageSync("serviceId");
let result1 = await this.createBLEConnection();
console.log("createBLEConnection: " + JSON.stringify(result1));
let result2 = await this.getBLEDeviceServices();
console.log("getBLEDeviceServices: " + JSON.stringify(result2));
let result3 = await this.getBLEDeviceCharacteristics();
console.log("getBLEDeviceCharacteristics: " + JSON.stringify(result3));
// this.writeId = uni.getStorageSync("writeId");
// this.notifyId = uni.getStorageSync("notifyId");
} catch (err) {
console.log("err: " + JSON.stringify(err));
}
})();
}
}
export default Bluetooth;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save