chore: import full Element Plus CSS, narrow action column, format

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
liuyun 2026-10-11 09:59:43 +08:00
parent d9f1a4d17e
commit d88b807638
4 changed files with 181 additions and 45 deletions

View File

@ -1,3 +1,4 @@
// 导入变量文件 // 导入变量文件
@use "element-plus/dist/index.css";
@use "./settings"; @use "./settings";
@use "./base"; @use "./base";

View File

@ -1,7 +1,12 @@
<template> <template>
<div class="ty-crud"> <div class="ty-crud">
<el-card v-if="searchSchema" class="ty-crud__search" shadow="never"> <el-card v-if="searchSchema" class="ty-crud__search" shadow="never">
<ty-search-form :schema="searchSchema" :model-value="query" @search="handleSearch" @reset="handleReset" /> <ty-search-form
:schema="searchSchema"
:model-value="query"
@search="handleSearch"
@reset="handleReset"
/>
</el-card> </el-card>
<el-card class="ty-crud__table" shadow="never"> <el-card class="ty-crud__table" shadow="never">
@ -37,7 +42,11 @@
> >
{{ a.label }} {{ a.label }}
</el-button> </el-button>
<el-dropdown v-if="overflowActions(row).length > 0" trigger="click" @command="handleCommand($event, row)"> <el-dropdown
v-if="overflowActions(row).length > 0"
trigger="click"
@command="handleCommand($event, row)"
>
<el-button link type="primary"> <el-button link type="primary">
更多 更多
<icon type="iconify" icon="mdi:chevron-down" /> <icon type="iconify" icon="mdi:chevron-down" />
@ -71,7 +80,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue' import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import type { CrudAction, CrudActionItem, JsonSchema, PageQuery, PageResult, TableColumn } from '@/types/crud' import type {
CrudAction,
CrudActionItem,
JsonSchema,
PageQuery,
PageResult,
TableColumn,
} from '@/types/crud'
defineOptions({ name: 'TyCrud' }) defineOptions({ name: 'TyCrud' })
@ -115,7 +131,7 @@ const props = withDefaults(
actions: () => ['edit', 'delete'], actions: () => ['edit', 'delete'],
collapseCount: 2, collapseCount: 2,
selection: false, selection: false,
actionsWidth: 220, actionsWidth: 140,
}, },
) )
@ -204,7 +220,9 @@ async function handleSubmit(): Promise<void> {
async function handleDelete(row: Record<string, any>): Promise<void> { async function handleDelete(row: Record<string, any>): Promise<void> {
const name = row[props.nameKey] ?? '' const name = row[props.nameKey] ?? ''
await ElMessageBox.confirm(`确认删除${props.entityName}「${name}」?`, '提示', { type: 'warning' }) await ElMessageBox.confirm(`确认删除${props.entityName}「${name}」?`, '提示', {
type: 'warning',
})
await props.remove(String(row[props.idKey])) await props.remove(String(row[props.idKey]))
ElMessage.success('删除成功') ElMessage.success('删除成功')
fetchList() fetchList()
@ -213,7 +231,8 @@ async function handleDelete(row: Record<string, any>): Promise<void> {
const resolvedActions = computed<CrudActionItem[]>(() => const resolvedActions = computed<CrudActionItem[]>(() =>
props.actions.map((a): CrudActionItem => { props.actions.map((a): CrudActionItem => {
if (a === 'edit') return { label: '编辑', type: 'primary', onClick: (row) => openEdit(row) } if (a === 'edit') return { label: '编辑', type: 'primary', onClick: (row) => openEdit(row) }
if (a === 'delete') return { label: '删除', type: 'danger', onClick: (row) => handleDelete(row) } if (a === 'delete')
return { label: '删除', type: 'danger', onClick: (row) => handleDelete(row) }
return a return a
}), }),
) )

View File

@ -16,7 +16,11 @@
:edit-readonly-keys="['username']" :edit-readonly-keys="['username']"
> >
<template #toolbar-right="{ selectedRows }"> <template #toolbar-right="{ selectedRows }">
<el-button type="danger" :disabled="selectedRows.length === 0" @click="batchDelete(selectedRows)"> <el-button
type="danger"
:disabled="selectedRows.length === 0"
@click="batchDelete(selectedRows)"
>
批量删除 批量删除
</el-button> </el-button>
</template> </template>
@ -24,9 +28,16 @@
<el-dialog v-model="resetPwd.visible" title="重置密码" width="420px" destroy-on-close> <el-dialog v-model="resetPwd.visible" title="重置密码" width="420px" destroy-on-close>
<el-form label-width="80px"> <el-form label-width="80px">
<el-form-item label="用户名"><span>{{ resetPwd.username }}</span></el-form-item> <el-form-item label="用户名"
><span>{{ resetPwd.username }}</span></el-form-item
>
<el-form-item label="新密码"> <el-form-item label="新密码">
<el-input v-model="resetPwd.password" type="password" show-password placeholder="请输入新密码" /> <el-input
v-model="resetPwd.password"
type="password"
show-password
placeholder="请输入新密码"
/>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
@ -54,7 +65,8 @@ const orgs = [
{ code: 'sales', name: '销售部' }, { code: 'sales', name: '销售部' },
] ]
const orgName = (code: unknown): string => orgs.find((o) => o.code === code)?.name ?? (code as string) ?? '-' const orgName = (code: unknown): string =>
orgs.find((o) => o.code === code)?.name ?? (code as string) ?? '-'
const columns: TableColumn[] = [ const columns: TableColumn[] = [
{ prop: 'username', label: '用户名', minWidth: 110 }, { prop: 'username', label: '用户名', minWidth: 110 },
@ -136,16 +148,106 @@ interface UserRow {
const now = Date.now() const now = Date.now()
const day = 24 * 60 * 60 * 1000 const day = 24 * 60 * 60 * 1000
const users: UserRow[] = [ const users: UserRow[] = [
{ id: '1', username: 'touyun', nickname: '超级管理员', email: 'touyun@example.com', phone: '13800000001', status: 'enabled', orgCode: 'touyun', createdAt: now - 30 * day }, {
{ id: '2', username: 'zhangsan', nickname: '张三', email: 'zhangsan@example.com', phone: '13800000002', status: 'enabled', orgCode: 'rd', createdAt: now - 20 * day }, id: '1',
{ id: '3', username: 'lisi', nickname: '李四', email: 'lisi@example.com', phone: '13800000003', status: 'disabled', orgCode: 'rd', createdAt: now - 18 * day }, username: 'touyun',
{ id: '4', username: 'wangwu', nickname: '王五', email: 'wangwu@example.com', phone: '13800000004', status: 'locked', orgCode: 'ops', createdAt: now - 15 * day }, nickname: '超级管理员',
{ id: '5', username: 'zhaoliu', nickname: '赵六', email: 'zhaoliu@example.com', phone: '13800000005', status: 'enabled', orgCode: 'sales', createdAt: now - 12 * day }, email: 'touyun@example.com',
{ id: '6', username: 'sunqi', nickname: '孙七', email: 'sunqi@example.com', phone: '13800000006', status: 'enabled', orgCode: 'ops', createdAt: now - 9 * day }, phone: '13800000001',
{ id: '7', username: 'zhouba', nickname: '周八', email: 'zhouba@example.com', phone: '13800000007', status: 'enabled', orgCode: 'rd', createdAt: now - 7 * day }, status: 'enabled',
{ id: '8', username: 'wujiu', nickname: '吴九', email: 'wujiu@example.com', phone: '13800000008', status: 'disabled', orgCode: 'sales', createdAt: now - 5 * day }, orgCode: 'touyun',
{ id: '9', username: 'zhengshi', nickname: '郑十', email: 'zhengshi@example.com', phone: '13800000009', status: 'enabled', orgCode: 'rd', createdAt: now - 3 * day }, createdAt: now - 30 * day,
{ id: '10', username: 'chenyi', nickname: '陈一', email: 'chenyi@example.com', phone: '13800000010', status: 'enabled', orgCode: 'sales', createdAt: now - 1 * day }, },
{
id: '2',
username: 'zhangsan',
nickname: '张三',
email: 'zhangsan@example.com',
phone: '13800000002',
status: 'enabled',
orgCode: 'rd',
createdAt: now - 20 * day,
},
{
id: '3',
username: 'lisi',
nickname: '李四',
email: 'lisi@example.com',
phone: '13800000003',
status: 'disabled',
orgCode: 'rd',
createdAt: now - 18 * day,
},
{
id: '4',
username: 'wangwu',
nickname: '王五',
email: 'wangwu@example.com',
phone: '13800000004',
status: 'locked',
orgCode: 'ops',
createdAt: now - 15 * day,
},
{
id: '5',
username: 'zhaoliu',
nickname: '赵六',
email: 'zhaoliu@example.com',
phone: '13800000005',
status: 'enabled',
orgCode: 'sales',
createdAt: now - 12 * day,
},
{
id: '6',
username: 'sunqi',
nickname: '孙七',
email: 'sunqi@example.com',
phone: '13800000006',
status: 'enabled',
orgCode: 'ops',
createdAt: now - 9 * day,
},
{
id: '7',
username: 'zhouba',
nickname: '周八',
email: 'zhouba@example.com',
phone: '13800000007',
status: 'enabled',
orgCode: 'rd',
createdAt: now - 7 * day,
},
{
id: '8',
username: 'wujiu',
nickname: '吴九',
email: 'wujiu@example.com',
phone: '13800000008',
status: 'disabled',
orgCode: 'sales',
createdAt: now - 5 * day,
},
{
id: '9',
username: 'zhengshi',
nickname: '郑十',
email: 'zhengshi@example.com',
phone: '13800000009',
status: 'enabled',
orgCode: 'rd',
createdAt: now - 3 * day,
},
{
id: '10',
username: 'chenyi',
nickname: '陈一',
email: 'chenyi@example.com',
phone: '13800000010',
status: 'enabled',
orgCode: 'sales',
createdAt: now - 1 * day,
},
] ]
let seq = 0 let seq = 0
@ -166,12 +268,26 @@ const list = (query: PageQuery): Promise<PageResult<Record<string, any>>> =>
return true return true
}) })
const start = (page - 1) * pageSize const start = (page - 1) * pageSize
return { list: filtered.slice(start, start + pageSize) as unknown as Record<string, any>[], total: filtered.length, page, pageSize } return {
list: filtered.slice(start, start + pageSize) as unknown as Record<string, any>[],
total: filtered.length,
page,
pageSize,
}
}) })
const create = (data: Record<string, any>): Promise<unknown> => const create = (data: Record<string, any>): Promise<unknown> =>
delay(() => { delay(() => {
users.unshift({ id: nextId(), username: data.username, nickname: data.nickname, email: data.email, phone: data.phone, status: data.status, orgCode: data.orgCode, createdAt: Date.now() }) users.unshift({
id: nextId(),
username: data.username,
nickname: data.nickname,
email: data.email,
phone: data.phone,
status: data.status,
orgCode: data.orgCode,
createdAt: Date.now(),
})
return null return null
}) })

File diff suppressed because one or more lines are too long