2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
export const cloneDeep = <T>(value: T, visited = new WeakMap()): T => {
if (value === null || typeof value !== 'object') {
return value
}
if (visited.has(value)) {
return visited.get(value)
}
if (Array.isArray(value)) {
const clonedArray = value.map((item) => cloneDeep(item, visited)) as any
visited.set(value, clonedArray)
return clonedArray
}
if (value instanceof Date) {
return new Date(value.getTime()) as any
}
if (value instanceof RegExp) {
const flags = value.flags
return new RegExp(value.source, flags) as any
}
const clonedObject = {} as T
visited.set(value, clonedObject)
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
clonedObject[key] = cloneDeep(value[key], visited)
}
}
const prototype = Object.getPrototypeOf(value)
Object.setPrototypeOf(clonedObject, cloneDeep(prototype, visited))
return clonedObject
}
+37
View File
@@ -0,0 +1,37 @@
/**
* 防抖函数
* @author jaylen
* @param fn 需要执行的方法
* @param delay 延迟时间
* @param immediate 是否立马执行
* @returns 防抖函数
*/
export const debounce = <T extends (...args: any[]) => any>(
fn: T,
delay: number,
immediate = false
) => {
let timer: ReturnType<typeof setTimeout> | null = null
let isInvoke = false
const _debounce = function (thisArg: any, ...args: Parameters<T>) {
if (timer) clearTimeout(timer)
if (immediate && !isInvoke) {
fn.apply(thisArg, args)
isInvoke = true
} else {
timer = setTimeout(() => {
fn.apply(thisArg, args)
isInvoke = false
}, delay)
}
}
// 取消防抖
_debounce.cancel = function () {
if (timer) clearTimeout(timer)
timer = null
isInvoke = false
}
return _debounce
}
+2
View File
@@ -0,0 +1,2 @@
export * from './unit'
export * from './select-query'
+43
View File
@@ -0,0 +1,43 @@
import type { ComponentInternalInstance } from 'vue'
/**
* 获取 SelectorQuery 对象实例
* @param instance 当前组件实例
* @returns SelectorQuery 对象实例
*/
export const createSelectorQuery = (instance: ComponentInternalInstance) => {
let query: UniApp.SelectorQuery | null = null
// #ifndef MP-ALIPAY
query = uni.createSelectorQuery().in(instance)
// #endif
// #ifdef MP-ALIPAY
query = uni.createSelectorQuery().in(null)
// #endif
return query
}
/**
* 获取节点布局信息
* @param query SelectorQuery 对象实例
* @param selector 需要查询的节点
* @returns 节点布局信息
*/
export const getSelectorNodeInfo = (
query: UniApp.SelectorQuery,
selector: string
): Promise<UniApp.NodeInfo> => {
return new Promise((resolve, reject) => {
query
.select(selector)
.boundingClientRect((res) => {
const selectRes: UniApp.NodeInfo = res as UniApp.NodeInfo
if (selectRes) {
resolve(selectRes)
} else {
reject(new Error(`未找到对应节点: ${selector}`))
}
})
.exec()
})
}
+19
View File
@@ -0,0 +1,19 @@
import { isString } from '../types'
/**
* 格式化dom的尺寸单位
* @param value 待处理的值
* @param unit 默认单位
* @param empty 是否返回空值
* @returns 处理后的值
*/
export const formatDomSizeValue = (
value: string | number,
unit = 'rpx',
empty = true
): string => {
if (!value) return empty ? '' : `0${unit}`
if (isString(value) && /(^calc)|(%|vw|vh|px|rpx|auto)$/.test(value as string))
return value as string
return `${value}${unit}`
}
+24
View File
@@ -0,0 +1,24 @@
import { isString } from '@vue/shared'
class TuniaoUIError extends Error {
constructor(message: string) {
super(message)
this.name = 'TuniaoUIError'
}
}
export function throwError(scope: string, msg: string): never {
throw new TuniaoUIError(`[${scope}] ${msg}`)
}
export function debugWarn(err: Error): void
export function debugWarn(scope: string, message: string): void
export function debugWarn(scope: string | Error, message?: string): void {
if (process.env.NODE_ENV !== 'production') {
const error: Error = isString(scope)
? new TuniaoUIError(`[${scope}] ${message}`)
: scope
// eslint-disable-next-line no-console
console.warn(error)
}
}
+29
View File
@@ -0,0 +1,29 @@
/**
* 截取指定长度的数值
* @param value 待截取的数值
* @param len 截取的长度
* @param prefixZero 如果只有一位是否添加0
* @returns 截取后的数值字符串
*/
export const formatNumber = (
value: string | number,
len = 2,
prefixZero = true
): string => {
let number: number | string = 0
// 判断传入的值是什么类型
if (typeof value === 'string') {
// 如果为空字符串直接返回
if (value === '') return value
number = Number(value)
} else if (typeof value === 'number') {
number = value
}
if (Number.isNaN(number) || number === 0) return prefixZero ? '00' : '0'
const maxNumber = Math.pow(10, len) - 1
if (number > maxNumber) return `${maxNumber}+`
number = String(number)
return prefixZero
? `00${number}`.slice(Math.max(0, number.length > 2 ? 2 : number.length))
: number
}
+13
View File
@@ -0,0 +1,13 @@
export * from './vue'
export * from './debounce'
export * from './throttle'
export * from './objects'
export * from './types'
export * from './typescript'
export * from './dom'
export * from './rand'
export * from './uniapp'
export * from './error'
export * from './format'
export * from './clone-deep'
export * from './is-empty'
+17
View File
@@ -0,0 +1,17 @@
export const isEmptyVariableInDefault = <T = any>(
variable: any,
defaultValue: any = undefined
): T => {
return variable === undefined || variable === null ? defaultValue : variable
}
export const isEmptyDoubleVariableInDefault = <T = any>(
variable1: any,
variable2: any,
defaultValue: any = undefined
): T => {
return isEmptyVariableInDefault(
variable1,
isEmptyVariableInDefault(variable2, defaultValue)
)
}
+24
View File
@@ -0,0 +1,24 @@
import { get, set } from '../libs/lodash'
import type { Entries } from 'type-fest'
import type { Arrayable } from '.'
export { hasOwn } from '@vue/shared'
export const keysOf = <T extends object>(arr: T) =>
Object.keys(arr) as Array<keyof T>
export const entriesOf = <T extends object>(arr: T) =>
Object.entries(arr) as Entries<T>
export const getProp = <T = any>(
obj: Record<string, any>,
path: Arrayable<string>,
defaultValue?: any
): { value: T } => {
return {
get value() {
return get(obj, path, defaultValue)
},
set value(val: any) {
set(obj, path, val)
},
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* 生成010000的随机数
*/
export const generateId = (): number => Math.floor(Math.random() * 10000)
/**
* 生成随机数(0 max
* @param max 最大值
* @returns 随机数
*/
export const getRandomInt = (max: number) =>
Math.floor(Math.random() * Math.floor(max))
+61
View File
@@ -0,0 +1,61 @@
interface ThrottleOptions {
leading: boolean
trailing: boolean
}
/**
* 节流函数
* @author jaylen
* @param fn 节流执行函数
* @param interval 间隔时间
* @param option 配置参数
* @returns 节流函数
*/
export const throttle = <T extends (...args: any[]) => any>(
fn: T,
interval: number,
option: ThrottleOptions
) => {
const { leading, trailing } = option
let lastTime = 0
let timer: ReturnType<typeof setTimeout> | null = null
const _throttle = function (
thisArg: ThisParameterType<T>,
...args: Parameters<T>
) {
const nowTime: number = Date.now()
// 判断是否需要第一次执行
if (!lastTime && !leading) lastTime = nowTime
// 剩余执行时间
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
// 如果当前已经触发执行了,但是事件还没有达到时间间隔,那么就清除定时器
if (timer) {
clearTimeout(timer)
timer = null
}
fn.apply(thisArg, args)
lastTime = nowTime
return
}
// 是否需要执行最后一次
if (trailing && !timer) {
timer = setTimeout(() => {
fn.apply(thisArg, args)
timer = null
lastTime = leading ? Date.now() : 0
}, remainTime)
}
}
// 取消节流
_throttle.cancel = function () {
if (timer) clearTimeout(timer)
timer = null
lastTime = 0
}
return _throttle
}
+34
View File
@@ -0,0 +1,34 @@
import { isArray, isObject, isString } from '@vue/shared'
import { isNil } from '../libs/lodash'
export {
isArray,
isFunction,
isObject,
isString,
isDate,
isPromise,
isSymbol,
} from '@vue/shared'
export { isBoolean, isNumber } from '../libs/lodash'
export const isUndefined = (val: any): val is undefined => val === undefined
export const isEmpty = (val: unknown) =>
(!val && val !== 0) ||
(isArray(val) && val.length === 0) ||
(isObject(val) && !Object.keys(val).length)
export const isElement = (e: unknown): e is Element => {
if (typeof Element === 'undefined') return false
return e instanceof Element
}
export const isPropAbsent = (prop: unknown): prop is null | undefined => {
return isNil(prop)
}
export const isStringNumber = (val: string): boolean => {
if (!isString(val)) return false
return !Number.isNaN(Number(val))
}
+10
View File
@@ -0,0 +1,10 @@
export const mutable = <T extends readonly any[] | Record<string, unknown>>(
val: T
) => val as Mutable<typeof val>
export type Mutable<T> = { -readonly [P in keyof T]: T[P] }
export type HTMLElementCustomized<T> = HTMLElement & T
export type Nullable<T> = T | null
export type Arrayable<T> = T | T[]
export type Awaitable<T> = T | Promise<T>
+1
View File
@@ -0,0 +1 @@
export * from './router'
+93
View File
@@ -0,0 +1,93 @@
import { debugWarn } from '../error'
import { isEmptyVariableInDefault } from '../is-empty'
type navType = 'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab'
/**
* 返回上一页
* @param indexUrl 首页地址
* @param delta 返回的页面数,如果 delta 大于现有页面数,则返回到首页
*/
export function tnNavBack(indexUrl?: string, delta = 1) {
const indexPageUrl = isEmptyVariableInDefault(indexUrl, '/pages/index/index')
// 通过判断当前页面的页面栈信息,是否有上一页进行返回,如果没有则跳转到首页
const pages = getCurrentPages()
if (pages?.length) {
const firstPage = pages[0]
if (
pages.length === 1 &&
(!firstPage.route || firstPage?.route != indexPageUrl)
) {
return tnNavPage(indexPageUrl, 'reLaunch')
} else {
uni.navigateBack({
delta,
})
return Promise.resolve()
}
} else {
return tnNavPage(indexPageUrl, 'reLaunch')
}
}
/**
* 跳转到指定页面
* @param url 页面地址
* @param type 跳转类型
*/
export function tnNavPage(url: string, type: navType = 'navigateTo') {
function handelNavFail(err: any) {
debugWarn('tnNavPage', `跳转页面失败: ${err}`)
}
return new Promise<void>((resolve, reject) => {
switch (type) {
case 'navigateTo':
uni.navigateTo({
url,
success: () => {
resolve()
},
fail: (err) => {
handelNavFail(err)
reject(err)
},
})
break
case 'redirectTo':
uni.redirectTo({
url,
success: () => {
resolve()
},
fail: (err) => {
handelNavFail(err)
reject(err)
},
})
break
case 'reLaunch':
uni.reLaunch({
url,
success: () => {
resolve()
},
fail: (err) => {
handelNavFail(err)
reject(err)
},
})
break
case 'switchTab':
uni.switchTab({
url,
success: () => {
resolve()
},
fail: (err) => {
handelNavFail(err)
reject(err)
},
})
}
})
}
+199
View File
@@ -0,0 +1,199 @@
/**
* 验证电子邮箱格式
*/
export const isEmail = (value: string): boolean => {
return /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/.test(
value
)
}
/**
* 验证手机格式
*/
export const isMobile = (value: string): boolean => {
return /^1[3-9]\d{9}$/.test(value)
}
/**
* 验证URL格式
*/
export const isUrl = (value: string): boolean => {
return /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?/.test(value)
}
/**
* 验证日期格式
*/
export const isDate = (value: string): boolean => {
return !/Invalid|NaN/.test(new Date(value).toString())
}
/**
* 验证ISO类型的日期格式
*/
export const isDateISO = (value: string): boolean => {
return /^\d{4}[/-](0?[1-9]|1[012])[/-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
}
/**
* 验证十进制数字
*/
export const isNumber = (value: string): boolean => {
// eslint-disable-next-line no-useless-escape
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}
/**
* 验证整数
*/
export const isDigits = (value: string): boolean => {
return /^\d+$/.test(value)
}
/**
* 验证身份证号码
*/
export const isIdCard = (value: string): boolean => {
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
value
)
}
/**
* 是否车牌号
*/
export const isCarNo = (value: string): boolean => {
// 新能源车牌
const xreg =
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
// 旧车牌
const creg =
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
if (value.length === 7) {
return creg.test(value)
} else if (value.length === 8) {
return xreg.test(value)
} else {
return false
}
}
/**
* 金额,只允许2位小数
*/
export const isAmount = (value: string): boolean => {
//金额,只允许保留两位小数
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
}
/**
* 中文
*/
export const isChinese = (value: string): boolean => {
// eslint-disable-next-line unicorn/escape-case
const reg = /^[\u4e00-\u9fa5]+$/gi
return reg.test(value)
}
/**
* 只能输入字母
*/
export const isLetter = (value: string): boolean => {
return /^[a-zA-Z]*$/.test(value)
}
/**
* 只能是字母或者数字
*/
export const isEnOrNum = (value: string): boolean => {
//英文或者数字
const reg = /^[0-9a-zA-Z]*$/g
return reg.test(value)
}
/**
* 验证是否包含某个值
*/
export const isContains = (value: string, param: string): boolean => {
return value.includes(param)
}
/**
* 验证一个值范围[min, max]
*/
export const isRange = (
value: string | number,
param: (string | number)[]
): boolean => {
return value >= param[0] && value <= param[1]
}
/**
* 验证一个长度范围[min, max]
*/
export const isRangeLength = (
value: string,
param: (string | number)[]
): boolean => {
return value.length >= Number(param[0]) && value.length <= Number(param[1])
}
/**
* 是否固定电话
*/
export const isLandline = (value: string): boolean => {
const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
return reg.test(value)
}
/**
* 判断是否为空
*/
export const isEmpty = (value: any): boolean => {
switch (typeof value) {
case 'undefined':
return true
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0)
return true
break
case 'boolean':
if (!value) return true
break
case 'number':
if (0 === value || Number.isNaN(value)) return true
break
case 'object':
if (null === value) return true
if (Object.keys(value).length === 0) return true
return false
}
return false
}
/**
* 是否json字符串
*/
export const isJsonString = (value: string): boolean => {
if (typeof value == 'string') {
try {
const obj = JSON.parse(value)
if (typeof obj == 'object' && obj) {
return true
} else {
return false
}
// eslint-disable-next-line unicorn/prefer-optional-catch-binding
} catch (e) {
return false
}
}
return false
}
/**
* 是否短信验证码
*/
export const isMessageCode = (value: string, len = 6): boolean => {
return new RegExp(`^\\d{${len}}$`).test(value)
}
+9
View File
@@ -0,0 +1,9 @@
import { definePropType } from './props'
export const iconPropType = definePropType<string>([String])
export const FormValidateIconsMap = {
validating: 'loading',
success: 'success-circle',
error: 'close-circle',
}
+4
View File
@@ -0,0 +1,4 @@
export * from './typescript'
export * from './install'
export * from './props'
export * from './icon'
+56
View File
@@ -0,0 +1,56 @@
import { isEmptyVariableInDefault } from '../is-empty'
import type { App, Directive } from 'vue'
import type { SFCInstallWithContext, SFCWithInstall } from './typescript'
// 注册组件
export const withInstall = <T, E extends Record<string, any>>(
main: T,
extra?: E
) => {
// 将组件注册到应用程序中
;(main as SFCWithInstall<T>).install = (app: App) => {
for (const comp of [
main,
...Object.values(isEmptyVariableInDefault<E>(extra, {})),
]) {
app.component(comp.name, comp)
}
}
// 为组件添加额外的属性
if (extra) {
for (const [key, comp] of Object.entries(extra)) {
;(main as any)[key] = comp
}
}
return main as SFCWithInstall<T> & E
}
// 将 fn 包装成一个带有 install 方法的 Vue 3 插件,并返回包装后的插件函数
export const withInstallFunction = <T>(fn: T, name: string) => {
;(fn as SFCWithInstall<T>).install = (app: App) => {
;(fn as SFCInstallWithContext<T>)._content = app._context
app.config.globalProperties[name] = fn
}
return fn as SFCInstallWithContext<T>
}
// 注册指令
export const withInstallDirective = <T extends Directive>(
directive: T,
name: string
) => {
;(directive as SFCWithInstall<T>).install = (app: App) => {
app.directive(name, directive)
}
}
// 返回一个新的组件对象,这个组件对象具有一个空的 install 方法
export const withNoopInstall = <T>(component: T) => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
;(component as SFCWithInstall<T>).install = () => {}
return component as SFCWithInstall<T>
}
+3
View File
@@ -0,0 +1,3 @@
export * from './util'
export * from './types'
export * from './runtime'
+124
View File
@@ -0,0 +1,124 @@
/* eslint-disable eslint-comments/no-unlimited-disable */
import { warn } from 'vue'
import { fromPairs } from '../../../libs/lodash'
import { isObject } from '../../types'
import { hasOwn } from '../../objects'
import type { PropType } from 'vue'
import type {
IfNativePropType,
IfTnProp,
NativePropType,
TnProp,
TnPropConvert,
TnPropFinalized,
TnPropInput,
TnPropMergeType,
} from './types'
export const tnPropKey = '__tnPropKey'
export const definePropType = <T>(val: any): PropType<T> => val
export const isTnProp = (val: unknown): val is TnProp<any, any, any> =>
isObject(val) && !!(val as any)[tnPropKey]
/**
* 生成 prop,能更好地优化类型
* @example
// limited options
// the type will be PropType<'light' | 'dark'>
buildProp({
type: String,
values: ['light', 'dark'],
} as const)
* @example
// limited options and other types
// the type will be PropType<'small' | 'large' | number>
buildProp({
type: [String, Number],
values: ['small', 'large'],
validator: (val: unknown): val is number => typeof val === 'number',
} as const)
*/
// eslint-disable-next-line eslint-comments/no-duplicate-disable
// eslint-disable-next-line eslint-comments/no-unlimited-disable
/* eslint-disable */
export const buildProp = <
Type = never,
Value = never,
Validator = never,
Default extends TnPropMergeType<Type, Value, Validator> = never,
Required extends boolean = false
>(
prop: TnPropInput<Type, Value, Validator, Default, Required>,
key?: string
): TnPropFinalized<Type, Value, Validator, Default, Required> => {
if (!isObject(prop) || isTnProp(prop)) return prop as any
const { values, required, default: defaultValue, type, validator } = prop
const _validator =
values || validator
? (val: unknown) => {
let valid = false
let allowedValues: unknown[] = []
if (values) {
allowedValues = Array.from(values)
if (hasOwn(prop, 'default')) {
allowedValues.push(defaultValue)
}
valid ||= allowedValues.includes(val)
}
if (validator) valid ||= validator(val)
if (!valid && allowedValues.length > 0) {
const allowValuesText = [...new Set(allowedValues)]
.map((value) => JSON.stringify(value))
.join(', ')
warn(
`Invalid prop: validation failed${
key ? ` for prop "${key}"` : ''
}. Expected one of [${allowValuesText}], got value ${JSON.stringify(
val
)}.`
)
}
return valid
}
: undefined
const tnProp: any = {
type,
required: !!required,
validator: _validator,
[tnPropKey]: true,
}
if (hasOwn(prop, 'default')) tnProp.default = defaultValue
return tnProp
}
export const buildProps = <
Props extends Record<
string,
| { [tnPropKey]: true }
| NativePropType
| TnPropInput<any, any, any, any, any>
>
>(
props: Props
): {
[K in keyof Props]: IfTnProp<
Props[K],
Props[K],
IfNativePropType<Props[K], Props[K], TnPropConvert<Props[K]>>
>
} =>
fromPairs(
Object.entries(props).map(([key, option]) => [
key,
buildProp(option as any, key),
])
) as any
/* eslint-enable */
+133
View File
@@ -0,0 +1,133 @@
import type { ExtractPropTypes, PropType } from 'vue'
import type { tnPropKey } from './runtime'
import type { IfNever, UnknowToNever, WriteableArray } from './util'
type Value<T> = T[keyof T]
/**
* 提取单个 prop 的参数类型
*
* @example
* ExtractPropType<{ type: StringConstructor }> => string | undefined
* ExtractPropType<{ type: StringConstructor, required: true }> => string
* ExtractPropType<{ type: BooleanConstructor }> => boolean
*/
export type ExtractPropType<T extends object> = Value<
ExtractPropTypes<{ key: T }>
>
/**
* 通过 `ExtractPropTypes` 提取类型,接受 `PropType<T>`、`XXXConstructor`、`never`...
*
* @example
* ResolvePropType<BooleanConstructor> => boolean
* ResolvePropType<PropType<T>> => T
*/
export type ResolvePropType<T> = IfNever<
T,
never,
ExtractPropType<{
type: WriteableArray<T>
required: true
}>
>
/**
* 合并 Type、Value、Validator 的类型
*
* @example
* EpPropMergeType<StringConstructor, '1', 1> => 1 | "1" // ignores StringConstructor
* EpPropMergeType<StringConstructor, never, number> => string | number
*/
export type TnPropMergeType<Type, Value, Validator> =
| IfNever<UnknowToNever<Value>, ResolvePropType<Type>, never>
| UnknowToNever<Value>
| UnknowToNever<Validator>
/**
* 处理输入参数的默认值(约束)
*/
export type TnPropInputDefault<
Required extends boolean,
Default
> = Required extends true
? never
: Default extends Record<string, unknown> | Array<any>
? () => Default
: (() => Default) | Default
/**
* 原生 prop `类型,BooleanConstructor`、`StringConstructor`、`null`、`undefined` 等
*/
export type NativePropType =
| ((...args: any) => any)
| { new (...args: any): any }
| undefined
| null
export type IfNativePropType<T, Y, N> = [T] extends [NativePropType] ? Y : N
/**
* prop 输入参数(约束)
*
* @example
* EpPropInput<StringConstructor, 'a', never, never, true>
* {
type?: StringConstructor | undefined;
required?: true | undefined;
values?: readonly "a"[] | undefined;
validator?: ((val: any) => boolean) | ((val: any) => val is never) | undefined;
default?: undefined;
}
*/
export type TnPropInput<
Type,
Value,
Validator,
Default extends TnPropMergeType<Type, Value, Validator>,
Required extends boolean
> = {
type?: Type
required?: Required
values?: readonly Value[]
validator?: ((val: any) => val is Validator) | ((val: any) => boolean)
default?: TnPropInputDefault<Required, Default>
}
/**
* prop 输出参数(约束)
*
* @example
* EpProp<'a', 'b', true>
* {
readonly type: PropType<"a">;
readonly required: true;
readonly validator: ((val: unknown) => boolean) | undefined;
readonly default: "b";
__epPropKey: true;
}
*/
export type TnProp<Type, Default, Required> = {
readonly type: PropType<Type>
readonly required: [Required] extends [true] ? true : false
readonly validator: ((val: unknown) => boolean) | undefined
[tnPropKey]: true
} & IfNever<Default, unknown, { readonly default: Default }>
export type IfTnProp<T, Y, N> = T extends { [tnPropKey]: true } ? Y : N
export type TnPropConvert<Input> = Input extends TnPropInput<
infer Type,
infer Value,
infer Validator,
any,
infer Required
>
? TnPropFinalized<Type, Value, Validator, Input['default'], Required>
: never
export type TnPropFinalized<Type, Value, Validator, Default, Required> = TnProp<
TnPropMergeType<Type, Value, Validator>,
UnknowToNever<Default>,
Required
>
export {}
+10
View File
@@ -0,0 +1,10 @@
export type Writable<T> = { -readonly [P in keyof T]: T[P] }
export type WriteableArray<T> = T extends readonly any[] ? Writable<T> : T
export type IfNever<T, Y = true, N = false> = [T] extends [never] ? Y : N
export type IfUnknown<T, Y, N> = [unknown] extends [T] ? Y : N
export type UnknowToNever<T> = IfUnknown<T, never, T>
export {}
+9
View File
@@ -0,0 +1,9 @@
import type { AppContext, Plugin, Ref } from 'vue'
export type SFCWithInstall<T> = T & Plugin
export type SFCInstallWithContext<T> = SFCWithInstall<T> & {
_content: AppContext | null
}
export type MaybeRef<T> = T | Ref<T>