mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-21 18:19:03 +08:00
暂时的Autoscore
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { PlusOutlined, HolderOutlined, DeleteOutlined } from '@ant-design/icons'
|
||||
import { HolderOutlined, DeleteOutlined } from '@ant-design/icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import RuleComponent from './autoScore/ruleComponent'
|
||||
import {
|
||||
@@ -526,14 +526,16 @@ export const AutoScoreManager: React.FC = () => {
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
<RuleComponent />
|
||||
|
||||
{/* <Card
|
||||
style={{ marginBottom: '24px', backgroundColor: 'var(--ss-card-bg)' }}
|
||||
title={t('autoScore.whenTriggered')}
|
||||
>
|
||||
<Space orientation="vertical" style={{ width: '100%' }}>
|
||||
{/* {triggerList.map((trigger, index) => renderTriggerItem(trigger, index))}
|
||||
*/}{' '}
|
||||
<RuleComponent />
|
||||
{triggerList.map((trigger, index) => renderTriggerItem(trigger, index))}
|
||||
{' '}
|
||||
|
||||
<Button
|
||||
type="dashed"
|
||||
icon={<PlusOutlined />}
|
||||
@@ -543,9 +545,9 @@ export const AutoScoreManager: React.FC = () => {
|
||||
{t('autoScore.addTrigger')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</Card> */}
|
||||
|
||||
<Card
|
||||
{/* <Card
|
||||
style={{ marginBottom: '24px', backgroundColor: 'var(--ss-card-bg)' }}
|
||||
title={t('autoScore.triggeredActions')}
|
||||
>
|
||||
@@ -560,7 +562,7 @@ export const AutoScoreManager: React.FC = () => {
|
||||
{t('autoScore.addAction')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</Card> */}
|
||||
|
||||
<div style={{ marginBottom: '24px', display: 'flex', gap: '12px' }}>
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
|
||||
@@ -15,6 +15,20 @@ interface TagEditorDialogProps {
|
||||
title?: string
|
||||
}
|
||||
|
||||
export async function fetchAllTags() {
|
||||
if (!(window as any).api) return []
|
||||
try {
|
||||
const res = await (window as any).api.tagsGetAll()
|
||||
if (res.success && res.data) {
|
||||
return res.data
|
||||
}
|
||||
return []
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch tags:', e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const TagEditorDialog: React.FC<TagEditorDialogProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
@@ -28,24 +42,11 @@ export const TagEditorDialog: React.FC<TagEditorDialogProps> = ({
|
||||
const [selectedTagIds, setSelectedTagIds] = useState<Set<number>>(new Set(initialTagIds))
|
||||
const [messageApi, contextHolder] = message.useMessage()
|
||||
|
||||
async function fetchAllTags() {
|
||||
if (!(window as any).api) return
|
||||
try {
|
||||
const res = await (window as any).api.tagsGetAll()
|
||||
if (res.success && res.data) {
|
||||
setAllTags(res.data)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch tags:', e)
|
||||
messageApi.error(t('tags.fetchFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setSelectedTagIds(new Set(initialTagIds))
|
||||
setInputValue('')
|
||||
fetchAllTags()
|
||||
fetchAllTags().then((tags) => setAllTags(tags))
|
||||
}
|
||||
}, [visible, initialTagIds])
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
.queryBuilder {
|
||||
--querybuilder-font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
|
||||
sans-serif;
|
||||
--querybuilder-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
||||
--querybuilder-background: var(--ss-card-bg, #ffffff);
|
||||
--querybuilder-color: var(--ss-text-main, rgba(0, 0, 0, 0.85));
|
||||
--querybuilder-border-radius: 6px;
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import type { RuleGroupType, Field, Operator } from 'react-querybuilder'
|
||||
import {defaultOperators} from 'react-querybuilder'
|
||||
import { fetchAllTags } from '../TagEditorDialog'
|
||||
|
||||
import type { FullField, RuleType } from 'react-querybuilder';
|
||||
import { toFullOption } from 'react-querybuilder';
|
||||
const tags = await fetchAllTags()
|
||||
|
||||
export interface AutoScoreTrigger {
|
||||
event: string
|
||||
@@ -12,52 +18,67 @@ export interface AutoScoreAction {
|
||||
reason?: string
|
||||
}
|
||||
|
||||
const musicalInstruments = fetchAllTags().then((tags) => tags.map((tag) => tag.name))
|
||||
|
||||
export interface AutoScoreRuleData {
|
||||
triggers: AutoScoreTrigger[]
|
||||
actions: AutoScoreAction[]
|
||||
}
|
||||
|
||||
// i18n key definitions for triggers and actions
|
||||
export const TRIGGER_TYPE_KEYS = {
|
||||
interval_time_passed: {
|
||||
labelKey: 'autoScore.triggerIntervalTime',
|
||||
descriptionKey: 'triggers.intervalTime.description'
|
||||
},
|
||||
student_has_tag: {
|
||||
labelKey: 'autoScore.triggerStudentTag',
|
||||
descriptionKey: 'triggers.studentTag.description'
|
||||
}
|
||||
}
|
||||
|
||||
export const ACTION_TYPE_KEYS = {
|
||||
add_score: {
|
||||
labelKey: 'autoScore.actionAddScore',
|
||||
descriptionKey: 'actions.addScore.description'
|
||||
},
|
||||
add_tag: {
|
||||
labelKey: 'autoScore.actionAddTag',
|
||||
descriptionKey: 'actions.addTag.description'
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get fields with i18n support
|
||||
export const getFields = (t: (key: string) => string): Field[] => [
|
||||
{
|
||||
name: 'interval_time_passed',
|
||||
label: t('autoScore.triggerIntervalTime'),
|
||||
placeholder: t('autoScore.intervalMinutesPlaceholder')
|
||||
placeholder: t('autoScore.intervalMinutesPlaceholder'),
|
||||
inputType: 'date',
|
||||
datatype: 'timestamp with time zone',
|
||||
},
|
||||
{
|
||||
name: 'student_has_tag',
|
||||
label: t('autoScore.triggerStudentTag'),
|
||||
placeholder: t('autoScore.tagNamesPlaceholder')
|
||||
}
|
||||
placeholder: t('autoScore.tagNamesPlaceholder'),
|
||||
valueEditorType: 'multiselect',
|
||||
values: tags.map((tag) => tag.name),
|
||||
defaultValue: tags.length > 0 ? [tags[0].name] : [],
|
||||
operators: defaultOperators.filter((op) => op.name === '='),
|
||||
},
|
||||
{
|
||||
name: 'tourStops',
|
||||
label: 'Tour stops',
|
||||
|
||||
matchModes: true,
|
||||
subproperties: [
|
||||
{ name: 'date', label: 'Date', inputType: 'date', datatype: 'date' },
|
||||
{ name: 'time', label: 'Time', inputType: 'time', datatype: 'time' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const operators: Operator[] = [
|
||||
/* export const operators: Operator[] = [
|
||||
{ name: '=', label: '=' },
|
||||
{ name: 'contains', label: 'contains' }
|
||||
]
|
||||
{ name: 'contains', label: 'contains' },
|
||||
{ name: 'between', label: 'between' }
|
||||
] */
|
||||
|
||||
export const fields: FullField[] = (
|
||||
[
|
||||
{
|
||||
name: 'interval_time_passed',
|
||||
label: ('autoScore.triggerIntervalTime'),
|
||||
placeholder: ('autoScore.intervalMinutesPlaceholder')
|
||||
},
|
||||
{
|
||||
name: 'student_has_tag',
|
||||
label: ('autoScore.triggerStudentTag'),
|
||||
placeholder: ('autoScore.tagNamesPlaceholder'),
|
||||
valueEditorType: 'multiselect',
|
||||
values: tags.map((tag) => tag.name),
|
||||
defaultValue: 'more_cowbell',
|
||||
operators: defaultOperators.filter((op) => op.name === 'in'),
|
||||
}
|
||||
] satisfies Field[]
|
||||
).map((o) => toFullOption(o));
|
||||
|
||||
export const defaultQuery: RuleGroupType = {
|
||||
combinator: 'and',
|
||||
@@ -117,3 +138,27 @@ export function autoScoreRuleToQuery(ruleData: AutoScoreRuleData): RuleGroupType
|
||||
rules: rules.length > 0 ? rules : defaultQuery.rules
|
||||
}
|
||||
}
|
||||
|
||||
/* import type { Field, FullField, RuleType } from 'react-querybuilder';
|
||||
import { defaultOperators, toFullOption } from 'react-querybuilder';
|
||||
import { fetchAllTags } from '../TagEditorDialog'
|
||||
const tags = await fetchAllTags()
|
||||
|
||||
export const fields: FullField[] = (
|
||||
[
|
||||
{
|
||||
name: 'interval_time_passed',
|
||||
label: ('autoScore.triggerIntervalTime'),
|
||||
placeholder: ('autoScore.intervalMinutesPlaceholder')
|
||||
},
|
||||
{
|
||||
name: 'student_has_tag',
|
||||
label: ('autoScore.triggerStudentTag'),
|
||||
placeholder: ('autoScore.tagNamesPlaceholder'),
|
||||
valueEditorType: 'multiselect',
|
||||
values: tags.map((tag) => tag.name),
|
||||
defaultValue: 'more_cowbell',
|
||||
operators: defaultOperators.filter((op) => op.name === 'in'),
|
||||
}
|
||||
] satisfies Field[]
|
||||
).map((o) => toFullOption(o)); */
|
||||
@@ -1,9 +1,14 @@
|
||||
import { formatQuery, QueryBuilder, RuleGroupType } from 'react-querybuilder'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { QueryBuilderDnD } from '@react-querybuilder/dnd';
|
||||
import * as ReactDnD from 'react-dnd';
|
||||
import { QueryBuilderAntD } from '@react-querybuilder/antd';
|
||||
import * as ReactDndHtml5Backend from 'react-dnd-html5-backend';
|
||||
import * as ReactDndTouchBackend from 'react-dnd-touch-backend';
|
||||
import {
|
||||
getFields,
|
||||
operators,
|
||||
/* operators, */
|
||||
defaultQuery,
|
||||
queryToAutoScoreRule,
|
||||
autoScoreRuleToQuery,
|
||||
@@ -23,9 +28,9 @@ interface RuleComponentProps {
|
||||
export const RuleComponent: React.FC<RuleComponentProps> = ({ initialData, onChange }) => {
|
||||
const { t } = useTranslation()
|
||||
const [query, setQuery] = useState<RuleGroupType>(
|
||||
initialData ? autoScoreRuleToQuery(initialData) : defaultQuery
|
||||
initialData ? autoScoreRuleToQuery(initialData) : defaultQuery
|
||||
)
|
||||
const [actions, setActions] = useState<AutoScoreRuleData['actions']>(initialData?.actions || [])
|
||||
/* const [actions, setActions] = useState<AutoScoreRuleData['actions']>(initialData?.actions || [])
|
||||
|
||||
useEffect(() => {
|
||||
if (onChange) {
|
||||
@@ -37,10 +42,6 @@ export const RuleComponent: React.FC<RuleComponentProps> = ({ initialData, onCha
|
||||
}
|
||||
}, [query, actions])
|
||||
|
||||
const handleQueryChange = (newQuery: RuleGroupType) => {
|
||||
setQuery(newQuery)
|
||||
}
|
||||
|
||||
const handleAddAction = () => {
|
||||
setActions([...actions, { event: 'add_score', value: '5', reason: '' }])
|
||||
}
|
||||
@@ -54,29 +55,34 @@ export const RuleComponent: React.FC<RuleComponentProps> = ({ initialData, onCha
|
||||
newActions[index] = { ...newActions[index], [field]: value }
|
||||
setActions(newActions)
|
||||
}
|
||||
|
||||
*/
|
||||
const handleQueryChange = (newQuery: RuleGroupType) => {
|
||||
setQuery(newQuery)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Card title={t('autoScore.triggerCondition')} style={{ marginBottom: '16px' }}>
|
||||
<QueryBuilder
|
||||
fields={getFields(t)}
|
||||
operators={operators}
|
||||
query={query}
|
||||
onQueryChange={handleQueryChange}
|
||||
/>
|
||||
<Card title={t('autoScore.triggerCondition')} style={{ marginBottom: '24px', backgroundColor: 'var(--ss-card-bg)' }}>
|
||||
<QueryBuilderDnD dnd={{ ...ReactDnD, ...ReactDndHtml5Backend, ...ReactDndTouchBackend }}>
|
||||
<QueryBuilderAntD>
|
||||
<QueryBuilder
|
||||
fields={getFields(t)}
|
||||
/* operators={operators} */
|
||||
query={query}
|
||||
/* onQueryChange={handleQueryChange} */
|
||||
onQueryChange={handleQueryChange} />
|
||||
</QueryBuilderAntD>
|
||||
</QueryBuilderDnD>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
<pre style={{ fontSize: '12px', color: '#999' }}>{formatQuery(query, 'json')}</pre>
|
||||
<pre style={{ fontSize: '12px', color: '#999' }}>{ formatQuery(query, 'json') }</pre>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title={t('autoScore.executeAction')}>
|
||||
{/* <Card title={t('autoScore.executeAction')} style={{ marginBottom: '24px', backgroundColor: 'var(--ss-card-bg)' }}>
|
||||
<ActionComponent
|
||||
actions={actions}
|
||||
onAdd={handleAddAction}
|
||||
onRemove={handleRemoveAction}
|
||||
onChange={handleActionChange}
|
||||
/>
|
||||
</Card>
|
||||
onChange={handleActionChange} />
|
||||
</Card> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user