116 lines
7.6 KiB
HTML
116 lines
7.6 KiB
HTML
@if (enabled() && error()) {
|
||
<div class="extension-notice" role="status">{{ error() }} <button type="button" (click)="refresh()">重试</button></div>
|
||
}
|
||
<ng-template #headerControls>
|
||
@if (data()?.plugins?.length) {
|
||
<nav class="panel-toggles" aria-label="任务扩展面板">
|
||
@for (side of ['left', 'right']; track side) {
|
||
@for (plugin of data()!.plugins; track plugin.plugin_id) {
|
||
@for (panel of plugin.workspace.panels; track panel.id) {
|
||
@if (panel.side === side) {
|
||
<button type="button" class="panel-toggle" [class.active]="opened()[key(plugin, panel)]" [attr.aria-expanded]="!!opened()[key(plugin, panel)]"
|
||
[attr.aria-label]="(opened()[key(plugin, panel)] ? '收起' : '展开') + panel.title"
|
||
[title]="(opened()[key(plugin, panel)] ? '收起' : '展开') + panel.title"
|
||
[attr.aria-controls]="'extension-' + key(plugin, panel)" (click)="toggle(plugin, panel)">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><rect x="2" y="3" width="16" height="14" rx="3" /><path [attr.d]="side === 'left' ? 'M7 3v14' : 'M13 3v14'" /></svg>
|
||
<span class="panel-toggle-label">{{ panel.title }}</span>
|
||
<span class="material-symbols-outlined" aria-hidden="true">{{ (side === 'left') !== !!opened()[key(plugin, panel)] ? 'chevron_left' : 'chevron_right' }}</span>
|
||
</button>
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</nav>
|
||
}
|
||
</ng-template>
|
||
<div class="extension-layout" [class.has-left]="panels('left').length" [class.has-right]="panels('right').length"
|
||
[style.--left-width]="width('left') + 'px'" [style.--right-width]="width('right') + 'px'">
|
||
@if (panels('left').length) {
|
||
<aside class="panel-column left-column" aria-label="任务左侧扩展">
|
||
@for (item of panels('left'); track key(item.plugin, item.panel)) {
|
||
<ng-container *ngTemplateOutlet="panelContent; context: { plugin: item.plugin, panel: item.panel }" />
|
||
}
|
||
</aside>
|
||
}
|
||
<div class="player-content"><ng-content /></div>
|
||
@if (panels('right').length) {
|
||
<aside class="panel-column right-column" aria-label="任务右侧扩展">
|
||
@for (item of panels('right'); track key(item.plugin, item.panel)) {
|
||
<ng-container *ngTemplateOutlet="panelContent; context: { plugin: item.plugin, panel: item.panel }" />
|
||
}
|
||
</aside>
|
||
}
|
||
</div>
|
||
|
||
<ng-template #panelContent let-plugin="plugin" let-panel="panel">
|
||
<section [id]="'extension-' + key(plugin, panel)" [attr.aria-label]="panel.title">
|
||
<header><h3>{{ panel.kind === 'original_case' ? (data()?.case_title || panel.title) : panel.title }}</h3><span class="panel-kind">{{ panel.kind === 'original_case' ? '原始用例' : panel.kind === 'initial_prompt' ? '原始提示词' : '人工填写 · 选填' }}</span></header>
|
||
@if (panel.kind === 'original_case') {
|
||
@if (!data()?.original_case) {
|
||
<p class="muted">此任务未记录原始用例内容;旧任务不会从 AI 提示词推测补写。</p>
|
||
}
|
||
<div class="case-section"><h4>预置条件</h4><app-report-markdown [content]="data()?.original_case?.preconditions || '未填写'" /></div>
|
||
<div class="case-section"><h4>操作步骤</h4><app-report-markdown [content]="data()?.original_case?.steps || '未填写'" /></div>
|
||
<div class="case-section"><h4>预期结果</h4><app-report-markdown [content]="data()?.original_case?.expected || '未填写'" /></div>
|
||
} @else if (panel.kind === 'initial_prompt') {
|
||
@if (data()?.initial_prompt) {
|
||
<app-report-markdown [content]="data()!.initial_prompt" />
|
||
} @else { <p class="muted">该任务未记录初始需求提示词。</p> }
|
||
} @else {
|
||
@if (!data()?.editable) { <p class="muted">任务结束后可填写评审。</p> }
|
||
<fieldset [disabled]="!data()?.editable">
|
||
@for (field of plugin.workspace.fields; track field.id) {
|
||
<div class="field" [class.choice-field]="field.kind === 'choice'" [class.note-field]="field.kind === 'text'">
|
||
<span class="field-label">{{ field.label }}</span>
|
||
@if (field.description) { <span class="muted">{{ field.description }}</span> }
|
||
@if (field.kind === 'choice') {
|
||
<div class="choice-group" role="group" [attr.aria-label]="field.label">
|
||
@for (option of field.options; track option) {
|
||
<button type="button" class="choice-button" [class.selected]="drafts[plugin.plugin_id].values[field.id] === option"
|
||
[class.pass]="option === 'Pass' || option === '通过'" [class.failed]="option === 'Failed' || option === '失败'"
|
||
[attr.aria-pressed]="drafts[plugin.plugin_id].values[field.id] === option" title="再次点击已选项可清除"
|
||
(click)="choose(plugin.plugin_id, field.id, option)">
|
||
@if (option === 'Pass' || option === 'Failed' || option === '通过' || option === '失败') {
|
||
<svg class="choice-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||
<path [attr.d]="option === 'Pass' || option === '通过' ? 'M5 12l4 4L19 6' : 'M6 6l12 12M18 6L6 18'" />
|
||
</svg>
|
||
}
|
||
<span class="choice-label">{{ option }}</span>
|
||
</button>
|
||
}
|
||
</div>
|
||
} @else {
|
||
<textarea rows="1" [attr.aria-label]="field.label" [maxLength]="field.max_length" placeholder="添加备注(选填)"
|
||
[ngModel]="drafts[plugin.plugin_id].values[field.id] || ''" (ngModelChange)="edit(plugin.plugin_id, field.id, $event)"></textarea>
|
||
}
|
||
</div>
|
||
}
|
||
<div class="form-actions">
|
||
<button type="button" class="save" [disabled]="!!saving()" (click)="save(plugin)">{{ saving() === plugin.plugin_id ? '正在保存…' : '保存评审' }}</button>
|
||
<button type="button" class="reload" [disabled]="!!saving()" (click)="reload(plugin)" title="放弃未保存内容,读取最新记录">重新加载</button>
|
||
<span class="muted unsaved">{{ saving() === plugin.plugin_id ? '正在保存…' : drafts[plugin.plugin_id].dirty ? '等待自动保存…' : '修改后自动保存' }}</span>
|
||
</div>
|
||
</fieldset>
|
||
<nav class="review-navigation" aria-label="切换评审任务">
|
||
<div class="navigation-buttons">
|
||
<button type="button" [disabled]="!navigation().previous || !!saving() || moving()" (click)="move('previous')"><span aria-hidden="true">‹</span>上一条</button>
|
||
<span class="review-position">{{ navigation().position }} / {{ navigation().total }}</span>
|
||
<button type="button" [disabled]="!navigation().next || !!saving() || moving()" (click)="move('next')">下一条<span aria-hidden="true">›</span></button>
|
||
</div>
|
||
<span class="muted">{{ navigation().scope === 'batch' ? '同批次已结束任务 · 按执行顺序' : '当前历史列表' }}</span>
|
||
@if (drafts[plugin.plugin_id].dirty) { <span class="muted">有待保存的修改,切换前会先保存。</span> }
|
||
</nav>
|
||
@if (notice()) { <p class="notice" role="status">{{ notice() }}</p> }
|
||
@if (plugin.history.length) {
|
||
<details class="history"><summary>修改记录({{ plugin.history.length }})</summary>
|
||
@for (entry of plugin.history; track entry.revision) {
|
||
<div class="history-entry"><strong>第 {{ entry.revision }} 次 · {{ entry.saved_at * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}</strong>
|
||
@for (field of entry.fields; track field.id) { <p>{{ field.label }}:{{ entry.values[field.id] || '未填写' }}</p> }
|
||
</div>
|
||
}
|
||
</details>
|
||
}
|
||
}
|
||
</section>
|
||
</ng-template>
|