console
import { createApp, reactive, nextTick } from 'https://ax.minicg.com/pvue.es.js';
const { log, dir, table, clear, warn, error } = console
clear()
const tooltipDirective = (ctx) => {
const tooltip = document.createElement("div")
tooltip.style.position = "absolute"
tooltip.style.background = "black"
tooltip.style.color = "white"
tooltip.style.padding = "5px"
tooltip.style.borderRadius = "3px"
tooltip.style.fontSize = "12px"
tooltip.style.visibility = "hidden"
tooltip.style.transition = "opacity 0.2s"
tooltip.textContent = ctx.get() // 获取传入的提示文本
document.body.appendChild(tooltip)
const showTooltip = (event) => {
const rect = ctx.el.getBoundingClientRect(); // 获取目标元素的位置和尺寸
const tooltipWidth = tooltip.offsetWidth;
const tooltipHeight = tooltip.offsetHeight;
// 计算 tooltip 的位置,右侧居中
const top = rect.top + rect.height / 2 - tooltipHeight / 2
const left = rect.right + 5 // 离元素10px
tooltip.style.top = `${top}px`
tooltip.style.left = `${left}px`
tooltip.style.visibility = "visible"
}
const hideTooltip = () => {
tooltip.style.visibility = "hidden"
};
ctx.el.addEventListener("mouseenter", showTooltip)
ctx.el.addEventListener("mouseleave", hideTooltip)
return () => {
tooltip.remove()
}
}
createApp({
Root,
Header,
Input,
Button,
})
.directive("tooltip", tooltipDirective)
.mount()
function Root() {
return {
$template: `
<div :class="['flex flex-col gap-4 px-4 pt-6 text-gray-700 bg-white rounded-2xl shadow-xl', size]">
<div v-scope="Header({
title: 'Input',
subtitle: 'Inspired by ElementUI',
icon: 'refresh',
})"></div>
<div class="flex flex-col gap-4 w-full">
<div v-scope="Input({
label: '账号',
type: 'text',
prefixIcon: 'user-3',
value: usr,
onUpdate: value => usr = value,
})"></div>
<div class="flex gap-3">
<div v-scope="Input({
label: '密码',
type: 'password',
prefixIcon: 'lock-unlock',
value: pwd,
onUpdate: value => pwd = value,
})" class="w-1/2"></div>
<div v-scope="Input({
label: '验证码',
type: 'text',
maxLength: 4,
prefixIcon: 'robot-2',
value: verify,
clearable: false,
captcha: 'https://ax.minicg.com/images/vcode.jpg',
onUpdate: value => verify = value,
})" class="w-1/2"></div>
</div>
<div v-scope="Input({
label: '手机',
type: 'tel',
placeholder: '请输入手机号码',
prefixIcon: 'smartphone',
value: phone,
onUpdate: value => phone = value,
})"></div>
<div v-scope="Input({
type: 'email',
placeholder: '请输入邮箱',
prefixIcon: 'mail',
suffixIcon: 'edit-box',
value: email,
onUpdate: value => email = value,
})" v-tooltip="'邮箱'"></div>
</div>
<div v-scope="Button({ label: '控制台打印', onClick: print })"></div>
</div>
`,
usr: 'admin',
pwd: '123',
verify: 'SNNV',
phone: '',
email: '@',
width: 375,
height: 667,
print(){
const { usr, pwd, verify, email } = this
log({ usr, pwd, verify, email })
},
get size() {
return `w-[${this.width}px] h-[${this.height}px]`
}
}
}
function Header(props = {}) {
const {
title = '',
subtitle = '',
icon = null,
onClick = null,
} = props
return {
$template: `
<div class="flex justify-between items-start font-medium text-2xl text-black">
<div class="flex flex-col justify-center gap-1">
<h2>{{title}}</h2>
<p class="text-xs text-black/40">{{subtitle}}</p>
</div>
<i :class="iconClass" @click="onClick"></i>
</div>
`,
title,
subtitle,
get iconClass() {
const iconStr = icon ? `ri-${icon}-line` : ''
return `${iconStr} text-xl text-indigo-500 px-1 cursor-pointer duration-200 hover:rotate-180 active:opacity-50`
},
onClick() {
// const { events } = this.appState
// events.dispatchEvent( new CustomEvent('REFRESH_DATA') )
}
}
}
function Input(props = {}) {
// 解构并设置默认值
const {
label = '',
type = 'text',
placeholder = '请输入...',
prefixIcon = null,
suffixIcon = null,
onUpdate = null,
onKeyUp = null,
onChange = null,
value = '',
clearable = true,
minLength = null,
maxLength = null,
captcha = null,
} = props
return {
$template: `
<div class="group flex flex-col justify-center gap-1 w-full" ref="wrapper" @vue:mounted="mounted()">
<span v-if="label!==''" class="text-sm text-gray-500">{{ label }}</span>
<div class="flex border border-gray-300 overflow-hidden rounded duration-300 hover:border-indigo-500 focus-within:ring-2 focus-within:ring-indigo-100">
<div v-if="prefixIcon" class="flex justify-center items-center pl-2 h-[36px] text-sm text-gray-400" v-html="prefixIcon"></div>
<input v-model = "value"
:class = "inputClass"
:type = "type"
:placeholder = "placeholder"
:minlength = "minLength"
:maxlength = "maxLength"
@keyup = "onKeyUp"
@change = "onChange"
/>
<div v-if="value!==''&&clearable" class="opacity-0 flex justify-center items-center pr-2 h-[36px] text-sm text-gray-400 cursor-pointer duration-300 group-hover:opacity-100 hover:text-gray-500" @click="value=''">
<i class="ri-close-circle-line"></i>
</div>
<div v-if="captcha" class="flex justify-center items-center h-[36px] py-1 pr-1 text-sm text-gray-400">
<img :src="captcha" class="h-full" />
</div>
<div v-if="suffixIcon" class="flex justify-center items-center pr-2 h-[36px] text-sm text-gray-400" v-html="suffixIcon"></div>
</div>
</div>
`,
label,
type,
value,
placeholder,
clearable,
minLength,
maxLength,
captcha,
get inputClass() {
return `flex-1 w-full h-[36px] px-2 text-sm bg-transparent !border-0 !ring-0`
},
get prefixIcon() {
return prefixIcon ? `<i class="ri-${prefixIcon}-line"></i>` : ''
},
get suffixIcon() {
return suffixIcon ? `<i class="ri-${suffixIcon}-line"></i>` : ''
},
onKeyUp(e) {
if (onUpdate) { onUpdate(this.value) }
if (onKeyUp) { onKeyUp(e) }
// this.$refs.wrapper.parentNode.setAttribute('bind', this.value)
},
onChange(e) {
if (onChange) { onChange(e) }
},
mounted() {
this.value = value
}
};
}
function Button(props = {}) {
const {
label = '按钮',
onClick = () => {}
} = props
return {
$template: `
<button class="w-full bg-indigo-500 text-white text-sm py-2 rounded !outline-none duration-200 hover:bg-indigo-400 active:bg-indigo-700" @click="onClick">{{label}}</button>
`,
label,
onClick,
}
}
<div v-scope="Root()"></div>
html, body {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}