318 lines
8.7 KiB
HTML
318 lines
8.7 KiB
HTML
<!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>
|