<script setup lang="ts">
|
import { DeleteOutlined } from '@ant-design/icons-vue';
|
import { computed, ref } from 'vue';
|
|
import { parseTag } from '../domain/tags';
|
import type { ControlType } from '../domain/types';
|
import type { OfficeContentControl } from '../onlyoffice/types';
|
import type { RuntimeStatus } from '../stores/annotation';
|
|
const props = defineProps<{
|
controls: OfficeContentControl[];
|
deleteDisabled?: boolean;
|
disabled?: boolean;
|
status: RuntimeStatus;
|
}>();
|
|
const emit = defineEmits<{
|
delete: [internalId: string];
|
refresh: [];
|
select: [internalId: string];
|
}>();
|
|
const controlTypeLabels: Record<ControlType, string> = {
|
text: '单行文本',
|
dropdown: '下拉选择',
|
checkbox: '复选框',
|
date: '日期',
|
multiline: '多行文本',
|
};
|
|
const parsedControls = computed(() => props.controls.map((control) => ({ control, tag: parseTag(control.Tag) })));
|
const searchKeyword = ref('');
|
const filteredControls = computed(() => {
|
const keyword = searchKeyword.value.trim().toLocaleLowerCase();
|
if (!keyword) return parsedControls.value;
|
|
return parsedControls.value.filter(({ control, tag }) =>
|
[control.Alias, control.Tag, metadata(control, tag)].some((value) => (value || '').toLocaleLowerCase().includes(keyword)),
|
);
|
});
|
const singleCount = computed(() => parsedControls.value.filter(({ tag }) => tag?.type === 'single').length);
|
const repeatCount = computed(() => parsedControls.value.filter(({ tag }) => tag?.type === 'repeat').length);
|
const refreshDisabled = computed(() => props.disabled || props.status === 'initializing' || props.status === 'working');
|
|
function metadata(control: OfficeContentControl, tag: ReturnType<typeof parseTag>): string {
|
if (!tag) return '非 ELN 内容控件';
|
const typeLabel = control.ControlType ? controlTypeLabels[control.ControlType] : '未知类型';
|
if (tag.type === 'single') return `${tag.fieldCode} · ${typeLabel}`;
|
return `重复表格 · ${tag.groupCode} · ${tag.rowId} · ${tag.fieldCode} · ${typeLabel}`;
|
}
|
|
function itemDisabled(control: OfficeContentControl): boolean {
|
return Boolean(props.disabled || props.status !== 'ready' || !control.InternalId);
|
}
|
|
function deleteDisabled(control: OfficeContentControl): boolean {
|
return Boolean(props.deleteDisabled || props.disabled || props.status !== 'ready' || !control.InternalId);
|
}
|
|
function select(control: OfficeContentControl): void {
|
if (!itemDisabled(control) && control.InternalId) emit('select', control.InternalId);
|
}
|
</script>
|
|
<template>
|
<section class="document-fields" aria-labelledby="document-fields-title">
|
<div class="section-heading">
|
<div>
|
<h2 id="document-fields-title">文档字段</h2>
|
<p data-test="control-summary">控件 {{ controls.length }},普通 {{ singleCount }},表格 {{ repeatCount }}</p>
|
</div>
|
<a-button aria-label="刷新内容控件" data-test="refresh-controls" :disabled="refreshDisabled" size="small" title="刷新内容控件" @click="emit('refresh')">
|
刷新
|
</a-button>
|
</div>
|
|
<a-input-search v-model:value="searchKeyword" aria-label="搜索文档字段" class="field-search" data-test="field-search" placeholder="搜索文档字段" />
|
|
<p v-if="status === 'ready' && controls.length === 0" class="empty-controls" data-test="empty-controls">当前文档没有内容控件</p>
|
<p v-else-if="controls.length > 0 && filteredControls.length === 0" class="empty-controls" data-test="empty-search-result">没有匹配的文档字段</p>
|
<ul v-else class="control-list" data-test="control-list" role="list">
|
<li v-for="({ control, tag }, index) in filteredControls" :key="control.InternalId || `${control.Tag}-${index}`" class="control-row">
|
<button class="control-item" data-test="control-item" :disabled="itemDisabled(control)" type="button" @click="select(control)">
|
<span class="control-alias">{{ control.Alias || `未命名字段 ${index + 1}` }}</span>
|
<span class="control-tag">{{ control.Tag || '无 Tag' }}</span>
|
<span class="control-metadata" data-test="control-metadata">{{ metadata(control, tag) }}</span>
|
</button>
|
<a-popconfirm
|
v-if="tag"
|
cancel-text="取消"
|
ok-text="解除"
|
title="解除该字段标注并保留内容?"
|
@confirm="control.InternalId && emit('delete', control.InternalId)"
|
>
|
<a-button
|
:aria-label="`解除标注:${control.Alias || control.Tag || `未命名字段 ${index + 1}`}`"
|
class="delete-control"
|
danger
|
data-test="delete-control"
|
:disabled="deleteDisabled(control)"
|
size="small"
|
title="解除标注"
|
type="text"
|
@click.stop
|
>
|
<DeleteOutlined />
|
</a-button>
|
</a-popconfirm>
|
</li>
|
</ul>
|
</section>
|
</template>
|