mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
fix(table): sorter invalid
This commit is contained in:
parent
ff7938b90c
commit
902aa28746
32 changed files with 805 additions and 399 deletions
|
@ -18,8 +18,8 @@ import (
|
||||||
func GetSiteList(c *gin.Context) {
|
func GetSiteList(c *gin.Context) {
|
||||||
name := c.Query("name")
|
name := c.Query("name")
|
||||||
enabled := c.Query("enabled")
|
enabled := c.Query("enabled")
|
||||||
orderBy := c.Query("order_by")
|
orderBy := c.Query("sort_by")
|
||||||
sort := c.DefaultQuery("sort", "desc")
|
sort := c.DefaultQuery("order", "desc")
|
||||||
querySiteCategoryId := cast.ToUint64(c.Query("site_category_id"))
|
querySiteCategoryId := cast.ToUint64(c.Query("site_category_id"))
|
||||||
|
|
||||||
configFiles, err := os.ReadDir(nginx.GetConfPath("sites-available"))
|
configFiles, err := os.ReadDir(nginx.GetConfPath("sites-available"))
|
||||||
|
|
5
app/components.d.ts
vendored
5
app/components.d.ts
vendored
|
@ -49,6 +49,8 @@ declare module 'vue' {
|
||||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||||
APopover: typeof import('ant-design-vue/es')['Popover']
|
APopover: typeof import('ant-design-vue/es')['Popover']
|
||||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||||
|
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||||
|
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||||
AResult: typeof import('ant-design-vue/es')['Result']
|
AResult: typeof import('ant-design-vue/es')['Result']
|
||||||
ARow: typeof import('ant-design-vue/es')['Row']
|
ARow: typeof import('ant-design-vue/es')['Row']
|
||||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||||
|
@ -86,6 +88,7 @@ declare module 'vue' {
|
||||||
SensitiveStringSensitiveString: typeof import('./src/components/SensitiveString/SensitiveString.vue')['default']
|
SensitiveStringSensitiveString: typeof import('./src/components/SensitiveString/SensitiveString.vue')['default']
|
||||||
SetLanguageSetLanguage: typeof import('./src/components/SetLanguage/SetLanguage.vue')['default']
|
SetLanguageSetLanguage: typeof import('./src/components/SetLanguage/SetLanguage.vue')['default']
|
||||||
StdDesignStdDataDisplayStdBatchEdit: typeof import('./src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue')['default']
|
StdDesignStdDataDisplayStdBatchEdit: typeof import('./src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue')['default']
|
||||||
|
StdDesignStdDataDisplayStdBulkActions: typeof import('./src/components/StdDesign/StdDataDisplay/StdBulkActions.vue')['default']
|
||||||
StdDesignStdDataDisplayStdCurd: typeof import('./src/components/StdDesign/StdDataDisplay/StdCurd.vue')['default']
|
StdDesignStdDataDisplayStdCurd: typeof import('./src/components/StdDesign/StdDataDisplay/StdCurd.vue')['default']
|
||||||
StdDesignStdDataDisplayStdCurdDetail: typeof import('./src/components/StdDesign/StdDataDisplay/StdCurdDetail.vue')['default']
|
StdDesignStdDataDisplayStdCurdDetail: typeof import('./src/components/StdDesign/StdDataDisplay/StdCurdDetail.vue')['default']
|
||||||
StdDesignStdDataDisplayStdPagination: typeof import('./src/components/StdDesign/StdDataDisplay/StdPagination.vue')['default']
|
StdDesignStdDataDisplayStdPagination: typeof import('./src/components/StdDesign/StdDataDisplay/StdPagination.vue')['default']
|
||||||
|
@ -95,6 +98,8 @@ declare module 'vue' {
|
||||||
StdDesignStdDataEntryComponentsStdSelector: typeof import('./src/components/StdDesign/StdDataEntry/components/StdSelector.vue')['default']
|
StdDesignStdDataEntryComponentsStdSelector: typeof import('./src/components/StdDesign/StdDataEntry/components/StdSelector.vue')['default']
|
||||||
StdDesignStdDataEntryStdDataEntry: typeof import('./src/components/StdDesign/StdDataEntry/StdDataEntry.vue')['default']
|
StdDesignStdDataEntryStdDataEntry: typeof import('./src/components/StdDesign/StdDataEntry/StdDataEntry.vue')['default']
|
||||||
StdDesignStdDataEntryStdFormItem: typeof import('./src/components/StdDesign/StdDataEntry/StdFormItem.vue')['default']
|
StdDesignStdDataEntryStdFormItem: typeof import('./src/components/StdDesign/StdDataEntry/StdFormItem.vue')['default']
|
||||||
|
StdDesignStdDataImportStdDataImport: typeof import('./src/components/StdDesign/StdDataImport/StdDataImport.vue')['default']
|
||||||
|
StdDesignStdDetailStdDetail: typeof import('./src/components/StdDesign/StdDetail/StdDetail.vue')['default']
|
||||||
SwitchAppearanceIconsVPIconMoon: typeof import('./src/components/SwitchAppearance/icons/VPIconMoon.vue')['default']
|
SwitchAppearanceIconsVPIconMoon: typeof import('./src/components/SwitchAppearance/icons/VPIconMoon.vue')['default']
|
||||||
SwitchAppearanceIconsVPIconSun: typeof import('./src/components/SwitchAppearance/icons/VPIconSun.vue')['default']
|
SwitchAppearanceIconsVPIconSun: typeof import('./src/components/SwitchAppearance/icons/VPIconSun.vue')['default']
|
||||||
SwitchAppearanceSwitchAppearance: typeof import('./src/components/SwitchAppearance/SwitchAppearance.vue')['default']
|
SwitchAppearanceSwitchAppearance: typeof import('./src/components/SwitchAppearance/SwitchAppearance.vue')['default']
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Notification } from '@/api/notification'
|
import type { Notification } from '@/api/notification'
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { SSEvent } from 'sse.js'
|
import type { SSEvent } from 'sse.js'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import notificationApi from '@/api/notification'
|
import notificationApi from '@/api/notification'
|
||||||
|
@ -52,7 +52,7 @@ function newSSE() {
|
||||||
|
|
||||||
notification[typeTrans[data.type]]({
|
notification[typeTrans[data.type]]({
|
||||||
message: $gettext(data.title),
|
message: $gettext(data.title),
|
||||||
description: detailRender({ text: data.details, record: data } as CustomRenderProps),
|
description: detailRender({ text: data.details, record: data } as CustomRender),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ function viewAll() {
|
||||||
</template>
|
</template>
|
||||||
<AListItemMeta
|
<AListItemMeta
|
||||||
:title="$gettext(item.title)"
|
:title="$gettext(item.title)"
|
||||||
:description="detailRender({ text: item.details, record: item } as CustomRenderProps)"
|
:description="detailRender({ text: item.details, record: item } as CustomRender)"
|
||||||
>
|
>
|
||||||
<template #avatar>
|
<template #avatar>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import { syncCertificateError, syncCertificateSuccess } from '@/components/Notification/cert'
|
import { syncCertificateError, syncCertificateSuccess } from '@/components/Notification/cert'
|
||||||
import {
|
import {
|
||||||
deleteSiteError,
|
deleteSiteError,
|
||||||
|
@ -17,7 +17,7 @@ import {
|
||||||
syncRenameConfigSuccess,
|
syncRenameConfigSuccess,
|
||||||
} from '@/components/Notification/config'
|
} from '@/components/Notification/config'
|
||||||
|
|
||||||
export function detailRender(args: CustomRenderProps) {
|
export function detailRender(args: CustomRender) {
|
||||||
try {
|
try {
|
||||||
switch (args.record.title) {
|
switch (args.record.title) {
|
||||||
case 'Sync Certificate Success':
|
case 'Sync Certificate Success':
|
||||||
|
|
110
app/src/components/StdDesign/StdDataDisplay/StdBulkActions.vue
Normal file
110
app/src/components/StdDesign/StdDataDisplay/StdBulkActions.vue
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
<script setup lang="ts" generic="T=any">
|
||||||
|
import type Curd from '@/api/curd'
|
||||||
|
import type { BulkActionOptions, BulkActions } from '@/components/StdDesign/types'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
api: Curd<T>
|
||||||
|
actions: BulkActions
|
||||||
|
selectedRowKeys: Array<number | string>
|
||||||
|
inTrash?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['onSuccess'])
|
||||||
|
|
||||||
|
const computedActions = computed(() => {
|
||||||
|
if (!props.inTrash) {
|
||||||
|
const result = { ...props.actions }
|
||||||
|
|
||||||
|
if (result.delete) {
|
||||||
|
result.delete = {
|
||||||
|
text: () => $gettext('Delete'),
|
||||||
|
action: ids => {
|
||||||
|
return props.api.batch_destroy(ids)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.recover)
|
||||||
|
delete result.recover
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const result = {} as { [key: string]: BulkActionOptions }
|
||||||
|
if (props.actions.delete) {
|
||||||
|
result.delete = {
|
||||||
|
text: () => $gettext('Delete Permanently'),
|
||||||
|
action: ids => {
|
||||||
|
return props.api.batch_destroy(ids, { permanent: true })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (props.actions.recover) {
|
||||||
|
result.recover = {
|
||||||
|
text: () => $gettext('Recover'),
|
||||||
|
action: ids => {
|
||||||
|
return props.api.batch_recover(ids)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}) as ComputedRef<Record<string, BulkActionOptions>>
|
||||||
|
|
||||||
|
const actionValue = ref('')
|
||||||
|
|
||||||
|
watch(() => props.inTrash, () => {
|
||||||
|
actionValue.value = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
function onClickApply() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (actionValue.value === '')
|
||||||
|
return resolve(false)
|
||||||
|
|
||||||
|
// call action
|
||||||
|
return resolve(
|
||||||
|
computedActions.value[actionValue.value]?.action(props.selectedRowKeys).then(async () => {
|
||||||
|
message.success($gettext('Apply bulk action successfully'))
|
||||||
|
emit('onSuccess')
|
||||||
|
}).catch(e => {
|
||||||
|
message.error($gettext(e?.message) ?? $gettext('Server error'))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AFormItem>
|
||||||
|
<ASpace>
|
||||||
|
<ASelect
|
||||||
|
v-model:value="actionValue"
|
||||||
|
style="min-width: 150px"
|
||||||
|
>
|
||||||
|
<ASelectOption value="">
|
||||||
|
{{ $gettext('Batch Actions') }}
|
||||||
|
</ASelectOption>
|
||||||
|
<ASelectOption
|
||||||
|
v-for="(action, key) in computedActions"
|
||||||
|
:key
|
||||||
|
:value="key"
|
||||||
|
>
|
||||||
|
{{ action.text() }}
|
||||||
|
</ASelectOption>
|
||||||
|
</ASelect>
|
||||||
|
<APopconfirm
|
||||||
|
:cancel-text="$gettext('No')"
|
||||||
|
:ok-text="$gettext('OK')"
|
||||||
|
:title="$gettext('Are you sure you want to apply to all selected?')"
|
||||||
|
@confirm="onClickApply"
|
||||||
|
>
|
||||||
|
<AButton
|
||||||
|
danger
|
||||||
|
:disabled="!actionValue || !selectedRowKeys?.length"
|
||||||
|
>
|
||||||
|
{{ $gettext('Apply') }}
|
||||||
|
</AButton>
|
||||||
|
</APopconfirm>
|
||||||
|
</ASpace>
|
||||||
|
</AFormItem>
|
||||||
|
</template>
|
|
@ -1,28 +1,13 @@
|
||||||
<script setup lang="ts" generic="T=any">
|
<script setup lang="ts" generic="T=any">
|
||||||
|
import type { StdCurdProps, StdTableProps } from '@/components/StdDesign/StdDataDisplay/types'
|
||||||
import type { Column } from '@/components/StdDesign/types'
|
import type { Column } from '@/components/StdDesign/types'
|
||||||
import type { ComputedRef } from 'vue'
|
import type { ComputedRef } from 'vue'
|
||||||
import type { StdTableProps } from './StdTable.vue'
|
|
||||||
import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
|
import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
|
||||||
import StdCurdDetail from '@/components/StdDesign/StdDataDisplay/StdCurdDetail.vue'
|
import StdCurdDetail from '@/components/StdDesign/StdDataDisplay/StdCurdDetail.vue'
|
||||||
import StdDataEntry from '@/components/StdDesign/StdDataEntry'
|
import StdDataEntry from '@/components/StdDesign/StdDataEntry'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import StdTable from './StdTable.vue'
|
import StdTable from './StdTable.vue'
|
||||||
|
|
||||||
export interface StdCurdProps<T> extends StdTableProps<T> {
|
|
||||||
cardTitleKey?: string
|
|
||||||
modalMaxWidth?: string | number
|
|
||||||
modalMask?: boolean
|
|
||||||
exportExcel?: boolean
|
|
||||||
importExcel?: boolean
|
|
||||||
|
|
||||||
disableAdd?: boolean
|
|
||||||
onClickAdd?: () => void
|
|
||||||
|
|
||||||
onClickEdit?: (id: number | string, record: T, index: number) => void
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
|
||||||
beforeSave?: (data: any) => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<StdTableProps<T> & StdCurdProps<T>>()
|
const props = defineProps<StdTableProps<T> & StdCurdProps<T>>()
|
||||||
|
|
||||||
const selectedRowKeys = defineModel<(number | string)[]>('selectedRowKeys', {
|
const selectedRowKeys = defineModel<(number | string)[]>('selectedRowKeys', {
|
||||||
|
@ -76,16 +61,9 @@ function add(preset: any = undefined) {
|
||||||
modifyMode.value = true
|
modifyMode.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = useTemplateRef('table')
|
const table = ref()
|
||||||
|
const inTrash = ref(false)
|
||||||
const getParams = reactive({
|
const getParams = reactive(props.getParams ?? {})
|
||||||
trash: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
|
||||||
function setParams(k: string, v: any) {
|
|
||||||
getParams[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_list() {
|
function get_list() {
|
||||||
table.value?.get_list()
|
table.value?.get_list()
|
||||||
|
@ -95,8 +73,7 @@ defineExpose({
|
||||||
add,
|
add,
|
||||||
get_list,
|
get_list,
|
||||||
data,
|
data,
|
||||||
getParams,
|
inTrash,
|
||||||
setParams,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function clearError() {
|
function clearError() {
|
||||||
|
@ -105,17 +82,14 @@ function clearError() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const stdEntryRef = useTemplateRef('stdEntryRef')
|
const stdEntryRef = ref()
|
||||||
|
|
||||||
async function ok() {
|
async function ok() {
|
||||||
if (!stdEntryRef.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
const { formRef } = stdEntryRef.value
|
const { formRef } = stdEntryRef.value
|
||||||
|
|
||||||
clearError()
|
clearError()
|
||||||
try {
|
try {
|
||||||
await formRef?.validateFields()
|
await formRef.validateFields()
|
||||||
props?.beforeSave?.(data)
|
props?.beforeSave?.(data)
|
||||||
props
|
props
|
||||||
.api!.save(data.id, { ...data, ...props.overwriteParams }, { params: { ...props.overwriteParams } }).then(r => {
|
.api!.save(data.id, { ...data, ...props.overwriteParams }, { params: { ...props.overwriteParams } }).then(r => {
|
||||||
|
@ -177,22 +151,21 @@ async function get(id: number | string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const modalTitle = computed(() => {
|
const modalTitle = computed(() => {
|
||||||
if (data.id)
|
// eslint-disable-next-line sonarjs/no-nested-conditional
|
||||||
return modifyMode.value ? $gettext('Modify') : $gettext('View Details')
|
return data.id ? modifyMode.value ? $gettext('Modify') : $gettext('View Details') : $gettext('Add')
|
||||||
return $gettext('Add')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const localOverwriteParams = reactive(props.overwriteParams ?? {})
|
const localOverwriteParams = reactive(props.overwriteParams ?? {})
|
||||||
|
|
||||||
const stdBatchEditRef = useTemplateRef('stdBatchEditRef')
|
const stdBatchEditRef = ref()
|
||||||
|
|
||||||
async function handleClickBatchEdit(batchColumns: Column[]) {
|
async function handleClickBatchEdit(batchColumns: Column[]) {
|
||||||
stdBatchEditRef.value?.showModal(batchColumns, selectedRowKeys.value, selectedRows.value)
|
stdBatchEditRef.value.showModal(batchColumns, selectedRowKeys.value, selectedRows.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBatchUpdated() {
|
function handleBatchUpdated() {
|
||||||
table.value?.get_list()
|
table.value.get_list()
|
||||||
table.value?.resetSelection()
|
table.value.resetSelection()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -208,24 +181,34 @@ function handleBatchUpdated() {
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<ASpace>
|
<ASpace>
|
||||||
<slot name="beforeAdd" />
|
<slot name="beforeAdd" />
|
||||||
<a
|
<AButton
|
||||||
v-if="!disableAdd && !getParams.trash"
|
v-if="!disableAdd && !inTrash"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
@click="add"
|
@click="add"
|
||||||
>{{ $gettext('Add') }}</a>
|
>
|
||||||
|
{{ $gettext('Add') }}
|
||||||
|
</AButton>
|
||||||
<slot name="extra" />
|
<slot name="extra" />
|
||||||
<template v-if="!disableDelete">
|
<template v-if="!disableDelete">
|
||||||
<a
|
<AButton
|
||||||
v-if="!getParams.trash"
|
v-if="!inTrash"
|
||||||
@click="getParams.trash = true"
|
type="link"
|
||||||
|
size="small"
|
||||||
|
:loading="table?.loading"
|
||||||
|
@click="inTrash = true"
|
||||||
>
|
>
|
||||||
{{ $gettext('Trash') }}
|
{{ $gettext('Trash') }}
|
||||||
</a>
|
</AButton>
|
||||||
<a
|
<AButton
|
||||||
v-else
|
v-else
|
||||||
@click="getParams.trash = false"
|
type="link"
|
||||||
|
size="small"
|
||||||
|
:loading="table?.loading"
|
||||||
|
@click="inTrash = false"
|
||||||
>
|
>
|
||||||
{{ $gettext('Back to list') }}
|
{{ $gettext('Back to list') }}
|
||||||
</a>
|
</AButton>
|
||||||
</template>
|
</template>
|
||||||
</ASpace>
|
</ASpace>
|
||||||
</template>
|
</template>
|
||||||
|
@ -240,6 +223,7 @@ function handleBatchUpdated() {
|
||||||
}"
|
}"
|
||||||
v-model:selected-row-keys="selectedRowKeys"
|
v-model:selected-row-keys="selectedRowKeys"
|
||||||
v-model:selected-rows="selectedRows"
|
v-model:selected-rows="selectedRows"
|
||||||
|
:in-trash="inTrash"
|
||||||
@click-edit="edit"
|
@click-edit="edit"
|
||||||
@click-view="view"
|
@click-view="view"
|
||||||
@selected="onSelect"
|
@selected="onSelect"
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Pagination } from '@/api/curd'
|
import type { Pagination } from '@/api/curd'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
pagination: Pagination
|
pagination: Pagination
|
||||||
size?: 'default' | 'small'
|
size?: 'default' | 'small'
|
||||||
loading: boolean
|
loading?: boolean
|
||||||
}>()
|
showSizeChanger?: boolean
|
||||||
|
}>(), {
|
||||||
|
showSizeChanger: true,
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['change', 'changePageSize', 'update:pagination'])
|
const emit = defineEmits(['change', 'changePageSize', 'update:pagination'])
|
||||||
|
|
||||||
|
@ -33,7 +36,8 @@ const pageSize = computed({
|
||||||
v-model:page-size="pageSize"
|
v-model:page-size="pageSize"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
:current="pagination.current_page"
|
:current="pagination.current_page"
|
||||||
show-size-changer
|
:show-size-changer="showSizeChanger"
|
||||||
|
:show-total="(total:number) => $ngettext('Total %{total} item', 'Total %{total} items', total, { total: total.toString() })"
|
||||||
:size="size"
|
:size="size"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
@change="change"
|
@change="change"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts" generic="T=any">
|
<script setup lang="ts" generic="T=any">
|
||||||
import type { Pagination } from '@/api/curd'
|
import type { GetListResponse, Pagination } from '@/api/curd'
|
||||||
import type Curd from '@/api/curd'
|
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/types'
|
||||||
import type { Column } from '@/components/StdDesign/types'
|
import type { Column } from '@/components/StdDesign/types'
|
||||||
import type { TableProps } from 'ant-design-vue'
|
import type { TableProps } from 'ant-design-vue'
|
||||||
import type { Key } from 'ant-design-vue/es/_util/type'
|
import type { Key } from 'ant-design-vue/es/_util/type'
|
||||||
|
@ -10,43 +10,13 @@ import type { ComputedRef, Ref } from 'vue'
|
||||||
import type { RouteParams } from 'vue-router'
|
import type { RouteParams } from 'vue-router'
|
||||||
import { getPithyColumns } from '@/components/StdDesign/StdDataDisplay/methods/columns'
|
import { getPithyColumns } from '@/components/StdDesign/StdDataDisplay/methods/columns'
|
||||||
import useSortable from '@/components/StdDesign/StdDataDisplay/methods/sortable'
|
import useSortable from '@/components/StdDesign/StdDataDisplay/methods/sortable'
|
||||||
import StdDataEntry from '@/components/StdDesign/StdDataEntry'
|
import StdBulkActions from '@/components/StdDesign/StdDataDisplay/StdBulkActions.vue'
|
||||||
|
import StdDataEntry, { labelRender } from '@/components/StdDesign/StdDataEntry'
|
||||||
import { HolderOutlined } from '@ant-design/icons-vue'
|
import { HolderOutlined } from '@ant-design/icons-vue'
|
||||||
import { watchPausable } from '@vueuse/core'
|
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import StdPagination from './StdPagination.vue'
|
import StdPagination from './StdPagination.vue'
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
|
||||||
export interface StdTableProps<T = any> {
|
|
||||||
title?: string
|
|
||||||
mode?: string
|
|
||||||
rowKey?: string
|
|
||||||
|
|
||||||
api: Curd<T>
|
|
||||||
columns: Column[]
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
|
||||||
getParams?: Record<string, any>
|
|
||||||
size?: string
|
|
||||||
disableQueryParams?: boolean
|
|
||||||
disableSearch?: boolean
|
|
||||||
pithy?: boolean
|
|
||||||
exportExcel?: boolean
|
|
||||||
exportMaterial?: boolean
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
|
||||||
overwriteParams?: Record<string, any>
|
|
||||||
disableView?: boolean
|
|
||||||
disableModify?: boolean
|
|
||||||
selectionType?: string
|
|
||||||
sortable?: boolean
|
|
||||||
disableDelete?: boolean
|
|
||||||
disablePagination?: boolean
|
|
||||||
sortableMoveHook?: (oldRow: number[], newRow: number[]) => boolean
|
|
||||||
scrollX?: string | number
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
|
||||||
getCheckboxProps?: (record: any) => any
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<StdTableProps<T>>(), {
|
const props = withDefaults(defineProps<StdTableProps<T>>(), {
|
||||||
rowKey: 'id',
|
rowKey: 'id',
|
||||||
})
|
})
|
||||||
|
@ -71,6 +41,9 @@ const dataSource: Ref<T[]> = ref([])
|
||||||
const expandKeysList: Ref<Key[]> = ref([])
|
const expandKeysList: Ref<Key[]> = ref([])
|
||||||
|
|
||||||
watch(dataSource, () => {
|
watch(dataSource, () => {
|
||||||
|
if (!props.expandAll)
|
||||||
|
return
|
||||||
|
|
||||||
const res: Key[] = []
|
const res: Key[] = []
|
||||||
|
|
||||||
function buildKeysList(record) {
|
function buildKeysList(record) {
|
||||||
|
@ -105,8 +78,26 @@ const pagination: Pagination = reactive({
|
||||||
total_pages: 1,
|
total_pages: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
const params = reactive({
|
const filterParams = ref({})
|
||||||
...props.getParams,
|
|
||||||
|
const paginationParams = ref({
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortParams = ref({
|
||||||
|
order: 'desc' as 'desc' | 'asc' | undefined,
|
||||||
|
sort_by: '' as Key | readonly Key[] | undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
const params = computed(() => {
|
||||||
|
return {
|
||||||
|
...filterParams.value,
|
||||||
|
...sortParams.value,
|
||||||
|
...props.getParams,
|
||||||
|
...props.overwriteParams,
|
||||||
|
trash: props.inTrash,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@ -149,33 +140,27 @@ const batchColumns = computed(() => {
|
||||||
return props.columns?.filter(column => column.batch) || []
|
return props.columns?.filter(column => column.batch) || []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const radioColumns = computed(() => {
|
||||||
|
return props.columns?.filter(column => column.radio) || []
|
||||||
|
})
|
||||||
|
|
||||||
const get_list = _.debounce(_get_list, 100, {
|
const get_list = _.debounce(_get_list, 100, {
|
||||||
leading: true,
|
leading: false,
|
||||||
trailing: false,
|
trailing: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const filterParams = reactive({})
|
onMounted(async () => {
|
||||||
|
|
||||||
watch(filterParams, () => {
|
|
||||||
Object.assign(params, {
|
|
||||||
...filterParams,
|
|
||||||
page: 1,
|
|
||||||
trash: route.query.trash === 'true',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (!props.disableQueryParams) {
|
if (!props.disableQueryParams) {
|
||||||
Object.assign(params, {
|
filterParams.value = {
|
||||||
...route.query,
|
...route.query,
|
||||||
trash: route.query.trash === 'true',
|
...props.getParams,
|
||||||
})
|
}
|
||||||
|
paginationParams.value.page = Number(route.query.page) || 1
|
||||||
Object.assign(filterParams, {
|
paginationParams.value.page_size = Number(route.query.page_size) || 20
|
||||||
...route.query,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
get_list()
|
get_list()
|
||||||
|
|
||||||
if (props.sortable)
|
if (props.sortable)
|
||||||
|
@ -188,10 +173,11 @@ defineExpose({
|
||||||
get_list,
|
get_list,
|
||||||
pagination,
|
pagination,
|
||||||
resetSelection,
|
resetSelection,
|
||||||
|
loading,
|
||||||
})
|
})
|
||||||
|
|
||||||
function destroy(id: number | string) {
|
function destroy(id: number | string) {
|
||||||
props.api!.destroy(id, { permanent: params.trash }).then(() => {
|
props.api!.destroy(id, { permanent: props.inTrash }).then(() => {
|
||||||
get_list()
|
get_list()
|
||||||
message.success($gettext('Deleted successfully'))
|
message.success($gettext('Deleted successfully'))
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
@ -211,7 +197,7 @@ function recover(id: number | string) {
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
function buildIndexMap(data: any, level: number = 0, index: number = 0, total: number[] = []) {
|
function buildIndexMap(data: any, level: number = 0, index: number = 0, total: number[] = []) {
|
||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
data.forEach((v: any) => {
|
data.forEach((v: any) => {
|
||||||
v.level = level
|
v.level = level
|
||||||
|
|
||||||
|
@ -224,18 +210,12 @@ function buildIndexMap(data: any, level: number = 0, index: number = 0, total: n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _get_list(page_num: number | null = null, page_size = 20) {
|
async function _get_list() {
|
||||||
dataSource.value = []
|
dataSource.value = []
|
||||||
loading.value = true
|
loading.value = true
|
||||||
if (page_num) {
|
|
||||||
params.page = page_num
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
params.page_size = page_size
|
await props.api?.get_list({ ...params.value, ...paginationParams.value }).then(async (r: GetListResponse<any>) => {
|
||||||
}
|
|
||||||
else {
|
|
||||||
params.page = 1
|
|
||||||
params.page_size = page_size
|
|
||||||
}
|
|
||||||
props.api?.get_list({ ...params, ...props.overwriteParams }).then(async r => {
|
|
||||||
dataSource.value = r.data
|
dataSource.value = r.data
|
||||||
rowsKeyIndexMap.value = {}
|
rowsKeyIndexMap.value = {}
|
||||||
if (props.sortable)
|
if (props.sortable)
|
||||||
|
@ -243,11 +223,11 @@ async function _get_list(page_num: number | null = null, page_size = 20) {
|
||||||
|
|
||||||
if (r.pagination)
|
if (r.pagination)
|
||||||
Object.assign(pagination, r.pagination)
|
Object.assign(pagination, r.pagination)
|
||||||
|
|
||||||
loading.value = false
|
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
message.error(e?.message ?? $gettext('Server error'))
|
message.error($gettext(e?.message ?? 'Server error'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
@ -255,17 +235,16 @@ function onTableChange(_pagination: TablePaginationConfig, filters: Record<strin
|
||||||
if (sorter) {
|
if (sorter) {
|
||||||
sorter = sorter as SorterResult
|
sorter = sorter as SorterResult
|
||||||
selectedRowKeys.value = []
|
selectedRowKeys.value = []
|
||||||
params.sort_by = sorter.field
|
sortParams.value.sort_by = sorter.field
|
||||||
params.order = sorter.order === 'ascend' ? 'asc' : 'desc'
|
|
||||||
switch (sorter.order) {
|
switch (sorter.order) {
|
||||||
case 'ascend':
|
case 'ascend':
|
||||||
params.sort = 'asc'
|
sortParams.value.order = 'asc'
|
||||||
break
|
break
|
||||||
case 'descend':
|
case 'descend':
|
||||||
params.sort = 'desc'
|
sortParams.value.order = 'desc'
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
params.sort = null
|
sortParams.value.order = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,7 +265,7 @@ function expandedTable(keys: Key[]) {
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
|
async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
|
||||||
// console.log('onSelect', record, selected, _selectedRows)
|
// console.log('onSelect', record, selected, _selectedRows)
|
||||||
if (props.selectionType === 'checkbox' || batchColumns.value.length > 0 || props.exportExcel) {
|
if (props.selectionType === 'checkbox' || props.exportExcel || batchColumns.value.length > 0 || props.bulkActions) {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
_selectedRows.forEach(v => {
|
_selectedRows.forEach(v => {
|
||||||
if (v) {
|
if (v) {
|
||||||
|
@ -349,72 +328,40 @@ function resetSelection() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
async function resetSearch() {
|
async function resetSearch() {
|
||||||
Object.keys(params).forEach(v => {
|
filterParams.value = {}
|
||||||
delete params[v]
|
|
||||||
})
|
|
||||||
|
|
||||||
Object.assign(params, {
|
|
||||||
...props.getParams,
|
|
||||||
})
|
|
||||||
|
|
||||||
router.push({ query: {} }).catch(() => {
|
|
||||||
})
|
|
||||||
|
|
||||||
Object.keys(filterParams).forEach(v => {
|
|
||||||
delete filterParams[v]
|
|
||||||
})
|
|
||||||
|
|
||||||
updateFilter.value++
|
updateFilter.value++
|
||||||
}
|
}
|
||||||
|
|
||||||
const { stop: stopWatchParams, resume: resumeWatchParams } = watchPausable(params, v => {
|
watch(params, async v => {
|
||||||
if (!init.value)
|
if (!init.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
paginationParams.value = {
|
||||||
|
page: 1,
|
||||||
|
page_size: paginationParams.value.page_size,
|
||||||
|
}
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
if (!props.disableQueryParams)
|
if (!props.disableQueryParams)
|
||||||
router.push({ query: { ...v as RouteParams } })
|
await router.push({ query: { ...v as unknown as RouteParams, ...paginationParams.value } })
|
||||||
else
|
else
|
||||||
get_list()
|
get_list()
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
watch(() => route.query, () => {
|
||||||
|
if (init.value)
|
||||||
|
get_list()
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => route.query, async () => {
|
|
||||||
params.trash = route.query.trash === 'true'
|
|
||||||
|
|
||||||
if (init.value)
|
|
||||||
await get_list()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (props.getParams) {
|
|
||||||
const getParams = computed(() => props.getParams)
|
|
||||||
|
|
||||||
watch(getParams, () => {
|
|
||||||
Object.assign(params, {
|
|
||||||
...props.getParams,
|
|
||||||
page: 1,
|
|
||||||
})
|
|
||||||
}, { deep: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.overwriteParams) {
|
|
||||||
const overwriteParams = computed(() => props.overwriteParams)
|
|
||||||
|
|
||||||
watch(overwriteParams, () => {
|
|
||||||
Object.assign(params, {
|
|
||||||
page: 1,
|
|
||||||
})
|
|
||||||
if (params.page === 1)
|
|
||||||
get_list()
|
|
||||||
}, { deep: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowSelection = computed(() => {
|
const rowSelection = computed(() => {
|
||||||
if (batchColumns.value.length > 0 || props.selectionType || props.exportExcel) {
|
if (batchColumns.value.length > 0 || props.selectionType || props.exportExcel || props.bulkActions) {
|
||||||
return {
|
return {
|
||||||
selectedRowKeys: unref(selectedRowKeys),
|
selectedRowKeys: unref(selectedRowKeys),
|
||||||
onSelect,
|
onSelect,
|
||||||
onSelectAll,
|
onSelectAll,
|
||||||
getCheckboxProps: props?.getCheckboxProps,
|
getCheckboxProps: props?.getCheckboxProps,
|
||||||
type: (batchColumns.value.length > 0 || props.exportExcel) ? 'checkbox' : props.selectionType,
|
type: (batchColumns.value.length > 0 || props.exportExcel || props.bulkActions) ? 'checkbox' : props.selectionType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -435,13 +382,25 @@ function initSortable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changePage(page: number, page_size: number) {
|
async function changePage(page: number, page_size: number) {
|
||||||
stopWatchParams()
|
if (page) {
|
||||||
Object.assign(params, {
|
paginationParams.value = {
|
||||||
page,
|
page,
|
||||||
page_size,
|
page_size,
|
||||||
})
|
}
|
||||||
resumeWatchParams()
|
}
|
||||||
await get_list(page, page_size)
|
else {
|
||||||
|
paginationParams.value = {
|
||||||
|
page: 1,
|
||||||
|
page_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
if (!props.disableQueryParams)
|
||||||
|
await router.push({ query: { ...route.query, ...paginationParams.value } })
|
||||||
|
|
||||||
|
get_list()
|
||||||
}
|
}
|
||||||
|
|
||||||
const paginationSize = computed(() => {
|
const paginationSize = computed(() => {
|
||||||
|
@ -454,6 +413,26 @@ const paginationSize = computed(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="std-table">
|
<div class="std-table">
|
||||||
|
<div v-if="radioColumns.length">
|
||||||
|
<AFormItem
|
||||||
|
v-for="column in radioColumns"
|
||||||
|
:key="column.dataIndex as PropertyKey"
|
||||||
|
:label="labelRender(column.title)"
|
||||||
|
>
|
||||||
|
<ARadioGroup v-model:value="params[column.dataIndex as string]">
|
||||||
|
<ARadioButton :value="undefined">
|
||||||
|
{{ $gettext('All') }}
|
||||||
|
</ARadioButton>
|
||||||
|
<ARadioButton
|
||||||
|
v-for="(value, key) in column.mask"
|
||||||
|
:key
|
||||||
|
:value="key"
|
||||||
|
>
|
||||||
|
{{ labelRender(value) }}
|
||||||
|
</ARadioButton>
|
||||||
|
</ARadioGroup>
|
||||||
|
</AFormItem>
|
||||||
|
</div>
|
||||||
<StdDataEntry
|
<StdDataEntry
|
||||||
v-if="!disableSearch && searchColumns.length"
|
v-if="!disableSearch && searchColumns.length"
|
||||||
:key="updateFilter"
|
:key="updateFilter"
|
||||||
|
@ -473,10 +452,26 @@ const paginationSize = computed(() => {
|
||||||
>
|
>
|
||||||
{{ $gettext('Batch Modify') }}
|
{{ $gettext('Batch Modify') }}
|
||||||
</AButton>
|
</AButton>
|
||||||
|
<Export
|
||||||
|
v-if="props.exportExcel"
|
||||||
|
:columns="props.columns"
|
||||||
|
:api="props.api"
|
||||||
|
:total="pagination.total"
|
||||||
|
:query="params"
|
||||||
|
:ids="selectedRowKeys"
|
||||||
|
/>
|
||||||
<slot name="append-search" />
|
<slot name="append-search" />
|
||||||
</ASpace>
|
</ASpace>
|
||||||
</template>
|
</template>
|
||||||
</StdDataEntry>
|
</StdDataEntry>
|
||||||
|
<StdBulkActions
|
||||||
|
v-if="bulkActions"
|
||||||
|
v-model:selected-row-keys="selectedRowKeys"
|
||||||
|
:api
|
||||||
|
:in-trash="inTrash"
|
||||||
|
:actions="bulkActions"
|
||||||
|
@on-success="() => { resetSelection(); get_list() }"
|
||||||
|
/>
|
||||||
<ATable
|
<ATable
|
||||||
:id="`std-table-${randomId}`"
|
:id="`std-table-${randomId}`"
|
||||||
:columns="pithyColumns"
|
:columns="pithyColumns"
|
||||||
|
@ -497,7 +492,7 @@ const paginationSize = computed(() => {
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.dataIndex === 'action'">
|
<template v-if="column.dataIndex === 'action'">
|
||||||
<template v-if="!props.disableView && !params.trash">
|
<template v-if="!props.disableView && !inTrash">
|
||||||
<AButton
|
<AButton
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -511,7 +506,7 @@ const paginationSize = computed(() => {
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="!props.disableModify && !params.trash">
|
<template v-if="!props.disableModify && !inTrash">
|
||||||
<AButton
|
<AButton
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -529,9 +524,9 @@ const paginationSize = computed(() => {
|
||||||
|
|
||||||
<template v-if="!props.disableDelete">
|
<template v-if="!props.disableDelete">
|
||||||
<APopconfirm
|
<APopconfirm
|
||||||
v-if="!params.trash"
|
v-if="!inTrash"
|
||||||
:cancel-text="$gettext('No')"
|
:cancel-text="$gettext('No')"
|
||||||
:ok-text="$gettext('OK')"
|
:ok-text="$gettext('Ok')"
|
||||||
:title="$gettext('Are you sure you want to delete this item?')"
|
:title="$gettext('Are you sure you want to delete this item?')"
|
||||||
@confirm="destroy(record[rowKey])"
|
@confirm="destroy(record[rowKey])"
|
||||||
>
|
>
|
||||||
|
@ -545,7 +540,7 @@ const paginationSize = computed(() => {
|
||||||
<APopconfirm
|
<APopconfirm
|
||||||
v-else
|
v-else
|
||||||
:cancel-text="$gettext('No')"
|
:cancel-text="$gettext('No')"
|
||||||
:ok-text="$gettext('OK')"
|
:ok-text="$gettext('Ok')"
|
||||||
:title="$gettext('Are you sure you want to recover this item?')"
|
:title="$gettext('Are you sure you want to recover this item?')"
|
||||||
@confirm="recover(record[rowKey])"
|
@confirm="recover(record[rowKey])"
|
||||||
>
|
>
|
||||||
|
@ -558,9 +553,9 @@ const paginationSize = computed(() => {
|
||||||
</APopconfirm>
|
</APopconfirm>
|
||||||
<ADivider type="vertical" />
|
<ADivider type="vertical" />
|
||||||
<APopconfirm
|
<APopconfirm
|
||||||
v-if="params.trash"
|
v-if="inTrash"
|
||||||
:cancel-text="$gettext('No')"
|
:cancel-text="$gettext('No')"
|
||||||
:ok-text="$gettext('OK')"
|
:ok-text="$gettext('Ok')"
|
||||||
:title="$gettext('Are you sure you want to delete this item permanently?')"
|
:title="$gettext('Are you sure you want to delete this item permanently?')"
|
||||||
@confirm="destroy(record[rowKey])"
|
@confirm="destroy(record[rowKey])"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
import type { VNode } from 'vue'
|
||||||
import type { JSX } from 'vue/jsx-runtime'
|
import type { JSX } from 'vue/jsx-runtime'
|
||||||
import { Tag } from 'ant-design-vue'
|
import { CopyOutlined } from '@ant-design/icons-vue'
|
||||||
|
import { message, Tag } from 'ant-design-vue'
|
||||||
// text, record, index, column
|
// text, record, index, column
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash'
|
||||||
|
|
||||||
export interface CustomRenderProps {
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
export interface CustomRender<T = any, R = any> {
|
||||||
text: any
|
text: T
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
record: R
|
||||||
record: any
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
index: any
|
index: any
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
@ -17,11 +18,14 @@ export interface CustomRenderProps {
|
||||||
isDetail?: boolean
|
isDetail?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function datetime(args: CustomRenderProps) {
|
export function datetime(args: CustomRender) {
|
||||||
|
if (!args.text)
|
||||||
|
return '/'
|
||||||
|
|
||||||
return dayjs(args.text).format('YYYY-MM-DD HH:mm:ss')
|
return dayjs(args.text).format('YYYY-MM-DD HH:mm:ss')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function date(args: CustomRenderProps) {
|
export function date(args: CustomRender) {
|
||||||
return args.text ? dayjs(args.text).format('YYYY-MM-DD') : '-'
|
return args.text ? dayjs(args.text).format('YYYY-MM-DD') : '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,11 +34,10 @@ date.isDate = true
|
||||||
datetime.isDatetime = true
|
datetime.isDatetime = true
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
export function mask(maskObj: any): (args: CustomRenderProps) => JSX.Element {
|
export function mask(maskObj: any): (args: CustomRender) => JSX.Element {
|
||||||
return (args: CustomRenderProps) => {
|
return (args: CustomRender) => {
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
let v: any
|
let v: any
|
||||||
|
|
||||||
if (typeof maskObj?.[args.text] === 'function')
|
if (typeof maskObj?.[args.text] === 'function')
|
||||||
v = maskObj[args.text]()
|
v = maskObj[args.text]()
|
||||||
else if (typeof maskObj?.[args.text] === 'string')
|
else if (typeof maskObj?.[args.text] === 'string')
|
||||||
|
@ -45,17 +48,17 @@ export function mask(maskObj: any): (args: CustomRenderProps) => JSX.Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function arrayToTextRender(args: CustomRenderProps) {
|
export function arrayToTextRender(args: CustomRender) {
|
||||||
return args.text?.join(', ')
|
return args.text?.join(', ')
|
||||||
}
|
}
|
||||||
export function actualValueRender(actualDataIndex: string | string[]) {
|
export function actualValueRender(actualDataIndex: string | string[]) {
|
||||||
return (args: CustomRenderProps) => {
|
return (args: CustomRender) => {
|
||||||
return get(args.record, actualDataIndex) || '/'
|
return get(args.record, actualDataIndex) || '/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function longTextWithEllipsis(len: number): (args: CustomRenderProps) => JSX.Element {
|
export function longTextWithEllipsis(len: number): (args: CustomRender) => JSX.Element {
|
||||||
return (args: CustomRenderProps) => {
|
return (args: CustomRender) => {
|
||||||
if (args.isExport || args.isDetail)
|
if (args.isExport || args.isDetail)
|
||||||
return args.text
|
return args.text
|
||||||
|
|
||||||
|
@ -63,13 +66,9 @@ export function longTextWithEllipsis(len: number): (args: CustomRenderProps) =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function year(args: CustomRenderProps) {
|
|
||||||
return dayjs(args.text).format('YYYY')
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
export function maskRenderWithColor(maskObj: any) {
|
export function maskRenderWithColor(maskObj: any, customColors?: Record<string | number, string> | string) {
|
||||||
return (args: CustomRenderProps) => {
|
return (args: CustomRender) => {
|
||||||
let label: string
|
let label: string
|
||||||
if (typeof maskObj[args.text] === 'function')
|
if (typeof maskObj[args.text] === 'function')
|
||||||
label = maskObj[args.text]()
|
label = maskObj[args.text]()
|
||||||
|
@ -80,14 +79,77 @@ export function maskRenderWithColor(maskObj: any) {
|
||||||
if (args.isExport)
|
if (args.isExport)
|
||||||
return label
|
return label
|
||||||
|
|
||||||
const colorMap = {
|
let colorMap: Record<string | number, string> = {
|
||||||
0: '',
|
0: '',
|
||||||
1: 'blue',
|
1: 'blue',
|
||||||
2: 'green',
|
2: 'green',
|
||||||
3: 'red',
|
3: 'purple',
|
||||||
4: 'cyan',
|
4: 'cyan',
|
||||||
}
|
}
|
||||||
|
|
||||||
return args.text ? h(Tag, { color: colorMap[args.text] }, maskObj[args.text]) : '-'
|
if (typeof customColors === 'object')
|
||||||
|
colorMap = customColors
|
||||||
|
|
||||||
|
let color = colorMap[args.text]
|
||||||
|
|
||||||
|
if (typeof customColors === 'string')
|
||||||
|
color = customColors
|
||||||
|
|
||||||
|
return args.text ? h(Tag, { color }, () => label) : '/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MultiFieldRenderProps {
|
||||||
|
key: string | number | string[] | number[]
|
||||||
|
label?: () => string
|
||||||
|
prefix?: string
|
||||||
|
suffix?: string
|
||||||
|
render?: ((args: CustomRender) => string | number | VNode) | (() => ((args: CustomRender) => string | VNode))
|
||||||
|
direction?: 'vertical' | 'horizontal'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function multiFieldsRender(fields: MultiFieldRenderProps[]) {
|
||||||
|
return (args: CustomRender) => {
|
||||||
|
const list = fields.map(field => {
|
||||||
|
let label = field.label?.()
|
||||||
|
let value = get(args.record, field.key)
|
||||||
|
|
||||||
|
if (field.prefix)
|
||||||
|
value = field.prefix + value
|
||||||
|
if (field.suffix)
|
||||||
|
value += field.suffix
|
||||||
|
|
||||||
|
if (label)
|
||||||
|
label += ':'
|
||||||
|
|
||||||
|
const valueNode = field.render?.({ ...args, text: value }) ?? value
|
||||||
|
const direction = field.direction ?? 'vertical'
|
||||||
|
|
||||||
|
const labelNode = label
|
||||||
|
// eslint-disable-next-line sonarjs/no-nested-conditional
|
||||||
|
? h(direction === 'vertical' ? 'div' : 'span', { class: 'text-gray-500 my-1 mr-1' }, label)
|
||||||
|
: null
|
||||||
|
|
||||||
|
return h('div', { class: 'my-4' }, [labelNode, valueNode])
|
||||||
|
})
|
||||||
|
|
||||||
|
return h('div', null, list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copiableFieldRender(args: CustomRender) {
|
||||||
|
return h('div', null, [
|
||||||
|
h('span', null, args.text),
|
||||||
|
h(CopyOutlined, {
|
||||||
|
style: {
|
||||||
|
marginLeft: '10px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
navigator.clipboard.writeText(args.text).then(() => {
|
||||||
|
message.success($gettext('Copied'))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
export function CustomRender(props: CustomRenderProps) {
|
// eslint-disable-next-line ts/no-redeclare,sonarjs/no-redeclare
|
||||||
|
export function CustomRender(props: CustomRender) {
|
||||||
return props.column.customRender
|
return props.column.customRender
|
||||||
? props.column.customRender(props)
|
? props.column.customRender(props)
|
||||||
: _.get(props.record, props.column.dataIndex!)
|
: _.get(props.record, props.column.dataIndex!)
|
||||||
|
|
|
@ -2,8 +2,4 @@ import StdBatchEdit from './StdBatchEdit.vue'
|
||||||
import StdCurd from './StdCurd.vue'
|
import StdCurd from './StdCurd.vue'
|
||||||
import StdTable from './StdTable.vue'
|
import StdTable from './StdTable.vue'
|
||||||
|
|
||||||
export {
|
export { StdBatchEdit, StdCurd, StdTable }
|
||||||
StdBatchEdit,
|
|
||||||
StdCurd,
|
|
||||||
StdTable,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import type { GetListResponse } from '@/api/curd'
|
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/types'
|
||||||
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
|
import type { Column, StdTableResponse } from '@/components/StdDesign/types'
|
||||||
import type { Column } from '@/components/StdDesign/types'
|
|
||||||
import type { ComputedRef } from 'vue'
|
import type { ComputedRef } from 'vue'
|
||||||
import { downloadCsv } from '@/lib/helper'
|
import { downloadCsv } from '@/lib/helper'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
@ -32,10 +31,9 @@ async function exportCsv(props: StdTableProps, pithyColumns: ComputedRef<Column[
|
||||||
let hasMore = true
|
let hasMore = true
|
||||||
let page = 1
|
let page = 1
|
||||||
while (hasMore) {
|
while (hasMore) {
|
||||||
// 准备 DataSource
|
// prepare dataSource
|
||||||
await props
|
await props
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
.api!.get_list({ page }).then((r: StdTableResponse) => {
|
||||||
.api!.get_list({ page }).then((r: GetListResponse<any>) => {
|
|
||||||
if (r.data.length === 0) {
|
if (r.data.length === 0) {
|
||||||
hasMore = false
|
hasMore = false
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
|
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/types'
|
||||||
import type { Key } from 'ant-design-vue/es/_util/type'
|
import type { Key } from 'ant-design-vue/es/_util/type'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
@ -26,111 +26,105 @@ function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Re
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
const table: any = document.querySelector(`#std-table-${randomId.value} tbody`)
|
const table: any = document.querySelector(`#std-table-${randomId.value} tbody`)
|
||||||
|
|
||||||
try {
|
// eslint-disable-next-line no-new,new-cap,sonarjs/constructor-for-side-effects
|
||||||
// eslint-disable-next-line no-new,new-cap
|
new sortable(table, {
|
||||||
new sortable(table, {
|
handle: '.ant-table-drag-icon',
|
||||||
handle: '.ant-table-drag-icon',
|
animation: 150,
|
||||||
animation: 150,
|
sort: true,
|
||||||
sort: true,
|
forceFallback: true,
|
||||||
forceFallback: true,
|
setData(dataTransfer) {
|
||||||
setData(dataTransfer) {
|
dataTransfer.setData('Text', '')
|
||||||
dataTransfer.setData('Text', '')
|
},
|
||||||
},
|
onStart({ item }) {
|
||||||
onStart({ item }) {
|
const targetRowKey = Number(getRowKey(item))
|
||||||
const targetRowKey = Number(getRowKey(item))
|
if (targetRowKey)
|
||||||
if (targetRowKey)
|
expandKeysList.value = expandKeysList.value.filter((_item: Key) => _item !== targetRowKey)
|
||||||
expandKeysList.value = expandKeysList.value.filter((_item: Key) => _item !== targetRowKey)
|
},
|
||||||
},
|
onMove({
|
||||||
onMove({
|
dragged,
|
||||||
dragged,
|
related,
|
||||||
related,
|
}) {
|
||||||
}) {
|
const oldRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(dragged))]
|
||||||
const oldRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(dragged))]
|
const newRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(related))]
|
||||||
const newRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(related))]
|
|
||||||
|
|
||||||
if (oldRow.length !== newRow.length || oldRow[oldRow.length - 2] !== newRow[newRow.length - 2])
|
if (oldRow.length !== newRow.length || oldRow[oldRow.length - 2] !== newRow[newRow.length - 2])
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if (props.sortableMoveHook)
|
if (props.sortableMoveHook)
|
||||||
return props.sortableMoveHook(oldRow, newRow)
|
return props.sortableMoveHook(oldRow, newRow)
|
||||||
},
|
},
|
||||||
async onEnd({
|
async onEnd({
|
||||||
item,
|
item,
|
||||||
newIndex,
|
newIndex,
|
||||||
oldIndex,
|
oldIndex,
|
||||||
}) {
|
}) {
|
||||||
if (newIndex === oldIndex || newIndex === undefined)
|
if (newIndex === oldIndex)
|
||||||
return
|
return
|
||||||
|
|
||||||
const indexDelta: number = Number(oldIndex) - Number(newIndex)
|
const indexDelta: number = Number(oldIndex) - Number(newIndex)
|
||||||
const direction: number = indexDelta > 0 ? +1 : -1
|
const direction: number = indexDelta > 0 ? +1 : -1
|
||||||
|
|
||||||
const rowIndex: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(item))]
|
const rowIndex: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(item))]
|
||||||
const newRow = getTargetData(dataSource.value, rowIndex)
|
const newRow = getTargetData(dataSource.value, rowIndex)
|
||||||
const newRowParent = newRow.parent
|
const newRowParent = newRow.parent
|
||||||
const level: number = newRow.level
|
const level: number = newRow.level
|
||||||
|
|
||||||
const currentRowIndex: number[] = [...rowsKeyIndexMap.value?.
|
const currentRowIndex: number[] = [...rowsKeyIndexMap.value![Number(getRowKey(table?.children?.[Number(newIndex) + direction]))]]
|
||||||
[Number(getRowKey(table.children[Number(newIndex) + direction]))]]
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
const currentRow: any = getTargetData(dataSource.value, currentRowIndex)
|
||||||
|
|
||||||
|
// Reset parent
|
||||||
|
currentRow.parent = newRow.parent = null
|
||||||
|
newRowParent.children.splice(rowIndex[level], 1)
|
||||||
|
newRowParent.children.splice(currentRowIndex[level], 0, toRaw(newRow))
|
||||||
|
|
||||||
|
const changeIds: number[] = []
|
||||||
|
|
||||||
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
function processChanges(row: any, children = false, _newIndex: number | undefined = undefined) {
|
||||||
|
// Build changes ID list expect new row
|
||||||
|
if (children || _newIndex === undefined)
|
||||||
|
changeIds.push(row.id)
|
||||||
|
|
||||||
|
if (_newIndex !== undefined)
|
||||||
|
rowsKeyIndexMap.value[row.id][level] = _newIndex
|
||||||
|
else if (children)
|
||||||
|
rowsKeyIndexMap.value[row.id][level] += direction
|
||||||
|
|
||||||
|
row.parent = null
|
||||||
|
if (row.children)
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
const currentRow: any = getTargetData(dataSource.value, currentRowIndex)
|
row.children.forEach((v: any) => processChanges(v, true, _newIndex))
|
||||||
|
}
|
||||||
|
|
||||||
// Reset parent
|
// Replace row index for new row
|
||||||
currentRow.parent = newRow.parent = null
|
processChanges(newRow, false, currentRowIndex[level])
|
||||||
newRowParent.children.splice(rowIndex[level], 1)
|
|
||||||
newRowParent.children.splice(currentRowIndex[level], 0, toRaw(newRow))
|
|
||||||
|
|
||||||
const changeIds: number[] = []
|
// Rebuild row index maps for changes row
|
||||||
|
// eslint-disable-next-line sonarjs/no-equals-in-for-termination
|
||||||
|
for (let i = Number(oldIndex); i !== newIndex; i -= direction) {
|
||||||
|
const _rowIndex: number[] = rowsKeyIndexMap.value?.[getRowKey(table.children[i])]
|
||||||
|
|
||||||
|
_rowIndex[level] += direction
|
||||||
|
processChanges(getTargetData(dataSource.value, _rowIndex))
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('Change row id', newRow.id, 'order', newRow.id, '=>', currentRow.id, ', direction: ', direction,
|
||||||
|
// ', changes IDs:', changeIds
|
||||||
|
|
||||||
|
props.api.update_order({
|
||||||
|
target_id: newRow.id,
|
||||||
|
direction,
|
||||||
|
affected_ids: changeIds,
|
||||||
|
}).then(() => {
|
||||||
|
message.success($gettext('Updated successfully'))
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
function processChanges(row: any, children = false, _newIndex: number | undefined = undefined) {
|
}).catch((e: any) => {
|
||||||
// Build changes ID list expect new row
|
message.error(e?.message ?? $gettext('Server error'))
|
||||||
if (children || _newIndex === undefined)
|
})
|
||||||
changeIds.push(row.id)
|
},
|
||||||
|
})
|
||||||
if (_newIndex !== undefined)
|
|
||||||
rowsKeyIndexMap.value[row.id][level] = _newIndex
|
|
||||||
else if (children)
|
|
||||||
rowsKeyIndexMap.value[row.id][level] += direction
|
|
||||||
|
|
||||||
row.parent = null
|
|
||||||
if (row.children) {
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
|
||||||
row.children.forEach((v: any) => processChanges(v, true, _newIndex))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace row index for new row
|
|
||||||
processChanges(newRow, false, currentRowIndex[level])
|
|
||||||
|
|
||||||
// Rebuild row index maps for changes row
|
|
||||||
for (let i = Number(oldIndex); i >= newIndex; i -= direction) {
|
|
||||||
const _rowIndex: number[] = rowsKeyIndexMap.value?.[getRowKey(table.children[i])]
|
|
||||||
|
|
||||||
_rowIndex[level] += direction
|
|
||||||
processChanges(getTargetData(dataSource.value, _rowIndex))
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('Change row id', newRow.id, 'order', newRow.id, '=>', currentRow.id, ', direction: ', direction,
|
|
||||||
// ', changes IDs:', changeIds
|
|
||||||
|
|
||||||
props.api.update_order({
|
|
||||||
target_id: newRow.id,
|
|
||||||
direction,
|
|
||||||
affected_ids: changeIds,
|
|
||||||
}).then(() => {
|
|
||||||
message.success($gettext('Updated successfully'))
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
|
||||||
}).catch((e: any) => {
|
|
||||||
message.error(e?.message ?? $gettext('Server error'))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useSortable
|
export default useSortable
|
||||||
|
|
|
@ -1,6 +1,50 @@
|
||||||
/* eslint-disable ts/no-explicit-any */
|
import type { ImportConfig } from '@/components/StdDesign/StdDataImport/types'
|
||||||
|
|
||||||
export interface StdTableSlots {
|
export interface StdCurdProps<T> extends StdTableProps<T> {
|
||||||
'append-search': (action) => any
|
cardTitleKey?: string
|
||||||
'actions': (actions: Record<string, any>) => any
|
modalMaxWidth?: string | number
|
||||||
|
modalMask?: boolean
|
||||||
|
exportExcel?: boolean
|
||||||
|
importExcel?: boolean
|
||||||
|
|
||||||
|
disableAdd?: boolean
|
||||||
|
onClickAdd?: () => void
|
||||||
|
|
||||||
|
onClickEdit?: (id: number | string, record: T, index: number) => void
|
||||||
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
beforeSave?: (data: any) => Promise<void>
|
||||||
|
importConfig?: ImportConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
export interface StdTableProps<T = any> {
|
||||||
|
title?: string
|
||||||
|
mode?: string
|
||||||
|
rowKey?: string
|
||||||
|
|
||||||
|
api: Curd<T>
|
||||||
|
columns: Column[]
|
||||||
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
getParams?: Record<string, any>
|
||||||
|
size?: string
|
||||||
|
disableQueryParams?: boolean
|
||||||
|
disableSearch?: boolean
|
||||||
|
pithy?: boolean
|
||||||
|
exportExcel?: boolean
|
||||||
|
exportMaterial?: boolean
|
||||||
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
overwriteParams?: Record<string, any>
|
||||||
|
disableView?: boolean
|
||||||
|
disableModify?: boolean
|
||||||
|
selectionType?: string
|
||||||
|
sortable?: boolean
|
||||||
|
disableDelete?: boolean
|
||||||
|
disablePagination?: boolean
|
||||||
|
sortableMoveHook?: (oldRow: number[], newRow: number[]) => boolean
|
||||||
|
scrollX?: string | number
|
||||||
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
getCheckboxProps?: (record: any) => any
|
||||||
|
bulkActions?: BulkActions
|
||||||
|
inTrash?: boolean
|
||||||
|
expandAll?: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SelectProps } from 'ant-design-vue'
|
import type { SelectProps } from 'ant-design-vue'
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mask?: Record<string | number, string | (() => string)> | (() => Promise<Record<string | number, string>>)
|
mask?: Record<string | number, string | (() => string)> | (() => Promise<Record<string | number, string>>)
|
||||||
|
@ -62,16 +61,14 @@ onMounted(() => {
|
||||||
<template>
|
<template>
|
||||||
<ASelect
|
<ASelect
|
||||||
v-model:value="selectedValue"
|
v-model:value="selectedValue"
|
||||||
|
allow-clear
|
||||||
:options="options"
|
:options="options"
|
||||||
:placeholder="props.placeholder"
|
:placeholder="props.placeholder"
|
||||||
:default-active-first-option="false"
|
:default-active-first-option="false"
|
||||||
:mode="props.multiple ? 'multiple' : undefined"
|
:mode="props.multiple ? 'multiple' : undefined"
|
||||||
style="min-width: 180px"
|
class="min-w-180px w-auto!"
|
||||||
allow-clear
|
|
||||||
:get-popup-container="triggerNode => triggerNode.parentNode"
|
:get-popup-container="triggerNode => triggerNode.parentNode"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ const props = defineProps<{
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
getCheckboxProps?: (record: any) => any
|
getCheckboxProps?: (record: any) => any
|
||||||
hideInputContainer?: boolean
|
hideInputContainer?: boolean
|
||||||
|
expandAll?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const selectedKey = defineModel<number | number[] | undefined | null | string | string[]>('selectedKey')
|
const selectedKey = defineModel<number | number[] | undefined | null | string | string[]>('selectedKey')
|
||||||
|
@ -195,6 +196,7 @@ defineExpose({ show })
|
||||||
:columns
|
:columns
|
||||||
:disable-search
|
:disable-search
|
||||||
:row-key="itemKey"
|
:row-key="itemKey"
|
||||||
|
:expand-all
|
||||||
:get-params
|
:get-params
|
||||||
:selection-type
|
:selection-type
|
||||||
:get-checkbox-props
|
:get-checkbox-props
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
InputNumber,
|
InputNumber,
|
||||||
RangePicker,
|
RangePicker,
|
||||||
Switch,
|
Switch,
|
||||||
Textarea,
|
|
||||||
} from 'ant-design-vue'
|
} from 'ant-design-vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
|
@ -66,13 +65,15 @@ export function inputNumber(edit: StdDesignEdit, dataSource: any, dataIndex: any
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
export function textarea(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
|
export function textarea(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
|
||||||
return h(Textarea, {
|
if (!dataSource[dataIndex])
|
||||||
'placeholder': placeholderHelper(edit),
|
dataSource[dataIndex] = edit.config?.defaultValue
|
||||||
'value': dataSource?.[dataIndex] ?? edit?.config?.defaultValue,
|
|
||||||
'onUpdate:value': value => {
|
return (
|
||||||
dataSource[dataIndex] = value
|
<Input
|
||||||
},
|
v-model:value={dataSource[dataIndex]}
|
||||||
})
|
placeholder={placeholderHelper(edit)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
|
@ -109,12 +110,14 @@ export function selector(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
|
||||||
v-model:selectedKey={dataSource[dataIndex]}
|
v-model:selectedKey={dataSource[dataIndex]}
|
||||||
selectedKey={dataSource[dataIndex] || edit?.config?.defaultValue}
|
selectedKey={dataSource[dataIndex] || edit?.config?.defaultValue}
|
||||||
recordValueIndex={edit.selector?.recordValueIndex}
|
recordValueIndex={edit.selector?.recordValueIndex}
|
||||||
selectionType={edit.selector?.selectionType}
|
selectionType={edit.selector?.selectionType ?? 'radio'}
|
||||||
api={edit.selector?.api}
|
api={edit.selector?.api}
|
||||||
columns={edit.selector?.columns}
|
columns={edit.selector?.columns}
|
||||||
disableSearch={edit.selector?.disableSearch}
|
disableSearch={edit.selector?.disableSearch}
|
||||||
getParams={edit.selector?.getParams}
|
getParams={edit.selector?.getParams}
|
||||||
description={edit.selector?.description}
|
description={edit.selector?.description}
|
||||||
|
getCheckboxProps={edit.selector?.getCheckboxProps}
|
||||||
|
expandAll={edit.selector?.expandAll}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -132,13 +135,15 @@ export function switcher(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
|
||||||
|
|
||||||
// eslint-disable-next-line ts/no-explicit-any
|
// eslint-disable-next-line ts/no-explicit-any
|
||||||
export function datePicker(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
|
export function datePicker(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
|
||||||
const date: Dayjs | undefined = dataSource?.[dataIndex] ? dayjs(dataSource?.[dataIndex]) : undefined
|
const date: Dayjs | undefined = dataSource?.[dataIndex] ? dayjs.unix(dataSource?.[dataIndex]) : undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
format={DATE_FORMAT}
|
allowClear
|
||||||
|
format={edit?.datePicker?.format ?? DATE_FORMAT}
|
||||||
|
picker={edit?.datePicker?.picker}
|
||||||
value={date}
|
value={date}
|
||||||
onChange={(_, dataString) => dataSource[dataIndex] = dataString ?? undefined}
|
onChange={(_, dataString) => dataSource[dataIndex] = dayjs(dataString).unix() ?? undefined}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -152,7 +157,9 @@ export function dateRangePicker(edit: StdDesignEdit, dataSource: any, dataIndex:
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RangePicker
|
<RangePicker
|
||||||
format={DATE_FORMAT}
|
allowClear
|
||||||
|
format={edit?.datePicker?.format ?? DATE_FORMAT}
|
||||||
|
picker={edit?.datePicker?.picker}
|
||||||
value={dates}
|
value={dates}
|
||||||
onChange={(_, dateStrings: [string, string]) => dataSource[dataIndex] = dateStrings}
|
onChange={(_, dateStrings: [string, string]) => dataSource[dataIndex] = dateStrings}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.std-data-entry-action {
|
.std-data-entry-action {
|
||||||
@media (max-width: 375px) {
|
@media (max-width: 375px) {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
app/src/components/StdDesign/StdDataEntry/types.d.ts
vendored
Normal file
25
app/src/components/StdDesign/StdDataEntry/types.d.ts
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import type { DefaultOptionType } from 'ant-design-vue/es/vc-cascader'
|
||||||
|
|
||||||
|
export interface Author {
|
||||||
|
id?: number
|
||||||
|
name: string
|
||||||
|
checked?: boolean
|
||||||
|
sort?: number
|
||||||
|
affiliated_unit?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthorSelector {
|
||||||
|
input?: {
|
||||||
|
title?: () => string
|
||||||
|
placeholder?: () => string
|
||||||
|
}
|
||||||
|
checkbox?: {
|
||||||
|
title?: () => string
|
||||||
|
placeholder?: () => string
|
||||||
|
}
|
||||||
|
select?: {
|
||||||
|
title?: () => string
|
||||||
|
placeholder?: () => string
|
||||||
|
options?: DefaultOptionType[]
|
||||||
|
}
|
||||||
|
}
|
141
app/src/components/StdDesign/StdDetail/StdDetail.vue
Normal file
141
app/src/components/StdDesign/StdDetail/StdDetail.vue
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
<script setup lang="ts" generic="T extends ModelBase">
|
||||||
|
import type { ModelBase } from '@/api/curd'
|
||||||
|
import type Curd from '@/api/curd'
|
||||||
|
import type { Column, StdDesignEdit } from '@/components/StdDesign/types'
|
||||||
|
import type { ButtonProps, FormInstance } from 'ant-design-vue'
|
||||||
|
import type { DataIndex } from 'ant-design-vue/es/vc-table/interface'
|
||||||
|
import { labelRender } from '@/components/StdDesign/StdDataEntry'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
import _, { get } from 'lodash'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
title?: string
|
||||||
|
dataSource?: T
|
||||||
|
api: Curd<T>
|
||||||
|
columns: Column[]
|
||||||
|
actionButtonProps?: ButtonProps
|
||||||
|
useOutsideData?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const detail = ref(props.dataSource) as Ref<T | undefined>
|
||||||
|
const editModel = ref({}) as Ref<T | undefined>
|
||||||
|
const editStatus = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
watch(() => props.dataSource, val => detail.value = val)
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
loading.value = true
|
||||||
|
props.api.save(editModel.value?.id, editModel.value).then(res => {
|
||||||
|
detail.value = res
|
||||||
|
editStatus.value = false
|
||||||
|
}).catch(() => {
|
||||||
|
message.error('Save failed')
|
||||||
|
}).finally(() => loading.value = false)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
message.error('Validation failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormController(p: { editConfig: StdDesignEdit, dataIndex?: DataIndex }) {
|
||||||
|
return p?.editConfig?.type?.(p.editConfig, editModel.value, p.dataIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomRender(p: { column?: Column, text: unknown, record?: T }) {
|
||||||
|
const { column, text, record } = p
|
||||||
|
return column?.customRender?.({ text, record }) ?? text ?? '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props?.useOutsideData) {
|
||||||
|
editModel.value = _.cloneDeep(props.dataSource)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
props.api.get(route.params.id).then(res => {
|
||||||
|
detail.value = res
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function clickEdit() {
|
||||||
|
editModel.value = _.cloneDeep(detail.value)
|
||||||
|
editStatus.value = true
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AForm
|
||||||
|
ref="formRef"
|
||||||
|
:model="editModel"
|
||||||
|
>
|
||||||
|
<ADescriptions
|
||||||
|
bordered
|
||||||
|
:title="props.title ?? $gettext('Info')"
|
||||||
|
:column="2"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<ASpace v-if="editStatus">
|
||||||
|
<AButton
|
||||||
|
type="primary"
|
||||||
|
:disabled="loading"
|
||||||
|
:loading="loading"
|
||||||
|
v-bind="props.actionButtonProps"
|
||||||
|
@click="save"
|
||||||
|
>
|
||||||
|
{{ $gettext('Save') }}
|
||||||
|
</AButton>
|
||||||
|
<AButton
|
||||||
|
:disabled="loading"
|
||||||
|
:loading="loading"
|
||||||
|
v-bind="props.actionButtonProps"
|
||||||
|
@click="editStatus = false"
|
||||||
|
>
|
||||||
|
{{ $gettext('Cancel') }}
|
||||||
|
</AButton>
|
||||||
|
</ASpace>
|
||||||
|
<div v-else>
|
||||||
|
<AButton
|
||||||
|
type="primary"
|
||||||
|
v-bind="props.actionButtonProps"
|
||||||
|
@click="clickEdit"
|
||||||
|
>
|
||||||
|
{{ $gettext('Edit') }}
|
||||||
|
</AButton>
|
||||||
|
<slot name="extra" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<ADescriptionsItem
|
||||||
|
v-for="c in props.columns.filter(c => c.dataIndex !== 'action')"
|
||||||
|
:key="c.dataIndex?.toString()"
|
||||||
|
:label="$gettext(labelRender(c.title) ?? '')"
|
||||||
|
>
|
||||||
|
<AFormItem
|
||||||
|
v-if="editStatus && c.edit"
|
||||||
|
class="mb-0"
|
||||||
|
:name="c.dataIndex?.toString()"
|
||||||
|
:required="c?.edit?.config?.required"
|
||||||
|
>
|
||||||
|
<FormController
|
||||||
|
:edit-config="c.edit"
|
||||||
|
:data-index="c.dataIndex"
|
||||||
|
/>
|
||||||
|
</AFormItem>
|
||||||
|
<span v-else>
|
||||||
|
<CustomRender
|
||||||
|
:column="c"
|
||||||
|
:text="get(detail, c.dataIndex as any)"
|
||||||
|
:record="detail"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</ADescriptionsItem>
|
||||||
|
</ADescriptions>
|
||||||
|
</AForm>
|
||||||
|
</template>
|
67
app/src/components/StdDesign/types.d.ts
vendored
67
app/src/components/StdDesign/types.d.ts
vendored
|
@ -1,33 +1,52 @@
|
||||||
|
/* eslint-disable ts/no-explicit-any */
|
||||||
|
|
||||||
|
import type { Pagination } from '@/api/curd'
|
||||||
import type Curd from '@/api/curd'
|
import type Curd from '@/api/curd'
|
||||||
|
|
||||||
import type { TableColumnType } from 'ant-design-vue'
|
import type { TableColumnType } from 'ant-design-vue'
|
||||||
import type { Ref } from 'vue'
|
import type { RuleObject } from 'ant-design-vue/es/form'
|
||||||
import type { JSX } from 'vue/jsx'
|
import type { JSX } from 'vue/jsx'
|
||||||
|
|
||||||
export type JSXElements = JSX.Element[]
|
export type JSXElements = JSX.Element[]
|
||||||
|
|
||||||
|
// use for select-option
|
||||||
|
export type StdDesignMask =
|
||||||
|
Record<string | number, string | (() => string)>
|
||||||
|
| (() => Promise<Record<string | number, string>>)
|
||||||
|
|
||||||
export interface StdDesignEdit {
|
export interface StdDesignEdit {
|
||||||
|
|
||||||
type?: (edit: StdDesignEdit, dataSource: any, dataIndex: any) => JSX.Element // component type
|
type?: (edit: StdDesignEdit, dataSource: any, dataIndex: any) => JSX.Element // component type
|
||||||
|
|
||||||
show?: (dataSource: any) => boolean // show component or not
|
show?: (dataSource: any) => boolean // show component or not
|
||||||
|
|
||||||
batch?: boolean // batch edit
|
batch?: boolean // batch edit
|
||||||
|
|
||||||
mask?: Record<string | number, string | (() => string)> | (() => Promise<Record<string | number, string>>) // use for select-option
|
mask?: StdDesignMask
|
||||||
|
|
||||||
rules?: [] // validator rules
|
rules?: RuleObject[] // validator rules
|
||||||
|
|
||||||
hint?: string | (() => string) // hint form item
|
hint?: string | (() => string) // hint form item
|
||||||
|
|
||||||
actualDataIndex?: string
|
actualDataIndex?: string
|
||||||
|
|
||||||
|
datePicker?: {
|
||||||
|
picker?: 'date' | 'week' | 'month' | 'year' | 'quarter'
|
||||||
|
format?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
cascader?: {
|
||||||
|
api: () => Promise<any>
|
||||||
|
fieldNames: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
select?: {
|
select?: {
|
||||||
multiple?: boolean
|
multiple?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
selector?: {
|
selector?: {
|
||||||
getParams?: object
|
getParams?: Record<string | number, any>
|
||||||
recordValueIndex: any // relative to api return
|
selectionType?: 'radio' | 'checkbox'
|
||||||
selectionType: any
|
|
||||||
api: Curd
|
api: Curd
|
||||||
valueApi?: Curd
|
valueApi?: Curd
|
||||||
columns: any
|
columns: any
|
||||||
|
@ -36,6 +55,9 @@ export interface StdDesignEdit {
|
||||||
bind?: any
|
bind?: any
|
||||||
itemKey?: any // default is id
|
itemKey?: any // default is id
|
||||||
dataSourceValueIndex?: any // relative to dataSource
|
dataSourceValueIndex?: any // relative to dataSource
|
||||||
|
recordValueIndex?: any // relative to dataSource
|
||||||
|
getCheckboxProps?: (record: any) => any
|
||||||
|
expandAll?: boolean
|
||||||
} // StdSelector Config
|
} // StdSelector Config
|
||||||
|
|
||||||
upload?: {
|
upload?: {
|
||||||
|
@ -73,14 +95,13 @@ export interface StdDesignEdit {
|
||||||
flex?: Flex
|
flex?: Flex
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlexType = string | number | boolean
|
|
||||||
|
|
||||||
export interface Flex {
|
export interface Flex {
|
||||||
sm?: FlexType
|
// eslint-disable-next-line sonarjs/use-type-alias
|
||||||
md?: FlexType
|
sm?: string | number | boolean
|
||||||
lg?: FlexType
|
md?: string | number | boolean
|
||||||
xl?: FlexType
|
lg?: string | number | boolean
|
||||||
xxl?: FlexType
|
xl?: string | number | boolean
|
||||||
|
xxl?: string | number | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Column extends TableColumnType {
|
export interface Column extends TableColumnType {
|
||||||
|
@ -98,9 +119,11 @@ export interface Column extends TableColumnType {
|
||||||
hiddenInExport?: boolean
|
hiddenInExport?: boolean
|
||||||
import?: boolean
|
import?: boolean
|
||||||
batch?: boolean
|
batch?: boolean
|
||||||
|
radio?: boolean
|
||||||
|
mask?: StdDesignMask
|
||||||
customRender?: function
|
customRender?: function
|
||||||
selector?: {
|
selector?: {
|
||||||
getParams?: object
|
getParams?: Record<string | number, any>
|
||||||
recordValueIndex: any // relative to api return
|
recordValueIndex: any // relative to api return
|
||||||
selectionType: any
|
selectionType: any
|
||||||
api: Curd
|
api: Curd
|
||||||
|
@ -111,5 +134,21 @@ export interface Column extends TableColumnType {
|
||||||
bind?: any
|
bind?: any
|
||||||
itemKey?: any // default is id
|
itemKey?: any // default is id
|
||||||
dataSourceValueIndex?: any // relative to dataSource
|
dataSourceValueIndex?: any // relative to dataSource
|
||||||
|
getCheckboxProps?: (record: any) => any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StdTableResponse {
|
||||||
|
data: any[]
|
||||||
|
pagination: Pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BulkActionOptions {
|
||||||
|
text: () => string
|
||||||
|
action: (rows: (number | string)[] | undefined) => Promise<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BulkActions = Record<string, BulkActionOptions | boolean> & {
|
||||||
|
delete?: boolean | BulkActionOptions
|
||||||
|
recover?: boolean | BulkActionOptions
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import type { AcmeUser } from '@/api/acme_user'
|
import type { AcmeUser } from '@/api/acme_user'
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { Column } from '@/components/StdDesign/types'
|
import type { Column } from '@/components/StdDesign/types'
|
||||||
import acme_user from '@/api/acme_user'
|
import acme_user from '@/api/acme_user'
|
||||||
import { StdCurd } from '@/components/StdDesign/StdDataDisplay'
|
import { StdCurd } from '@/components/StdDesign/StdDataDisplay'
|
||||||
|
@ -64,7 +64,7 @@ const columns: Column[] = [
|
||||||
{
|
{
|
||||||
title: () => $gettext('Status'),
|
title: () => $gettext('Status'),
|
||||||
dataIndex: ['registration', 'body', 'status'],
|
dataIndex: ['registration', 'body', 'status'],
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
if (args.text === 'valid')
|
if (args.text === 'valid')
|
||||||
return <Tag color="green">{$gettext('Valid')}</Tag>
|
return <Tag color="green">{$gettext('Valid')}</Tag>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { Column, JSXElements } from '@/components/StdDesign/types'
|
import type { Column, JSXElements } from '@/components/StdDesign/types'
|
||||||
import { datetime, mask } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import { datetime, mask } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import { input } from '@/components/StdDesign/StdDataEntry'
|
import { input } from '@/components/StdDesign/StdDataEntry'
|
||||||
|
@ -11,7 +11,7 @@ const columns: Column[] = [{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
pithy: true,
|
pithy: true,
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
const { text, record } = args
|
const { text, record } = args
|
||||||
if (!text)
|
if (!text)
|
||||||
return h('div', record.domain)
|
return h('div', record.domain)
|
||||||
|
@ -24,7 +24,7 @@ const columns: Column[] = [{
|
||||||
}, {
|
}, {
|
||||||
title: () => $gettext('Type'),
|
title: () => $gettext('Type'),
|
||||||
dataIndex: 'auto_cert',
|
dataIndex: 'auto_cert',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
const template: JSXElements = []
|
const template: JSXElements = []
|
||||||
const { text } = args
|
const { text } = args
|
||||||
const sync = $gettext('Sync Certificate')
|
const sync = $gettext('Sync Certificate')
|
||||||
|
@ -68,7 +68,7 @@ const columns: Column[] = [{
|
||||||
title: () => $gettext('Status'),
|
title: () => $gettext('Status'),
|
||||||
dataIndex: 'certificate_info',
|
dataIndex: 'certificate_info',
|
||||||
pithy: true,
|
pithy: true,
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
const template: JSXElements = []
|
const template: JSXElements = []
|
||||||
|
|
||||||
const text = args.text?.not_before
|
const text = args.text?.not_before
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { Column } from '@/components/StdDesign/types'
|
import type { Column } from '@/components/StdDesign/types'
|
||||||
import dns_credential from '@/api/dns_credential'
|
import dns_credential from '@/api/dns_credential'
|
||||||
import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
|
import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
|
||||||
|
@ -18,7 +18,7 @@ const columns: Column[] = [{
|
||||||
}, {
|
}, {
|
||||||
title: () => $gettext('Provider'),
|
title: () => $gettext('Provider'),
|
||||||
dataIndex: ['config', 'name'],
|
dataIndex: ['config', 'name'],
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
return args.record.provider
|
return args.record.provider
|
||||||
},
|
},
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { JSXElements } from '@/components/StdDesign/types'
|
import type { JSXElements } from '@/components/StdDesign/types'
|
||||||
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import { input } from '@/components/StdDesign/StdDataEntry'
|
import { input } from '@/components/StdDesign/StdDataEntry'
|
||||||
|
@ -15,7 +15,7 @@ const configColumns = [{
|
||||||
}, {
|
}, {
|
||||||
title: () => $gettext('Type'),
|
title: () => $gettext('Type'),
|
||||||
dataIndex: 'is_dir',
|
dataIndex: 'is_dir',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
const template: JSXElements = []
|
const template: JSXElements = []
|
||||||
const { text } = args
|
const { text } = args
|
||||||
if (text === true || text > 0)
|
if (text === true || text > 0)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { Column, JSXElements } from '@/components/StdDesign/types'
|
import type { Column, JSXElements } from '@/components/StdDesign/types'
|
||||||
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import { input, switcher } from '@/components/StdDesign/StdDataEntry'
|
import { input, switcher } from '@/components/StdDesign/StdDataEntry'
|
||||||
|
@ -74,7 +74,7 @@ const columns: Column[] = [{
|
||||||
{
|
{
|
||||||
title: () => $gettext('Status'),
|
title: () => $gettext('Status'),
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
const template: JSXElements = []
|
const template: JSXElements = []
|
||||||
const { text } = args
|
const { text } = args
|
||||||
if (args.record.enabled) {
|
if (args.record.enabled) {
|
||||||
|
@ -99,7 +99,7 @@ const columns: Column[] = [{
|
||||||
}, {
|
}, {
|
||||||
title: () => $gettext('Enabled'),
|
title: () => $gettext('Enabled'),
|
||||||
dataIndex: 'enabled',
|
dataIndex: 'enabled',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
const template: JSXElements = []
|
const template: JSXElements = []
|
||||||
const { text } = args
|
const { text } = args
|
||||||
if (text === true || text > 0)
|
if (text === true || text > 0)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { Column } from '@/components/StdDesign/types'
|
import type { Column } from '@/components/StdDesign/types'
|
||||||
import { detailRender } from '@/components/Notification/detailRender'
|
import { detailRender } from '@/components/Notification/detailRender'
|
||||||
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
|
@ -8,7 +8,7 @@ import { Tag } from 'ant-design-vue'
|
||||||
const columns: Column[] = [{
|
const columns: Column[] = [{
|
||||||
title: () => $gettext('Type'),
|
title: () => $gettext('Type'),
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
if (args.text === NotificationTypeT.Error) {
|
if (args.text === NotificationTypeT.Error) {
|
||||||
return (
|
return (
|
||||||
<Tag color="error">
|
<Tag color="error">
|
||||||
|
@ -43,7 +43,7 @@ const columns: Column[] = [{
|
||||||
}, {
|
}, {
|
||||||
title: () => $gettext('Title'),
|
title: () => $gettext('Title'),
|
||||||
dataIndex: 'title',
|
dataIndex: 'title',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
return h('span', $gettext(args.text))
|
return h('span', $gettext(args.text))
|
||||||
},
|
},
|
||||||
pithy: true,
|
pithy: true,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import type { BannedIP, Settings } from '@/api/settings'
|
import type { BannedIP, Settings } from '@/api/settings'
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import setting from '@/api/settings'
|
import setting from '@/api/settings'
|
||||||
import TOTP from '@/views/preference/components/TOTP.vue'
|
import TOTP from '@/views/preference/components/TOTP.vue'
|
||||||
|
@ -19,7 +19,7 @@ const bannedIPColumns = [{
|
||||||
}, {
|
}, {
|
||||||
title: $gettext('Banned Until'),
|
title: $gettext('Banned Until'),
|
||||||
dataIndex: 'expired_at',
|
dataIndex: 'expired_at',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
return dayjs.unix(args.text).format('YYYY-MM-DD HH:mm:ss')
|
return dayjs.unix(args.text).format('YYYY-MM-DD HH:mm:ss')
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Column, JSXElements } from '@/components/StdDesign/types'
|
||||||
import site_category from '@/api/site_category'
|
import site_category from '@/api/site_category'
|
||||||
import {
|
import {
|
||||||
actualValueRender,
|
actualValueRender,
|
||||||
type CustomRenderProps,
|
type CustomRender,
|
||||||
datetime,
|
datetime,
|
||||||
} from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
} from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import { input, select, selector } from '@/components/StdDesign/StdDataEntry'
|
import { input, select, selector } from '@/components/StdDesign/StdDataEntry'
|
||||||
|
@ -37,7 +37,7 @@ const columns: Column[] = [{
|
||||||
}, {
|
}, {
|
||||||
title: () => $gettext('Status'),
|
title: () => $gettext('Status'),
|
||||||
dataIndex: 'enabled',
|
dataIndex: 'enabled',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
const template: JSXElements = []
|
const template: JSXElements = []
|
||||||
const { text } = args
|
const { text } = args
|
||||||
if (text === true || text > 0) {
|
if (text === true || text > 0) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { Column, JSXElements } from '@/components/StdDesign/types'
|
import type { Column, JSXElements } from '@/components/StdDesign/types'
|
||||||
import stream from '@/api/stream'
|
import stream from '@/api/stream'
|
||||||
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
|
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
|
||||||
|
@ -21,7 +21,7 @@ const columns: Column[] = [{
|
||||||
}, {
|
}, {
|
||||||
title: () => $gettext('Status'),
|
title: () => $gettext('Status'),
|
||||||
dataIndex: 'enabled',
|
dataIndex: 'enabled',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
const template: JSXElements = []
|
const template: JSXElements = []
|
||||||
const { text } = args
|
const { text } = args
|
||||||
if (text === true || text > 0) {
|
if (text === true || text > 0) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import type { Column, JSXElements } from '@/components/StdDesign/types'
|
import type { Column, JSXElements } from '@/components/StdDesign/types'
|
||||||
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
import { input, password } from '@/components/StdDesign/StdDataEntry'
|
import { input, password } from '@/components/StdDesign/StdDataEntry'
|
||||||
|
@ -31,7 +31,7 @@ const columns: Column[] = [{
|
||||||
}, {
|
}, {
|
||||||
title: () => $gettext('2FA'),
|
title: () => $gettext('2FA'),
|
||||||
dataIndex: 'enabled_2fa',
|
dataIndex: 'enabled_2fa',
|
||||||
customRender: (args: CustomRenderProps) => {
|
customRender: (args: CustomRender) => {
|
||||||
const template: JSXElements = []
|
const template: JSXElements = []
|
||||||
const { text } = args
|
const { text } = args
|
||||||
if (text === true || text > 0)
|
if (text === true || text > 0)
|
||||||
|
|
|
@ -33,6 +33,8 @@ func (c ConfigsSort) Less(i, j int) bool {
|
||||||
flag = boolToInt(c.ConfigList[i].IsDir) > boolToInt(c.ConfigList[j].IsDir)
|
flag = boolToInt(c.ConfigList[i].IsDir) > boolToInt(c.ConfigList[j].IsDir)
|
||||||
case "enabled":
|
case "enabled":
|
||||||
flag = boolToInt(c.ConfigList[i].Enabled) > boolToInt(c.ConfigList[j].Enabled)
|
flag = boolToInt(c.ConfigList[i].Enabled) > boolToInt(c.ConfigList[j].Enabled)
|
||||||
|
case "site_category_id":
|
||||||
|
flag = c.ConfigList[i].SiteCategoryID > c.ConfigList[j].SiteCategoryID
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Order == "asc" {
|
if c.Order == "asc" {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue