shish 2 месяцев назад
Родитель
Сommit
99c2d7abe8

+ 1 - 1
hdPad/src/pages/home/components/console.vue

@@ -10,7 +10,7 @@
 	<view class="active" @click="showCustomPop()" style="background-color: #09C567;border:1upx sold #09C567;color:white;">选客</view>
 	<view class="active" @click="part()" style="border:1upx solid black;color:black">拆散</view>
 	<view class="active" @click="fastCreate()" style="font-size:9upx;">快捷开单</view>
-	<view class="active" @click="gatheringFn()" style="color:#3385FF;border:1upx solid #3385FF;font-size:9upx;">快捷收款</view>
+	<view class="active" @click="gatheringFn()" style="color:#3385FF;border:1upx solid #3385FF;font-size:9upx;">计算器</view>
     <view class="active" @click="clearItemFn" style="color:#636161;border:1upx solid #636161;">清空</view>
 
 	<view v-if="Number(customId)>0" :class="[fastAction==1 ? 'custom-fast' : 'custom']">{{customName}}</view>

+ 975 - 134
hdPad/src/pages/home/components/selectPrice.vue

@@ -1,233 +1,1074 @@
 <template>
     <view class="boardset-flowert">
-        <view class="flower-input_box">
-            <view class="flower-open_box">
-                <view @tap="checkCurrent(1)">
-                    <view class="desc">订单金额</view>
-                    <div class="flower-unit_box">
-                        <text :class="{selected:checkType == 1}">{{price}}</text>
-                    </div>
-                </view>
-            </view>
-            <view class="flower-open_box">
-                <view @tap="checkCurrent(2)" style="position:relative;">
-                    <view class="desc">
-                        收现金
+        <view class="register-main">
+            <scroll-view class="register-left-scroll" scroll-y :enable-back-to-top="false">
+                <view class="flower-input_box">
+                    <view class="flower-input_box-wrap">
+                        <view class="history-entry" @tap.stop="openCalcHistory" hover-class="history-entry--hover">
+                            <view class="iconfont iconmingxi1 history-entry__icon"></view>
+                        </view>
+                        <!-- 计算器:多笔累加 -->
+                        <view class="calc-section" :class="{ 'calc-section-active': keyboardTarget === 'calc' }" @tap="focusCalc">
+                            <view class="calc-title">计算器</view>
+                        <view class="calc-formula">
+                            <block v-if="tokens.length === 0 && isEmptyCalcInput">
+                                <text class="calc-placeholder">输入数字</text>
+                            </block>
+                            <view v-else class="calc-formula-inner">
+                                <block v-for="(t, idx) in tokens" :key="'tok-' + idx">
+                                    <text v-if="t.type === 'num'" class="calc-num">{{ t.s }}</text>
+                                    <text v-else class="calc-op">{{ t.o }}</text>
+                                </block>
+                                <text v-if="!isEmptyCalcInput" class="calc-num">{{ calcInput }}</text>
+                            </view>
+                        </view>
+                        <view class="calc-total-row">
+                            <text class="calc-total-label">合计</text>
+                            <text class="calc-total-val">{{ formatMoney(stackTotal) }}</text>
+                        </view>
+                    </view>
+
+                    <view class="pay-amount-row">
+                        <view class="flower-open_box pay-amount-col">
+                            <view @tap="checkCurrent(1)">
+                                <view class="desc">订单金额</view>
+                                <view class="flower-unit_box">
+                                    <text :class="{ selected: checkType == 1 }">{{ formatMoney(price) }}</text>
+                                </view>
+                            </view>
+                        </view>
+                        <view class="flower-open_box pay-amount-col">
+                            <view @tap="checkCurrent(2)">
+                                <view class="desc">收现金</view>
+                                <view class="flower-unit_box">
+                                    <text :class="{ selected: checkType == 2 }">{{ formatMoney(cash) }}</text>
+                                </view>
+                            </view>
+                        </view>
+                        <view class="flower-open_box pay-amount-col">
+                            <view class="pay-change-inner">
+                                <view class="desc pay-change-desc">
+                                    <text v-if="Number(zl) > 0" class="change-label-ok">找零</text>
+                                    <text v-else-if="Number(zl) < 0" class="change-label-bad">还差</text>
+                                    <text v-else class="change-label-neutral">找零</text>
+                                </view>
+                                <view class="flower-unit_box flower-unit_box-change">
+                                    <text v-if="Number(zl) > 0" class="change-hint-ok">{{ formatChangeAmount(zl) }}</text>
+                                    <text v-else-if="Number(zl) < 0" class="change-hint-bad">{{ formatChangeAmount(-zl) }}</text>
+                                    <text v-else class="change-hint-neutral">—</text>
+                                </view>
+                            </view>
+                        </view>
                     </view>
