Summary: - Refactor timeline API to read from qc_field_status (SSOT) instead of qc_logs - Add field-issues paginated API with severity/dimension/recordId filters - Add LEFT JOIN field_metadata + qc_event_status for Chinese display names - Implement per-project ChatOrchestrator cache and SessionMemory isolation - Redesign admin IIT config tabs (REDCap -> Fields -> KB -> Rules -> Members) - Add AI-powered QC rule generation (D3 programmatic + D1/D5/D6 LLM-based) - Add clickable warning/critical detail Modal in ReportsPage - Auto-dispatch eQuery after batch QC via DailyQcOrchestrator - Update module status documentation to v3.2 Backend changes: - iitQcCockpitController: rewrite getTimeline from qc_field_status, add getFieldIssues - iitQcCockpitRoutes: add field-issues route - ChatOrchestrator: per-projectId cached instances - SessionMemory: keyed by userId::projectId - WechatCallbackController: resolve projectId from iitUserMapping - iitRuleSuggestionService: dimension-based suggest + generateD3Rules - iitBatchController: call DailyQcOrchestrator after batch QC Frontend changes: - AiStreamPage: adapt to new timeline structure with dimension tags - ReportsPage: clickable stats cards with issue detail Modal - IitProjectDetailPage: reorder tabs, add AI rule generation UI - iitProjectApi: add TimelineIssue, FieldIssueItem types and APIs Status: TypeScript compilation verified, no new lint errors Made-with: Cursor
141 lines
3.3 KiB
TypeScript
141 lines
3.3 KiB
TypeScript
/**
|
|
* 模块权限配置弹窗
|
|
*/
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Modal,
|
|
Checkbox,
|
|
Row,
|
|
Col,
|
|
message,
|
|
Typography,
|
|
Spin,
|
|
Alert,
|
|
} from 'antd';
|
|
import * as userApi from '../api/userApi';
|
|
import type { TenantMembership, ModuleOption } from '../types/user';
|
|
|
|
const { Text } = Typography;
|
|
|
|
interface ModulePermissionModalProps {
|
|
visible: boolean;
|
|
userId: string;
|
|
membership: TenantMembership;
|
|
onClose: () => void;
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
const ModulePermissionModal: React.FC<ModulePermissionModalProps> = ({
|
|
visible,
|
|
userId,
|
|
membership,
|
|
onClose,
|
|
onSuccess,
|
|
}) => {
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [moduleOptions, setModuleOptions] = useState<ModuleOption[]>([]);
|
|
const [selectedModules, setSelectedModules] = useState<string[]>([]);
|
|
|
|
// 加载模块选项
|
|
useEffect(() => {
|
|
if (visible && membership) {
|
|
setLoading(true);
|
|
userApi.getModuleOptions(membership.tenantId)
|
|
.then((options) => {
|
|
setModuleOptions(options);
|
|
// 设置当前已启用的模块
|
|
const enabled = membership.allowedModules
|
|
.filter((m) => m.isEnabled)
|
|
.map((m) => m.code);
|
|
setSelectedModules(enabled);
|
|
})
|
|
.catch(console.error)
|
|
.finally(() => setLoading(false));
|
|
}
|
|
}, [visible, membership]);
|
|
|
|
// 提交
|
|
const handleSubmit = async () => {
|
|
setSubmitting(true);
|
|
try {
|
|
await userApi.updateUserModules(userId, {
|
|
tenantId: membership.tenantId,
|
|
modules: selectedModules,
|
|
});
|
|
message.success('模块权限更新成功');
|
|
onSuccess();
|
|
} catch (error: any) {
|
|
message.error(error.response?.data?.message || '更新失败');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
title={`配置模块权限 - ${membership.tenantName}`}
|
|
open={visible}
|
|
onCancel={onClose}
|
|
onOk={handleSubmit}
|
|
confirmLoading={submitting}
|
|
destroyOnClose
|
|
>
|
|
<Spin spinning={loading}>
|
|
<Alert
|
|
message="模块权限说明"
|
|
description="选择用户在该租户内可以访问的模块。取消所有选择将默认继承租户的全部模块权限。灰色模块表示租户尚未订阅。"
|
|
type="info"
|
|
showIcon
|
|
style={{ marginBottom: 16 }}
|
|
/>
|
|
|
|
{moduleOptions.length > 0 ? (
|
|
<Checkbox.Group
|
|
value={selectedModules}
|
|
onChange={(values) => setSelectedModules(values as string[])}
|
|
style={{ width: '100%' }}
|
|
>
|
|
<Row gutter={[16, 16]}>
|
|
{moduleOptions.map((module) => (
|
|
<Col span={12} key={module.code}>
|
|
<Checkbox value={module.code}>
|
|
{module.name}
|
|
{!module.isSubscribed && (
|
|
<Text type="secondary" style={{ fontSize: 12, marginLeft: 4 }}>
|
|
(未订阅)
|
|
</Text>
|
|
)}
|
|
</Checkbox>
|
|
</Col>
|
|
))}
|
|
</Row>
|
|
</Checkbox.Group>
|
|
) : (
|
|
<Text type="secondary">暂无可用模块</Text>
|
|
)}
|
|
</Spin>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default ModulePermissionModal;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|