feat: 5项UX优化 - 合并占位符prompt、默认200x200尺寸、系统默认设置、签名状态列、素材预加载前置
This commit is contained in:
@@ -46,6 +46,21 @@ export default function Login() {
|
||||
storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS);
|
||||
}
|
||||
|
||||
const savedAssets = storage.get<{id: string; name: string; dataUrl: string}[]>('imageAssets', []);
|
||||
if (savedAssets.length === 0) {
|
||||
fetch('/logo_square.png')
|
||||
.then(res => res.blob())
|
||||
.then(blob => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const dataUrl = reader.result as string;
|
||||
storage.set('imageAssets', [{ id: 'asset_logo', name: '医院Logo', dataUrl }]);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
const settingsRaw = storage.get<SystemSettings>('systemSettings', {} as SystemSettings);
|
||||
if (!settingsRaw.frameCount) {
|
||||
const round1 = (n: number) => Math.round(n * 10) / 10;
|
||||
@@ -59,7 +74,10 @@ export default function Login() {
|
||||
apiEndpoint: '',
|
||||
apiKey: '',
|
||||
defaultTemplate: savedTemplates[0]?.id || '',
|
||||
frameMode: 'uniform'
|
||||
frameMode: 'uniform',
|
||||
autoInsertFrames: true,
|
||||
autoInsertDelay: 1,
|
||||
autoInsertFrameIndices: [0, 1, 2, 3, 4, 5]
|
||||
};
|
||||
storage.set('systemSettings', defaultSettings);
|
||||
}
|
||||
|
||||
@@ -422,15 +422,26 @@ export default function ReportEditor() {
|
||||
|
||||
const insertImage = () => {
|
||||
editorRef.current?.focus();
|
||||
const widthStr = prompt('请输入占位符最大宽度 (px),留空无限制:\n(提示:正文一行文字高度约为 20 像素左右)', '');
|
||||
const heightStr = prompt('请输入占位符最大高度 (px),留空无限制:', '');
|
||||
const width = parseInt(widthStr || '0');
|
||||
const height = parseInt(heightStr || '0');
|
||||
const input = prompt('请输入占位符的最大宽度和高度(px),用英文逗号分隔(如: 100,50)。留空则默认宽高为 200*200。(提示: 正文一行文字高度约为 20 像素左右)', '');
|
||||
if (input === null) return;
|
||||
const parts = input.split(',').map(s => s.trim());
|
||||
const widthStr = parts[0] || '';
|
||||
const heightStr = parts[1] || '';
|
||||
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;';
|
||||
if (width > 0) styleStr += ` max-width:${width}px;`;
|
||||
if (height > 0) styleStr += ` max-height:${height}px;`;
|
||||
if (!width && !height) styleStr += ' padding:8px 16px;';
|
||||
let width = parseInt(widthStr) || 0;
|
||||
let height = parseInt(heightStr) || 0;
|
||||
if (!widthStr && !heightStr) {
|
||||
width = 200;
|
||||
height = 200;
|
||||
} else if (widthStr && !heightStr) {
|
||||
height = 200;
|
||||
} else if (!widthStr && heightStr) {
|
||||
width = 200;
|
||||
}
|
||||
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;overflow:hidden;';
|
||||
if (width > 0) styleStr += `width:${width}px;`;
|
||||
if (height > 0) styleStr += `height:${height}px;`;
|
||||
|
||||
const showShortText = width > 0 && width < 80;
|
||||
const hintText = showShortText ? '插入图片' : '插入/点击放置图片';
|
||||
|
||||
@@ -486,18 +486,29 @@ export default function TemplateManage() {
|
||||
const insertImage = () => {
|
||||
editorRef.current?.focus();
|
||||
restoreSelection();
|
||||
const widthStr = prompt('请输入占位符最大宽度 (px),留空无限制:\n(提示:正文一行文字高度约为 20 像素左右)', '');
|
||||
const heightStr = prompt('请输入占位符最大高度 (px),留空无限制:', '');
|
||||
const width = parseInt(widthStr || '0');
|
||||
const height = parseInt(heightStr || '0');
|
||||
const input = prompt('请输入占位符的最大宽度和高度(px),用英文逗号分隔(如: 100,50)。留空则默认宽高为 200*200。(提示: 正文一行文字高度约为 20 像素左右)', '');
|
||||
if (input === null) return;
|
||||
const parts = input.split(',').map(s => s.trim());
|
||||
const widthStr = parts[0] || '';
|
||||
const heightStr = parts[1] || '';
|
||||
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;';
|
||||
if (width > 0) styleStr += ` max-width:${width}px;`;
|
||||
if (height > 0) styleStr += ` max-height:${height}px;`;
|
||||
if (!width && !height) styleStr += ' padding:8px 16px;';
|
||||
let width = parseInt(widthStr) || 0;
|
||||
let height = parseInt(heightStr) || 0;
|
||||
if (!widthStr && !heightStr) {
|
||||
width = 200;
|
||||
height = 200;
|
||||
} else if (widthStr && !heightStr) {
|
||||
height = 200;
|
||||
} else if (!widthStr && heightStr) {
|
||||
width = 200;
|
||||
}
|
||||
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;overflow:hidden;';
|
||||
if (width > 0) styleStr += `width:${width}px;`;
|
||||
if (height > 0) styleStr += `height:${height}px;`;
|
||||
|
||||
const showShortText = width > 0 && width < 80;
|
||||
const hintText = showShortText ? '插入图片' : '插入/点击放置图片';
|
||||
const hintText = showShortText ? '插图' : '插入/点击放置图片';
|
||||
|
||||
const id = 'ph_' + Date.now();
|
||||
const html = `<span id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false" style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${hintText}</span></span>​`;
|
||||
|
||||
@@ -416,6 +416,7 @@ export default function UserManage() {
|
||||
<th className="px-6 py-4 text-left text-[11px] font-bold text-text-muted uppercase tracking-wider border-b border-border">联系电话</th>
|
||||
<th className="px-6 py-4 text-left text-[11px] font-bold text-text-muted uppercase tracking-wider border-b border-border">角色</th>
|
||||
<th className="px-6 py-4 text-left text-[11px] font-bold text-text-muted uppercase tracking-wider border-b border-border">部门</th>
|
||||
<th className="px-6 py-4 text-left text-[11px] font-bold text-text-muted uppercase tracking-wider border-b border-border">签名状态</th>
|
||||
<th className="px-6 py-4 text-left text-[11px] font-bold text-text-muted uppercase tracking-wider border-b border-border">状态</th>
|
||||
<th className="px-6 py-4 text-left text-[11px] font-bold text-text-muted uppercase tracking-wider border-b border-border">操作</th>
|
||||
</tr>
|
||||
@@ -437,6 +438,13 @@ export default function UserManage() {
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-text-main">{user.department || '-'}</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className={`inline-block px-2.5 py-1 rounded-full text-[11px] font-bold ${
|
||||
user.signature ? 'bg-blue-100 text-blue-700' : 'bg-slate-100 text-slate-500'
|
||||
}`}>
|
||||
{user.signature ? '已上传' : '未上传'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className={`inline-block px-2.5 py-1 rounded-full text-[11px] font-bold ${
|
||||
user.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
|
||||
|
||||
51
过往经验/经验记录.md
51
过往经验/经验记录.md
@@ -236,6 +236,57 @@ if ((settings.autoInsertDelay || 0) > 0) {
|
||||
- 在 `setTimeout` 等异步回调中操作 DOM 时,应在回调触发时"实时查询"目标元素,而不是在循环中提前捕获元素引用,以防 DOM 在延迟期间已被用户修改。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 记录 12:5 项交互与默认值优化(占位符尺寸、签名状态、素材预加载)
|
||||
|
||||
**A. 具体问题**
|
||||
用户提出 5 个 UI/UX 改进需求:
|
||||
1. 插入图片占位符时两次 `prompt` 弹窗合并为一次,用英文逗号分隔宽高;
|
||||
2. 占位符未指定尺寸时默认显示为 `200×200px`,且样式直接使用 `width`/`height` 而非 `max-width`/`max-height`;
|
||||
3. 系统重置后的默认设置中增加 `autoInsertFrames: true`、`autoInsertDelay: 1`、`autoInsertFrameIndices: [0,1,2,3,4,5]`;
|
||||
4. 用户管理表格在「部门」与「状态」之间新增「签名状态」列,根据 `user.signature` 显示「已上传」/「未上传」;
|
||||
5. 修复系统重置后 `ReportEditor` 的素材库为空的问题,将 logo 预加载逻辑从 `TemplateManage.tsx` 前置到 `Login.tsx` 的 `initData()` 中。
|
||||
|
||||
**B. 产生问题原因**
|
||||
1. `insertImage()` 在两个编辑器(`TemplateManage`、`ReportEditor`)中均使用两次独立的 `prompt()`,操作冗余且中断感强。
|
||||
2. 旧占位符样式使用 `max-width`/`max-height`,当内容区域大于占位符时,边框和背景不会收缩到指定尺寸,视觉尺寸不可控;且未指定时的 `padding:8px 16px` 导致占位符尺寸随文字变化,不统一。
|
||||
3. `Login.tsx` 初始化 `systemSettings` 时遗漏了自动帧插入相关的 3 个字段,导致新系统首次进入 `/system-settings` 时相关开关为空。
|
||||
4. `UserManage.tsx` 表格缺少签名可视化列,管理员无法一眼辨别哪些医生已上传电子签名。
|
||||
5. `imageAssets` 的预加载仅在 `TemplateManage.tsx` 的 `useEffect` 中执行。若用户首次登录后直接进入 `ReportEditor`,素材库为空,图片选择器无法使用系统默认 logo。
|
||||
|
||||
**C. 解决问题方案**
|
||||
1. **合并 prompt**:
|
||||
```ts
|
||||
const input = prompt('请输入占位符的最大宽度和高度(px),用英文逗号分隔(如: 100,50)。留空则默认宽高为 200*200。(提示: 正文一行文字高度约为 20 像素左右)', '');
|
||||
const parts = input.split(',').map(s => s.trim());
|
||||
```
|
||||
按逗号分割,第一部分为宽度,第二部分为高度。留空或单侧留空时,另一侧自动回退到 `200`。
|
||||
2. **固定尺寸样式**:
|
||||
- 移除 `max-width`/`max-height`,改用 `width:${width}px;` / `height:${height}px;`。
|
||||
- 默认值逻辑:`!widthStr && !heightStr` → `200×200`;`widthStr && !heightStr` → 宽自定义、高 `200`;`!widthStr && heightStr` → 宽 `200`、高自定义。
|
||||
3. **默认设置补全**:在 `Login.tsx` 的 `defaultSettings` 中显式加入:
|
||||
```ts
|
||||
autoInsertFrames: true,
|
||||
autoInsertDelay: 1,
|
||||
autoInsertFrameIndices: [0, 1, 2, 3, 4, 5]
|
||||
```
|
||||
4. **签名状态列**:在 `UserManage.tsx` 表格的 `<th>` 和 `<td>` 中,于「部门」之后、「状态」之前插入:
|
||||
```tsx
|
||||
<span className={`inline-block px-2.5 py-1 rounded-full text-[11px] font-bold ${
|
||||
user.signature ? 'bg-blue-100 text-blue-700' : 'bg-slate-100 text-slate-500'
|
||||
}`}>
|
||||
{user.signature ? '已上传' : '未上传'}
|
||||
</span>
|
||||
```
|
||||
5. **素材预加载前置**:将 `fetch('/logo_square.png') → FileReader → storage.set('imageAssets', [...])` 的逻辑从 `TemplateManage.tsx` 迁移到 `Login.tsx` 的 `initData()` 中,并增加 `savedAssets.length === 0` 的判空保护,避免覆盖用户后续上传的素材。
|
||||
|
||||
**D. 后续如何避免问题**
|
||||
- 对于成对的数值输入(如宽高、行列),优先考虑单输入框 + 分隔符,减少弹窗次数;同时做好格式解析和容错(空值、单侧空值、非数字)。
|
||||
- 使用 `width`/`height` 代替 `max-width`/`max-height` 能确保占位符尺寸严格可控,避免 `inline-flex` 内容撑大容器。
|
||||
- 任何需要在多个页面共享的初始化数据(如素材库、默认配置),应放在全局初始化入口(如登录页的 `initData`),而不是分散在各个页面的 `useEffect` 中。
|
||||
- 表格字段变更时,注意保持 `<thead>` 与 `<tbody>` 的列顺序严格一致,避免列错位。
|
||||
|
||||
---
|
||||
|
||||
## 记录 11:关键帧在路由切换后丢失——压缩 Canvas 分辨率并增加存储错误日志
|
||||
|
||||
Reference in New Issue
Block a user