更新:登录功能

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,168 @@
@import '../common/abstracts/variable';
@import '../common/abstracts/mixin';
.wot-theme-dark {
@include b(col-picker) {
@include e(list-item) {
@include when(disabled) {
color: $-dark-color3;
}
}
@include e(list-item-tip) {
color: $-dark-color-gray;
}
:deep(.wd-col-picker__arrow) {
color: $-dark-color;
}
@include e(list) {
color: $-dark-color;
}
@include e(selected) {
color: $-dark-color;
}
:deep(.wd-col-picker__cell--placeholder) {
.wd-cell__value {
color: $-dark-color-gray;
}
}
}
}
@include b(col-picker) {
@include edeep(cell) {
@include when(disabled) {
.wd-cell__value {
color: $-input-disabled-color;
cursor: not-allowed;
}
}
@include when(error) {
.wd-cell__value {
color: $-input-error-color;
}
.wd-col-picker__arrow {
color: $-input-error-color;
}
}
@include when(large) {
.wd-col-picker__arrow {
font-size: $-cell-icon-size-large;
}
}
@include m(placeholder) {
.wd-cell__value {
color: $-input-placeholder-color;
}
}
}
@include edeep(arrow) {
display: block;
font-size: $-cell-icon-size;
color: $-cell-arrow-color;
line-height: $-cell-line-height;
}
@include e(selected) {
height: $-col-picker-selected-height;
font-size: $-col-picker-selected-fs;
color: $-col-picker-selected-color;
overflow: hidden;
}
@include e(selected-container){
position: relative;
display: flex;
user-select: none;
}
@include e(selected-item) {
flex: 0 0 auto;
height: $-col-picker-selected-height;
line-height: $-col-picker-selected-height;
padding: $-col-picker-selected-padding;
@include when(selected) {
font-weight: $-col-picker-selected-fw;
}
}
@include e(selected-line) {
position: absolute;
bottom: 5px;
width: $-col-picker-line-width;
left: 0;
height: $-col-picker-line-height;
background: $-col-picker-line-color;
z-index: 1;
border-radius: calc($-col-picker-line-height / 2);
box-shadow: $-col-picker-line-box-shadow;
}
@include e(list-container){
position: relative;
}
@include e(list) {
height: $-col-picker-list-height;
padding-bottom: $-col-picker-list-padding-bottom;
box-sizing: border-box;
overflow: auto;
color: $-col-picker-list-color;
font-size: $-col-picker-list-fs;
-webkit-overflow-scrolling: touch;
}
@include e(list-item) {
display: flex;
padding: $-col-picker-list-item-padding;
align-items: flex-start;
@include when(selected) {
color: $-col-picker-list-color-checked;
:deep(.wd-col-picker__checked) {
opacity: 1;
}
}
@include when(disabled) {
color: $-col-picker-list-color-disabled;
}
}
@include e(list-item-label) {
line-height: 1.285;
}
@include e(list-item-tip) {
margin-top: 2px;
font-size: $-col-picker-list-fs-tip;
color: $-col-picker-list-color-tip;
}
@include edeep(checked) {
display: block;
margin-left: 4px;
font-size: $-col-picker-list-checked-icon-size;
color: $-col-picker-list-color-checked;
opacity: 0;
}
@include e(loading) {
display: flex;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
align-items: center;
justify-content: center;
}
}

View File

