|
|
@@ -0,0 +1,418 @@
|
|
|
+<template>
|
|
|
+ <view class="discount-panel" @tap.stop>
|
|
|
+ <view class="discount-header">
|
|
|
+ <text class="discount-title">选择折扣</text>
|
|
|
+ <view class="header-actions">
|
|
|
+ <view class="edit-btn" :class="{ active: editMode }" @tap.stop="toggleEdit">
|
|
|
+ {{editMode ? '完成' : '编辑'}}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="editMode && showAdd" class="add-box">
|
|
|
+ <view class="picker-row">
|
|
|
+ <picker mode="selector" :range="numberOptions" :value="integerIndex" @change="changeInteger">
|
|
|
+ <view class="picker-value">{{numberOptions[integerIndex]}}</view>
|
|
|
+ </picker>
|
|
|
+ <text class="dot">.</text>
|
|
|
+ <picker mode="selector" :range="numberOptions" :value="decimalIndex" @change="changeDecimal">
|
|
|
+ <view class="picker-value">{{numberOptions[decimalIndex]}}</view>
|
|
|
+ </picker>
|
|
|
+ <text class="discount-unit">折</text>
|
|
|
+ </view>
|
|
|
+ <button class="add-confirm" @tap.stop="addDiscount">添加</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="discount-list">
|
|
|
+ <view
|
|
|
+ v-for="(item, index) in innerList"
|
|
|
+ :key="item.value"
|
|
|
+ class="discount-item"
|
|
|
+ :class="{
|
|
|
+ active: !editMode && Number(selectedValue) === Number(item.value),
|
|
|
+ dragging: draggingIndex === index,
|
|
|
+ 'add-discount-item': editMode && isNoDiscount(item)
|
|
|
+ }"
|
|
|
+ @tap.stop="selectDiscount(item)"
|
|
|
+ >
|
|
|
+ <text class="discount-name">{{editMode && isNoDiscount(item) ? '+' : item.name}}</text>
|
|
|
+ <view v-if="editMode && !isNoDiscount(item)" class="item-actions">
|
|
|
+ <view class="delete-btn" @tap.stop="deleteDiscount(index)">×</view>
|
|
|
+ <view
|
|
|
+ class="drag-handle"
|
|
|
+ @tap.stop
|
|
|
+ @touchstart.stop="startDrag(index, $event)"
|
|
|
+ @touchmove.stop.prevent="moveDrag"
|
|
|
+ @touchend.stop="endDrag"
|
|
|
+ @touchcancel.stop="endDrag"
|
|
|
+ >
|
|
|
+ <text></text>
|
|
|
+ <text></text>
|
|
|
+ <text></text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="editMode" class="tips">点击x按钮删除,按住☰按钮拖动排序</view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+const DEFAULT_LIST = [
|
|
|
+ { name: '7折', value: 0.7 },
|
|
|
+ { name: '8折', value: 0.8 },
|
|
|
+ { name: '9折', value: 0.9 },
|
|
|
+ { name: '不打折', value: 1 },
|
|
|
+]
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'DiscountPanel',
|
|
|
+ props: {
|
|
|
+ value: {
|
|
|
+ type: Array,
|
|
|
+ default: () => DEFAULT_LIST
|
|
|
+ },
|
|
|
+ selectedValue: {
|
|
|
+ type: [Number, String],
|
|
|
+ default: 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ innerList: [],
|
|
|
+ numberOptions: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
|
|
+ integerIndex: 8,
|
|
|
+ decimalIndex: 5,
|
|
|
+ showAdd: false,
|
|
|
+ editMode: false,
|
|
|
+ draggingIndex: -1,
|
|
|
+ itemRects: []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.initDiscountList()
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ value: {
|
|
|
+ handler(list) {
|
|
|
+ if (Array.isArray(this.innerList) && this.innerList.length > 0) return
|
|
|
+ this.innerList = this.normalizeList(list)
|
|
|
+ },
|
|
|
+ immediate: true,
|
|
|
+ deep: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ normalizeList(list) {
|
|
|
+ const source = Array.isArray(list) && list.length > 0 ? list : DEFAULT_LIST
|
|
|
+ return this.keepNoDiscountLast(source.map(item => ({
|
|
|
+ name: item.name,
|
|
|
+ value: Number(item.value)
|
|
|
+ })))
|
|
|
+ },
|
|
|
+ keepNoDiscountLast(list) {
|
|
|
+ const noDiscount = list.find(item => this.isNoDiscount(item)) || { name: '不打折', value: 1 }
|
|
|
+ return list.filter(item => !this.isNoDiscount(item)).concat([noDiscount])
|
|
|
+ },
|
|
|
+ isNoDiscount(item) {
|
|
|
+ return item && Number(item.value) === 1
|
|
|
+ },
|
|
|
+ initDiscountList() {
|
|
|
+ let cacheList = []
|
|
|
+ if (this.$store && this.$store.getters) {
|
|
|
+ cacheList = this.$store.getters.getDiscountList
|
|
|
+ }
|
|
|
+ const nextList = this.normalizeList(Array.isArray(cacheList) && cacheList.length > 0 ? cacheList : DEFAULT_LIST)
|
|
|
+ this.innerList = nextList
|
|
|
+ this.$emit('input', nextList.slice())
|
|
|
+ this.$emit('change-list', nextList.slice())
|
|
|
+ },
|
|
|
+ emitList() {
|
|
|
+ this.innerList = this.keepNoDiscountLast(this.innerList)
|
|
|
+ if (this.$store && this.$store.dispatch) {
|
|
|
+ this.$store.dispatch('setDiscountListCache', this.innerList.slice())
|
|
|
+ }
|
|
|
+ this.$emit('input', this.innerList.slice())
|
|
|
+ this.$emit('change-list', this.innerList.slice())
|
|
|
+ },
|
|
|
+ toggleEdit() {
|
|
|
+ this.editMode = !this.editMode
|
|
|
+ if (!this.editMode) {
|
|
|
+ this.showAdd = false
|
|
|
+ this.endDrag()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ changeInteger(e) {
|
|
|
+ this.integerIndex = Number(e.detail.value)
|
|
|
+ },
|
|
|
+ changeDecimal(e) {
|
|
|
+ this.decimalIndex = Number(e.detail.value)
|
|
|
+ },
|
|
|
+ addDiscount() {
|
|
|
+ const integer = Number(this.numberOptions[this.integerIndex])
|
|
|
+ const decimal = Number(this.numberOptions[this.decimalIndex])
|
|
|
+ const value = Number(((integer * 10 + decimal) / 100).toFixed(2))
|
|
|
+ if (value <= 0) {
|
|
|
+ uni.showToast({ title: '折扣不能为0', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.innerList.some(item => Number(item.value) === value)) {
|
|
|
+ uni.showToast({ title: '该折扣已存在', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const name = decimal === 0 ? `${integer}折` : `${integer}.${decimal}折`
|
|
|
+ const noDiscountIndex = this.innerList.findIndex(item => this.isNoDiscount(item))
|
|
|
+ if (noDiscountIndex === -1) {
|
|
|
+ this.innerList.push({ name, value }, { name: '不打折', value: 1 })
|
|
|
+ } else {
|
|
|
+ this.innerList.splice(noDiscountIndex, 0, { name, value })
|
|
|
+ }
|
|
|
+ this.showAdd = false
|
|
|
+ this.emitList()
|
|
|
+ },
|
|
|
+ deleteDiscount(index) {
|
|
|
+ if (this.isNoDiscount(this.innerList[index])) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.innerList.length <= 1) {
|
|
|
+ uni.showToast({ title: '至少保留一个折扣', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ uni.showModal({
|
|
|
+ title: '删除折扣',
|
|
|
+ content: `确定删除${this.innerList[index].name}吗?`,
|
|
|
+ success: (res) => {
|
|
|
+ if (!res.confirm) return
|
|
|
+ const deleted = this.innerList.splice(index, 1)[0]
|
|
|
+ this.emitList()
|
|
|
+ if (deleted && Number(deleted.value) === Number(this.selectedValue)) {
|
|
|
+ this.$emit('select', this.innerList[0].value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ selectDiscount(item) {
|
|
|
+ if (this.draggingIndex !== -1) return
|
|
|
+ if (this.editMode && this.isNoDiscount(item)) {
|
|
|
+ this.showAdd = !this.showAdd
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.$emit('select', item.value)
|
|
|
+ },
|
|
|
+ startDrag(index) {
|
|
|
+ if (!this.editMode || this.isNoDiscount(this.innerList[index])) return
|
|
|
+ this.draggingIndex = index
|
|
|
+ this.queryItemRects()
|
|
|
+ },
|
|
|
+ moveDrag(e) {
|
|
|
+ if (this.draggingIndex === -1) return
|
|
|
+ const touch = e.touches && e.touches[0]
|
|
|
+ if (!touch || this.itemRects.length === 0) return
|
|
|
+ const targetIndex = this.itemRects.findIndex(rect => (
|
|
|
+ touch.clientX >= rect.left &&
|
|
|
+ touch.clientX <= rect.right &&
|
|
|
+ touch.clientY >= rect.top &&
|
|
|
+ touch.clientY <= rect.bottom
|
|
|
+ ))
|
|
|
+ if (
|
|
|
+ targetIndex === -1 ||
|
|
|
+ targetIndex === this.draggingIndex ||
|
|
|
+ this.isNoDiscount(this.innerList[targetIndex])
|
|
|
+ ) return
|
|
|
+ const current = this.innerList.splice(this.draggingIndex, 1)[0]
|
|
|
+ this.innerList.splice(targetIndex, 0, current)
|
|
|
+ this.innerList = this.keepNoDiscountLast(this.innerList)
|
|
|
+ this.draggingIndex = targetIndex
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.queryItemRects()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ endDrag() {
|
|
|
+ if (this.draggingIndex !== -1) {
|
|
|
+ this.emitList()
|
|
|
+ }
|
|
|
+ this.draggingIndex = -1
|
|
|
+ this.itemRects = []
|
|
|
+ },
|
|
|
+ queryItemRects() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ uni.createSelectorQuery()
|
|
|
+ .in(this)
|
|
|
+ .selectAll('.discount-item')
|
|
|
+ .boundingClientRect((rects) => {
|
|
|
+ this.itemRects = Array.isArray(rects) ? rects : []
|
|
|
+ })
|
|
|
+ .exec()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.discount-panel {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 190upx;
|
|
|
+ padding: 12upx;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border: 1upx solid #eeeeee;
|
|
|
+ border-radius: 6upx;
|
|
|
+ z-index: 99999999;
|
|
|
+ box-shadow: 0 4upx 16upx rgba(0, 0, 0, 0.12);
|
|
|
+}
|
|
|
+.discount-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 10upx;
|
|
|
+}
|
|
|
+.discount-title {
|
|
|
+ font-size: 12upx;
|
|
|
+ color: #333333;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.header-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.edit-btn {
|
|
|
+ min-width: 34upx;
|
|
|
+ height: 24upx;
|
|
|
+ line-height: 24upx;
|
|
|
+ margin-left: 8upx;
|
|
|
+ padding: 0 6upx;
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 12upx;
|
|
|
+ border: 1upx solid #09C567;
|
|
|
+ color: #09C567;
|
|
|
+ font-size: 10upx;
|
|
|
+}
|
|
|
+.edit-btn.active {
|
|
|
+ color: #ffffff;
|
|
|
+ background-color: #09C567;
|
|
|
+}
|
|
|
+.add-box {
|
|
|
+ padding: 8upx;
|
|
|
+ margin-bottom: 10upx;
|
|
|
+ background-color: #f8f8f8;
|
|
|
+ border-radius: 4upx;
|
|
|
+}
|
|
|
+.picker-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+.picker-value {
|
|
|
+ width: 36upx;
|
|
|
+ height: 28upx;
|
|
|
+ line-height: 28upx;
|
|
|
+ text-align: center;
|
|
|
+ border: 1upx solid #dddddd;
|
|
|
+ border-radius: 4upx;
|
|
|
+ background-color: #ffffff;
|
|
|
+ font-size: 12upx;
|
|
|
+}
|
|
|
+.dot,
|
|
|
+.discount-unit {
|
|
|
+ font-size: 12upx;
|
|
|
+ color: #333333;
|
|
|
+ margin-left: 4upx;
|
|
|
+}
|
|
|
+.add-confirm {
|
|
|
+ height: 28upx;
|
|
|
+ line-height: 28upx;
|
|
|
+ margin-top: 8upx;
|
|
|
+ padding: 0;
|
|
|
+ font-size: 12upx;
|
|
|
+ color: #ffffff;
|
|
|
+ background-color: #09C567;
|
|
|
+ border-radius: 4upx;
|
|
|
+}
|
|
|
+.discount-list {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+.discount-item {
|
|
|
+ width: 48upx;
|
|
|
+ height: 48upx;
|
|
|
+ margin-left: 8upx;
|
|
|
+ margin-bottom: 8upx;
|
|
|
+ border: 1upx solid #999999;
|
|
|
+ border-radius: 24upx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+ background-color: #ffffff;
|
|
|
+}
|
|
|
+.discount-item:nth-child(3n + 1) {
|
|
|
+ margin-left: 0;
|
|
|
+}
|
|
|
+.discount-item.active {
|
|
|
+ border-color: #09C567;
|
|
|
+ color: #09C567;
|
|
|
+}
|
|
|
+.discount-item.dragging {
|
|
|
+ opacity: 0.7;
|
|
|
+ background-color: #f0fff8;
|
|
|
+}
|
|
|
+.discount-item.add-discount-item {
|
|
|
+ border-color: #09C567;
|
|
|
+ color: #09C567;
|
|
|
+ font-size: 18upx;
|
|
|
+}
|
|
|
+.discount-name {
|
|
|
+ font-size: 10upx;
|
|
|
+}
|
|
|
+.add-discount-item .discount-name {
|
|
|
+ font-size: 20upx;
|
|
|
+ line-height: 20upx;
|
|
|
+}
|
|
|
+.item-actions {
|
|
|
+ position: absolute;
|
|
|
+ top: -4upx;
|
|
|
+ right: -12upx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.delete-btn,
|
|
|
+.drag-handle {
|
|
|
+ width: 18upx;
|
|
|
+ height: 18upx;
|
|
|
+ border-radius: 9upx;
|
|
|
+}
|
|
|
+.delete-btn {
|
|
|
+ line-height: 16upx;
|
|
|
+ text-align: center;
|
|
|
+ margin-right: 10upx;
|
|
|
+ background-color: #ff4d4f;
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 14upx;
|
|
|
+}
|
|
|
+.drag-handle {
|
|
|
+ background-color: #666666;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-right: 12upx;
|
|
|
+}
|
|
|
+.drag-handle text {
|
|
|
+ width: 8upx;
|
|
|
+ height: 1upx;
|
|
|
+ margin-top: 2upx;
|
|
|
+ background-color: #ffffff;
|
|
|
+}
|
|
|
+.drag-handle text:first-child {
|
|
|
+ margin-top: 0;
|
|
|
+}
|
|
|
+.tips {
|
|
|
+ margin-top: 2upx;
|
|
|
+ font-size: 9upx;
|
|
|
+ color: #999999;
|
|
|
+}
|
|
|
+</style>
|