Skip to content

引入Vant UI制作数字按键

安装Vant依赖pnpm add vant,并按需引入需安装unplugin-vue-componentspnpm add unplugin-vue-components -D,然后在vite.config.ts内配置插件:

typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
// @ts-nocheck
import { svgstore } from './src/vite_plugins/svgstore'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueJsx({
      transformOn: true,
      mergeProps: true
    }),
    svgstore(),
    Components({
      resolvers: [VantResolver()],
    }),
  ]
})

但是使用组件的时候,还需自行导入vant的样式,因为unplugin-vue-components无法自动改引入对应的样式,案例如下所示:

typescript
import { DatetimePicker, Popup } from 'vant';
import 'vant/es/popup/style';
import 'vant/es/datetime-picker/style';

封装Time.tsx

由于用到的Date,为了方便处理时间,需要封装一个Time.tsx的方法,也可以直接使用其他库,比如day.js

typescript
export const time = (date = new Date()) => {
  const api = {
    format: (pattern = 'YYYY-MM-DD') => {
      // 目前支持的格式有 YYYY MM DD HH mm ss SSS
      const year = date.getFullYear()
      const month = date.getMonth() + 1
      const day = date.getDate()
      const hour = date.getHours()
      const minute = date.getMinutes()
      const second = date.getSeconds()
      const msecond = date.getMilliseconds()
      return pattern.replace(/YYYY/g, year.toString())
        .replace(/MM/, month.toString().padStart(2, '0'))
        .replace(/DD/, day.toString().padStart(2, '0'))
        .replace(/HH/, hour.toString().padStart(2, '0'))
        .replace(/mm/, minute.toString().padStart(2, '0'))
        .replace(/ss/, second.toString().padStart(2, '0'))
        .replace(/SSS/, msecond.toString().padStart(3, '0'))
    }
  }
  return api
}

使用grid布局制作数字按键

使用grid布局的grid-template-areas来布局玩橙数字按键,之后可以通过grid areas来特定命名,具体见MDN的grid-template-areas。然后导入Vant的PopupDatetimePicker组件和其样式。

数字累加的时候需要进行逻辑处理。

tsx
import { defineComponent, PropType, ref } from 'vue'
import { Icon } from '../../shared/Icon'
import { time } from '../../shared/time'
import s from './InputPad.module.scss'
import { DatetimePicker, Popup } from 'vant';
import 'vant/es/popup/style';
import 'vant/es/datetime-picker/style';
export const InputPad = defineComponent({
  setup: (props, context) => {
    const buttons = [
      { text: '1', onClick: () => { appendText(1) } },
      { text: '2', onClick: () => { appendText(2) } },
      { text: '3', onClick: () => { appendText(3) } },
      { text: '4', onClick: () => { appendText(4) } },
      { text: '5', onClick: () => { appendText(5) } },
      { text: '6', onClick: () => { appendText(6) } },
      { text: '7', onClick: () => { appendText(7) } },
      { text: '8', onClick: () => { appendText(8) } },
      { text: '9', onClick: () => { appendText(9) } },
      { text: '.', onClick: () => { appendText('.') } },
      { text: '0', onClick: () => { appendText(0) } },
      { text: '清空', onClick: () => { refAmount.value = '0' } },
      { text: '提交', onClick: () => { } },
    ]
    const refAmount = ref('0')
    const now = new Date()
    const refDate = ref<Date>(now)
    const refDatePickerVisible = ref(false)
    const setDate = (date: Date) => { refDate.value = date; hideDatePicker() }
    const showDatePicker = () => refDatePickerVisible.value = true
    const hideDatePicker = () => refDatePickerVisible.value = false
    const appendText = (n: string | number) => {
      const nString = n.toString()
      const amountValue = refAmount.value
      const amountValueLength = amountValue.length
      const dotIndex = amountValue.indexOf('.')
      if (amountValueLength >= 13) {
        // 字符串超过13后不在添加
        return
      }
      if (dotIndex >= 0 && amountValueLength - dotIndex > 2) {
        // 若存在点且超过后两位则不在添加
        return
      }
      if (nString === '.') {
        if (dotIndex >= 0) {
          // 若以及存在点了,则不在添加
          return
        }
      } else if (nString === '0') { 
        if (dotIndex === -1) {
          // 若输入的为0,且点不存在,就不在添加
          return
        }
      } else { 
        if (refAmount.value === '0') {
          // 若输入的是0,则制空。
          refAmount.value = ''
        }
      }
      refAmount.value += nString
    }
    return () => <>
      <div class={s.dateAndAmount}>
        <span class={s.date}>
          <Icon name='date' class={s.icon} />
          <span>
            <span onClick={showDatePicker}>{time(refDate.value).format()}</span>
            <Popup position='bottom' v-model:show={refDatePickerVisible.value}>
              <DatetimePicker value={refDate.value} type="date" title="选择年月日"
                onConfirm={setDate} onCancel={hideDatePicker}
              />
            </Popup>
          </span>
        </span>
        <span class={s.amount}>{refAmount.value}</span>
      </div>
      <div class={s.buttons}>
        {buttons.map(button => <button onClick={button.onClick}>{button.text}</button>)}
      </div>
    </>
  }
})
tsx
.dateAndAmount {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px;
  font-family: monospace; // 等高字体
  border-top: 1px solid var(--button-border-color);
  > .date {
    font-size: 12px;
    display: flex;
    align-items: center;
    justify-content: start;
    color: var(--date-text);
    .icon {
      width: 24px;
      height: 24px;
      margin-right: 8px;
      fill: var(--amount-text);
    }
  }
  > .amount {
    font-size: 20px;
    color: var(--amount-text)
  }

}
.buttons {
  display: grid;
  grid-template-areas: 
    "n1 n2 n3 d"
    "n4 n5 n6 d"
    "n7 n8 n9 s"
    "n0 n0 nd s";
  // 布局划分
  grid-auto-rows: 48px;
  // 隐式创建横行高度
  grid-auto-columns: 1fr;
  // 隐式创建纵向宽度
  gap: 1px;
  background: var(--button-border-color);
  border-top: 1px solid var(--button-border-color);
  > button {
    border: none;
    background: var(--button-bg);
    &:nth-child(1) {
      grid-area: n1;
    }
    &:nth-child(2) {
      grid-area: n2;
    }
    &:nth-child(3) {
      grid-area: n3;
    }
    &:nth-child(4) {
      grid-area: n4;
    }
    &:nth-child(5) {
      grid-area: n5;
    }
    &:nth-child(6) {
      grid-area: n6;
    }
    &:nth-child(7) {
      grid-area: n7;
    }
    &:nth-child(8) {
      grid-area: n8;
    }
    &:nth-child(9) {
      grid-area: n9;
    }
    &:nth-child(10) {
      grid-area: nd;
    }
    &:nth-child(11) {
      grid-area: n0;
    }
    &:nth-child(12) {
      grid-area: d;
    }
    &:nth-child(13) {
      grid-area: s;
      background: var(--button-bg-important);
      color: var(--button-text-important);
    }
  }
}