@@ -0,0 +1,166 @@
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeRequiredProp, makeStringProp, numericProp } from '../common/props'
import type { FormItemRule } from '../wd-form/types'
export const colPickerProps = {
...baseProps,
/**
* 选中项
*/
modelValue: makeRequiredProp(Array as PropType<Array<string | number>>),
/**
* 选择器数据,二维数组
*/
columns: makeArrayProp<Record<string, any>[]>(),
/**
* 选择器左侧文案
*/
label: String,
/**
* 设置左侧标题宽度
*/
labelWidth: makeStringProp('33%'),
/**
* 使用 label 插槽时设置该选项
*/
useLabelSlot: makeBooleanProp(false),
/**
* 使用默认插槽时设置该选项
*/
useDefaultSlot: makeBooleanProp(false),
/**
* 禁用
*/
disabled: makeBooleanProp(false),
/**
* 只读
*/
readonly: makeBooleanProp(false),
/**
* 选择器占位符
*/
placeholder: String,
/**
* 弹出层标题
*/
title: String,
/**
* 接收当前列的选中项 item、当前列下标、当前列选中项下标下一列数据处理函数 resolve、结束选择 finish
*/
columnChange: Function as PropType<ColPickerColumnChange>,
/**
* 自定义展示文案的格式化函数,返回一个字符串
*/
displayFormat: Function as PropType<ColPickerDisplayFormat>,
/**
* 确定前校验函数,接收 (value, resolve) 参数,通过 resolve 继续执行 pickerresolve 接收 1 个 boolean 参数
*/
beforeConfirm: Function as PropType<ColPickerBeforeConfirm>,
/**
* 选择器的值靠右展示
*/
alignRight: makeBooleanProp(false),
/**
* 是否为错误状态,错误状态时右侧内容为红色
*/
error: makeBooleanProp(false),
/**
* 是否必填
*/
required: makeBooleanProp(false),
/**
* 设置选择器大小可选值large
*/
size: String,
/**
* 选项对象中value 对应的 key
*/
valueKey: makeStringProp('value'),
/**
* 选项对象中,展示的文本对应的 key
*/
labelKey: makeStringProp('label'),
/**
* 选项对象中,提示文案对应的 key
*/
tipKey: makeStringProp('tip'),
/**
* loading 图标的颜色
*/
loadingColor: makeStringProp('#4D80F0'),
/**
* 点击遮罩是否关闭
*/
closeOnClickModal: makeBooleanProp(true),
/**
* 自动触发 column-change 事件来补全数据,当 columns 为空数组或者 columns 数组长度小于 value 数组长度时,会自动触发 column-change
*/
autoComplete: makeBooleanProp(false),
/**
* 弹窗层级
*/
zIndex: makeNumberProp(15),
/**
* 弹出面板是否设置底部安全距离iphone X 类型的机型)
*/
safeAreaInsetBottom: makeBooleanProp(true),
/**
* 是否超出隐藏
*/
ellipsis: makeBooleanProp(false),
/**
* 表单域 model 字段名,在使用表单校验功能的情况下,该属性是必填的
*/
prop: String,
/**
* 表单验证规则结合wd-form组件使用
*/
rules: makeArrayProp<FormItemRule>(),
/**
* 底部条宽度,单位像素
*/
lineWidth: numericProp,
/**
* 底部条高度,单位像素
*/
lineHeight: numericProp,
/**
* label 外部自定义样式
*/
customViewClass: makeStringProp(''),
/**
* value 外部自定义样式
*/
customLabelClass: makeStringProp(''),
customValueClass: makeStringProp(''),
/**
* 是否从页面中脱离出来,用于解决各种 fixed 失效问题 (H5: teleport, APP: renderjs, 小程序: root-portal)
*/
rootPortal: makeBooleanProp(false),
/**
* 必填标记位置可选值before、after
*/
markerSide: makeStringProp<'before' | 'after'>('before')
}
export type ColPickerProps = ExtractPropTypes<typeof colPickerProps>
export type ColPickerColumnChangeOption = {
selectedItem: Record<string, any>
index: number
rowIndex: number
resolve: (nextColumn: Record<string, any>[]) => void
finish: (isOk?: boolean) => void
}
export type ColPickerColumnChange = (option: ColPickerColumnChangeOption) => void
export type ColPickerDisplayFormat = (selectedItems: Record<string, any>[]) => string
export type ColPickerBeforeConfirm = (value: (string | number)[], selectedItems: Record<string, any>[], resolve: (isPass: boolean) => void) => void
export type ColPickerExpose = {
// 关闭picker弹框
close: () => void
// 打开picker弹框
open: () => void
}
export type ColPickerInstance = ComponentPublicInstance<ColPickerExpose, ColPickerProps>

View File

