11import { useState , useEffect , useRef } from 'react' ;
22
3- import { DownOutlined } from '@ant-design/icons' ;
3+ import { PlusOutlined , EditOutlined , DeleteOutlined } from '@ant-design/icons' ;
44import { Form , Input , Button , Tree , Modal , Spin , Dropdown , Card , MenuProps , Popconfirm , message , Radio , Select , Skeleton } from 'antd' ;
55import type { DataNode } from 'antd/es/tree' ;
66
@@ -26,7 +26,6 @@ export default () => {
2626
2727 const getCateList = async ( ) => {
2828 try {
29- // 如果是第一次加载,使用 initialLoading
3029 if ( isFirstLoadRef . current ) {
3130 setInitialLoading ( true ) ;
3231 } else {
@@ -107,7 +106,6 @@ export default () => {
107106
108107 await getCateList ( ) ;
109108
110- // 初始化表单状态
111109 form . resetFields ( ) ;
112110 setCate ( { } as Cate ) ;
113111
@@ -130,38 +128,63 @@ export default () => {
130128 setCate ( { } as Cate ) ;
131129 } ;
132130
133- // 将数据转换为树形结构
134131 const toTreeData = ( data : Cate [ ] ) : DataNode [ ] =>
135132 data . map ( ( item ) => {
136133 const items : MenuProps [ 'items' ] = [
137134 {
138135 key : '1' ,
139- label : < span onClick = { ( ) => addCateData ( item . id ! ) } > 新增</ span > ,
136+ label : (
137+ < span className = "flex items-center gap-2 py-1" >
138+ < PlusOutlined className = "text-md" />
139+ 新增
140+ </ span >
141+ ) ,
142+ onClick : ( ) => addCateData ( item . id ! ) ,
140143 } ,
141144 {
142145 key : '2' ,
143- label : < span onClick = { ( ) => editCateData ( item . id ! ) } > 编辑</ span > ,
146+ label : (
147+ < span className = "flex items-center gap-2 py-1" >
148+ < EditOutlined className = "text-md" />
149+ 编辑
150+ </ span >
151+ ) ,
152+ onClick : ( ) => editCateData ( item . id ! ) ,
144153 } ,
145154 {
146155 key : '3' ,
147156 label : (
148- < Popconfirm title = "警告" description = "你确定要删除吗" okText = "确定" cancelText = "取消" onConfirm = { ( ) => delCateData ( item . id ! ) } >
149- < span > 删除</ span >
157+ < Popconfirm
158+ title = "删除确认"
159+ description = "确定要删除该分类吗?"
160+ okText = "确定"
161+ cancelText = "取消"
162+ onConfirm = { ( ) => delCateData ( item . id ! ) }
163+ >
164+ < span className = "flex items-center gap-2 py-1 text-red-500 hover:text-red-600" >
165+ < DeleteOutlined className = "text-md" />
166+ 删除
167+ </ span >
150168 </ Popconfirm >
151169 ) ,
152170 } ,
153171 ] ;
154172
155173 return {
156174 title : (
157- < div className = "group w-full flex justify-between items-center" >
158- < h3 >
159- { item . icon } < span className = "ml-2" > { item . name } </ span >
160- </ h3 >
161-
162- < Dropdown menu = { { items } } arrow >
163- < Button type = "link" size = "small" >
164- 操作 < DownOutlined />
175+ < div className = "group flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2 -ml-2.5 transition-colors hover:bg-slate-50 dark:hover:bg-white/5" >
176+ < div className = "flex min-w-0 flex-1 items-center gap-2" >
177+ < span className = "text-lg leading-none opacity-80" > { item . icon } </ span >
178+ < span className = "truncate text-slate-600 dark:text-slate-200" > { item . name } </ span >
179+ </ div >
180+
181+ < Dropdown menu = { { items } } trigger = { [ 'click' ] } placement = "bottomRight" >
182+ < Button
183+ type = "text"
184+ size = "small"
185+ className = "flex shrink-0 items-center gap-1 text-slate-500 hover:bg-slate-100 hover:text-slate-700 dark:text-slate-400 dark:hover:bg-white/10 dark:hover:text-slate-200"
186+ >
187+ 操作
165188 </ Button >
166189 </ Dropdown >
167190 </ div >
@@ -181,110 +204,142 @@ export default () => {
181204 } ) ) ,
182205 ] ;
183206
184- // 初始加载时显示骨架屏
185207 if ( initialLoading ) {
186208 return (
187- < div >
188- { /* Title 骨架屏 */ }
189- < Card className = "[&>.ant-card-body]:!py-2 [&>.ant-card-body]:!px-5 mb-2" >
190- < div className = "flex justify-between items-center" >
191- < Skeleton . Input active size = "large" style = { { width : 150 , height : 32 } } />
192- < Skeleton . Button active size = "large" style = { { width : 120 , height : 40 } } />
209+ < div className = "space-y-2" >
210+ < Card className = "!rounded-xl !border-stroke !shadow-sm [&>.ant-card-body]:!p-4 dark:!border-strokedark" >
211+ < div className = "flex items-center justify-between" >
212+ < Skeleton . Input active size = "large" className = "!h-9 !w-40" />
213+ < Skeleton . Button active size = "large" className = "!h-10 !w-28" />
193214 </ div >
194215 </ Card >
195216
196- { /* 树形结构骨架屏 */ }
197- < Card className = { `border-stroke [&>.ant-card-body]:!p-[30px_20px] [&>.ant-card-body]:!pb-6 mt-2 min-h-[calc(100vh-160px)]` } >
198- { [ 1 , 2 , 3 , 4 , 5 , 6 ] . map ( ( item ) => (
199- < div key = { item } className = "mb-4" >
200- < div className = "flex items-center justify-between mb-2" >
201- < Skeleton . Input active size = "default" style = { { width : 200 , height : 24 } } />
202- < Skeleton . Button active size = "small" style = { { width : 60 , height : 24 } } />
203- </ div >
204- { /* 子项骨架屏 */ }
205- { item <= 3 && (
206- < div className = "ml-6 space-y-2" >
207- { [ 1 , 2 , 3 ] . map ( ( child ) => (
208- < div key = { child } className = "flex items-center justify-between" >
209- < Skeleton . Input active size = "small" style = { { width : 150 , height : 20 } } />
210- < Skeleton . Button active size = "small" style = { { width : 60 , height : 20 } } />
211- </ div >
212- ) ) }
217+ < Card className = "!min-h-[calc(100vh-160px)] !rounded-xl !border-stroke !shadow-sm [&>.ant-card-body]:!p-6 dark:!border-strokedark" >
218+ < div className = "space-y-5" >
219+ { [ 1 , 2 , 3 , 4 , 5 , 6 ] . map ( ( item ) => (
220+ < div key = { item } className = "space-y-2" >
221+ < div className = "flex items-center justify-between" >
222+ < Skeleton . Input active className = "!h-6 !w-48" />
223+ < Skeleton . Button active size = "small" className = "!h-6 !w-14" />
213224 </ div >
214- ) }
215- </ div >
216- ) ) }
225+ { item <= 3 && (
226+ < div className = "ml-6 space-y-2" >
227+ { [ 1 , 2 , 3 ] . map ( ( child ) => (
228+ < div key = { child } className = "flex items-center justify-between" >
229+ < Skeleton . Input active size = "small" className = "!h-5 !w-36" />
230+ < Skeleton . Button active size = "small" className = "!h-5 !w-12" />
231+ </ div >
232+ ) ) }
233+ </ div >
234+ ) }
235+ </ div >
236+ ) ) }
237+ </ div >
217238 </ Card >
218239 </ div >
219240 ) ;
220241 }
221242
222243 return (
223- < div >
244+ < div className = "space-y-2" >
224245 < Title value = "分类管理" >
225- < Button type = "primary" size = "large" onClick = { ( ) => addCateData ( 0 ) } >
246+ < Button
247+ type = "primary"
248+ size = "large"
249+ onClick = { ( ) => addCateData ( 0 ) }
250+ >
226251 新增分类
227252 </ Button >
228253 </ Title >
229254
230- < Card className = { `border-stroke [&>.ant-card-body]:!p-[30px_20px] [&>.ant-card-body]:!pb-6 mt-2 min-h-[calc(100vh-160px)]` } >
231- < Spin spinning = { loading } >
232- < Tree className = "CatePage" defaultExpandAll = { true } treeData = { toTreeData ( list ) } />
255+ < Card
256+ className = { `CatePage !min-h-[calc(100vh-160px)] !rounded-xl !border !border-stroke !bg-white !shadow-sm [&>.ant-card-body]:!p-6 dark:!border-strokedark dark:!bg-boxdark` }
257+ >
258+ < Spin spinning = { loading } className = "min-h-[280px]" >
259+ < Tree
260+ className = "!bg-transparent [&_.ant-tree-treenode]:!py-0.5 [&_.ant-tree-indent-unit]:!w-4"
261+ defaultExpandAll
262+ treeData = { toTreeData ( list ) }
263+ showLine = { { showLeafIcon : false } }
264+ blockNode
265+ />
233266 </ Spin >
267+ </ Card >
234268
235- < Modal loading = { editLoading } title = { isMethod === 'edit' ? '编辑分类' : '新增分类' } open = { isModelOpen } onCancel = { closeModel } destroyOnClose footer = { null } >
236- < Form form = { form } layout = "vertical" initialValues = { cate } size = "large" preserve = { false } className = "mt-6" >
269+ < Modal
270+ open = { isModelOpen }
271+ onCancel = { closeModel }
272+ footer = { null }
273+ title = { isMethod === 'edit' ? '编辑分类' : '新增分类' }
274+ loading = { editLoading }
275+ className = "[&_.ant-modal-content]:!rounded-2xl"
276+ >
277+ < Form
278+ form = { form }
279+ layout = "vertical"
280+ initialValues = { cate }
281+ size = "large"
282+ preserve = { false }
283+ className = "mt-2 [&_.ant-input]:!rounded-lg [&_.ant-select-selector]:!rounded-lg"
284+ >
285+ < div className = "grid gap-x-4 sm:grid-cols-2" >
237286 < Form . Item label = "名称" name = "name" rules = { [ { required : true , message : '分类名称不能为空' } ] } >
238287 < Input placeholder = "请输入分类名称" />
239288 </ Form . Item >
240-
241289 < Form . Item label = "标识" name = "mark" rules = { [ { required : true , message : '分类标识不能为空' } ] } >
242290 < Input placeholder = "请输入分类标识" />
243291 </ Form . Item >
292+ </ div >
244293
245- < Form . Item label = "图标" name = "icon" >
246- < Input placeholder = "请输入分类图标" />
247- </ Form . Item >
294+ < Form . Item label = "图标" name = "icon" >
295+ < Input placeholder = "请输入分类图标(如 emoji 或图标名) " />
296+ </ Form . Item >
248297
249- { isCateShow && (
250- < Form . Item label = "链接" name = "url" >
251- < Input placeholder = "请输入分类链接" />
252- </ Form . Item >
253- ) }
298+ { isCateShow && (
299+ < Form . Item label = "链接" name = "url" >
300+ < Input placeholder = "请输入分类链接" />
301+ </ Form . Item >
302+ ) }
254303
304+ < div className = "grid gap-x-4 sm:grid-cols-2" >
255305 < Form . Item label = "顺序" name = "order" >
256- < Input placeholder = "请输入分类顺序( 值越小越靠前) " />
306+ < Input placeholder = "值越小越靠前" />
257307 </ Form . Item >
258-
259308 < Form . Item label = "级别" name = "level" >
260309 < Select options = { toCascaderOptions ( list ) } placeholder = "请选择分类级别" />
261310 </ Form . Item >
311+ </ div >
262312
263- < Form . Item label = "模式" name = "type" >
264- < Radio . Group
265- onChange = { ( e ) => {
266- const type = e . target . value ;
267-
268- if ( type === 'nav' ) {
269- setIsCateShow ( true ) ;
270- } else {
271- setIsCateShow ( false ) ;
272- }
273- } }
274- >
275- < Radio value = "cate" > 分类</ Radio >
276- < Radio value = "nav" > 导航</ Radio >
277- </ Radio . Group >
278- </ Form . Item >
279-
280- < Form . Item className = "!mb-0 w-full" >
281- < Button type = "primary" onClick = { submit } loading = { btnLoading } className = "w-full ml-2" >
282- { isMethod === 'edit' ? '编辑分类' : '新增分类' }
283- </ Button >
284- </ Form . Item >
285- </ Form >
286- </ Modal >
287- </ Card >
313+ < Form . Item label = "模式" name = "type" >
314+ < Radio . Group
315+ className = "!flex !gap-4"
316+ onChange = { ( e ) => {
317+ const type = e . target . value ;
318+ setIsCateShow ( type === 'nav' ) ;
319+ } }
320+ >
321+ < Radio value = "cate" className = "!m-0" >
322+ 分类
323+ </ Radio >
324+ < Radio value = "nav" className = "!m-0" >
325+ 导航
326+ </ Radio >
327+ </ Radio . Group >
328+ </ Form . Item >
329+
330+ < Form . Item className = "!mb-0" >
331+ < Button
332+ type = "primary"
333+ onClick = { submit }
334+ loading = { btnLoading }
335+ className = "!h-12 !w-full !rounded-lg !font-medium"
336+ icon = { isMethod === 'edit' ? < EditOutlined /> : < PlusOutlined /> }
337+ >
338+ { isMethod === 'edit' ? '保存修改' : '新增分类' }
339+ </ Button >
340+ </ Form . Item >
341+ </ Form >
342+ </ Modal >
288343 </ div >
289344 ) ;
290345} ;
0 commit comments