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.
This commit is contained in:
duanshuwen
2026-04-17 07:09:56 +08:00
parent d233b94b2a
commit b1dea9a5c2
68 changed files with 5910 additions and 397 deletions

View File

@@ -0,0 +1,203 @@
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>
);
}