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) {
name := c.Query("name")
enabled := c.Query("enabled")
orderBy := c.Query("order_by")
sort := c.DefaultQuery("sort", "desc")
orderBy := c.Query("sort_by")
sort := c.DefaultQuery("order", "desc")
querySiteCategoryId := cast.ToUint64(c.Query("site_category_id"))
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']
APopover: typeof import('ant-design-vue/es')['Popover']
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']
ARow: typeof import('ant-design-vue/es')['Row']
ASelect: typeof import('ant-design-vue/es')['Select']
@ -86,6 +88,7 @@ declare module 'vue' {
SensitiveStringSensitiveString: typeof import('./src/components/SensitiveString/SensitiveString.vue')['default']
SetLanguageSetLanguage: typeof import('./src/components/SetLanguage/SetLanguage.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']
StdDesignStdDataDisplayStdCurdDetail: typeof import('./src/components/StdDesign/StdDataDisplay/StdCurdDetail.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']
StdDesignStdDataEntryStdDataEntry: typeof import('./src/components/StdDesign/StdDataEntry/StdDataEntry.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']
SwitchAppearanceIconsVPIconSun: typeof import('./src/components/SwitchAppearance/icons/VPIconSun.vue')['default']
SwitchAppearanceSwitchAppearance: typeof import('./src/components/SwitchAppearance/SwitchAppearance.vue')['default']

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
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 { Ref } from 'vue'
import notificationApi from '@/api/notification'
@ -52,7 +52,7 @@ function newSSE() {
notification[typeTrans[data.type]]({
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>
<AListItemMeta
:title="$gettext(item.title)"
:description="detailRender({ text: item.details, record: item } as CustomRenderProps)"
:description="detailRender({ text: item.details, record: item } as CustomRender)"
>
<template #avatar>
<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 {
deleteSiteError,
@ -17,7 +17,7 @@ import {
syncRenameConfigSuccess,
} from '@/components/Notification/config'
export function detailRender(args: CustomRenderProps) {
export function detailRender(args: CustomRender) {
try {
switch (args.record.title) {
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">
import type { StdCurdProps, StdTableProps } from '@/components/StdDesign/StdDataDisplay/types'
import type { Column } from '@/components/StdDesign/types'
import type { ComputedRef } from 'vue'
import type { StdTableProps } from './StdTable.vue'
import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
import StdCurdDetail from '@/components/StdDesign/StdDataDisplay/StdCurdDetail.vue'
import StdDataEntry from '@/components/StdDesign/StdDataEntry'
import { message } from 'ant-design-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 selectedRowKeys = defineModel<(number | string)[]>('selectedRowKeys', {
@ -76,16 +61,9 @@ function add(preset: any = undefined) {
modifyMode.value = true
}
const table = useTemplateRef('table')
const getParams = reactive({
trash: false,
})
// eslint-disable-next-line ts/no-explicit-any
function setParams(k: string, v: any) {
getParams[k] = v
}
const table = ref()
const inTrash = ref(false)
const getParams = reactive(props.getParams ?? {})
function get_list() {
table.value?.get_list()
@ -95,8 +73,7 @@ defineExpose({
add,
get_list,
data,
getParams,
setParams,
inTrash,
})
function clearError() {
@ -105,17 +82,14 @@ function clearError() {
})
}
const stdEntryRef = useTemplateRef('stdEntryRef')
const stdEntryRef = ref()
async function ok() {
if (!stdEntryRef.value)
return
const { formRef } = stdEntryRef.value
clearError()
try {
await formRef?.validateFields()
await formRef.validateFields()
props?.beforeSave?.(data)
props
.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(() => {
if (data.id)
return modifyMode.value ? $gettext('Modify') : $gettext('View Details')
return $gettext('Add')
// eslint-disable-next-line sonarjs/no-nested-conditional
return data.id ? modifyMode.value ? $gettext('Modify') : $gettext('View Details') : $gettext('Add')
})
const localOverwriteParams = reactive(props.overwriteParams ?? {})
const stdBatchEditRef = useTemplateRef('stdBatchEditRef')
const stdBatchEditRef = ref()
async function handleClickBatchEdit(batchColumns: Column[]) {
stdBatchEditRef.value?.showModal(batchColumns, selectedRowKeys.value, selectedRows.value)
stdBatchEditRef.value.showModal(batchColumns, selectedRowKeys.value, selectedRows.value)
}
function handleBatchUpdated() {
table.value?.get_list()
table.value?.resetSelection()
table.value.get_list()
table.value.resetSelection()
}
</script>
@ -208,24 +181,34 @@ function handleBatchUpdated() {
<template #extra>
<ASpace>
<slot name="beforeAdd" />
<a
v-if="!disableAdd && !getParams.trash"
<AButton
v-if="!disableAdd && !inTrash"
type="link"
size="small"
@click="add"
>{{ $gettext('Add') }}</a>
>
{{ $gettext('Add') }}
</AButton>
<slot name="extra" />
<template v-if="!disableDelete">
<a
v-if="!getParams.trash"
@click="getParams.trash = true"
<AButton
v-if="!inTrash"
type="link"
size="small"
:loading="table?.loading"
@click="inTrash = true"
>
{{ $gettext('Trash') }}
</a>
<a
</AButton>
<AButton
v-else
@click="getParams.trash = false"
type="link"
size="small"
:loading="table?.loading"
@click="inTrash = false"
>
{{ $gettext('Back to list') }}
</a>
</AButton>
</template>
</ASpace>
</template>
@ -240,6 +223,7 @@ function handleBatchUpdated() {
}"
v-model:selected-row-keys="selectedRowKeys"
v-model:selected-rows="selectedRows"
:in-trash="inTrash"
@click-edit="edit"
@click-view="view"
@selected="onSelect"

View file

@ -1,11 +1,14 @@
<script setup lang="ts">
import type { Pagination } from '@/api/curd'
const props = defineProps<{
const props = withDefaults(defineProps<{
pagination: Pagination
size?: 'default' | 'small'
loading: boolean
}>()
loading?: boolean
showSizeChanger?: boolean
}>(), {
showSizeChanger: true,
})
const emit = defineEmits(['change', 'changePageSize', 'update:pagination'])
@ -33,7 +36,8 @@ const pageSize = computed({
v-model:page-size="pageSize"
:disabled="loading"
: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"
:total="pagination.total"
@change="change"

View file

@ -1,6 +1,6 @@
<script setup lang="ts" generic="T=any">
import type { Pagination } from '@/api/curd'
import type Curd from '@/api/curd'
import type { GetListResponse, Pagination } from '@/api/curd'
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/types'
import type { Column } from '@/components/StdDesign/types'
import type { TableProps } from 'ant-design-vue'
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 { getPithyColumns } from '@/components/StdDesign/StdDataDisplay/methods/columns'
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 { watchPausable } from '@vueuse/core'
import { message } from 'ant-design-vue'
import _ from 'lodash'
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>>(), {
rowKey: 'id',
})
@ -71,6 +41,9 @@ const dataSource: Ref<T[]> = ref([])
const expandKeysList: Ref<Key[]> = ref([])
watch(dataSource, () => {
if (!props.expandAll)
return
const res: Key[] = []
function buildKeysList(record) {
@ -105,8 +78,26 @@ const pagination: Pagination = reactive({
total_pages: 1,
})
const params = reactive({
const filterParams = ref({})
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(() => {
@ -149,32 +140,26 @@ const batchColumns = computed(() => {
return props.columns?.filter(column => column.batch) || []
})
const radioColumns = computed(() => {
return props.columns?.filter(column => column.radio) || []
})
const get_list = _.debounce(_get_list, 100, {
leading: true,
trailing: false,
leading: false,
trailing: true,
})
const filterParams = reactive({})
watch(filterParams, () => {
Object.assign(params, {
...filterParams,
page: 1,
trash: route.query.trash === 'true',
})
})
onMounted(() => {
onMounted(async () => {
if (!props.disableQueryParams) {
Object.assign(params, {
filterParams.value = {
...route.query,
trash: route.query.trash === 'true',
})
Object.assign(filterParams, {
...route.query,
})
...props.getParams,
}
paginationParams.value.page = Number(route.query.page) || 1
paginationParams.value.page_size = Number(route.query.page_size) || 20
}
await nextTick()
get_list()
@ -188,10 +173,11 @@ defineExpose({
get_list,
pagination,
resetSelection,
loading,
})
function destroy(id: number | string) {
props.api!.destroy(id, { permanent: params.trash }).then(() => {
props.api!.destroy(id, { permanent: props.inTrash }).then(() => {
get_list()
message.success($gettext('Deleted successfully'))
}).catch(e => {
@ -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 = []
loading.value = true
if (page_num) {
params.page = page_num
params.page_size = page_size
}
else {
params.page = 1
params.page_size = page_size
}
props.api?.get_list({ ...params, ...props.overwriteParams }).then(async r => {
// eslint-disable-next-line ts/no-explicit-any
await props.api?.get_list({ ...params.value, ...paginationParams.value }).then(async (r: GetListResponse<any>) => {
dataSource.value = r.data
rowsKeyIndexMap.value = {}
if (props.sortable)
@ -243,11 +223,11 @@ async function _get_list(page_num: number | null = null, page_size = 20) {
if (r.pagination)
Object.assign(pagination, r.pagination)
}).catch(e => {
message.error($gettext(e?.message ?? 'Server error'))
})
loading.value = false
}).catch(e => {
message.error(e?.message ?? $gettext('Server error'))
})
}
// eslint-disable-next-line ts/no-explicit-any
@ -255,17 +235,16 @@ function onTableChange(_pagination: TablePaginationConfig, filters: Record<strin
if (sorter) {
sorter = sorter as SorterResult
selectedRowKeys.value = []
params.sort_by = sorter.field
params.order = sorter.order === 'ascend' ? 'asc' : 'desc'
sortParams.value.sort_by = sorter.field
switch (sorter.order) {
case 'ascend':
params.sort = 'asc'
sortParams.value.order = 'asc'
break
case 'descend':
params.sort = 'desc'
sortParams.value.order = 'desc'
break
default:
params.sort = null
sortParams.value.order = undefined
break
}
}
@ -286,7 +265,7 @@ function expandedTable(keys: Key[]) {
// eslint-disable-next-line ts/no-explicit-any
async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
// 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) {
_selectedRows.forEach(v => {
if (v) {
@ -349,72 +328,40 @@ function resetSelection() {
const router = useRouter()
async function resetSearch() {
Object.keys(params).forEach(v => {
delete params[v]
})
Object.assign(params, {
...props.getParams,
})
router.push({ query: {} }).catch(() => {
})
Object.keys(filterParams).forEach(v => {
delete filterParams[v]
})
filterParams.value = {}
updateFilter.value++
}
const { stop: stopWatchParams, resume: resumeWatchParams } = watchPausable(params, v => {
watch(params, async v => {
if (!init.value)
return
paginationParams.value = {
page: 1,
page_size: paginationParams.value.page_size,
}
await nextTick()
if (!props.disableQueryParams)
router.push({ query: { ...v as RouteParams } })
await router.push({ query: { ...v as unknown as RouteParams, ...paginationParams.value } })
else
get_list()
})
watch(() => route.query, async () => {
params.trash = route.query.trash === 'true'
}, { deep: true })
watch(() => route.query, () => {
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(() => {
if (batchColumns.value.length > 0 || props.selectionType || props.exportExcel) {
if (batchColumns.value.length > 0 || props.selectionType || props.exportExcel || props.bulkActions) {
return {
selectedRowKeys: unref(selectedRowKeys),
onSelect,
onSelectAll,
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 {
@ -435,13 +382,25 @@ function initSortable() {
}
async function changePage(page: number, page_size: number) {
stopWatchParams()
Object.assign(params, {
if (page) {
paginationParams.value = {
page,
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(() => {
@ -454,6 +413,26 @@ const paginationSize = computed(() => {
<template>
<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
v-if="!disableSearch && searchColumns.length"
:key="updateFilter"
@ -473,10 +452,26 @@ const paginationSize = computed(() => {
>
{{ $gettext('Batch Modify') }}
</AButton>
<Export
v-if="props.exportExcel"
:columns="props.columns"
:api="props.api"
:total="pagination.total"
:query="params"
:ids="selectedRowKeys"
/>
<slot name="append-search" />
</ASpace>
</template>
</StdDataEntry>
<StdBulkActions
v-if="bulkActions"
v-model:selected-row-keys="selectedRowKeys"
:api
:in-trash="inTrash"
:actions="bulkActions"
@on-success="() => { resetSelection(); get_list() }"
/>
<ATable
:id="`std-table-${randomId}`"
:columns="pithyColumns"
@ -497,7 +492,7 @@ const paginationSize = computed(() => {
{{ text }}
</template>
<template v-if="column.dataIndex === 'action'">
<template v-if="!props.disableView && !params.trash">
<template v-if="!props.disableView && !inTrash">
<AButton
type="link"
size="small"
@ -511,7 +506,7 @@ const paginationSize = computed(() => {
/>
</template>
<template v-if="!props.disableModify && !params.trash">
<template v-if="!props.disableModify && !inTrash">
<AButton
type="link"
size="small"
@ -529,9 +524,9 @@ const paginationSize = computed(() => {
<template v-if="!props.disableDelete">
<APopconfirm
v-if="!params.trash"
v-if="!inTrash"
:cancel-text="$gettext('No')"
:ok-text="$gettext('OK')"
:ok-text="$gettext('Ok')"
:title="$gettext('Are you sure you want to delete this item?')"
@confirm="destroy(record[rowKey])"
>
@ -545,7 +540,7 @@ const paginationSize = computed(() => {
<APopconfirm
v-else
:cancel-text="$gettext('No')"
:ok-text="$gettext('OK')"
:ok-text="$gettext('Ok')"
:title="$gettext('Are you sure you want to recover this item?')"
@confirm="recover(record[rowKey])"
>
@ -558,9 +553,9 @@ const paginationSize = computed(() => {
</APopconfirm>
<ADivider type="vertical" />
<APopconfirm
v-if="params.trash"
v-if="inTrash"
:cancel-text="$gettext('No')"
:ok-text="$gettext('OK')"
:ok-text="$gettext('Ok')"
:title="$gettext('Are you sure you want to delete this item permanently?')"
@confirm="destroy(record[rowKey])"
>

View file

@ -1,14 +1,15 @@
import type { VNode } from 'vue'
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
import dayjs from 'dayjs'
import { get } from 'lodash'
export interface CustomRenderProps {
// eslint-disable-next-line ts/no-explicit-any
text: any
// eslint-disable-next-line ts/no-explicit-any
record: any
// eslint-disable-next-line ts/no-explicit-any
export interface CustomRender<T = any, R = any> {
text: T
record: R
// eslint-disable-next-line ts/no-explicit-any
index: any
// eslint-disable-next-line ts/no-explicit-any
@ -17,11 +18,14 @@ export interface CustomRenderProps {
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')
}
export function date(args: CustomRenderProps) {
export function date(args: CustomRender) {
return args.text ? dayjs(args.text).format('YYYY-MM-DD') : '-'
}
@ -30,11 +34,10 @@ date.isDate = true
datetime.isDatetime = true
// eslint-disable-next-line ts/no-explicit-any
export function mask(maskObj: any): (args: CustomRenderProps) => JSX.Element {
return (args: CustomRenderProps) => {
export function mask(maskObj: any): (args: CustomRender) => JSX.Element {
return (args: CustomRender) => {
// eslint-disable-next-line ts/no-explicit-any
let v: any
if (typeof maskObj?.[args.text] === 'function')
v = maskObj[args.text]()
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(', ')
}
export function actualValueRender(actualDataIndex: string | string[]) {
return (args: CustomRenderProps) => {
return (args: CustomRender) => {
return get(args.record, actualDataIndex) || '/'
}
}
export function longTextWithEllipsis(len: number): (args: CustomRenderProps) => JSX.Element {
return (args: CustomRenderProps) => {
export function longTextWithEllipsis(len: number): (args: CustomRender) => JSX.Element {
return (args: CustomRender) => {
if (args.isExport || args.isDetail)
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
export function maskRenderWithColor(maskObj: any) {
return (args: CustomRenderProps) => {
export function maskRenderWithColor(maskObj: any, customColors?: Record<string | number, string> | string) {
return (args: CustomRender) => {
let label: string
if (typeof maskObj[args.text] === 'function')
label = maskObj[args.text]()
@ -80,14 +79,77 @@ export function maskRenderWithColor(maskObj: any) {
if (args.isExport)
return label
const colorMap = {
let colorMap: Record<string | number, string> = {
0: '',
1: 'blue',
2: 'green',
3: 'red',
3: 'purple',
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'
export function CustomRender(props: CustomRenderProps) {
// eslint-disable-next-line ts/no-redeclare,sonarjs/no-redeclare
export function CustomRender(props: CustomRender) {
return props.column.customRender
? props.column.customRender(props)
: _.get(props.record, props.column.dataIndex!)

View file

@ -2,8 +2,4 @@ import StdBatchEdit from './StdBatchEdit.vue'
import StdCurd from './StdCurd.vue'
import StdTable from './StdTable.vue'
export {
StdBatchEdit,
StdCurd,
StdTable,
}
export { StdBatchEdit, StdCurd, StdTable }

View file

@ -1,6 +1,5 @@
import type { GetListResponse } from '@/api/curd'
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
import type { Column } from '@/components/StdDesign/types'
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/types'
import type { Column, StdTableResponse } from '@/components/StdDesign/types'
import type { ComputedRef } from 'vue'
import { downloadCsv } from '@/lib/helper'
import { message } from 'ant-design-vue'
@ -32,10 +31,9 @@ async function exportCsv(props: StdTableProps, pithyColumns: ComputedRef<Column[
let hasMore = true
let page = 1
while (hasMore) {
// 准备 DataSource
// prepare dataSource
await props
// eslint-disable-next-line ts/no-explicit-any
.api!.get_list({ page }).then((r: GetListResponse<any>) => {
.api!.get_list({ page }).then((r: StdTableResponse) => {
if (r.data.length === 0) {
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 { Ref } from 'vue'
import { message } from 'ant-design-vue'
@ -26,8 +26,7 @@ function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Re
// eslint-disable-next-line ts/no-explicit-any
const table: any = document.querySelector(`#std-table-${randomId.value} tbody`)
try {
// eslint-disable-next-line no-new,new-cap
// eslint-disable-next-line no-new,new-cap,sonarjs/constructor-for-side-effects
new sortable(table, {
handle: '.ant-table-drag-icon',
animation: 150,
@ -59,7 +58,7 @@ function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Re
newIndex,
oldIndex,
}) {
if (newIndex === oldIndex || newIndex === undefined)
if (newIndex === oldIndex)
return
const indexDelta: number = Number(oldIndex) - Number(newIndex)
@ -70,8 +69,7 @@ function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Re
const newRowParent = newRow.parent
const level: number = newRow.level
const currentRowIndex: number[] = [...rowsKeyIndexMap.value?.
[Number(getRowKey(table.children[Number(newIndex) + direction]))]]
const currentRowIndex: number[] = [...rowsKeyIndexMap.value![Number(getRowKey(table?.children?.[Number(newIndex) + direction]))]]
// eslint-disable-next-line ts/no-explicit-any
const currentRow: any = getTargetData(dataSource.value, currentRowIndex)
@ -95,17 +93,17 @@ function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Re
rowsKeyIndexMap.value[row.id][level] += direction
row.parent = null
if (row.children) {
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) {
// 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
@ -127,10 +125,6 @@ function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Re
})
},
})
}
catch (e) {
console.error(e)
}
}
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 {
'append-search': (action) => any
'actions': (actions: Record<string, any>) => any
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>
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">
import type { SelectProps } from 'ant-design-vue'
import { ref } from 'vue'
const props = defineProps<{
mask?: Record<string | number, string | (() => string)> | (() => Promise<Record<string | number, string>>)
@ -62,16 +61,14 @@ onMounted(() => {
<template>
<ASelect
v-model:value="selectedValue"
allow-clear
:options="options"
:placeholder="props.placeholder"
:default-active-first-option="false"
:mode="props.multiple ? 'multiple' : undefined"
style="min-width: 180px"
allow-clear
class="min-w-180px w-auto!"
:get-popup-container="triggerNode => triggerNode.parentNode"
/>
</template>
<style lang="less" scoped>
</style>
<style lang="less" scoped></style>

View file

@ -27,6 +27,7 @@ const props = defineProps<{
// eslint-disable-next-line ts/no-explicit-any
getCheckboxProps?: (record: any) => any
hideInputContainer?: boolean
expandAll?: boolean
}>()
const selectedKey = defineModel<number | number[] | undefined | null | string | string[]>('selectedKey')
@ -195,6 +196,7 @@ defineExpose({ show })
:columns
:disable-search
:row-key="itemKey"
:expand-all
:get-params
:selection-type
:get-checkbox-props

View file

@ -7,7 +7,6 @@ import {
InputNumber,
RangePicker,
Switch,
Textarea,
} from 'ant-design-vue'
import dayjs from 'dayjs'
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
export function textarea(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
return h(Textarea, {
'placeholder': placeholderHelper(edit),
'value': dataSource?.[dataIndex] ?? edit?.config?.defaultValue,
'onUpdate:value': value => {
dataSource[dataIndex] = value
},
})
if (!dataSource[dataIndex])
dataSource[dataIndex] = edit.config?.defaultValue
return (
<Input
v-model:value={dataSource[dataIndex]}
placeholder={placeholderHelper(edit)}
/>
)
}
// 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]}
selectedKey={dataSource[dataIndex] || edit?.config?.defaultValue}
recordValueIndex={edit.selector?.recordValueIndex}
selectionType={edit.selector?.selectionType}
selectionType={edit.selector?.selectionType ?? 'radio'}
api={edit.selector?.api}
columns={edit.selector?.columns}
disableSearch={edit.selector?.disableSearch}
getParams={edit.selector?.getParams}
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
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 (
<DatePicker
format={DATE_FORMAT}
allowClear
format={edit?.datePicker?.format ?? DATE_FORMAT}
picker={edit?.datePicker?.picker}
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 (
<RangePicker
format={DATE_FORMAT}
allowClear
format={edit?.datePicker?.format ?? DATE_FORMAT}
picker={edit?.datePicker?.picker}
value={dates}
onChange={(_, dateStrings: [string, string]) => dataSource[dataIndex] = dateStrings}
/>

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 { 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'
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 {
type?: (edit: StdDesignEdit, dataSource: any, dataIndex: any) => JSX.Element // component type
show?: (dataSource: any) => boolean // show component or not
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
actualDataIndex?: string
datePicker?: {
picker?: 'date' | 'week' | 'month' | 'year' | 'quarter'
format?: string
}
cascader?: {
api: () => Promise<any>
fieldNames: Record<string, string>
}
select?: {
multiple?: boolean
}
selector?: {
getParams?: object
recordValueIndex: any // relative to api return
selectionType: any
getParams?: Record<string | number, any>
selectionType?: 'radio' | 'checkbox'
api: Curd
valueApi?: Curd
columns: any
@ -36,6 +55,9 @@ export interface StdDesignEdit {
bind?: any
itemKey?: any // default is id
dataSourceValueIndex?: any // relative to dataSource
recordValueIndex?: any // relative to dataSource
getCheckboxProps?: (record: any) => any
expandAll?: boolean
} // StdSelector Config
upload?: {
@ -73,14 +95,13 @@ export interface StdDesignEdit {
flex?: Flex
}
type FlexType = string | number | boolean
export interface Flex {
sm?: FlexType
md?: FlexType
lg?: FlexType
xl?: FlexType
xxl?: FlexType
// eslint-disable-next-line sonarjs/use-type-alias
sm?: string | number | boolean
md?: string | number | boolean
lg?: string | number | boolean
xl?: string | number | boolean
xxl?: string | number | boolean
}
export interface Column extends TableColumnType {
@ -98,9 +119,11 @@ export interface Column extends TableColumnType {
hiddenInExport?: boolean
import?: boolean
batch?: boolean
radio?: boolean
mask?: StdDesignMask
customRender?: function
selector?: {
getParams?: object
getParams?: Record<string | number, any>
recordValueIndex: any // relative to api return
selectionType: any
api: Curd
@ -111,5 +134,21 @@ export interface Column extends TableColumnType {
bind?: any
itemKey?: any // default is id
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">
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 acme_user from '@/api/acme_user'
import { StdCurd } from '@/components/StdDesign/StdDataDisplay'
@ -64,7 +64,7 @@ const columns: Column[] = [
{
title: () => $gettext('Status'),
dataIndex: ['registration', 'body', 'status'],
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
if (args.text === 'valid')
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 { datetime, mask } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
import { input } from '@/components/StdDesign/StdDataEntry'
@ -11,7 +11,7 @@ const columns: Column[] = [{
dataIndex: 'name',
sorter: true,
pithy: true,
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
const { text, record } = args
if (!text)
return h('div', record.domain)
@ -24,7 +24,7 @@ const columns: Column[] = [{
}, {
title: () => $gettext('Type'),
dataIndex: 'auto_cert',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
const template: JSXElements = []
const { text } = args
const sync = $gettext('Sync Certificate')
@ -68,7 +68,7 @@ const columns: Column[] = [{
title: () => $gettext('Status'),
dataIndex: 'certificate_info',
pithy: true,
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
const template: JSXElements = []
const text = args.text?.not_before

View file

@ -1,5 +1,5 @@
<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 dns_credential from '@/api/dns_credential'
import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
@ -18,7 +18,7 @@ const columns: Column[] = [{
}, {
title: () => $gettext('Provider'),
dataIndex: ['config', 'name'],
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
return args.record.provider
},
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 { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
import { input } from '@/components/StdDesign/StdDataEntry'
@ -15,7 +15,7 @@ const configColumns = [{
}, {
title: () => $gettext('Type'),
dataIndex: 'is_dir',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
const template: JSXElements = []
const { text } = args
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 { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
import { input, switcher } from '@/components/StdDesign/StdDataEntry'
@ -74,7 +74,7 @@ const columns: Column[] = [{
{
title: () => $gettext('Status'),
dataIndex: 'status',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
const template: JSXElements = []
const { text } = args
if (args.record.enabled) {
@ -99,7 +99,7 @@ const columns: Column[] = [{
}, {
title: () => $gettext('Enabled'),
dataIndex: 'enabled',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
const template: JSXElements = []
const { text } = args
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 { detailRender } from '@/components/Notification/detailRender'
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
@ -8,7 +8,7 @@ import { Tag } from 'ant-design-vue'
const columns: Column[] = [{
title: () => $gettext('Type'),
dataIndex: 'type',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
if (args.text === NotificationTypeT.Error) {
return (
<Tag color="error">
@ -43,7 +43,7 @@ const columns: Column[] = [{
}, {
title: () => $gettext('Title'),
dataIndex: 'title',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
return h('span', $gettext(args.text))
},
pithy: true,

View file

@ -1,6 +1,6 @@
<script setup lang="tsx">
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 setting from '@/api/settings'
import TOTP from '@/views/preference/components/TOTP.vue'
@ -19,7 +19,7 @@ const bannedIPColumns = [{
}, {
title: $gettext('Banned Until'),
dataIndex: 'expired_at',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
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 {
actualValueRender,
type CustomRenderProps,
type CustomRender,
datetime,
} from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
import { input, select, selector } from '@/components/StdDesign/StdDataEntry'
@ -37,7 +37,7 @@ const columns: Column[] = [{
}, {
title: () => $gettext('Status'),
dataIndex: 'enabled',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
const template: JSXElements = []
const { text } = args
if (text === true || text > 0) {

View file

@ -1,5 +1,5 @@
<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 stream from '@/api/stream'
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
@ -21,7 +21,7 @@ const columns: Column[] = [{
}, {
title: () => $gettext('Status'),
dataIndex: 'enabled',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
const template: JSXElements = []
const { text } = args
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 { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
import { input, password } from '@/components/StdDesign/StdDataEntry'
@ -31,7 +31,7 @@ const columns: Column[] = [{
}, {
title: () => $gettext('2FA'),
dataIndex: 'enabled_2fa',
customRender: (args: CustomRenderProps) => {
customRender: (args: CustomRender) => {
const template: JSXElements = []
const { text } = args
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)
case "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" {