fix(table): sorter invalid

This commit is contained in:
Jacky 2024-11-15 17:36:52 +08:00
parent ff7938b90c
commit 902aa28746
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
32 changed files with 805 additions and 399 deletions

View file

@ -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
View file

@ -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']

View file

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

View file

@ -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':

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

View file

@ -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"

View file

@ -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"

View file

@ -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])"
> >

View file

@ -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'))
})
},
}),
])
}

View file

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

View file

@ -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,
}

View file

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

View file

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

View file

@ -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
} }

View file

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

View file

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

View file

@ -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}
/> />

View file

@ -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;
} }
} }

View 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[]
}
}

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

View file

@ -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
}

View file

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

View file

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

View file

@ -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,

View file

@ -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)

View file

@ -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)

View file

@ -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,

View file

@ -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')
}, },
}, { }, {

View file

@ -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) {

View file

@ -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) {

View file

@ -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)

View file

@ -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" {