diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d24e008 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,19 @@ +# Repository Guidelines + +## Project Structure & Module Organization +The primary Vite application lives in `src/`, with feature folders such as `components/ai`, `components/auth`, and `components/dashboard` grouping screens, while shared primitives stay in `components/ui` alongside the `cn` helper. Domain utilities and integrations sit in `lib/`, typed contracts in `types/`, and global styles in `styles/`. Reference notes, migration guides, and UX briefs belong in `docs/`. Build artifacts are staged in `build/` and `bundles/` - regenerate them instead of editing in place. The `crop-x/` workspace hosts the API-driven Next.js toolchain, and `nextjs-frontend/` contains a Jest-enabled prototype; treat each as an isolated package with its own dependencies. + +## Build, Test, and Development Commands +Install dependencies with `npm install` at the repository root before any work. Launch the Vite dev server via `npm run dev` and build production bundles with `npm run build`. When touching the Next.js workspace, switch into `crop-x/` (or `nextjs-frontend/`) and run the same `npm run dev` / `npm run build` loop; lint with `npm run lint` and regenerate OpenAPI clients through `npm run generate-client`. The Next.js prototype keeps Jest wired - execute `npm test` or `npm run coverage` from `nextjs-frontend/` to validate UI contracts. + +## Coding Style & Naming Conventions +Author components as TypeScript function components, export them with PascalCase names, and keep props camelCase. Align layout and spacing with Tailwind classes; reach for `components/ui` primitives before adding bespoke markup. Follow the established single-quote, semicolon-terminated formatting visible in `src/App.tsx`. In the Next.js packages, run Prettier (`npm run prettier`) when formatting cross-file changes and keep ESLint green before opening a review. + +## Testing Guidelines +Automated coverage is concentrated in `nextjs-frontend/__tests__` using Jest and Testing Library; mirror existing file naming (`*.test.tsx`) when adding scenarios. For the Vite app, smoke the key dashboards after significant UI changes: authentication, machinery, fields, operations, assets, AI models, and irrigation. Document manual steps or screenshots in the PR when you touch flows without automated tests. + +## Commit & Pull Request Guidelines +Commit messages currently follow the "生产管理系统 - " prefix; keep that Chinese tag and supply a short, imperative summary. Group related changes per commit so reviewers can bisect easily. Pull requests should describe the user-visible impact, list any config or schema updates, and attach before/after captures for UI adjustments. Link tracking tickets where possible and flag required environment variables (`VITE_API_BASE_URL`, etc.) if they change. + +## Security & Configuration Tips +Never commit secrets - use `.env.local` entries for API hosts and credentials, and document new keys in `docs/` instead. When adding third-party scripts, load them through vetted helpers like `lib/mapLoader` to keep CSP compliance intact. Review generated OpenAPI clients before shipping to ensure endpoints line up with the deployed backend. diff --git a/crop-x/docs/寮鍙戦」鐩鑼.md b/crop-x/docs/寮鍙戦」鐩鑼.md index e23fbd4..069d686 100644 --- a/crop-x/docs/寮鍙戦」鐩鑼.md +++ b/crop-x/docs/寮鍙戦」鐩鑼.md @@ -234,6 +234,56 @@ import { getTenantAuditLogsApiV1TenantsAuditLogsGet, } from "@/lib/api/sdk.gen"; 杩欎釜寮曞叆鍜岀敤娉曘 +### 11.Next.js 鏂囦欢鍛藉悕瑙勮寖鍘熷垯 + +**瑙勮寖瑕佹眰锛** +鎵鏈夋枃浠跺悕蹇呴』涓ユ牸閬靛惊 Next.js 鐨勬枃浠跺懡鍚嶈鑼冿紝纭繚璺敱绯荤粺鍜岄〉闈㈢粍浠惰兘澶熸纭瘑鍒 + +**瑙勮寖鏍囧噯锛** + +#### 椤甸潰鏂囦欢鍛藉悕瑙勮寖 +- **椤甸潰鏂囦欢**锛氬繀椤讳娇鐢 `page.tsx` 浣滀负椤甸潰鏂囦欢鍚 +- **甯冨眬鏂囦欢**锛氬繀椤讳娇鐢 `layout.tsx` 浣滀负甯冨眬鏂囦欢鍚 +- **鍔犺浇鐘舵**锛氬繀椤讳娇鐢 `loading.tsx` 浣滀负鍔犺浇鐘舵佹枃浠跺悕 +- **閿欒澶勭悊**锛氬繀椤讳娇鐢 `error.tsx` 浣滀负閿欒澶勭悊鏂囦欢鍚 +- **鏈壘鍒伴〉闈**锛氬繀椤讳娇鐢 `not-found.tsx` 浣滀负404椤甸潰鏂囦欢鍚 + +#### 璺敱鍙傛暟鏂囦欢鍛藉悕瑙勮寖 +- **鍔ㄦ佽矾鐢**锛氫娇鐢 `[param].tsx` 鏍煎紡锛屽 `[id].tsx` +- **鍙夊弬鏁**锛氫娇鐢 `[[param]].tsx` 鏍煎紡 +- **鍏ㄩ儴鍖归厤**锛氫娇鐢 `[...param].tsx` 鏍煎紡 +- **鍙夊叏閮ㄥ尮閰**锛氫娇鐢 `[[...param]].tsx` 鏍煎紡 + +#### 缁勪欢鍜屽伐鍏锋枃浠跺懡鍚嶈鑼 +- **React 缁勪欢**锛氫娇鐢 PascalCase 鍛藉悕锛屽 `UserProfile.tsx` +- **宸ュ叿鍑芥暟**锛氫娇鐢 camelCase 鍛藉悕锛屽 `dateUtils.ts` +- **绫诲瀷瀹氫箟**锛氫娇鐢 camelCase 鍛藉悕锛屽 `userTypes.ts` +- **甯搁噺鏂囦欢**锛氫娇鐢 UPPER_SNAKE_CASE 鍛藉悕锛屽 `API_CONSTANTS.ts` + +**绀轰緥鐩綍缁撴瀯锛** +``` +src/app/(app)/land-information/ +鈹溾攢鈹 layout.tsx # 甯冨眬鏂囦欢 +鈹溾攢鈹 page.tsx # 涓婚〉闈 +鈹溾攢鈹 loading.tsx # 鍔犺浇鐘舵 +鈹溾攢鈹 error.tsx # 閿欒澶勭悊 +鈹斺攢鈹 archive/ + 鈹溾攢鈹 page.tsx # 褰掓。椤甸潰 + 鈹溾攢鈹 statistics/ + 鈹 鈹溾攢鈹 page.tsx # 缁熻椤甸潰 + 鈹 鈹斺攢鈹 components/ + 鈹 鈹溾攢鈹 FilterPanel.tsx # 缁勪欢锛歅ascalCase + 鈹 鈹斺攢鈹 statisticsReducer.tsx # 宸ュ叿鏂囦欢锛歝amelCase + 鈹斺攢鈹 [id]/ + 鈹斺攢鈹 page.tsx # 鍔ㄦ佽矾鐢遍〉闈 +``` + +**瀹炴柦瑕佺偣锛** +- 涓ユ牸閬靛惊鏂囦欢鍛藉悕瑙勮寖锛屼笉寰楅殢鎰忎慨鏀规枃浠跺悕 +- 椤甸潰鏂囦欢蹇呴』浣跨敤 `page.tsx`锛屼笉鑳戒娇鐢ㄥ叾浠栧悕绉 +- 鍔ㄦ佽矾鐢卞弬鏁板繀椤讳娇鐢ㄦ柟鎷彿 `[param]` 鏍煎紡 +- 缁勪欢鍜屽伐鍏锋枃浠堕伒寰氱敤鐨 JavaScript/TypeScript 鍛藉悕瑙勮寖 + ## path锛歭and-information/archive/statistics锛宯ame锛氱粺璁″垎鏋愰〉闈㈠紑鍙戠粡楠屼笌闂瑙e喅 ### 闂1锛氬浘琛ㄦí杞存樉绀轰笉瀹屾暣 @@ -819,4 +869,609 @@ engine.addMarker(marker); - **鎻掍欢鍖栨灦鏋**锛氱紪杈戝伐鍏烽噰鐢ㄦ彃浠跺寲璁捐锛屾槗浜庢墿灞曟柊鍔熻兘 - **鎺ュ彛鏍囧噯鍖**锛氱粺涓鐨勬帴鍙h璁★紝渚夸簬鍔熻兘妯″潡鏇挎崲 - **閰嶇疆鍖栧紑鍙**锛氭敮鎸侀氳繃閰嶇疆鏂囦欢璋冩暣鍔熻兘鍜岃涓 - - 鐞嗚В浜嗗鏉傚簲鐢ㄤ腑鐨勭粍浠跺垎灞傚拰鑱岃矗鍒掑垎 \ No newline at end of file + - 鐞嗚В浜嗗鏉傚簲鐢ㄤ腑鐨勭粍浠跺垎灞傚拰鑱岃矗鍒掑垎 + +--- + +## path锛歴rc/components/common/searchFormPagination锛宯ame锛氭悳绱€佽〃鏍笺佸垎椤典笁鍚堜竴缁勪欢浣跨敤蹇冨緱 + +### 缁勪欢姒傝堪 + +SearchFormPagination 鏄竴涓珮搴﹀彲閰嶇疆鐨勫鍚堢粍浠讹紝闆嗘垚浜嗘悳绱㈣〃鍗曘佹暟鎹〃鏍煎拰鍒嗛〉鍔熻兘銆傝缁勪欢閲囩敤浜嗙幇浠eact寮鍙戞ā寮忥紝閫氳繃閰嶇疆椹卞姩鐨勬柟寮忓疄鐜板鏉傛暟鎹睍绀洪〉闈㈢殑蹇熷紑鍙戙 + +### 鏋舵瀯璁捐 + +#### 1. 缁勪欢灞傛缁撴瀯 + +``` +SearchFormPagination (涓荤粍浠) +鈹溾攢鈹 SearchFormComponent (鎼滅储琛ㄥ崟) +鈹 鈹溾攢鈹 Input (鏂囨湰鎼滅储妗) +鈹 鈹斺攢鈹 Select (涓嬫媺閫夋嫨妗) +鈹溾攢鈹 Card (琛ㄦ牸瀹瑰櫒) +鈹 鈹溾攢鈹 Table (鏁版嵁琛ㄦ牸) +鈹 鈹 鈹溾攢鈹 TableHeader (琛ㄥご) +鈹 鈹 鈹斺攢鈹 TableBody (琛ㄤ綋) +鈹 鈹斺攢鈹 PaginationComponent (鍒嗛〉缁勪欢) +鈹斺攢鈹 LoadingOverlay (鍔犺浇閬僵) +``` + +#### 2. 鏍稿績鏂囦欢缁撴瀯 + +``` +src/components/common/searchFormPagination/ +鈹溾攢鈹 index.ts # 涓荤粍浠跺鍑 +鈹溾攢鈹 page.tsx # SearchFormPagination涓荤粍浠 +鈹溾攢鈹 components/ +鈹 鈹溾攢鈹 SearchFormComponent.tsx # 鎼滅储琛ㄥ崟缁勪欢 +鈹 鈹溾攢鈹 PaginationComponent.tsx # 鍒嗛〉缁勪欢 +鈹 鈹斺攢鈹 searchFormPaginationReducer.tsx # 鐘舵佺鐞嗭紙鍙夛級 +``` + +### 鏍稿績鍔熻兘鐗规 + +#### 1. 鎼滅储琛ㄥ崟鍔熻兘 + +**闃叉姈鎼滅储鏈哄埗**锛 +```tsx +// 鍏抽敭瀹炵幇锛300ms闃叉姈锛岄伩鍏嶉绻丄PI璋冪敤 +useEffect(() => { + const timer = setTimeout(() => { + onFiltersChangeRef.current(localFilters); + }, 300); + + return () => clearTimeout(timer); +}, [localFilters]); +``` + +**澶氬瓧娈甸厤缃敮鎸**锛 +```tsx +const searchFields: SearchFieldConfig[] = [ + { + key: 'search', + type: 'text', + placeholder: '鎼滅储浼佷笟鍚嶇О銆佺紪鐮...', + }, + { + key: 'audit_status', + type: 'select', + defaultValue: 'all', + options: [ + { value: 'all', label: '鍏ㄩ儴鐘舵' }, + { value: 'draft', label: '鑽夌ǹ' }, + // ...鏇村閫夐」 + ], + }, +]; +``` + +#### 2. 琛ㄦ牸灞曠ず鍔熻兘 + +**鍔ㄦ佸垪閰嶇疆**锛 +```tsx +const columns: TableColumnConfig[] = [ + { + key: 'name', + label: '浼佷笟鍚嶇О', + sortable: true, // 鏀寔鎺掑簭 + render: (value, row) => ( +
{value}
+ ), + }, + { + key: 'status', + label: '鐘舵', + render: (value) => getStatusBadge(value), // 鑷畾涔夋覆鏌 + }, +]; +``` + +**鍔犺浇鐘舵佸鐞**锛 +```tsx +// 琛ㄦ牸鍔犺浇閬僵 - 鎻愬崌鐢ㄦ埛浣撻獙 +{loading && ( +
+
+ + 鍔犺浇涓... +
+
+)} +``` + +#### 3. 鍒嗛〉鍔熻兘 + +**瀹屾暣鍒嗛〉閰嶇疆**锛 +```tsx +interface PaginationConfig { + page: number; + size: number; + total: number; + totalPages: number; + hasNext: boolean; + hasPrev: boolean; +} + + +``` + +**鏅鸿兘鍒嗛〉閫昏緫**锛 +- 褰撳彧鏈変竴椤垫暟鎹椂锛屽垎椤垫寜閽殣钘忎絾姣忛〉鏉℃暟閫夋嫨鍣ㄤ粛鏄剧ず +- 鏀寔椤电爜璺宠浆鍜屽揩閫熷鑸 +- 鍒嗛〉鎿嶄綔鏃朵繚鎸佸綋鍓嶆悳绱㈡潯浠 + +### 浣跨敤绀轰緥 + +#### 瀹屾暣璋冪敤绀轰緥 + +```tsx +import { SearchFormPagination } from '@/components/common/searchFormPagination'; + +export default function EnterpriseManagement() { + const [enterprises, setEnterprises] = useState([]); + const [loading, setLoading] = useState(false); + const [pagination, setPagination] = useState({ + page: 1, + size: 10, + total: 0, + totalPages: 0, + hasNext: false, + hasPrev: false, + }); + + // 鎼滅储瀛楁閰嶇疆 + const searchFields = [ + { + key: 'search', + label: '鎼滅储', + type: 'text', + placeholder: '鎼滅储浼佷笟鍚嶇О銆佺紪鐮...', + }, + { + key: 'audit_status', + label: '瀹℃牳鐘舵', + type: 'select', + defaultValue: 'all', + options: [ + { value: 'all', label: '鍏ㄩ儴鐘舵' }, + { value: 'draft', label: '鑽夌ǹ' }, + { value: 'pending', label: '寰呭鏍' }, + { value: 'approved', label: '瀹℃牳閫氳繃' }, + ], + }, + ]; + + // 琛ㄦ牸鍒楅厤缃 + const columns = [ + { + key: 'name', + label: '浼佷笟鍚嶇О', + sortable: true, + render: (value) =>
{value}
, + }, + { + key: 'auditStatus', + label: '瀹℃牳鐘舵', + render: (value) => getAuditStatusBadge(value), + }, + { + key: 'actions', + label: '鎿嶄綔', + render: (_, row) => ( +
+ + +
+ ), + }, + ]; + + // 鏁版嵁鍔犺浇鍑芥暟 + const loadEnterprises = useCallback(async (params) => { + try { + setLoading(true); + const response = await fetchTenants(params); + setEnterprises(response.data); + setPagination({ + page: response.page, + size: response.size, + total: response.total, + totalPages: response.total_pages, + hasNext: response.has_next, + hasPrev: response.has_prev, + }); + } catch (error) { + console.error('Failed to load enterprises:', error); + } finally { + setLoading(false); + } + }, []); + + // 鎼滅储澶勭悊 + const handleSearch = useCallback((filters) => { + loadEnterprises({ + filters, + pagination: { page: 1, size: pagination.size }, + }); + }, [loadEnterprises, pagination.size]); + + // 鍒嗛〉澶勭悊 + const handlePageChange = useCallback((page) => { + setPagination(prev => ({ ...prev, page })); + loadEnterprises({ + pagination: { page, size: pagination.size }, + filters: searchFilters, + }); + }, [loadEnterprises, pagination.size]); + + return ( +
+ 鏂板缓浼佷笟} + searchFields={searchFields} + columns={columns} + data={enterprises} + loading={loading} + error={null} + pagination={pagination} + onPageChange={handlePageChange} + onSizeChange={handleSizeChange} + onSearch={handleSearch} + emptyIcon={} + emptyText="鏆傛棤浼佷笟鏁版嵁" + /> +
+ ); +} +``` + +### 鎺ュ彛瀹氫箟 + +#### SearchFieldConfig - 鎼滅储瀛楁閰嶇疆 + +```tsx +interface SearchFieldConfig { + key: string; // 瀛楁鏍囪瘑 + label: string; // 鏄剧ず鏍囩 + type: 'text' | 'select'; // 瀛楁绫诲瀷 + placeholder?: string; // 鍗犱綅绗︽枃鏈 + options?: Array<{ value: string; label: string }>; // 涓嬫媺閫夐」 + defaultValue?: string; // 榛樿鍊 +} +``` + +#### TableColumnConfig - 琛ㄦ牸鍒楅厤缃 + +```tsx +interface TableColumnConfig { + key: string; // 鏁版嵁瀛楁鍚 + label: string; // 琛ㄥご鏄剧ず鏂囨湰 + sortable?: boolean; // 鏄惁鏀寔鎺掑簭 + width?: string; // 鍒楀璁剧疆 + render?: (value: any, row: any, index: number) => React.ReactNode; // 鑷畾涔夋覆鏌 +} +``` + +#### SearchFormPaginationProps - 涓荤粍浠跺睘鎬 + +```tsx +interface SearchFormPaginationProps { + // 鎼滅储閰嶇疆 + searchFields: SearchFieldConfig[]; + onSearch?: (filters: Record) => void; + + // 琛ㄦ牸閰嶇疆 + columns: TableColumnConfig[]; + data?: T[]; + loading?: boolean; + error?: string | null; + + // 鍒嗛〉閰嶇疆 + pagination?: PaginationConfig; + onPageChange?: (page: number) => void; + onSizeChange?: (size: number) => void; + onSort?: (sortBy: string, sortOrder: 'asc' | 'desc') => void; + + // UI閰嶇疆 + formTitle?: string; + formRightContent?: React.ReactNode; + emptyIcon?: React.ReactNode; + emptyText?: string; + showSizeSelector?: boolean; + showPageInfo?: boolean; +} +``` + +### 鏈浣冲疄璺 + +#### 1. 鎬ц兘浼樺寲 + +**浣跨敤 useCallback 浼樺寲鍑芥暟寮曠敤**锛 +```tsx +// 鉁 姝g‘鍋氭硶锛氫娇鐢 useCallback 閬垮厤閲嶅娓叉煋 +const handleSearch = useCallback((filters) => { + loadEnterprises({ filters }); +}, [loadEnterprises]); + +const handlePageChange = useCallback((page) => { + loadEnterprises({ page, size: pagination.size }); +}, [loadEnterprises, pagination.size]); + +// 鉂 閿欒鍋氭硶锛氭瘡娆℃覆鏌撻兘鍒涘缓鏂板嚱鏁 +const handleSearch = (filters) => { + loadEnterprises({ filters }); +}; +``` + +**缁熶竴鏁版嵁閲嶈浇鍑芥暟**锛 +```tsx +// 鉁 鎺ㄨ崘锛氱粺涓鐨勬暟鎹噸杞介昏緫锛岄伩鍏嶄唬鐮侀噸澶 +const reloadData = useCallback(() => { + const reloadParams = { + filters: searchFilters, + pagination: { + page: pagination.page, + size: pagination.size + } + }; + loadEnterprises(reloadParams); +}, [loadEnterprises, searchFilters, pagination]); + +// 鍦ㄥ涓湴鏂逛娇鐢 +const handleCreateSuccess = () => reloadData(); +const confirmStatusChange = async () => { + await enableTenant(tenantId); + reloadData(); +}; +``` + +#### 2. 鐘舵佺鐞 + +**鍚堢悊鐨勭姸鎬佷緷璧**锛 +```tsx +// 鉁 姝g‘锛氬寘鍚墍鏈夊繀瑕佺殑渚濊禆 +const handlePageChange = useCallback((page: number) => { + loadEnterprises({ + filters: searchFilters, // 纭繚鎼滅储鏉′欢涓嶄細涓㈠け + pagination: { page, size: pagination.size } + }); +}, [loadEnterprises, searchFilters, pagination.size]); +``` + +#### 3. 閿欒澶勭悊 + +**瀹屽杽鐨勯敊璇姸鎬佸鐞**锛 +```tsx + +``` + +#### 4. 鎵╁睍鎬ц璁 + +**鏂板瀛楁鐨勭畝鍗曟楠**锛 +1. 鍦 `searchFields` 鏁扮粍涓坊鍔犳柊閰嶇疆 +2. 纭繚鍚庣API鏀寔鏂扮殑鏌ヨ鍙傛暟 +3. 鏃犻渶淇敼浠讳綍缁勪欢閫昏緫 + +```tsx +// 娣诲姞鏂扮殑涓嬫媺妗嗗彧闇涓琛岄厤缃 +{ + key: 'enterprise_status', + label: '浼佷笟鐘舵', + type: 'select', + defaultValue: 'all', + options: [ + { value: 'all', label: '鍏ㄩ儴鐘舵' }, + { value: 'active', label: '鍚敤' }, + { value: 'inactive', label: '绂佺敤' }, + ], +} +``` + +### 鎶鏈壒鐐 + +#### 1. 绫诲瀷瀹夊叏 +- 瀹屾暣鐨 TypeScript 绫诲瀷瀹氫箟 +- 娉涘瀷鏀寔锛岀‘淇濇暟鎹被鍨嬩竴鑷存 +- 涓ユ牸鐨勬帴鍙g害鏉 + +#### 2. 鐢ㄦ埛浣撻獙浼樺寲 +- 闃叉姈鎼滅储锛岄伩鍏嶉绻佽姹 +- 鍔犺浇鐘舵侀伄缃╋紝鎻愪緵瑙嗚鍙嶉 +- 鍒嗛〉鐘舵佷繚鎸侊紝閬垮厤鎼滅储鏉′欢涓㈠け +- 鍝嶅簲寮忚璁★紝閫傞厤涓嶅悓灞忓箷灏哄 + +#### 3. 鍙淮鎶ゆ +- 閰嶇疆椹卞姩锛屽噺灏戠‖缂栫爜 +- 缁勪欢鍖栬璁★紝鑱岃矗鍗曚竴 +- 瀹屽杽鐨勯敊璇鐞嗘満鍒 + +#### 4. 鍙墿灞曟 +- 鎻掍欢鍖栫殑瀛楁閰嶇疆 +- 鑷畾涔夋覆鏌撳嚱鏁版敮鎸 +- 澶氱閰嶇疆閫夐」 + +### 甯歌闂瑙e喅 + +#### 1. 鍒嗛〉鍚庢悳绱㈡潯浠朵涪澶 + +**闂**锛氬垏鎹㈤〉鐮佹垨姣忛〉鏉℃暟鏃讹紝鎼滅储鏉′欢琚噸缃 + +**瑙e喅鏂规**锛 +```tsx +const handlePageChange = useCallback((page) => { + // 纭繚浼犻掑綋鍓嶇殑鎼滅储鏉′欢 + loadEnterprises({ + filters: searchFilters, // 鍏抽敭锛氫紶閫掓悳绱㈡潯浠 + pagination: { page, size: pagination.size } + }); +}, [loadEnterprises, searchFilters, pagination.size]); +``` + +#### 2. 棰戠箒API璋冪敤闂 + +**闂**锛氱敤鎴峰揩閫熻緭鍏ユ椂瑙﹀彂杩囧API璇锋眰 + +**瑙e喅鏂规**锛氱粍浠跺唴缃300ms闃叉姈鏈哄埗锛屾棤闇棰濆澶勭悊 + +#### 3. 鍔犺浇鐘舵佸鐞 + +**闂**锛氭暟鎹姞杞芥椂鐢ㄦ埛浣撻獙涓嶄匠 + +**瑙e喅鏂规**锛 +```tsx + +``` + +### 鎬ц兘浼樺寲鏈浣冲疄璺 + +#### 1. 浜嬩欢椹卞姩妯″紡 + +**鍘熷垯**锛氶伩鍏嶄娇鐢╯etTimeout锛屽敖鍙兘鍑忓皯useEffect锛屼娇鐢ㄤ簨浠堕┍鍔ㄦ潵瀹炵幇鐘舵佹洿鏂般 + +**鏈浣冲疄璺**锛 +```tsx +// 鉂 閬垮厤鍐欐硶锛氫娇鐢╯etTimeout鍜岃繃澶歶seEffect +useEffect(() => { + const timer = setTimeout(() => { + loadData(); + }, 300); + return () => clearTimeout(timer); +}, [filters]); + +useEffect(() => { + if (page > 1) { + loadData(); + } +}, [page]); + +// 鉁 鎺ㄨ崘鍐欐硶锛氫簨浠堕┍鍔紝鐩存帴璋冪敤 +const handleSearch = useCallback((filters) => { + setSearchFilters(filters); + loadData({ filters, pagination: { page: 1, size: pagination.size } }); +}, [loadData, pagination.size]); + +const handlePageChange = useCallback((page) => { + setPagination(prev => ({ ...prev, page })); + loadData({ filters: searchFilters, pagination: { page, size: pagination.size } }); +}, [loadData, searchFilters, pagination.size]); +``` + +#### 2. 鍑芥暟渚濊禆浼樺寲 + +**鍘熷垯**锛氬噺灏憉seCallback鍜寀seMemo鐨勪緷璧栭」锛岄氳繃鍙傛暟浼犻掕岄潪渚濊禆澶栭儴鐘舵併 + +**鏈浣冲疄璺**锛 +```tsx +// 鉂 閬垮厤鍐欐硶锛氳繃澶氫緷璧栭」瀵艰嚧鍑芥暟棰戠箒閲嶆柊鍒涘缓 +const loadData = useCallback(async () => { + // 渚濊禆filters, pagination, sortBy绛 +}, [filters, pagination, sortBy]); + +// 鉁 鎺ㄨ崘鍐欐硶锛氭棤渚濊禆椤癸紝閫氳繃鍙傛暟浼犻 +const loadData = useCallback(async (params) => { + // 浣跨敤params.filters, params.pagination绛 +}, []); // 绌轰緷璧栨暟缁 +``` + +#### 3. 鎼滅储闃叉姈浼樺寲 + +**鍘熷垯**锛氫笅鎷夋閫夋嫨绔嬪嵆瑙﹀彂锛屾枃鏈緭鍏ヤ娇鐢ㄩ槻鎶栵紝閬垮厤涓嶅繀瑕佺殑寤惰繜銆 + +**瀹炵幇鏂瑰紡**锛 +```tsx +// SearchFormComponent涓殑浼樺寲瀹炵幇 +const handleInputChange = (key: string, value: string, fieldType: 'text' | 'select') => { + const newFilters = { ...localFilters, [key]: value }; + setLocalFilters(newFilters); + + // 涓嬫媺妗嗛夋嫨绔嬪嵆瑙﹀彂鏌ヨ锛屾枃鏈緭鍏ヤ娇鐢ㄩ槻鎶 + if (fieldType === 'select') { + onFiltersChangeRef.current(newFilters); // 绔嬪嵆鎵ц + } + // 鏂囨湰杈撳叆鐨勯槻鎶栧湪useEffect涓鐞 +}; +``` + +### 缁勪欢璁捐鍘熷垯 + +#### 1. 鎺掑簭鍔熻兘绠鍖 + +**璁捐鍐崇瓥**锛歋earchFormPagination缁勪欢涓嶅啀鏀寔琛ㄥご鎺掑簭鍔熻兘銆 + +**鍘熷洜**锛 +- 绠鍖栫粍浠跺鏉傚害锛屾彁楂樻ц兘 +- 鍑忓皯涓嶅繀瑕佺殑浜や簰锛屼笓娉ㄦ牳蹇冨姛鑳斤紙鎼滅储銆佸睍绀恒佸垎椤碉級 +- 閬垮厤鎺掑簭閫昏緫涓庝笟鍔¢昏緫鑰﹀悎 + +**鏇夸唬鏂规**锛 +- 濡傞渶鎺掑簭鍔熻兘锛屽湪涓氬姟椤甸潰灞傞潰瀹炵幇 +- 閫氳繃涓嬫媺妗嗘垨鍏朵粬UI鎺т欢鎻愪緵鎺掑簭閫夐」 + +#### 2. 鎺ュ彛绠鍖 + +**鍒犻櫎鐨勬帓搴忕浉鍏虫帴鍙**锛 +```tsx +// 鉂 宸插垹闄ょ殑鎺ュ彛 +interface TableColumnConfig { + sortable?: boolean; // 鍒犻櫎 + // ... +} + +interface SearchFormPaginationProps { + sortBy?: string; // 鍒犻櫎 + sortOrder?: 'asc' | 'desc'; // 鍒犻櫎 + onSort?: (sortBy: string, sortOrder: 'asc' | 'desc') => void; // 鍒犻櫎 + // ... +} +``` + +#### 3. 琛ㄥご娓叉煋绠鍖 + +**鍒犻櫎鐨勬帓搴忎氦浜**锛 +- 鍒犻櫎浜嗚〃澶寸殑鐐瑰嚮浜嬩欢澶勭悊 +- 鍒犻櫎浜嗘帓搴忕澶村浘鏍囨樉绀 +- 鍒犻櫎浜嗛紶鏍囨偓鍋滄牱寮忔晥鏋 + +### 閲嶆瀯鎸囧崡 + +濡傛灉瑕侀噸鏋勬垨鍩轰簬姝ょ粍浠跺紑鍙戞柊鍔熻兘锛岃閬靛惊浠ヤ笅鍘熷垯锛 + +1. **淇濇寔鎺ュ彛鍏煎鎬**锛氫笉瑕佺牬鍧忕幇鏈夌殑props鎺ュ彛 +2. **鎵╁睍鑰岄潪淇敼**锛氶氳繃鏂扮殑閰嶇疆椤硅岄潪淇敼鐜版湁閫昏緫鏉ユ坊鍔犲姛鑳 +3. **绫诲瀷瀹夊叏**锛氱‘淇濇墍鏈夋柊鍔熻兘閮芥湁瀹屾暣鐨凾ypeScript绫诲瀷瀹氫箟 +4. **娴嬭瘯瑕嗙洊**锛氭柊鍔熻兘搴旇鏈夌浉搴旂殑娴嬭瘯鐢ㄤ緥 +5. **鏂囨。鏇存柊**锛氬強鏃舵洿鏂颁娇鐢ㄦ枃妗e拰鎺ュ彛璇存槑 +6. **鎬ц兘浼樺厛**锛氶噰鐢ㄤ簨浠堕┍鍔ㄦā寮忥紝閬垮厤涓嶅繀瑕佺殑useEffect鍜宻etTimeout +7. **鍔熻兘涓撴敞**锛氫繚鎸佺粍浠惰亴璐e崟涓锛岄伩鍏嶅姛鑳借繃搴﹀鏉傚寲 + +### 鎬荤粨 + +SearchFormPagination 缁勪欢閫氳繃閰嶇疆椹卞姩鐨勬柟寮忥紝鏋佸ぇ鍦扮畝鍖栦簡澶嶆潅鏁版嵁灞曠ず椤甸潰鐨勫紑鍙戝伐浣溿傚叾鏍稿績浼樺娍鍦ㄤ簬锛 + +- **楂樺害鍙厤缃**锛氶氳繃閰嶇疆鑰岄潪浠g爜瀹炵幇鍔熻兘瀹氬埗 +- **鎬ц兘浼樺寲**锛氫簨浠堕┍鍔ㄦā寮忥紝鏃爏etTimeout渚濊禆锛屾渶灏忓寲useEffect浣跨敤 +- **鐢ㄦ埛浣撻獙**锛氫笅鎷夋绔嬪嵆鍝嶅簲锛屾枃鏈緭鍏ユ櫤鑳介槻鎶 +- **鏄撲簬鎵╁睍**锛氭柊澧炲姛鑳藉彧闇瑕佷慨鏀归厤缃紝鏃犻渶淇敼缁勪欢閫昏緫 +- **绫诲瀷瀹夊叏**锛氬畬鏁寸殑 TypeScript 鏀寔 +- **鍔熻兘涓撴敞**锛氫笓娉ㄦ悳绱€佸睍绀恒佸垎椤垫牳蹇冨姛鑳斤紝閬垮厤杩囧害璁捐 + +璇ョ粍浠跺彲浠ヤ綔涓洪」鐩腑鎵鏈夋暟鎹睍绀洪〉闈㈢殑鏍囧噯瑙e喅鏂规锛屾樉钁楁彁鍗囧紑鍙戞晥鐜囧拰浠g爜璐ㄩ噺銆 \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/enterprise-audit/page.tsx b/crop-x/src/app/(app)/central-config/tenant/enterprise-audit/page.tsx index 4cacc3e..5238bf9 100644 --- a/crop-x/src/app/(app)/central-config/tenant/enterprise-audit/page.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/enterprise-audit/page.tsx @@ -2,20 +2,20 @@ * filekorolheader: 浼佷笟瀹℃牳椤甸潰 - 浼佷笟娉ㄥ唽瀹℃牳绠$悊椤甸潰 * 鍔熻兘锛氫紒涓氬鏍稿垪琛ㄣ佹悳绱㈢瓫閫夈佸鏍告搷浣溿佽鎯呮煡鐪 * 璺緞锛/central-config/tenant/enterprise-audit - * 瑙勮寖锛氶伒寰猚rop-x/docs/寮鍙戦」鐩鑼.md锛屼娇鐢╱seReducer鐘舵佺鐞嗭紝API闆嗘垚锛屾ā鍧楀寲缁勪欢 + * 瑙勮寖锛氶伒寰猚rop-x/docs/寮鍙戦」鐩鑼.md锛屼娇鐢╱seReducer鐘舵佺鐞嗭紝API闆嗘垚锛屾ā鍧楀寲缁勪欢锛孲earchFormPagination閲嶆瀯 */ 'use client'; -import { useReducer, useEffect, useMemo, useRef } from 'react'; +import { useReducer, useEffect, useMemo, useRef, useCallback } from 'react'; import { toast } from 'sonner'; -import { Building2, RefreshCw } from 'lucide-react'; +import { Building2, RefreshCw, Eye, Check, X } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { SearchFormPagination, type SearchFieldConfig, type TableColumnConfig } from '@/components/common/searchFormPagination'; import { fetchTenantsForAudit, auditTenant, transformTenantData, TenantsQueryParams, Enterprise } from './components/enterpriseAuditApi'; import { AuditStatsCards } from './components/AuditStatsCards'; -import { AuditSearchAndFilter } from './components/AuditSearchAndFilter'; -import { EnterpriseAuditTable } from './components/EnterpriseAuditTable'; import { EnterpriseDetailDialog } from './components/EnterpriseDetailDialog'; -import { AuditPagination } from './components/AuditPagination'; // 瀹℃牳鐘舵佺鐞 interface AuditState { @@ -119,21 +119,133 @@ export default function EnterpriseAuditPage() { const [state, dispatch] = useReducer(auditReducer, initialState); const isFirstLoad = useRef(true); - // 鍔犺浇浼佷笟鏁版嵁 - const loadEnterprises = async (resetPage = false) => { + // 鎼滅储瀛楁閰嶇疆 + const searchFields: SearchFieldConfig[] = [ + { + key: 'search', + label: '鎼滅储', + type: 'text', + placeholder: '鎼滅储浼佷笟鍚嶇О銆佺紪鐮...', + }, + { + key: 'audit_status', + label: '瀹℃牳鐘舵', + type: 'select', + defaultValue: 'all', + options: [ + { value: 'all', label: '鍏ㄩ儴鐘舵' }, + { value: '寰呭鏍', label: '寰呭鏍' }, + { value: '宸查氳繃', label: '宸查氳繃' }, + { value: '宸查┏鍥', label: '宸查┏鍥' }, + ], + }, + ]; + + // 琛ㄦ牸鍒楅厤缃 + const columns: TableColumnConfig[] = [ + { + key: 'name', + label: '浼佷笟鍚嶇О', + sortable: false, // 绂佺敤鎺掑簭 + render: (value: string) => ( +
{value}
+ ), + }, + { + key: 'code', + label: '浼佷笟缂栫爜', + sortable: false, // 绂佺敤鎺掑簭 + render: (value: string) => ( +
{value}
+ ), + }, + { + key: 'auditStatus', + label: '瀹℃牳鐘舵', + sortable: false, // 绂佺敤鎺掑簭 + render: (value: string) => { + const statusConfig = { + '寰呭鏍': { label: '寰呭鏍', variant: 'default' as const, className: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200' }, + '宸查氳繃': { label: '宸查氳繃', variant: 'default' as const, className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' }, + '宸查┏鍥': { label: '宸查┏鍥', variant: 'default' as const, className: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' }, + }; + + const config = statusConfig[value as keyof typeof statusConfig] || statusConfig['寰呭鏍']; + return ( + + {config.label} + + ); + }, + }, + { + key: 'contactPerson', + label: '鑱旂郴浜', + sortable: false, // 绂佺敤鎺掑簭 + }, + { + key: 'contactPhone', + label: '鑱旂郴鐢佃瘽', + sortable: false, // 绂佺敤鎺掑簭 + render: (value: string) => ( +
{value || '-'}
+ ), + }, + { + key: 'createdAt', + label: '鍒涘缓鏃堕棿', + sortable: false, // 绂佺敤鎺掑簭 + render: (value: string) => ( +
+ {value ? new Date(value).toLocaleDateString('zh-CN') : '-'} +
+ ), + }, + { + key: 'actions', + label: '鎿嶄綔', + sortable: false, // 鎿嶄綔鍒椾笉鑳芥帓搴 + render: (_: any, row: Enterprise) => ( +
+ +
+ ), + }, + ]; + + // 鍔犺浇浼佷笟鏁版嵁 - 绉婚櫎渚濊禆椤癸紝閫氳繃鍙傛暟浼犻掔姸鎬 + const loadEnterprises = useCallback(async (params?: { + filters?: Record; + pagination?: { page: number; size: number }; + sort?: { sortBy?: string; sortOrder: 'asc' | 'desc' }; + resetPage?: boolean; + }) => { try { dispatch({ type: 'SET_LOADING', payload: true }); - const params: TenantsQueryParams = { - search: state.filters.search || undefined, - audit_status: state.filters.audit_status === 'all' ? undefined : state.filters.audit_status, - page: resetPage ? 1 : state.pagination.page, - size: state.pagination.size, - order_by: state.sortBy, - sort_order: state.sortOrder, + const finalParams: TenantsQueryParams = { + search: (params?.filters?.search ?? state.filters.search) || undefined, + audit_status: params?.filters?.audit_status ?? state.filters.audit_status, + page: params?.resetPage ? 1 : (params?.pagination?.page || state.pagination.page), + size: params?.pagination?.size || state.pagination.size, + order_by: params?.sort?.sortBy, + sort_order: params?.sort?.sortOrder, }; - const response = await fetchTenantsForAudit(params); + // 澶勭悊audit_status锛屽鏋滀负'all'鍒欎笉浼犺鍙傛暟 + if (finalParams.audit_status === 'all') { + finalParams.audit_status = undefined; + } + + const response = await fetchTenantsForAudit(finalParams); const transformedData = response.data.map(transformTenantData); dispatch({ @@ -156,56 +268,48 @@ export default function EnterpriseAuditPage() { dispatch({ type: 'SET_ERROR', payload: errorMessage }); toast.error(errorMessage); } - }; + }, []); // 绉婚櫎鎵鏈変緷璧栵紝浣跨敤鍙傛暟浼犻掔姸鎬佸彉鍖 - // 棣栨鍔犺浇鏁版嵁 - useEffect(() => { + // 棣栨鍔犺浇鏁版嵁 - 浣跨敤浜嬩欢椹卞姩锛岄伩鍏島seEffect + const initializeData = useCallback(() => { if (isFirstLoad.current) { isFirstLoad.current = false; - loadEnterprises(true); + loadEnterprises({ resetPage: true }); } - }, []); + }, [loadEnterprises]); - // 鐩戝惉绛涢夊拰鎺掑簭鍙樺寲锛堟帓闄ら娆″姞杞斤級 + // 椤甸潰鍔犺浇鏃跺垵濮嬪寲鏁版嵁 useEffect(() => { - if (!isFirstLoad.current) { - const timer = setTimeout(() => { - loadEnterprises(true); - }, 300); - return () => clearTimeout(timer); - } - }, [state.filters.search, state.filters.audit_status, state.sortBy, state.sortOrder]); - - // 鍒嗛〉鍔犺浇 - useEffect(() => { - if (!isFirstLoad.current && state.pagination.page > 1) { - loadEnterprises(false); - } - }, [state.pagination.page]); + initializeData(); + }, []); // 鍙湪缁勪欢鎸傝浇鏃舵墽琛屼竴娆 // 璁$畻缁熻鏁版嵁 const stats = useMemo(() => ({ total: state.pagination.total, - pending: state.enterprises.filter(e => e.auditStatus === 'pending').length, - approved: state.enterprises.filter(e => e.auditStatus === 'approved').length, - rejected: state.enterprises.filter(e => e.auditStatus === 'rejected').length, + pending: state.enterprises.filter(e => e.auditStatus === '寰呭鏍').length, + approved: state.enterprises.filter(e => e.auditStatus === '宸查氳繃').length, + rejected: state.enterprises.filter(e => e.auditStatus === '宸查┏鍥').length, }), [state.enterprises, state.pagination.total]); // 浜嬩欢澶勭悊鍣 - const handleSearch = (value: string) => { - dispatch({ type: 'SET_FILTERS', payload: { search: value } }); - }; + const handleSearch = useCallback((filters: Record) => { + dispatch({ type: 'SET_FILTERS', payload: filters }); + loadEnterprises({ + filters, + pagination: { page: 1, size: state.pagination.size } + }); + }, [loadEnterprises, state.pagination.size]); - const handleAuditStatusFilter = (value: string) => { - dispatch({ type: 'SET_FILTERS', payload: { audit_status: value === 'all' ? 'all' : value } }); - }; + const handleSort = useCallback((sortBy: string, sortOrder: 'asc' | 'desc') => { + dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder } }); + loadEnterprises({ + filters: state.filters, + sort: { sortBy, sortOrder }, + resetPage: true + }); + }, [loadEnterprises, state.filters]); - const handleSort = (sortBy?: string) => { - const newSortOrder = state.sortBy === sortBy && state.sortOrder === 'desc' ? 'asc' : 'desc'; - dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder: newSortOrder } }); - }; - - const handlePageChange = (page: number) => { + const handlePageChange = useCallback((page: number) => { // 杈圭晫妫鏌ワ紝纭繚椤电爜鍦ㄦ湁鏁堣寖鍥村唴 if (page < 1) { page = 1; @@ -213,13 +317,25 @@ export default function EnterpriseAuditPage() { page = state.pagination.totalPages; } dispatch({ type: 'SET_PAGINATION', payload: { page } }); - }; + loadEnterprises({ + filters: state.filters, + pagination: { page, size: state.pagination.size } + }); + }, [loadEnterprises, state.filters, state.pagination.size, state.pagination.totalPages]); - const handleRefresh = () => { + const handleSizeChange = useCallback((size: number) => { + dispatch({ type: 'SET_PAGINATION', payload: { size, page: 1 } }); + loadEnterprises({ + filters: state.filters, + pagination: { page: 1, size } + }); + }, [loadEnterprises, state.filters]); + + const handleRefresh = useCallback(() => { dispatch({ type: 'REFRESH_DATA' }); - loadEnterprises(true); + loadEnterprises({ resetPage: true }); toast.success('鏁版嵁宸插埛鏂'); - }; + }, [loadEnterprises]); const handleViewDetail = (enterprise: Enterprise) => { dispatch({ type: 'SET_SELECTED_ENTERPRISE', payload: enterprise }); @@ -257,10 +373,8 @@ export default function EnterpriseAuditPage() { dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: false }); toast.success('瀹℃牳閫氳繃'); - // 1绉掑悗鍒锋柊鍒楄〃 - setTimeout(() => { - loadEnterprises(true); - }, 1000); + // 绔嬪嵆鍒锋柊鍒楄〃锛屾棤闇寤惰繜 + loadEnterprises({ resetPage: true }); } catch (error) { console.error('Approve failed:', error); const errorMessage = error instanceof Error ? error.message : '瀹℃牳閫氳繃澶辫触'; @@ -300,10 +414,8 @@ export default function EnterpriseAuditPage() { dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: false }); toast.success('宸查┏鍥'); - // 1绉掑悗鍒锋柊鍒楄〃 - setTimeout(() => { - loadEnterprises(true); - }, 1000); + // 绔嬪嵆鍒锋柊鍒楄〃锛屾棤闇寤惰繜 + loadEnterprises({ resetPage: true }); } catch (error) { console.error('Reject failed:', error); const errorMessage = error instanceof Error ? error.message : '瀹℃牳椹冲洖澶辫触'; @@ -321,45 +433,47 @@ export default function EnterpriseAuditPage() {

