Commit b52ff11f authored by David Yang's avatar David Yang

fix: bugs

parent 40f35588
......@@ -27,6 +27,10 @@ function asSaveValue(value, fieldDef) {
return value;
}
function getClearedValue(fieldDef) {
return fieldDef?.multiple ? [] : null;
}
function Pill({ children, tone = 'zinc' }) {
const toneClass =
tone === 'rose'
......@@ -241,6 +245,12 @@ export default function TailwindEditableField({
}
};
const clearValue = async () => {
const clearedValue = getClearedValue(fieldDef);
setDraft(clearedValue);
await save(clearedValue);
};
const cancel = () => {
setDraft(value);
setError('');
......@@ -259,7 +269,8 @@ export default function TailwindEditableField({
try {
const uploaded = await uploadFile(file);
const fileValue = uploaded?.file || uploaded?.data || uploaded;
const nextValue = fieldDef?.multiple ? [...toArray(value), fileValue] : fileValue;
const nextValue = fieldDef?.multiple ? [...toArray(draft), fileValue] : fileValue;
setDraft(nextValue);
await onSave(nextValue);
setEditing(false);
} catch (uploadError) {
......@@ -269,6 +280,25 @@ export default function TailwindEditableField({
}
};
const handleRemoveFile = async (indexToRemove) => {
setError('');
const nextValue = fieldDef?.multiple
? toArray(draft).filter((_, index) => index !== indexToRemove)
: null;
try {
setDraft(nextValue);
await onSave(nextValue);
if (!fieldDef?.multiple) {
setEditing(false);
}
} catch (saveError) {
setDraft(draft);
setError(saveError?.message || '删除附件失败,请稍后重试。');
}
};
if (!editing) {
return (
<button
......@@ -300,7 +330,10 @@ export default function TailwindEditableField({
<div className="flex items-center gap-3 rounded-2xl border border-zinc-200 bg-white px-3 py-2">
<Checkbox checked={Boolean(draft)} onChange={(checked) => setDraft(checked)} color="rose" />
<span className="text-sm text-zinc-700">{draft ? '是' : '否'}</span>
<button type="button" onClick={() => save(Boolean(draft))} className="ml-auto text-sm font-medium text-rose-700">
<button type="button" onClick={clearValue} className="ml-auto text-sm font-medium text-rose-600">
清空
</button>
<button type="button" onClick={() => save(Boolean(draft))} className="text-sm font-medium text-rose-700">
保存
</button>
<button type="button" onClick={cancel} className="text-sm text-zinc-500">
......@@ -334,6 +367,7 @@ export default function TailwindEditableField({
))}
</Select>
<div className="flex justify-end gap-2">
<button type="button" onClick={clearValue} className="text-sm font-medium text-rose-600">清空</button>
<button type="button" onClick={cancel} className="text-sm text-zinc-500">取消</button>
<button type="button" onClick={() => save()} className="text-sm font-medium text-rose-700">保存</button>
</div>
......@@ -347,6 +381,7 @@ export default function TailwindEditableField({
<div className="space-y-3">
<UserSelector value={draft} onChange={setDraft} multiple={fieldDef?.multiple} />
<div className="flex justify-end gap-2">
<button type="button" onClick={clearValue} className="text-sm font-medium text-rose-600">清空</button>
<button type="button" onClick={cancel} className="text-sm text-zinc-500">取消</button>
<button type="button" onClick={() => save()} className="text-sm font-medium text-rose-700">保存</button>
</div>
......@@ -364,6 +399,7 @@ export default function TailwindEditableField({
onChange={setDraft}
/>
<div className="flex justify-end gap-2">
<button type="button" onClick={clearValue} className="text-sm font-medium text-rose-600">清空</button>
<button type="button" onClick={cancel} className="text-sm text-zinc-500">取消</button>
<button type="button" onClick={() => save()} className="text-sm font-medium text-rose-700">保存</button>
</div>
......@@ -375,10 +411,47 @@ export default function TailwindEditableField({
if (fieldType === 'file') {
return (
<div className="space-y-3 rounded-2xl border border-zinc-200 bg-white p-3">
<FieldValue value={value} fieldDef={fieldDef} />
<div className="space-y-2">
{toArray(draft).length === 0 ? (
<span className="text-zinc-400">未上传</span>
) : (
toArray(draft).map((file, index) => {
const label = file?.name || file?.title || file?.url || `附件 ${index + 1}`;
return (
<div
key={`${label}-${index}`}
className="flex items-center justify-between gap-3 rounded-2xl border border-zinc-100 bg-zinc-50 px-3 py-2"
>
<div className="min-w-0 flex-1">
{file?.url ? (
<a
href={file.url}
target="_blank"
rel="noreferrer"
className="text-sm font-medium text-sky-700 transition hover:text-sky-800"
>
{label}
</a>
) : (
<div className="text-sm font-medium text-zinc-700">{label}</div>
)}
</div>
<button
type="button"
onClick={() => handleRemoveFile(index)}
className="shrink-0 text-sm font-medium text-rose-600"
>
删除
</button>
</div>
);
})
)}
</div>
<input type="file" onChange={handleFileUpload} disabled={uploading} className="block w-full text-sm text-zinc-600" />
{error ? <div className="text-sm text-rose-600">{error}</div> : null}
<div className="flex justify-end gap-2">
<button type="button" onClick={clearValue} className="text-sm font-medium text-rose-600">清空</button>
<button type="button" onClick={cancel} className="text-sm text-zinc-500">取消</button>
<span className="text-sm text-zinc-400">{uploading ? '上传中...' : '选择文件后自动保存'}</span>
</div>
......@@ -393,6 +466,7 @@ export default function TailwindEditableField({
<Textarea rows={8} value={jsonValue} onChange={(event) => setDraft(event.target.value)} resizable />
{error ? <div className="text-sm text-rose-600">{error}</div> : null}
<div className="flex justify-end gap-2">
<button type="button" onClick={clearValue} className="text-sm font-medium text-rose-600">清空</button>
<button type="button" onClick={cancel} className="text-sm text-zinc-500">取消</button>
<button
type="button"
......@@ -411,6 +485,7 @@ export default function TailwindEditableField({
<div className="space-y-3">
<Textarea rows={5} value={draft || ''} onChange={(event) => setDraft(event.target.value)} autoFocus />
<div className="flex justify-end gap-2">
<button type="button" onClick={clearValue} className="text-sm font-medium text-rose-600">清空</button>
<button type="button" onClick={cancel} className="text-sm text-zinc-500">取消</button>
<button type="button" onClick={() => save()} className="text-sm font-medium text-rose-700">保存</button>
</div>
......@@ -433,6 +508,7 @@ export default function TailwindEditableField({
autoFocus
/>
<div className="flex justify-end gap-2">
<button type="button" onClick={clearValue} className="text-sm font-medium text-rose-600">清空</button>
<button type="button" onClick={cancel} className="text-sm text-zinc-500">取消</button>
<button type="button" onClick={() => save()} className="text-sm font-medium text-rose-700">保存</button>
</div>
......
......@@ -63,6 +63,7 @@ const RecordDetailDrawer = (props, ref) => {
onDataChange,
readonly = false,
filterView = [],
preferredViewName,
} = props;
const [refreshKey, setRefreshKey] = useState(() => Date.now());
......@@ -112,11 +113,21 @@ const RecordDetailDrawer = (props, ref) => {
return;
}
if (preferredViewName) {
const matchedView = detailViews.find(
(view) => view.name === preferredViewName || view.title === preferredViewName,
);
if (matchedView && matchedView.name !== activeTabKey) {
setActiveTabKey(matchedView.name);
return;
}
}
const currentTab = detailViews.find((view) => view.name === activeTabKey);
if (!currentTab) {
setActiveTabKey(detailViews[0].name);
}
}, [detailViews, activeTabKey]);
}, [detailViews, activeTabKey, preferredViewName]);
useImperativeHandle(
ref,
......
import xyLogo from '@/assets/xy-logo.png';
import xyLogoSmall from '@/assets/xy-logo_small.png';
import { UserContext } from '@/wrapper/Auth';
import {
......@@ -244,16 +243,13 @@ export default function ProblemRectificationLayout() {
sidebarCollapsed ? 'justify-center' : 'gap-3 px-1',
)}
>
<img
src={sidebarCollapsed ? xyLogoSmall : xyLogo}
alt="XY Logo"
className={clsx(
'shrink-0 object-contain',
sidebarCollapsed
? 'h-8 w-auto max-w-8'
: 'h-8 w-auto max-w-[140px]',
)}
/>
{sidebarCollapsed ? (
<img
src={xyLogoSmall}
alt="XY Logo"
className="h-8 w-auto max-w-8 shrink-0 object-contain"
/>
) : null}
{!sidebarCollapsed ? (
<div className="min-w-0">
<h1 className="truncate text-sm font-semibold tracking-[0.02em] text-white/95">
......@@ -327,12 +323,7 @@ export default function ProblemRectificationLayout() {
close={() => setIsMobileSidebarOpen(false)}
>
<div className="px-5 pt-6">
<div className="flex items-center gap-2">
<img
src={xyLogo}
alt="XY Logo"
className="h-8 w-auto max-w-[140px] shrink-0 object-contain"
/>
<div>
<div>
<h1 className="text-base font-semibold tracking-[0.02em] text-white">
{APP_NAME}
......
......@@ -51,6 +51,19 @@
- 新增、编辑、查看详情优先采用抽屉或弹窗,不走重型嵌套表格交互
- 批量操作放在顶部工具栏,不再依赖右键菜单作为唯一入口
### 3.3 视图配置驱动约定
问题整改主列表与详情抽屉统一以 `getSubjectView(subjectName)` 返回的视图配置驱动,不再在页面内写死部门标签和字段顺序:
- `type = "grid"` 的视图用于主列表顶部视图切换,替代原先写死的 tab
- `settings.columns` 决定列表列顺序、宽度和排序优先级
- `settings.restrictions` 与 `settings.filters.conditions` 共同决定该视图下可见的数据范围
- `settings.viewAuth` 决定当前用户是否能看到该视图
- `type = "detail"` 的视图用于详情抽屉字段编排,按 `columns` 顺序渲染字段
- `settings.auth` 与 `disableColumns` 用于控制详情抽屉内字段是否允许编辑
因此前端新增字段展示、部门视图调整、列顺序变化时,应优先改 Subject View 配置,而不是直接修改页面常量。
### 3.1 目标页面拆分
为便于当前项目直接重构,建议把模块拆成以下 4 个页面入口:
......@@ -179,6 +192,13 @@
- 表格内分组拖拽
- 全屏容器能力
替代实现约定:
- 列表页保留表格浏览、搜索、筛选、批量选择
- 字段编辑统一进入右侧详情抽屉
- 长文本、附件、状态类字段在详情抽屉中直接维护
- 详情抽屉内字段顺序与显示权限跟随 `detail view` 配置
---
## 6. 流程角色
......@@ -624,6 +644,7 @@ const result = await queryUsers(0, 20, {
布局层负责:
- 左侧菜单
- 桌面展开态仅展示模块名称,去掉大 Logo;仅在桌面折叠态展示小 Logo
- 顶部用户区
- 内容容器
- 移动端侧栏开关
......
......@@ -7,6 +7,7 @@ import {
import { DEPARTMENT_GROUPS } from './problemRectificationDashboardService';
import {
buildViewColumns,
checkViewCondition,
emptyValueForField,
formatFieldValue,
getFieldNameFromPath,
......@@ -162,6 +163,18 @@ export function buildProblemRectificationConfiguredListView(records, filters, gr
return false;
}
if (
!checkViewCondition(
{
relationship: gridView?.settings?.filters?.relationship || 'all',
rules: gridView?.settings?.filters?.conditions || [],
},
rawRecord,
)
) {
return false;
}
if (nextProgress && record.progress !== nextProgress) {
return false;
}
......@@ -200,6 +213,18 @@ export function buildProblemRectificationConfiguredListView(records, filters, gr
};
}
export function buildGridViewCountMap(records, filters, views, subject) {
return views.reduce((counts, view) => {
counts[view.name] = buildProblemRectificationConfiguredListView(
records,
filters,
view,
subject,
).filteredRecords.length;
return counts;
}, {});
}
function matchesDepartmentGroup(record, groupKey) {
if (!groupKey || groupKey === 'all') {
return true;
......
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