Kaynağa Gözat

Merge branch 'master' of http://192.168.1.41:3000/outaozhen/cldV2react

outaozhen 5 yıl önce
ebeveyn
işleme
14d084dc53

+ 1 - 1
package.json

@@ -6,7 +6,7 @@
   "scripts": {
     "analyze": "cross-env ANALYZE=1 umi build",
     "build": "cross-env REACT_APP_ENV=prod umi build",
-    "deploy:qa": "auto-deploy build -t qa",
+    "deploy:qa": "cross-env REACT_APP_ENV=qa auto-deploy build -t qa",
     "deploy:prod": "auto-deploy build -t prod",
     "deploy-gh-pages": "npm run site && npm run gh-pages",
     "dev": "npm run start:dev",

+ 6 - 6
src/components/GlobalHeader/RightContent.jsx

@@ -1,9 +1,9 @@
-import { Tooltip, Tag } from 'antd'
-import { QuestionCircleOutlined } from '@ant-design/icons'
+import { Tag } from 'antd'
+// import { QuestionCircleOutlined } from '@ant-design/icons'
 import React from 'react'
-import { connect, SelectLang } from 'umi'
+import { connect } from 'umi'
 import Avatar from './AvatarDropdown'
-import HeaderSearch from '../HeaderSearch'
+// import HeaderSearch from '../HeaderSearch'
 import styles from './index.less'
 
 const ENVTagColor = {
@@ -12,7 +12,7 @@ const ENVTagColor = {
   pre: '#87d068'
 }
 
-const GlobalHeaderRight = (props) => {
+const GlobalHeaderRight = props => {
   const { theme, layout } = props
   let className = styles.right
 
@@ -61,7 +61,7 @@ const GlobalHeaderRight = (props) => {
         </a>
       </Tooltip> */}
       <Avatar />
-      {REACT_APP_ENV && (
+      {REACT_APP_ENV && REACT_APP_ENV !== 'prod' && (
         <span>
           <Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
         </span>

+ 4 - 0
src/global.less

@@ -100,3 +100,7 @@ input:-webkit-autofill:active {
   -webkit-transition: color 11111s ease-out, background-color 111111s ease-out;
   -webkit-transition-delay: 111111s;
 }
+
+.card-group {
+  box-shadow: 0 0 13px 0 rgba(74, 53, 107, 8%);
+}

+ 13 - 0
src/settings/encryptionSetting.ts

@@ -0,0 +1,13 @@
+import { isDevMode } from '@/utils/env'
+
+// System default cache time, in seconds
+export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7
+
+// aes encryption key
+export const cacheCipher = {
+  key: '_11111000001111@',
+  iv: '@11111000001111_'
+}
+
+// Whether the system cache is encrypted using aes
+export const enableStorageEncryption = !isDevMode()

+ 8 - 0
src/settings/projectSetting.ts

@@ -0,0 +1,8 @@
+/* eslint-disable */
+
+import type { ProjectConfig } from '@/types/config'
+import { CacheTypeEnum } from '@/utils/cache/cacheEnum'
+
+const setting: ProjectConfig = {
+  permissionCacheType: CacheTypeEnum.LOCAL
+}

+ 7 - 0
src/types/config.d.ts

@@ -0,0 +1,7 @@
+/* eslint-disable */
+import type { CacheTypeEnum } from '@/utils/cache/cacheEnum'
+
+export interface ProjectConfig {
+  // Storage location of permission related information
+  permissionCacheType: CacheTypeEnum
+}

+ 17 - 0
src/utils/cache/cacheEnum.ts

@@ -0,0 +1,17 @@
+/* eslint-disable */
+// token key
+export const TOKEN_KEY = 'TOKEN__'
+
+// role role key
+export const ROLES_KEY = 'ROLES__KEY__'
+
+// base global local key
+export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__'
+
+// base global session key
+export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__'
+
+export enum CacheTypeEnum {
+  SESSION,
+  LOCAL
+}

+ 33 - 0
src/utils/cache/index.ts

@@ -0,0 +1,33 @@
+/* eslint-disable */
+import { getStorageShortName } from '@/utils/env'
+import { createStorage as create, CreateStorageParams } from './storageCache'
+import { enableStorageEncryption } from '@/settings/encryptionSetting'
+import { DEFAULT_CACHE_TIME } from '@/settings/encryptionSetting'
+
+export type Options = Partial<CreateStorageParams>
+
+const createOptions = (storage: Storage, options: Options = {}): Options => {
+  return {
+    // No encryption in debug mode
+    hasEncrypt: enableStorageEncryption,
+    storage,
+    prefixKey: getStorageShortName(),
+    ...options
+  }
+}
+
+export const WebStorage = create(createOptions(sessionStorage))
+
+export const createStorage = (storage: Storage = sessionStorage, options: Options = {}) => {
+  return create(createOptions(storage, options))
+}
+
+export const createSessionStorage = (options: Options = {}) => {
+  return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME })
+}
+
+export const createLocalStorage = (options: Options = {}) => {
+  return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME })
+}
+
+export default WebStorage

+ 96 - 0
src/utils/cache/memory.ts

@@ -0,0 +1,96 @@
+/* eslint-disable */
+export interface Cache<V = any> {
+  value?: V
+  timeoutId?: ReturnType<typeof setTimeout>
+  time?: number
+  alive?: number
+}
+
+const NOT_ALIVE = 0
+
+export class Memory<T = any, V = any> {
+  private cache: { [key in keyof T]?: Cache<V> } = {}
+
+  private alive: number
+
+  constructor(alive = NOT_ALIVE) {
+    this.alive = alive
+  }
+
+  get getCache() {
+    return this.cache
+  }
+
+  setCache(cache) {
+    this.cache = cache
+  }
+
+  get<K extends keyof T>(key: K) {
+    return this.cache[key]
+  }
+
+  set<K extends keyof T>(key: K, value: V, expires?: number) {
+    let item = this.get(key)
+
+    if (!expires || (expires as number) <= 0) {
+      expires = this.alive
+    }
+
+    if (item) {
+      if (item.timeoutId) {
+        clearTimeout(item.timeoutId)
+        item.timeoutId = undefined
+      }
+      item.value = value
+    } else {
+      item = { value, alive: expires }
+      this.cache[key] = item
+    }
+
+    if (!expires) {
+      return value
+    }
+
+    const now = new Date().getTime()
+    item.time = now + this.alive
+    item.timeoutId = setTimeout(
+      () => {
+        this.remove(key)
+      },
+      expires > now ? expires - now : expires
+    )
+
+    return value
+  }
+
+  remove<K extends keyof T>(key: K) {
+    const item = this.get(key)
+    Reflect.deleteProperty(this.cache, key)
+    if (item) {
+      clearTimeout(item.timeoutId)
+      return item.value
+    }
+  }
+
+  resetCache(cache: { [K in keyof T]: cache }) {
+    Object.keys(cache).forEach(key => {
+      const k = (key as any) as keyof T
+      const item = cache[k]
+      if (item && item.time) {
+        const now = new Date().getTime()
+        const expire = item.time
+        if (expire > now) {
+          this.set(k, item.value, expire)
+        }
+      }
+    })
+  }
+
+  clear() {
+    Object.keys(this.cache).forEach(key => {
+      const item = this.cache[key]
+      item.timeoutId && clearTimeout(item.timeoutId)
+    })
+    this.cache = {}
+  }
+}

+ 113 - 0
src/utils/cache/persistent.ts

@@ -0,0 +1,113 @@
+/* eslint-disable */
+
+import { Memory } from './memory'
+
+import { createLocalStorage, createSessionStorage } from '/@/utils/cache'
+import { TOKEN_KEY, ROLES_KEY, APP_LOCAL_CACHE_KEY, APP_SESSION_CACHE_KEY } from './cacheEnum'
+import { pick, omit } from 'lodash-es'
+
+interface BasicStore {
+  [TOKEN_KEY]: string | null | undefined
+  [ROLES_KEY]: string[]
+}
+
+type LocalStore = BasicStore
+
+type SessionStore = BasicStore
+
+export type BasicKeys = keyof BasicStore
+type LocalKeys = keyof LocalStore
+type SessionKeys = keyof SessionStore
+
+const ls = createLocalStorage()
+const ss = createSessionStorage()
+
+function initPersistentMemory() {
+  const localCache = ls.get(APP_LOCAL_CACHE_KEY)
+  const sessionCache = ss.get(APP_SESSION_CACHE_KEY)
+  localCache && localMemory.resetCache(localCache)
+  sessionCache && sessionMemory.resetCache(sessionCache)
+}
+
+export class Persistent {
+  static getLocal<T>(key: LocalKeys) {
+    return localMemory.get(key)?.value as Nullable<T>
+  }
+
+  static setLocal(key: LocalKeys, value: LocalStore[LocalKeys], immediate = false): void {
+    localMemory.set(key, value)
+    immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache)
+  }
+
+  static removeLocal(key: LocalKeys, immediate = false): void {
+    localMemory.remove(key)
+    immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache)
+  }
+
+  static clearLocal(immediate = false): void {
+    localMemory.clear()
+    immediate && ls.clear()
+  }
+
+  static getSession<T>(key: SessionKeys) {
+    return sessionMemory.get(key)?.value as Nullable<T>
+  }
+
+  static setSession(key: SessionKeys, value: SessionStore[SessionKeys], immediate = false): void {
+    sessionMemory.set(key, toRaw(value))
+    immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache)
+  }
+
+  static removeSession(key: SessionKeys, immediate = false): void {
+    sessionMemory.remove(key)
+    immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache)
+  }
+  static clearSession(immediate = false): void {
+    sessionMemory.clear()
+    immediate && ss.clear()
+  }
+
+  static clearAll(immediate = false) {
+    sessionMemory.clear()
+    localMemory.clear()
+    if (immediate) {
+      ls.clear()
+      ss.clear()
+    }
+  }
+}
+
+window.addEventListener('beforeunload', function () {
+  // TOKEN_KEY 在登录或注销时已经写入到storage了,此处为了解决同时打开多个窗口时token不同步的问题
+  // LOCK_INFO_KEY 在锁屏和解锁时写入,此处也不应修改
+  ls.set(APP_LOCAL_CACHE_KEY, {
+    ...omit(localMemory.getCache, LOCK_INFO_KEY),
+    ...pick(ls.get(APP_LOCAL_CACHE_KEY), [TOKEN_KEY])
+  })
+  ss.set(APP_SESSION_CACHE_KEY, {
+    ...omit(sessionMemory.getCache, LOCK_INFO_KEY),
+    ...pick(ss.get(APP_SESSION_CACHE_KEY), [TOKEN_KEY])
+  })
+})
+
+function storageChange(e: any) {
+  const { key, newValue, oldValue } = e
+
+  if (!key) {
+    Persistent.clearAll()
+    return
+  }
+
+  if (!!newValue && !!oldValue) {
+    if (APP_LOCAL_CACHE_KEY === key) {
+      Persistent.clearLocal()
+    }
+    if (APP_SESSION_CACHE_KEY === key) {
+      Persistent.clearSession()
+    }
+  }
+}
+
+window.addEventListener('storage', storageChange)
+
+initPersistentMemory()

