暂时的Autoscore

This commit is contained in:
NanGua-QWQ
2026-03-13 22:35:26 +08:00
parent f47b6d483c
commit 77d399ea0f
7 changed files with 294 additions and 85 deletions
@@ -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 -14
View File
@@ -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>
)
}