SOURCE

console 命令行工具 X clear

                    
>
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;
}

本项目引用的自定义外部资源