+ 117 - 0
src/utils/cache/storageCache.ts

@@ -0,0 +1,117 @@
+/* eslint-disable */
+import { cacheCipher } from '@/settings/encryptionSetting'
+import type { EncryptionParams } from '@/utils/cipher'
+import { AesEncryption } from '@/utils/cipher'
+import { isNullOrUnDef } from '@/utils/is'
+
+type Nullable<T> = T | null
+
+export interface CreateStorageParams extends EncryptionParams {
+  prefixKey: string
+  storage: Storage
+  hasEncrypt: boolean
+  timeout?: Nullable<number>
+}
+
+export const createStorage = ({
+  prefixKey = '',
+  storage = sessionStorage,
+  key = cacheCipher.key,
+  iv = cacheCipher.iv,
+  timeout = null,
+  hasEncrypt = true
+}: Partial<CreateStorageParams> = {}) => {
+  if (hasEncrypt && [key.length, iv.length].some(item => item !== 16)) {
+    throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!')
+  }
+
+  const encryption = new AesEncryption({ key, iv })
+
+  /**
+   *Cache class
+   *Construction parameters can be passed into sessionStorage, localStorage,
+   * @class Cache
+   * @example
+   */
+  const WebStorage = class WebStorage {
+    private storage: Storage
+
+    private prefixKey?: string
+
+    private encryption: AesEncryption
+
+    private hasEncrypt: boolean
+
+    /**
+     *
+     * @param {*} storage
+     */
+    constructor() {
+      this.storage = storage
+      this.prefixKey = prefixKey
+      this.encryption = encryption
+      this.hasEncrypt = hasEncrypt
+    }
+
+    private getKey(akey: string) {
+      return `${this.prefixKey}${akey}`.toUpperCase()
+    }
+
+    /**
+     *
+     *  Set cache
+     * @param {string} key
+     * @param {*} value
+     * @expire Expiration time in seconds
+     * @memberof Cache
+     */
+    set(key: string, value: any, expire: number | null = timeout) {
+      const stringData = JSON.stringify({
+        value,
+        time: Date.now(),
+        expire: !isNullOrUnDef(expire) ? new Date().getTime() + expire * 1000 : null
+      })
+      const stringifyValue = this.hasEncrypt ? this.encryption.encryptByAES(stringData) : stringData
+      this.storage.setItem(this.getKey(key), stringifyValue)
+    }
+
+    /**
+     *Read cache
+     * @param {string} key
+     * @memberof Cache
+     */
+    get(key: string, def: any = null): any {
+      const val = this.storage.getItem(this.getKey(key))
+      if (!val) return def
+
+      try {
+        const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val
+        const data = JSON.parse(decVal)
+        const { value, expire } = data
+        if (isNullOrUnDef(expire) || expire >= new Date().getTime()) {
+          return value
+        }
+        this.remove(key)
+      } catch (e) {
+        return def
+      }
+    }
+
+    /**
+     * Delete cache based on key
+     * @param {string} key
+     * @memberof Cache
+     */
+    remove(key: string) {
+      this.storage.removeItem(this.getKey(key))
+    }
+
+    /**
+     * Delete all caches of this instance
+     */
+    clear(): void {
+      this.storage.clear()
+    }
+  }
+  return new WebStorage()
+}

