feat: add oaer

v3
veypi 1 month ago
parent 42115a4cee
commit 3cadef35be

@ -10,7 +10,7 @@
<body> <body>
<div id="app"> <div id="app">
<div id='oaer'></div> <div id='voa'></div>
</div> </div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>

@ -5,11 +5,65 @@
* Distributed under terms of the MIT license. * Distributed under terms of the MIT license.
*/ */
@keyframes scale-up { :root {
--vanimate-time: 200ms
}
.voa-animate-slow {
--vanimate-time: 300ms
}
.voa-hover-line-b {
cursor: pointer;
position: relative;
&:hover {
opacity: 0.7;
}
&:after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 0.1em;
background-color: #000;
transition: all 0.3s;
}
&:hover::after {
left: 0px;
width: 100%;
}
}
.voa-scale-off {
transform-origin: center center;
animation: scale-off var(--vanimate-time) ease-out forwards !important;
}
.voa-scale-in {
transform-origin: center center;
animation: scale-in var(--vanimate-time) ease-in forwards;
}
.voa-slidein-right {
animation: slidein-right var(--vanimate-time) ease-in forwards;
}
.voa-slidein-up {
animation: slidein-up var(--vanimate-time) ease-in forwards;
}
@keyframes scale-in {
0% { 0% {
transform: scale(0); transform: scale(0);
opacity: 0; opacity: 0;
} }
100% { 100% {
transform: scale(1); transform: scale(1);
opacity: 1; opacity: 1;
@ -21,19 +75,29 @@
transform: scale(1); transform: scale(1);
opacity: 1; opacity: 1;
} }
100% { 100% {
transform: scale(0); transform: scale(0);
opacity: 0; opacity: 0;
} }
} }
@keyframes slide-in { @keyframes slidein-up {
0% { 0% {
transform: translateX(200%) scale(0); transform: translateY(-100%) scale(1);
opacity: 0;
} }
100% {
transform: translateX(0) scale(1);
}
}
@keyframes slidein-right {
0% {
transform: translateX(100%) scale(1);
}
100% { 100% {
transform: translateX(0) scale(1); transform: translateX(0) scale(1);
opacity: 1;
} }
} }

