forked from mengyxu/noob-components
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
292 lines
7.2 KiB
292 lines
7.2 KiB
|
3 months ago
|
# Store
|
||
|
|
|
||
|
|
> Vuex-like state management pattern with Actions, Getters, Mutations, and State classes.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The store implements a Vuex-like pattern using separate classes for State, Actions, Getters, and Mutations. It uses Vuex's `createStore` function.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
Store
|
||
|
|
├── State (reactive data)
|
||
|
|
├── Actions (async operations that commit mutations)
|
||
|
|
├── Mutations (synchronous state changes)
|
||
|
|
└── Getters (computed values)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## State Class
|
||
|
|
|
||
|
|
**File**: `/home/hechang27/Documents/sprt/noob-components/plugs/store/index.ts`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export class State {
|
||
|
|
dict = {
|
||
|
|
active_status: {
|
||
|
|
A: "启用",
|
||
|
|
B: "禁用",
|
||
|
|
},
|
||
|
|
};
|
||
|
|
roles = {};
|
||
|
|
menus = [];
|
||
|
|
roleRefresh = true;
|
||
|
|
style = Styles.plain;
|
||
|
|
size = Size.normal;
|
||
|
|
actions = [];
|
||
|
|
user = {};
|
||
|
|
actionPers = {};
|
||
|
|
refreshFlags = new Map();
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### State Properties
|
||
|
|
|
||
|
|
| Property | Type | Description |
|
||
|
|
|----------|------|-------------|
|
||
|
|
| `dict` | object | Dictionary mappings by code |
|
||
|
|
| `roles` | object | Role ID to name mapping |
|
||
|
|
| `menus` | array | User menu permissions |
|
||
|
|
| `roleRefresh` | boolean | Flag to trigger role refresh |
|
||
|
|
| `style` | Styles.plain | Current theme style |
|
||
|
|
| `size` | Size.normal | Current size variant |
|
||
|
|
| `actions` | array | User action permissions |
|
||
|
|
| `user` | object | Current user object |
|
||
|
|
| `actionPers` | object | Action permission mappings |
|
||
|
|
| `refreshFlags` | Map | Refresh flag tokens per flag name |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Actions Class
|
||
|
|
|
||
|
|
**File**: `/home/hechang27/Documents/sprt/noob-components/plugs/store/index.ts`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export class Actions {
|
||
|
|
getDictMap = ({ state, commit }, codes) => {
|
||
|
|
if (codes == null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const arr: Array<any> = [];
|
||
|
|
for (let i = 0; i < codes.length; i++) {
|
||
|
|
const code = codes[i];
|
||
|
|
state.dict[code] || arr.push(code);
|
||
|
|
}
|
||
|
|
if (arr.length == 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
getByCodes({ codes: arr.join() }).then((rsp: any) => {
|
||
|
|
if (rsp) {
|
||
|
|
for (let i = 0; i < arr.length; i++) {
|
||
|
|
const key = arr[i];
|
||
|
|
const dict = {};
|
||
|
|
if (rsp[key]) {
|
||
|
|
rsp[key].forEach((item) => {
|
||
|
|
dict[item.code] = item.name;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
commit("updateDict", [key, dict]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
getRoleMap = ({ state, commit }) => {
|
||
|
|
state.roleRefresh &&
|
||
|
|
mapping().then((rsp) => {
|
||
|
|
commit("updateState", ["roles", rsp]);
|
||
|
|
commit("updateState", ["roleRefresh", false]);
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
getMenus = ({ state, commit }) => {
|
||
|
|
getMenus().then((rsp) => commit("updateState", ["menus", rsp]));
|
||
|
|
};
|
||
|
|
|
||
|
|
getMyActions = ({ state, commit }, content) => {
|
||
|
|
getActions(content).then((rsp) => commit("updateState", ["actions", rsp]));
|
||
|
|
};
|
||
|
|
|
||
|
|
login = (store) => {
|
||
|
|
const { state, commit } = store;
|
||
|
|
state.size.headHeight = state.size.head + "px";
|
||
|
|
state.size.asideWidth = state.size.aside + "px";
|
||
|
|
commit("initSize");
|
||
|
|
this.getMenus(store);
|
||
|
|
};
|
||
|
|
|
||
|
|
setRefreshFlag = ({ state, commit }, flags: string[]) => {
|
||
|
|
commit("setRefreshFlag", flags);
|
||
|
|
};
|
||
|
|
|
||
|
|
unsetRefreshFlag = ({ state, commit }, params: { flags: string[]; token: string }) => {
|
||
|
|
commit("unsetRefreshFlag", params);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Actions Pattern
|
||
|
|
|
||
|
|
- Actions receive Vuex store object with `state`, `commit`, `dispatch`
|
||
|
|
- Actions call API functions and commit mutations
|
||
|
|
- Actions never directly modify state
|
||
|
|
- Actions can call other actions via `dispatch`
|
||
|
|
|
||
|
|
### Key Actions
|
||
|
|
|
||
|
|
| Action | Description |
|
||
|
|
|--------|-------------|
|
||
|
|
| `getDictMap` | Fetches dictionary entries by codes, caches in state |
|
||
|
|
| `getRoleMap` | Fetches role mappings if refresh is needed |
|
||
|
|
| `getMenus` | Fetches user menu permissions |
|
||
|
|
| `getMyActions` | Fetches user action permissions |
|
||
|
|
| `login` | Initializes size settings and fetches menus |
|
||
|
|
| `setRefreshFlag` | Creates empty refresh flag sets |
|
||
|
|
| `unsetRefreshFlag` | Adds token to refresh flag sets |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Mutations Class
|
||
|
|
|
||
|
|
**File**: `/home/hechang27/Documents/sprt/noob-components/plugs/store/index.ts`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export class Mutations {
|
||
|
|
updateState = (state, [k, v]) => {
|
||
|
|
state[k] = v;
|
||
|
|
};
|
||
|
|
|
||
|
|
initSize = (state, param) => {
|
||
|
|
const size = state.size;
|
||
|
|
const aside = parseInt(size.asideWidth);
|
||
|
|
const head = parseInt(size.headHeight);
|
||
|
|
const mainPad = parseInt(size.mainPad);
|
||
|
|
const searchRow = parseInt(size.searchRowHeight);
|
||
|
|
const searchRowPad = parseInt(size.searchRowPad);
|
||
|
|
const headRightWidth = parseInt(size.headRightWidth);
|
||
|
|
if (param) {
|
||
|
|
size.height = param[0];
|
||
|
|
size.width = param[1];
|
||
|
|
}
|
||
|
|
size.mainHeight = Math.floor(size.height - head) + "px";
|
||
|
|
size.tableHeight = size.height - 2 * (mainPad + searchRowPad) - 3 - searchRow - head;
|
||
|
|
size.pTableHeight = size.tableHeight - size.pageHeight;
|
||
|
|
|
||
|
|
size.headLeftWidth = size.width - aside - headRightWidth - 20 + "px";
|
||
|
|
};
|
||
|
|
|
||
|
|
updateDict = (state, param) => {
|
||
|
|
state.dict[param[0]] = param[1];
|
||
|
|
};
|
||
|
|
|
||
|
|
setRefreshFlag = (state, flags: string[]) => {
|
||
|
|
for (const flag of flags) {
|
||
|
|
state.refreshFlags.set(flag, new Set());
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
unsetRefreshFlag = (state, { flags, token }: { flags: string[]; token: string }) => {
|
||
|
|
for (const flag of flags) {
|
||
|
|
if (!state.refreshFlags.has(flag)) {
|
||
|
|
state.refreshFlags.set(flag, new Set());
|
||
|
|
}
|
||
|
|
state.refreshFlags.get(flag).add(token);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Mutations Pattern
|
||
|
|
|
||
|
|
- Mutations receive `state` directly
|
||
|
|
- Mutations are synchronous
|
||
|
|
- Mutations use array destructuring for multiple parameters: `([k, v])`
|
||
|
|
- Mutations directly modify state properties
|
||
|
|
|
||
|
|
### Key Mutations
|
||
|
|
|
||
|
|
| Mutation | Description |
|
||
|
|
|----------|-------------|
|
||
|
|
| `updateState` | Generic state update: `commit("updateState", ["key", value])` |
|
||
|
|
| `initSize` | Initializes size-dependent calculated values |
|
||
|
|
| `updateDict` | Updates dictionary entry: `commit("updateDict", [code, mapping])` |
|
||
|
|
| `setRefreshFlag` | Creates new refresh flag with empty Set |
|
||
|
|
| `unsetRefreshFlag` | Adds token to refresh flag Set |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Getters Class
|
||
|
|
|
||
|
|
**File**: `/home/hechang27/Documents/sprt/noob-components/plugs/store/index.ts`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export class Getters {
|
||
|
|
menuIconSize = (state) => {
|
||
|
|
return state.size.menuIconSize;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Getters Pattern
|
||
|
|
|
||
|
|
- Getters receive `state` as first parameter
|
||
|
|
- Getters compute and return derived values
|
||
|
|
- Getters are cached based on dependencies
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Store Creation
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export interface StoreOptions {
|
||
|
|
state?: State;
|
||
|
|
getters?: Getters;
|
||
|
|
actions?;
|
||
|
|
mutations?: Mutations;
|
||
|
|
modules?;
|
||
|
|
plugins?;
|
||
|
|
strict?: boolean;
|
||
|
|
devtools?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const createStore = (options) => {
|
||
|
|
return create(options);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Usage in Component
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { createStore } from "vuex";
|
||
|
|
import { State, Actions, Mutations, Getters } from "./store";
|
||
|
|
|
||
|
|
const store = createStore({
|
||
|
|
state: new State(),
|
||
|
|
getters: new Getters(),
|
||
|
|
actions: new Actions(),
|
||
|
|
mutations: new Mutations(),
|
||
|
|
strict: true,
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Anti-Patterns
|
||
|
|
|
||
|
|
1. **Never modify state directly in actions** - Always use `commit` to call mutations
|
||
|
|
2. **Do not put async logic in mutations** - Mutations must be synchronous
|
||
|
|
3. **Do not use mutation names as variables** - Always use string literals for mutation names
|
||
|
|
4. **Do not skip the `initSize` mutation** - Without it, derived values like `mainHeight` will be incorrect
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Language**: English
|