-                    <view style="position:absolute;top:20upx;left:95upx;width:300upx;text-align:left;font-size:13upx;">
-                        <text v-if="Number(zl)>0" style="color:green;">找零 {{zl}}</text>
-                        <text style="color:red;" v-if="Number(zl)<0">还差 {{-zl}}</text>
+                    <view class="pay-submit-column">
+                        <view class="pay-submit-btn" @tap.stop="submitGathering">提交</view>
+                        <view class="pay-submit-btn pay-submit-btn-secondary" @tap.stop="cancel">取消</view>
+                    </view>
+                    </view>
+                </view>
+            </scroll-view>
+            <view class="register-right">
+                <view class="keyboard-body_box">
+                    <view class="numpad">
+                        <view class="numpad-grid-wrap">
+                            <view class="numpad-digits">
+                                <view class="numpad-row">
+                                    <view class="numpad-key" data-char="1" @tap="onNumpadTap">1</view>
+                                    <view class="numpad-key" data-char="2" @tap="onNumpadTap">2</view>
+                                    <view class="numpad-key" data-char="3" @tap="onNumpadTap">3</view>
+                                </view>
+                                <view class="numpad-row">
+                                    <view class="numpad-key" data-char="4" @tap="onNumpadTap">4</view>
+                                    <view class="numpad-key" data-char="5" @tap="onNumpadTap">5</view>
+                                    <view class="numpad-key" data-char="6" @tap="onNumpadTap">6</view>
+                                </view>
+                                <view class="numpad-row">
+                                    <view class="numpad-key" data-char="7" @tap="onNumpadTap">7</view>
+                                    <view class="numpad-key" data-char="8" @tap="onNumpadTap">8</view>
+                                    <view class="numpad-key" data-char="9" @tap="onNumpadTap">9</view>
+                                </view>
+                                <view class="numpad-row">
+                                    <view class="numpad-key numpad-key-muted" data-char="." @tap="onNumpadTap">.</view>
+                                    <view class="numpad-key" data-char="0" @tap="onNumpadTap">0</view>
+                                    <view class="numpad-key" data-char="00" @tap="onNumpadTap">00</view>
+                                </view>
+                                <view class="numpad-row numpad-row-actions">
+                                    <view class="numpad-key numpad-key-danger numpad-key-delete-only" data-action="backspace" @tap="onNumpadTap">
+                                        <text class="numpad-key-text">删除</text>
+                                    </view>
+                                </view>
+                            </view>
+                            <view class="numpad-op-col">
+                                <view class="numpad-key numpad-key-op" data-action="calc-mul" @tap="onNumpadTap">×</view>
+                                <view class="numpad-key numpad-key-op" data-action="calc-add" @tap="onNumpadTap">+</view>
+                                <view class="numpad-key numpad-key-op" data-action="calc-sub" @tap="onNumpadTap">−</view>
+                                <view class="numpad-key numpad-key-op" data-action="calc-div" @tap="onNumpadTap">÷</view>
+                                <view class="numpad-key numpad-key-op numpad-key-op-muted" data-action="calc-clear" @tap="onNumpadTap">清空</view>
+                            </view>
+                        </view>
                     </view>
-                    <div class="flower-unit_box">
-                        <text :class="{selected:checkType == 2}">{{cash}}</text>
-                    </div>
                 </view>
             </view>
         </view>
-        <view class="keyboard-body_box">
-         <VKeyboard ref="keyboard" :pointIsShow="true" :digital="true" :disorderly="false" @typing="typing" @enter="enter" @cancel="cancel"> </VKeyboard>
-        </view>
+        <uni-popup ref="calcHistoryPopup" type="center" background-color="#ffffff">
+            <view class="calc-history-popup">
+                <view class="calc-history-popup-hd">
+                    <text class="calc-history-popup-title">历史记录</text>
+                    <text class="calc-history-popup-close" @tap.stop="closeCalcHistory">关闭</text>
+                </view>
+                <scroll-view scroll-y class="calc-history-popup-body">
+                    <view
+                        v-for="(row, idx) in calcHistory"
+                        :key="'hist-' + idx"
+                        class="history-item"
+                        :data-idx="idx"
+                        @tap.stop="onHistoryRowTap"
+                    >
+                        <view class="history-line">
+                            <text class="history-time">{{ row.timeLabel }}</text>
+                            <text class="history-total">合计 {{ formatMoney(row.total) }}</text>
+                        </view>
+                        <text class="history-detail">{{ row.detailText }}</text>
+                    </view>
+                    <view v-if="calcHistory.length === 0" class="history-empty">暂无记录</view>
+                </scroll-view>
+            </view>
+        </uni-popup>
     </view>
 </template>
 <script>
