Browse Source

feat (1.70.3 -> 1.70.4): 添加站点信息SN码显示和复制功能,修正系统模式标签并添加扫码枪翻译

- 在 productSlice.ts 中添加 sn 字段到产品状态
- 在 SiteInfo.tsx 中添加 SN 码显示和一键复制功能
- 在 SystemMode.tsx 中启用系统模式选项的标签和描述显示
- 更新中文和英文翻译文件,修正系统模式标签并添加扫码枪相关翻译
- 更新 package.json 添加 deploy:h5 脚本
- 更新 README.md 文档格式和部署脚本说明

改动文件:
- README.md
- package.json
- scripts/output/i18n/en.js
- scripts/output/i18n/zh.js
- src/assets/i18n/messages/en.js
- src/assets/i18n/messages/zh.js
- src/pages/system/SettingsModal/sections/SystemHome/SiteInfo.tsx
- src/pages/system/SettingsModal/sections/SystemHome/SystemMode.tsx
- src/states/productSlice.ts
szy 21 hours ago
parent
commit
155e06a85a

+ 23 - 0
CHANGELOG.md

@@ -2,6 +2,29 @@
 
 本项目的所有重要变更都将记录在此文件中.
 
+## [1.70.4] - 2026-01-20 15:28
+
+feat (1.70.3 -> 1.71.0): 添加站点信息SN码显示和复制功能,修正系统模式标签并添加扫码枪翻译
+
+- 在 productSlice.ts 中添加 sn 字段到产品状态
+- 在 SiteInfo.tsx 中添加 SN 码显示和一键复制功能
+- 在 SystemMode.tsx 中启用系统模式选项的标签和描述显示
+- 更新中文和英文翻译文件,修正系统模式标签并添加扫码枪相关翻译
+- 更新 package.json 添加 deploy:h5 脚本
+- 更新 README.md 文档格式和部署脚本说明
+
+改动文件:
+
+- README.md
+- package.json
+- scripts/output/i18n/en.js
+- scripts/output/i18n/zh.js
+- src/assets/i18n/messages/en.js
+- src/assets/i18n/messages/zh.js
+- src/pages/system/SettingsModal/sections/SystemHome/SiteInfo.tsx
+- src/pages/system/SettingsModal/sections/SystemHome/SystemMode.tsx
+- src/states/productSlice.ts
+
 ## [1.70.3] - 2026-01-20 13:45
 
 feat (1.70.2 -> 1.70.3): 重构系统模式选项为领域模型并修复国际化标签

+ 15 - 8
README.md

@@ -111,11 +111,13 @@ npm run pkg
 **目的**:将 `src/assets/i18n/messages/*.js` 文件转换为纯 JSON 格式,移除 JavaScript 语法(`export default` 和结尾的 `;`),方便用于其他用途(如后端API、文档生成等)。
 
 **使用方法**:
+
 ```bash
 node scripts/extract-i18n-json.js
 ```
 
 **输出文件**:
+
 - `scripts/output/i18n/zh.js` - 中文翻译(纯JSON格式,.js扩展名)
 - `scripts/output/i18n/en.js` - 英文翻译(纯JSON格式,.js扩展名)
 
@@ -155,6 +157,7 @@ dros.exe --enable-dev-menu
 ```
 
 **参数说明**:
+
 - `--enable-dev-menu` : 启用开发者菜单,包含Monkey Testing启动选项 , 如果期望点击 Toggle developer tools菜单项唤出开发者工具面板,需要同时传递启动选项 --enable-dev-tools
 - `--enable-dev-tools` : 启动时自动打开开发者工具(可选)
 
@@ -173,26 +176,30 @@ dros.exe --enable-dev-menu
 ## 测试期间功能
 
 ### 菜单自动隐藏
+
 - 测试启动后,应用菜单栏会自动隐藏,避免干扰测试
 - 使用快捷键 **Ctrl+Alt+M** 可以临时显示菜单
 - 菜单会在5秒后自动隐藏
 
 ### 停止测试
+
 测试会根据设置的持续时间或操作次数自动停止,也可以:
+
 - 再次按 **Ctrl+Alt+M** 显示菜单
 - 手动停止测试(需要添加停止功能)
 
 ## 参数说明
 
-| 参数 | 说明 | 默认值 | 单位 |
-|------|------|--------|------|
-| Interval | 每次操作的时间间隔 | 100 | 毫秒 |
-| Duration | 测试总持续时间 | 2147483646 | 毫秒 |
-| Max Operations | 最大操作次数限制 | 600000 | 次 |
+| 参数           | 说明               | 默认值     | 单位 |
+| -------------- | ------------------ | ---------- | ---- |
+| Interval       | 每次操作的时间间隔 | 100        | 毫秒 |
+| Duration       | 测试总持续时间     | 2147483646 | 毫秒 |
+| Max Operations | 最大操作次数限制   | 600000     | 次   |
 
 ## 操作类型
 
 Monkey Test 会随机执行以下操作:
+
 - 鼠标移动到随机位置
 - 鼠标左键点击
 - 鼠标双击
@@ -210,9 +217,9 @@ Monkey Test 会随机执行以下操作:
 
 - **Ctrl+Alt+M**: 测试期间临时显示/隐藏菜单
 - **F12**: 切换开发者工具(如果启用)
+
 # h5构建并部署到发布服务器
 
-在项目根目录下运行命令:
 ```
-node ./.build/deploy-h5.js
-```
+npm run deploy:h5
+```

+ 3 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "zsis",
-  "version": "1.70.3",
+  "version": "1.70.4",
   "private": true,
   "description": "医学成像系统",
   "main": "main.js",
@@ -45,7 +45,8 @@
     "h5:browser": "cross-env TARO_API_URL= TARO_MQTT_URL=/mqtt node ./.build/h5_for_production.js",
     "h5:electron": "cross-env TARO_API_URL=http://localhost:6001 TARO_MQTT_URL=ws://localhost:8083/mqtt node ./.build/h5_for_production.js",
     "extract:i18n": "node scripts/extract-i18n-json.js",
-    "clean:cache": "node ./.build/clean-cache.js"
+    "clean:cache": "node ./.build/clean-cache.js",
+    "deploy:h5": "node ./.build/deploy-h5.js"
   },
   "browserslist": [
     "defaults and fully supports es6-module",

+ 8 - 5
scripts/output/i18n/en.js

@@ -393,8 +393,8 @@
   "exam.exitFeedback.complete": "Complete",
   "exam.exitFeedback.cancel": "Cancel",
   "systemMode.title": "System Mode",
-  "systemMode.R": "Demo Mode",
-  "systemMode.S": "Live Mode (Generator Controlled)",
+  "systemMode.S": "Demo Mode",
+  "systemMode.R": "Live Mode (Generator Controlled)",
   "systemMode.R-S": "Live Mode (Generator Uncontrolled)",
   "systemMode.hint": "Changing system mode requires saving configuration and restarting the software to take effect",
   "systemMode.save": "Save",
@@ -402,7 +402,10 @@
   "systemMode.selectRequired": "Please select a system mode",
   "systemMode.saveSuccess": "System mode saved successfully, restart software to take effect",
   "systemMode.saveFailed": "Failed to save system mode",
-  "systemMode.R.desc": "Both generator and FPD are virtual",
-  "systemMode.S.desc": "Both generator and FPD are real hardware",
-  "systemMode.R-S.desc": "FPD is real hardware, generator is virtual"
+  "systemMode.S.desc": "Both generator and FPD are virtual",
+  "systemMode.R.desc": "Both generator and FPD are real hardware",
+  "systemMode.R-S.desc": "generator is real hardware, FPD is virtual",
+  "scanner.indicator.tooltip.ready": "Scanner is ready, you can scan QR code",
+  "scanner.indicator.tooltip.notReady": "Scanner unavailable, click to activate",
+  "scanner.indicator.activated": "Scanner activated, please scan QR code"
 }

+ 8 - 5
scripts/output/i18n/zh.js

@@ -393,8 +393,8 @@
   "exam.exitFeedback.complete": "完成",
   "exam.exitFeedback.cancel": "取消",
   "systemMode.title": "系统模式",
-  "systemMode.R": "演示模式",
-  "systemMode.S": "真机模式(发生器受控)",
+  "systemMode.S": "演示模式",
+  "systemMode.R": "真机模式(发生器受控)",
   "systemMode.R-S": "真机模式(发生器不受控)",
   "systemMode.hint": "切换系统模式,需保存配置并重启软件才能生效",
   "systemMode.save": "保存",
@@ -402,7 +402,10 @@
   "systemMode.selectRequired": "请选择系统模式",
   "systemMode.saveSuccess": "系统模式保存成功,重启软件后生效",
   "systemMode.saveFailed": "保存系统模式失败",
-  "systemMode.R.desc": "发生器、FPD均为虚拟",
-  "systemMode.S.desc": "发生器、FPD均为真实硬件",
-  "systemMode.R-S.desc": "FPD真实、发生器虚拟"
+  "systemMode.S.desc": "发生器、FPD均为虚拟",
+  "systemMode.R.desc": "发生器、FPD均为真实硬件",
+  "systemMode.R-S.desc": "发生器真实、FPD虚拟",
+  "scanner.indicator.tooltip.ready": "扫码枪已就绪,可以扫描二维码",
+  "scanner.indicator.tooltip.notReady": "扫码枪不可用,点击此处激活",
+  "scanner.indicator.activated": "扫码枪已就绪,请扫描二维码"
 }

+ 1 - 1
src/assets/i18n/messages/en.js

@@ -406,7 +406,7 @@ export default {
   'systemMode.saveFailed': 'Failed to save system mode',
   'systemMode.S.desc': 'Both generator and FPD are virtual',
   'systemMode.R.desc': 'Both generator and FPD are real hardware',
-  'systemMode.R-S.desc': 'FPD is real hardware, generator is virtual',
+  'systemMode.R-S.desc': 'generator is real hardware, FPD is virtual',
   'scanner.indicator.tooltip.ready': 'Scanner is ready, you can scan QR code',
   'scanner.indicator.tooltip.notReady': 'Scanner unavailable, click to activate',
   'scanner.indicator.activated': 'Scanner activated, please scan QR code',

+ 1 - 1
src/assets/i18n/messages/zh.js

@@ -406,7 +406,7 @@ export default {
   "systemMode.saveFailed": "保存系统模式失败",
   "systemMode.S.desc": "发生器、FPD均为虚拟",
   "systemMode.R.desc": "发生器、FPD均为真实硬件",
-  "systemMode.R-S.desc": "FPD真实、发生器虚拟",
+  "systemMode.R-S.desc": "发生器真实、FPD虚拟",
   "scanner.indicator.tooltip.ready": "扫码枪已就绪,可以扫描二维码",
   "scanner.indicator.tooltip.notReady": "扫码枪不可用,点击此处激活",
   "scanner.indicator.activated": "扫码枪已就绪,请扫描二维码",

+ 47 - 13
src/pages/system/SettingsModal/sections/SystemHome/SiteInfo.tsx

@@ -14,7 +14,9 @@ import {
   Spin,
   Modal,
 } from 'antd';
+import { CopyOutlined } from '@ant-design/icons';
 import { SPACING } from '../../constants';
+import { RootState, useAppSelector } from '@/states/store';
 
 const { Title } = Typography;
 const { TextArea } = Input;
@@ -31,7 +33,7 @@ interface SiteInfoData {
 
 /**
  * 站点信息组件
- * 
+ *
  * 布局结构:
  * - Layout Type: Vertical Stack Layout
  * - Form Layout: Horizontal
@@ -42,7 +44,8 @@ const SiteInfo: React.FC = () => {
   const [loading, setLoading] = useState(false);
   const [saving, setSaving] = useState(false);
   const [initialValues, setInitialValues] = useState<SiteInfoData | null>(null);
-
+  const sn = useAppSelector((state: RootState) => state.product.sn);
+  const { Search } = Input;
   // 模拟加载数据
   useEffect(() => {
     loadSiteInfo();
@@ -54,7 +57,7 @@ const SiteInfo: React.FC = () => {
     try {
       // TODO: 替换为实际的 API 调用
       // const data = await getSiteInfo();
-      
+
       // 模拟数据
       const mockData: SiteInfoData = {
         siteName: 'Medical Imaging Center',
@@ -80,18 +83,18 @@ const SiteInfo: React.FC = () => {
     try {
       // 触发表单验证
       const values = await form.validateFields();
-      
+
       setSaving(true);
-      
+
       // TODO: 替换为实际的 API 调用
       // await updateSiteInfo(values);
-      
+
       // 模拟保存延迟
-      await new Promise(resolve => setTimeout(resolve, 1000));
-      
+      await new Promise((resolve) => setTimeout(resolve, 1000));
+
       message.success('保存成功');
       setInitialValues(values);
-      
+
       // 提示用户可能需要重启服务
       Modal.info({
         title: '提示',
@@ -110,12 +113,13 @@ const SiteInfo: React.FC = () => {
   };
 
   // 重置表单
-  const handleReset = () => {
+  const handleReset = (): void => {
     if (initialValues) {
       // 检查是否有未保存的更改
       const currentValues = form.getFieldsValue();
-      const hasChanges = JSON.stringify(currentValues) !== JSON.stringify(initialValues);
-      
+      const hasChanges =
+        JSON.stringify(currentValues) !== JSON.stringify(initialValues);
+
       if (hasChanges) {
         Modal.confirm({
           title: '确认重置',
@@ -133,11 +137,28 @@ const SiteInfo: React.FC = () => {
   };
 
   // AE Title 输入转大写
-  const handleAETitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+  const handleAETitleChange = (
+    e: React.ChangeEvent<HTMLInputElement>
+  ): void => {
     const value = e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, '');
     form.setFieldsValue({ aeTitle: value });
   };
 
+  // 复制SN号码
+  const handleCopySN = async (): Promise<void> => {
+    if (sn) {
+      try {
+        await navigator.clipboard.writeText(sn);
+        message.success('SN码已复制到剪贴板');
+      } catch (error) {
+        message.error('复制失败,请手动复制');
+        console.error('Failed to copy SN:', error);
+      }
+    } else {
+      message.warning('SN码为空');
+    }
+  };
+
   if (loading) {
     return (
       <div
@@ -166,6 +187,19 @@ const SiteInfo: React.FC = () => {
         size="large"
         style={{ marginTop: SPACING.LG }}
       >
+        {/* SN码 */}
+        <Form.Item label="SN码">
+          <Space.Compact style={{ width: '100%' }}>
+            <Input style={{ width: '90%' }} disabled value={sn || ''} />
+            <Button
+              style={{ width: '10%', height: 'auto' }}
+              icon={<CopyOutlined />}
+              onClick={handleCopySN}
+              title="复制SN码"
+            />
+          </Space.Compact>
+        </Form.Item>
+
         {/* 站点名称 */}
         <Form.Item
           label="站点名称"

+ 2 - 2
src/pages/system/SettingsModal/sections/SystemHome/SystemMode.tsx

@@ -85,8 +85,8 @@ export const SystemMode: React.FC = () => {
 
   const radioOptions = SYSTEM_MODE_OPTIONS.map((option) => ({
     ...option,
-    // label: intl.formatMessage({ id: `systemMode.${option.value}` }),
-    // desc: intl.formatMessage({ id: `systemMode.${option.value}.desc` }),
+    label: intl.formatMessage({ id: `systemMode.${option.value}` }),
+    desc: intl.formatMessage({ id: `systemMode.${option.value}.desc` }),
   }));
 
   const handleModeSelect = (modeValue: string) => {

+ 5 - 0
src/states/productSlice.ts

@@ -8,6 +8,7 @@ interface ProductState {
   guest: string; //本质是token,只用于急诊情况
   fpd: string; // 平板探测器类型: "Simulator" 或 "Physics"
   gen: string; // 发生器类型: "Simulator" 或 "Physics"
+  sn: string;
 }
 
 const initialState: ProductState = {
@@ -17,6 +18,7 @@ const initialState: ProductState = {
   guest: '',
   fpd: '',
   gen: '',
+  sn: '',
 };
 
 export const initializeProductState = createAsyncThunk(
@@ -31,6 +33,7 @@ export const initializeProductState = createAsyncThunk(
       guest: softwareInfo.guest,
       fpd: softwareInfo.FPD,
       gen: softwareInfo.GEN,
+      sn: softwareInfo.sn,
     };
   }
 );
@@ -46,6 +49,7 @@ const productSlice = createSlice({
       state.guest = action.payload.guest;
       state.fpd = action.payload.fpd;
       state.gen = action.payload.gen;
+      state.sn = action.payload.sn;
     },
   },
   extraReducers: (builder) => {
@@ -57,6 +61,7 @@ const productSlice = createSlice({
         state.guest = action.payload.guest;
         state.fpd = action.payload.fpd;
         state.gen = action.payload.gen;
+        state.sn = action.payload.sn;
       })
       .addCase(initializeProductState.rejected, (state, action) => {
         console.error(