+ 56 - 0
src/utils/cipher.ts

@@ -0,0 +1,56 @@
+/* eslint-disable */
+import { encrypt, decrypt } from 'crypto-js/aes'
+import { parse } from 'crypto-js/enc-utf8'
+import pkcs7 from 'crypto-js/pad-pkcs7'
+import ECB from 'crypto-js/mode-ecb'
+import md5 from 'crypto-js/md5'
+import UTF8 from 'crypto-js/enc-utf8'
+import Base64 from 'crypto-js/enc-base64'
+
+export interface EncryptionParams {
+  key: string
+  iv: string
+}
+
+export class AesEncryption {
+  private key
+  private iv
+
+  constructor(opt: Partial<EncryptionParams> = {}) {
+    const { key, iv } = opt
+    if (key) {
+      this.key = parse(key)
+    }
+    if (iv) {
+      this.iv = parse(iv)
+    }
+  }
+
+  get getOptions() {
+    return {
+      mode: ECB,
+      padding: pkcs7,
+      iv: this.iv
+    }
+  }
+
+  encryptByAES(cipherText: string) {
+    return encrypt(cipherText, this.key, this.getOptions).toString()
+  }
+
+  decryptByAES(cipherText: string) {
+    return decrypt(cipherText, this.key, this.getOptions).toString(UTF8)
+  }
+}
+
+export function encryptByBase64(cipherText: string) {
+  return UTF8.parse(cipherText).toString(Base64)
+}
+
+export function decodeByBase64(cipherText: string) {
+  return Base64.parse(cipherText).toString(UTF8)
+}
+
+export function encryptByMd5(password: string) {
+  return md5(password).toString()
+}

