Commit cd90a7d4 authored by Andy-bubu's avatar Andy-bubu

fix: localize data grid headers

parent 4e87d139
...@@ -136,6 +136,7 @@ Do not respond to this failure by repeatedly increasing cell radii, adding corne ...@@ -136,6 +136,7 @@ Do not respond to this failure by repeatedly increasing cell radii, adding corne
## Text and Cells ## Text and Cells
- DataGrid headers use the Chinese business label from the Subject field definition or view configuration. Never expose `metadata.*` paths, snake_case names, database keys, or other technical identifiers as visible headers. When backend definitions are incomplete, resolve approved Chinese fallbacks in the shared column adapter so main grids and related-record grids remain consistent; do not add page-local string replacements.
- Use the approved five-level list typography contract everywhere: header `12px/500`, ordinary body field `14px/400`, primary field `14px/550`, secondary field `13px/400`, and chip/tag/status text `12px/500`. Use `var(--rc-text-muted)` for headers and secondary fields, `var(--rc-text-secondary)` for ordinary fields, and `var(--rc-text-primary)` for primary fields. - Use the approved five-level list typography contract everywhere: header `12px/500`, ordinary body field `14px/400`, primary field `14px/550`, secondary field `13px/400`, and chip/tag/status text `12px/500`. Use `var(--rc-text-muted)` for headers and secondary fields, `var(--rc-text-secondary)` for ordinary fields, and `var(--rc-text-primary)` for primary fields.
- The shared DataGrid wrapper must own reusable `primary`, `secondary`, `text-pair`, `number`, and `chip` styles. Pages identify field meaning; they must not recreate font size, weight, color, gap, truncation, or tag typography. - The shared DataGrid wrapper must own reusable `primary`, `secondary`, `text-pair`, `number`, and `chip` styles. Pages identify field meaning; they must not recreate font size, weight, color, gap, truncation, or tag typography.
- A primary/secondary pair is a compact vertical stack with a `0.25rem` gap. Primary and secondary lines both remain one line and use Tooltip when truncated unless the migrated legacy comparison/list cell already intentionally allows short wrapping. The primary line carries identity or the main comparison value at `14px/550` and `var(--rc-text-primary)`; the secondary line carries code, department, role, location, related project names, or other context at `13px/400` using the weaker `--rc-data-grid-secondary-color` (`#7A8596` in light theme and `#8B8B95` in dark theme). The two lines must be visually separated enough that they do not read as one bold block, especially in dark mode. If a primary/secondary cell has no click-through detail action, both visible lines must expose their complete raw text through the shared HeroUI Tooltip on Hover/focus; do not hide overflow without an affordance to read the full value. - A primary/secondary pair is a compact vertical stack with a `0.25rem` gap. Primary and secondary lines both remain one line and use Tooltip when truncated unless the migrated legacy comparison/list cell already intentionally allows short wrapping. The primary line carries identity or the main comparison value at `14px/550` and `var(--rc-text-primary)`; the secondary line carries code, department, role, location, related project names, or other context at `13px/400` using the weaker `--rc-data-grid-secondary-color` (`#7A8596` in light theme and `#8B8B95` in dark theme). The two lines must be visually separated enough that they do not read as one bold block, especially in dark mode. If a primary/secondary cell has no click-through detail action, both visible lines must expose their complete raw text through the shared HeroUI Tooltip on Hover/focus; do not hide overflow without an affordance to read the full value.
......
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
## 7. DataGrid 与列表 ## 7. DataGrid 与列表
- 所有业务表格使用 HeroUI Pro DataGrid。当前系统不在 DataGrid 上方重复显示数据集标题:关键词搜索占用原列表标题的左侧位置,业务操作保持同一行右对齐;操作列表头在列内居中,行内图标组贴近操作列右边缘,删除按钮在图标组内最右;没有搜索和操作时不保留空标题行。DataGrid 自己拥有 20px 表面,Pagination 紧随其后,无外层大 Card 和分隔线。 - 所有业务表格使用 HeroUI Pro DataGrid。当前系统不在 DataGrid 上方重复显示数据集标题:关键词搜索占用原列表标题的左侧位置,业务操作保持同一行右对齐;操作列表头在列内居中,行内图标组贴近操作列右边缘,删除按钮在图标组内最右;没有搜索和操作时不保留空标题行。DataGrid 自己拥有 20px 表面,Pagination 紧随其后,无外层大 Card 和分隔线。
- DataGrid 表头必须使用 Subject 字段或视图配置中的中文业务名称;`metadata.*`、snake_case、数据库键及其他技术标识不得直接展示。缺失字段定义时,由共享列适配层统一解析中文名称,禁止在页面组件内零散替换。
- 当前系统所有 DataGrid 行操作统一使用 32px 正圆图标按钮:普通操作使用 `var(--default)` 中性浅灰背景,Hover 使用 `var(--default-hover)`;删除等危险操作保留浅红背景和危险色图标。所有图标保持 16px 居中、Tooltip 和可访问名称,禁用态降低透明度。 - 当前系统所有 DataGrid 行操作统一使用 32px 正圆图标按钮:普通操作使用 `var(--default)` 中性浅灰背景,Hover 使用 `var(--default-hover)`;删除等危险操作保留浅红背景和危险色图标。所有图标保持 16px 居中、Tooltip 和可访问名称,禁用态降低透明度。
- 详情弹窗内的单列关联记录属于辅助长文本列表,不套用主清单首列强调字重:正文固定为 14px/400、48px 行高、单行省略,并通过 Tooltip 快速预览完整文本。首列必须可点击并打开对应关联记录的只读详情弹窗,关闭后返回原记录详情。关联 DataGrid 不建立独立的纵向滚动区,由详情弹窗正文统一负责纵向滚动;单列表不产生横向滚动,多列关联记录只在真实宽度溢出时由 DataGrid 负责横向滚动。 - 详情弹窗内的单列关联记录属于辅助长文本列表,不套用主清单首列强调字重:正文固定为 14px/400、48px 行高、单行省略,并通过 Tooltip 快速预览完整文本。首列必须可点击并打开对应关联记录的只读详情弹窗,关闭后返回原记录详情。关联 DataGrid 不建立独立的纵向滚动区,由详情弹窗正文统一负责纵向滚动;单列表不产生横向滚动,多列关联记录只在真实宽度溢出时由 DataGrid 负责横向滚动。
- 所有详情编辑控件使用 HeroUI/HeroUI Pro:日期和日期时间使用 `FormDatePicker`,附件使用 `DropZone`,布尔值与单/多选使用 HeroUI Checkbox/Radio,远程人员选择使用 HeroUI ComboBox。禁止显示原生 date/file/checkbox/radio 控件;关联详情在同一个详情弹窗内前进并通过“返回上一级”恢复父记录,禁止叠加第二层详情 Modal。 - 所有详情编辑控件使用 HeroUI/HeroUI Pro:日期和日期时间使用 `FormDatePicker`,附件使用 `DropZone`,布尔值与单/多选使用 HeroUI Checkbox/Radio,远程人员选择使用 HeroUI ComboBox。禁止显示原生 date/file/checkbox/radio 控件;关联详情在同一个详情弹窗内前进并通过“返回上一级”恢复父记录,禁止叠加第二层详情 Modal。
......
...@@ -6,6 +6,7 @@ import { getSubjectView, loadSubject, queryRecords } from '@/services/DataServic ...@@ -6,6 +6,7 @@ import { getSubjectView, loadSubject, queryRecords } from '@/services/DataServic
import { import {
buildViewColumns, buildViewColumns,
formatFieldValue, formatFieldValue,
getFieldLabel,
getFieldValue, getFieldValue,
getReferenceSubjectName, getReferenceSubjectName,
normalizeRecordId, normalizeRecordId,
...@@ -50,11 +51,15 @@ function inferColumnsFromRecords(records) { ...@@ -50,11 +51,15 @@ function inferColumnsFromRecords(records) {
}); });
}); });
const metadataColumns = metadataKeys.map((key) => ({ const metadataColumns = metadataKeys.map((key) => {
field: `metadata.${key}`, const field = `metadata.${key}`;
label: key, const label = getFieldLabel(field);
fieldDef: { name: key, label: key, type: 'text' }, return {
})); field,
label,
fieldDef: { name: key, label, type: 'text' },
};
});
return metadataColumns.length > 0 ? metadataColumns : [TITLE_COLUMN]; return metadataColumns.length > 0 ? metadataColumns : [TITLE_COLUMN];
} }
...@@ -79,11 +84,14 @@ function buildColumns(subject, records, views) { ...@@ -79,11 +84,14 @@ function buildColumns(subject, records, views) {
); );
const configuredColumns = gridView ? buildViewColumns(gridView, subject) : []; const configuredColumns = gridView ? buildViewColumns(gridView, subject) : [];
const fallbackColumns = subject?.fields const fallbackColumns = subject?.fields
? [TITLE_COLUMN, ...toArray(subject.fields).map((field) => ({ ? [TITLE_COLUMN, ...toArray(subject.fields).map((field) => {
field: `metadata.${field.name}`, const fieldPath = `metadata.${field.name}`;
label: field.label || field.name, return {
fieldDef: field, field: fieldPath,
}))] label: getFieldLabel(fieldPath, field),
fieldDef: field,
};
})]
: inferColumnsFromRecords(records); : inferColumnsFromRecords(records);
const candidates = configuredColumns.length > 0 ? configuredColumns : fallbackColumns; const candidates = configuredColumns.length > 0 ? configuredColumns : fallbackColumns;
const visibleColumns = candidates const visibleColumns = candidates
......
...@@ -22,6 +22,29 @@ const REFERENCE_FIELD_TYPES = new Set([ ...@@ -22,6 +22,29 @@ const REFERENCE_FIELD_TYPES = new Set([
'category', 'category',
]); ]);
const COMMON_FIELD_LABELS = {
title: '标题',
archive_status: '归档状态',
year: '年度',
actual_filling_department: '实际填报部门',
department: '部门',
editor: '填报人',
informant: '填报人',
type: '类型',
entry_item: '填报条目',
project_outcome: '项目成果',
template: '模板',
reference_integrity_practice_report: '廉洁从业报告',
reference_self_inspection_submission_form: '自查报送表',
reference_statistical_table: '统计表',
is_locked: '锁定状态',
progress_id_editor: '填报进度',
created_at: '创建时间',
updated_at: '更新时间',
created_by: '创建人',
updated_by: '更新人',
};
export function getCurrentUserFromStorage() { export function getCurrentUserFromStorage() {
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return null; return null;
...@@ -153,6 +176,22 @@ export function getFieldNameFromPath(fieldPath) { ...@@ -153,6 +176,22 @@ export function getFieldNameFromPath(fieldPath) {
: fieldPath.split('.').pop(); : fieldPath.split('.').pop();
} }
export function getFieldLabel(fieldPath, fieldDef, configuredLabel = '') {
const fieldName = getFieldNameFromPath(fieldPath);
const subjectLabel = normalizeText(fieldDef?.label);
const viewLabel = normalizeText(configuredLabel);
if (subjectLabel && subjectLabel !== fieldName && subjectLabel !== fieldPath) {
return subjectLabel;
}
if (viewLabel && viewLabel !== fieldName && viewLabel !== fieldPath) {
return viewLabel;
}
return COMMON_FIELD_LABELS[fieldName] || '业务字段';
}
function pickReferenceSubjectName(value) { function pickReferenceSubjectName(value) {
if (!value) { if (!value) {
return ''; return '';
...@@ -231,11 +270,16 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {}) ...@@ -231,11 +270,16 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
.filter((column) => includeHidden || !column.hide) .filter((column) => includeHidden || !column.hide)
.map((column) => { .map((column) => {
const fieldDef = getFieldDefinition(column.field, subject); const fieldDef = getFieldDefinition(column.field, subject);
const label = getFieldLabel(column.field, fieldDef, column.label);
return { return {
...column, ...column,
width: column.width || 180, width: column.width || 180,
label: fieldDef?.label || (column.field === 'title' ? '标题' : column.field), label,
fieldDef: fieldDef || { name: column.field, label: column.field, type: 'text' }, fieldDef: fieldDef || {
name: getFieldNameFromPath(column.field),
label,
type: 'text',
},
}; };
}); });
} }
...@@ -249,10 +293,11 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {}) ...@@ -249,10 +293,11 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
}); });
toArray(subject?.fields).forEach((field) => { toArray(subject?.fields).forEach((field) => {
const fieldPath = `metadata.${field.name}`;
defaultColumns.push({ defaultColumns.push({
field: `metadata.${field.name}`, field: fieldPath,
width: 180, width: 180,
label: field.label || field.name, label: getFieldLabel(fieldPath, field),
fieldDef: field, fieldDef: field,
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment