告警工单初始化

This commit is contained in:
mmc
2026-07-13 16:25:44 +08:00
parent e54dd4047a
commit 2b851831da
6 changed files with 245 additions and 110 deletions

View File

@@ -37,7 +37,7 @@ export function updateWorkOrder(data) {
export function dispatchWorkOrder(data) {
return request({
url: `/iot/ioTworkOrder/dispat`,
url: `/iot/ioTworkOrder/dispatch`,
method: 'post',
data
});
@@ -45,10 +45,11 @@ export function dispatchWorkOrder(data) {
//开始执行工单
export function startWorkOrder(id) {
export function startWorkOrder(data) {
return request({
url: `/iot/ioTworkOrder/start/${id}`,
method: 'put',
url: `/iot/ioTworkOrder/start`,
method: 'post',
data
});
}

View File

@@ -30,7 +30,7 @@ import {
} from '../../api/systemSetting';
import { useSelector } from 'react-redux';
import { RootState } from '@/src/store';
import utils, { workOrderTypeOptions, getWorkOrderTypeText, errorLevelOptions, LEVEL_TAG_COLOR } from '@/lib/utils';
import utils, { errorSourceOptions, getErrorSourceText } from '@/lib/utils';
const { Option } = Select;
@@ -121,6 +121,7 @@ const AlarmModelManagement: React.FC = () => {
form.setFieldsValue({
errorCode: record.errorCode,
modelId: record.modelId,
sourceType: record.sourceType,
autoGenerateOrder: record.autoGenerateOrder
});
setModalVisible(true);
@@ -183,7 +184,8 @@ const AlarmModelManagement: React.FC = () => {
// 后端要求字段
errorCode: values.errorCode,
modelId: values.modelId,
autoGenerateOrder: values.autoGenerateOrder ?? false
autoGenerateOrder: values.autoGenerateOrder ?? false,
sourceType: values.sourceType ?? 1
};
const res = await saveAlarmOrderConfigApi(submitParams);
if (res.code === 200 && res.data === true) {
@@ -200,6 +202,12 @@ const AlarmModelManagement: React.FC = () => {
// 表格列配置
const columns = [
{ title: '错误编码', dataIndex: 'errorCode', key: 'errorCode' },
{
title: '错误来源',
dataIndex: 'sourceType',
key: 'sourceType',
render: (val: string) => getErrorSourceText(val)
},
{
title: '关联工单模型',
dataIndex: 'modelId',
@@ -284,6 +292,15 @@ const AlarmModelManagement: React.FC = () => {
<Select placeholder="请选择工单模型" options={modelOptions} allowClear />
</Form.Item>
<Form.Item
label="错误来源"
name="sourceType"
rules={[{ required: true, message: '请选择错误来源' }]}
>
<Select placeholder="请选择错误来源" options={errorSourceOptions} allowClear />
</Form.Item>
{/* 是否自动生成工单 */}
<Form.Item
label="自动生成工单"

View File

@@ -7,10 +7,11 @@ import {
BellOutlined, ClockCircleOutlined, CheckCircleOutlined, ExclamationCircleOutlined,
SearchOutlined, ReloadOutlined, FileTextOutlined, NotificationOutlined,
EyeOutlined,
EditOutlined
EditOutlined,
ExclamationOutlined
} from '@ant-design/icons';
import dayjs from 'dayjs';
import { exportTable } from "../../lib/utils.js"
import { errorLevelOptions, exportTable } from "../../lib/utils.js"
// 接口引入
import {
@@ -24,14 +25,12 @@ import {
import { UniversalBarChart, UniversalLineChart, UniversalPieChart } from '../recharts.jsx';
import { addWorkOrder } from '@/api/workOrder.js';
import { alarmLevelConfig, alarmTypeMap, handleStatusMap } from '../../constants.js';
import { useSelector } from 'react-redux';
import { RootState } from '@/src/store/index.js';
const { RangePicker } = DatePicker;
const { Text } = Typography;
const { Option } = Select;
// ====================== 你提供的枚举 ======================
// 颜色映射(从枚举自动生成)
const levelColor = Object.keys(alarmLevelConfig).reduce((map, key) => {
map[key] = alarmLevelConfig[key].color;
return map;
@@ -42,6 +41,70 @@ const levelText = Object.keys(alarmLevelConfig).reduce((map, key) => {
return map;
}, {});
// 级别颜色配置
const levelColorMap = {
ERROR: '#FF4D4F', // 错误 红
WARNING: '#FA8C16', // 警告 橙
INFO: '#1890FF', // 信息 蓝
};
// 快速根据 value 获取 label
const getLevelLabel = (val) => {
const item = errorLevelOptions.find(opt => opt.value === val);
return item?.label || '未知';
};
/**
* 通用告警级别标签组件
* @param level 接口返回字符串 INFO / WARNING / ERROR
* @param size small表格 | large详情弹窗
*/
const AlarmLevelTag = ({ level, size = 'small' }) => {
const color = levelColorMap[level] || '#86909C';
const text = getLevelLabel(level);
// 详情大标签
if (size === 'large') {
return (
<Tag
color={color}
style={{
border: 'none',
color: '#fff',
padding: '4px 10px',
fontSize: 14,
borderRadius: 4,
display: 'inline-flex',
alignItems: 'center',
gap: 6
}}
>
{text}
</Tag>
);
}
// 表格小标签
return (
<Tag
color={color}
style={{
border: 'none',
color: '#fff',
padding: '2px 6px',
fontSize: 12,
borderRadius: 2,
display: 'inline-flex',
alignItems: 'center',
gap: 4
}}
>
{text}
</Tag>
);
};
export default function AlarmCenterPage() {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
@@ -55,6 +118,8 @@ export default function AlarmCenterPage() {
const [messageApi, contextHolder] = message.useMessage();
const [todayData, setTodayData] = useState({});
const [yesterdayData, setYesterdayData] = useState({});
const { stationId } = useSelector((state: RootState) => state.station);
const { userInfo } = useSelector((state: RootState) => state.user);
const [editModalVisible, setEditModalVisible] = useState(false);
const [editForm] = Form.useForm();
@@ -86,8 +151,8 @@ export default function AlarmCenterPage() {
const fetchList = async () => {
setLoading(true);
try {
const res = await getAlarmList({ pageNum, pageSize, ...queryParams });
if (res.code === 0) {
const res = await getAlarmList({ pageNum, pageSize, ...queryParams, siteId: stationId });
if (res.code === 200) {
setAlarmList(res.rows || []);
setTotal(res.total || 0);
}
@@ -189,7 +254,7 @@ export default function AlarmCenterPage() {
fetchList();
}, [pageNum]);
}, [pageNum, stationId]);
useEffect(() => {
fetchTodayAndYesterdayStat();
fetchStat();
@@ -201,7 +266,6 @@ export default function AlarmCenterPage() {
{
title: '告警编号',
dataIndex: 'alarmNo',
width: 180,
render: (text) => <Text style={{ color: '#165DFF', fontWeight: 500 }}>{text}</Text>
},
{
@@ -218,7 +282,7 @@ export default function AlarmCenterPage() {
{
title: '设备名称',
dataIndex: 'deviceName',
width: 160
width: 280
},
{
title: '告警类型',
@@ -230,11 +294,8 @@ export default function AlarmCenterPage() {
title: '告警级别',
dataIndex: 'alarmLevel',
width: 100,
render: (l) => (
<Tag color={levelColor[l]} style={{ borderRadius: 2, border: 'none', color: '#fff' }}>
{levelText[l]}
</Tag>
)
render: (l) => <AlarmLevelTag level={l} size="small" />
},
{
title: '告警时间',
@@ -379,10 +440,9 @@ export default function AlarmCenterPage() {
priorityLevel: currentAlarm.alarmLevel === 1 ? 1 : 2, // 严重=高,其他=中
deviceId: currentAlarm.deviceId || '',
deviceName: currentAlarm.deviceName || '',
deviceType: '', // 没有就传空
siteId: '',
siteName: '',
orgId: '',
deviceType: currentAlarm.deviceType, // 没有就传空
siteId: stationId,
orgId: userInfo?.orgId || '',
assigneeId: '',
assigneeName: '',
collaboratorIds: '',
@@ -752,7 +812,7 @@ export default function AlarmCenterPage() {
title="告警详情"
open={drawerVisible}
onClose={() => setDrawerVisible(false)}
width={420}
width={480}
destroyOnClose
>
<Spin spinning={detailLoading}>
@@ -767,9 +827,7 @@ export default function AlarmCenterPage() {
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span style={{ fontSize: 16, fontWeight: 700 }}>{currentAlarm.alarmTitle}</span>
<Tag color={levelColor[currentAlarm.alarmLevel]} style={{ borderRadius: 2, border: 'none', color: '#fff' }}>
{levelText[currentAlarm.alarmLevel]}
</Tag>
</div>
<div style={{ fontSize: 12, color: '#86909C' }}>告警编号:{currentAlarm.alarmNo}</div>
</div>
@@ -795,7 +853,6 @@ export default function AlarmCenterPage() {
return <Tag color={color}>{handleStatusMap[status] || '-'}</Tag>;
})()}
</Descriptions.Item>
<Descriptions.Item label="负责人">{currentAlarm.handleUserName || '-'}</Descriptions.Item>
</Descriptions>
{/* 告警描述 */}
@@ -902,11 +959,8 @@ export default function AlarmCenterPage() {
name="alarmLevel"
rules={[{ required: true, message: '请选择告警级别' }]}
>
<Select>
<Option value={1}>严重</Option>
<Option value={2}>重要</Option>
<Option value={3}>一般</Option>
<Option value={4}>提示</Option>
<Select options={errorLevelOptions}>
</Select>
</Form.Item>

View File

@@ -237,12 +237,28 @@ export function dataUrlToFile(dataUrl, fileName) {
// 错误来源
export const errorSourceOptions = [
{ label: '无人机', value: 'UAV' },
{ label: '割草机', value: 'MOWER' },
{ label: '其他', value: 'OTHERS' },
export const ERROR_SOURCE = {
UAV: 'UAV',
MOWER: 'MOWER',
OTHERS: 'OTHERS'
} as const;
];
export type ErrorSourceType = typeof ERROR_SOURCE[keyof typeof ERROR_SOURCE];
export const errorSourceOptions: Array<{
value: ErrorSourceType;
label: string;
}> = [
{ label: '无人机', value: ERROR_SOURCE.UAV },
{ label: '割草机', value: ERROR_SOURCE.MOWER },
{ label: '其他', value: ERROR_SOURCE.OTHERS },
];
export const getErrorSourceText = (source?: ErrorSourceType | string): string => {
if (!source) return "";
const target = errorSourceOptions.find((item) => item.value === source);
return target?.label ?? String(source);
};
// 错误等级
export const errorLevelOptions = [
@@ -296,9 +312,9 @@ export const workOrderTypeOptions: Array<{
{ value: WORK_ORDER_TYPE.UAV_ERROR, label: "无人机故障" },
{ value: WORK_ORDER_TYPE.INSPECTION_TASK, label: "巡检" },
{ value: WORK_ORDER_TYPE.COMPONENT_DEFECT, label: "光伏组件故障" },
{ value: WORK_ORDER_TYPE.CLEAN_TASK, label: "清洗任务" },
{ value: WORK_ORDER_TYPE.MAINTAIN_TASK, label: "维护任务" },
{ value: WORK_ORDER_TYPE.REPAIR_TASK, label: "维修任务" },
{ value: WORK_ORDER_TYPE.CLEAN_TASK, label: "清洗" },
{ value: WORK_ORDER_TYPE.MAINTAIN_TASK, label: "维护" },
{ value: WORK_ORDER_TYPE.REPAIR_TASK, label: "维修" },
{ value: WORK_ORDER_TYPE.OTHER, label: "其他" },
];

View File

@@ -33,18 +33,19 @@ import {
getWorkOrderApi,
getWorkOrderDetailApi,
updateWorkOrder,
dispatchWorkOrder,
startWorkOrder,
completeWorkOrder,
suspendWorkOrder,
deleteWorkOrder
} from '../api/workOrder';
import { getSiteDevice } from '../api/device';
import { UniversalLineChart } from '../components/recharts';
import { orderTypeConfig, sourceTypeMap } from '../constants';
import { useSelector } from 'react-redux';
import { RootState } from '../store';
import { getUser } from '../api/user';
import utils from '../lib/utils';
import utils, { getWorkOrderTypeText, workOrderTypeOptions } from '../lib/utils';
const { Option } = Select;
const { Search } = Input;
@@ -78,6 +79,7 @@ const WorkOrderPage = () => {
const [workOrderTotal, setWorkOrderTotal] = useState(0);
const [currentDetail, setCurrentDetail] = useState(null);
const [userList, setUserList] = useState([]);
const [deviceList, setDeviceList] = useState([]);
// 从 redux 获取用户信息
const { userInfo } = useSelector((state: RootState) => state.user);
@@ -108,6 +110,23 @@ const WorkOrderPage = () => {
}
};
// 获取设备列表
const fetchDeviceList = async () => {
try {
const res: any = await getSiteDevice({
siteId: userInfo?.siteId,
pageNum: 1,
pageSize: 10000,
orgId: userInfo?.orgId
});
if (res.code == 200) {
setDeviceList(res.rows || []);
}
} catch (error) {
console.error('获取设备列表失败', error);
}
};
// 统计数据
const stats = [
{ title: '今日新增工单', value: 18, subValue: '+3', subText: '较昨日', icon: <ClipboardList size={24} color="#1890ff" />, bg: '#e6f7ff' },
@@ -241,20 +260,24 @@ const WorkOrderPage = () => {
setEditModalVisible(true);
};
// 打开派发工单弹窗
const openDispatchModal = () => {
if (!selectedRowKey) return messageApi.warning('请先选择一条工单');
if (!isAdmin) return messageApi.error('只有管理员才可以派发工单');
// 打开执行工单弹窗
const openExecuteModal = () => {
if (selectedRowKeys.length > 0) {
// 批量执行
form.setFieldsValue({
orderIds: selectedRowKeys
});
} else if (selectedRowKey) {
// 单个执行
form.setFieldsValue({
orderIds: [selectedRowKey]
});
} else {
return messageApi.warning('请先选择一条工单');
}
fetchUserList();
form.setFieldsValue({
orderId: selectedRowKey,
assigneeId: currentDetail?.assigneeId ?? '',
assigneeName: currentDetail?.assigneeName || '',
planStartTime: currentDetail?.planStartTime ? dayjs(currentDetail.planStartTime) : null,
planEndTime: currentDetail?.planEndTime ? dayjs(currentDetail.planEndTime) : null,
deadlineTime: currentDetail?.deadlineTime ? dayjs(currentDetail.deadlineTime) : null,
});
fetchDeviceList();
setDispatchModalVisible(true);
};
@@ -280,25 +303,21 @@ const WorkOrderPage = () => {
}
};
// 提交派发工单
const submitDispatch = async (values) => {
// 提交执行工单
const submitExecute = async (values) => {
try {
// 调用 startWorkOrder 接口,传递 ids 和 选择的设备 id
const params = {
orderId: values.orderId,
assigneeId: values.assigneeId ?? '',
assigneeName: values.assigneeName,
collaboratorIds: '',
collaboratorNames: values.collaboratorNames?.join(',') || '',
planStartTime: values.planStartTime?.format('YYYY-MM-DDTHH:mm:ss') || '',
planEndTime: values.planEndTime?.format('YYYY-MM-DDTHH:mm:ss') || '',
deadlineTime: values.deadlineTime?.format('YYYY-MM-DDTHH:mm:ss') || '',
ids: values.orderIds,
deviceId: values.deviceId
};
await dispatchWorkOrder(params);
messageApi.success('工单派发成功');
await startWorkOrder(params);
messageApi.success('工单执行成功');
setDispatchModalVisible(false);
setSelectedRowKeys([]); // 清空选中
getWorkOrderList();
} catch (error) {
messageApi.error('工单派发失败');
messageApi.error('工单执行失败');
console.error(error);
}
};
@@ -517,20 +536,32 @@ const WorkOrderPage = () => {
<Text strong style={{ fontSize: 15, color: '#1D2129' }}>工单列表</Text>
<Text type="secondary" style={{ fontSize: 12 }}>(共 {workOrderTotal} 条)</Text>
{selectedRowKeys.length > 0 && (
<Popconfirm
title={`确定删除已选中的 ${selectedRowKeys.length} 条工单吗?`}
onConfirm={handleBatchDelete}
>
<Button size="small" danger icon={<DeleteOutlined />}>
批量删除
<>
<Popconfirm
title={`确定删除已选中的 ${selectedRowKeys.length} 条工单吗?`}
onConfirm={handleBatchDelete}
>
<Button size="small" danger icon={<DeleteOutlined />}>
批量删除
</Button>
</Popconfirm>
<Button
type="primary"
size="small"
icon={<PlayCircleFilled />}
onClick={openExecuteModal}
>
批量执行 ({selectedRowKeys.length})
</Button>
</Popconfirm>
</>
)}
</Space>
}
bodyStyle={{ padding: 0 }}
style={{ ...cardStyle, marginBottom: 8 }}
>
<Table
dataSource={workOrderList}
columns={columns}
@@ -565,7 +596,7 @@ const WorkOrderPage = () => {
legend={{ position: 'right', layout: 'vertical' }}
height={260}
/>
<div style={{ position: 'absolute', top: '42%', left: '30%', textAlign: 'center', transform: 'translate(-50%, -50%)' }}>
<div style={{ position: 'absolute', top: '45%', left: '50%', textAlign: 'center', transform: 'translate(-50%, -50%)' }}>
<div style={{ fontSize: 12, color: '#86909C' }}>总数</div>
<div style={{ fontSize: 28, fontWeight: 800, color: '#1D2129' }}>{workOrderTotal}</div>
</div>
@@ -623,14 +654,14 @@ const WorkOrderPage = () => {
<span level={4} style={{ margin: 0, fontSize: 18, fontWeight: 800 }}>{currentDetail?.orderTitle || '暂无标题'}</span>
{currentDetail?.priorityLevel && (
<Tag style={{ color: priorityConfig[currentDetail.priorityLevel]?.color, background: priorityConfig[currentDetail.priorityLevel]?.bg, border: `1px solid ${priorityConfig[currentDetail.priorityLevel]?.color}`, borderRadius: 4, fontWeight: 600 }}>
{priorityConfig[currentDetail.priorityLevel].label}优先级
{currentDetail.priorityLevel}
</Tag>
)}
</div>
<Row gutter={[16, 16]} style={{ background: '#f8fafc', padding: 16, borderRadius: 12, marginBottom: 20 }}>
<Col span={12}><span style={{ fontSize: 12 }}>来源:</span><span style={{ fontSize: 12, fontWeight: 500 }}>{sourceTypeMap[currentDetail?.sourceType] || '--'}</span></Col>
<Col span={12}><span style={{ fontSize: 12 }}>工单类型:</span><span style={{ fontSize: 12, fontWeight: 500 }}>{orderTypeConfig[currentDetail?.orderType] || '--'}</span></Col>
<Col span={12}><span style={{ fontSize: 12 }}>工单类型:</span><span style={{ fontSize: 12, fontWeight: 500 }}>{getWorkOrderTypeText(currentDetail?.orderType)}</span></Col>
<Col span={12}><span style={{ fontSize: 12 }}>负责人:</span><span style={{ fontSize: 12, fontWeight: 500 }}>{currentDetail?.assigneeName || '--'}</span></Col>
<Col span={12}><span style={{ fontSize: 12 }}>协同人员:</span><span style={{ fontSize: 12, fontWeight: 500 }}>{currentDetail?.collaboratorNames || '无'}</span></Col>
<Col span={24}><span style={{ fontSize: 12 }}>计划时间:</span>
@@ -672,7 +703,7 @@ const WorkOrderPage = () => {
}}>
{item.active ? <CheckOutlined /> : item.step}
</div>
<div style={{ fontSize: 11, marginTop: 8, color: item.active ? '#1D2129' : '#86909C', fontWeight: item.active ? 600 : 400 }}>{item.label}</div>
<div style={{ fontSize: 11, marginTop: 8, color: item.active ? '#1D2129' : '#86909C', fontWeight: item.active ? 600 : 400 }}>{item?.label}</div>
</div>
))}
</div>
@@ -683,12 +714,11 @@ const WorkOrderPage = () => {
<Button
type="primary"
block
onClick={openDispatchModal}
onClick={openExecuteModal}
style={{ borderRadius: 8, height: 36, fontWeight: 600 }}
disabled={!isAdmin}
title={!isAdmin ? "只有管理员可以派发工单" : ""}
icon={<PlayCircleFilled />}
>
派发工单
执行工单
</Button>
</Col>
<Col span={6}><Button icon={<EditOutlined />} block onClick={openEditModal} style={{ borderRadius: 8, height: 36, fontWeight: 600 }}>修改</Button></Col>
@@ -781,9 +811,8 @@ const WorkOrderPage = () => {
<Input placeholder="请输入工单标题" />
</FormItem>
<FormItem name="orderType" label="工单类型" rules={[{ required: true, message: '请选择工单类型' }]}>
<Select placeholder="请选择工单类型">
<Option value={1}>巡检</Option><Option value={2}>检修</Option><Option value={3}>清洗</Option><Option value={4}>维修</Option><Option value={5}>运维</Option>
</Select>
<Select placeholder="请选择工单类型" options={workOrderTypeOptions} />
</FormItem>
<FormItem name="priorityLevel" label="优先级" rules={[{ required: true, message: '请选择优先级' }]}>
<Select placeholder="请选择优先级">
@@ -800,30 +829,49 @@ const WorkOrderPage = () => {
</Form>
</Modal>
{/* 派发工单弹窗 */}
<Modal title="派发工单" open={dispatchModalVisible} onCancel={() => setDispatchModalVisible(false)} footer={null} width={550}>
<Form form={form} layout="vertical" onFinish={submitDispatch}>
<FormItem name="orderId" label="工单ID" hidden><Input /></FormItem>
<FormItem name="assigneeName" label="负责人姓名" hidden><Input /></FormItem>
<FormItem name="assigneeId" label="负责人" rules={[{ required: true, message: '请选择负责人' }]}>
{/* 执行工单弹窗 */}
<Modal title="执行工单" open={dispatchModalVisible} onCancel={() => setDispatchModalVisible(false)} footer={null} width={550}>
<Form form={form} layout="vertical" onFinish={submitExecute}>
<FormItem name="orderIds" label="工单ID" hidden><Input /></FormItem>
<FormItem label="选中工单">
<div style={{
padding: 8,
background: '#f8fafc',
borderRadius: 6,
maxHeight: 150,
overflow: 'auto'
}}>
{selectedRowKeys.length > 0 ? (
workOrderList
.filter(item => selectedRowKeys.includes(item.id))
.map(item => (
<div key={item.id} style={{ marginBottom: 4 }}>
• {item.orderTitle} ({item.orderNo})
</div>
))
) : (
<div>• {currentDetail?.orderTitle} ({currentDetail?.orderNo})</div>
)}
</div>
</FormItem>
<FormItem
name="deviceId"
label="选择执行设备"
rules={[{ required: true, message: '请选择执行设备' }]}
>
<Select
placeholder="请选择负责人"
onChange={(val, option: any) => {
form.setFieldsValue({ assigneeName: option.children });
}}
placeholder="请选择执行设备"
>
{userList.map(user => (
<Option key={user.userId} value={user.userId}>{user.nickName || user.userName}</Option>
{deviceList.map(device => (
<Option key={device.id} value={device.id}>
{device.deviceName} ({device.serialNumber})
</Option>
))}
</Select>
</FormItem>
<FormItem name="collaboratorNames" label="协同人员"><Select mode="tags" placeholder="可输入多个,回车确认" /></FormItem>
<FormItem name="planStartTime" label="计划开始时间"><DatePicker showTime style={{ width: '100%' }} /></FormItem>
<FormItem name="planEndTime" label="计划结束时间"><DatePicker showTime style={{ width: '100%' }} /></FormItem>
<FormItem name="deadlineTime" label="截止时间"><DatePicker showTime style={{ width: '100%' }} /></FormItem>
<FormItem style={{ textAlign: 'right', marginBottom: 0 }}>
<Button onClick={() => setDispatchModalVisible(false)}>取消</Button>
<Button type="primary" htmlType="submit" style={{ marginLeft: 8 }}>确认派发</Button>
<Button type="primary" htmlType="submit" style={{ marginLeft: 8 }}>确认执行</Button>
</FormItem>
</Form>
</Modal>

View File

@@ -18,9 +18,7 @@ const ENV = { DEV: 'development', PROD: 'production' };
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, '.', '');
return {
define: {
'process.env': {}
},
plugins: [
react(),
tailwindcss(),
@@ -83,6 +81,7 @@ export default defineConfig(({ mode }) => {
),
// 环境变量注入
'import.meta.env.VITE_API_BASE_URL': JSON.stringify(env.VITE_API_BASE_URL ?? env.baseURL ?? ''),
'process.env': {}
},
server: {