+ 43 - 0
src/utils/env.ts

@@ -0,0 +1,43 @@
+import pkg from '../../package.json'
+
+// Generate cache key according to version
+export function getStorageShortName() {
+  return `CLD${`__${pkg.version}`}__`.toUpperCase()
+}
+/**
+ * @description: Development model
+ */
+export const devMode = 'dev'
+
+/**
+ * @description: Production mode
+ */
+export const prodMode = 'prod'
+/**
+ * @description: Get environment variables
+ * @returns:
+ * @example:
+ */
+export function getEnv(): string {
+  return process.env
+}
+
+/**
+ * @description: Is it a development mode
+ * @returns:
+ * @example:
+ */
+export function isDevMode(): boolean {
+  const { REACT_APP_ENV } = process.env
+  return REACT_APP_ENV === 'dev'
+}
+
+/**
+ * @description: Is it a production mode
+ * @returns:
+ * @example:
+ */
+export function isProdMode(): boolean {
+  const { REACT_APP_ENV } = process.env
+  return REACT_APP_ENV === 'prod'
+}

+ 21 - 0
src/utils/is.ts

@@ -0,0 +1,21 @@
+const { toString } = Object.prototype
+
+export function is(val: unknown, type: string) {
+  return toString.call(val) === `[object ${type}]`
+}
+
+export function isDef<T = unknown>(val?: T): val is T {
+  return typeof val !== 'undefined'
+}
+
+export function isUnDef<T = unknown>(val?: T): val is T {
+  return !isDef(val)
+}
+
+export function isNull(val: unknown): val is null {
+  return val === null
+}
+
+export function isNullOrUnDef(val: unknown): val is null | undefined {
+  return isUnDef(val) || isNull(val)
+}

+ 3 - 0
windi.config.ts

@@ -22,6 +22,9 @@ export default defineConfig({
         lg: '992px',
         xl: '1200px',
         '2xl': '1600px'
+      },
+      boxShadow: {
+        card: '0 0 13px 0 rgba(74, 53, 107, 0.08)'
       }
     }
   }