更新:登录功能

This commit is contained in:
2025-11-04 12:37:04 +08:00
commit a21fb92916
897 changed files with 51500 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
@import "./../common/abstracts/_mixin.scss";
@import "./../common/abstracts/variable.scss";
.wot-theme-dark {
@include b(number-keyboard) {
background: $-dark-background5;
@include e(header){
color: $-dark-color;
}
}
}
@include b(number-keyboard) {
width: 100%;
background: $-number-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: $-number-keyboard-title-height;
padding-top: 6px;
color: $-number-keyboard-title-color;
font-size: $-number-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;
}
@include e(close) {
position: absolute;
display: flex;
align-items: center;
right: 0;
height: 100%;
padding: $-number-keyboard-close-padding;
color: $-number-keyboard-close-color;
font-size: $-number-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;
}
}

View File

@@ -0,0 +1,81 @@
@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: $-number-keyboard-key-height;
font-size: $-number-keyboard-key-font-size;
line-height: 1.5;
background: $-number-keyboard-key-background;
border-radius: $-number-keyboard-key-border-radius;
&:active {
background-color: $-number-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: $-number-keyboard-delete-font-size;
}
@include m(active) {
background-color: $-number-keyboard-key-active-color;
}
@include m(close) {
color: $-number-keyboard-button-text-color;
background: $-number-keyboard-button-background;
&:active {
background: $-number-keyboard-button-background;
opacity: $-number-keyboard-button-active-opacity;
}
}
@include edeep(loading-icon) {
color: $-number-keyboard-button-text-color;
}
@include edeep(icon) {
font-size: $-number-keyboard-icon-size;
}
}

View File

@@ -0,0 +1,73 @@
<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 wdLoading from '../../wd-loading/wd-loading.vue'
import wdIcon from '../../wd-icon/wd-icon.vue'
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>

View File

@@ -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)
}

View File

@@ -0,0 +1,83 @@
import type { PropType } from 'vue'
import { baseProps, makeBooleanProp, makeNumberProp, makeStringProp } from '../common/props'
export type KeyboardMode = 'default' | 'custom'
export type KeyType = '' | 'delete' | 'extra' | 'close'
export interface Key {
text?: number | string // key文本
type?: KeyType // key的类型
wider?: boolean // 是否占2个key的宽度
}
export const numberKeyboardProps = {
...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)
}

View File

@@ -0,0 +1,151 @@
<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-number-keyboard ${customClass}`" :style="customStyle">
<view class="wd-number-keyboard__header" v-if="showHeader">
<slot name="title" v-if="showTitle">
<text class="wd-number-keyboard__title">{{ title }}</text>
</slot>
<view class="wd-number-keyboard__close" hover-class="wd-number-keyboard__close--hover" v-if="showClose" @click="handleClose">
<text>{{ closeText }}</text>
</view>
</view>
<view class="wd-number-keyboard__body">
<view class="wd-number-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-number-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>
</view>
</wd-popup>
</template>
<script lang="ts">
export default {
name: 'wd-number-keyboard',
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import wdPopup from '../wd-popup/wd-popup.vue'
import { computed, ref, useSlots, watch } from 'vue'
import WdKey from './key/index.vue'
import { numberKeyboardProps, type Key } from './types'
import type { NumberKeyType } from './key/types'
const props = defineProps(numberKeyboardProps)
const emit = defineEmits(['update:visible', 'input', 'close', 'delete', 'update:modelValue'])
const slots = useSlots()
const show = ref(props.visible)
watch(
() => props.visible,
(newValue) => {
show.value = newValue
}
)
const keys = computed(() => (props.mode === 'custom' ? genCustomKeys() : genDefaultKeys()))
const showClose = computed(() => {
return props.closeText && props.mode === 'default'
})
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
}
const handleClose = () => {
emit('close')
emit('update:visible', false)
}
const handlePress = (text: string, type: NumberKeyType) => {
if (text === '' && type === 'extra') {
return handleClose()
}
const value = props.modelValue
if (type === 'delete') {
emit('delete')
emit('update:modelValue', value.slice(0, value.length - 1))
} else if (type === 'close') {
handleClose()
} else if (value.length < +props.maxlength) {
emit('input', text)
emit('update:modelValue', value + text)
}
}
</script>
<style lang="scss">
@import './index.scss';
</style>