| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814 |
- <template>
- <!--
- H5 向供货商付款页(支付宝收款风格)
- 用途:扫码后输入金额并用支付宝支付;展示待结与余额入口
- -->
- <view class="pay-page">
- <!-- 收款方信息 -->
- <view class="pay-merchant">
- <view class="pay-merchant__avatar">
- <text class="pay-merchant__avatar-text">{{ merchantInitial }}</text>
- </view>
- <view class="pay-merchant__meta">
- <text class="pay-merchant__name">{{ merchantName }}</text>
- </view>
- </view>
- <!-- 金额主视觉 -->
- <view class="pay-amount-card">
- <view class="pay-amount-card__head">
- <text class="pay-amount-card__title">付款金额</text>
- <text v-if="canPay" class="pay-amount-card__clear" @click="changeAmount(0)">清空</text>
- </view>
- <view class="pay-amount-row">
- <text class="pay-amount-row__currency">¥</text>
- <text class="pay-amount-row__value" :class="{ 'pay-amount-row__value--placeholder': !canPay }">{{ amountText }}</text>
- <view class="pay-amount-row__cursor"></view>
- </view>
- <!-- 待结提示:点按可填入金额 -->
- <view
- v-if="hasOutstanding"
- class="pay-debt-tip"
- @click="fillOutstanding">
- <view class="pay-debt-tip__left">
- <text class="pay-debt-tip__label">待结金额</text>
- <text class="pay-debt-tip__value">¥{{ outstandingAmount }}</text>
- </view>
- <view class="pay-debt-tip__btn">
- <text class="pay-debt-tip__action">一键填入</text>
- </view>
- </view>
- <!-- 有可用余额时展示当前账户余额 -->
- <view v-else-if="hasPositiveBalance" class="pay-balance-tip">
- <text class="pay-balance-tip__label">当前账户余额</text>
- <text class="pay-balance-tip__value">¥{{ accountBalance }}</text>
- </view>
- </view>
- <!-- 账户与明细:独立信息卡,居中排版 -->
- <view class="pay-extra-card">
- <view v-if="displayCustomName" class="pay-account">
- <view class="pay-account__meta">
- <text class="pay-account__name">付款人:{{ displayCustomName }}</text>
- </view>
- </view>
- <view
- class="pay-action-row"
- :class="{
- 'pay-action-row--single': !showGatheringBtn,
- 'pay-action-row--with-account': !!displayCustomName
- }">
- <view
- v-if="showGatheringBtn"
- class="pay-action-chip pay-action-chip--debt"
- @click="goGathering">
- <text class="pay-action-chip__text">查看待结订单</text>
- </view>
- <view
- class="pay-action-chip"
- :class="{ 'pay-action-chip--disabled': !id }"
- @click="goBalanceChange">
- <text class="pay-action-chip__text">查看余额明细</text>
- </view>
- </view>
- </view>
- <!-- 底部:支付宝式数字键盘 + 右侧付款键 -->
- <view class="pay-keyboard-wrap">
- <view class="pay-keyboard">
- <view class="pay-keyboard__keys">
- <view
- v-for="(key, index) in flatKeys"
- :key="index"
- class="pay-key"
- :class="{ 'pay-key--del': key.type === 'delete' }"
- @click="handleKeyPress(key)">
- <view class="pay-key__inner">
- <text v-if="key.type === 'delete'" class="pay-key__del">⌫</text>
- <text v-else class="pay-key__num">{{ key.value }}</text>
- </view>
- </view>
- </view>
- <view
- class="pay-key-pay"
- :class="{ 'pay-key-pay--disabled': !canPay }"
- @click="handlePay">
- <text class="pay-key-pay__text">付款</text>
- </view>
- </view>
- <view class="pay-safe-area"></view>
- </view>
- </view>
- </template>
- <script>
- import { getGhsData } from '@/api/ghs'
- import { rechargeFn } from '@/api/custom-recharge'
- const KEY_TYPES = {
- NUMBER: 'number',
- DELETE: 'delete',
- DOT: 'dot',
- CONFIRM: 'confirm'
- }
- const MAX_INTEGER_LENGTH = 8
- const MAX_DECIMAL_LENGTH = 2
- export default {
- name: 'pay',
- data() {
- return {
- currentAmount: '0',
- displayAmount: '0',
- keyboardKeys: [
- [
- { type: KEY_TYPES.NUMBER, value: '1' },
- { type: KEY_TYPES.NUMBER, value: '2' },
- { type: KEY_TYPES.NUMBER, value: '3' }
- ],
- [
- { type: KEY_TYPES.NUMBER, value: '4' },
- { type: KEY_TYPES.NUMBER, value: '5' },
- { type: KEY_TYPES.NUMBER, value: '6' }
- ],
- [
- { type: KEY_TYPES.NUMBER, value: '7' },
- { type: KEY_TYPES.NUMBER, value: '8' },
- { type: KEY_TYPES.NUMBER, value: '9' }
- ],
- [
- { type: KEY_TYPES.DOT, value: '.' },
- { type: KEY_TYPES.NUMBER, value: '0' },
- { type: KEY_TYPES.DELETE, value: '' }
- ]
- ],
- amountBuffer: '0',
- updateScheduled: false,
- updateTimer: null,
- id: 0,
- salt: '',
- ghsInfo: {},
- customInfo: {},
- payWay: 1
- }
- },
- computed: {
- canPay() {
- return parseFloat(this.currentAmount) > 0
- },
- /** 金额展示:输入中保留原始缓冲,避免过早补零打断输入 */
- amountText() {
- if (!this.canPay && this.amountBuffer === '0') {
- return '0.00'
- }
- return this.amountBuffer
- },
- displayCustomName() {
- const name = (this.customInfo && this.customInfo.name) ? String(this.customInfo.name) : ''
- if (!name) {
- return ''
- }
- if (name.length > 7) {
- return name.slice(0, 7) + '...'
- }
- return name
- },
- merchantName() {
- const name = (this.ghsInfo && this.ghsInfo.name) ? String(this.ghsInfo.name) : '供货商'
- if (name.length > 12) {
- return name.slice(0, 12) + '...'
- }
- return name
- },
- merchantInitial() {
- const name = (this.ghsInfo && this.ghsInfo.name) ? String(this.ghsInfo.name) : '商'
- return name.charAt(0) || '商'
- },
- outstandingAmount() {
- const info = this.customInfo || {}
- const remain = parseFloat(info.remainDebtAmount || 0)
- if (remain > 0) {
- return parseFloat(remain.toFixed(2))
- }
- const bal = parseFloat(info.balance || 0)
- return bal < 0 ? parseFloat(Math.abs(bal).toFixed(2)) : 0
- },
- hasOutstanding() {
- return this.outstandingAmount > 0
- },
- /** 净 balance 为正时的可用余额 */
- accountBalance() {
- const bal = parseFloat((this.customInfo && this.customInfo.balance) || 0)
- return bal > 0 ? parseFloat(bal.toFixed(2)) : 0
- },
- hasPositiveBalance() {
- return this.accountBalance > 0
- },
- showGatheringBtn() {
- return this.hasOutstanding && !!this.id
- },
- /** 键盘扁平化为 12 键网格,配合右侧付款柱 */
- flatKeys() {
- const keys = []
- this.keyboardKeys.forEach((row) => {
- row.forEach((key) => {
- keys.push(key)
- })
- })
- return keys
- }
- },
- onLoad(options) {
- if (options.id) {
- this.id = options.id
- }
- if (options.salt) {
- this.salt = options.salt
- }
- if (this.id && this.salt) {
- getGhsData({ id: this.id, salt: this.salt }).then(res => {
- if (res.data && res.data.ghs) {
- this.ghsInfo = res.data.ghs
- this.customInfo = res.data.custom ? res.data.custom : {}
- if (this.hasOutstanding) {
- this.fillOutstanding()
- }
- }
- })
- }
- },
- methods: {
- /** 同步缓冲与展示金额 */
- changeAmount(amount) {
- const num = parseFloat(amount)
- if (!num || num <= 0) {
- this.amountBuffer = '0'
- this.currentAmount = '0'
- this.displayAmount = '0'
- return
- }
- const amountStr = parseFloat(num.toFixed(2)).toString()
- this.amountBuffer = amountStr
- this.currentAmount = amountStr
- this.updateDisplay()
- },
- /** 一键填入待结金额 */
- fillOutstanding() {
- if (!this.hasOutstanding) {
- return
- }
- this.changeAmount(this.outstandingAmount)
- },
- handleTouchStart() {},
- handleTouchEnd() {},
- handleKeyPress(key) {
- if (this.updateTimer) {
- clearTimeout(this.updateTimer)
- }
- if (key.type === KEY_TYPES.NUMBER) {
- this.addNumber(key.value)
- } else if (key.type === KEY_TYPES.DELETE) {
- this.deleteNumber()
- } else if (key.type === KEY_TYPES.DOT) {
- this.addDecimal()
- }
- this.scheduleUpdate()
- },
- scheduleUpdate() {
- if (!this.updateScheduled) {
- this.updateScheduled = true
- requestAnimationFrame(() => {
- this.updateDisplay()
- this.updateScheduled = false
- })
- }
- },
- addNumber(num) {
- const buffer = this.amountBuffer
- if (buffer === '0' && num !== '.') {
- this.amountBuffer = num
- this.currentAmount = num
- return
- }
- const parts = buffer.split('.')
- const integerPart = parts[0]
- const decimalPart = parts[1]
- if (!decimalPart && integerPart.length >= MAX_INTEGER_LENGTH) {
- uni.showToast({ title: '金额不能超过8位数', icon: 'none', duration: 1500 })
- return
- }
- if (decimalPart && decimalPart.length >= MAX_DECIMAL_LENGTH) {
- uni.showToast({ title: '最多支持2位小数', icon: 'none', duration: 1500 })
- return
- }
- this.amountBuffer = buffer + num
- this.currentAmount = this.amountBuffer
- },
- addDecimal() {
- if (!this.amountBuffer.includes('.')) {
- this.amountBuffer += '.'
- this.currentAmount = this.amountBuffer
- }
- },
- deleteNumber() {
- const buffer = this.amountBuffer
- if (buffer.length > 1) {
- this.amountBuffer = buffer.slice(0, -1)
- } else {
- this.amountBuffer = '0'
- }
- this.currentAmount = this.amountBuffer
- },
- updateDisplay() {
- let amount = this.currentAmount
- if (typeof amount !== 'string') {
- amount = amount.toString()
- }
- if (amount === '0') {
- this.displayAmount = '0'
- return
- }
- if (amount.includes('.')) {
- const numAmount = parseFloat(amount) || 0
- this.displayAmount = numAmount.toFixed(2)
- } else {
- this.displayAmount = amount
- }
- },
- setAmount(amount) {
- this.changeAmount(amount)
- },
- handlePay() {
- if (!this.canPay) {
- return
- }
- const amount = parseFloat(this.currentAmount)
- if (amount <= 0) {
- uni.showToast({ title: '请输入金额!', icon: 'none', duration: 2000 })
- return false
- }
- const params = {
- actPrice: amount,
- payWay: this.payWay,
- ghsId: this.id,
- salt: this.salt
- }
- uni.showLoading({ title: '跳转中...', mask: true })
- rechargeFn(params).then(res => {
- uni.hideLoading()
- if (res.code == 0) {
- uni.showToast({ title: res.msg, icon: 'none', duration: 2000 })
- return false
- }
- uni.showToast({ title: '正在跳转支付...', icon: 'success', duration: 1500 })
- setTimeout(() => {
- window.location.href = res.data.url
- }, 300)
- }).catch(() => {
- uni.hideLoading()
- uni.showToast({ title: '请求失败,请重试', icon: 'none', duration: 2000 })
- })
- },
- goBack() {
- uni.navigateBack({ delta: 1 })
- },
- goGathering() {
- if (!this.id) {
- uni.showToast({ title: '供货商信息加载中,请稍后再试', icon: 'none' })
- return
- }
- uni.navigateTo({
- url: `/pagesPurchase/gathering?id=${this.id}&salt=${this.salt}`
- })
- },
- /** 跳转余额变动明细(与小程序共用接口,salt 访客可查) */
- goBalanceChange() {
- if (!this.id) {
- uni.showToast({ title: '供货商信息加载中,请稍后再试', icon: 'none' })
- return
- }
- uni.navigateTo({
- url: `/admin/ghs/balanceChange?ghsId=${this.id}&salt=${this.salt}`
- })
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .pay-page {
- min-height: 100vh;
- background: #f5f5f5;
- box-sizing: border-box;
- padding-bottom: 560upx;
- }
- /* 收款方:简洁头像 + 名称 */
- .pay-merchant {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- padding: 56upx 40upx 28upx;
- }
- .pay-merchant__avatar {
- width: 72upx;
- height: 72upx;
- border-radius: 16upx;
- background: #1677ff;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .pay-merchant__avatar-text {
- color: #fff;
- font-size: 34upx;
- font-weight: 600;
- line-height: 1;
- }
- .pay-merchant__meta {
- margin-left: 20upx;
- display: flex;
- flex-direction: column;
- min-width: 0;
- }
- .pay-merchant__label {
- font-size: 24upx;
- color: #999;
- line-height: 32upx;
- }
- .pay-merchant__name {
- margin-top: 4upx;
- font-size: 32upx;
- font-weight: 600;
- color: #222;
- line-height: 44upx;
- }
- /* 金额白卡 */
- .pay-amount-card {
- margin: 0 24upx;
- padding: 36upx 32upx 28upx;
- background: #fff;
- border-radius: 16upx;
- box-sizing: border-box;
- }
- .pay-amount-card__head {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- }
- .pay-amount-card__title {
- font-size: 28upx;
- color: #666;
- line-height: 40upx;
- }
- .pay-amount-card__clear {
- font-size: 26upx;
- color: #1677ff;
- line-height: 40upx;
- padding: 0 8upx;
- }
- .pay-amount-row {
- display: flex;
- flex-direction: row;
- align-items: flex-end;
- margin-top: 28upx;
- min-height: 96upx;
- }
- .pay-amount-row__currency {
- font-size: 48upx;
- font-weight: 600;
- color: #222;
- line-height: 1;
- margin-right: 12upx;
- padding-bottom: 10upx;
- }
- .pay-amount-row__value {
- font-size: 80upx;
- font-weight: 600;
- color: #222;
- line-height: 1;
- font-family: 'DIN Alternate', 'Helvetica Neue', Arial, sans-serif;
- word-break: break-all;
- }
- .pay-amount-row__value--placeholder {
- color: #ccc;
- }
- /* 仿输入光标闪烁 */
- .pay-amount-row__cursor {
- width: 4upx;
- height: 64upx;
- margin-left: 8upx;
- margin-bottom: 8upx;
- background: #1677ff;
- animation: pay-cursor-blink 1s steps(1) infinite;
- }
- @keyframes pay-cursor-blink {
- 0%,
- 50% {
- opacity: 1;
- }
- 51%,
- 100% {
- opacity: 0;
- }
- }
- .pay-debt-tip {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- margin-top: 28upx;
- padding: 20upx 22upx;
- background: #fff7f0;
- border-radius: 12upx;
- box-sizing: border-box;
- }
- .pay-debt-tip__left {
- display: flex;
- flex-direction: row;
- align-items: baseline;
- flex: 1;
- min-width: 0;
- }
- .pay-debt-tip__label {
- font-size: 26upx;
- color: #999;
- line-height: 36upx;
- flex-shrink: 0;
- }
- .pay-debt-tip__value {
- margin-left: 12upx;
- font-size: 32upx;
- font-weight: 600;
- color: #ff6a00;
- line-height: 36upx;
- }
- .pay-debt-tip__btn {
- flex-shrink: 0;
- padding: 8upx 20upx;
- background: #1677ff;
- border-radius: 28upx;
- }
- .pay-debt-tip__action {
- font-size: 24upx;
- color: #fff;
- line-height: 32upx;
- }
- /* 正余额提示条:与待结条对称,蓝色系 */
- .pay-balance-tip {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- margin-top: 28upx;
- padding: 20upx 22upx;
- background: #f0f7ff;
- border-radius: 12upx;
- box-sizing: border-box;
- }
- .pay-balance-tip__label {
- font-size: 30upx;
- color: #333;
- line-height: 36upx;
- }
- .pay-balance-tip__value {
- margin-left: 12upx;
- font-size: 34upx;
- font-weight: 600;
- color: #1677ff;
- line-height: 36upx;
- }
- /* 账户 + 明细入口:白卡居中 */
- .pay-extra-card {
- margin: 20upx 24upx 0;
- padding: 28upx 24upx 30upx;
- background: #fff;
- border-radius: 16upx;
- box-sizing: border-box;
- }
- .pay-account {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- }
- .pay-account__badge {
- width: 56upx;
- height: 56upx;
- border-radius: 50%;
- background: #e8f3ff;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .pay-account__badge-text {
- font-size: 24upx;
- color: #1677ff;
- font-weight: 600;
- line-height: 1;
- }
- .pay-account__meta {
- display: flex;
- flex-direction: column;
- align-items: center;
- min-width: 0;
- }
- .pay-account__name {
- font-size: 32upx;
- font-weight: 600;
- color: #222;
- line-height: 40upx;
- }
- .pay-action-row {
- display: flex;
- flex-direction: row;
- align-items: stretch;
- justify-content: center;
- }
- .pay-action-row--with-account {
- margin-top: 28upx;
- padding-top: 28upx;
- border-top: 1upx solid #f0f0f0;
- }
- .pay-action-row--single .pay-action-chip {
- width: 300upx;
- flex: none;
- }
- .pay-action-chip {
- flex: 1;
- max-width: 300upx;
- height: 72upx;
- display: flex;
- align-items: center;
- justify-content: center;
- box-sizing: border-box;
- background: #f5f9ff;
- border: 1upx solid #d6e4ff;
- border-radius: 36upx;
- margin-left: 20upx;
- &:first-child {
- margin-left: 0;
- }
- }
- .pay-action-chip--debt {
- background: #fff7f0;
- border-color: #ffd8b8;
- }
- .pay-action-chip--debt .pay-action-chip__text {
- color: #ff6a00;
- }
- .pay-action-chip--disabled {
- opacity: 0.45;
- }
- .pay-action-chip:active {
- opacity: 0.85;
- }
- .pay-action-chip__text {
- font-size: 26upx;
- color: #1677ff;
- line-height: 36upx;
- font-weight: 500;
- }
- /* 底部键盘:左 3×4 数字区 + 右通栏付款 */
- .pay-keyboard-wrap {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- background: #d1d3d9;
- z-index: 20;
- box-sizing: border-box;
- }
- .pay-keyboard {
- display: flex;
- flex-direction: row;
- align-items: stretch;
- padding: 12upx;
- box-sizing: border-box;
- }
- .pay-keyboard__keys {
- flex: 3;
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- }
- .pay-key {
- width: 33.333%;
- height: 108upx;
- display: flex;
- align-items: center;
- justify-content: center;
- box-sizing: border-box;
- padding: 6upx;
- }
- .pay-key__inner {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #fff;
- border-radius: 12upx;
- box-shadow: 0 2upx 0 #b8bbc3;
- }
- .pay-key:active .pay-key__inner {
- background: #e8e8e8;
- }
- .pay-key__num {
- font-size: 44upx;
- font-weight: 500;
- color: #222;
- line-height: 1;
- }
- .pay-key__del {
- font-size: 40upx;
- color: #333;
- line-height: 1;
- }
- .pay-key-pay {
- flex: 1;
- margin: 6upx;
- margin-left: 4upx;
- border-radius: 12upx;
- background: #1677ff;
- display: flex;
- align-items: center;
- justify-content: center;
- box-sizing: border-box;
- }
- .pay-key-pay--disabled {
- background: #a0c4ff;
- }
- .pay-key-pay:not(.pay-key-pay--disabled):active {
- background: #0958d9;
- }
- .pay-key-pay__text {
- color: #fff;
- font-size: 36upx;
- font-weight: 600;
- letter-spacing: 4upx;
- }
- .pay-safe-area {
- height: env(safe-area-inset-bottom);
- background: #d1d3d9;
- }
- </style>
|