feat: search field width + collapse actions into dropdown
- ty-form 字段支持 width(行内默认 200px、弹窗默认 100%) - ty-search-form 展开为多行高级搜索 - ty-crud 操作列数据驱动(actions 内置/自定义),超过 collapseCount 折叠为「更多」下拉 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
104b7678d3
commit
1ee3ed28a5
@ -20,9 +20,28 @@
|
||||
:actions-width="actionsWidth"
|
||||
>
|
||||
<template #actions="{ row }">
|
||||
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
<slot name="row-actions" :row="row" />
|
||||
<el-button
|
||||
v-for="a in displayActions(row)"
|
||||
:key="a.label"
|
||||
link
|
||||
:type="a.type"
|
||||
@click="a.onClick(row)"
|
||||
>
|
||||
{{ a.label }}
|
||||
</el-button>
|
||||
<el-dropdown v-if="overflowActions(row).length > 0" trigger="click" @command="handleCommand($event, row)">
|
||||
<el-button link type="primary">
|
||||
更多
|
||||
<icon type="iconify" icon="mdi:chevron-down" />
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="a in overflowActions(row)" :key="a.label" :command="a">
|
||||
{{ a.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</ty-table>
|
||||
</el-card>
|
||||
@ -41,9 +60,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { JsonSchema, PageQuery, PageResult, TableColumn } from '@/types/crud'
|
||||
import type { CrudAction, CrudActionItem, JsonSchema, PageQuery, PageResult, TableColumn } from '@/types/crud'
|
||||
|
||||
defineOptions({ name: 'TyCrud' })
|
||||
|
||||
@ -71,6 +90,10 @@ const props = withDefaults(
|
||||
nameKey?: string
|
||||
/** 编辑模式下强制只读的字段。 */
|
||||
editReadonlyKeys?: string[]
|
||||
/** 操作列动作(内置 'edit'/'delete' 或自定义动作)。 */
|
||||
actions?: CrudAction[]
|
||||
/** 操作列按钮超过该数量时折叠为「更多」。 */
|
||||
collapseCount?: number
|
||||
actionsWidth?: number | string
|
||||
}>(),
|
||||
{
|
||||
@ -78,6 +101,8 @@ const props = withDefaults(
|
||||
idKey: 'id',
|
||||
nameKey: 'name',
|
||||
editReadonlyKeys: () => [],
|
||||
actions: () => ['edit', 'delete'],
|
||||
collapseCount: 2,
|
||||
actionsWidth: 220,
|
||||
},
|
||||
)
|
||||
@ -172,6 +197,32 @@ async function handleDelete(row: Record<string, any>): Promise<void> {
|
||||
fetchList()
|
||||
}
|
||||
|
||||
const resolvedActions = computed<CrudActionItem[]>(() =>
|
||||
props.actions.map((a): CrudActionItem => {
|
||||
if (a === 'edit') return { label: '编辑', type: 'primary', onClick: (row) => openEdit(row) }
|
||||
if (a === 'delete') return { label: '删除', type: 'danger', onClick: (row) => handleDelete(row) }
|
||||
return a
|
||||
}),
|
||||
)
|
||||
|
||||
function visibleActions(row: Record<string, any>): CrudActionItem[] {
|
||||
return resolvedActions.value.filter((a) => !a.show || a.show(row))
|
||||
}
|
||||
|
||||
function displayActions(row: Record<string, any>): CrudActionItem[] {
|
||||
const visible = visibleActions(row)
|
||||
return visible.length > props.collapseCount ? visible.slice(0, 1) : visible
|
||||
}
|
||||
|
||||
function overflowActions(row: Record<string, any>): CrudActionItem[] {
|
||||
const visible = visibleActions(row)
|
||||
return visible.length > props.collapseCount ? visible.slice(1) : []
|
||||
}
|
||||
|
||||
function handleCommand(action: CrudActionItem, row: Record<string, any>): void {
|
||||
action.onClick(row)
|
||||
}
|
||||
|
||||
onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
:placeholder="prop.placeholder ?? '请选择'"
|
||||
:clearable="!isRequired(key)"
|
||||
:disabled="isReadonly(key, prop)"
|
||||
style="width: 100%"
|
||||
:style="widgetStyle(prop)"
|
||||
>
|
||||
<el-option
|
||||
v-for="(v, i) in prop.enum"
|
||||
@ -30,6 +30,7 @@
|
||||
:min="prop.minimum"
|
||||
:max="prop.maximum"
|
||||
:disabled="isReadonly(key, prop)"
|
||||
:style="widgetStyle(prop)"
|
||||
/>
|
||||
|
||||
<el-input
|
||||
@ -39,6 +40,7 @@
|
||||
show-password
|
||||
:placeholder="prop.placeholder"
|
||||
:disabled="isReadonly(key, prop)"
|
||||
:style="widgetStyle(prop)"
|
||||
/>
|
||||
|
||||
<el-input
|
||||
@ -47,6 +49,7 @@
|
||||
type="textarea"
|
||||
:placeholder="prop.placeholder"
|
||||
:disabled="isReadonly(key, prop)"
|
||||
:style="widgetStyle(prop)"
|
||||
/>
|
||||
|
||||
<el-input
|
||||
@ -54,6 +57,7 @@
|
||||
v-model="modelValue[key]"
|
||||
:placeholder="prop.placeholder"
|
||||
:disabled="isReadonly(key, prop)"
|
||||
:style="widgetStyle(prop)"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@ -127,6 +131,10 @@ function isReadonly(key: string, prop: { readOnly?: boolean }): boolean {
|
||||
return prop.readOnly === true || props.readonlyKeys.includes(key)
|
||||
}
|
||||
|
||||
function widgetStyle(prop: JsonSchemaProperty): Record<string, string> {
|
||||
return { width: prop.width ?? (props.inline ? '200px' : '100%') }
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate: () => formRef.value?.validate(),
|
||||
resetFields: () => formRef.value?.resetFields(),
|
||||
|
||||
@ -8,14 +8,11 @@
|
||||
:create="create"
|
||||
:update="update"
|
||||
:remove="remove"
|
||||
:actions="actions"
|
||||
id-key="id"
|
||||
name-key="username"
|
||||
:edit-readonly-keys="['username']"
|
||||
>
|
||||
<template #row-actions="{ row }">
|
||||
<el-button link type="warning" @click="openResetPwd(row)">重置密码</el-button>
|
||||
</template>
|
||||
</ty-crud>
|
||||
/>
|
||||
|
||||
<el-dialog v-model="resetPwd.visible" title="重置密码" width="420px" destroy-on-close>
|
||||
<el-form label-width="80px">
|
||||
@ -34,7 +31,7 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { JsonSchema, PageQuery, PageResult, TableColumn } from '@/types/crud'
|
||||
import type { CrudAction, JsonSchema, PageQuery, PageResult, TableColumn } from '@/types/crud'
|
||||
|
||||
const STATUS_MAP = {
|
||||
enabled: { label: '启用', type: 'success' },
|
||||
@ -110,6 +107,12 @@ const formSchema: JsonSchema = {
|
||||
required: ['username', 'password'],
|
||||
}
|
||||
|
||||
const actions: CrudAction[] = [
|
||||
'edit',
|
||||
'delete',
|
||||
{ label: '重置密码', type: 'warning', onClick: (row) => openResetPwd(row) },
|
||||
]
|
||||
|
||||
// ---- 占位数据(后续对接后端时替换为 http 调用)----
|
||||
interface UserRow {
|
||||
id: string
|
||||
|
||||
@ -24,6 +24,8 @@ export interface JsonSchemaProperty {
|
||||
minimum?: number
|
||||
maximum?: number
|
||||
placeholder?: string
|
||||
/** 控件宽度(如 '160px');缺省时搜索行内默认 200px、弹窗表单默认 100%。 */
|
||||
width?: string
|
||||
}
|
||||
|
||||
/** JSON Schema 对象(表单 schema)。 */
|
||||
@ -64,3 +66,15 @@ export interface PageQuery {
|
||||
pageSize: number
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
/** 操作列自定义动作。 */
|
||||
export interface CrudActionItem {
|
||||
label: string
|
||||
type?: 'primary' | 'success' | 'warning' | 'danger' | 'info'
|
||||
onClick: (row: Record<string, any>) => void | Promise<void>
|
||||
/** 是否对当前行显示(缺省显示)。 */
|
||||
show?: (row: Record<string, any>) => boolean
|
||||
}
|
||||
|
||||
/** 操作列动作:内置 'edit'/'delete' 或自定义动作。 */
|
||||
export type CrudAction = 'edit' | 'delete' | CrudActionItem
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user