shish 2 месяцев назад
Родитель
Сommit
b5dfc2a856
1 измененных файлов с 423 добавлено и 6 удалено
  1. 423 6
      hdPad/src/pages/home/components/selectPrice.vue

+ 423 - 6
hdPad/src/pages/home/components/selectPrice.vue

@@ -4,6 +4,13 @@
             <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="discount-entry"
+                            @tap.stop="openDiscountPopup"
+                            hover-class="discount-entry--hover"
+                        >
+                            <text class="discount-entry__glyph">折</text>
+                        </view>
                         <view class="history-entry" @tap.stop="openCalcHistory" hover-class="history-entry--hover">
                             <view class="iconfont iconmingxi1 history-entry__icon"></view>
                         </view>
@@ -26,6 +33,10 @@
                             <text class="calc-total-label">合计</text>
                             <text class="calc-total-val">{{ formatMoney(stackTotal) }}</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="pay-amount-row">
@@ -138,10 +149,59 @@
                 </scroll-view>
             </view>
         </uni-popup>
+        <uni-popup ref="discountPopup" type="center" background-color="#ffffff" @change="onDiscountPopupChange">
+            <view class="calc-history-popup discount-popup">
+                <view class="calc-history-popup-hd">
+                    <text class="calc-history-popup-title">选择折扣</text>
+                    <text class="calc-history-popup-close" @tap.stop="onDiscountPopupCloseTap">关闭</text>
+                </view>
+                <view class="discount-popup-toolbar">
+                    <view class="discount-custom-row">
+                        <input
+                            class="discount-custom-input"
+                            v-model="customZheInput"
+                            type="text"
+                            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 enable-flex="true" class="calc-history-popup-body discount-popup-body-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>
 const CALC_HISTORY_STORAGE_KEY = 'hdPad_selectPrice_calcHistory'
