|
|
@@ -0,0 +1,1968 @@
|
|
|
+<template>
|
|
|
+ <view class="calc-page">
|
|
|
+ <scroll-view class="calc-scroll" scroll-y :enable-back-to-top="false">
|
|
|
+ <view class="calc-sheet">
|
|
|
+ <view class="calc-card-wrap">
|
|
|
+ <view
|
|
|
+ class="calc-section"
|
|
|
+ :class="{ 'calc-section-active': keyboardTarget === 'calc' }"
|
|
|
+ @tap="focusCalc"
|
|
|
+ >
|
|
|
+ <view class="calc-actions-top">
|
|
|
+ <view class="discount-entry" @tap.stop="openDiscountPopup" hover-class="discount-entry-hover">
|
|
|
+ <text class="discount-entry-glyph">折</text>
|
|
|
+ </view>
|
|
|
+ <text class="calc-section-title">计算器</text>
|
|
|
+ <view class="history-entry" @tap.stop="openCalcHistory" hover-class="history-entry-hover">
|
|
|
+ <text class="iconfont iconmingxi1 history-entry-icon"></text>
|
|
|
+ </view>
|
|
|
+ </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(stackDiscountedAmount) }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="isDiscountActive" class="calc-discount-row">
|
|
|
+ <text class="calc-discount-label">{{ formatDiscountZheLabel(discountZhe) }}后</text>
|
|
|
+ <text class="calc-discount-val">{{ formatMoney(stackDiscountedAmount) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="customer-strip" @tap.stop="pickCustomer">
|
|
|
+ <text class="customer-strip-label">客户</text>
|
|
|
+ <text class="customer-strip-name">{{ customerDisplayName }}</text>
|
|
|
+ <text class="customer-strip-action">修改客户</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="rcv-pay-block">
|
|
|
+ <view class="rcv-pay-title">收款方式</view>
|
|
|
+ <view class="rcv-item-row">
|
|
|
+ <view
|
|
|
+ v-for="item in optHasPayList"
|
|
|
+ :key="'hp-' + item.id"
|
|
|
+ class="rcv-item-chip"
|
|
|
+ :data-hpid="String(item.id)"
|
|
|
+ :class="{ active: selHasPay === item.id }"
|
|
|
+ @tap.stop="onHasPayChipTap"
|
|
|
+ >
|
|
|
+ {{ item.name }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="selHasPay === 1" class="rcv-sub-block">
|
|
|
+ <view class="rcv-pay-title rcv-pay-title-sub">线下方式</view>
|
|
|
+ <view class="rcv-item-row">
|
|
|
+ <view
|
|
|
+ v-for="item in optPayWayList"
|
|
|
+ :key="'pw-' + item.id"
|
|
|
+ class="rcv-item-chip"
|
|
|
+ :data-pwid="String(item.id)"
|
|
|
+ :class="{ active: selPayWay === item.id }"
|
|
|
+ @tap.stop="onPayWayChipTap"
|
|
|
+ >
|
|
|
+ {{ item.name }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="showCashPayCols" class="pay-cash-panel">
|
|
|
+ <view class="pay-cash-item" @tap="checkCurrent(2)">
|
|
|
+ <text class="pay-cash-desc">收到现金</text>
|
|
|
+ <text
|
|
|
+ class="pay-cash-amt"
|
|
|
+ :class="{ 'pay-cash-amt-selected': keyboardTarget !== 'calc' && checkType == 2 }"
|
|
|
+ >
|
|
|
+ {{ formatMoney(cash) }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view class="pay-cash-item pay-cash-item-change">
|
|
|
+ <view class="pay-change-line">
|
|
|
+ <text v-if="Number(zl) > 0" class="lbl-ok">找零</text>
|
|
|
+ <text v-else-if="Number(zl) < 0" class="lbl-bad">还差</text>
|
|
|
+ <text v-else class="lbl-neu">找零</text>
|
|
|
+ </view>
|
|
|
+ <text v-if="Number(zl) > 0" class="amt-ok">{{ formatChangeAmount(zl) }}</text>
|
|
|
+ <text v-else-if="Number(zl) < 0" class="amt-bad">{{ formatChangeAmount(-zl) }}</text>
|
|
|
+ <text v-else class="amt-neu">—</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="pay-submit-row">
|
|
|
+ <view class="pay-submit-btn pay-submit-btn-outline" @tap.stop="stashCurrent">暂存</view>
|
|
|
+ <view class="pay-submit-btn" @tap.stop="submitCashierPay">提交</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="safe-bottom-spacer" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+
|
|
|
+ <view class="numpad-dock safe-area-padding">
|
|
|
+ <view class="numpad">
|
|
|
+ <view class="numpad-grid-wrap">
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="1" @tap="onNumpadTap">1</view></view>
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="2" @tap="onNumpadTap">2</view></view>
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="3" @tap="onNumpadTap">3</view></view>
|
|
|
+ <view class="numpad-cell numpad-cell-op numpad-cell-mul">
|
|
|
+ <view class="numpad-key numpad-key-op" data-action="calc-mul" @tap="onNumpadTap">×</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="4" @tap="onNumpadTap">4</view></view>
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="5" @tap="onNumpadTap">5</view></view>
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="6" @tap="onNumpadTap">6</view></view>
|
|
|
+ <view class="numpad-cell numpad-cell-op numpad-cell-add">
|
|
|
+ <view class="numpad-key numpad-key-op numpad-key-op-add" data-action="calc-add" @tap="onNumpadTap">
|
|
|
+ +
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="7" @tap="onNumpadTap">7</view></view>
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="8" @tap="onNumpadTap">8</view></view>
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="9" @tap="onNumpadTap">9</view></view>
|
|
|
+
|
|
|
+ <view class="numpad-cell"><view class="numpad-key numpad-key-muted" data-char="." @tap="onNumpadTap">.</view></view>
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="0" @tap="onNumpadTap">0</view></view>
|
|
|
+ <view class="numpad-cell"><view class="numpad-key" data-char="00" @tap="onNumpadTap">00</view></view>
|
|
|
+ <view class="numpad-cell numpad-cell-op numpad-cell-sub">
|
|
|
+ <view class="numpad-key numpad-key-op" data-action="calc-sub" @tap="onNumpadTap">−</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="numpad-cell numpad-cell-actions-row">
|
|
|
+ <view
|
|
|
+ class="numpad-key numpad-key-action-dual numpad-key-action-muted"
|
|
|
+ @tap.stop="tapClearCalculator"
|
|
|
+ >
|
|
|
+ <text class="numpad-key-text">清空</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="numpad-key numpad-key-danger numpad-key-action-dual"
|
|
|
+ data-action="backspace"
|
|
|
+ @tap="onNumpadTap"
|
|
|
+ >
|
|
|
+ <text class="numpad-key-text">删除</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="numpad-cell numpad-cell-op numpad-cell-div">
|
|
|
+ <view class="numpad-key numpad-key-op" data-action="calc-div" @tap="onNumpadTap">÷</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <uni-popup ref="calcHistoryPopup" type="center" background-color="#ffffff" @change="onCalcHistoryPopupChange">
|
|
|
+ <view class="mob-popup-wrap mob-popup-history">
|
|
|
+ <view class="mob-popup-hd">
|
|
|
+ <text class="mob-popup-title">暂存记录</text>
|
|
|
+ <text class="mob-popup-close" @tap.stop="onHistoryPopupCloseTap">关闭</text>
|
|
|
+ </view>
|
|
|
+ <scroll-view scroll-y class="mob-popup-body">
|
|
|
+ <view v-for="(row, idx) in calcHistory" :key="row.id || 'hist-' + idx" class="history-item">
|
|
|
+ <view class="history-item-body" :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>
|
|
|
+ <text class="history-del" :data-idx="idx" @tap.stop="onHistoryDeleteTap">删除</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="calcHistory.length === 0" class="history-empty">暂无记录</view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </uni-popup>
|
|
|
+
|
|
|
+ <uni-popup ref="discountPopup" type="center" background-color="#ffffff" @change="onDiscountPopupChange">
|
|
|
+ <view class="mob-popup-wrap mob-popup-discount">
|
|
|
+ <view class="mob-popup-hd">
|
|
|
+ <text class="mob-popup-title">折扣</text>
|
|
|
+ <text class="mob-popup-close" @tap.stop="onDiscountPopupCloseTap">关闭</text>
|
|
|
+ </view>
|
|
|
+ <view class="discount-toolbar">
|
|
|
+ <view class="discount-custom-row">
|
|
|
+ <input
|
|
|
+ class="discount-custom-input"
|
|
|
+ v-model="customZheInput"
|
|
|
+ type="digit"
|
|
|
+ placeholder="如 8.8 折"
|
|
|
+ confirm-type="done"
|
|
|
+ />
|
|
|
+ <view class="discount-custom-add-btn" @tap.stop="addCustomDiscountZhe">添加</view>
|
|
|
+ </view>
|
|
|
+ <text v-if="customDiscountZheList.length > 0" class="discount-toolbar-hint">
|
|
|
+ 已存 {{ customDiscountZheList.length }} 条,点下方选用
|
|
|
+ </text>
|
|
|
+ <text v-else class="discount-toolbar-hint discount-toolbar-hint-muted">先输入折扣数字再点添加</text>
|
|
|
+ </view>
|
|
|
+ <scroll-view scroll-y class="mob-popup-body discount-options-scroll">
|
|
|
+ <view
|
|
|
+ class="discount-option"
|
|
|
+ :class="{ 'discount-option-active': isZheActive(10) }"
|
|
|
+ data-zhe="10"
|
|
|
+ @tap.stop="onDiscountRowPickTap"
|
|
|
+ >
|
|
|
+ <text class="discount-option-text">无折扣</text>
|
|
|
+ <text v-if="isZheActive(10)" class="discount-option-check">✓</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ v-for="(z, cidx) in customDiscountZheList"
|
|
|
+ :key="'d-' + cidx + '-' + z"
|
|
|
+ class="discount-option discount-option-with-del"
|
|
|
+ :class="{ 'discount-option-active': isZheActive(z) }"
|
|
|
+ >
|
|
|
+ <view class="discount-option-main" :data-zhe="String(z)" @tap.stop="onDiscountRowPickTap">
|
|
|
+ <text class="discount-option-text">{{ formatDiscountRowLabel(z) }}</text>
|
|
|
+ <text v-if="isZheActive(z)" class="discount-option-check">✓</text>
|
|
|
+ </view>
|
|
|
+ <text class="discount-option-del" :data-cidx="String(cidx)" @tap.stop="onCustomDiscountDeleteTap">
|
|
|
+ 删除
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </uni-popup>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { createOrder } from '@/api/order/index'
|
|
|
+import { mapGetters } from 'vuex'
|
|
|
+
|
|
|
+const CALC_HISTORY_STORAGE_KEY = 'ghsApp_calculator_calcHistory'
|
|
|
+const DISCOUNT_CUSTOM_STORAGE_KEY = 'ghsApp_calculator_customDiscountZhe'
|
|
|
+const DISCOUNT_SELECTED_STORAGE_KEY = 'ghsApp_calculator_discountZhe'
|
|
|
+/** 计算器选客回填:对应 selectCustom style=3 */
|
|
|
+const CALCULATOR_PICK_CUSTOMER_KEY = 'ghsApp_calculator_pick_customer'
|
|
|
+/** 与首页「快捷开单」一致:取自 loginInfo.skCustomId,界面展示名固定 */
|
|
|
+const SHORTCUT_CUSTOM_DISPLAY_NAME = '快捷开单'
|
|
|
+
|
|
|
+const SETTLE_FORM_DEFAULTS = {
|
|
|
+ sendType: 1,
|
|
|
+ hasPay: 0,
|
|
|
+ payWay: 0,
|
|
|
+ needPrint: 1,
|
|
|
+ freight: '',
|
|
|
+ groupName: '',
|
|
|
+ remark: '',
|
|
|
+ sendCost: 0,
|
|
|
+ customSendCost: 0,
|
|
|
+ labourCost: 0,
|
|
|
+ modifyPrice: 0,
|
|
|
+ getStaffId: 0,
|
|
|
+ getStaffName: '',
|
|
|
+ shopAdminId: 0,
|
|
|
+ shopAdminName: '',
|
|
|
+ cash: 0,
|
|
|
+ dealPrice: 0,
|
|
|
+ reductionRule: 0,
|
|
|
+ callErrand: 0,
|
|
|
+ weight: 1,
|
|
|
+ deliveryRemark: '',
|
|
|
+}
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'ToolsCalculator',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ customId: 0,
|
|
|
+ customName: '',
|
|
|
+ price: 0,
|
|
|
+ checkType: 3,
|
|
|
+ keyboardTarget: 'calc',
|
|
|
+ name: '',
|
|
|
+ cash: 0,
|
|
|
+ tokens: [],
|
|
|
+ calcInput: '',
|
|
|
+ calcHistory: [],
|
|
|
+ maxHistory: 20,
|
|
|
+ _historyPopupCloseByApi: false,
|
|
|
+ discountZhe: 10,
|
|
|
+ customDiscountZheList: [],
|
|
|
+ customZheInput: '',
|
|
|
+ _discountPopupCloseByApi: false,
|
|
|
+ selHasPay: 0,
|
|
|
+ selPayWay: 0,
|
|
|
+ optHasPayList: [
|
|
|
+ { name: '扫码', id: 0 },
|
|
|
+ { name: '赊账', id: 2 },
|
|
|
+ { name: '线下', id: 1 },
|
|
|
+ ],
|
|
|
+ optPayWayList: [
|
|
|
+ { name: '微信', id: 0 },
|
|
|
+ { name: '支付宝', id: 1 },
|
|
|
+ { name: '现金', id: 4 },
|
|
|
+ { name: '银行卡', id: 5 },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ ...mapGetters({ loginInfo: 'getLoginInfo' }),
|
|
|
+ /** 工作台「快捷开单」客户 id,与 workbench skKd 使用的 loginInfo.skCustomId 一致 */
|
|
|
+ shortcutCustomerId() {
|
|
|
+ const login = this.loginInfo || {}
|
|
|
+ const sid = login.skCustomId
|
|
|
+ if (this.$util.isEmpty(sid) || Number(sid) <= 0) return 0
|
|
|
+ return Number(sid)
|
|
|
+ },
|
|
|
+ customerDisplayName() {
|
|
|
+ const n = (this.customName || '').trim()
|
|
|
+ if (n) return n
|
|
|
+ const skid = this.shortcutCustomerId
|
|
|
+ if (Number(this.customId) > 0 && skid > 0 && Number(this.customId) === skid) {
|
|
|
+ return SHORTCUT_CUSTOM_DISPLAY_NAME
|
|
|
+ }
|
|
|
+ if (Number(this.customId) > 0) return '—'
|
|
|
+ return '未选择'
|
|
|
+ },
|
|
|
+ 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
|
|
|
+ },
|
|
|
+ discountRate() {
|
|
|
+ const z = Number(this.discountZhe)
|
|
|
+ if (isNaN(z) || z <= 0) return 1
|
|
|
+ return Math.min(10, Math.max(0.01, z)) / 10
|
|
|
+ },
|
|
|
+ stackDiscountedAmount() {
|
|
|
+ const raw = Number(this.stackTotal)
|
|
|
+ if (isNaN(raw)) return 0
|
|
|
+ return parseFloat((raw * this.discountRate).toFixed(2))
|
|
|
+ },
|
|
|
+ isDiscountActive() {
|
|
|
+ return Number(this.discountZhe) < 10 - 1e-6
|
|
|
+ },
|
|
|
+ zl() {
|
|
|
+ if (!(this.selHasPay === 1 && this.selPayWay === 4)) return 0
|
|
|
+ let amount = 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)
|
|
|
+ },
|
|
|
+ showCashPayCols() {
|
|
|
+ return this.selHasPay === 1 && this.selPayWay === 4
|
|
|
+ },
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ shortcutCustomerId(n) {
|
|
|
+ if (n > 0 && Number(this.customId) <= 0) this.applyDefaultShortcutCustomer()
|
|
|
+ },
|
|
|
+ stackDiscountedAmount: {
|
|
|
+ handler(v) {
|
|
|
+ if (this.keyboardTarget === 'calc') {
|
|
|
+ const n = parseFloat(Number(v).toFixed(2))
|
|
|
+ this.price = isNaN(n) ? 0 : n
|
|
|
+ }
|
|
|
+ },
|
|
|
+ immediate: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ onLoad(options) {
|
|
|
+ if (options && options.customId) {
|
|
|
+ const id = Number(options.customId)
|
|
|
+ if (!isNaN(id) && id > 0) this.customId = id
|
|
|
+ }
|
|
|
+ if (options && options.customName) {
|
|
|
+ this.customName = decodeURIComponent(String(options.customName))
|
|
|
+ }
|
|
|
+ this.applyDefaultShortcutCustomer()
|
|
|
+ },
|
|
|
+ onShow() {
|
|
|
+ this.applyPickCustomerFromStorage()
|
|
|
+ this.applyDefaultShortcutCustomer()
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.loadCalcHistoryFromStorage()
|
|
|
+ this.loadDiscountFromStorage()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ init(){
|
|
|
+
|
|
|
+ },
|
|
|
+ pickCustomer() {
|
|
|
+ this.tapVoice()
|
|
|
+ this.$util.pageTo({url: '/admin/custom/selectCustom?style=3'})
|
|
|
+ },
|
|
|
+ applyPickCustomerFromStorage() {
|
|
|
+ try {
|
|
|
+ const raw = uni.getStorageSync(CALCULATOR_PICK_CUSTOMER_KEY)
|
|
|
+ if (!raw) return
|
|
|
+ const obj = typeof raw === 'string' ? JSON.parse(raw) : raw
|
|
|
+ if (obj && obj.id != null) {
|
|
|
+ this.customId = Number(obj.id) || 0
|
|
|
+ this.customName = obj.name != null ? String(obj.name) : ''
|
|
|
+ }
|
|
|
+ uni.removeStorageSync(CALCULATOR_PICK_CUSTOMER_KEY)
|
|
|
+ } catch (e) {}
|
|
|
+ },
|
|
|
+ /** 未选客且无路由参数带入时,默认工作台设置的快捷客户 */
|
|
|
+ applyDefaultShortcutCustomer() {
|
|
|
+ if (Number(this.customId) > 0) return
|
|
|
+ const skid = this.shortcutCustomerId
|
|
|
+ if (!skid || skid <= 0) return
|
|
|
+ this.customId = skid
|
|
|
+ this.customName = SHORTCUT_CUSTOM_DISPLAY_NAME
|
|
|
+ },
|
|
|
+ loadCalcHistoryFromStorage() {
|
|
|
+ try {
|
|
|
+ const raw = uni.getStorageSync(CALC_HISTORY_STORAGE_KEY)
|
|
|
+ if (raw === undefined || raw === null || raw === '') return
|
|
|
+ const list = typeof raw === 'string' ? JSON.parse(raw) : raw
|
|
|
+ if (!Array.isArray(list) || !list.length) return
|
|
|
+ this.calcHistory = list.slice(0, this.maxHistory).map((row, i) => this.normalizeHistoryRow(row, i))
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('[calculator] loadCalcHistoryFromStorage', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ normalizeHistoryRow(row, idx) {
|
|
|
+ const r = row && typeof row === 'object' ? row : {}
|
|
|
+ const id = r.id != null && String(r.id) !== '' ? String(r.id) : 'hist-' + Date.now() + '-' + idx
|
|
|
+ return {
|
|
|
+ id,
|
|
|
+ tokens: Array.isArray(r.tokens) ? r.tokens : null,
|
|
|
+ addends: Array.isArray(r.addends) ? r.addends : null,
|
|
|
+ calcInput: r.calcInput != null ? String(r.calcInput) : '',
|
|
|
+ total: r.total,
|
|
|
+ timeLabel: r.timeLabel != null ? String(r.timeLabel) : '--:--',
|
|
|
+ detailText: r.detailText != null ? String(r.detailText) : '',
|
|
|
+ cashSnapshot: r.cashSnapshot,
|
|
|
+ priceSnapshot: r.priceSnapshot,
|
|
|
+ orderOnly: !!r.orderOnly,
|
|
|
+ source: r.source === 'stash' ? 'stash' : 'submit',
|
|
|
+ discountZhe:
|
|
|
+ r.discountZhe != null && r.discountZhe !== '' && !isNaN(Number(r.discountZhe))
|
|
|
+ ? parseFloat(Number(r.discountZhe).toFixed(2))
|
|
|
+ : undefined,
|
|
|
+ }
|
|
|
+ },
|
|
|
+ persistCalcHistoryToStorage() {
|
|
|
+ try {
|
|
|
+ uni.setStorageSync(CALC_HISTORY_STORAGE_KEY, JSON.stringify(this.calcHistory))
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('[calculator] persistCalcHistoryToStorage', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ loadDiscountFromStorage() {
|
|
|
+ try {
|
|
|
+ const customRaw = uni.getStorageSync(DISCOUNT_CUSTOM_STORAGE_KEY)
|
|
|
+ if (customRaw !== undefined && customRaw !== null && customRaw !== '') {
|
|
|
+ const parsed = typeof customRaw === 'string' ? JSON.parse(customRaw) : customRaw
|
|
|
+ if (Array.isArray(parsed)) {
|
|
|
+ this.customDiscountZheList = parsed
|
|
|
+ .map((x) => Number(x))
|
|
|
+ .filter((n) => !isNaN(n) && n > 0 && n < 10 - 1e-6)
|
|
|
+ .map((n) => parseFloat(n.toFixed(2)))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const sel = uni.getStorageSync(DISCOUNT_SELECTED_STORAGE_KEY)
|
|
|
+ if (sel !== undefined && sel !== null && sel !== '') {
|
|
|
+ const z = Number(sel)
|
|
|
+ if (!isNaN(z) && z > 0 && z <= 10) this.discountZhe = parseFloat(z.toFixed(2))
|
|
|
+ }
|
|
|
+ this.normalizeDiscountSelectionToCustomOrNone()
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('[calculator] loadDiscountFromStorage', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ normalizeDiscountSelectionToCustomOrNone() {
|
|
|
+ const z = Number(this.discountZhe)
|
|
|
+ if (isNaN(z)) {
|
|
|
+ this.discountZhe = 10
|
|
|
+ this.persistDiscountSelected()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (z >= 10 - 1e-6) {
|
|
|
+ this.discountZhe = 10
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const ok = this.customDiscountZheList.some((x) => Math.abs(Number(x) - z) < 1e-4)
|
|
|
+ if (!ok) {
|
|
|
+ this.discountZhe = 10
|
|
|
+ this.persistDiscountSelected()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ persistDiscountSelected() {
|
|
|
+ try {
|
|
|
+ uni.setStorageSync(DISCOUNT_SELECTED_STORAGE_KEY, String(this.discountZhe))
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('[calculator] persistDiscountSelected', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ persistCustomDiscountList() {
|
|
|
+ try {
|
|
|
+ uni.setStorageSync(DISCOUNT_CUSTOM_STORAGE_KEY, JSON.stringify(this.customDiscountZheList))
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('[calculator] persistCustomDiscountList', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ resetActiveDiscountToNone() {
|
|
|
+ this.discountZhe = 10
|
|
|
+ this.persistDiscountSelected()
|
|
|
+ },
|
|
|
+ formatDiscountZheLabel(z) {
|
|
|
+ const n = Number(z)
|
|
|
+ if (isNaN(n) || n >= 10 - 1e-6) return ''
|
|
|
+ return parseFloat(n.toFixed(2)) + '折'
|
|
|
+ },
|
|
|
+ formatDiscountRowLabel(z) {
|
|
|
+ const n = Number(z)
|
|
|
+ if (isNaN(n)) return '—'
|
|
|
+ return parseFloat(n.toFixed(2)) + '折'
|
|
|
+ },
|
|
|
+ isZheActive(z) {
|
|
|
+ const a = Number(this.discountZhe)
|
|
|
+ const b = Number(z)
|
|
|
+ if (isNaN(a) || isNaN(b)) return false
|
|
|
+ return Math.abs(a - b) < 1e-4
|
|
|
+ },
|
|
|
+ openDiscountPopup() {
|
|
|
+ this.tapVoice()
|
|
|
+ this.$refs.discountPopup && this.$refs.discountPopup.open()
|
|
|
+ },
|
|
|
+ onDiscountPopupCloseTap() {
|
|
|
+ this.tapVoice()
|
|
|
+ this.closeDiscountPopup()
|
|
|
+ },
|
|
|
+ closeDiscountPopup() {
|
|
|
+ this._discountPopupCloseByApi = true
|
|
|
+ this.$refs.discountPopup && this.$refs.discountPopup.close()
|
|
|
+ },
|
|
|
+ onDiscountPopupChange(e) {
|
|
|
+ if (!e || e.show !== false) return
|
|
|
+ if (this._discountPopupCloseByApi) {
|
|
|
+ this._discountPopupCloseByApi = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.tapVoice()
|
|
|
+ },
|
|
|
+ onDiscountRowPickTap(e) {
|
|
|
+ const ds = e.currentTarget.dataset || {}
|
|
|
+ const z = Number(ds.zhe)
|
|
|
+ if (isNaN(z)) return
|
|
|
+ this.pickDiscountZhe(z)
|
|
|
+ },
|
|
|
+ onCustomDiscountDeleteTap(e) {
|
|
|
+ const ds = e.currentTarget.dataset || {}
|
|
|
+ const idx = Number(ds.cidx)
|
|
|
+ if (isNaN(idx)) return
|
|
|
+ this.removeCustomDiscountByIndex(idx)
|
|
|
+ },
|
|
|
+ pickDiscountZhe(z) {
|
|
|
+ this.tapVoice()
|
|
|
+ const n = Number(z)
|
|
|
+ if (isNaN(n) || n <= 0 || n > 10) {
|
|
|
+ this.$msg('折扣无效')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (n < 10 - 1e-6) {
|
|
|
+ const ok = this.customDiscountZheList.some((x) => Math.abs(Number(x) - n) < 1e-4)
|
|
|
+ if (!ok) {
|
|
|
+ this.$msg('请先从列表选择或添加')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.discountZhe = parseFloat(n.toFixed(2))
|
|
|
+ this.persistDiscountSelected()
|
|
|
+ this.closeDiscountPopup()
|
|
|
+ },
|
|
|
+ addCustomDiscountZhe() {
|
|
|
+ this.tapVoice()
|
|
|
+ const raw = String(this.customZheInput || '').trim()
|
|
|
+ if (!raw) {
|
|
|
+ this.$msg('请输入折扣')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const n = parseFloat(raw)
|
|
|
+ if (isNaN(n) || n <= 0 || n > 10) {
|
|
|
+ this.$msg('请输入 0.1~10(如 8.8)')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const rounded = parseFloat(n.toFixed(2))
|
|
|
+ if (rounded >= 10 - 1e-6) {
|
|
|
+ this.$msg('不打折请选「无折扣」')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.customDiscountZheList.some((x) => Math.abs(x - rounded) < 1e-4)) {
|
|
|
+ this.$msg('已在列表中')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.customDiscountZheList = [rounded].concat(this.customDiscountZheList)
|
|
|
+ this.persistCustomDiscountList()
|
|
|
+ this.customZheInput = ''
|
|
|
+ this.$msg('已添加')
|
|
|
+ },
|
|
|
+ removeCustomDiscountByIndex(cidx) {
|
|
|
+ const idx = Number(cidx)
|
|
|
+ if (isNaN(idx) || idx < 0 || idx >= this.customDiscountZheList.length) return
|
|
|
+ this.tapVoice()
|
|
|
+ const removed = this.customDiscountZheList[idx]
|
|
|
+ this.customDiscountZheList = this.customDiscountZheList.filter((_, i) => i !== idx)
|
|
|
+ this.persistCustomDiscountList()
|
|
|
+ if (this.isZheActive(removed)) {
|
|
|
+ this.discountZhe = 10
|
|
|
+ this.persistDiscountSelected()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ tapVoice() {
|
|
|
+ this.$util.hitRemind()
|
|
|
+ },
|
|
|
+ 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
|
|
|
+ },
|
|
|
+ /** 金额展示:最多两位小数,不保留小数末尾多余的 0 */
|
|
|
+ formatMoney(n) {
|
|
|
+ const x = Number(n)
|
|
|
+ if (isNaN(x)) return '0'
|
|
|
+ return parseFloat(x.toFixed(2)).toString()
|
|
|
+ },
|
|
|
+ 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.tapVoice()
|
|
|
+ this.keyboardTarget = 'calc'
|
|
|
+ this.checkType = 3
|
|
|
+ },
|
|
|
+ checkCurrent(n) {
|
|
|
+ this.tapVoice()
|
|
|
+ this.checkType = n
|
|
|
+ this.keyboardTarget = n === 1 ? 'order' : 'cash'
|
|
|
+ },
|
|
|
+ onHasPayChipTap(e) {
|
|
|
+ const ds = e.currentTarget.dataset || {}
|
|
|
+ const val = ds.hpid !== undefined ? Number(ds.hpid) : NaN
|
|
|
+ if (isNaN(val)) return
|
|
|
+ this.applySelHasPay(val)
|
|
|
+ },
|
|
|
+ onPayWayChipTap(e) {
|
|
|
+ const ds = e.currentTarget.dataset || {}
|
|
|
+ const val = ds.pwid !== undefined ? Number(ds.pwid) : NaN
|
|
|
+ if (isNaN(val)) return
|
|
|
+ this.applySelPayWay(val)
|
|
|
+ },
|
|
|
+ applySelHasPay(val) {
|
|
|
+ this.tapVoice()
|
|
|
+ this.selHasPay = val
|
|
|
+ this.cash = 0
|
|
|
+ if (Number(val) !== 1) this.selPayWay = 0
|
|
|
+ this.blurCashIfNoColumn()
|
|
|
+ },
|
|
|
+ applySelPayWay(val) {
|
|
|
+ this.tapVoice()
|
|
|
+ this.selPayWay = val
|
|
|
+ if (Number(val) !== 4) {
|
|
|
+ this.cash = 0
|
|
|
+ } else {
|
|
|
+ this.checkCurrent(2)
|
|
|
+ }
|
|
|
+ this.blurCashIfNoColumn()
|
|
|
+ },
|
|
|
+ blurCashIfNoColumn() {
|
|
|
+ if (this.keyboardTarget === 'cash' && !this.showCashPayCols) this.focusCalc()
|
|
|
+ },
|
|
|
+ openCalcHistory() {
|
|
|
+ this.tapVoice()
|
|
|
+ this.$refs.calcHistoryPopup && this.$refs.calcHistoryPopup.open()
|
|
|
+ },
|
|
|
+ onHistoryPopupCloseTap() {
|
|
|
+ this.tapVoice()
|
|
|
+ this.closeCalcHistory()
|
|
|
+ },
|
|
|
+ closeCalcHistory() {
|
|
|
+ this._historyPopupCloseByApi = true
|
|
|
+ this.$refs.calcHistoryPopup && this.$refs.calcHistoryPopup.close()
|
|
|
+ },
|
|
|
+ onCalcHistoryPopupChange(e) {
|
|
|
+ if (!e || e.show !== false) return
|
|
|
+ if (this._historyPopupCloseByApi) {
|
|
|
+ this._historyPopupCloseByApi = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.tapVoice()
|
|
|
+ },
|
|
|
+ onHistoryRowTap(e) {
|
|
|
+ this.tapVoice()
|
|
|
+ const idx = Number(e.currentTarget.dataset.idx)
|
|
|
+ if (isNaN(idx) || idx < 0) return
|
|
|
+ const row = this.calcHistory[idx]
|
|
|
+ this.applyHistoryRow(row)
|
|
|
+ this.closeCalcHistory()
|
|
|
+ },
|
|
|
+ onHistoryDeleteTap(e) {
|
|
|
+ this.tapVoice()
|
|
|
+ const idx = Number(e.currentTarget.dataset.idx)
|
|
|
+ if (isNaN(idx) || idx < 0 || idx >= this.calcHistory.length) return
|
|
|
+ this.calcHistory.splice(idx, 1)
|
|
|
+ this.persistCalcHistoryToStorage()
|
|
|
+ },
|
|
|
+ pushHistorySnapshot(total, opts) {
|
|
|
+ opts = opts || {}
|
|
|
+ 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 hasCalc =
|
|
|
+ this.tokens.length > 0 ||
|
|
|
+ (this.calcInput != null && !this.$util.isEmpty(this.calcInput))
|
|
|
+ const orderOnly =
|
|
|
+ opts.orderOnly !== undefined ? !!opts.orderOnly : !hasCalc
|
|
|
+ const expr = this.getExprDisplayString(true)
|
|
|
+ let detailText = orderOnly
|
|
|
+ ? '订单 ' + this.formatMoney(total)
|
|
|
+ : expr || '订单 ' + this.formatMoney(total)
|
|
|
+ if (!orderOnly && hasCalc && this.isDiscountActive && expr) {
|
|
|
+ detailText =
|
|
|
+ expr +
|
|
|
+ '(原价' +
|
|
|
+ this.formatMoney(this.stackTotal) +
|
|
|
+ ' ' +
|
|
|
+ this.formatDiscountZheLabel(this.discountZhe) +
|
|
|
+ '→' +
|
|
|
+ this.formatMoney(total) +
|
|
|
+ ')'
|
|
|
+ }
|
|
|
+ const row = this.normalizeHistoryRow(
|
|
|
+ {
|
|
|
+ id: Date.now() + '-' + Math.random().toString(16).slice(2, 10),
|
|
|
+ tokens: this.cloneTokens(this.tokens),
|
|
|
+ calcInput: this.calcInput != null ? String(this.calcInput) : '',
|
|
|
+ total,
|
|
|
+ timeLabel,
|
|
|
+ detailText,
|
|
|
+ cashSnapshot: Number(this.cash) || 0,
|
|
|
+ priceSnapshot: Number(this.price) || 0,
|
|
|
+ orderOnly,
|
|
|
+ source: opts.source === 'stash' ? 'stash' : 'submit',
|
|
|
+ discountZhe: parseFloat(Number(this.discountZhe).toFixed(2)),
|
|
|
+ },
|
|
|
+ 0
|
|
|
+ )
|
|
|
+ this.calcHistory.unshift(row)
|
|
|
+ if (this.calcHistory.length > this.maxHistory) this.calcHistory.pop()
|
|
|
+ this.persistCalcHistoryToStorage()
|
|
|
+ },
|
|
|
+ stashCurrent() {
|
|
|
+ this.tapVoice()
|
|
|
+ const hasCalc =
|
|
|
+ this.tokens.length > 0 ||
|
|
|
+ (this.calcInput != null && !this.$util.isEmpty(this.calcInput))
|
|
|
+ const st = this.stackTotal
|
|
|
+ const orderP = Number(this.price)
|
|
|
+ let total = null
|
|
|
+ let orderOnly = false
|
|
|
+ if (hasCalc) {
|
|
|
+ if (!Number.isFinite(st) || isNaN(st) || st <= 0) {
|
|
|
+ this.$msg('请先完成有效算式')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const payable = this.stackDiscountedAmount
|
|
|
+ if (!Number.isFinite(payable) || isNaN(payable) || payable <= 0) {
|
|
|
+ this.$msg('请先完成有效算式')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ total = payable
|
|
|
+ orderOnly = false
|
|
|
+ } else if (!isNaN(orderP) && orderP > 0) {
|
|
|
+ total = orderP
|
|
|
+ orderOnly = true
|
|
|
+ } else {
|
|
|
+ this.$msg('没有可暂存的内容')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.pushHistorySnapshot(total, { source: 'stash', orderOnly })
|
|
|
+ this.$msg('已暂存')
|
|
|
+ this.clearStack()
|
|
|
+ this.keyboardTarget = 'calc'
|
|
|
+ this.checkType = 3
|
|
|
+ },
|
|
|
+ clearStack() {
|
|
|
+ this.tokens = []
|
|
|
+ this.calcInput = ''
|
|
|
+ this.resetActiveDiscountToNone()
|
|
|
+ },
|
|
|
+ tapClearCalculator() {
|
|
|
+ this.tapVoice()
|
|
|
+ this.clearStack()
|
|
|
+ this.keyboardTarget = 'calc'
|
|
|
+ this.checkType = 3
|
|
|
+ this.price = 0
|
|
|
+ },
|
|
|
+ restoreHistoryDiscountFromRow(row) {
|
|
|
+ let z = 10
|
|
|
+ const raw = row && row.discountZhe
|
|
|
+ if (raw != null && raw !== '' && !isNaN(Number(raw))) {
|
|
|
+ const n = Number(raw)
|
|
|
+ if (n > 0 && n <= 10) z = parseFloat(n.toFixed(2))
|
|
|
+ }
|
|
|
+ if (z >= 10 - 1e-6) {
|
|
|
+ this.discountZhe = 10
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const ok = this.customDiscountZheList.some((x) => Math.abs(Number(x) - z) < 1e-4)
|
|
|
+ this.discountZhe = ok ? z : 10
|
|
|
+ },
|
|
|
+ applyHistoryRow(row) {
|
|
|
+ if (!row) return
|
|
|
+ this.restoreHistoryDiscountFromRow(row)
|
|
|
+ const ci = row.calcInput != null ? String(row.calcInput) : ''
|
|
|
+ const hasTok = row.tokens && row.tokens.length > 0
|
|
|
+ const hasLegacy = row.addends && row.addends.length
|
|
|
+ const restoreCash = () => {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (row.cashSnapshot != null && !isNaN(Number(row.cashSnapshot))) {
|
|
|
+ this.cash = Number(row.cashSnapshot)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (hasLegacy) {
|
|
|
+ this.tokens = this.legacyAddendsToTokens(row.addends)
|
|
|
+ this.calcInput = ci
|
|
|
+ this.keyboardTarget = 'calc'
|
|
|
+ this.checkType = 3
|
|
|
+ restoreCash()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (hasTok) {
|
|
|
+ this.tokens = this.cloneTokens(row.tokens)
|
|
|
+ this.calcInput = ci
|
|
|
+ this.keyboardTarget = 'calc'
|
|
|
+ this.checkType = 3
|
|
|
+ restoreCash()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!this.$util.isEmpty(ci)) {
|
|
|
+ this.tokens = []
|
|
|
+ this.calcInput = ci
|
|
|
+ this.keyboardTarget = 'calc'
|
|
|
+ this.checkType = 3
|
|
|
+ restoreCash()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const orderAmt =
|
|
|
+ row.priceSnapshot != null &&
|
|
|
+ !isNaN(Number(row.priceSnapshot)) &&
|
|
|
+ Number(row.priceSnapshot) > 0
|
|
|
+ ? Number(row.priceSnapshot)
|
|
|
+ : Number(row.total)
|
|
|
+ if (!isNaN(orderAmt) && orderAmt > 0) {
|
|
|
+ this.tokens = []
|
|
|
+ this.calcInput = ''
|
|
|
+ this.price = orderAmt
|
|
|
+ this.cash =
|
|
|
+ row.cashSnapshot != null && !isNaN(Number(row.cashSnapshot))
|
|
|
+ ? Number(row.cashSnapshot)
|
|
|
+ : 0
|
|
|
+ this.keyboardTarget = 'order'
|
|
|
+ this.checkType = 1
|
|
|
+ return
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onNumpadTap(e) {
|
|
|
+ this.tapVoice()
|
|
|
+ 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 === '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 (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 (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.showCashPayCols) return
|
|
|
+ if (this.checkType === 2) this.cash = ''
|
|
|
+ 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 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 (field === 'cash') {
|
|
|
+ if (!this.showCashPayCols) return
|
|
|
+ if (this.$util.isEmpty(this.cash) || this.checkType === 2) this.cash = ch
|
|
|
+ else {
|
|
|
+ if (ch === '.' && String(this.cash).indexOf('.') >= 0) return
|
|
|
+ this.cash += ch
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.checkType = 0
|
|
|
+ },
|
|
|
+ submitCashierPay() {
|
|
|
+ this._submitCashierPayCore()
|
|
|
+ },
|
|
|
+ _resolveXjPayload() {
|
|
|
+ try {
|
|
|
+ if (Number(this.customId) > 0) {
|
|
|
+ return uni.getStorageSync('xj' + this.customId)
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
+ try {
|
|
|
+ return uni.getStorageSync('xj')
|
|
|
+ } catch (e2) {}
|
|
|
+ return null
|
|
|
+ },
|
|
|
+ _submitCashierPayCore() {
|
|
|
+ this.tapVoice()
|
|
|
+ if (this.keyboardTarget === 'calc') {
|
|
|
+ const st = this.stackTotal
|
|
|
+ if (!Number.isFinite(st) || isNaN(st)) {
|
|
|
+ this.$msg('算式无效')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.price = this.stackDiscountedAmount
|
|
|
+ }
|
|
|
+ const p = Number(this.price)
|
|
|
+ if (isNaN(p) || p <= 0) {
|
|
|
+ this.$msg('金额须大于 0')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.selHasPay === 2 && (!this.customId || Number(this.customId) <= 0)) {
|
|
|
+ this.$msg('请先选择客户')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.showCashPayCols) {
|
|
|
+ const cx = Number(this.cash) || 0
|
|
|
+ if (cx <= 0) {
|
|
|
+ this.$msg('请输入收到的现金')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (Number(this.zl) < 0) {
|
|
|
+ this.$msg('现金不足')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const hasCalc =
|
|
|
+ this.tokens.length > 0 ||
|
|
|
+ (this.calcInput != null && !this.$util.isEmpty(this.calcInput))
|
|
|
+ const cashSubmit = this.showCashPayCols ? (Number(this.cash) || 0) : 0
|
|
|
+ const emitHasPay = this.selHasPay
|
|
|
+ const emitPayWay = this.selHasPay === 1 ? this.selPayWay : 0
|
|
|
+ this.pushHistorySnapshot(p, {
|
|
|
+ source: 'submit',
|
|
|
+ orderOnly: !hasCalc,
|
|
|
+ })
|
|
|
+ const orderName = (this.name != null ? String(this.name) : '').trim()
|
|
|
+ const formPayload = {
|
|
|
+ ...SETTLE_FORM_DEFAULTS,
|
|
|
+ hasPay: emitHasPay,
|
|
|
+ payWay: emitPayWay,
|
|
|
+ modifyPrice: p,
|
|
|
+ cash: cashSubmit,
|
|
|
+ orderName,
|
|
|
+ gatheringPrice: p,
|
|
|
+ }
|
|
|
+ const xjRaw = this._resolveXjPayload()
|
|
|
+ const payload = {
|
|
|
+ ...formPayload,
|
|
|
+ isCashier: 1,
|
|
|
+ customId: Number(this.customId) || 0,
|
|
|
+ xj: JSON.stringify(xjRaw),
|
|
|
+ version: 3,
|
|
|
+ zjGathering: 1,
|
|
|
+ dealPrice: 1,
|
|
|
+ getGoods: 1,
|
|
|
+ product: '',
|
|
|
+ payWay: Number(formPayload.hasPay) === 1 ? (formPayload.payWay != null ? formPayload.payWay : 0) : 0,
|
|
|
+ }
|
|
|
+ uni.showLoading({ mask: true })
|
|
|
+ createOrder(payload)
|
|
|
+ .then((res) => {
|
|
|
+ uni.hideLoading()
|
|
|
+ if (res.code != 1) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (res.data && res.data.diff && !this.$util.isEmpty(res.data.diff)) {
|
|
|
+ this.$msg('存在价差条目,请到开单页处理')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let navigatedPay = false
|
|
|
+ if (res.data && res.data.status == 1) {
|
|
|
+ const id = res.data.id
|
|
|
+ const orderSn = res.data.orderSn
|
|
|
+ const actPrice = res.data.actPrice != null ? res.data.actPrice : p
|
|
|
+ if (formPayload.hasPay == 0) {
|
|
|
+ navigatedPay = true
|
|
|
+ this.$util.pageTo({
|
|
|
+ url: '/admin/billing/toPay',
|
|
|
+ type: 2,
|
|
|
+ query: { orderId: id, orderSn: orderSn, actPrice: actPrice },
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ navigatedPay = true
|
|
|
+ this.$util.pageTo({
|
|
|
+ url: '/admin/billing/result',
|
|
|
+ type: 2,
|
|
|
+ query: { orderId: id, orderSn: orderSn, title: '收款成功' },
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else if (!navigatedPay) {
|
|
|
+ uni.showToast({ title: '操作成功', icon: 'success' })
|
|
|
+ }
|
|
|
+ this.price = 0
|
|
|
+ this.checkType = 3
|
|
|
+ this.keyboardTarget = 'calc'
|
|
|
+ this.cash = 0
|
|
|
+ this.selHasPay = 0
|
|
|
+ this.selPayWay = 0
|
|
|
+ this.tokens = []
|
|
|
+ this.calcInput = ''
|
|
|
+ this.resetActiveDiscountToNone()
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ uni.hideLoading()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ },
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.calc-page {
|
|
|
+ height: 100%;
|
|
|
+ background: #f4f6f8;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* 上 4/7 内容区,下 3/7 数字键区(随屏高同比伸缩) */
|
|
|
+.calc-scroll {
|
|
|
+ flex: 4;
|
|
|
+ flex-basis: 0;
|
|
|
+ min-height: 0;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-sheet {
|
|
|
+ padding: 20upx 24upx 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-card-wrap {
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-section {
|
|
|
+ position: relative;
|
|
|
+ border: 2upx solid #e5e9ef;
|
|
|
+ border-radius: 16upx;
|
|
|
+ padding: 20upx 20upx 24upx;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 4upx 20upx rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.calc-section-active {
|
|
|
+ border-color: #09c567;
|
|
|
+ background: linear-gradient(180deg, #f6fff9 0%, #fff 52%);
|
|
|
+}
|
|
|
+
|
|
|
+.calc-actions-top {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 16upx;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-section-title {
|
|
|
+ font-size: 28upx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1a1a1a;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-entry {
|
|
|
+ width: 64upx;
|
|
|
+ height: 64upx;
|
|
|
+ border-radius: 12upx;
|
|
|
+ background: rgba(255, 251, 245, 0.98);
|
|
|
+ border: 2upx solid #f0dcc8;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-entry-hover {
|
|
|
+ opacity: 0.82;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-entry-glyph {
|
|
|
+ font-size: 26upx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #d97706;
|
|
|
+}
|
|
|
+
|
|
|
+.history-entry {
|
|
|
+ width: 64upx;
|
|
|
+ height: 64upx;
|
|
|
+ border-radius: 12upx;
|
|
|
+ background: rgba(248, 255, 248, 0.95);
|
|
|
+ border: 2upx solid #c8ecd8;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.history-entry-hover {
|
|
|
+ opacity: 0.82;
|
|
|
+}
|
|
|
+
|
|
|
+.history-entry-icon {
|
|
|
+ font-size: 34upx;
|
|
|
+ color: #09c567;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-formula {
|
|
|
+ min-height: 88upx;
|
|
|
+ font-size: 38upx;
|
|
|
+ color: #333;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 1.45;
|
|
|
+ word-break: break-all;
|
|
|
+ padding: 0 8upx;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-formula-inner {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-placeholder {
|
|
|
+ font-size: 32upx;
|
|
|
+ color: #9aa4b2;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-num {
|
|
|
+ font-size: 42upx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1a1a1a;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-op {
|
|
|
+ font-size: 42upx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #09c567;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-total-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: baseline;
|
|
|
+ justify-content: center;
|
|
|
+ margin-top: 20upx;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-total-label {
|
|
|
+ font-size: 26upx;
|
|
|
+ color: #9aa4b2;
|
|
|
+ margin-right: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-total-val {
|
|
|
+ font-size: 44upx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #09c567;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-discount-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-top: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-discount-label {
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #d97706;
|
|
|
+ margin-right: 10upx;
|
|
|
+}
|
|
|
+
|
|
|
+.calc-discount-val {
|
|
|
+ font-size: 34upx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #d97706;
|
|
|
+}
|
|
|
+
|
|
|
+.customer-strip {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 14upx;
|
|
|
+ padding: 32upx 28upx;
|
|
|
+ min-height: 94upx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ margin-top: 12upx;
|
|
|
+ border: 2upx solid #e5e9ef;
|
|
|
+}
|
|
|
+
|
|
|
+.customer-strip-label {
|
|
|
+ font-size: 34upx;
|
|
|
+ color: #9aa4b2;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.customer-strip-name {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ font-size: 34upx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1a1a1a;
|
|
|
+ margin-left: 16upx;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.customer-strip-action {
|
|
|
+ font-size: 32upx;
|
|
|
+ color: #09c567;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-left: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.rcv-pay-block {
|
|
|
+ margin-top: 12upx;
|
|
|
+ padding: 22upx 20upx;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 14upx;
|
|
|
+ border: 2upx solid #e8edf3;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.rcv-pay-title {
|
|
|
+ font-size: 30upx;
|
|
|
+ color: #5c6573;
|
|
|
+ text-align: left;
|
|
|
+ margin-bottom: 12upx;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.rcv-pay-title-sub {
|
|
|
+ margin-top: 16upx;
|
|
|
+}
|
|
|
+
|
|
|
+.rcv-sub-block {
|
|
|
+ margin-top: 4upx;
|
|
|
+}
|
|
|
+
|
|
|
+.rcv-item-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.rcv-item-chip {
|
|
|
+ min-width: 140upx;
|
|
|
+ padding: 0 20upx;
|
|
|
+ height: 68upx;
|
|
|
+ line-height: 68upx;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 28upx;
|
|
|
+ border: 2upx solid #e8edf3;
|
|
|
+ color: #3d4654;
|
|
|
+ border-radius: 10upx;
|
|
|
+ background: #fafbfc;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.rcv-item-chip {
|
|
|
+ margin-right: 18upx;
|
|
|
+ margin-bottom: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.rcv-item-chip.active {
|
|
|
+ background: #09c567;
|
|
|
+ color: #fff;
|
|
|
+ border-color: #09c567;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-cash-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: stretch;
|
|
|
+ margin-top: 16upx;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-cash-item {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ background: #fff;
|
|
|
+ border: 2upx solid #e8edf3;
|
|
|
+ border-radius: 14upx;
|
|
|
+ padding: 22upx 16upx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-cash-item.pay-cash-item-change {
|
|
|
+ border-style: dashed;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-cash-item.pay-cash-item-change {
|
|
|
+ margin-left: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-cash-desc {
|
|
|
+ display: block;
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #9aa4b2;
|
|
|
+ margin-bottom: 10upx;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-cash-amt {
|
|
|
+ font-size: 32upx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1a1a1a;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-cash-amt-selected {
|
|
|
+ color: #fff;
|
|
|
+ background: #09c567;
|
|
|
+ padding: 8upx 16upx;
|
|
|
+ border-radius: 10upx;
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-change-line {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 8upx;
|
|
|
+}
|
|
|
+
|
|
|
+.lbl-ok {
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #09c567;
|
|
|
+}
|
|
|
+.lbl-bad {
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #e54545;
|
|
|
+}
|
|
|
+.lbl-neu {
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #c5ccd6;
|
|
|
+}
|
|
|
+
|
|
|
+.amt-ok {
|
|
|
+ font-size: 34upx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #09c567;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.amt-bad {
|
|
|
+ font-size: 34upx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #e54545;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.amt-neu {
|
|
|
+ font-size: 30upx;
|
|
|
+ color: #c5ccd6;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-submit-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ margin-top: 20upx;
|
|
|
+ margin-bottom: 8upx;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-submit-btn {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 30upx;
|
|
|
+ padding: 30upx 22upx;
|
|
|
+ background: #09c567;
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 12upx;
|
|
|
+ font-weight: 600;
|
|
|
+ box-sizing: border-box;
|
|
|
+ border: 2upx solid #09c567;
|
|
|
+}
|
|
|
+
|
|
|
+.pay-submit-btn-outline {
|
|
|
+ flex: 1;
|
|
|
+ background: #fff;
|
|
|
+ color: #09c567;
|
|
|
+ border: 2upx solid #09c567;
|
|
|
+ font-weight: 600;
|
|
|
+ border-radius: 12upx;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 30upx;
|
|
|
+ padding: 30upx 22upx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ margin-right: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.safe-bottom-spacer {
|
|
|
+ height: 24upx;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-dock {
|
|
|
+ flex: 3;
|
|
|
+ flex-basis: 0;
|
|
|
+ min-height: 0;
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ box-sizing: border-box;
|
|
|
+ background: linear-gradient(180deg, transparent 0%, #eceef1 22%, #dcdfe4 100%);
|
|
|
+ padding: 12upx 16upx 10upx;
|
|
|
+ box-shadow: 0 -4upx 24upx rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+.safe-area-padding {
|
|
|
+ padding-bottom: calc(10upx + constant(safe-area-inset-bottom));
|
|
|
+ padding-bottom: calc(10upx + env(safe-area-inset-bottom));
|
|
|
+}
|
|
|
+
|
|
|
+.numpad {
|
|
|
+ width: 100%;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background: #e2e5ea;
|
|
|
+ border-radius: 14upx;
|
|
|
+ padding: 10upx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-grid-wrap {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr 1fr 25%;
|
|
|
+ grid-template-rows: repeat(5, minmax(0, 1fr));
|
|
|
+ box-sizing: border-box;
|
|
|
+ align-items: stretch;
|
|
|
+ justify-items: stretch;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 小程序端 grid 子项 margin 常与 100% 宽高组合失效 → 用 cell 的内边距留白,不配 gap,兼容微信与 App */
|
|
|
+.numpad-cell {
|
|
|
+ box-sizing: border-box;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: visible;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+ padding-right: 10upx;
|
|
|
+ padding-bottom: 8upx;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-cell-op {
|
|
|
+ padding-right: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-cell-mul {
|
|
|
+ grid-column: 4;
|
|
|
+ grid-row: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-cell-add {
|
|
|
+ grid-column: 4;
|
|
|
+ grid-row: 2 / span 2;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-cell-sub {
|
|
|
+ grid-column: 4;
|
|
|
+ grid-row: 4;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-cell-div {
|
|
|
+ grid-column: 4;
|
|
|
+ grid-row: 5;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-cell-actions-row {
|
|
|
+ grid-column: 1 / span 3;
|
|
|
+ grid-row: 5;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: stretch;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-grid-wrap .numpad-cell-actions-row > .numpad-key {
|
|
|
+ flex: 1;
|
|
|
+ width: auto;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ align-self: stretch;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-grid-wrap .numpad-cell-actions-row > .numpad-key + .numpad-key {
|
|
|
+ margin-left: 10upx;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-key {
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 46upx;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-left: 0;
|
|
|
+ color: #1a2430;
|
|
|
+ background: linear-gradient(180deg, #fff 0%, #f5f7fa 100%);
|
|
|
+ border-radius: 12upx;
|
|
|
+ box-shadow: 0 3upx 0 #bdc3c9;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-grid-wrap .numpad-cell > .numpad-key {
|
|
|
+ flex: 1;
|
|
|
+ width: 100%;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ align-self: stretch;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-key:active {
|
|
|
+ opacity: 0.88;
|
|
|
+ transform: translateY(1px);
|
|
|
+ box-shadow: 0 1upx 0 #a8afb8;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-grid-wrap .numpad-key.numpad-key-op:not(.numpad-key-op-add) {
|
|
|
+ font-size: 46upx;
|
|
|
+ font-weight: 600;
|
|
|
+ background: linear-gradient(180deg, #fff 0%, #f0f2f5 100%);
|
|
|
+ color: #1a2430;
|
|
|
+ box-shadow: 0 3upx 0 #bdc3c9;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-grid-wrap .numpad-key.numpad-key-op-add {
|
|
|
+ font-size: 48upx;
|
|
|
+ font-weight: 700;
|
|
|
+ background: #52b384;
|
|
|
+ color: #063d2b;
|
|
|
+ box-shadow: 0 4upx 0 #388f65;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-key-muted {
|
|
|
+ color: #4a5666;
|
|
|
+ font-size: 50upx;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-key-text {
|
|
|
+ font-size: 34upx;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-key-action-muted {
|
|
|
+ font-size: 32upx !important;
|
|
|
+ color: #c0392b !important;
|
|
|
+ background: #eac997;
|
|
|
+ box-shadow: 0 3upx 0 #b9c2cf !important;
|
|
|
+}
|
|
|
+
|
|
|
+.numpad-key-danger {
|
|
|
+ background: #eac997;
|
|
|
+ color: black !important;
|
|
|
+ box-shadow: 0 3upx 0 #e0abab !important;
|
|
|
+}
|
|
|
+
|
|
|
+.mob-popup-wrap {
|
|
|
+ width: 86vw;
|
|
|
+ max-width: 640upx;
|
|
|
+ max-height: 70vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ border-radius: 16upx;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.mob-popup-hd {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 24upx 28upx;
|
|
|
+ border-bottom: 2upx solid #eef1f6;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.mob-popup-title {
|
|
|
+ font-size: 32upx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #1a1a1a;
|
|
|
+}
|
|
|
+
|
|
|
+.mob-popup-close {
|
|
|
+ font-size: 28upx;
|
|
|
+ color: #09c567;
|
|
|
+ padding: 8upx 16upx;
|
|
|
+}
|
|
|
+
|
|
|
+.mob-popup-body {
|
|
|
+ max-height: 46vh;
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding: 0 12upx 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-toolbar {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding: 20upx 24upx 22upx;
|
|
|
+ border-bottom: 2upx solid #f0f2f5;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-custom-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-custom-input {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ height: 72upx;
|
|
|
+ line-height: 72upx;
|
|
|
+ padding: 0 20upx;
|
|
|
+ font-size: 28upx;
|
|
|
+ border: 2upx solid #e8edf3;
|
|
|
+ border-radius: 10upx;
|
|
|
+ background: #fff;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-custom-add-btn {
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-left: 14upx;
|
|
|
+ padding: 0 28upx;
|
|
|
+ height: 72upx;
|
|
|
+ line-height: 72upx;
|
|
|
+ font-size: 28upx;
|
|
|
+ color: #fff;
|
|
|
+ background: #09c567;
|
|
|
+ border-radius: 10upx;
|
|
|
+ text-align: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-toolbar-hint {
|
|
|
+ display: block;
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #09c567;
|
|
|
+ line-height: 1.45;
|
|
|
+ padding: 14upx 6upx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-toolbar-hint-muted {
|
|
|
+ color: #9aa4b2;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-options-scroll {
|
|
|
+ padding: 20upx 20upx 28upx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ max-height: 44vh;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-option {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 24upx 20upx;
|
|
|
+ border: 2upx solid #e8edf3;
|
|
|
+ border-radius: 12upx;
|
|
|
+ margin-bottom: 12upx;
|
|
|
+ background: #fafbfc;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-option:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-option-active {
|
|
|
+ border-color: #09c567;
|
|
|
+ background: #f0fff5;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-option-text {
|
|
|
+ font-size: 30upx;
|
|
|
+ color: #1a2430;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-option-check {
|
|
|
+ font-size: 30upx;
|
|
|
+ color: #09c567;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-option-with-del {
|
|
|
+ padding-right: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-option-main {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding-right: 12upx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.discount-option-del {
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 26upx;
|
|
|
+ color: #e54545;
|
|
|
+ padding: 8upx 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.history-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ padding: 18upx 12upx;
|
|
|
+ border-bottom: 2upx solid #f0f3f8;
|
|
|
+}
|
|
|
+
|
|
|
+.history-item-body {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.history-del {
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 26upx;
|
|
|
+ color: #e54545;
|
|
|
+ padding: 10upx 16upx;
|
|
|
+ margin-left: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.history-line {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 8upx;
|
|
|
+}
|
|
|
+
|
|
|
+.history-time {
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #9aa4b2;
|
|
|
+}
|
|
|
+
|
|
|
+.history-total {
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #09c567;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.history-detail {
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #5c6573;
|
|
|
+ word-break: break-all;
|
|
|
+ line-height: 1.35;
|
|
|
+}
|
|
|
+
|
|
|
+.history-empty {
|
|
|
+ text-align: center;
|
|
|
+ font-size: 26upx;
|
|
|
+ color: #c5ccd6;
|
|
|
+ padding: 60upx 0;
|
|
|
+}
|
|
|
+</style>
|