-import VKeyboard from '@/components/vKeyboard/VKeyboard.vue'
 export default {
-    name:'selectPrice',
-    components:{ VKeyboard },
+    name: 'selectPrice',
     data() {
         return {
-            price:0,
-            checkType:1,
-            fillType:1,
-            name:'',
-            cash:0
-        };
-    },
-    mounted(){
-        this.activeKeyboard()
+            price: 0,
+            checkType: 3,
+            keyboardTarget: 'calc',
+            name: '',
+            cash: 0,
+            /** 表达式:数字与运算符交替,运算符一点即显示 */
+            tokens: [],
+            calcInput: '',
+            calcHistory: [],
+            maxHistory: 20,
+        }
     },
-    computed:{
-        zl(){
+    computed: {
+        isEmptyCalcInput() {
+            return this.$util.isEmpty(this.calcInput)
+        },
+        stackTotal() {
+            const { nums, ops } = this.buildEvalState()
+            if (!nums.length) {
+                return 0
+            }
+            const r = this.evalExpr(nums, ops)
+            return isNaN(r) ? 0 : r
+        },
+        zl() {
             let amount = 0
-            if(Number(this.cash) > 0 && Number(this.price) > 0){
+            if (Number(this.cash) > 0 && Number(this.price) > 0) {
                 amount = Number(this.cash) - Number(this.price)
                 amount = parseFloat(amount.toFixed(2))
             }
             return Number(amount)
-        }
+        },
+    },
+    watch: {
+        stackTotal: {
+            handler(v) {
+                if (this.keyboardTarget === 'calc') {
+                    const n = parseFloat(Number(v).toFixed(2))
+                    this.price = isNaN(n) ? 0 : n
+                }
+            },
+            immediate: true,
+        },
     },
     methods: {
-        checkCurrent(n){
+        /** 构造用于求值的数字列与运算符列(末尾仅挂运算符、无下一操作数时不参与运算) */
+        buildEvalState() {
+            const nums = []
+            const ops = []
+            for (let i = 0; i < this.tokens.length; i++) {
+                const t = this.tokens[i]
+                if (t.type === 'num') {
+                    const v = Number(t.s)
+                    if (!isNaN(v)) {
+                        nums.push(v)
+                    }
+                } else {
+                    ops.push(t.o)
+                }
+            }
+            const hasTrailingOp =
+                this.tokens.length > 0 &&
+                this.tokens[this.tokens.length - 1].type === 'op'
+            const ci = this.calcInput
+            const hasPartial =
+                ci &&
+                !this.$util.isEmpty(ci) &&
+                ci !== '.' &&
+                !isNaN(parseFloat(ci))
+
+            let evalNums = nums.slice()
+            let evalOps = ops.slice()
+
+            if (hasPartial) {
+                const c = parseFloat(ci)
+                if (!isNaN(c) && c > 0) {
+                    evalNums.push(parseFloat(c.toFixed(2)))
+                }
+            } else if (hasTrailingOp && evalOps.length > 0) {
+                evalOps = evalOps.slice(0, -1)
+            }
+
+            while (evalOps.length > 0 && evalNums.length <= evalOps.length) {
+                evalOps.pop()
+            }
+            return { nums: evalNums, ops: evalOps }
+        },
+        opToInternal(o) {
+            if (o === '×') {
+                return '*'
+            }
+            if (o === '÷') {
+                return '/'
+            }
+            if (o === '−') {
+                return '-'
+            }
+            return '+'
+        },
+        /** 先 ×÷ 再 +− */
+        evalExpr(nums, ops) {
+            if (!nums.length) {
+                return 0
+            }
+            const nArr = nums.map((x) => Number(x))
+            const oArr = ops.map((o) => this.opToInternal(o))
+            while (oArr.length > nArr.length - 1) {
+                oArr.pop()
+            }
+            if (nArr.length === 1) {
+                return parseFloat(nArr[0].toFixed(2))
+            }
+            if (oArr.length !== nArr.length - 1) {
+                return nArr[0] || 0
+            }
+            let i = 0
+            while (i < oArr.length) {
+                if (oArr[i] === '*' || oArr[i] === '/') {
+                    const a = nArr[i]
+                    const b = nArr[i + 1]
+                    if (oArr[i] === '/' && b === 0) {
+                        return NaN
+                    }
+                    const r = oArr[i] === '*' ? a * b : a / b
+                    nArr.splice(i, 2, r)
+                    oArr.splice(i, 1)
+                } else {
+                    i++
+                }
+            }
+            let s = nArr[0]
+            for (let k = 0; k < oArr.length; k++) {
+                const b = nArr[k + 1]
+                if (oArr[k] === '+') {
+                    s += b
+                } else {
+                    s -= b
+                }
+            }
+            return parseFloat(s.toFixed(2))
+        },
+        cloneTokens(arr) {
+            return arr.map((t) =>
+                t.type === 'num' ? { type: 'num', s: t.s } : { type: 'op', o: t.o }
+            )
+        },
+        getExprDisplayString(includePartial) {
+            let s = ''
+            this.tokens.forEach((t) => {
+                s += t.type === 'num' ? t.s : t.o
+            })
+            if (includePartial && !this.$util.isEmpty(this.calcInput)) {
+                s += this.calcInput
+            }
+            return s
+        },
+        legacyAddendsToTokens(addends) {
+            const tokens = []
+            if (!addends || !addends.length) {
+                return tokens
+            }
+            addends.forEach((raw, i) => {
+                const v = Number(raw)
+                if (i === 0) {
+                    tokens.push({ type: 'num', s: String(Math.abs(v)) })
+                } else {
+                    tokens.push({ type: 'op', o: v >= 0 ? '+' : '−' })
+                    tokens.push({ type: 'num', s: String(Math.abs(v)) })
+                }
+            })
+            return tokens
+        },
+        pressCalcOp(displayOp) {
+            if (!this.ensureCalcModeForOps()) {
+                return
+            }
+            if (!this.$util.isEmpty(this.calcInput) && this.calcInput !== '.') {
+                const n = Number(this.calcInput)
+                if (isNaN(n) || n <= 0) {
+                    this.$msg('请输入有效金额')
+                    return
+                }
+                const fs = parseFloat(n.toFixed(2))
+                this.tokens.push({ type: 'num', s: String(fs) })
+                this.calcInput = ''
+            }
+            const last = this.tokens.length > 0 ? this.tokens[this.tokens.length - 1] : null
+            if (last && last.type === 'op') {
+                last.o = displayOp
+                return
+            }
+            if (last && last.type === 'num') {
+                this.tokens.push({ type: 'op', o: displayOp })
+                return
+            }
+            this.$msg('请先输入数字')
+        },
+        ensureCalcModeForOps() {
+            if (this.keyboardTarget !== 'calc') {
+                this.$msg('请先在左侧点击「金额累加」')
+                return false
+            }
+            return true
+        },
+        formatMoney(n) {
+            const x = Number(n)
+            if (isNaN(x)) {
+                return '0.00'
+            }
+            return x.toFixed(2)
+        },
+        /** 找零/差额展示:不保留小数点后多余的 0 */
+        formatChangeAmount(n) {
+            const x = Number(n)
+            if (isNaN(x)) {
+                return '0'
+            }
+            const s = (Math.round(x * 100) / 100).toFixed(2)
+            return parseFloat(s).toString()
+        },
+        focusCalc() {
+            this.$util.hitVoice()
+            this.keyboardTarget = 'calc'
+            this.checkType = 3
+        },
+        checkCurrent(n) {
             this.$util.hitVoice()
             this.checkType = n
-            this.fillType = n
+            this.keyboardTarget = n === 1 ? 'order' : 'cash'
         },
-        cancel(){
+        openCalcHistory() {
+            this.$util.hitVoice()
+            this.$refs.calcHistoryPopup && this.$refs.calcHistoryPopup.open()
+        },
+        closeCalcHistory() {
+            this.$refs.calcHistoryPopup && this.$refs.calcHistoryPopup.close()
+        },
+        onHistoryRowTap(e) {
+            this.$util.hitVoice()
+            const idx = Number(e.currentTarget.dataset.idx)
+            if (isNaN(idx) || idx < 0) {
+                return
+            }
+            const row = this.calcHistory[idx]
+            this.applyHistoryRow(row)
+            this.closeCalcHistory()
+        },
+        pushHistorySnapshot(total) {
+            const now = new Date()
+            const hh = String(now.getHours())
+            const m = now.getMinutes() < 10 ? '0' + now.getMinutes() : String(now.getMinutes())
+            const timeLabel = hh + ':' + m
+            const detailText = this.getExprDisplayString(true)
+            this.calcHistory.unshift({
+                tokens: this.cloneTokens(this.tokens),
+                total,
+                timeLabel,
+                detailText,
+            })
+            if (this.calcHistory.length > this.maxHistory) {
+                this.calcHistory.pop()
+            }
+        },
+        clearStack() {
+            this.$util.hitVoice()
+            this.tokens = []
+            this.calcInput = ''
+        },
+        applyHistoryRow(row) {
+            if (!row) {
+                return
+            }
+            if (row.tokens !== undefined && row.tokens !== null) {
+                this.tokens = this.cloneTokens(row.tokens)
+                this.calcInput = ''
+            } else if (row.addends && row.addends.length) {
+                this.tokens = this.legacyAddendsToTokens(row.addends)
+                this.calcInput = ''
+            } else {
+                return
+            }
+            this.keyboardTarget = 'calc'
+            this.checkType = 3
+        },
+        cancel() {
             this.$util.hitVoice()
             this.$emit('cancelSelectPrice')
         },
-        activeKeyboard() {
-            this.$refs.keyboard.activate();
+        onNumpadTap(e) {
+            this.$util.hitVoice()
+            const ds = e.currentTarget.dataset || {}
+            const action = ds.action
+            const char = ds.char
+            if (action === 'calc-add') {
+                this.pressCalcOp('+')
+                return
+            }
+            if (action === 'calc-sub') {
+                this.pressCalcOp('−')
+                return
+            }
+            if (action === 'calc-mul') {
+                this.pressCalcOp('×')
+                return
+            }
+            if (action === 'calc-div') {
+                this.pressCalcOp('÷')
+                return
+            }
+            if (action === 'calc-clear') {
+                if (!this.ensureCalcModeForOps()) {
+                    return
+                }
+                this.clearStack()
+                return
+            }
+            if (action === 'backspace') {
+                this.typing({ backspace: true })
+            } else if (char !== undefined && char !== null && char !== '') {
+                this.typing({ backspace: false, char: String(char) })
+            }
         },
         typing(e) {
+            const field = this.keyboardTarget
             if (e.backspace) {
-                if(this.fillType == 1){
-                    if(this.checkType == 1){
-                        this.price = ''
-                    }else{
-                        if (this.price.length > 0) {
-                            this.price = this.price.substring(0, this.price.length - 1)   
+                if (field === 'calc') {
+                    if (this.calcInput && String(this.calcInput).length > 0) {
+                        this.calcInput = String(this.calcInput).substring(0, String(this.calcInput).length - 1)
+                    } else if (this.tokens.length > 0) {
+                        const last = this.tokens[this.tokens.length - 1]
+                        if (last.type === 'op') {
+                            this.tokens.pop()
+                        } else if (last.s.length > 1) {
+                            last.s = String(last.s).substring(0, String(last.s).length - 1)
+                        } else {
+                            this.tokens.pop()
                         }
                     }
-                }else if(this.fillType == 2){
-                    if(this.checkType == 2){
+                } else if (field === 'order') {
+                    if (this.checkType === 1) {
+                        this.price = ''
+                    } else if (String(this.price).length > 0) {
+                        this.price = String(this.price).substring(0, String(this.price).length - 1)
+                    }
+                } else if (field === 'cash') {
+                    if (this.checkType === 2) {
                         this.cash = ''
-                    }else{
-                        if (this.cash.length > 0) {
-                            this.cash = this.cash.substring(0, this.cash.length - 1)   
+                    } else if (String(this.cash).length > 0) {
+                        this.cash = String(this.cash).substring(0, String(this.cash).length - 1)
+                    }
+                }
+            } else {
+                const ch = e.char
+                if (field === 'calc') {
+                    if (this.$util.isEmpty(this.calcInput) || this.checkType === 3) {
+                        this.calcInput = ch
+                    } else {
+                        if (ch === '.' && String(this.calcInput).indexOf('.') >= 0) {
+                            return
                         }
+                        this.calcInput += ch
                     }
-                }else{
-
-                }                
-            }else {
-                if(this.fillType == 1){
-                    if(this.$util.isEmpty(this.price) || this.checkType == 1) {
-                        this.price = e.char
-                    }else {
-                        this.price += e.char
+                } else if (field === 'order') {
+                    if (this.$util.isEmpty(this.price) || this.checkType === 1) {
+                        this.price = ch
+                    } else {
+                        if (ch === '.' && String(this.price).indexOf('.') >= 0) {
+                            return
+                        }
+                        this.price += ch
                     }
-                }else if(this.fillType == 2){
-                    if(this.$util.isEmpty(this.cash) || this.checkType == 2) {
-                        this.cash = e.char
-                    }else {
-                        this.cash += e.char
+                } else if (field === 'cash') {
+                    if (this.$util.isEmpty(this.cash) || this.checkType === 2) {
+                        this.cash = ch
+                    } else {
+                        if (ch === '.' && String(this.cash).indexOf('.') >= 0) {
+                            return
+                        }
+                        this.cash += ch
                     }
-                }else{
-
                 }
             }
             this.checkType = 0
         },
-        enter() {
-            if(Number(this.price)  <= 0) {
-                this.$msg('请填写金额')
+        submitGathering() {
+            this.$util.hitVoice()
+            if (this.keyboardTarget === 'calc') {
+                const st = this.stackTotal
+                if (!Number.isFinite(st) || isNaN(st)) {
+                    this.$msg('算式无效(如除数为 0)')
+                    return
+                }
+                this.price = st
+            }
+            const p = Number(this.price)
+            if (isNaN(p) || p <= 0) {
+                this.$msg('订单金额须大于 0')
                 return
             }
-            this.$emit('zjGathering', {price:Number(this.price),name:this.name,cash:Number(this.cash)})
+            this.pushHistorySnapshot(p)
+            this.$emit('zjGathering', { price: p, name: this.name, cash: Number(this.cash) || 0 })
             this.price = 0
-            this.checkType = 1
-        }
-    }
+            this.checkType = 3
+            this.keyboardTarget = 'calc'
+            this.cash = 0
+            this.tokens = []
+            this.calcInput = ''
+        },
+    },
 }
 </script>
 <style lang="scss" scoped>
-.pay-style{
-    text-align:center;
-    margin-top:10upx;
+.pay-style {
+    text-align: center;
+    margin-top: 10upx;
     button {
-        width:80upx;
-        height:30upx;
-        font-size:12upx;
-        display:inline-block;
-        color:#777575;
+        width: 80upx;
+        height: 30upx;
+        font-size: 12upx;
+        display: inline-block;
+        color: #777575;
     }
-    button:first-child{
-        margin-right:8upx;
+    button:first-child {
+        margin-right: 8upx;
     }
-    .selected{
-        color:#09C567;
-        border:1upx solid #09C567;
+    .selected {
+        color: #09c567;
+        border: 1upx solid #09c567;
     }
 }
-.grey-color{
-    color:#d1cfcf;
+.grey-color {
+    color: #d1cfcf;
 }
 .boardset-flowert {
-    width:34vw;
-    height: 90vh;
+    width: 88vw;
+    max-width: 900upx;
+    height: 84vh;
+    max-height: 900px;
     background-color: #ffffff;
     margin: 0 auto;
     border-radius: 5upx;
     padding: 10upx;
-    overflow-y: auto;
+    overflow: hidden;
     display: flex;
     flex-direction: column;
     box-sizing: border-box;
-    & .flower-input_box {
+
+    .register-main {
         flex: 1;
-        & .flower-title {
-            text-align: center;
-            color: #333333;
-            font-size: 14upx;
+        display: flex;
+        flex-direction: row;
+        align-items: stretch;
+        min-height: 0;
+    }
+    .register-left-scroll {
+        flex: 1;
+        min-width: 0;
+        width: 0;
+        height: 100%;
+        box-sizing: border-box;
+        padding-right: 10upx;
+    }
+    .register-right {
+        width: 52%;
+        flex-shrink: 0;
+        min-width: 260upx;
+        height: 100%;
+        display: flex;
+        flex-direction: column;
+        box-sizing: border-box;
+        padding-left: 10upx;
+        border-left: 1upx solid #e8e8e8;
+    }
+    .flower-input_box {
+        padding-bottom: 16upx;
+    }
+    .flower-input_box-wrap {
+        position: relative;
+    }
+    .history-entry {
+        position: absolute;
+        top: 6upx;
+        right: 6upx;
+        z-index: 3;
+        width: 44upx;
+        height: 44upx;
+        padding: 0;
+        margin: 0;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: center;
+        border-radius: 8upx;
+        background: rgba(248, 255, 248, 0.95);
+        border: 1upx solid #c8ecd8;
+        box-sizing: border-box;
+        pointer-events: auto;
+    }
+    .history-entry--hover {
+        opacity: 0.82;
+    }
+    .history-entry__icon {
+        font-size: 22upx;
+        color: #09c567;
+        line-height: 1;
+    }
+    .pay-amount-row {
+        display: flex;
+        flex-direction: row;
+        margin-top: 10upx;
+        align-items: flex-start;
+    }
+    .pay-amount-col {
+        flex: 1;
+        min-width: 0;
+        margin-top: 0;
+        & + .pay-amount-col {
+            margin-left: 8upx;
         }
-        & .flower-ratio{
-            text-align: center;
-            color: #CCCCCC;
-            font-size: 9upx;
-            margin-bottom:10upx;
-            margin-top:6upx;
+    }
+    .pay-change-desc {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: center;
+    }
+    .change-label-ok {
+        font-size: 13upx;
+        color: #09c567;
+    }
+    .change-label-bad {
+        font-size: 13upx;
+        color: #e54545;
+    }
+    .change-label-neutral {
+        font-size: 13upx;
+        color: #cccccc;
+    }
+    .change-hint-ok {
+        font-size: 15upx;
+        color: #09c567;
+    }
+    .change-hint-bad {
+        font-size: 15upx;
+        color: #e54545;
+    }
+    .change-hint-neutral {
+        font-size: 15upx;
+        color: #cccccc;
+    }
+    .flower-unit_box-change {
+        min-height: 25upx;
+    }
+    .pay-submit-column {
+        display: flex;
+        flex-direction: column;
+        margin-top: 12upx;
+    }
+    .pay-submit-btn {
+        text-align: center;
+        font-size: 16upx;
+        padding: 14upx 16upx;
+        background: #09c567;
+        color: #ffffff;
+        border-radius: 8upx;
+        border: 1upx solid #09c567;
+        font-weight: 600;
+        box-sizing: border-box;
+        & + .pay-submit-btn {
+            margin-top: 8upx;
+        }
+    }
+    .pay-submit-btn-secondary {
+        background: #f7f7f7;
+        color: #333333;
+        border-color: #dddddd;
+        font-weight: 500;
+    }
+    .pay-submit-btn:active {
+        opacity: 0.9;
+    }
+    .calc-section {
+        border: 1upx solid #eeeeee;
+        border-radius: 6upx;
+        padding: 10upx;
+        margin-top: 0;
+        margin-bottom: 10upx;
+        &.calc-section-active {
+            border-color: #09c567;
+            background: #f8fff8;
+        }
+    }
+    .calc-title {
+        text-align: center;
+        font-size: 13upx;
+        color: #333333;
+        margin-bottom: 8upx;
+    }
+    .calc-formula {
+        min-height: 36upx;
+        font-size: 14upx;
+        color: #333333;
+        text-align: center;
+        line-height: 1.4;
+        word-break: break-all;
+    }
+    .calc-formula-inner {
+        display: flex;
+        flex-direction: row;
+        flex-wrap: wrap;
+        align-items: center;
+        justify-content: center;
+    }
+    .calc-placeholder {
+        font-size: 11upx;
+        color: #999999;
+    }
+    .calc-num {
+        color: #333333;
+    }
+    .calc-op {
+        color: #09c567;
+    }
+    .calc-total-row {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: center;
+        margin-top: 10upx;
+    }
+    .calc-total-label {
+        font-size: 12upx;
+        color: #999999;
+        margin-right: 8upx;
+    }
+    .calc-total-val {
+        font-size: 16upx;
+        font-weight: bold;
+        color: #09c567;
+    }
+    .calc-btn-row {
+        display: flex;
+        flex-direction: row;
+        margin-top: 10upx;
+        & + .calc-btn-row {
+            margin-top: 8upx;
         }
-        & .set-residue_box {
-            font-size: 12upx;
-            color: #CCCCCC;
-            margin-top: 10upx;
-            text-align: left;
+    }
+    .calc-btn-row-single .calc-btn {
+        flex: 1;
+    }
+    .calc-btn {
+        flex: 1;
+        text-align: center;
+        font-size: 12upx;
+        padding: 8upx 4upx;
+        border: 1upx solid #dddddd;
+        border-radius: 4upx;
+        color: #333333;
+        background: #fafafa;
+        & + .calc-btn {
+            margin-left: 8upx;
         }
-        .goods-name{
-            border:1upx solid #EEEEEE;
-            width:150upx;
-            margin:10upx auto 0 auto;
-            height:30upx;
-            font-size:15upx;
-            text-align:center;
+    }
+    .calc-btn-primary {
+        background: #09c567;
+        color: #ffffff;
+        border-color: #09c567;
+    }
+    .calc-history-popup {
+        width: 72vw;
+        max-width: 520upx;
+        max-height: 75vh;
+        display: flex;
+        flex-direction: column;
+        box-sizing: border-box;
+        border-radius: 8upx;
+        overflow: hidden;
+    }
+    .calc-history-popup-hd {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: space-between;
+        padding: 12upx 16upx;
+        border-bottom: 1upx solid #eeeeee;
+        flex-shrink: 0;
+    }
+    .calc-history-popup-title {
+        font-size: 15upx;
+        font-weight: bold;
+        color: #333333;
+    }
+    .calc-history-popup-close {
+        font-size: 13upx;
+        color: #09c567;
+        padding: 4upx 8upx;
+    }
+    .calc-history-popup-body {
+        height: 380upx;
+        max-height: 55vh;
+        box-sizing: border-box;
+    }
+    .history-item {
+        padding: 8upx 6upx;
+        border-bottom: 1upx solid #f0f0f0;
+        & + .history-item {
+            margin-top: 0;
         }
     }
-    & .flower-open_box {
+    .history-line {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        margin-bottom: 4upx;
+    }
+    .history-time {
+        font-size: 11upx;
+        color: #999999;
+    }
+    .history-total {
+        font-size: 11upx;
+        color: #09c567;
+    }
+    .history-detail {
+        font-size: 11upx;
+        color: #666666;
+        word-break: break-all;
+    }
+    .history-empty {
+        text-align: center;
+        font-size: 11upx;
+        color: #cccccc;
+        padding: 16upx 0;
+    }
+    .flower-open_box {
         display: flex;
         align-items: center;
-        margin-top:10upx;
+        margin-top: 10upx;
         justify-content: center;
         & > view {
-            width:90upx;
+            width: 100%;
+            box-sizing: border-box;
             & .desc {
                 text-align: center;
                 font-size: 13upx;
                 margin-bottom: 5upx;
-                color:#CCCCCC;
+                color: #cccccc;
             }
             & .flower-unit_box {
                 display: flex;
-                font-size:15upx;
+                font-size: 15upx;
                 align-items: center;
-                justify-content:center;
+                justify-content: center;
                 border-bottom: 1px solid #eee;
-                & > text{
-                    height:25upx;
+                & > text {
+                    height: 25upx;
                 }
-                .selected{
-                    background:blue;
-                    color:white;
+                .selected {
+                    background: blue;
+                    color: white;
                 }
             }
         }
     }
-    .switch-button{
+    .flower-open_box.pay-amount-col {
+        margin-top: 0;
+    }
+    .switch-button {
         display: flex;
         justify-content: center;
         width: 35upx;
         height: 35upx;
         position: absolute;
-        top:70upx;
-        left:14vw;
+        top: 70upx;
+        left: 14vw;
         .change-unit {
             width: 35upx;
             height: 35upx;
         }
     }
-    & .keyboard-body_box {
+    .keyboard-body_box {
         flex: 1;
+        min-height: 0;
+        display: flex;
+        flex-direction: column;
+        padding-top: 0;
+        border-top: none;
         overflow: hidden;
     }
 }
-</style>
+.keyboard-body_box .numpad {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    min-height: 0;
+}
+.numpad {
+    width: 100%;
+    background: #ececec;
+    border-radius: 8upx;
+    padding: 8upx;
+    box-sizing: border-box;
+}
+.numpad-grid-wrap {
+    flex: 1;
+    display: flex;
+    flex-direction: row;
+    align-items: stretch;
+    min-height: 0;
+}
+.numpad-digits {
+    flex: 1;
+    min-width: 0;
+    display: flex;
+    flex-direction: column;
+    min-height: 0;
+}
+.numpad-digits .numpad-row {
+    flex: 1;
+}
+.numpad-op-col {
+    width: 26%;
+    flex-shrink: 0;
+    display: flex;
+    flex-direction: column;
+    margin-left: 8upx;
+}
+.numpad-op-col .numpad-key {
+    margin-left: 0;
+}
+.numpad-op-col .numpad-key-op {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    min-height: 44upx;
+    line-height: normal;
+    font-size: 22upx;
+    font-weight: 600;
+}
+.numpad-op-col .numpad-key-op + .numpad-key-op {
+    margin-top: 8upx;
+}
+.numpad-key-op-muted {
+    font-size: 13upx;
+    font-weight: 600;
+    background: #e8e8e8;
+    color: #333333;
+    box-shadow: 0 2upx 0 #bdbdbd;
+}
+.keyboard-body_box .numpad-op-col .numpad-key-op {
+    min-height: 0;
+}
+.keyboard-body_box .numpad-row {
+    flex: 1;
+    min-height: 40upx;
+}
+.numpad-row {
+    display: flex;
+    flex-direction: row;
+    margin-top: 8upx;
+    &:first-child {
+        margin-top: 0;
+    }
+}
+.keyboard-body_box .numpad-key {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: auto;
+    min-height: 44upx;
+    line-height: normal;
+}
+.numpad-key {
+    flex: 1;
+    margin-left: 8upx;
+    height: 44upx;
+    line-height: 44upx;
+    text-align: center;
+    font-size: 18upx;
+    font-weight: 500;
+    color: #222222;
+    background: #ffffff;
+    border-radius: 8upx;
+    box-shadow: 0 2upx 0 #c8c8c8;
+    &:first-child {
+        margin-left: 0;
+    }
+    &:active {
+        background: #e8e8e8;
+        box-shadow: 0 1upx 0 #bbbbbb;
+    }
+}
+.numpad-key-muted {
+    font-size: 22upx;
+    font-weight: 400;
+    color: #555555;
+}
+.numpad-key-text {
+    font-size: 14upx;
+}
+.numpad-row-actions {
+    margin-top: 10upx;
+}
+.numpad-key-delete-only {
+    flex: 1;
+    margin-left: 0;
+}
+.numpad-key-wide {
+    flex: 1.2;
+}
+.numpad-key-danger {
+    background: #ffeded;
+    color: #cc4444;
+    box-shadow: 0 2upx 0 #ddb8b8;
+}
+.numpad-key-action {
+    font-size: 14upx;
+    background: #f7f7f7;
+}
+.numpad-key-ok {
+    background: #09c567;
+    color: #ffffff;
+    font-weight: 600;
+    box-shadow: 0 2upx 0 #078a4a;
+}
+.numpad-key-ok:active {
+    background: #08b35d;
+}
+</style>