Files
zn-ai/src-react/pages/Home/components/TaskOperationDialog.tsx
duanshuwen b1dea9a5c2 feat: implement task management store with IPC integration
- Added a new task store in `src-react/stores/task.ts` to manage tasks and their statuses.
- Implemented functions for creating, executing, and retrying tasks, along with handling task progress and completion.
- Introduced persistence for tasks using IPC.
- Created utility functions for normalizing room types and building subtasks.
- Added a new CSS file for global styles in `src-react/styles.css`.
- Created runtime types in `src-react/types/runtime.ts` and exported them.
- Updated the main entry points for Vue and React applications to support dynamic framework loading.
- Refactored chat model interfaces and utility functions into `src/shared/chat-model.ts`.
- Updated TypeScript configuration to include paths for React components and types.
- Enhanced Vite configuration to support both Vue and React frameworks.
2026-04-17 07:09:56 +08:00

204 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useState } from 'react';
import { hotelStaffTypeMappingListUsingPost } from '@api/typeMapping';
import type { RoomTypeMapping } from '@api/types';
import type { TaskOperationInput } from '../../../stores';
import DialogSurface from './DialogSurface';
type TaskOperationDialogProps = {
open: boolean;
onClose: () => void;
onConfirm: (options: TaskOperationInput) => Promise<void>;
};
type RoomTypeMappingLike = RoomTypeMapping & {
dyHotSpringName?: string;
};
const OPERATION_OPTIONS: Array<{ label: string; value: 'open' | 'close' }> = [
{ label: '开启', value: 'open' },
{ label: '关闭', value: 'close' },
];
function normalizeRoomType(item: RoomTypeMappingLike): RoomTypeMappingLike {
return {
...item,
dyHotSpringName: item.dyHotSpringName ?? item.dyHotSrpingName ?? '',
dyHotSrpingName: item.dyHotSrpingName ?? item.dyHotSpringName ?? '',
};
}
export default function TaskOperationDialog({
open,
onClose,
onConfirm,
}: TaskOperationDialogProps) {
const [roomList, setRoomList] = useState<RoomTypeMappingLike[]>([]);
const [roomType, setRoomType] = useState('');
const [startTime, setStartTime] = useState('');
const [endTime, setEndTime] = useState('');
const [operation, setOperation] = useState<'open' | 'close' | ''>('');
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!open) return;
setRoomType('');
setStartTime('');
setEndTime('');
setOperation('');
setError(null);
setSubmitting(false);
setLoading(true);
void (async () => {
try {
const response = await hotelStaffTypeMappingListUsingPost({ body: {} as RoomTypeMapping });
setRoomList(Array.isArray(response?.data) ? response.data.map((item: RoomTypeMappingLike) => normalizeRoomType(item)) : []);
} catch (requestError) {
setRoomList([]);
setError(requestError instanceof Error ? requestError.message : String(requestError));
} finally {
setLoading(false);
}
})();
}, [open]);
async function handleConfirm(): Promise<void> {
if (!roomType) {
setError('请选择房型');
return;
}
if (!startTime || !endTime) {
setError('请选择日期范围');
return;
}
if (startTime > endTime) {
setError('开始日期不能晚于结束日期');
return;
}
if (!operation) {
setError('请选择操作');
return;
}
setSubmitting(true);
setError(null);
try {
await onConfirm({
roomType,
startTime,
endTime,
operation,
roomList: roomList.map((item) => ({ ...item })),
});
} catch (confirmError) {
setError(confirmError instanceof Error ? confirmError.message : String(confirmError));
setSubmitting(false);
return;
}
setSubmitting(false);
}
return (
<DialogSurface open={open} title="渠道房型操作" widthClassName="max-w-[480px]" onClose={onClose}>
<div className="space-y-4">
<label className="block space-y-2">
<span className="text-[14px] font-medium text-[#4B4B4B] dark:text-[#9ca3af]"></span>
<select
value={roomType}
onChange={(event) => setRoomType(event.target.value)}
className="h-[48px] w-full rounded-[12px] border border-transparent bg-[#EDECE4] px-4 text-[14px] text-[#171717] outline-none transition-colors focus:border-[#3B6DE8] dark:bg-[#222225] dark:text-[#f3f4f6]"
disabled={loading || submitting}
>
<option value="">{loading ? '正在加载房型...' : '请选择房型'}</option>
{roomList.map((item) => (
<option key={item.id || item.pmsName} value={item.id}>
{item.pmsName || item.id}
</option>
))}
</select>
</label>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<label className="block space-y-2">
<span className="text-[14px] font-medium text-[#4B4B4B] dark:text-[#9ca3af]"></span>
<input
type="date"
value={startTime}
onChange={(event) => setStartTime(event.target.value)}
className="h-[48px] w-full rounded-[12px] border border-transparent bg-[#EDECE4] px-4 text-[14px] text-[#171717] outline-none transition-colors focus:border-[#3B6DE8] dark:bg-[#222225] dark:text-[#f3f4f6]"
disabled={submitting}
/>
</label>
<label className="block space-y-2">
<span className="text-[14px] font-medium text-[#4B4B4B] dark:text-[#9ca3af]"></span>
<input
type="date"
value={endTime}
onChange={(event) => setEndTime(event.target.value)}
className="h-[48px] w-full rounded-[12px] border border-transparent bg-[#EDECE4] px-4 text-[14px] text-[#171717] outline-none transition-colors focus:border-[#3B6DE8] dark:bg-[#222225] dark:text-[#f3f4f6]"
disabled={submitting}
/>
</label>
</div>
<label className="block space-y-2">
<span className="text-[14px] font-medium text-[#4B4B4B] dark:text-[#9ca3af]"></span>
<select
value={operation}
onChange={(event) => setOperation(event.target.value as 'open' | 'close' | '')}
className="h-[48px] w-full rounded-[12px] border border-transparent bg-[#EDECE4] px-4 text-[14px] text-[#171717] outline-none transition-colors focus:border-[#3B6DE8] dark:bg-[#222225] dark:text-[#f3f4f6]"
disabled={submitting}
>
<option value=""></option>
{OPERATION_OPTIONS.map((item) => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
</label>
<div className="rounded-[12px] border border-dashed border-black/5 bg-[#E8E6DE]/30 px-4 py-3 text-[12px] leading-6 text-[#6B7280] dark:border-[#2a2a2d] dark:bg-[#222225]/50 dark:text-gray-400">
沿 `typeMapping` `dyHotSrpingName / dyHotSpringName`
</div>
{error ? (
<div className="rounded-[12px] border border-[#f2c7cd] bg-[#fff5f6] px-4 py-3 text-[13px] text-[#c24150] dark:border-[#4b2229] dark:bg-[#2b1c1f] dark:text-[#ffb4bf]">
{error}
</div>
) : null}
<div className="flex justify-end gap-3 pt-2">
<button
type="button"
className="h-[40px] rounded-full bg-[#EDECE4] px-6 text-[13px] font-semibold text-[#4B4B4B] transition-colors hover:bg-[#E5E4DC] dark:bg-[#222225] dark:text-gray-200"
onClick={onClose}
disabled={submitting}
>
</button>
<button
type="button"
className="h-[40px] rounded-full bg-[#2B7FFF] px-6 text-[13px] font-semibold text-white transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => {
void handleConfirm();
}}
disabled={loading || submitting}
>
{submitting ? '执行中...' : '确认'}
</button>
</div>
</div>
</DialogSurface>
);
}