ui
This commit is contained in:
parent
7a1aae1e2f
commit
0b29e4e856
1
vyes-ui
1
vyes-ui
@ -1 +0,0 @@
|
|||||||
Subproject commit f1c7dedc1e6c2bceec011c4da59caa35e104f10f
|
|
||||||
21
vyes-ui/LICENSE
Normal file
21
vyes-ui/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 veypi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
9
vyes-ui/Makefile
Normal file
9
vyes-ui/Makefile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# Makefile
|
||||||
|
# Copyright (C) 2024 veypi <i@veypi.com>
|
||||||
|
# 2025-03-04 14:03:56
|
||||||
|
# Distributed under terms of the GPL license.
|
||||||
|
#
|
||||||
|
|
||||||
|
run:
|
||||||
|
@go run ./cli/*.go -f ./cfg/dev.yaml
|
||||||
1
vyes-ui/README.md
Normal file
1
vyes-ui/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# vyes-ui
|
||||||
24
vyes-ui/go.mod
Normal file
24
vyes-ui/go.mod
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
module vyesui
|
||||||
|
|
||||||
|
go 1.23.2
|
||||||
|
|
||||||
|
replace github.com/veypi/OneBD => ../../../OneBD/
|
||||||
|
|
||||||
|
replace github.com/veypi/utils => ../../../utils/
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/veypi/OneBD v0.0.0-00010101000000-000000000000
|
||||||
|
github.com/veypi/utils v0.3.7
|
||||||
|
gorm.io/gorm v1.25.12
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/rs/zerolog v1.17.2 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
34
vyes-ui/go.sum
Normal file
34
vyes-ui/go.sum
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
|
github.com/rs/zerolog v1.17.2 h1:RMRHFw2+wF7LO0QqtELQwo8hqSmqISyCJeFeAAuWcRo=
|
||||||
|
github.com/rs/zerolog v1.17.2/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
|
||||||
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
25
vyes-ui/init.go
Normal file
25
vyes-ui/init.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||||
|
// 2025-03-04 14:03:56
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package vyesui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"github.com/veypi/OneBD/rest"
|
||||||
|
"github.com/veypi/OneBD/rest/middlewares/vyes"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Router = rest.NewRouter()
|
||||||
|
|
||||||
|
//go:embed ui/*
|
||||||
|
var uifs embed.FS
|
||||||
|
|
||||||
|
//go:embed ui/root.html
|
||||||
|
var rootFile []byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
vyes.WrapUI(Router, uifs)
|
||||||
|
}
|
||||||
136
vyes-ui/ui/btn.html
Normal file
136
vyes-ui/ui/btn.html
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<style>
|
||||||
|
.loading-spinner {
|
||||||
|
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-left-color: #000;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 基础样式 */
|
||||||
|
body {
|
||||||
|
--color: #3498db;
|
||||||
|
--size: 1rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--color);
|
||||||
|
select: none;
|
||||||
|
line-height: 1;
|
||||||
|
user-select: none;
|
||||||
|
font-size: var(--size);
|
||||||
|
padding: calc(var(--size) / 2) var(--size);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[size=lg] {
|
||||||
|
--size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[size=md] {
|
||||||
|
--size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[size=sm] {
|
||||||
|
--size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[size=xs] {
|
||||||
|
--size: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:hover {
|
||||||
|
background-color: color-mix(in srgb, var(--color), white 20%);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 经典按钮 */
|
||||||
|
body[typ='classic'] {
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[typ='classic']:hover {
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 圆角按钮 */
|
||||||
|
body[typ=rounded] {
|
||||||
|
color: white;
|
||||||
|
border-radius: 25px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[typ=rounded]:hover {
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 扁平按钮 */
|
||||||
|
body[typ=flat] {
|
||||||
|
color: white;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[typ=flat]:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 边框按钮 */
|
||||||
|
body[typ=outline] {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--color);
|
||||||
|
border: 2px solid var(--color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[typ=outline]:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
color: color-mix(in srgb, var(--color), white 40%);
|
||||||
|
border: 2px solid color-mix(in srgb, var(--color), white 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.disable {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body :typ='typ' :class="{disable}" :style='`--color:`+color' :size='size'>
|
||||||
|
<vslot @click='wrap' class="w-full h-full" v-if='ok'>ok</vslot>
|
||||||
|
<div v-else class="animate-spin loading-spinner"></div>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
typ = 'classic'
|
||||||
|
color = ''
|
||||||
|
ok = true
|
||||||
|
size = 'md'
|
||||||
|
disable = false
|
||||||
|
click = async () => { }
|
||||||
|
wrap = async () => {
|
||||||
|
if (!ok || disable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok = false
|
||||||
|
let fc = click
|
||||||
|
if (fc instanceof Promise) {
|
||||||
|
fc = await fc
|
||||||
|
} else if (fc instanceof Function) {
|
||||||
|
fc = fc()
|
||||||
|
} else {
|
||||||
|
console.log('click is not a function', fc)
|
||||||
|
}
|
||||||
|
if (fc instanceof Promise) {
|
||||||
|
await fc
|
||||||
|
}
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
58
vyes-ui/ui/dialog.html
Normal file
58
vyes-ui/ui/dialog.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<body style="display:none;">
|
||||||
|
<vslot>
|
||||||
|
<div @click='show=false'>
|
||||||
|
blank dialog
|
||||||
|
</div>
|
||||||
|
</vslot>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
show = true
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
let dom = document.createElement('div')
|
||||||
|
Object.assign(dom.style, {
|
||||||
|
position: 'fixed',
|
||||||
|
left: '50%',
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
'z-index': 1000,
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
})
|
||||||
|
dom.classList.add('animate__animated')
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
$data.show = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
if (dom.contains(event.target)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$data.show = false
|
||||||
|
})
|
||||||
|
let nodes = Array.from($node.childNodes)
|
||||||
|
setTimeout(() => {
|
||||||
|
dom.innerHTML = ''
|
||||||
|
dom.append(...nodes)
|
||||||
|
$vyes.vproxy.Watch(() => {
|
||||||
|
dom.classList.remove('animate__fadeIn')
|
||||||
|
dom.classList.remove('animate__fadeOut')
|
||||||
|
if ($data.show) {
|
||||||
|
dom.classList.add('animate__fadeIn')
|
||||||
|
document.body.appendChild(dom)
|
||||||
|
} else {
|
||||||
|
dom.classList.add('animate__fadeOut')
|
||||||
|
setTimeout(() => {
|
||||||
|
// dom.style.display = 'none'
|
||||||
|
// $node.appendChild(...nodes)
|
||||||
|
dom.remove()
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 100)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
81
vyes-ui/ui/dropdown.html
Normal file
81
vyes-ui/ui/dropdown.html
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Dropdown Menu</title>
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
.dropdown {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-btn {
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-btn:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
transform: translateZ(0) translateY(-10px);
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--background-color, #fff);
|
||||||
|
color: var(--color, #000);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown:hover .dropdown-content {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateZ(0) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
body .dropdown-item {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.2s;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body .dropdown-item:hover {
|
||||||
|
background-color: color-mix(in srgb, var(--background-color, #fff), #888 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="dropdown">
|
||||||
|
<vslot class="dropdown-btn">
|
||||||
|
<button>Dropdown Menu</button>
|
||||||
|
</vslot>
|
||||||
|
<vslot name='menu' class="dropdown-content">
|
||||||
|
<a href="#" class="dropdown-item">Option 1</a>
|
||||||
|
<a href="#" class="dropdown-item">Option 2</a>
|
||||||
|
</vslot>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
||||||
46
vyes-ui/ui/form.html
Normal file
46
vyes-ui/ui/form.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<form vdom='vform' class="flex flex-col gap-4">
|
||||||
|
<div class="my-2" v-if='!key.disabled' :style="'height:'+key.height" v-for='key in keys'>
|
||||||
|
<div class="flex gap-2 h-full">
|
||||||
|
<div class="truncate" :style="'width:'+label_width">{{key.label || key.name}}</div>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<div refu='input' :required='key.required' v:value='data[key.name]' :type='key.type' :validate='key.validate'
|
||||||
|
:opts='key.opts'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="color: red;">{{errmsg}}</div>
|
||||||
|
<div class="flex justify-center mt-10">
|
||||||
|
<div size='lg' refu='btn' :click='onOk' style="width: 8rem;">Ok</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
label_width = '6rem'
|
||||||
|
keys = []
|
||||||
|
data = {}
|
||||||
|
vform = null
|
||||||
|
errmsg = ''
|
||||||
|
onsave = async () => { }
|
||||||
|
onOk = async () => {
|
||||||
|
let valid = $data.vform.checkValidity()
|
||||||
|
if (!valid) {
|
||||||
|
$data.vform.reportValidity()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await $data.onsave($data.data)
|
||||||
|
} catch (error) {
|
||||||
|
errmsg = error.message || error || '未知错误'
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
55
vyes-ui/ui/icon.html
Normal file
55
vyes-ui/ui/icon.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<style>
|
||||||
|
body {
|
||||||
|
--icon-size: var(--icon-size, 2rem);
|
||||||
|
--icon-color: var(--icon-color, #000);
|
||||||
|
height: var(--icon-size);
|
||||||
|
width: var(--icon-size);
|
||||||
|
font-size: calc(var(--icon-size) - 0.5rem);
|
||||||
|
line-height: var(--icon-size);
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
body i {
|
||||||
|
font-family: "iconfont" !important;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[type=round] {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[type=round]:hover {
|
||||||
|
background-color: color-mix(in srgb, var(--icon-color), #999 40%);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[type=rect]:hover {
|
||||||
|
background-color: color-mix(in srgb, var(--icon-color), #999 40%);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[type=outline] {
|
||||||
|
border: 1px solid var(--icon-color);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body :style="`--icon-size:${size};--icon-color:${color}`" :type>
|
||||||
|
<i :class="'icon-'+name"></i>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
size = '2rem'
|
||||||
|
color = '#000'
|
||||||
|
name = ''
|
||||||
|
type = 'round' // rect, round, outline, none
|
||||||
|
</script>
|
||||||
134
vyes-ui/ui/input.html
Normal file
134
vyes-ui/ui/input.html
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.clear {
|
||||||
|
background: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear:invalid+hr {
|
||||||
|
border: #f00 solid 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -1px;
|
||||||
|
border: rgba(128, 128, 128, 0.2) solid 1px;
|
||||||
|
transition: all 0.2s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:hover hr {
|
||||||
|
border: rgba(128, 128, 128, 9) solid 1px;
|
||||||
|
width: 100% !important;
|
||||||
|
left: 0rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.vinput-core {
|
||||||
|
width: calc(100% - 1rem);
|
||||||
|
padding-left: 1rem;
|
||||||
|
min-width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus {}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body class='relative'>
|
||||||
|
<div class="flex relative">
|
||||||
|
<div v-if='label' class="flex-shrink-0" :style="{width: label_width}">{{label}}</div>
|
||||||
|
<textarea vdom='vref' :rows='opts?.rows' v-if="type==='textarea'" class="clear vinput-core grow" @input='sync_evt'
|
||||||
|
!value :placeholder !required>
|
||||||
|
</textarea>
|
||||||
|
<div v-else-if='type==="bool"'>
|
||||||
|
<div @click='value=!value'>{{value?'true':'false'}}</div>
|
||||||
|
</div>
|
||||||
|
<input vdom='vref' v-else-if='type==="number"' class="clear vinput-core grow" type="number" @input='sync_evt'
|
||||||
|
!value='value||0' :placeholder !required />
|
||||||
|
<input-select v:value v-else-if='type==="select"' class="vinput-core grow" :input='sync' :options='opts.options'
|
||||||
|
:placeholder></input-select>
|
||||||
|
<input vdom='vref' v-else class="clear vinput-core grow" type="text" @input='sync_evt' !value="value||''"
|
||||||
|
:placeholder !required />
|
||||||
|
<hr v-if='type!="select"'
|
||||||
|
:style="label?`width:calc(100% - 2rem - ${label_width});left: calc(1rem + ${label_width})`:'width:calc(100% - 2rem);left:1rem'">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
vref = null
|
||||||
|
type = 'text' // text textarea
|
||||||
|
required = false
|
||||||
|
value = ''
|
||||||
|
label = ''
|
||||||
|
label_width = '6rem'
|
||||||
|
placeholder = ''
|
||||||
|
opts = {
|
||||||
|
rows: 5,
|
||||||
|
options: []
|
||||||
|
}
|
||||||
|
|
||||||
|
validate = (v) => true
|
||||||
|
sync_evt = (e) => {
|
||||||
|
sync(e.target.value)
|
||||||
|
}
|
||||||
|
sync = (v) => {
|
||||||
|
if (!$data.check(v)) {
|
||||||
|
$data.vref?.setCustomValidity("invalid");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$data.vref?.setCustomValidity('');
|
||||||
|
if ($data.type === 'textarea') {
|
||||||
|
$data.value = v
|
||||||
|
} else if ($data.type === 'number') {
|
||||||
|
$data.value = Number(v)
|
||||||
|
} else if ($data.type === 'bool') {
|
||||||
|
$data.value = v === 'true'
|
||||||
|
} else if ($data.type === 'select') {
|
||||||
|
$data.value = v
|
||||||
|
} else {
|
||||||
|
// type text
|
||||||
|
$data.value = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check = (v) => {
|
||||||
|
if (!v) {
|
||||||
|
return !$data.required
|
||||||
|
}
|
||||||
|
if (!$data.validate) {
|
||||||
|
return true
|
||||||
|
} else if ($data.validate instanceof RegExp) {
|
||||||
|
if ($data.validate.test(v)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if (typeof $data.validate === 'function') {
|
||||||
|
if ($data.validate(v)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
if (!$data.value) {
|
||||||
|
switch ($data.type) {
|
||||||
|
case 'number':
|
||||||
|
sync(0)
|
||||||
|
break
|
||||||
|
case 'bool':
|
||||||
|
sync(false)
|
||||||
|
break
|
||||||
|
case 'select':
|
||||||
|
sync('')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
sync('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
119
vyes-ui/ui/input/select.html
Normal file
119
vyes-ui/ui/input/select.html
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.option-item {
|
||||||
|
padding: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar,
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
width: 0.25rem;
|
||||||
|
height: 0.25rem;
|
||||||
|
background: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-list {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 1;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: all 0.3s;
|
||||||
|
max-height: 40vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:hover .option-list {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div @click='onclick' class="select-none cursor-pointer">
|
||||||
|
{{selectLabel()||placeholder||'请选择'}}
|
||||||
|
</div>
|
||||||
|
<div @click='onclick'>🔻</div>
|
||||||
|
<div class="option-list">
|
||||||
|
<input class="clear grow border-gray-400 border mx-auto my-2 p-1 block" style="width: 90%" @input='filter'
|
||||||
|
placeholder='筛选' />
|
||||||
|
<div v-if='filterOptions.length===0' class="option-item">
|
||||||
|
Not Found Options
|
||||||
|
</div>
|
||||||
|
<div @click='onselect(v)' class="option-item border-b border-gray-100" v-for='v in filterOptions'>
|
||||||
|
{{v.label || v }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
placeholder = ''
|
||||||
|
options = [
|
||||||
|
{value: '1', label: 'Label 1'},
|
||||||
|
{value: '2', label: 'Label 2'},
|
||||||
|
{value: '3', label: 'Label 3'},
|
||||||
|
]
|
||||||
|
filterOptions = []
|
||||||
|
value = ''
|
||||||
|
input = (v) => {
|
||||||
|
value = v
|
||||||
|
}
|
||||||
|
filter = (event) => {
|
||||||
|
filterOptions = options.filter((o) => {
|
||||||
|
let tag = o.label || o.value || o
|
||||||
|
if (typeof tag !== 'string') {
|
||||||
|
tag = tag.toString()
|
||||||
|
}
|
||||||
|
return tag.includes(event.target.value || '')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onselect = (v) => {
|
||||||
|
input(v.value || v)
|
||||||
|
show = false
|
||||||
|
}
|
||||||
|
selectLabel = () => {
|
||||||
|
let res = value
|
||||||
|
options.forEach((o) => {
|
||||||
|
if (typeof o === 'string') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (o.value === value) {
|
||||||
|
res = o.label
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
filterOptions = [...options]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
11
vyes-ui/ui/preview.html
Normal file
11
vyes-ui/ui/preview.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div>value</div>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
value = '2'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
65
vyes-ui/ui/root.html
Normal file
65
vyes-ui/ui/root.html
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>VyesUI - 高效前端组件库</title>
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.features {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 2rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.feature-card {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
width: 300px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="h-full w-full bg-gray-100">
|
||||||
|
<div class="container">
|
||||||
|
<header class="header">
|
||||||
|
<h1 class="text-4xl font-bold">欢迎使用 VyesUI</h1>
|
||||||
|
<p class="mt-2 text-gray-700">一个基于 Vyes 框架构建的高效前端组件库</p>
|
||||||
|
</header>
|
||||||
|
<section class="features">
|
||||||
|
<div class="feature-card">
|
||||||
|
<h2 class="text-xl font-semibold">简洁易用</h2>
|
||||||
|
<p>遵循 HTML5 规范,提供直观的 API 设计,让开发者快速上手。</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h2 class="text-xl font-semibold">响应式设计</h2>
|
||||||
|
<p>内置移动端优先的设计理念,确保在各种设备上都能获得良好的用户体验。</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h2 class="text-xl font-semibold">灵活定制</h2>
|
||||||
|
<p>支持 Tailwind CSS 样式系统,方便开发者根据需求定制样式。</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h2 class="text-xl font-semibold">丰富的组件</h2>
|
||||||
|
<p>包含多种常用组件,满足不同场景下的开发需求。</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<script setup>
|
||||||
|
// 可以在这里定义页面所需的响应式数据或方法
|
||||||
|
// 当前页面为静态展示,无需定义额外逻辑
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
317
vyes-ui/ui/table.html
Normal file
317
vyes-ui/ui/table.html
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<style>
|
||||||
|
.loading-spinner {
|
||||||
|
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-left-color: #000;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.table {}
|
||||||
|
|
||||||
|
.table-column {
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 30%;
|
||||||
|
border-bottom: 2px solid black;
|
||||||
|
background-color: var(--background-color, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-column {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-key {
|
||||||
|
border-bottom: 2px solid black;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
height: 3rem;
|
||||||
|
line-height: 3rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-value {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0.5rem;
|
||||||
|
height: 3rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-value[odd='1'] {
|
||||||
|
/* background-color: color-mix(in srgb, var(--background-color, #fff), #888 20%); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-btn {}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
min-height: 50vh;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: auto;
|
||||||
|
width: 50vw;
|
||||||
|
background-color: var(--background-color, #fff);
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
::-webkit-scrollbar,
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
width: 0.25rem;
|
||||||
|
height: 0.25rem;
|
||||||
|
background: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.keysearch {
|
||||||
|
outline: none;
|
||||||
|
border: 1px solid var(--color, #000);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="flex justify-evenly overflow-x-auto">
|
||||||
|
<div v-if='!key.hidden' class="table-column" :class="{'sticky-column':index===0}" style="min-height: 21rem;left:0"
|
||||||
|
v-for='(key,index) in keys'>
|
||||||
|
<div class="header-key">{{key.label||key.name}}</div>
|
||||||
|
<div class="table-value" :odd='index%2' v-for='(row, index) in data'>
|
||||||
|
<vslot :name='key.name' v='row,index' :style="key.style">
|
||||||
|
<div refu='input' v:value='row[key.name]' :type="key.type==='textarea'?'text':key.type"
|
||||||
|
:required='key.required' :validate='key.validate' :opts='key.opts'
|
||||||
|
v-if='editable && !key.disabled && row._enable'>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{{ (key.field?key.field(row):row[key.name]) || ' '}}
|
||||||
|
</div>
|
||||||
|
</vslot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-column sticky-column" style="right:0;min-width: 8rem;">
|
||||||
|
<vslot name='_key' class="header-key">
|
||||||
|
<div refu='dropdown' class="w-full">
|
||||||
|
<div refu='icon' class="text-2xl" name='ecs'></div>
|
||||||
|
<div vslot='menu'>
|
||||||
|
<div class="dropdown-item" @click='show(0)'>创建</div>
|
||||||
|
<div class="dropdown-item" @click='show(1)'>高级检索</div>
|
||||||
|
<div class="dropdown-item" @click='show(2)'>智能导入</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</vslot>
|
||||||
|
<div class="table-value" :odd='index%2' v-for='(row, index) in data'>
|
||||||
|
<vslot class="w-full flex justify-center gap-2 text-xl" name='_addon' v='row,index'>
|
||||||
|
<div refu='icon' name='edit-square' color='#78c' v-if='!row._enable' @click='row._enable=true'></div>
|
||||||
|
<div refu='icon' name='save' color='#ff3300' v-else @click='wrap(1, row)'></div>
|
||||||
|
<div refu='icon' name='delete' color='#f66' v-if='!row._enable' @click='wrap(3, row)'></div>
|
||||||
|
<div refu='icon' name='close' color='#aaa' v-else @click='delete row._enable'></div>
|
||||||
|
</vslot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 px-4 select-none">
|
||||||
|
<input !value='listOpts.keyword' @input.delay1s='search' class="keysearch" placeholder="简单检索" />
|
||||||
|
<div refu='icon' name='left' class="ml-auto" @click='wrap(0,-1)'></div>
|
||||||
|
<div>{{listOpts.page}}</div>
|
||||||
|
<div refu='icon' name='right' class="mr-auto" @click='wrap(0,1)'></div>
|
||||||
|
<div class="">总计{{total}}条数据</div>
|
||||||
|
</div>
|
||||||
|
<div refu='dialog' v:show='showFlag'>
|
||||||
|
<div class="dialog">
|
||||||
|
<div v-if='showMode==0' refu='form' :keys="keys" :data='{}' :onsave='async (d)=> await wrap(2,d)'>
|
||||||
|
</div>
|
||||||
|
<table-setting :keys='keys' :opts='listOpts' v-else-if='showMode==1' :apply='()=>wrap(0)'>
|
||||||
|
</table-setting>
|
||||||
|
<div v-else-if class="w-full flex flex-col flex-grow items-center gap-4" style=" height: calc(100% - 0px);">
|
||||||
|
<textarea class="w-full bg-gray-200 flex-grow p-4" placeholder="请输入文本内容或者拖入文件" !value='ai_content'
|
||||||
|
@input='ai_content=$event.target.value' style="resize:vertical;"></textarea>
|
||||||
|
<div refu='btn' class="mx-auto" size='lg' :click='ai'>智能识别</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
showFlag = false
|
||||||
|
showMode = 0
|
||||||
|
loading = false
|
||||||
|
keys = []
|
||||||
|
data = []
|
||||||
|
host = window.location.origin
|
||||||
|
api = ''
|
||||||
|
editable = false
|
||||||
|
ai_content = ''
|
||||||
|
ai = async () => {
|
||||||
|
let keyData = []
|
||||||
|
keys.forEach((k) => {
|
||||||
|
if (k.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keyData.push({key: k.name, label: k.label, type: k.type || 'text'})
|
||||||
|
})
|
||||||
|
let body = JSON.stringify({
|
||||||
|
system: "",
|
||||||
|
model: 'qwen-max-latest',
|
||||||
|
messages: [{
|
||||||
|
role: 'user', content: `
|
||||||
|
接下来我将给你一个结构体描述信息和一段文本,结构体参数默认为字符串类型,如果参数 请直接以json方式返回提取的结构信息,如果提取失败就返回空列表对象[],如果用户提出生成随机数据或者生成数据等请求,请你根据字段描述生成mock数据
|
||||||
|
结构体:
|
||||||
|
${JSON.stringify(keyData)}
|
||||||
|
描述信息:
|
||||||
|
${ai_content}
|
||||||
|
`}]
|
||||||
|
})
|
||||||
|
ai_content = ''
|
||||||
|
let json_res = ''
|
||||||
|
let checked = false
|
||||||
|
await $env.api.SSE(`${window.location.origin}/_/api/ai/gen`, {method: "POST", body: body}, (line, idx) => {
|
||||||
|
if (idx === -1) {
|
||||||
|
try {
|
||||||
|
console.log(json_res)
|
||||||
|
let data = JSON.parse(ai_content.slice(7, -3))
|
||||||
|
console.log(data)
|
||||||
|
let fc = async () => {
|
||||||
|
let c = 0
|
||||||
|
for (d of data) {
|
||||||
|
try {
|
||||||
|
create(d)
|
||||||
|
c++
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alert(`成功导入${c}条数据`)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
fc()
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
console.log('done')
|
||||||
|
}
|
||||||
|
if (line.indexOf('```json') != -1) {
|
||||||
|
json_res = line.slice(line.indexOf('```json'))
|
||||||
|
checked = true
|
||||||
|
} else if (checked && line.indexOf('```') != -1) {
|
||||||
|
json_res += line.slice(0, line.indexOf('```'))
|
||||||
|
checked = false
|
||||||
|
} else if (checked) {
|
||||||
|
json_res += line
|
||||||
|
}
|
||||||
|
ai_content += line
|
||||||
|
})
|
||||||
|
}
|
||||||
|
show = (m) => {
|
||||||
|
showMode = m
|
||||||
|
showFlag = true
|
||||||
|
}
|
||||||
|
function delay(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
update = async (row) => {
|
||||||
|
if (api) {
|
||||||
|
return await $env.api.Patch(host + api + '/' + row.id, row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total = 0
|
||||||
|
max = () => {
|
||||||
|
if (total > 0) {
|
||||||
|
return Math.ceil(total / listOpts.page_size)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
listOpts = {
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
keyword: '',
|
||||||
|
keywords: {},
|
||||||
|
sort_by: 'created_at',
|
||||||
|
order: 'desc',
|
||||||
|
}
|
||||||
|
next = async (opts) => {
|
||||||
|
if (api) {
|
||||||
|
opts = Object.assign({}, opts)
|
||||||
|
if (opts.keywords && Object.keys(opts.keywords).length > 0) {
|
||||||
|
opts.keywords = JSON.stringify(opts.keywords)
|
||||||
|
} else {
|
||||||
|
delete opts.keywords
|
||||||
|
}
|
||||||
|
return await $env.api.Get(host + api, opts)
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
create = async (data) => {
|
||||||
|
if (api) {
|
||||||
|
return await $env.api.Post(host + api, data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
del = async (row) => {
|
||||||
|
if (api) {
|
||||||
|
return await $env.api.Delete(host + api + '/' + row.id, row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
search = (e) => {
|
||||||
|
listOpts.keyword = e.target.value
|
||||||
|
wrap(0)
|
||||||
|
}
|
||||||
|
wrap = async (mode, props) => {
|
||||||
|
showFlag = false
|
||||||
|
if (mode === 0) {
|
||||||
|
if (typeof props === 'number') {
|
||||||
|
listOpts.page += props
|
||||||
|
}
|
||||||
|
if (listOpts.page < 1) {
|
||||||
|
listOpts.page = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let data = await $data.next(listOpts)
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
$data.data = data
|
||||||
|
} else {
|
||||||
|
$data.data = data.items
|
||||||
|
$data.total = data.total
|
||||||
|
}
|
||||||
|
} else if (mode === 1) {
|
||||||
|
await update(props)
|
||||||
|
props._enable = false
|
||||||
|
} else if (mode === 2) {
|
||||||
|
let res = await create(props)
|
||||||
|
showFlag = false
|
||||||
|
$data.data.push(res)
|
||||||
|
return res
|
||||||
|
} else if (mode == 3) {
|
||||||
|
await del(props)
|
||||||
|
$data.data = $data.data.filter((d) => d.id !== data.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onsave = () => { }
|
||||||
|
onerr = () => { }
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
if ($data.data.length === 0 && $data.api) {
|
||||||
|
try {
|
||||||
|
wrap(0)
|
||||||
|
} catch (e) {
|
||||||
|
$data.onerr(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
96
vyes-ui/ui/table/setting.html
Normal file
96
vyes-ui/ui/table/setting.html
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.config-item {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-item {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body class="container flex flex-col h-full gap-8">
|
||||||
|
<!-- 字段选择 -->
|
||||||
|
<h2 class="text-2xl">高级检索设置</h2>
|
||||||
|
<!-- 行数设置 -->
|
||||||
|
<div class="config-item">
|
||||||
|
<label>行数限制:</label>
|
||||||
|
<input type="number" !value="opts.page_size" @input='opts.page_size=Number($event.target.value)' min="1"
|
||||||
|
max="100" />
|
||||||
|
</div>
|
||||||
|
<!-- 排序设置 -->
|
||||||
|
<div class="config-item flex">
|
||||||
|
<label>排序字段:</label>
|
||||||
|
<input-select class="flex-grow" v:value='opts.sort_by' :options='options()'></input-select>
|
||||||
|
<label style="margin-left:1rem">排序方向:</label>
|
||||||
|
<input-select class="flex-grow" v:value='opts.order' :options='directionOpts'></input-select>
|
||||||
|
</div>
|
||||||
|
<div class="config-item">
|
||||||
|
<h4>选择显示字段:</h4>
|
||||||
|
<div class="field-list mt-4">
|
||||||
|
<span v-for="field in keys" :class="['field-item', field.hidden?'':'bg-blue-100' ]"
|
||||||
|
@click="field.hidden=!field.hidden">
|
||||||
|
{{ field.label || field.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 筛选设置 -->
|
||||||
|
<div class="config-item">
|
||||||
|
<h4 class="mb-4">字段筛选:</h4>
|
||||||
|
<div class="flex flex-wrap justify-between">
|
||||||
|
<div v-for="field in keys" class="mb-2">
|
||||||
|
<div refu='input' label_width='8rem' :label='field.label' v:value='filters[field.name]' type='text'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow"></div>
|
||||||
|
<button class="float-right bg-blue-400 rounded px-4 py-2" @click="wrap_apply">应用配置</button>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
keys = [
|
||||||
|
{name: 'id', label: 'ID', type: 'number'},
|
||||||
|
{name: 'name', label: '姓名', type: 'string'},
|
||||||
|
]
|
||||||
|
opts = {
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
keyword: '',
|
||||||
|
keywords: {},
|
||||||
|
sort_by: 'created_at',
|
||||||
|
order: 'desc',
|
||||||
|
}
|
||||||
|
filters = {}
|
||||||
|
options = () => keys.map(f => {
|
||||||
|
return {value: f.name, label: f.label}
|
||||||
|
})
|
||||||
|
directionOpts = [
|
||||||
|
{value: 'asc', label: '升序'},
|
||||||
|
{value: 'desc', label: '降序'}
|
||||||
|
]
|
||||||
|
apply = () => {
|
||||||
|
console.log('当前配置:', opts)
|
||||||
|
}
|
||||||
|
wrap_apply = () => {
|
||||||
|
let tmp = {}
|
||||||
|
for (let key in filters) {
|
||||||
|
if (filters[key] !== undefined && filters[key] !== '' && filters[key] !== null) {
|
||||||
|
tmp[key] = filters[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.keywords = tmp
|
||||||
|
console.log('当前配置:', opts)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
28
vyes-ui/ui/tailwind.config.js
Normal file
28
vyes-ui/ui/tailwind.config.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./components/**/*.{js,vue,ts}",
|
||||||
|
"./layouts/**/*.vue",
|
||||||
|
"./pages/**/*.vue",
|
||||||
|
"./plugins/**/*.{js,ts}",
|
||||||
|
"./app.vue",
|
||||||
|
"./error.vue",
|
||||||
|
"*.html",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
vprimary: '#2196f3',
|
||||||
|
vsecondary: '#ecc94b',
|
||||||
|
vaccents: '#ff9800',
|
||||||
|
verror: '#f44336',
|
||||||
|
vwaring: '#ff5722',
|
||||||
|
vinfo: '#ffc107',
|
||||||
|
vsuccess: '#53de58',
|
||||||
|
vignore: '#d1d5db',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
|
||||||
41
vyes-ui/ui/tree.html
Normal file
41
vyes-ui/ui/tree.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="tree select-none">
|
||||||
|
<div class="node">
|
||||||
|
<div @click='onclick'>
|
||||||
|
<vslot name='name' v='row,depth'>
|
||||||
|
{{row.name}}
|
||||||
|
</vslot>
|
||||||
|
</div>
|
||||||
|
<div v-if='row.children' class="children transition-all overflow-hidden"
|
||||||
|
:style="{'max-height':row.expand?'100%':'0'}">
|
||||||
|
<vslot name='child' v='row,k,depth' v-for="row in row.children">
|
||||||
|
<div>
|
||||||
|
{{row.name}}
|
||||||
|
</div>
|
||||||
|
</vslot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script setup>
|
||||||
|
depth = 0
|
||||||
|
row = {
|
||||||
|
name: 'demo-dir',
|
||||||
|
expand: false,
|
||||||
|
children: [
|
||||||
|
{name: 'file1'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onexpand = () => { }
|
||||||
|
onclick = () => {
|
||||||
|
$data.row.expand = !$data.row.expand
|
||||||
|
if ($data.row.expand) {
|
||||||
|
$data.onexpand($data.row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user