1
0
mirror of https://github.com/PanJiaChen/vue-element-admin.git synced 2025-08-07 18:25:45 +08:00

Mobile Point of Sale Support (#893)

* Mobile Point of Sale Support

* Correcting observations

Co-authored-by: elsiosanchez <elsiossanches@gmail.com>
This commit is contained in:
Elsio Sanchez 2021-06-01 09:21:38 -04:00 committed by GitHub
parent f8b6eadf09
commit 21154c82ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 635 additions and 130 deletions

View File

@ -28,7 +28,7 @@
>
<el-form label-position="top" label-width="10px" @submit.native.prevent="notSubmitForm">
<el-row :gutter="24" style="display: flex;">
<el-col :span="isEmptyValue(currentOrder) ? 14 : 11 " style="padding-left: 0px; padding-right: 0px;">
<el-col :span="colFieldProductCode" style="padding-left: 0px; padding-right: 0px;">
<template
v-for="(field) in fieldsList"
>
@ -39,7 +39,7 @@
/>
</template>
</el-col>
<el-col :span="isEmptyValue(currentOrder) ? 9 : 7" style="padding-left: 0px; padding-right: 0px;">
<el-col :span="colFieldBusinessPartner" style="padding-left: 0px; padding-right: 0px;">
<business-partner
:parent-metadata="{
name: panelMetadata.name,
@ -50,7 +50,7 @@
:is-disabled="isDisabled"
/>
</el-col>
<el-col :span="isEmptyValue(currentOrder) ? 1 : 4" :style="isShowedPOSKeyLayout ? 'padding: 0px;' : 'padding: 0px;margin-top: 2.9%;'">
<el-col v-if="!isMobile" :span="isEmptyValue(currentOrder) ? 1 : 4" :style="isShowedPOSKeyLayout ? 'padding: 0px;' : 'padding: 0px;margin-top: 2.9%;'">
<el-form-item>
<el-row :gutter="24">
<el-col :span="10" style="padding-left: 0px; padding-right: 0px;">
@ -82,9 +82,9 @@
v-shortkey="shortsKey"
:data="listOrderLine"
border
style="width: 100%; max-width: 100%; background-color: #FFFFFF; font-size: 14px; overflow: auto; color: #606266;"
highlight-current-row
fit
style="overflow: auto;"
@current-change="handleCurrentLineChange"
@shortkey.native="shortcutKeyMethod"
>
@ -170,7 +170,7 @@
</el-table>
</el-main>
<el-footer class="footer-table">
<el-footer :class="classOrderFooter">
<div class="keypad">
<el-button type="primary" icon="el-icon-top" :disabled="isDisabled" @click="arrowTop" />
<el-button type="primary" icon="el-icon-bottom" :disabled="isDisabled" @click="arrowBottom" />
@ -207,6 +207,27 @@
</el-dropdown>
</p>
</div>
<span v-if="isMobile" style="float: right;padding-right: 3%;">
<p class="total">{{ $t('form.pos.order.order') }}: <b class="order-info">{{ currentOrder.documentNo }}</b></p>
<p class="total">
{{ $t('form.pos.order.date') }}:
<b class="order-info">
{{ orderDate }}
</b>
</p>
<p class="total">{{ $t('form.pos.order.type') }}:<b class="order-info">{{ currentOrder.documentType.name }}</b></p>
<p class="total">
{{ $t('form.pos.order.itemQuantity') }}
<b class="order-info">
{{ getItemQuantity }}
</b>
</p>
<p class="total">
{{ $t('form.pos.order.numberLines') }}
<b class="order-info">
{{ numberOfLines }}
</b></p>
</span>
<span style="float: right;">
<p class="total">{{ $t('form.pos.order.seller') }}:<b style="float: right;">
{{ currentOrder.salesRepresentative.name }}
@ -237,7 +258,7 @@
</b>
</p>
</span>
<span style="float: right;padding-right: 3%;">
<span v-if="!isMobile" style="float: right;padding-right: 3%;">
<p class="total">{{ $t('form.pos.order.order') }}: <b class="order-info">{{ currentOrder.documentNo }}</b></p>
<p class="total">
{{ $t('form.pos.order.date') }}:
@ -262,7 +283,15 @@
</el-container>
</el-main>
</el-container>
<div style="position: relative;padding-top: 30vh; z-index: 100;">
<div v-if="isMobile && isShowedPOSKeyLayout" :style="classButtomRight">
<el-button
:circle="true"
type="primary"
:icon="isShowedPOSKeyLayout ? 'el-icon-arrow-left' : 'el-icon-arrow-right'"
@click="isShowedPOSKeyLayout = !isShowedPOSKeyLayout"
/>
</div>
<div v-if="!isMobile" :style="classButtomRight">
<el-button
:circle="true"
type="primary"
@ -317,6 +346,42 @@ export default {
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
classOrderFooter() {
if (this.isMobile) {
return 'footer-mobile'
}
return 'footer-table'
},
classButtomRight() {
if (this.isMobile) {
if (this.$store.getters.getIsShowPOSOptions) {
return 'position: fixed'
}
return 'position: absolute;top: 34%;z-index: 100;right: 0;'
}
return 'position: relative;padding-top: 30vh; z-index: 100;'
},
colFieldBusinessPartner() {
if (this.isMobile) {
return 12
}
if (this.isEmptyValue(this.currentOrder)) {
return 14
}
return 11
},
colFieldProductCode() {
if (this.isMobile) {
return 12
}
if (this.isEmptyValue(this.currentOrder)) {
return 9
}
return 7
},
shortsKey() {
return {
popoverConvet: ['ctrl', 'x']
@ -452,7 +517,7 @@ export default {
formatPrice,
formatQuantity,
openCollectionPanel() {
this.isShowedPOSKeyLayout = true
this.isShowedPOSKeyLayout = this.isMobile ? !this.isShowedPOSKeyLayout : true
this.$store.commit('setShowPOSCollection', true)
const orderUuid = this.$route.query.action
this.$store.dispatch('listPayments', { orderUuid })
@ -560,6 +625,12 @@ export default {
padding-right: 0px !important;
padding-left: 0px !important;
}
.footer-mobile {
padding: 0px;
height: auto !important;
overflow: auto;
display: contents;
}
.footer-table {
padding-top: 0px;
padding-right: 9px;

View File

@ -1,7 +1,7 @@
<!--
ADempiere-Vue (Frontend) for ADempiere ERP & CRM Smart Business Solution
Copyright (C) 2017-Present E.R.P. Consultores y Asociados, C.A.
Contributor(s): Yamel Senih ysenih@erpya.com www.erpya.com
Contributor(s): Elsio Sanchez elsiosanches@gmail.com www.erpya.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
@ -16,141 +16,32 @@
along with this program. If not, see <https:www.gnu.org/licenses/>.
-->
<template>
<el-container style="height: 100% !important;">
<el-main style="padding-right: 0px;padding-bottom: 0px;">
<Split :gutter-size="isShowedPOSOptions ? 10 : 0" @onDrag="onDragOption">
<SplitArea :size="isShowedPOSOptions ? 20 : 1" :min-size="400">
<el-container style="height: 100% !important;">
<el-aside :width="isShowedPOSOptions ? '100%' : '0%'" style="background: white; padding: 0px !important; margin-bottom: 0px">
<options
:metadata="metadata"
/>
</el-aside>
<div style="width: 36px;padding-top: 30vh; z-index: 100;">
<el-button
:circle="true"
type="primary"
:icon="isShowedPOSOptions ? 'el-icon-arrow-left' : 'el-icon-arrow-right'"
:style="isShowedPOSOptions ? 'position: absolute;': 'position: absolute;left: 0.8%;'"
@click="isShowedPOSOptions = !isShowedPOSOptions"
/>
</div>
</el-container>
</SplitArea>
<SplitArea :size="isShowedPOSOptions ? 80 : 99" :min-size="990">
<Split :gutter-size="isShowedPOSKeyLaout ? 10 : 0" @onDrag="onDragKeyLayout">
<SplitArea :size="isShowedPOSKeyLaout ? 69 : 99" :min-size="900" style="overflow: hidden">
<order
:metadata="metadata"
/>
</SplitArea>
<SplitArea
v-show="isShowedPOSKeyLaout"
:size="isShowedPOSKeyLaout ? 31: 1"
:min-size="300"
style="overflow: auto"
>
<key-layout
v-if="!showCollection"
key="layout-component"
/>
<collection
v-else
key="collection-component"
/>
</SplitArea>
</Split>
</SplitArea>
</Split>
</el-main>
</el-container>
<component
:is="templateDevice"
:metadata="metadata"
/>
</template>
<script>
import Order from '@/components/ADempiere/Form/VPOS/Order'
import KeyLayout from '@/components/ADempiere/Form/VPOS/KeyLayout'
import Options from '@/components/ADempiere/Form/VPOS/Options'
import Collection from '@/components/ADempiere/Form/VPOS/Collection'
export default {
name: 'VPOS',
components: {
Order,
KeyLayout,
Options,
Collection
},
props: {
metadata: {
type: Object,
required: true
}
},
data() {
return {
unsubscribePOSList: () => {}
}
},
computed: {
// options to POS, panel left
isShowedPOSOptions: {
get() {
return this.$store.getters.getIsShowPOSOptions
},
set(val) {
this.$store.commit('setShowPOSOptions', val)
// is Movile
isMobile() {
return this.$store.state.app.device === 'mobile'
},
templateDevice() {
if (this.isMobile) {
return () => import('@/components/ADempiere/Form/VPOS/templateDevice/mobile')
}
},
isShowedPOSKeyLaout() {
return this.$store.getters.getShowPOSKeyLayout
},
showCollection() {
return this.$store.getters.getShowCollectionPos
},
listPointOfSales() {
return this.$store.getters.posAttributes.listPointOfSales
}
},
watch: {
isShowedPOSOptions(value) {
if (value) {
if (this.isShowedPOSKeyLaout) {
this.$store.dispatch('changeWidthRight', 3)
}
} else {
this.$store.dispatch('changeWidthRight', 3)
}
}
},
created() {
// load pont of sales list
if (this.isEmptyValue(this.listPointOfSales)) {
// set pos id with query path
this.$store.dispatch('listPointOfSalesFromServer', this.$route.query.pos)
}
this.unsubscribePOSList = this.posListWithOrganization()
},
beforeDestroy() {
this.unsubscribePOSList()
},
methods: {
posListWithOrganization() {
return this.$store.subscribe((mutation, state) => {
if (mutation.type === 'user/SET_ORGANIZATION') {
this.$store.dispatch('listPointOfSalesFromServer')
}
})
},
onDragKeyLayout(size) {
const sizeWidthRight = size[1] / 10
this.$store.dispatch('changeWidthRight', Math.trunc(sizeWidthRight))
},
onDragOption(size) {
const sizeWidthLeft = size[0] / 10
this.$store.dispatch('changeWidthLeft', Math.trunc(sizeWidthLeft))
return () => import('@/components/ADempiere/Form/VPOS/templateDevice/desktop')
}
}
}

View File

@ -0,0 +1,264 @@
<!--
ADempiere-Vue (Frontend) for ADempiere ERP & CRM Smart Business Solution
Copyright (C) 2017-Present E.R.P. Consultores y Asociados, C.A.
Contributor(s): Yamel Senih ysenih@erpya.com www.erpya.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https:www.gnu.org/licenses/>.
-->
<template>
<el-container style="height: 100% !important;">
<el-main style="padding-right: 0px;padding-bottom: 0px;">
<Split :gutter-size="isShowedPOSOptions ? 10 : 0" @onDrag="onDragOption">
<SplitArea :size="isShowedPOSOptions ? 20 : 1" :min-size="400">
<el-container style="height: 100% !important;">
<el-aside :width="isShowedPOSOptions ? '100%' : '0%'" style="background: white; padding: 0px !important; margin-bottom: 0px">
<options
:metadata="metadata"
/>
</el-aside>
<div style="width: 36px;padding-top: 30vh; z-index: 100;">
<el-button
:circle="true"
type="primary"
:icon="isShowedPOSOptions ? 'el-icon-arrow-left' : 'el-icon-arrow-right'"
:style="isShowedPOSOptions ? 'position: absolute;': 'position: absolute;left: 0.8%;'"
@click="isShowedPOSOptions = !isShowedPOSOptions"
/>
</div>
</el-container>
</SplitArea>
<SplitArea :size="isShowedPOSOptions ? 80 : 99" :min-size="990">
<Split :gutter-size="isShowedPOSKeyLaout ? 10 : 0" @onDrag="onDragKeyLayout">
<SplitArea :size="isShowedPOSKeyLaout ? 69 : 99" :min-size="900" style="overflow: hidden">
<order
:metadata="metadata"
/>
</SplitArea>
<SplitArea
v-show="isShowedPOSKeyLaout"
:size="isShowedPOSKeyLaout ? 31: 1"
:min-size="300"
style="overflow: auto"
>
<key-layout
v-if="!showCollection"
key="layout-component"
/>
<collection
v-else
key="collection-component"
/>
</SplitArea>
</Split>
</SplitArea>
</Split>
</el-main>
</el-container>
</template>
<script>
import Order from '@/components/ADempiere/Form/VPOS/Order'
import KeyLayout from '@/components/ADempiere/Form/VPOS/KeyLayout'
import Options from '@/components/ADempiere/Form/VPOS/Options'
import Collection from '@/components/ADempiere/Form/VPOS/Collection'
export default {
name: 'VposDesktop',
components: {
Order,
KeyLayout,
Options,
Collection
},
props: {
metadata: {
type: Object,
required: true
}
},
data() {
return {
unsubscribePOSList: () => {}
}
},
computed: {
// options to POS, panel left
isShowedPOSOptions: {
get() {
return this.$store.getters.getIsShowPOSOptions
},
set(val) {
this.$store.commit('setShowPOSOptions', val)
}
},
isShowedPOSKeyLaout() {
return this.$store.getters.getShowPOSKeyLayout
},
showCollection() {
return this.$store.getters.getShowCollectionPos
},
listPointOfSales() {
return this.$store.getters.posAttributes.listPointOfSales
}
},
watch: {
isShowedPOSOptions(value) {
if (value) {
if (this.isShowedPOSKeyLaout) {
this.$store.dispatch('changeWidthRight', 3)
}
} else {
this.$store.dispatch('changeWidthRight', 3)
}
}
},
created() {
// load pont of sales list
if (this.isEmptyValue(this.listPointOfSales)) {
// set pos id with query path
this.$store.dispatch('listPointOfSalesFromServer', this.$route.query.pos)
}
this.unsubscribePOSList = this.posListWithOrganization()
},
beforeDestroy() {
this.unsubscribePOSList()
},
methods: {
posListWithOrganization() {
return this.$store.subscribe((mutation, state) => {
if (mutation.type === 'user/SET_ORGANIZATION') {
this.$store.dispatch('listPointOfSalesFromServer')
}
})
},
onDragKeyLayout(size) {
const sizeWidthRight = size[1] / 10
this.$store.dispatch('changeWidthRight', Math.trunc(sizeWidthRight))
},
onDragOption(size) {
const sizeWidthLeft = size[0] / 10
this.$store.dispatch('changeWidthLeft', Math.trunc(sizeWidthLeft))
}
}
}
</script>
<style scoped>
.split {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow-y: hidden;
overflow-x: hidden;
height: 100%;
width: 100%;
}
.el-card__body {
padding-top: 0px !important;
padding-right: 0px!important;
padding-bottom: 20px;
padding-left: 10px!important;
height: 100%!important;
}
/* Style of Table */
.el-table {
position: relative;
overflow: hidden;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
width: 100%;
max-width: 100%;
height: 100%;
background-color: #FFFFFF;
font-size: 14px;
color: #606266;
}
.el-card__header {
padding: 0px 20px;
border-bottom: 1px solid #e6ebf5;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.time {
font-size: 13px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
width: 100%;
display: block;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.el-header {
background: 'white';
color: #333;
line-height: 10px;
}
.el-aside {
color: #333;
}
.el-row {
margin: 0px!important;
}
.el-col {
border-radius: 4px;
}
.bg-purple-dark {
background: #99a9bf;
}
.bg-purple {
background: #d3dce6;
}
.bg-purple-light {
background: #e5e9f2;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.row-bg {
padding: 10px 0;
background-color: #f9fafc;
}
.order-header {
padding-left: 10px;
font-size: 13px;
}
</style>

View File

@ -0,0 +1,279 @@
<!--
ADempiere-Vue (Frontend) for ADempiere ERP & CRM Smart Business Solution
Copyright (C) 2017-Present E.R.P. Consultores y Asociados, C.A.
Contributor(s): Yamel Senih ysenih@erpya.com www.erpya.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https:www.gnu.org/licenses/>.
-->
<template>
<el-container style="height: 100% !important;">
<el-main style="padding-right: 0px;padding-bottom: 0px;display: contents;">
<Split :gutter-size="isShowedPOSOptions ? 10 : 0" @onDrag="onDragOption">
<SplitArea :size="isShowedPOSOptions ? 90 : 1">
<el-container style="height: 100% !important;">
<el-aside :width="isShowedPOSOptions ? '100%' : '0%'" style="background: white; padding: 0px !important; margin-bottom: 0px">
<options
:metadata="metadata"
/>
</el-aside>
<div style="width: 36px;padding-top: 30vh; z-index: 100;">
<el-button
v-if="isShowedPOSKeyLayout"
:circle="true"
type="primary"
:icon="isShowedPOSOptions ? 'el-icon-arrow-left' : 'el-icon-arrow-right'"
:style="isShowedPOSOptions ? 'position: absolute;': 'position: absolute;left: 0.8%;'"
@click="isShowedPOSOptions = !isShowedPOSOptions"
/>
<el-button
v-else
:circle="true"
type="primary"
:icon="isShowedPOSKeyLayout ? 'el-icon-arrow-left' : 'el-icon-arrow-right'"
:style="isShowedPOSKeyLayout ? 'position: absolute;': 'position: absolute;left: 0.8%;'"
@click="isShowedPOSKeyLayout = !isShowedPOSKeyLayout"
/>
</div>
</el-container>
</SplitArea>
<SplitArea :size="isShowedPOSOptions ? 80 : 99" :min-size="990">
<Split :gutter-size="isShowedPOSKeyLaout ? 10 : 0" @onDrag="onDragKeyLayout">
<SplitArea :size="isShowedPOSKeyLaout ? 99 : 1" style="overflow: hidden">
<order
:metadata="metadata"
/>
</SplitArea>
<SplitArea
:size="isShowedPOSKeyLaout ? 1: 99"
:min-size="300"
style="overflow: auto"
>
<key-layout
v-if="!showCollection"
key="layout-component"
/>
<collection
v-else
key="collection-component"
/>
</SplitArea>
</Split>
</SplitArea>
</Split>
</el-main>
</el-container>
</template>
<script>
import Order from '@/components/ADempiere/Form/VPOS/Order'
import KeyLayout from '@/components/ADempiere/Form/VPOS/KeyLayout'
import Options from '@/components/ADempiere/Form/VPOS/Options'
import Collection from '@/components/ADempiere/Form/VPOS/Collection'
export default {
name: 'VposMobile',
components: {
Order,
KeyLayout,
Options,
Collection
},
props: {
metadata: {
type: Object,
required: true
}
},
data() {
return {
unsubscribePOSList: () => {}
}
},
computed: {
isShowedPOSKeyLayout: {
get() {
return this.$store.getters.getShowPOSKeyLayout
},
set(val) {
this.$store.commit('setShowPOSKeyLayout', val)
}
},
// options to POS, panel left
isShowedPOSOptions: {
get() {
return this.$store.getters.getIsShowPOSOptions
},
set(val) {
this.$store.commit('setShowPOSOptions', val)
}
},
isShowedPOSKeyLaout() {
return this.$store.getters.getShowPOSKeyLayout
},
showCollection() {
return this.$store.getters.getShowCollectionPos
},
listPointOfSales() {
return this.$store.getters.posAttributes.listPointOfSales
}
},
watch: {
isShowedPOSOptions(value) {
if (value) {
if (this.isShowedPOSKeyLaout) {
this.$store.dispatch('changeWidthRight', 3)
}
} else {
this.$store.dispatch('changeWidthRight', 3)
}
}
},
created() {
// load pont of sales list
if (this.isEmptyValue(this.listPointOfSales)) {
// set pos id with query path
this.$store.dispatch('listPointOfSalesFromServer', this.$route.query.pos)
}
this.unsubscribePOSList = this.posListWithOrganization()
},
beforeDestroy() {
this.unsubscribePOSList()
},
methods: {
posListWithOrganization() {
return this.$store.subscribe((mutation, state) => {
if (mutation.type === 'user/SET_ORGANIZATION') {
this.$store.dispatch('listPointOfSalesFromServer')
}
})
},
onDragKeyLayout(size) {
const sizeWidthRight = size[1] / 10
this.$store.dispatch('changeWidthRight', Math.trunc(sizeWidthRight))
},
onDragOption(size) {
const sizeWidthLeft = size[0] / 10
this.$store.dispatch('changeWidthLeft', Math.trunc(sizeWidthLeft))
}
}
}
</script>
<style scoped>
.split {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow-y: hidden;
overflow-x: hidden;
height: 100%;
width: 100%;
}
.el-card__body {
padding-top: 0px !important;
padding-right: 0px!important;
padding-bottom: 20px;
padding-left: 10px!important;
height: 100%!important;
}
/* Style of Table */
.el-table {
position: relative;
overflow: hidden;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
width: 100%;
max-width: 100%;
height: 100%;
background-color: #FFFFFF;
font-size: 14px;
color: #606266;
}
.el-card__header {
padding: 0px 20px;
border-bottom: 1px solid #e6ebf5;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.time {
font-size: 13px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
width: 100%;
display: block;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.el-header {
background: 'white';
color: #333;
line-height: 10px;
}
.el-aside {
color: #333;
}
.el-row {
margin: 0px!important;
}
.el-col {
border-radius: 4px;
}
.bg-purple-dark {
background: #99a9bf;
}
.bg-purple {
background: #d3dce6;
}
.bg-purple-light {
background: #e5e9f2;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.row-bg {
padding: 10px 0;
background-color: #f9fafc;
}
.order-header {
padding-left: 10px;
font-size: 13px;
}
</style>