@ -7,57 +7,41 @@
@import "./animate.scss"; @import "./animate.scss";
:root {
--bg-base: #efefef;
--fg-base: #090909
}
.voa { .voa {
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
height: 4rem; height: inherit;
width: 4rem; width: inherit;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
.voa-off {
} .voa-off {}
.voa-on { .voa-on {
border-radius: 100%; border-radius: 100%;
background: #999; background: #999;
animation: scale-up 100ms ease-out forwards; height: inherit;
width: inherit;
img {
height: inherit;
width: inherit;
vertical-align: middle;
border-radius: 50%;
}
} }
:hover { :hover {
opacity: 0.9; opacity: 0.9;
} }
} }
.voa-scale-off {
transform-origin: center center;
animation: scale-off 200ms ease-out forwards !important;
}
.voa-scale-up {
animation: scale-up 200ms ease-in;
}
.hover-line-b {
cursor: pointer;
position: relative;
&:hover {
opacity: 0.7;
}
&:after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 0.1em;
background-color: #000;
transition: all 0.3s;
}
&:hover::after {
left: 0px;
width: 100%;
}
}
.voa-modal { .voa-modal {
user-select: none; user-select: none;
@ -69,6 +53,7 @@
z-index: 10000; z-index: 10000;
width: 40%; width: 40%;
min-width: 20rem; min-width: 20rem;
&::before { &::before {
content: ""; content: "";
position: absolute; position: absolute;
@ -78,19 +63,21 @@
height: 100%; height: 100%;
border-radius: 2rem; border-radius: 2rem;
background-color: rgba(240, 240, 240, 0.5); background-color: rgba(240, 240, 240, 0.5);
backdrop-filter: blur(20px); /* 模糊效果 */ backdrop-filter: blur(20px);
/* 模糊效果 */
z-index: -1; z-index: -1;
} }
} }
#voa-mask { .voa-slide-mask {
position: fixed; position: fixed;
display: none; visibility: hidden;
top: 0; top: 0;
left: 0; left: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
z-index: 999; z-index: 999;
&::before { &::before {
content: ""; content: "";
position: absolute; position: absolute;
@ -98,11 +85,59 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(255, 255, 255, 0.01); /* 半透明白色遮罩 */ background: rgba(255, 255, 255, 0.11);
backdrop-filter: blur(1px); /* 模糊效果 */ /* 半透明白色遮罩 */
backdrop-filter: blur(1px);
/* 模糊效果 */
}
.voa-slide {
position: absolute;
top: 0;
right: 0;
height: 100vh;
width: 20rem;
.voa-slide-header {
position: relative;
width: 100%;
height: 4rem;
background: linear-gradient(90deg, #6849E1, #b09ef4);
z-index: 1;
}
.voa-slide-body {
position: relative;
width: 100%;
height: calc(100% - 4rem);
background: var(--bg-base);
color: var(--fg-base);
transform: translateY(-140%);
.voa-slide-main {
height: calc(100% - 3rem);
overflow: auto;
}
.voa-slide-footer {
border-top: solid 2px #000;
height: 3rem;
font-size: 1.5rem;
line-height: 3rem;
text-align: center;
cursor: pointer;
box-sizing: border-box;
opacity: 0.6;
&:hover {
opacity: 1;
}
}
}
} }
} }
.voa-logo { .voa-logo {
background-image: url("../favicon.svg"); background-image: url("../favicon.svg");
background-size: cover; background-size: cover;
@ -114,6 +149,7 @@
text-align: center; text-align: center;
display: block; display: block;
border: none; border: none;
&::after { &::after {
content: ""; content: "";
position: absolute; position: absolute;
@ -121,10 +157,12 @@
border-radius: inherit; border-radius: inherit;
transition: 0.3s; transition: 0.3s;
} }
&:active::after { &:active::after {
box-shadow: 0 1px 0px 0px rgba(0, 0, 0, 0.5); box-shadow: 0 1px 0px 0px rgba(0, 0, 0, 0.5);
/* opacity: 0; */ /* opacity: 0; */
} }
&:active { &:active {
opacity: 0.8; opacity: 0.8;
} }

@ -0,0 +1,11 @@
/*
* bus.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-10-22 21:46
* Distributed under terms of the GPL license.
*/
import mitt from "mitt";
const bus = mitt()
export default bus

@ -0,0 +1,39 @@
/*
* account.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-10-22 22:07
* Distributed under terms of the GPL license.
*/
import Base from './base'
export default class extends Base {
doms: { [key: string]: HTMLElement }
main: HTMLElement
constructor() {
super()
this.main = this.build({
class: 'voa-account'
})
let u = {
username: 'asd',
icon: 'https://public.veypi.com/img/avatar/0001.jpg'
}
this.main.innerHTML = `
<div class="voa-account-header">
<div class="vah-1">my account</div>
<div class="vah-2">account center</div>
</div>
<div class="voa-account-body">
<div class="vab-ico">
<img style="" class="" src="${u.icon}" />
</div>
<div class="vab-info">
<div class="vabi-1"><span></span> <span>${u.username}</span> </div>
<div class="vabi-2"><span></span> <span>${u.username}</span> </div>
<div class="vabi-3"><span></span> <span>${u.username}</span> </div>
<div class="vabi-4"><span></span> <span>${u.username}</span> </div>
</div>
</div>
`
}
}

@ -0,0 +1,73 @@
/*
* base.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-10-22 18:21
* Distributed under terms of the GPL license.
*/
interface buildOpts {
id?: string
typ?: 'div'
class?: string
style?: string
innerHtml?: string
onclick?: any
children?: HTMLElement[]
}
export default class {
class_prefix: string
constructor(p?: string) {
this.class_prefix = p || 'voa-'
}
build(opts: buildOpts) {
let dom = document.createElement(opts.typ || 'div')
if (opts.id) {
dom.id = opts.id
}
if (opts.class) {
this.addClass(dom, opts.class)
}
if (opts.innerHtml) {
dom.innerHTML = opts.innerHtml
}
if (opts.onclick) {
dom.onclick = opts.onclick
}
if (opts.children) {
for (let c in opts.children) {
dom.appendChild(opts.children[c])
}
}
if (opts.style) {
const regex = /([a-zA-Z-]+)\s*:\s*([^;]+);?/g;
let match;
while ((match = regex.exec(opts.style)) !== null) {
const key = match[1].trim();
const value = match[2].trim();
console.log([key, value])
dom.style.setProperty(key, value)
}
}
return dom
}
addClass(dom: HTMLElement, c: string) {
let items = c.split(' ')
for (let i of items) {
if (i.startsWith(this.class_prefix)) {
dom.classList.add(i)
} else {
dom.classList.add(this.class_prefix + i)
}
}
}
removeClass(dom: HTMLElement, c: string) {
let items = c.split(' ')
for (let i of items) {
if (i.startsWith(this.class_prefix)) {
dom.classList.remove(i)
} else {
dom.classList.remove(this.class_prefix + i)
}
}
}
}

@ -0,0 +1,60 @@
/*
* index.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-10-22 17:51
* Distributed under terms of the GPL license.
*/
import slide from './slide'
import Base from './base'
import bus from '../bus'
export default class extends Base {
slide: slide
frame: HTMLDivElement
frame_login?: HTMLDivElement
frame_user?: HTMLDivElement
constructor(frame: HTMLDivElement) {
super()
this.frame = frame
this.frame.classList.add('voa')
this.slide = new slide()
this.mount_user()
bus.on('logout', () => {
this.mount_login()
this.slide.hide()
})
}
mount_login() {
this.frame_login = this.build({
class: 'off hover-line-b scale-in',
innerHtml: '登录',
onclick: () => {
console.log('click login')
this.addClass(this.frame_login!, 'scale-off')
this.mount_user()
}
})
if (this.frame_user) {
this.frame.removeChild(this.frame_user)
}
this.frame.appendChild(this.frame_login)
}
mount_user() {
let icon = 'https://public.veypi.com/img/avatar/0001.jpg'
this.frame_user = this.build({
class: 'on scale-in',
innerHtml: `
<img style="" class="" src="${icon}" />
`,
onclick: () => {
this.slide.show()
// this.mount_login()
}
})
if (this.frame_login) {
this.frame.removeChild(this.frame_login)
}
this.frame.appendChild(this.frame_user)
}
}

@ -0,0 +1,75 @@
/*
* slide.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-10-22 17:57
* Distributed under terms of the GPL license.
*/
import Base from './base'
import bus from '../bus'
import account from './account'
/*
mask
slide
header
body
main
footer
*
* */
export default class extends Base {
mask: HTMLDivElement
slide: HTMLDivElement
header: HTMLDivElement
body: HTMLElement
main: HTMLElement
footer: HTMLElement
constructor() {
super()
this.header = this.build({
class: 'slide-header animate-slow',
})
this.footer = this.build({
class: 'slide-footer',
innerHtml: 'logout',
onclick: () => {
bus.emit('logout')
}
})
this.main = this.build({
class: 'slide-main',
children: [new account().main]
})
this.body = this.build({
class: 'slide-body animate-slow',
style: 'animation-delay: 300ms',
children: [this.main, this.footer]
})
this.slide = this.build({
id: 'voa-slide',
class: 'slide',
children: [this.header, this.body]
})
this.mask = this.build({
class: 'slide-mask',
style: 'visibility: hidden',
children: [this.slide],
onclick: (e: MouseEvent) => {
if (e.target === e.currentTarget) {
this.hide()
}
}
})
document.body.appendChild(this.mask)
}
show() {
this.mask.style.visibility = 'visible'
this.addClass(this.header, 'slidein-right')
this.addClass(this.body, 'slidein-up')
}
hide() {
this.mask.style.visibility = 'hidden'
this.removeClass(this.header, 'slidein-right')
this.removeClass(this.body, 'slidein-up')
}
}

@ -6,106 +6,30 @@
*/ */
import './assets/css/oaer.scss' import './assets/css/oaer.scss'
import bus from './bus'
import ui from './components'
export class OAer { export class OAer {
id: string host: string
code: string domid?: string
domid: string ui?: ui
dom: { constructor(host: string, domid?: string) {
mask: HTMLDivElement this.host = host
frame: HTMLDivElement if (domid) {
b0?: HTMLDivElement this.domid = domid
b1?: HTMLDivElement this.ui = new ui(document.querySelector(`#${this.domid}`)!)
login_box?: HTMLDivElement
}
constructor(id: string, code: string, domid?: string) {
this.id = id
this.code = code
this.domid = domid || 'oaer'
this.dom = {
frame: document.querySelector(`#${this.domid}`)!,
mask: document.createElement('div'),
} }
this.dom.mask.id = 'voa-mask'
document.body.appendChild(this.dom.mask)
this.dom.frame.classList.add('voa')
this.build_b0()
this.dom.frame.appendChild(this.dom.b0!)
this.offclick()
} }
build_b0() { login() {
this.dom.b0 = document.createElement('div') bus.emit('login')
this.dom.b0.classList.add('hover-line-b')
this.dom.b0.onclick = () => {
console.log('click b0')
this.build_login()
// this.dom.b1?.classList.add('voa-animate-scale-off')
// this.build_b1()
// this.dom.frame.replaceChild(this.dom.b1!, this.dom.b0!)
}
this.dom.b0.innerHTML = `
`
} }
build_login() { logout() {
if (this.dom.login_box) { bus.emit('logout')
return
}
this.dom.login_box = document.createElement('div')
this.dom.login_box.classList.add('voa-modal', 'voa-scale-up')
this.dom.login_box.innerHTML = `
<div class="voa-login-box">
<svg class="close" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="black" stroke-width="2"/>
</svg>
<div class="header">
<div class="voa-logo"></div>
<div class="txt">OneAuth</div>
</div>
<div class="username">
<input autocomplete="username" placeholder="username, phone or Email">
</div>
<div class="password">
<input autocomplete="password" type='password' placeholder="password">
</div>
<button class='ok voa-btn'>
login
</button>
<div class="last">
<div class="icos">
<div class="github"></div>
<div class="wechat"></div>
<div class="google"></div>
</div>
<div class="txt">
<div>Create Account</div>
<div>Forgot Password?</div>
</div>
</div>
</div>
`
document.body.appendChild(this.dom.login_box)
document.querySelector('.voa-login-box .close')?.addEventListener('click', () => {
this.dom.login_box?.classList.add('voa-scale-off')
setTimeout(() => {
this.dom.login_box?.remove()
this.dom.login_box = undefined
}, 300)
})
let uin = document.querySelector('.voa-login-box .username input') as HTMLInputElement
console.log(uin)
} }
build_b1() { onlogout(fc: () => void) {
this.dom.b1 = document.createElement('div') bus.on('logout', fc)
this.dom.b1.classList.add('voa-on')
this.dom.b1.innerHTML = `
<img style="" class="" src="" />
`
}
offclick() {
} }
} }

@ -30,6 +30,7 @@
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"axios": "^1.7.2" "axios": "^1.7.2",
"mitt": "^3.0.1"
} }
} }

@ -1,6 +1,6 @@
import './style.css' import './style.css'
import { OAer } from '../lib/main' import { OAer } from '../lib/main'
let t = new OAer('123', '') let t = new OAer('http://localhost:3000', 'voa')
console.log(t.domid) console.log(t.domid)

@ -14,6 +14,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
html {
overflow: hidden;
}
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -28,7 +32,13 @@ body {
#app { #app {
height: 80px; height: 80px;
padding-right: 20px; padding-right: 20px;
background: #efefef;
display: flex;
align-items: center;
justify-content: end;
} }
#oaer {
float: right; #voa {
height: 3rem;
width: 3rem;
} }

@ -366,6 +366,11 @@ mime-types@^2.1.12:
dependencies: dependencies:
mime-db "1.52.0" mime-db "1.52.0"
mitt@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
nanoid@^3.3.7: nanoid@^3.3.7:
version "3.3.7" version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"

Loading…
Cancel
Save