+const DISCOUNT_CUSTOM_STORAGE_KEY = 'hdPad_selectPrice_customDiscountZhe'
+const DISCOUNT_SELECTED_STORAGE_KEY = 'hdPad_selectPrice_discountZhe'
 export default {
     name: 'selectPrice',
     data() {
@@ -158,6 +218,11 @@ export default {
             maxHistory: 20,
             /** 通过代码调用 uni-popup.close 时为 true,避免与遮罩关闭的 @change 重复播点击音 */
             _historyPopupCloseByApi: false,
+            /** 10 = 无折扣,9 = 九折,实付 = 合计 × (zhe/10) */
+            discountZhe: 10,
+            customDiscountZheList: [],
+            customZheInput: '',
+            _discountPopupCloseByApi: false,
         }
     },
     computed: {
@@ -172,6 +237,23 @@ export default {
             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() {
             let amount = 0
             if (Number(this.cash) > 0 && Number(this.price) > 0) {
@@ -182,7 +264,7 @@ export default {
         },
     },
     watch: {
-        stackTotal: {
+        stackDiscountedAmount: {
             handler(v) {
                 if (this.keyboardTarget === 'calc') {
                     const n = parseFloat(Number(v).toFixed(2))
@@ -194,6 +276,7 @@ export default {
     },
     mounted() {
         this.loadCalcHistoryFromStorage()
+        this.loadDiscountFromStorage()
     },
     methods: {
         loadCalcHistoryFromStorage() {
@@ -239,6 +322,181 @@ export default {
                 console.warn('[selectPrice] 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('[selectPrice] loadDiscountFromStorage', e)
+            }
+        },
+        /** 仅允许无折扣(10)或自定义列表中的折数 */
+        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('[selectPrice] persistDiscountSelected', e)
+            }
+        },
+        persistCustomDiscountList() {
+            try {
+                uni.setStorageSync(DISCOUNT_CUSTOM_STORAGE_KEY, JSON.stringify(this.customDiscountZheList))
+            } catch (e) {
+                console.warn('[selectPrice] persistCustomDiscountList', e)
+            }
+        },
+        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()
+        },
+        /** 微信小程序 data-event-opts 对 @tap 带参编译易报错,统一用 dataset 取参 */
+        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()
+            }
+        },
         /** 全页可点区域统一按键音(依赖 $util.hitVoice) */
         tapVoice() {
             this.$util.hitVoice()
@@ -483,9 +741,20 @@ export default {
                     ? !!opts.orderOnly
                     : !hasCalc
             const expr = this.getExprDisplayString(true)
-            const detailText = orderOnly
+            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),
@@ -521,7 +790,12 @@ export default {
                     this.$msg('请先完成有效算式后再暂存')
                     return
                 }
-                total = st
+                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
@@ -702,7 +976,7 @@ export default {
                     this.$msg('算式无效(如除数为 0)')
                     return
                 }
-                this.price = st
+                this.price = this.stackDiscountedAmount
             }
             const p = Number(this.price)
             if (isNaN(p) || p <= 0) {
@@ -795,6 +1069,34 @@ export default {
     .flower-input_box-wrap {
         position: relative;
     }
+    .discount-entry {
+        position: absolute;
+        top: 6upx;
+        left: 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(255, 251, 245, 0.98);
+        border: 1upx solid #f0dcc8;
+        box-sizing: border-box;
+        pointer-events: auto;
+    }
+    .discount-entry--hover {
+        opacity: 0.82;
+    }
+    .discount-entry__glyph {
+        font-size: 17upx;
+        font-weight: 700;
+        color: #d97706;
+        line-height: 1;
+    }
     .history-entry {
         position: absolute;
         top: 6upx;
@@ -970,6 +1272,23 @@ export default {
         font-weight: bold;
         color: #09c567;
     }
+    .calc-discount-row {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: center;
+        margin-top: 6upx;
+    }
+    .calc-discount-label {
+        font-size: 11upx;
+        color: #d97706;
+        margin-right: 8upx;
+    }
+    .calc-discount-val {
+        font-size: 15upx;
+        font-weight: bold;
+        color: #d97706;
+    }
     .calc-btn-row {
         display: flex;
         flex-direction: row;
@@ -1000,8 +1319,8 @@ export default {
         border-color: #09c567;
     }
     .calc-history-popup {
-        width: 52vw;
-        max-width: 400upx;
+        width: 45vw;
+        max-width: 300upx;
         max-height: 82vh;
         display: flex;
         flex-direction: column;
@@ -1076,6 +1395,104 @@ export default {
         color: #cccccc;
         padding: 16upx 0;
     }
+    .discount-popup {
+        width: 52vw;
+        max-width: 400upx;
+    }
+    .discount-popup-toolbar {
+        flex-shrink: 0;
+        padding: 10upx 12upx 12upx;
+        border-bottom: 1upx solid #f0f0f0;
+        box-sizing: border-box;
+    }
+    .discount-custom-row {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+    }
+    .discount-custom-input {
+        flex: 1;
+        min-width: 0;
+        height: 36upx;
+        line-height: 36upx;
+        padding: 0 10upx;
+        font-size: 13upx;
+        border: 1upx solid #dddddd;
+        border-radius: 6upx;
+        background: #ffffff;
+        box-sizing: border-box;
+    }
+    .discount-custom-add-btn {
+        flex-shrink: 0;
+        margin-left: 8upx;
+        padding: 0 14upx;
+        height: 36upx;
+        line-height: 36upx;
+        font-size: 13upx;
+        color: #ffffff;
+        background: #09c567;
+        border-radius: 6upx;
+        text-align: center;
+        box-sizing: border-box;
+    }
+    .discount-toolbar-hint {
+        display: block;
+        font-size: 11upx;
+        color: #09c567;
+        line-height: 1.4;
+        padding: 8upx 4upx 0;
+    }
+    .discount-toolbar-hint--muted {
+        color: #999999;
+    }
+    .discount-popup-body-scroll {
+        box-sizing: border-box;
+        padding: 10upx 12upx 12upx;
+    }
+    .discount-option {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: space-between;
+        padding: 12upx 10upx;
+        border: 1upx solid #eeeeee;
+        border-radius: 6upx;
+        margin-bottom: 8upx;
+        background: #fafafa;
+        box-sizing: border-box;
+    }
+    .discount-option--active {
+        border-color: #09c567;
+        background: #f0fff4;
+    }
+    .discount-option-text {
+        font-size: 14upx;
+        color: #333333;
+    }
+    .discount-option-check {
+        font-size: 14upx;
+        color: #09c567;
+        font-weight: bold;
+    }
+    .discount-option--with-del {
+        padding-right: 6upx;
+    }
+    .discount-option-main {
+        flex: 1;
+        min-width: 0;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: space-between;
+        padding-right: 8upx;
+        box-sizing: border-box;
+    }
+    .discount-option-del {
+        flex-shrink: 0;
+        font-size: 11upx;
+        color: #e54545;
+        padding: 4upx 8upx;
+    }
     .flower-open_box {
         display: flex;
         align-items: center;