sliderAdjustmentPanelSlice.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /**
  2. * 滑动参数调节面板状态管理
  3. */
  4. import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
  5. import type { RootState } from '../store';
  6. import type {
  7. ProcessingStyle,
  8. LUTType,
  9. FullProcessingParams
  10. } from '../../types/imageProcessing';
  11. import {
  12. PROCESSING_PRESETS,
  13. DEFAULT_ALGORITHM
  14. } from '../../domain/processingPresets';
  15. import { DEFAULT_LUT } from '../../domain/lutConfig';
  16. import {
  17. getImageProcessingParams,
  18. saveImageProcessingParams
  19. } from '../../API/imageActions';
  20. /**
  21. * 状态接口
  22. */
  23. interface SliderAdjustmentPanelState {
  24. // 当前图像ID
  25. currentImageId: string | null;
  26. // 当前参数值
  27. parameters: FullProcessingParams;
  28. // 当前选择的风格
  29. selectedStyle: ProcessingStyle;
  30. // 当前选择的算法(独立管理)
  31. selectedAlgorithm: string;
  32. // 当前选择的LUT(独立管理)
  33. selectedLUT: LUTType;
  34. // 是否正在加载
  35. isLoading: boolean;
  36. // 是否正在保存
  37. isSaving: boolean;
  38. // 初始加载标志(区分初始加载和用户操作)
  39. isInitialLoad: boolean;
  40. // 错误信息
  41. error: string | null;
  42. }
  43. /**
  44. * 初始状态
  45. */
  46. const initialState: SliderAdjustmentPanelState = {
  47. currentImageId: null,
  48. parameters: PROCESSING_PRESETS['均衡'].params,
  49. selectedStyle: '均衡',
  50. selectedAlgorithm: DEFAULT_ALGORITHM,
  51. selectedLUT: DEFAULT_LUT,
  52. isLoading: false,
  53. isSaving: false,
  54. isInitialLoad: true,
  55. error: null,
  56. };
  57. /**
  58. * 异步操作:加载图像处理参数
  59. */
  60. export const loadImageProcessingParams = createAsyncThunk(
  61. 'sliderAdjustmentPanel/loadParams',
  62. async (sopInstanceUid: string, { rejectWithValue }) => {
  63. try {
  64. const response = await getImageProcessingParams(sopInstanceUid);
  65. return {
  66. sopInstanceUid,
  67. params: response.data,
  68. };
  69. } catch (error) {
  70. return rejectWithValue(
  71. error instanceof Error ? error.message : '加载参数失败'
  72. );
  73. }
  74. }
  75. );
  76. /**
  77. * 异步操作:保存图像处理参数
  78. */
  79. export const saveProcessingParams = createAsyncThunk(
  80. 'sliderAdjustmentPanel/saveParams',
  81. async (
  82. {
  83. sopInstanceUid,
  84. params
  85. }: {
  86. sopInstanceUid: string;
  87. params: { contrast: number; detail: number; latitude: number; noise: number }
  88. },
  89. { rejectWithValue }
  90. ) => {
  91. try {
  92. await saveImageProcessingParams(sopInstanceUid, params);
  93. return params;
  94. } catch (error) {
  95. return rejectWithValue(
  96. error instanceof Error ? error.message : '保存参数失败'
  97. );
  98. }
  99. }
  100. );
  101. /**
  102. * Slice
  103. */
  104. const sliderAdjustmentPanelSlice = createSlice({
  105. name: 'sliderAdjustmentPanel',
  106. initialState,
  107. reducers: {
  108. /**
  109. * 设置当前图像ID
  110. */
  111. setCurrentImageId: (state, action: PayloadAction<string>) => {
  112. state.currentImageId = action.payload;
  113. state.isInitialLoad = true;
  114. },
  115. /**
  116. * 更新单个参数
  117. */
  118. updateParameter: (
  119. state,
  120. action: PayloadAction<{ name: keyof FullProcessingParams; value: number }>
  121. ) => {
  122. const { name, value } = action.payload;
  123. state.parameters[name] = value;
  124. // 用户手动调整参数后,不再是初始加载状态
  125. state.isInitialLoad = false;
  126. },
  127. /**
  128. * 应用风格预设
  129. */
  130. applyPreset: (state, action: PayloadAction<ProcessingStyle>) => {
  131. const preset = PROCESSING_PRESETS[action.payload];
  132. state.selectedStyle = action.payload;
  133. state.parameters = { ...preset.params };
  134. // 应用风格后,不再是初始加载状态
  135. state.isInitialLoad = false;
  136. },
  137. /**
  138. * 重置为当前风格的默认值
  139. */
  140. resetToPreset: (state) => {
  141. const preset = PROCESSING_PRESETS[state.selectedStyle];
  142. state.parameters = { ...preset.params };
  143. state.isInitialLoad = false;
  144. },
  145. /**
  146. * 设置算法
  147. */
  148. setAlgorithm: (state, action: PayloadAction<string>) => {
  149. state.selectedAlgorithm = action.payload;
  150. },
  151. /**
  152. * 设置LUT
  153. */
  154. setLUT: (state, action: PayloadAction<LUTType>) => {
  155. state.selectedLUT = action.payload;
  156. },
  157. /**
  158. * 清除错误
  159. */
  160. clearError: (state) => {
  161. state.error = null;
  162. },
  163. /**
  164. * 重置状态
  165. */
  166. resetState: () => initialState,
  167. },
  168. extraReducers: (builder) => {
  169. builder
  170. // 加载参数
  171. .addCase(loadImageProcessingParams.pending, (state) => {
  172. state.isLoading = true;
  173. state.error = null;
  174. })
  175. .addCase(loadImageProcessingParams.fulfilled, (state, action) => {
  176. state.isLoading = false;
  177. state.currentImageId = action.payload.sopInstanceUid;
  178. // 只更新后端返回的4个参数,保持前端的 brightness 和 sharpness
  179. state.parameters.contrast = action.payload.params.contrast;
  180. state.parameters.detail = action.payload.params.detail;
  181. state.parameters.latitude = action.payload.params.latitude;
  182. state.parameters.noise = action.payload.params.noise;
  183. // 标记为初始加载,避免触发自动保存
  184. state.isInitialLoad = true;
  185. })
  186. .addCase(loadImageProcessingParams.rejected, (state, action) => {
  187. state.isLoading = false;
  188. state.error = action.payload as string;
  189. // 加载失败时使用默认值
  190. state.parameters = PROCESSING_PRESETS[state.selectedStyle].params;
  191. })
  192. // 保存参数
  193. .addCase(saveProcessingParams.pending, (state) => {
  194. state.isSaving = true;
  195. state.error = null;
  196. })
  197. .addCase(saveProcessingParams.fulfilled, (state) => {
  198. state.isSaving = false;
  199. })
  200. .addCase(saveProcessingParams.rejected, (state, action) => {
  201. state.isSaving = false;
  202. state.error = action.payload as string;
  203. });
  204. },
  205. });
  206. /**
  207. * Actions
  208. */
  209. export const {
  210. setCurrentImageId,
  211. updateParameter,
  212. applyPreset,
  213. resetToPreset,
  214. setAlgorithm,
  215. setLUT,
  216. clearError,
  217. resetState,
  218. } = sliderAdjustmentPanelSlice.actions;
  219. /**
  220. * Selectors
  221. */
  222. export const selectCurrentImageId = (state: RootState) =>
  223. state.sliderAdjustmentPanel.currentImageId;
  224. export const selectParameters = (state: RootState) =>
  225. state.sliderAdjustmentPanel.parameters;
  226. export const selectSelectedStyle = (state: RootState) =>
  227. state.sliderAdjustmentPanel.selectedStyle;
  228. export const selectSelectedAlgorithm = (state: RootState) =>
  229. state.sliderAdjustmentPanel.selectedAlgorithm;
  230. export const selectSelectedLUT = (state: RootState) =>
  231. state.sliderAdjustmentPanel.selectedLUT;
  232. export const selectIsLoading = (state: RootState) =>
  233. state.sliderAdjustmentPanel.isLoading;
  234. export const selectIsSaving = (state: RootState) =>
  235. state.sliderAdjustmentPanel.isSaving;
  236. export const selectIsInitialLoad = (state: RootState) =>
  237. state.sliderAdjustmentPanel.isInitialLoad;
  238. export const selectError = (state: RootState) =>
  239. state.sliderAdjustmentPanel.error;
  240. /**
  241. * Reducer
  242. */
  243. export default sliderAdjustmentPanelSlice.reducer;