You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/oaweb/components/crud.vue

247 lines
5.6 KiB
Vue

<!--
* crud.vue
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-10 19:29
* Distributed under terms of the MIT license.
-->
<template>
<div>
<div class="v-crud" :vertical='modeV'>
<div class="v-crud-keys">
<template v-for="k of keys" :key="k.name">
<div class="v-crud-cell" :style="Object.assign({},
cstyle.width[k.name], cstyle.k, cstyle.kv[k.name])">
{{ k.label || k.name }}
</div>
</template>
</div>
<div class="v-crud-values">
<div class="v-crud-line rounded-3xl" :class="cstyle.line" v-for=" (item, idx) in items" :key="idx">
<template v-for="k of keys" :key="k.name">
<div class="v-crud-cell" :changed="item.__changed[k.name]" :selected="selected
=== `${item.__idx}.${k.name}`" @click="ifselect ?
selected = `${item.__idx}.${k.name}` : ''" :style="Object.assign({},
cstyle.width[k.name], cstyle.v, cstyle.kv[k.name])">
<slot :name="`k_${k.name}`" :row="item" :value="item[k.name]" :set="setv(item, k.name)">
<template v-if="k.editable === undefined ? editable :
k.editable">
<Vinput :align="valign" :model-value="item[k.name] === undefined ?
k.default : item[k.name]" :type="k.typ" :options="k.options" @update:model-value="setv(item,
k.name)($event)"></Vinput>
</template>
<template v-else>
<span class="truncate">
{{ item[k.name] === undefined ? k.default :
item[k.name] }}
</span>
</template>
<slot :name="`k_${k.name}_append`" :row="item" :value="item[k.name]" :set="setv(item, k.name)">
</slot>
</slot>
</div>
</template>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
interface keyProp {
name: string,
label?: string,
default?: any,
width?: number,
typ?: ArgType,
editable?: boolean,
options?: any,
style?: { [k: string]: string },
}
let emits = defineEmits<{
(e: 'update', v: any): void
}>()
let props = withDefaults(defineProps<{
vertical?: boolean
keys?: keyProp[],
data?: any[]
hover?: boolean,
kstyle?: { [k: string]: string },
vstyle?: { [k: string]: string },
cstyle?: { [k: string]: string },
kalign?: 'center' | 'left' | 'right',
valign?: 'center' | 'left' | 'right',
editable?: boolean
ifselect?: boolean
}>(),
{
vertical: false,
data: [] as any,
hover: false,
kvstyle: {} as any,
kstyle: {} as any,
vstyle: {} as any,
cstyle: {} as any,
kalign: 'center',
valign: 'center',
}
)
const modeV = ref(props.vertical)
watch(computed(() => props.vertical), (v) => modeV.value = v)
let items = ref<any[]>([])
watch(computed(() => JSON.stringify(props.data)), (_) => {
syncItems()
// if (JSON.stringify(v) !== JSON.stringify(o)) {
// console.log(JSON.stringify(v))
// syncItems()
// }
}, {})
const syncItems = () => {
let res = props.data?.map((v: any, i: any) => {
return Object.assign({ __idx: i, __changed: {} }, v)
}) as any
items.value.splice(0, items.value.length)
items.value.push(...res)
// Object.assign(items, res)
}
const selected = ref()
let alignDic = { 'center': 'center', 'left': 'start', 'right': 'end' }
const cstyle = computed(() => {
let res = { line: [], width: {}, kv: {} } as any
let l = props.keys?.length || 0
let w = 100
let style = modeV.value ? 'flex-basis' : 'height'
props.keys?.forEach((k, i) => {
if (k.width && k.width > 0 && k.width < 100) {
res.width[k.name] = { [style]: (k.width || 1) + '%' }
w = w - k.width
l = l - 1
}
if (k.style) {
res.kv[k.name] = k.style
}
})
props.keys?.forEach((k, i) => {
if (k.width && k.width > 0 && k.width < 100) {
} else {
res.width[k.name] = { [style]: w / l + '%' }
}
})
res.k = Object.assign({ 'justify-content': alignDic[props.kalign] }, props.cstyle, props.kstyle)
res.v = Object.assign({ 'justify-content': alignDic[props.valign] }, props.cstyle, props.vstyle)
if (props.hover) {
res.line.push('hover:bg-gray-200');
}
return res
})
const updatedItems = ref<Dict[]>([])
const setv = (item: any, k: string) => {
return (v: any) => {
item[k] = v
item.__changed[k] = true
if (!updatedItems.value[item.__idx]) {
updatedItems.value[item.__idx] = {}
}
updatedItems.value[item.__idx][k] = v
emits('update', updatedItems.value)
console.log(`update ${k} to ${v}`)
}
}
const save = () => {
}
const reload = () => {
console.log('reload')
syncItems()
}
onMounted(() => {
syncItems()
})
defineExpose({
reload
})
</script>
<style lang="scss" scoped>
.v-crud {
display: flex;
}
.v-crud-keys {
display: flex;
}
.v-crud-values {
display: flex;
}
.v-crud-line {
display: flex;
}
.v-crud-cell {
min-width: 8rem;
min-height: 4rem;
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
overflow: auto;
overflow-y: auto;
&[selected=true] {
background: rgba($color: #888, $alpha: .1);
}
&[changed=true] {
background: rgba($color: #888, $alpha: .1);
}
}
.v-crud[vertical=true] {
flex-direction: column;
.v-crud-keys {
flex-wrap: nowrap;
}
.v-crud-values {
flex-direction: column;
.v-crud-line {
flex-wrap: nowrap;
}
}
}
.v-crud[vertical=false] {
flex-wrap: nowrap;
.v-crud-keys {
flex-direction: column;
}
.v-crud-values {
flex-wrap: nowrap;
.v-crud-line {
flex-direction: column;
}
}
}
</style>