This commit is contained in:
Wyle.Gong-巩文昕
2025-04-23 11:22:46 +08:00
parent 7a1aae1e2f
commit 0b29e4e856
20 changed files with 1301 additions and 1 deletions
+136
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<body>
<div>value</div>
</body>
<script setup>
value = '2'
</script>
</html>
+65
View 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
View 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]) || '&nbsp;'}}
</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
View 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
View 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
View 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>