更新:登录功能
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 车牌号键盘-省份简称
|
||||
*/
|
||||
export const CAR_KEYBOARD_AREAS = [
|
||||
'京',
|
||||
'沪',
|
||||
'粤',
|
||||
'津',
|
||||
'冀',
|
||||
'豫',
|
||||
'云',
|
||||
'辽',
|
||||
'黑',
|
||||
'湘',
|
||||
'皖',
|
||||
'鲁',
|
||||
'苏',
|
||||
'浙',
|
||||
'赣',
|
||||
'鄂',
|
||||
'桂',
|
||||
'甘',
|
||||
'晋',
|
||||
'陕',
|
||||
'蒙',
|
||||
'吉',
|
||||
'闽',
|
||||
'贵',
|
||||
'渝',
|
||||
'川',
|
||||
'青',
|
||||
'琼',
|
||||
'宁',
|
||||
'挂',
|
||||
'藏',
|
||||
'港',
|
||||
'澳',
|
||||
'新',
|
||||
'使',
|
||||
'学'
|
||||
]
|
||||
|
||||
/**
|
||||
* 车牌号键盘-数字和字母(不包含I和O,因为车牌号规则中不使用这两个字母)
|
||||
*/
|
||||
export const CAR_KEYBOARD_KEYS = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'P',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M'
|
||||
]
|
||||
102
uni_modules/wot-design-uni/components/wd-keyboard/index.scss
Normal file
102
uni_modules/wot-design-uni/components/wd-keyboard/index.scss
Normal file
@@ -0,0 +1,102 @@
|
||||
@import './../common/abstracts/_mixin.scss';
|
||||
@import './../common/abstracts/variable.scss';
|
||||
|
||||
.wot-theme-dark {
|
||||
@include b(keyboard) {
|
||||
background: $-dark-background5;
|
||||
|
||||
@include e(header) {
|
||||
color: $-dark-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include b(keyboard) {
|
||||
width: 100%;
|
||||
background: $-keyboard-background;
|
||||
color: $-color-black;
|
||||
user-select: none;
|
||||
|
||||
@include m(with-title) {
|
||||
border-radius: 20px 20px 0 0;
|
||||
}
|
||||
|
||||
@include e(header) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: content-box;
|
||||
height: $-keyboard-title-height;
|
||||
padding-top: 6px;
|
||||
color: $-keyboard-title-color;
|
||||
font-size: $-keyboard-title-font-size;
|
||||
}
|
||||
|
||||
@include e(title) {
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
|
||||
&-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(body) {
|
||||
display: flex;
|
||||
padding: 6px 0 0 6px;
|
||||
}
|
||||
|
||||
@include e(keys) {
|
||||
display: flex;
|
||||
flex: 3;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&-car {
|
||||
@include e(body) {
|
||||
display: flex;
|
||||
padding: 6px 0 0 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&-car {
|
||||
@include e(keys) {
|
||||
display: flex;
|
||||
flex: 10;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.wd-key-wrapper {
|
||||
--wot-keyboard-key-font-size: 18px;
|
||||
flex-basis: 10%;
|
||||
|
||||
@include m(wider) {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include e(close) {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
padding: $-keyboard-close-padding;
|
||||
color: $-keyboard-close-color;
|
||||
font-size: $-keyboard-close-font-size;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
@include m(hover) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(sidebar) {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
@import './../../common/abstracts/_mixin.scss';
|
||||
@import './../../common/abstracts/variable.scss';
|
||||
|
||||
.wot-theme-dark {
|
||||
@include b(key) {
|
||||
background: $-dark-background2;
|
||||
color: $-dark-color;
|
||||
|
||||
&:active {
|
||||
background-color: $-dark-background4;
|
||||
}
|
||||
|
||||
@include m(active) {
|
||||
background-color: $-dark-background4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wd-key-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
flex-basis: 33%;
|
||||
box-sizing: border-box;
|
||||
padding: 0 6px 6px 0;
|
||||
|
||||
@include m(wider) {
|
||||
flex-basis: 66%;
|
||||
}
|
||||
}
|
||||
|
||||
@include b(key) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: $-keyboard-key-height;
|
||||
font-size: $-keyboard-key-font-size;
|
||||
line-height: 1.5;
|
||||
background: $-keyboard-key-background;
|
||||
border-radius: $-keyboard-key-border-radius;
|
||||
|
||||
&:active {
|
||||
background-color: $-keyboard-key-active-color;
|
||||
}
|
||||
|
||||
@include m(large) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 6px;
|
||||
bottom: 6px;
|
||||
left: 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@include m(delete, close) {
|
||||
font-size: $-keyboard-delete-font-size;
|
||||
}
|
||||
|
||||
@include m(active) {
|
||||
background-color: $-keyboard-key-active-color;
|
||||
}
|
||||
|
||||
@include m(close) {
|
||||
color: $-keyboard-button-text-color;
|
||||
background: $-keyboard-button-background;
|
||||
|
||||
&:active {
|
||||
background: $-keyboard-button-background;
|
||||
opacity: $-keyboard-button-active-opacity;
|
||||
}
|
||||
}
|
||||
|
||||
@include edeep(loading-icon) {
|
||||
color: $-keyboard-button-text-color;
|
||||
}
|
||||
|
||||
@include edeep(icon) {
|
||||
font-size: $-keyboard-icon-size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<view :class="`wd-key-wrapper ${wider ? 'wd-key-wrapper--wider' : ''}`" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd">
|
||||
<view :class="keyClass">
|
||||
<wd-loading custom-class="wd-key__loading-icon" v-if="props.loading" />
|
||||
<template v-if="type === 'delete'">
|
||||
<template v-if="text">
|
||||
{{ text }}
|
||||
</template>
|
||||
<wd-icon v-else custom-class="wd-key__icon" name="keyboard-delete" size="22px"></wd-icon>
|
||||
</template>
|
||||
<template v-else-if="type === 'extra'">
|
||||
<template v-if="text">
|
||||
{{ text }}
|
||||
</template>
|
||||
<wd-icon v-else custom-class="wd-key__icon" name="keyboard-collapse" size="22px"></wd-icon>
|
||||
</template>
|
||||
<template v-else>{{ text }}</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'wd-key',
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useTouch } from '../../composables/useTouch'
|
||||
import { keyProps } from './types'
|
||||
|
||||
const props = defineProps(keyProps)
|
||||
const emit = defineEmits(['press'])
|
||||
|
||||
const touch = useTouch()
|
||||
const active = ref<boolean>(false)
|
||||
|
||||
const keyClass = computed(() => {
|
||||
return `wd-key ${props.large ? 'wd-key--large' : ''} ${props.type === 'delete' ? 'wd-key--delete' : ''} ${
|
||||
props.type === 'close' ? 'wd-key--close' : ''
|
||||
}`
|
||||
})
|
||||
|
||||
function onTouchStart(event: TouchEvent) {
|
||||
touch.touchStart(event)
|
||||
active.value = true
|
||||
}
|
||||
|
||||
function onTouchMove(event: TouchEvent) {
|
||||
touch.touchMove(event)
|
||||
if (touch.direction.value) {
|
||||
active.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchEnd() {
|
||||
if (active.value) {
|
||||
active.value = false
|
||||
emit('press', props.text, props.type)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { makeBooleanProp, makeNumericProp, makeStringProp } from '../../common/props'
|
||||
|
||||
export type NumberKeyType = '' | 'delete' | 'extra' | 'close'
|
||||
|
||||
export const keyProps = {
|
||||
type: makeStringProp<NumberKeyType>(''),
|
||||
text: makeNumericProp(''),
|
||||
wider: makeBooleanProp(false),
|
||||
large: makeBooleanProp(false),
|
||||
loading: makeBooleanProp(false)
|
||||
}
|
||||
92
uni_modules/wot-design-uni/components/wd-keyboard/types.ts
Normal file
92
uni_modules/wot-design-uni/components/wd-keyboard/types.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { PropType } from 'vue'
|
||||
import { baseProps, makeBooleanProp, makeNumberProp, makeStringProp } from '../common/props'
|
||||
|
||||
export type KeyboardMode = 'default' | 'custom' | 'car'
|
||||
export type KeyType = '' | 'delete' | 'extra' | 'close'
|
||||
export type CarKeyboardLang = 'zh' | 'en'
|
||||
|
||||
export interface Key {
|
||||
text?: number | string // key文本
|
||||
type?: KeyType // key的类型
|
||||
wider?: boolean // 是否占2个key的宽度
|
||||
}
|
||||
|
||||
export const keyboardProps = {
|
||||
...baseProps,
|
||||
/**
|
||||
* 是否可见
|
||||
*/
|
||||
visible: makeBooleanProp(false),
|
||||
/**
|
||||
* 绑定的值
|
||||
*/
|
||||
modelValue: makeStringProp(''),
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
title: String,
|
||||
/**
|
||||
* 键盘模式
|
||||
*/
|
||||
mode: makeStringProp<KeyboardMode>('default'),
|
||||
/**
|
||||
* 层级
|
||||
*/
|
||||
zIndex: makeNumberProp(100),
|
||||
/**
|
||||
* 最大长度
|
||||
*/
|
||||
maxlength: makeNumberProp(Infinity),
|
||||
/**
|
||||
* 是否显示删除键
|
||||
*/
|
||||
showDeleteKey: makeBooleanProp(true),
|
||||
/**
|
||||
* 是否随机键盘按键顺序
|
||||
*/
|
||||
randomKeyOrder: makeBooleanProp(false),
|
||||
/**
|
||||
* 确认按钮文本
|
||||
*/
|
||||
closeText: String,
|
||||
/**
|
||||
* 删除按钮文本
|
||||
*/
|
||||
deleteText: String,
|
||||
/**
|
||||
* 关闭按钮是否显示加载状态
|
||||
*/
|
||||
closeButtonLoading: makeBooleanProp(false),
|
||||
/**
|
||||
* 是否显示蒙层
|
||||
*/
|
||||
modal: makeBooleanProp(false),
|
||||
/**
|
||||
* 是否在点击外部时收起键盘
|
||||
*/
|
||||
hideOnClickOutside: makeBooleanProp(true),
|
||||
/**
|
||||
* 是否锁定滚动
|
||||
*/
|
||||
lockScroll: makeBooleanProp(true),
|
||||
/**
|
||||
* 是否在底部安全区域内
|
||||
*/
|
||||
safeAreaInsetBottom: makeBooleanProp(true),
|
||||
/**
|
||||
* 额外按键
|
||||
*/
|
||||
extraKey: [String, Array] as PropType<string | Array<string>>,
|
||||
/**
|
||||
* 是否从页面中脱离出来,用于解决各种 fixed 失效问题 (H5: teleport, APP: renderjs, 小程序: root-portal)
|
||||
*/
|
||||
rootPortal: makeBooleanProp(false),
|
||||
/**
|
||||
* 车牌键盘语言模式 当mode=car时生效
|
||||
*/
|
||||
carLang: String as PropType<CarKeyboardLang>,
|
||||
/**
|
||||
* 是否自动切换车牌键盘语言 当mode=car且carLang是非受控状态时生效
|
||||
*/
|
||||
autoSwitchLang: makeBooleanProp(false)
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<wd-popup
|
||||
v-model="show"
|
||||
position="bottom"
|
||||
:z-index="zIndex"
|
||||
:safe-area-inset-bottom="safeAreaInsetBottom"
|
||||
:modal-style="modal ? '' : 'opacity: 0;'"
|
||||
:modal="hideOnClickOutside"
|
||||
:lockScroll="lockScroll"
|
||||
:root-portal="rootPortal"
|
||||
@click-modal="handleClose"
|
||||
>
|
||||
<view :class="`wd-keyboard ${customClass}`" :style="customStyle">
|
||||
<view class="wd-keyboard__header" v-if="showHeader">
|
||||
<slot name="title" v-if="showTitle">
|
||||
<text class="wd-keyboard__title">{{ title }}</text>
|
||||
</slot>
|
||||
<view class="wd-keyboard__close" hover-class="wd-keyboard__close--hover" v-if="showClose" @click="handleClose">
|
||||
<text>{{ closeText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<template v-if="mode !== 'car'">
|
||||
<view class="wd-keyboard__body">
|
||||
<view class="wd-keyboard__keys">
|
||||
<wd-key v-for="key in keys" :key="key.text" :text="key.text" :type="key.type" :wider="key.wider" @press="handlePress"></wd-key>
|
||||
</view>
|
||||
<view class="wd-keyboard__sidebar" v-if="mode === 'custom'">
|
||||
<wd-key v-if="showDeleteKey" large :text="deleteText" type="delete" @press="handlePress"></wd-key>
|
||||
<wd-key large :text="closeText" type="close" :loading="closeButtonLoading" @press="handlePress"></wd-key>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-if="mode === 'car'">
|
||||
<view class="wd-keyboard-car__body">
|
||||
<view class="wd-keyboard-car__keys">
|
||||
<wd-key v-for="key in keys" :key="key.text" :text="key.text" :type="key.type" :wider="key.wider" @press="handlePress"></wd-key>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'wd-keyboard',
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch, useSlots } from 'vue'
|
||||
import wdPopup from '../wd-popup/wd-popup.vue'
|
||||
import WdKey from './key/index.vue'
|
||||
import { keyboardProps, type Key, type CarKeyboardLang } from './types'
|
||||
import type { NumberKeyType } from './key/types'
|
||||
import { CAR_KEYBOARD_AREAS, CAR_KEYBOARD_KEYS } from './constants'
|
||||
|
||||
const props = defineProps(keyboardProps)
|
||||
const emit = defineEmits(['update:visible', 'input', 'close', 'delete', 'update:modelValue', 'update:carLang'])
|
||||
const slots = useSlots()
|
||||
|
||||
const show = ref(props.visible)
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newValue) => {
|
||||
show.value = newValue
|
||||
}
|
||||
)
|
||||
|
||||
const carLang = ref<CarKeyboardLang>('zh')
|
||||
const carKeyboardLang = computed({
|
||||
get: () => (props.carLang ? props.carLang : carLang.value),
|
||||
set: (value: CarKeyboardLang) => {
|
||||
carLang.value = value
|
||||
}
|
||||
})
|
||||
|
||||
const keys = computed(() => (props.mode !== 'car' ? (props.mode === 'custom' ? genCustomKeys() : genDefaultKeys()) : genCarKeys()))
|
||||
|
||||
const showClose = computed(() => {
|
||||
return props.closeText && (props.mode === 'default' || props.mode === 'car')
|
||||
})
|
||||
|
||||
const showTitle = computed(() => {
|
||||
return !!props.title || !!slots.title
|
||||
})
|
||||
|
||||
const showHeader = computed(() => {
|
||||
return showTitle.value || showClose.value
|
||||
})
|
||||
|
||||
/**
|
||||
* 随机打乱数组的顺序
|
||||
* @param arr 要打乱顺序的数组
|
||||
* @returns 打乱顺序后的数组
|
||||
*/
|
||||
function shuffleArray<T>(arr: T[]): T[] {
|
||||
const newArr = [...arr]
|
||||
for (let i = newArr.length - 1; i > 0; i--) {
|
||||
// 生成一个随机索引 j,范围是 [0, i]
|
||||
const j = Math.floor(Math.random() * (i + 1))
|
||||
|
||||
// 交换索引 i 和 j 处的元素
|
||||
;[newArr[i], newArr[j]] = [newArr[j], newArr[i]]
|
||||
}
|
||||
return newArr
|
||||
}
|
||||
function genBasicKeys(): Key[] {
|
||||
const keys = Array.from({ length: 9 }, (_, i) => ({ text: i + 1 }))
|
||||
|
||||
// 如果需要随机顺序,则调用 shuffleArray 方法打乱数组顺序
|
||||
return props.randomKeyOrder ? shuffleArray(keys) : keys
|
||||
}
|
||||
|
||||
function genDefaultKeys(): Key[] {
|
||||
return [
|
||||
...genBasicKeys(),
|
||||
{ text: props.extraKey as string, type: 'extra' },
|
||||
{ text: 0 },
|
||||
{
|
||||
// 根据条件是否显示删除键的文本和类型
|
||||
text: props.showDeleteKey ? props.deleteText : '',
|
||||
type: props.showDeleteKey ? 'delete' : ''
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function genCustomKeys(): Key[] {
|
||||
const keys = genBasicKeys()
|
||||
const extraKeys = Array.isArray(props.extraKey) ? props.extraKey : [props.extraKey]
|
||||
if (extraKeys.length === 1) {
|
||||
// 如果只有一个额外按键,则添加一个宽度较大的数字0和一个额外按键
|
||||
keys.push({ text: 0, wider: true }, { text: extraKeys[0], type: 'extra' })
|
||||
} else if (extraKeys.length === 2) {
|
||||
// 如果有两个额外按键,则添加两个额外按键和一个数字0
|
||||
keys.push({ text: extraKeys[0], type: 'extra' }, { text: 0 }, { text: extraKeys[1], type: 'extra' })
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// 生成车牌键盘
|
||||
function genCarKeys(): Array<Key> {
|
||||
const [keys, remainKeys] = splitCarKeys()
|
||||
return [
|
||||
...keys,
|
||||
{ text: carKeyboardLang.value === 'zh' ? 'ABC' : '省份', type: 'extra', wider: true },
|
||||
...remainKeys,
|
||||
{ text: props.deleteText, type: 'delete', wider: true }
|
||||
]
|
||||
}
|
||||
|
||||
function splitCarKeys(): Array<Array<Key>> {
|
||||
const keys = carKeyboardLang.value === 'zh' ? CAR_KEYBOARD_AREAS.map((key) => ({ text: key })) : CAR_KEYBOARD_KEYS.map((key) => ({ text: key }))
|
||||
return [keys.slice(0, 30), keys.slice(30)]
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
const handlePress = (text: string, type: NumberKeyType) => {
|
||||
if (type === 'extra') {
|
||||
if (text === '') {
|
||||
return handleClose()
|
||||
} else if (text === 'ABC' || text === '省份') {
|
||||
const newLang = carKeyboardLang.value === 'zh' ? 'en' : 'zh'
|
||||
if (props.carLang) {
|
||||
emit('update:carLang', newLang)
|
||||
} else {
|
||||
carKeyboardLang.value = newLang
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const value = props.modelValue
|
||||
if (type === 'delete') {
|
||||
emit('delete')
|
||||
const newValue = value.slice(0, value.length - 1)
|
||||
emit('update:modelValue', newValue)
|
||||
if (props.mode === 'car' && newValue.length === 0 && props.autoSwitchLang) {
|
||||
carKeyboardLang.value = 'zh'
|
||||
}
|
||||
} else if (type === 'close') {
|
||||
handleClose()
|
||||
} else if (value.length < +props.maxlength) {
|
||||
emit('input', text)
|
||||
const newValue = value + text
|
||||
emit('update:modelValue', newValue)
|
||||
if (props.mode === 'car' && newValue.length === 1 && props.autoSwitchLang) {
|
||||
// 输入第一位(省份)后,自动切换到英文
|
||||
carKeyboardLang.value = 'en'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
Reference in New Issue
Block a user