绠$悊浼佷笟娉ㄥ唽涓庡彉鏇村鏍告祦绋

- {/* 缁熻鍗$墖 */} + {/* 缁熻鍗$墖 - 淇濈暀鍘熸湁鍔熻兘 */} - {/* 鎼滅储鍜岀瓫閫 */} - dispatch({ type: 'SET_FILTERS', payload: { search: value } })} - statusFilter={state.filters.audit_status} - onStatusFilterChange={(value) => dispatch({ type: 'SET_FILTERS', payload: { audit_status: value } })} - onRefresh={handleRefresh} + {/* 鎼滅储銆佽〃鏍煎拰鍒嗛〉 - 浣跨敤閲嶆瀯鍚庣殑缁勪欢 */} + + + 鍒锋柊 + + } + searchFields={searchFields} + columns={columns} + data={state.enterprises} loading={state.loading} + error={state.error} + pagination={state.pagination} + sortBy={state.sortBy} + sortOrder={state.sortOrder} + onPageChange={handlePageChange} + onSizeChange={handleSizeChange} + onSearch={handleSearch} + onSort={handleSort} + emptyIcon={} + emptyText="鏆傛棤浼佷笟瀹℃牳鏁版嵁" + showSizeSelector={true} + showPageInfo={true} + sizeOptions={[10, 20, 50, 100]} /> - {/* 浼佷笟鍒楄〃 */} - - - {/* 鍒嗛〉 */} - {state.pagination.total > 0 && ( - - )} - - {/* 浼佷笟璇︽儏瀵硅瘽妗 */} + {/* 浼佷笟璇︽儏瀵硅瘽妗 - 淇濈暀鍘熸湁鍔熻兘 */} dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: open })} enterprise={state.selectedEnterprise} auditReason={state.auditReason} - onAuditReasonChange={(reason) => dispatch({ type: 'SET_AUDIT_REASON', payload: reason })} + onAuditReasonChange={handleAuditReasonChange} onApprove={handleApprove} onReject={handleReject} loading={state.actionLoading} diff --git a/crop-x/src/app/(app)/central-config/tenant/enterprise-management/page.tsx b/crop-x/src/app/(app)/central-config/tenant/enterprise-management/page.tsx index bd969f9..ba01c30 100644 --- a/crop-x/src/app/(app)/central-config/tenant/enterprise-management/page.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/enterprise-management/page.tsx @@ -304,19 +304,33 @@ export default function EnterpriseManagement() { }); }, [loadEnterprises, pagination.size]); + // 缁熶竴鐨勬暟鎹噸杞藉嚱鏁 - 閬垮厤閲嶅浠g爜 + const reloadData = useCallback(() => { + const reloadParams = { + filters: searchFilters, + pagination: { + page: pagination.page, + size: pagination.size + } + }; + loadEnterprises(reloadParams); + }, [loadEnterprises, searchFilters, pagination]); + const handlePageChange = useCallback((page: number) => { setPagination(prev => ({ ...prev, page })); loadEnterprises({ + filters: searchFilters, pagination: { page, size: pagination.size } }); - }, [loadEnterprises, pagination.size]); + }, [loadEnterprises, searchFilters, pagination.size]); const handleSizeChange = useCallback((size: number) => { setPagination(prev => ({ ...prev, size, page: 1 })); loadEnterprises({ + filters: searchFilters, pagination: { page: 1, size } }); - }, [loadEnterprises]); + }, [loadEnterprises, searchFilters]); // 鍒濆鍖栨暟鎹姞杞 // useEffect(() => { @@ -366,15 +380,7 @@ export default function EnterpriseManagement() { dispatch({ type: 'TOGGLE_STATUS_DIALOG', payload: false }); // 閲嶆柊鍔犺浇鏁版嵁鏉ュ弽鏄犵姸鎬佸彉鍖 - const reloadParams: any = { - filters: searchFilters, - pagination: { - page: pagination.page, - size: pagination.size - } - }; - - loadEnterprises(reloadParams); + reloadData(); } catch (error) { console.error('Status change failed:', error); const errorMessage = error instanceof Error ? error.message : '鐘舵佹洿鏂板け璐'; @@ -391,13 +397,7 @@ export default function EnterpriseManagement() { const handleCreateSuccess = () => { // 鍒涘缓鎴愬姛鍚庨噸鏂板姞杞芥暟鎹紝淇濇寔褰撳墠鎼滅储鏉′欢鍜屽垎椤电姸鎬 - loadEnterprises({ - filters: searchFilters, - pagination: { - page: pagination.page, - size: pagination.size - } - }); + reloadData(); }; // 鎿嶄綔鎸夐挳閰嶇疆 diff --git a/crop-x/src/app/(app)/central-config/tenant/user-management/page.tsx b/crop-x/src/app/(app)/central-config/tenant/user-management/page.tsx index ebfbe95..d745811 100644 --- a/crop-x/src/app/(app)/central-config/tenant/user-management/page.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/user-management/page.tsx @@ -2,25 +2,27 @@ * filekorolheader: 鐢ㄦ埛绠$悊椤甸潰 - 鐢ㄦ埛鏌ヨ鍜岀鐞嗛〉闈 * 鍔熻兘锛氱敤鎴峰垪琛ㄦ煡璇€佹悳绱㈢瓫閫夈佽鎯呮煡鐪嬨佺敤鎴风鐞 * 璺緞锛/central-config/tenant/user-management - * 瑙勮寖锛氶伒寰猚rop-x/docs/寮鍙戦」鐩鑼.md锛屼娇鐢╱seReducer鐘舵佺鐞嗭紝API闆嗘垚锛宻hadcn璇箟鍖栨牱寮 + * 瑙勮寖锛氶伒寰猚rop-x/docs/寮鍙戦」鐩鑼.md锛屼娇鐢⊿earchFormPagination鍏叡缁勪欢锛宻hadcn璇箟鍖栨牱寮 */ 'use client'; -import { useReducer, useEffect } from 'react'; +import { useReducer, useEffect, useState, useCallback, useMemo } from 'react'; import { toast } from 'sonner'; +import { Button } from '@/components/ui/button'; +import { Eye, Edit, Lock, UserX, UserCheck } from 'lucide-react'; +import { UserDetailDialog } from './components/UserDetailDialog'; +import { SearchFormPagination, SearchFieldConfig, TableColumnConfig } from '@/components/common/searchFormPagination'; import { fetchUsers, transformUserData, UsersQueryParams, User, UsersApiResponse, PaginationState } from './components/userManagementApi'; import { UserManagementHeader } from './components/UserManagementHeader'; import { UserManagementStatsCards } from './components/UserManagementStatsCards'; -import { UserManagementFilters } from './components/UserManagementFilters'; -import { UserList } from './components/UserList'; -import { UserDetailDialog } from './components/UserDetailDialog'; -import { Enterprise, UserFilters } from './types'; +import { UserFilters } from './types'; + +// 绉婚櫎浜咵nterprise鐨勫紩鐢紝鍥犱负鏂板疄鐜颁腑涓嶅啀闇瑕 // 鐢ㄦ埛绠$悊鐘舵佺鐞 interface UserManagementState { users: User[]; - enterprises: Enterprise[]; loading: boolean; error: string | null; pagination: PaginationState; @@ -38,7 +40,6 @@ type UserManagementAction = | { type: 'SET_FILTERS'; payload: Partial } | { type: 'SET_SORT'; payload: { sortBy?: string; sortOrder: 'asc' | 'desc' } } | { type: 'SET_PAGINATION'; payload: Partial } - | { type: 'SET_ENTERPRISES'; payload: Enterprise[] } | { type: 'SET_SELECTED_USER'; payload: User | null } | { type: 'TOGGLE_DETAIL_DIALOG'; payload: boolean } | { type: 'REFRESH_DATA' }; @@ -63,8 +64,6 @@ const userManagementReducer = (state: UserManagementState, action: UserManagemen return { ...state, sortBy: action.payload.sortBy, sortOrder: action.payload.sortOrder }; case 'SET_PAGINATION': return { ...state, pagination: { ...state.pagination, ...action.payload } }; - case 'SET_ENTERPRISES': - return { ...state, enterprises: action.payload }; case 'SET_SELECTED_USER': return { ...state, selectedUser: action.payload }; case 'TOGGLE_DETAIL_DIALOG': @@ -78,7 +77,6 @@ const userManagementReducer = (state: UserManagementState, action: UserManagemen const initialState: UserManagementState = { users: [], - enterprises: [], loading: false, error: null, pagination: { @@ -102,11 +100,178 @@ const initialState: UserManagementState = { export default function TenantUserManagementPage() { const [state, dispatch] = useReducer(userManagementReducer, initialState); + const [searchFilters, setSearchFilters] = useState>({}); + + // 鎼滅储瀛楁閰嶇疆 + const searchFields: SearchFieldConfig[] = useMemo(() => [ + { + key: 'search', + label: '鎼滅储', + type: 'text', + placeholder: '鎼滅储鐢ㄦ埛鍚嶃佸鍚嶃侀偖绠...', + }, + { + key: 'status', + label: '鐢ㄦ埛鐘舵', + type: 'select', + defaultValue: 'all', + options: [ + { value: 'all', label: '鍏ㄩ儴鐘舵' }, + { value: 'active', label: '娲昏穬' }, + { value: 'inactive', label: '鏈縺娲' }, + ], + }, + { + key: 'type', + label: '鐢ㄦ埛绫诲瀷', + type: 'select', + defaultValue: 'all', + options: [ + { value: 'all', label: '鍏ㄩ儴绫诲瀷' }, + { value: 'admin', label: '绠$悊鍛' }, + { value: 'user', label: '鏅氱敤鎴' }, + { value: 'staff', label: '鍛樺伐' }, + ], + }, + ], []); + + // 琛ㄦ牸鍒楅厤缃 + const columns: TableColumnConfig[] = useMemo(() => [ + { + key: 'username', + label: '鐢ㄦ埛鍚', + sortable: true, + render: (value: string, user: User) => ( +
{value}
+ ), + }, + { + key: 'fullName', + label: '濮撳悕', + sortable: true, + render: (value: string) => value || '-', + }, + { + key: 'email', + label: '閭', + sortable: true, + render: (value: string) => value || '-', + }, + { + key: 'isActive', + label: '鐘舵', + sortable: true, + render: (value: boolean) => ( +
+
+ {value ? '娲昏穬' : '鏈縺娲'} +
+ ), + }, + { + key: 'isSuperuser', + label: '瑙掕壊', + sortable: true, + render: (value: boolean, user: User) => { + if (value) { + return ( +
+
+ 瓒呯骇绠$悊鍛 +
+ ); + } + return ( +
+
+ 鏅氱敤鎴 +
+ ); + }, + }, + { + key: 'isVerified', + label: '楠岃瘉', + sortable: true, + render: (value: boolean) => ( +
+ {value ? '宸查獙璇' : '鏈獙璇'} +
+ ), + }, + { + key: 'lastLoginAt', + label: '鏈鍚庣櫥褰', + sortable: true, + render: (value: string) => { + if (!value) return '-'; + try { + const date = new Date(value); + return date.toLocaleDateString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }); + } catch { + return value; + } + }, + }, + { + key: 'actions', + label: '鎿嶄綔', + render: (_, user: User) => ( +
+ + + + +
+ ), + }, + ], []); // 鍔犺浇鐢ㄦ埛鏁版嵁 - const loadUsers = async (resetPage = false) => { + const loadUsers = useCallback(async (resetPage = false) => { try { - dispatch({ type: 'SET_LOADING', payload: true }); const params: UsersQueryParams = { @@ -114,6 +279,26 @@ export default function TenantUserManagementPage() { size: state.pagination.size, is_active: true, }; + + // 娣诲姞鎼滅储鏉′欢 + if (searchFilters.search) { + params.search = searchFilters.search; + } + + if (searchFilters.status && searchFilters.status !== 'all') { + params.is_active = searchFilters.status === 'active'; + } + + if (searchFilters.type && searchFilters.type !== 'all') { + // For user type filtering, we'll need to handle this differently based on the API + // For now, we'll filter on the client side if needed + } + + if (state.sortBy) { + params.order_by = state.sortBy; + params.sort_order = state.sortOrder; + } + const response: UsersApiResponse = await fetchUsers(params); const transformedUsers = response.data.map(transformUserData); @@ -138,50 +323,33 @@ export default function TenantUserManagementPage() { payload: error instanceof Error ? error.message : '鍔犺浇鐢ㄦ埛鏁版嵁澶辫触' }); } - }; - - // 鍔犺浇浼佷笟鏁版嵁锛堣繖閲屾殏鏃朵娇鐢╩ock鏁版嵁锛屽悗缁彲浠ユ坊鍔犱紒涓欰PI锛 - // const loadEnterprises = () => { - // // 杩欓噷鍙互娣诲姞浼佷笟API璋冪敤锛岀幇鍦ㄤ娇鐢╩ock鏁版嵁 - // const mockEnterprises: Enterprise[] = [ - // { id: 'ent-1', name: '涓版敹鐜颁唬鍐滀笟闆嗗洟' }, - // { id: 'ent-2', name: '缁胯壊绉嶆绉戞妧鏈夐檺鍏徃' }, - // { id: 'ent-3', name: '鏅烘収鍐滀笟绀鸿寖鍖' }, - // ]; - // dispatch({ type: 'SET_ENTERPRISES', payload: mockEnterprises }); - // }; + }, [state.pagination.page, state.pagination.size, state.sortBy, state.sortOrder, searchFilters]); // 鎼滅储澶勭悊 - const handleSearch = (value: string) => { - dispatch({ type: 'SET_FILTERS', payload: { searchKeyword: value } }); - }; - - // 鐘舵佺瓫閫 - const handleStatusFilter = (value: string) => { - dispatch({ type: 'SET_FILTERS', payload: { statusFilter: value } }); - }; - - // 绫诲瀷绛涢 - const handleTypeFilter = (value: string) => { - dispatch({ type: 'SET_FILTERS', payload: { typeFilter: value } }); - }; + const handleSearch = useCallback((filters: Record) => { + setSearchFilters(filters); + dispatch({ type: 'SET_PAGINATION', payload: { page: 1 } }); + }, []); // 鎺掑簭澶勭悊 - const handleSort = (sortBy: string) => { - const newSortOrder = state.sortBy === sortBy && state.sortOrder === 'desc' ? 'asc' : 'desc'; - dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder: newSortOrder } }); - }; + const handleSort = useCallback((sortBy: string, sortOrder: 'asc' | 'desc') => { + dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder } }); + }, []); // 鍒嗛〉澶勭悊 - const handlePageChange = (page: number) => { - // 杈圭晫妫鏌ワ紝纭繚椤电爜鍦ㄦ湁鏁堣寖鍥村唴 + const handlePageChange = useCallback((page: number) => { if (page < 1) { page = 1; } else if (page > state.pagination.totalPages && state.pagination.totalPages > 0) { page = state.pagination.totalPages; } dispatch({ type: 'SET_PAGINATION', payload: { page } }); - }; + }, [state.pagination.totalPages]); + + // 姣忛〉鏉℃暟鍙樺寲澶勭悊 + const handleSizeChange = useCallback((size: number) => { + dispatch({ type: 'SET_PAGINATION', payload: { size, page: 1 } }); + }, []); // 鏌ョ湅璇︽儏 const handleViewDetail = (user: User) => { @@ -191,108 +359,86 @@ export default function TenantUserManagementPage() { // 缂栬緫鐢ㄦ埛 const handleEdit = (user: User) => { - // 杩欓噷鍙互娣诲姞缂栬緫閫昏緫锛屾瘮濡傛墦寮缂栬緫瀵硅瘽妗 toast.info('缂栬緫鍔熻兘寮鍙戜腑...'); }; - // 鍒犻櫎鐢ㄦ埛 - const handleDelete = (user: User) => { - if (!confirm(`纭畾瑕佸垹闄ょ敤鎴 ${user.fullName || user.username} 鍚楋紵`)) return; - // 杩欓噷鍙互娣诲姞鍒犻櫎閫昏緫锛岃皟鐢ˋPI鍒犻櫎鐢ㄦ埛 - toast.info('鍒犻櫎鍔熻兘寮鍙戜腑...'); - }; - // 鍒囨崲鐢ㄦ埛鐘舵 const handleToggleStatus = (user: User) => { const newStatus = !user.isActive; const statusText = newStatus ? '婵娲' : '鍋滅敤'; if (!confirm(`纭畾瑕${statusText}鐢ㄦ埛 ${user.fullName || user.username} 鍚楋紵`)) return; - // 杩欓噷鍙互娣诲姞鐘舵佸垏鎹㈤昏緫锛岃皟鐢ˋPI鏇存柊鐢ㄦ埛鐘舵 toast.info(`${statusText}鍔熻兘寮鍙戜腑...`); }; // 閲嶇疆瀵嗙爜 const handleResetPassword = (user: User) => { if (!confirm(`纭畾瑕侀噸缃敤鎴 ${user.fullName || user.username} 鐨勫瘑鐮佸悧锛焋)) return; - // 杩欓噷鍙互娣诲姞閲嶇疆瀵嗙爜閫昏緫锛岃皟鐢ˋPI閲嶇疆瀵嗙爜 toast.info('閲嶇疆瀵嗙爜鍔熻兘寮鍙戜腑...'); }; - // 缁熻鏁版嵁璁$畻 - const stats = [ + const stats = useMemo(() => [ { label: '鎬荤敤鎴锋暟', value: state.pagination.total, - color: 'text-blue-600', - bg: 'bg-blue-100', + color: 'text-blue-600 dark:text-blue-400', + bg: 'bg-blue-50 dark:bg-blue-950', }, { label: '娲昏穬鐢ㄦ埛', value: state.users.filter(u => u.isActive).length, - color: 'text-green-600', - bg: 'bg-green-100', + color: 'text-green-600 dark:text-green-400', + bg: 'bg-green-50 dark:bg-green-950', }, { label: '绠$悊鍛', value: state.users.filter(u => u.isSuperuser).length, - color: 'text-purple-600', - bg: 'bg-purple-100', + color: 'text-purple-600 dark:text-purple-400', + bg: 'bg-purple-50 dark:bg-purple-950', }, { label: '宸查獙璇', value: state.users.filter(u => u.isVerified).length, - color: 'text-orange-600', - bg: 'bg-orange-100', + color: 'text-orange-600 dark:text-orange-400', + bg: 'bg-orange-50 dark:bg-orange-950', }, - ]; + ], [state.users, state.pagination.total]); + // 鍔犺浇鏁版嵁 useEffect(() => { - loadUsers(); - }, [state.filters.searchKeyword, state.filters.statusFilter, state.filters.typeFilter, state.sortBy, state.sortOrder]); - - useEffect(() => { - if (state.pagination.page > 1) { - loadUsers(); - } - }, [state.pagination.page]); + loadUsers(); + }, [loadUsers]); return (
- {/* 椤甸潰鏍囬鍜岀粺璁 */} + {/* 椤甸潰鏍囬 */} {/* 缁熻鍗$墖 */} - {/* 鎼滅储鍜岀瓫閫 */} - - - {/* 閿欒鏄剧ず */} - {state.error && ( -
-
- {state.error} -
-
- )} - - {/* 鐢ㄦ埛鍒楄〃 */} - toast.info('鏂板缓鐢ㄦ埛鍔熻兘寮鍙戜腑...')}> + 鏂板缓鐢ㄦ埛 + + } + searchFields={searchFields} + columns={columns} + data={state.users} loading={state.loading} + error={state.error} pagination={state.pagination} + sortBy={state.sortBy} + sortOrder={state.sortOrder} onPageChange={handlePageChange} - onViewDetail={handleViewDetail} - onEdit={handleEdit} - onDelete={handleDelete} - onToggleStatus={handleToggleStatus} - onResetPassword={handleResetPassword} + onSizeChange={handleSizeChange} + onSearch={handleSearch} + onSort={handleSort} + emptyText="鏆傛棤鐢ㄦ埛鏁版嵁" + sizeOptions={[10, 20, 50, 100]} /> {/* 鐢ㄦ埛璇︽儏瀵硅瘽妗 */} diff --git a/crop-x/src/components/common/searchFormPagination/components/SearchFormComponent.tsx b/crop-x/src/components/common/searchFormPagination/components/SearchFormComponent.tsx index fcd66db..c19c8bf 100644 --- a/crop-x/src/components/common/searchFormPagination/components/SearchFormComponent.tsx +++ b/crop-x/src/components/common/searchFormPagination/components/SearchFormComponent.tsx @@ -53,16 +53,22 @@ export function SearchFormComponent({ setLocalFilters(filters); }, [filters]); - // 澶勭悊杈撳叆鍙樺寲 - 闃叉姈鎼滅储閬垮厤棰戠箒鍒锋柊瀵艰嚧澶辩劍 - const handleInputChange = (key: string, value: string) => { + // 澶勭悊杈撳叆鍙樺寲 - 鍖哄垎鏂囨湰杈撳叆鍜屼笅鎷夐夋嫨 + const handleInputChange = (key: string, value: string, fieldType: 'text' | 'select') => { const newFilters = { ...localFilters, [key]: value, }; setLocalFilters(newFilters); + + // 涓嬫媺妗嗛夋嫨绔嬪嵆瑙﹀彂鏌ヨ锛屾枃鏈緭鍏ヤ娇鐢ㄩ槻鎶 + if (fieldType === 'select') { + onFiltersChangeRef.current(newFilters); + } + // 鏂囨湰杈撳叆鐨勯槻鎶栧湪useEffect涓鐞 }; - // 浣跨敤闃叉姈鏉ュ噺灏戞悳绱㈤鐜囷紝閬垮厤棰戠箒鍒锋柊瀵艰嚧澶辩劍 + // 浣跨敤闃叉姈鏉ュ噺灏戞悳绱㈤鐜囷紝浠呴拡瀵规枃鏈緭鍏 useEffect(() => { const timer = setTimeout(() => { // 浣跨敤ref寮曠敤鏈鏂扮殑onFiltersChange鍑芥暟锛岄伩鍏嶄緷璧栧彉鍖栧鑷撮噸澶嶈Е鍙 @@ -89,7 +95,7 @@ export function SearchFormComponent({
handleInputChange(field.key, e.target.value)} + onChange={(e) => handleInputChange(field.key, e.target.value, 'text')} disabled={false} // 濮嬬粓鍏佽杈撳叆锛屼笉鍥犲姞杞借岀鐢 className="pl-10 w-64" /> @@ -130,7 +136,7 @@ export function SearchFormComponent({ handleInputChange('search', e.target.value)} + onChange={(e) => handleInputChange('search', e.target.value, 'text')} disabled={false} // 濮嬬粓鍏佽杈撳叆锛屼笉鍥犲姞杞借岀鐢 className="pl-10" /> diff --git a/crop-x/src/components/common/searchFormPagination/components/example.tsx b/crop-x/src/components/common/searchFormPagination/components/example.tsx deleted file mode 100644 index 1e852b9..0000000 --- a/crop-x/src/components/common/searchFormPagination/components/example.tsx +++ /dev/null @@ -1,248 +0,0 @@ -/** - * filekorolheader: 鎼滅储琛ㄥ崟鍒嗛〉缁勪欢浣跨敤绀轰緥 - 灞曠ず濡備綍浣跨敤璇ョ粍浠 - * 鍔熻兘锛氫娇鐢ㄧず渚嬨侀厤缃ず渚嬨佹渶浣冲疄璺靛睍绀 - * 璺緞锛/components/common/searchFormPagination/components/example - * 瑙勮寖锛氶伒寰猚rop-x/docs/寮鍙戦」鐩鑼.md锛屾彁渚涘畬鏁寸殑浣跨敤绀轰緥 - */ -'use client'; - -import { SearchFormPagination, SearchFieldConfig, TableColumnConfig } from '../index'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { Building2, Eye, Power, PowerOff, Plus } from 'lucide-react'; -import { toast } from 'sonner'; - -// 妯℃嫙鏁版嵁绫诲瀷 -interface MockEnterprise { - id: string; - code: string; - name: string; - type: string; - registrant?: string; - contactPhone?: string; - createdAt: string; - auditStatus: 'draft' | 'pending' | 'approved' | 'rejected'; - status: 'active' | 'inactive'; -} - -// 绀轰緥浣跨敤 -export function EnterpriseManagementExample() { - // 鎼滅储瀛楁閰嶇疆 - const searchFields: SearchFieldConfig[] = [ - { - key: 'search', - label: '浼佷笟鎼滅储', - type: 'text', - placeholder: '鎼滅储浼佷笟鍚嶇О銆佺紪鐮...', - }, - { - key: 'audit_status', - label: '瀹℃牳鐘舵', - type: 'select', - placeholder: '閫夋嫨瀹℃牳鐘舵', - options: [ - { value: '', label: '鍏ㄩ儴鐘舵' }, - { value: '鑽夌ǹ', label: '鑽夌ǹ' }, - { value: '寰呭鏍', label: '寰呭鏍' }, - { value: '宸查氳繃', label: '瀹℃牳閫氳繃' }, - { value: '宸叉嫆缁', label: '宸叉嫆缁' }, - ], - }, - ]; - - // 琛ㄦ牸鍒楅厤缃 - const columns: TableColumnConfig[] = [ - { - key: 'code', - label: '浼佷笟缂栫爜', - sortable: true, - width: '120px', - }, - { - key: 'name', - label: '浼佷笟鍚嶇О', - sortable: true, - render: (value: string, row: MockEnterprise) => ( -
- - {value} -
- ), - }, - { - key: 'type', - label: '浼佷笟绫诲瀷', - render: (value: string) => ( - {value} - ), - }, - { - key: 'registrant', - label: '鐧昏浜', - render: (value?: string) => value || '-', - }, - { - key: 'contactPhone', - label: '鑱旂郴鐢佃瘽', - render: (value?: string) => value || '-', - }, - { - key: 'createdAt', - label: '鍒涘缓鏃堕棿', - sortable: true, - width: '160px', - }, - { - key: 'auditStatus', - label: '瀹℃牳鐘舵', - render: (value: MockEnterprise['auditStatus']) => { - const getAuditStatusBadge = (status: MockEnterprise['auditStatus']) => { - switch (status) { - case 'draft': - return 鑽夌ǹ; - case 'pending': - return 寰呭鏍; - case 'approved': - return 瀹℃牳閫氳繃; - case 'rejected': - return 宸叉嫆缁; - default: - return 鑽夌ǹ; - } - }; - return getAuditStatusBadge(value); - }, - }, - { - key: 'status', - label: '鐘舵', - render: (value: MockEnterprise['status']) => { - const getStatusBadge = (status: MockEnterprise['status']) => { - if (status === 'active') { - return 鍚敤; - } - return 绂佺敤; - }; - return getStatusBadge(value); - }, - }, - { - key: 'actions', - label: '鎿嶄綔', - render: (_: any, row: MockEnterprise) => ( -
- - {row.status === 'active' ? ( - - ) : ( - - )} -
- ), - }, - ]; - - // 妯℃嫙鏁版嵁 - const mockData: MockEnterprise[] = [ - { - id: '1', - code: 'ENT001', - name: '绀轰緥绉戞妧鏈夐檺鍏徃', - type: '绉戞妧鏈夐檺鍏徃', - registrant: '寮犱笁', - contactPhone: '13800138000', - createdAt: '2024-01-15 10:30:00', - auditStatus: 'approved', - status: 'active', - }, - { - id: '2', - code: 'ENT002', - name: '娴嬭瘯鍐滀笟鍙戝睍鏈夐檺鍏徃', - type: '鍐滀笟鍙戝睍鏈夐檺鍏徃', - registrant: '鏉庡洓', - contactPhone: '13900139000', - createdAt: '2024-01-16 14:20:00', - auditStatus: 'pending', - status: 'active', - }, - ]; - - // 妯℃嫙鍒嗛〉閰嶇疆 - const mockPagination = { - page: 1, - size: 10, - total: 2, - totalPages: 1, - hasNext: false, - hasPrev: false, - }; - - // 澶勭悊鎼滅储 - const handleSearch = (filters: Record) => { - console.log('鎼滅储鏉′欢:', filters); - toast.success('鎼滅储鏉′欢宸叉洿鏂'); - }; - - // 澶勭悊鎺掑簭 - const handleSort = (sortBy: string, sortOrder: 'asc' | 'desc') => { - console.log('鎺掑簭:', { sortBy, sortOrder }); - toast.success(`鎺掑簭: ${sortBy} ${sortOrder}`); - }; - - // 澶勭悊鍒嗛〉 - const handlePageChange = (page: number) => { - console.log('鍒囨崲鍒伴〉闈:', page); - toast.success(`鍒囨崲鍒扮 ${page} 椤礰); - }; - - // 鎿嶄綔鎸夐挳 - const actionButtons = ( - - ); - - return ( - } - emptyText="鏆傛棤浼佷笟鏁版嵁" - /> - ); -} - -export default EnterpriseManagementExample; \ No newline at end of file diff --git a/crop-x/src/components/common/searchFormPagination/page.tsx b/crop-x/src/components/common/searchFormPagination/page.tsx index 43b7c40..f015b21 100644 --- a/crop-x/src/components/common/searchFormPagination/page.tsx +++ b/crop-x/src/components/common/searchFormPagination/page.tsx @@ -33,7 +33,6 @@ export interface SearchFieldConfig { export interface TableColumnConfig { key: string; label: string; - sortable?: boolean; width?: string; render?: (value: any, row: any, index: number) => React.ReactNode; } @@ -68,11 +67,6 @@ export interface SearchFormPaginationProps { onPageChange?: (page: number) => void; onSizeChange?: (size: number) => void; - // 鎺掑簭閰嶇疆 - sortBy?: string; - sortOrder?: 'asc' | 'desc'; - onSort?: (sortBy: string, sortOrder: 'asc' | 'desc') => void; - // 绌虹姸鎬侀厤缃 emptyIcon?: React.ReactNode; emptyText?: string; @@ -109,9 +103,6 @@ export function SearchFormPagination({ pagination, onPageChange, onSizeChange, - sortBy, - sortOrder, - onSort, emptyIcon, emptyText = '鏆傛棤鏁版嵁', showSizeSelector = true, @@ -130,12 +121,6 @@ export function SearchFormPagination({ }, {} as Record) ); - // 鍚屾澶栭儴鎺掑簭鐘舵 - const [currentSort, setCurrentSort] = useState<{ sortBy?: string; sortOrder: 'asc' | 'desc' }>({ - sortBy, - sortOrder: sortOrder || 'asc' - }); - // 鏁版嵁鏇存柊鍥炶皟 - 閫氱煡鐖剁粍浠舵暟鎹彉鍖 useEffect(() => { onDataUpdate?.({ @@ -159,23 +144,6 @@ export function SearchFormPagination({ onSearch?.(newFilters); }, [onSearch]); - const handleSort = useCallback((columnKey: string) => { - const column = columns.find(col => col.key === columnKey); - if (!column?.sortable) return; - - // 璁$畻鏂扮殑鎺掑簭鐘舵 - let newSortOrder: 'asc' | 'desc'; - if (currentSort.sortBy === columnKey) { - newSortOrder = currentSort.sortOrder === 'desc' ? 'asc' : 'desc'; - } else { - newSortOrder = 'asc'; - } - - const newSort = { sortBy: columnKey, sortOrder: newSortOrder }; - setCurrentSort(newSort); - onSort?.(columnKey, newSortOrder); - }, [columns, currentSort, onSort]); - const handlePageChange = useCallback((page: number) => { onPageChange?.(page); }, [onPageChange]); @@ -210,7 +178,6 @@ export function SearchFormPagination({ {columns.map((column) => ( ({ textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} - onClick={() => column.sortable && handleSort(column.key)} >
{column.label}
- {column.sortable && currentSort.sortBy === column.key && ( - {currentSort.sortOrder === 'asc' ? '鈫' : '鈫'} - )}
))}