@@ -0,0 +1,498 @@
<template>
<view :class="`wd-col-picker ${customClass}`" :style="customStyle">
<wd-cell
v-if="!$slots.default"
:title="label"
:value="showValue || placeholder || translate('placeholder')"
:required="required"
:size="size"
:title-width="labelWidth"
:prop="prop"
:rules="rules"
:clickable="!disabled && !readonly"
:value-align="alignRight ? 'right' : 'left'"
:custom-class="cellClass"
:custom-style="customStyle"
:custom-title-class="customLabelClass"
:custom-value-class="customValueClass"
:ellipsis="ellipsis"
:use-title-slot="!!$slots.label"
:marker-side="markerSide"
@click="showPicker"
>
<template v-if="$slots.label" #title>
<slot name="label"></slot>
</template>
<template #right-icon>
<wd-icon v-if="showArrow" custom-class="wd-col-picker__arrow" name="arrow-right" />
</template>
</wd-cell>
<view v-else @click="showPicker">
<slot></slot>
</view>
<wd-action-sheet
v-model="pickerShow"
:duration="250"
:title="title || translate('title')"
:close-on-click-modal="closeOnClickModal"
:z-index="zIndex"
:safe-area-inset-bottom="safeAreaInsetBottom"
:root-portal="rootPortal"
@open="handlePickerOpend"
@close="handlePickerClose"
@closed="handlePickerClosed"
>
<view class="wd-col-picker__selected">
<scroll-view :scroll-x="true" scroll-with-animation :scroll-left="scrollLeft">
<view class="wd-col-picker__selected-container">
<view
v-for="(_, colIndex) in selectList"
:key="colIndex"
:class="`wd-col-picker__selected-item ${colIndex === currentCol && 'is-selected'}`"
@click="handleColClick(colIndex)"
>
{{ selectShowList[colIndex] || translate('select') }}
</view>
<view class="wd-col-picker__selected-line" :style="state.lineStyle"></view>
</view>
</scroll-view>
</view>
<view class="wd-col-picker__list-container">
<view
v-for="(col, colIndex) in selectList"
:key="colIndex"
class="wd-col-picker__list"
:style="colIndex === currentCol ? 'display: block;' : 'display: none;'"
>
<view
v-for="(item, index) in col"
:key="index"
:class="`wd-col-picker__list-item ${pickerColSelected[colIndex] && item[valueKey] === pickerColSelected[colIndex] && 'is-selected'} ${
item.disabled && 'is-disabled'
}`"
@click="chooseItem(colIndex, index)"
>
<view>
<view class="wd-col-picker__list-item-label">{{ item[labelKey] }}</view>
<view v-if="item[tipKey]" class="wd-col-picker__list-item-tip">{{ item[tipKey] }}</view>
</view>
<wd-icon custom-class="wd-col-picker__checked" name="check"></wd-icon>
</view>
<view v-if="loading" class="wd-col-picker__loading">
<wd-loading :color="loadingColor" />
</view>
</view>
</view>
</wd-action-sheet>
</view>
</template>
<script lang="ts">
export default {
name: 'wd-col-picker',
options: {
addGlobalClass: true,
virtualHost: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import wdIcon from '../wd-icon/wd-icon.vue'
import wdLoading from '../wd-loading/wd-loading.vue'
import wdActionSheet from '../wd-action-sheet/wd-action-sheet.vue'
import wdCell from '../wd-cell/wd-cell.vue'
import { computed, getCurrentInstance, onMounted, ref, watch, type CSSProperties, reactive } from 'vue'
import { addUnit, debounce, getRect, isArray, isBoolean, isDef, isFunction, objToStyle } from '../common/util'
import { useTranslate } from '../composables/useTranslate'
import { colPickerProps, type ColPickerExpose } from './types'
const { translate } = useTranslate('col-picker')
const $container = '.wd-col-picker__selected-container'
const $item = '.wd-col-picker__selected-item'
const props = defineProps(colPickerProps)
const emit = defineEmits(['close', 'update:modelValue', 'confirm'])
const pickerShow = ref<boolean>(false)
const currentCol = ref<number>(0)
const selectList = ref<Record<string, any>[][]>([])
const pickerColSelected = ref<(string | number)[]>([])
const selectShowList = ref<Record<string, any>[]>([])
const loading = ref<boolean>(false)
const isChange = ref<boolean>(false)
const lastSelectList = ref<Record<string, any>[][]>([])
const lastPickerColSelected = ref<(string | number)[]>([])
const scrollLeft = ref<number>(0)
const inited = ref<boolean>(false)
const isCompleting = ref<boolean>(false)
const state = reactive({
lineStyle: 'display:none;' // 激活项边框线样式
})
const { proxy } = getCurrentInstance() as any
const updateLineAndScroll = debounce(function (animation = true) {
setLineStyle(animation)
lineScrollIntoView()
}, 50)
const showValue = computed(() => {
const selectedItems = (props.modelValue || []).map((item, colIndex) => {
return getSelectedItem(item, colIndex, selectList.value)
})
if (props.displayFormat) {
return props.displayFormat(selectedItems)
} else {
return selectedItems
.map((item) => {
return item[props.labelKey]
})
.join('')
}
})
const cellClass = computed(() => {
const classes = ['wd-col-picker__cell']
if (props.disabled) classes.push('is-disabled')
if (props.readonly) classes.push('is-readonly')
if (props.error) classes.push('is-error')
if (!showValue.value) classes.push('wd-col-picker__cell--placeholder')
return classes.join(' ')
})
watch(
() => props.modelValue,
(newValue) => {
if (newValue === pickerColSelected.value) return
pickerColSelected.value = newValue
newValue.map((item, colIndex) => {
return getSelectedItem(item, colIndex, selectList.value)[props.labelKey]
})
handleAutoComplete()
},
{
deep: true,
immediate: true
}
)
watch(
() => props.columns,
(newValue, oldValue) => {
if (newValue.length && !isArray(newValue[0])) {
console.error('[wot ui] error(wd-col-picker): the columns props of wd-col-picker should be a two-dimensional array')
return
}
if (newValue.length === 0 && !oldValue) return
const newSelectedList = newValue.slice(0)
selectList.value = newSelectedList
selectShowList.value = pickerColSelected.value.map((item, colIndex) => {
return getSelectedItem(item, colIndex, newSelectedList)[props.labelKey]
})
lastSelectList.value = newSelectedList
if (newSelectedList.length > 0) {
currentCol.value = newSelectedList.length - 1
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.columnChange,
(fn) => {
if (fn && !isFunction(fn)) {
console.error('The type of columnChange must be Function')
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.displayFormat,
(fn) => {
if (fn && !isFunction(fn)) {
console.error('The type of displayFormat must be Function')
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.beforeConfirm,
(fn) => {
if (fn && !isFunction(fn)) {
console.error('The type of beforeConfirm must be Function')
}
},
{
deep: true,
immediate: true
}
)
// 是否展示箭头
const showArrow = computed(() => {
return !props.disabled && !props.readonly
})
onMounted(() => {
inited.value = true
})
// 打开弹框
function open() {
showPicker()
}
// 关闭弹框
function close() {
handlePickerClose()
}
function handlePickerOpend() {
updateLineAndScroll(false)
}
function handlePickerClose() {
pickerShow.value = false
emit('close')
}
function handlePickerClosed() {
if (isChange.value) {
setTimeout(() => {
selectList.value = lastSelectList.value.slice(0)
pickerColSelected.value = lastPickerColSelected.value.slice(0)
selectShowList.value = lastPickerColSelected.value.map((item, colIndex) => {
return getSelectedItem(item, colIndex, lastSelectList.value)[props.labelKey]
})
currentCol.value = lastSelectList.value.length - 1
isChange.value = false
}, 250)
}
}
function showPicker() {
const { disabled, readonly } = props
if (disabled || readonly) return
pickerShow.value = true
lastPickerColSelected.value = pickerColSelected.value.slice(0)
lastSelectList.value = selectList.value.slice(0)
}
function getSelectedItem(value: string | number, colIndex: number, selectList: Record<string, any>[][]) {
const { valueKey, labelKey } = props
if (selectList[colIndex]) {
const selecteds = selectList[colIndex].filter((item) => {
return item[valueKey] === value
})
if (selecteds.length > 0) {
return selecteds[0]
}
}
return {
[valueKey]: value,
[labelKey]: ''
}
}
function chooseItem(colIndex: number, index: number) {
const item = selectList.value[colIndex][index]
if (item.disabled) return
const newPickerColSelected = pickerColSelected.value.slice(0, colIndex)
newPickerColSelected.push(item[props.valueKey])
isChange.value = true
pickerColSelected.value = newPickerColSelected
selectList.value = selectList.value.slice(0, colIndex + 1)
selectShowList.value = newPickerColSelected.map((item, colIndex) => {
return getSelectedItem(item, colIndex, selectList.value)[props.labelKey]
})
if (selectShowList.value[colIndex] && colIndex === currentCol.value) {
updateLineAndScroll(true)
}
handleColChange(colIndex, item, index)
}
function handleColChange(colIndex: number, item: Record<string, any>, index: number, callback?: () => void) {
loading.value = true
const { columnChange, beforeConfirm } = props
columnChange &&
columnChange({
selectedItem: item,
index: colIndex,
rowIndex: index,
resolve: (nextColumn: Record<string, any>[]) => {
if (!isArray(nextColumn)) {
console.error('[wot ui] error(wd-col-picker): the data of each column of wd-col-picker should be an array')
return
}
const newSelectList = selectList.value.slice(0)
newSelectList[colIndex + 1] = nextColumn
selectList.value = newSelectList
loading.value = false
currentCol.value = colIndex + 1
updateLineAndScroll(true)
if (typeof callback === 'function') {
isCompleting.value = false
selectShowList.value = pickerColSelected.value.map((item, colIndex) => {
return getSelectedItem(item, colIndex, selectList.value)[props.labelKey]
})
callback()
}
},
finish: (isOk?: boolean) => {
// 每设置展示数据回显
if (typeof callback === 'function') {
loading.value = false
isCompleting.value = false
return
}
if (isBoolean(isOk) && !isOk) {
loading.value = false
return
}
if (beforeConfirm) {
beforeConfirm(
pickerColSelected.value,
pickerColSelected.value.map((item, colIndex) => {
return getSelectedItem(item, colIndex, selectList.value)
}),
(isPass: boolean) => {
if (isPass) {
onConfirm()
} else {
loading.value = false
}
}
)
} else {
onConfirm()
}
}
})
}
function onConfirm() {
isChange.value = false
loading.value = false
pickerShow.value = false
emit('update:modelValue', pickerColSelected.value)
emit('confirm', {
value: pickerColSelected.value,
selectedItems: pickerColSelected.value.map((item, colIndex) => {
return getSelectedItem(item, colIndex, selectList.value)
})
})
}
function handleColClick(index: number) {
isChange.value = true
currentCol.value = index
updateLineAndScroll(true)
}
/**
* @description 更新navBar underline的偏移量
* @param {Boolean} animation 是否伴随动画
*/
function setLineStyle(animation: boolean = true) {
if (!inited.value) return
const { lineWidth, lineHeight } = props
getRect($item, true, proxy)
.then((rects) => {
const lineStyle: CSSProperties = {}
if (isDef(lineWidth)) {
lineStyle.width = addUnit(lineWidth)
}
if (isDef(lineHeight)) {
lineStyle.height = addUnit(lineHeight)
lineStyle.borderRadius = `calc(${addUnit(lineHeight)} / 2)`
}
const rect = rects[currentCol.value]
let left = rects.slice(0, currentCol.value).reduce((prev, curr) => prev + Number(curr.width), 0) + Number(rect.width) / 2
lineStyle.transform = `translateX(${left}px) translateX(-50%)`
if (animation) {
lineStyle.transition = 'width 300ms ease, transform 300ms ease'
}
state.lineStyle = objToStyle(lineStyle)
})
.catch(() => {})
}
/**
* @description scroll-view滑动到active的tab_nav
*/
function lineScrollIntoView() {
if (!inited.value) return
Promise.all([getRect($item, true, proxy), getRect($container, false, proxy)])
.then(([navItemsRects, navRect]) => {
if (!isArray(navItemsRects) || navItemsRects.length === 0) return
// 选中元素
const selectItem = navItemsRects[currentCol.value]
// 选中元素之前的节点的宽度总和
const offsetLeft = navItemsRects.slice(0, currentCol.value).reduce((prev, curr) => prev + Number(curr.width), 0)
// scroll-view滑动到selectItem的偏移量
scrollLeft.value = offsetLeft - ((navRect as any).width - Number(selectItem.width)) / 2
})
.catch(() => {})
}
// 递归列数据补齐
function diffColumns(colIndex: number) {
// colIndex 为 -1 时item 为空对象,>=0 时则具有 value 属性
const item = colIndex === -1 ? {} : { [props.valueKey]: props.modelValue[colIndex] }
handleColChange(colIndex, item, -1, () => {
// 如果 columns 长度还小于 value 长度colIndex + 1继续递归补齐
if (selectList.value.length < props.modelValue.length) {
diffColumns(colIndex + 1)
}
})
}
function handleAutoComplete() {
if (props.autoComplete) {
// 如果 columns 数组长度为空,或者长度小于 value 的长度,自动触发 columnChange 来补齐数据
if (selectList.value.length < props.modelValue.length || selectList.value.length === 0) {
// isCompleting 是否在自动补全,锁操作
if (!isCompleting.value) {
// 如果 columns 长度为空,则传入的 colIndex 为 -1
const colIndex = selectList.value.length === 0 ? -1 : selectList.value.length - 1
diffColumns(colIndex)
}
isCompleting.value = true
}
}
}
defineExpose<ColPickerExpose>({
close,
open
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>