mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
fix(table): sorter invalid
This commit is contained in:
parent
ff7938b90c
commit
902aa28746
32 changed files with 805 additions and 399 deletions
|
@ -18,8 +18,8 @@ import (
|
|||
func GetSiteList(c *gin.Context) {
|
||||
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
5
app/components.d.ts
vendored
|
@ -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']
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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':
|
||||
|
|
110
app/src/components/StdDesign/StdDataDisplay/StdBulkActions.vue
Normal file
110
app/src/components/StdDesign/StdDataDisplay/StdBulkActions.vue
Normal file
|
@ -0,0 +1,110 @@
|
|||
<script setup lang="ts" generic="T=any">
|
||||
import type Curd from '@/api/curd'
|
||||
import type { BulkActionOptions, BulkActions } from '@/components/StdDesign/types'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const props = defineProps<{
|
||||
api: Curd<T>
|
||||
actions: BulkActions
|
||||
selectedRowKeys: Array<number | string>
|
||||
inTrash?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['onSuccess'])
|
||||
|
||||
const computedActions = computed(() => {
|
||||
if (!props.inTrash) {
|
||||
const result = { ...props.actions }
|
||||
|
||||
if (result.delete) {
|
||||
result.delete = {
|
||||
text: () => $gettext('Delete'),
|
||||
action: ids => {
|
||||
return props.api.batch_destroy(ids)
|
||||
},
|
||||
}
|
||||
}
|
||||
if (result.recover)
|
||||
delete result.recover
|
||||
return result
|
||||
}
|
||||
else {
|
||||
const result = {} as { [key: string]: BulkActionOptions }
|
||||
if (props.actions.delete) {
|
||||
result.delete = {
|
||||
text: () => $gettext('Delete Permanently'),
|
||||
action: ids => {
|
||||
return props.api.batch_destroy(ids, { permanent: true })
|
||||
},
|
||||
}
|
||||
}
|
||||
if (props.actions.recover) {
|
||||
result.recover = {
|
||||
text: () => $gettext('Recover'),
|
||||
action: ids => {
|
||||
return props.api.batch_recover(ids)
|
||||
},
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}) as ComputedRef<Record<string, BulkActionOptions>>
|
||||
|
||||
const actionValue = ref('')
|
||||
|
||||
watch(() => props.inTrash, () => {
|
||||
actionValue.value = ''
|
||||
})
|
||||
|
||||
function onClickApply() {
|
||||
return new Promise(resolve => {
|
||||
if (actionValue.value === '')
|
||||
return resolve(false)
|
||||
|
||||
// call action
|
||||
return resolve(
|
||||
computedActions.value[actionValue.value]?.action(props.selectedRowKeys).then(async () => {
|
||||
message.success($gettext('Apply bulk action successfully'))
|
||||
emit('onSuccess')
|
||||
}).catch(e => {
|
||||
message.error($gettext(e?.message) ?? $gettext('Server error'))
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AFormItem>
|
||||
<ASpace>
|
||||
<ASelect
|
||||
v-model:value="actionValue"
|
||||
style="min-width: 150px"
|
||||
>
|
||||
<ASelectOption value="">
|
||||
{{ $gettext('Batch Actions') }}
|
||||
</ASelectOption>
|
||||
<ASelectOption
|
||||
v-for="(action, key) in computedActions"
|
||||
:key
|
||||
:value="key"
|
||||
>
|
||||
{{ action.text() }}
|
||||
</ASelectOption>
|
||||
</ASelect>
|
||||
<APopconfirm
|
||||
:cancel-text="$gettext('No')"
|
||||
:ok-text="$gettext('OK')"
|
||||
:title="$gettext('Are you sure you want to apply to all selected?')"
|
||||
@confirm="onClickApply"
|
||||
>
|
||||
<AButton
|
||||
danger
|
||||
:disabled="!actionValue || !selectedRowKeys?.length"
|
||||
>
|
||||
{{ $gettext('Apply') }}
|
||||
</AButton>
|
||||
</APopconfirm>
|
||||
</ASpace>
|
||||
</AFormItem>
|
||||
</template>
|
|
@ -1,28 +1,13 @@
|
|||
<script setup lang="ts" generic="T=any">
|
||||
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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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])"
|
||||
>
|
||||
|
|
|
@ -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'))
|
||||
})
|
||||
},
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
|
|
@ -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!)
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
25
app/src/components/StdDesign/StdDataEntry/types.d.ts
vendored
Normal file
25
app/src/components/StdDesign/StdDataEntry/types.d.ts
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
import type { DefaultOptionType } from 'ant-design-vue/es/vc-cascader'
|
||||
|
||||
export interface Author {
|
||||
id?: number
|
||||
name: string
|
||||
checked?: boolean
|
||||
sort?: number
|
||||
affiliated_unit?: string
|
||||
}
|
||||
|
||||
export interface AuthorSelector {
|
||||
input?: {
|
||||
title?: () => string
|
||||
placeholder?: () => string
|
||||
}
|
||||
checkbox?: {
|
||||
title?: () => string
|
||||
placeholder?: () => string
|
||||
}
|
||||
select?: {
|
||||
title?: () => string
|
||||
placeholder?: () => string
|
||||
options?: DefaultOptionType[]
|
||||
}
|
||||
}
|
141
app/src/components/StdDesign/StdDetail/StdDetail.vue
Normal file
141
app/src/components/StdDesign/StdDetail/StdDetail.vue
Normal file
|
@ -0,0 +1,141 @@
|
|||
<script setup lang="ts" generic="T extends ModelBase">
|
||||
import type { ModelBase } from '@/api/curd'
|
||||
import type Curd from '@/api/curd'
|
||||
import type { Column, StdDesignEdit } from '@/components/StdDesign/types'
|
||||
import type { ButtonProps, FormInstance } from 'ant-design-vue'
|
||||
import type { DataIndex } from 'ant-design-vue/es/vc-table/interface'
|
||||
import { labelRender } from '@/components/StdDesign/StdDataEntry'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
import _, { get } from 'lodash'
|
||||
|
||||
const props = defineProps<{
|
||||
title?: string
|
||||
dataSource?: T
|
||||
api: Curd<T>
|
||||
columns: Column[]
|
||||
actionButtonProps?: ButtonProps
|
||||
useOutsideData?: boolean
|
||||
}>()
|
||||
|
||||
const detail = ref(props.dataSource) as Ref<T | undefined>
|
||||
const editModel = ref({}) as Ref<T | undefined>
|
||||
const editStatus = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
watch(() => props.dataSource, val => detail.value = val)
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
props.api.save(editModel.value?.id, editModel.value).then(res => {
|
||||
detail.value = res
|
||||
editStatus.value = false
|
||||
}).catch(() => {
|
||||
message.error('Save failed')
|
||||
}).finally(() => loading.value = false)
|
||||
}
|
||||
catch {
|
||||
message.error('Validation failed')
|
||||
}
|
||||
}
|
||||
|
||||
function FormController(p: { editConfig: StdDesignEdit, dataIndex?: DataIndex }) {
|
||||
return p?.editConfig?.type?.(p.editConfig, editModel.value, p.dataIndex)
|
||||
}
|
||||
|
||||
function CustomRender(p: { column?: Column, text: unknown, record?: T }) {
|
||||
const { column, text, record } = p
|
||||
return column?.customRender?.({ text, record }) ?? text ?? '/'
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
onMounted(() => {
|
||||
if (props?.useOutsideData) {
|
||||
editModel.value = _.cloneDeep(props.dataSource)
|
||||
return
|
||||
}
|
||||
|
||||
props.api.get(route.params.id).then(res => {
|
||||
detail.value = res
|
||||
})
|
||||
})
|
||||
|
||||
function clickEdit() {
|
||||
editModel.value = _.cloneDeep(detail.value)
|
||||
editStatus.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AForm
|
||||
ref="formRef"
|
||||
:model="editModel"
|
||||
>
|
||||
<ADescriptions
|
||||
bordered
|
||||
:title="props.title ?? $gettext('Info')"
|
||||
:column="2"
|
||||
>
|
||||
<template #extra>
|
||||
<ASpace v-if="editStatus">
|
||||
<AButton
|
||||
type="primary"
|
||||
:disabled="loading"
|
||||
:loading="loading"
|
||||
v-bind="props.actionButtonProps"
|
||||
@click="save"
|
||||
>
|
||||
{{ $gettext('Save') }}
|
||||
</AButton>
|
||||
<AButton
|
||||
:disabled="loading"
|
||||
:loading="loading"
|
||||
v-bind="props.actionButtonProps"
|
||||
@click="editStatus = false"
|
||||
>
|
||||
{{ $gettext('Cancel') }}
|
||||
</AButton>
|
||||
</ASpace>
|
||||
<div v-else>
|
||||
<AButton
|
||||
type="primary"
|
||||
v-bind="props.actionButtonProps"
|
||||
@click="clickEdit"
|
||||
>
|
||||
{{ $gettext('Edit') }}
|
||||
</AButton>
|
||||
<slot name="extra" />
|
||||
</div>
|
||||
</template>
|
||||
<ADescriptionsItem
|
||||
v-for="c in props.columns.filter(c => c.dataIndex !== 'action')"
|
||||
:key="c.dataIndex?.toString()"
|
||||
:label="$gettext(labelRender(c.title) ?? '')"
|
||||
>
|
||||
<AFormItem
|
||||
v-if="editStatus && c.edit"
|
||||
class="mb-0"
|
||||
:name="c.dataIndex?.toString()"
|
||||
:required="c?.edit?.config?.required"
|
||||
>
|
||||
<FormController
|
||||
:edit-config="c.edit"
|
||||
:data-index="c.dataIndex"
|
||||
/>
|
||||
</AFormItem>
|
||||
<span v-else>
|
||||
<CustomRender
|
||||
:column="c"
|
||||
:text="get(detail, c.dataIndex as any)"
|
||||
:record="detail"
|
||||
/>
|
||||
</span>
|
||||
</ADescriptionsItem>
|
||||
</ADescriptions>
|
||||
</AForm>
|
||||
</template>
|
67
app/src/components/StdDesign/types.d.ts
vendored
67
app/src/components/StdDesign/types.d.ts
vendored
|
@ -1,33 +1,52 @@
|
|||
/* eslint-disable ts/no-explicit-any */
|
||||
|
||||
import type { Pagination } from '@/api/curd'
|
||||
import type Curd from '@/api/curd'
|
||||
|
||||
import type { 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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
},
|
||||
}, {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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" {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue