Explorar o código

Merge branch 'master' of github.com:usernamedd/zsis

merge from github
sw hai 3 semanas
pai
achega
ad253f0be4

+ 46 - 0
.build/h5_for_local_e2e.js

@@ -0,0 +1,46 @@
+// 执行脚本 taro build --type h5  ,并且把环境变量 TARO_API_URL  传递过去
+
+const { execSync } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+
+function isCI() {
+    const isCI = process.env.CI === 'true' || !!process.env.GITHUB_ACTIONS;
+    return isCI;
+}
+
+function getGhToken() {
+    // 1. 在 GitHub Actions 里官方会注入 ACTIONS_RUNTIME_TOKEN 或 CI=true
+    const isCI = process.env.CI === 'true' || !!process.env.GITHUB_ACTIONS;
+
+    if (isCI) {
+        console.log(`you are running in ic and the GH_token is ${process.env.GH_TOKEN}`)
+        // CI 环境:直接拿 secret(工作流里必须 env:{GH_TOKEN:${{secrets.GH_TOKEN}}})
+        const token = process.env.GH_TOKEN;
+        if (!token) throw new Error('CI 环境缺少 GH_TOKEN,请检查 workflow yaml');
+        return token;
+    }
+
+    // 2. 本地开发:优先读本地 ~/.gh-token(自己放的),没有再读环境变量,还没有就空
+    try {
+        return readFileSync(join(require('os').homedir(), '.gh-token'), 'utf8').trim();
+    } catch {
+        return process.env.GH_TOKEN || '';
+    }
+}
+
+const TARO_API_URL = ''; // 远程地址,这里写死,要做成部署后可配置
+const TARO_MQTT_URL = 'ws://localhost:8083/mqtt';
+const rootDir = path.join(__dirname, '..');          // 项目根目录
+
+execSync(`npm run build:h5`, { cwd: rootDir, stdio: 'inherit', env: { ...process.env, TARO_API_URL, TARO_MQTT_URL } }, (error, stdout, stderr) => {
+    if (error) {
+        console.error(`Error executing command: ${error.message}`);
+        return;
+    }
+    if (stderr) {
+        console.error(`Command stderr: ${stderr}`);
+        return;
+    }
+    console.log(`Command stdout: ${stdout}`);
+});

+ 5 - 3
README.md

@@ -86,10 +86,12 @@ npm run pkg
 
 ### 启动
 
-在执行启动命令之前,需要先启动应用的 H5 端开发模式
+构建用于e2e的静态资源文件
 
-- npm run dev:h5
+- node .build/h5_for_local_e2e.js
 
 然后执行测试命令:
 
-- npm run e2e
+- npx cypress open --e2e
+
+最后人工选择要执行的用例

+ 1 - 1
cypress.config.ts

@@ -11,7 +11,7 @@ export default defineConfig({
   viewportWidth: 1920,
   viewportHeight: 1080,
   e2e: {
-    baseUrl: 'http://localhost:10086/#/pages/index/index', // Adjust this to match your application's base URL
+    baseUrl: null,//'http://localhost:10086/#/pages/index/index', // Adjust this to match your application's base 
     // specPattern: '__e2e_test__/**/*.spec.ts',
     setupNodeEvents(on, config) {
       on('task', {

+ 16 - 3
cypress/e2e/login.cy.ts

@@ -1,4 +1,5 @@
 // import { describe, it } from 'node:test';
+import { mockLoginFail, mockLoginSuccess } from '../support/mock/handlers/user';
 import LoginPage from '../support/pageObjects/LoginPage';
 // import "cypress";
 
@@ -6,19 +7,31 @@ describe('Login Page', () => {
   const loginPage = new LoginPage();
 
   beforeEach(() => {
+    // loginPage.visit();
+    // loginPage.getUsernameInput().should('be.visible');
+    // loginPage.getPasswordInput().should('be.visible');
+    // loginPage.getSubmitButton().should('be.visible');
+  });
+
+  it('should successfully log in with correct credentials', () => {
+    mockLoginSuccess();
     loginPage.visit();
     loginPage.getUsernameInput().should('be.visible');
     loginPage.getPasswordInput().should('be.visible');
     loginPage.getSubmitButton().should('be.visible');
-  });
-
-  it('should successfully log in with correct credentials', () => {
     loginPage.login('admin', '123456');
+    cy.wait('@loginSuccess');
     cy.contains('登录成功').should('be.visible', { timeout: 10000 });
   });
 
   it('should show an error message for incorrect credentials', () => {
+    mockLoginFail();
+    loginPage.visit();
+    loginPage.getUsernameInput().should('be.visible');
+    loginPage.getPasswordInput().should('be.visible');
+    loginPage.getSubmitButton().should('be.visible');
     loginPage.login('wronguser', 'wrongpassword');
+    cy.wait('@loginFail');
     cy.contains('登录失败').should('be.visible', { timeout: 10000 });
   });
 });

+ 35 - 0
cypress/support/mock/handlers/user.ts

@@ -1 +1,36 @@
 // User-related mock handlers
+// 封装登录成功的 mock
+export function mockLoginSuccess() {
+  cy.intercept('POST', '/dr/api/v1/pub/login', (req) => {
+    req.reply({
+      statusCode: 200,
+      body: {
+        code: "0x000000",
+        description: "Success",
+        solution: "",
+        data: {
+          token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+          expire: 1751277588,
+          uid: 1,
+          name: "admin",
+          avatar: ""
+        }
+      }
+    });
+  }).as('loginSuccess');
+}
+
+// 封装登录失败的 mock
+export function mockLoginFail() {
+  cy.intercept('POST', '/dr/api/v1/pub/login', (req) => {
+    req.reply({
+      statusCode: 200, // 一般还是200,通过 code 区分失败
+      body: {
+        code: "0x000001",
+        description: "Invalid username or password",
+        solution: "Please check your credentials",
+        data: {}
+      }
+    });
+  }).as('loginFail');
+}

+ 2 - 2
cypress/support/pageObjects/LoginPage.ts

@@ -1,8 +1,8 @@
 // import 'cypress';
-
+import {fileIndex} from '../util';
 class LoginPage {
   visit() {
-    cy.visit('/');
+    cy.visit(`${fileIndex}#/pages/index/index`);
   }
 
   getUsernameInput() {

+ 4 - 0
cypress/support/util.ts

@@ -0,0 +1,4 @@
+/**
+ * 作为base url
+ */
+export const fileIndex = './dist/h5/index.html';

+ 2 - 0
src/layouts/BusinessZone.tsx

@@ -229,6 +229,8 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
     return originalName;
   };
 
+  useSelector((state: RootState) => state.permission.triggerPermissionCalc);
+
   return (
     // <div
 

+ 28 - 0
src/states/permissionSlice.ts

@@ -0,0 +1,28 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { deleteWorkThunk } from './patient/worklist/slices/workSlice';
+
+interface PermissionState {
+  triggerPermissionCalc: boolean;
+}
+
+const initialState: PermissionState = {
+  triggerPermissionCalc: false,
+};
+
+const permissionSlice = createSlice({
+  name: 'permission',
+  initialState,
+  reducers: {
+    setTriggerPermissionCalc: (state, action: PayloadAction<boolean>) => {
+      state.triggerPermissionCalc = action.payload;
+    },
+  },
+  extraReducers: (builder) => {
+    builder.addCase(deleteWorkThunk.fulfilled, (state) => {
+      state.triggerPermissionCalc = !state.triggerPermissionCalc;
+    });
+  },
+});
+
+export const { setTriggerPermissionCalc } = permissionSlice.actions;
+export default permissionSlice.reducer;

+ 2 - 0
src/states/store.ts

@@ -53,6 +53,7 @@ import studyFilterReducer from './patient/DiagnosticReport/studyFilterSlice';
 import templateReducer from './patient/DiagnosticReport/templateSlice';
 import imageSelectionReducer from './patient/DiagnosticReport/imageSelectionSlice';
 import diagnosticReportReducer from './patient/DiagnosticReport/slice';
+import permissionReducer from './permissionSlice';
 
 const store = configureStore({
   reducer: {
@@ -98,6 +99,7 @@ const store = configureStore({
     template: templateReducer,
     imageSelection: imageSelectionReducer,
     diagnosticReport: diagnosticReportReducer,
+    permission: permissionReducer,
   },
   middleware: (getDefaultMiddleware) =>
     getDefaultMiddleware().concat(