testFlow/vyes-ui/ui/table.html
Wyle.Gong-巩文昕 0b29e4e856 ui
2025-04-23 11:22:46 +08:00

318 lines
8.7 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>