lf-pickup-code.vue 5.92 KB
<template>
  <view class="pickup-code-card">
    <view class="pickup-code-head">
      <view>
        <text class="pickup-code-kicker">{{ used ? 'VERIFIED PASS' : 'PICKUP QR PASS' }}</text>
        <text class="pickup-code-title">{{ title }}</text>
      </view>
      <text class="pickup-code-status" :class="{ used }">{{ used ? '已核销' : '待核销' }}</text>
    </view>

    <view class="pickup-code-panel">
      <view class="qr-frame">
        <view class="qr-board">
          <view v-for="(cell, index) in cells" :key="index" class="qr-cell" :class="{ filled: cell }"></view>
          <view class="qr-center-badge">
            <text class="qr-center-text">绿纤</text>
          </view>
        </view>
      </view>
    </view>

    <text class="pickup-code-text">{{ code }}</text>
    <text class="pickup-code-desc">{{ desc }}</text>
  </view>
</template>

<script>
const SIZE = 29
const FINDER_SIZE = 7

const createMatrix = (size, fill = false) => Array.from({ length: size }, () => Array.from({ length: size }, () => fill))

const setFinder = (matrix, reserved, startRow, startCol) => {
  for (let row = 0; row < FINDER_SIZE; row += 1) {
    for (let col = 0; col < FINDER_SIZE; col += 1) {
      const globalRow = startRow + row
      const globalCol = startCol + col
      const outer = row === 0 || row === FINDER_SIZE - 1 || col === 0 || col === FINDER_SIZE - 1
      const inner = row >= 2 && row <= 4 && col >= 2 && col <= 4
      matrix[globalRow][globalCol] = outer || inner
      reserved[globalRow][globalCol] = true
    }
  }
}

const setAlignment = (matrix, reserved, centerRow, centerCol) => {
  for (let row = -2; row <= 2; row += 1) {
    for (let col = -2; col <= 2; col += 1) {
      const globalRow = centerRow + row
      const globalCol = centerCol + col
      if (globalRow < 0 || globalRow >= SIZE || globalCol < 0 || globalCol >= SIZE) {
        continue
      }
      const outer = Math.abs(row) === 2 || Math.abs(col) === 2
      const core = row === 0 && col === 0
      matrix[globalRow][globalCol] = outer || core
      reserved[globalRow][globalCol] = true
    }
  }
}

const createSeed = code => String(code || 'LQ-0000').split('').reduce((sum, ch, index) => sum + ch.charCodeAt(0) * (index + 3), 97)

export default {
  props: {
    code: {
      type: String,
      default: 'LQ-5001-0101'
    },
    title: {
      type: String,
      default: '门店核销二维码'
    },
    desc: {
      type: String,
      default: '到店后出示二维码,门店核销后即可自提。'
    },
    used: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    cells() {
      const matrix = createMatrix(SIZE)
      const reserved = createMatrix(SIZE)

      setFinder(matrix, reserved, 0, 0)
      setFinder(matrix, reserved, 0, SIZE - FINDER_SIZE)
      setFinder(matrix, reserved, SIZE - FINDER_SIZE, 0)
      setAlignment(matrix, reserved, SIZE - 7, SIZE - 7)

      for (let index = 0; index < SIZE; index += 1) {
        if (!reserved[6][index]) {
          matrix[6][index] = index % 2 === 0
          reserved[6][index] = true
        }
        if (!reserved[index][6]) {
          matrix[index][6] = index % 2 === 0
          reserved[index][6] = true
        }
      }

      let seed = createSeed(this.code)
      for (let row = 0; row < SIZE; row += 1) {
        for (let col = 0; col < SIZE; col += 1) {
          if (reserved[row][col]) {
            continue
          }
          seed = (seed * 1103515245 + 12345 + row * 31 + col * 17) & 0x7fffffff
          matrix[row][col] = (seed % 100) < 46
        }
      }

      const centerStart = 11
      const centerEnd = 17
      for (let row = centerStart; row <= centerEnd; row += 1) {
        for (let col = centerStart; col <= centerEnd; col += 1) {
          matrix[row][col] = false
        }
      }

      return matrix.flat()
    }
  }
}
</script>

<style lang="scss" scoped>
@import '@/styles/mixins.scss';

.pickup-code-card {
  @include glass-panel(0.92);
  padding: 32rpx;
  margin-bottom: 24rpx;
}

.pickup-code-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20rpx;
}

.pickup-code-kicker,
.pickup-code-title,
.pickup-code-status,
.pickup-code-text,
.pickup-code-desc {
  display: block;
}

.pickup-code-kicker {
  font-size: 20rpx;
  letter-spacing: 4rpx;
  color: rgba(143, 118, 86, 0.82);
}

.pickup-code-title {
  margin-top: 12rpx;
  font-size: 30rpx;
  font-weight: 700;
  color: $text-primary;
}

.pickup-code-status {
  @include chip-soft;
  white-space: nowrap;
}

.pickup-code-status.used {
  @include chip-rose;
}

.pickup-code-panel {
  display: flex;
  justify-content: center;
  margin-top: 28rpx;
}

.qr-frame {
  padding: 22rpx;
  border-radius: 40rpx;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 247, 0.98));
  box-shadow: inset 0 1rpx 0 rgba(255, 255, 255, 0.92), 0 20rpx 42rpx rgba(54, 77, 64, 0.08);
}

.qr-board {
  position: relative;
  width: 430rpx;
  height: 430rpx;
  padding: 18rpx;
  display: grid;
  grid-template-columns: repeat(29, 1fr);
  gap: 2rpx;
  border-radius: 28rpx;
  background: #ffffff;
}

.qr-cell {
  border-radius: 2rpx;
  background: rgba(255, 255, 255, 1);
}

.qr-cell.filled {
  background: #111111;
}

.qr-center-badge {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 86rpx;
  height: 86rpx;
  margin-left: -43rpx;
  margin-top: -43rpx;
  border-radius: 26rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(145deg, #ffffff, #f3f6f1);
  border: 8rpx solid #ffffff;
  box-shadow: 0 10rpx 24rpx rgba(54, 77, 64, 0.08);
}

.qr-center-text {
  font-size: 24rpx;
  font-weight: 700;
  letter-spacing: 4rpx;
  color: $brand-primary-deep;
}

.pickup-code-text {
  margin-top: 24rpx;
  text-align: center;
  font-size: 28rpx;
  font-weight: 700;
  letter-spacing: 4rpx;
  color: $brand-primary-deep;
}

.pickup-code-desc {
  margin-top: 14rpx;
  text-align: center;
  font-size: 23rpx;
  line-height: 1.8;
  color: $text-secondary;
}
</style>