2022-04-15 ·前端·JavaScript

我写的一些可以日后参考的代码

保存页面变动,批量接口请求

// 这里有两层 key
// 子组件传来的 key、value
// 更新 updateConfigs 来保存所有的变动信息
function handleUpdateConf({ catalogKey, bizId, section, keyName, strValue, oldStrValue, form }) {
  const updated = strValue !== oldStrValue
  if (!this.updateConfigs[bizId]) {
    this.$set(this.updateConfigs, bizId, {
      catalogKey,
      form,
      updateCount: 0,
      sections: {}
    })
  }
  const bizConf = this.updateConfigs[bizId]
  if (!bizConf.sections[section]) {
    this.$set(bizConf.sections, section, {
      configs: {}
    })
  }
  const sectionConf = bizConf.sections[section]
  const noUpdateCount = sectionConf.configs[keyName] && sectionConf.configs[keyName].updated === updated
  if (!noUpdateCount) {
    if (updated) {
      this.updateCount++
      this.catalogUpdateCount[this.catalogKey]++
      bizConf.updateCount++
    }
    else {
      this.updateCount--
      this.catalogUpdateCount[this.catalogKey]--
      bizConf.updateCount--
    }
  }
  this.$set(sectionConf.configs, keyName, {
    updated,
    strValue,
    oldStrValue
  })
}

// 保存接口,便利所有的 key
async function saveAllConfigs() {
  if (this.updateCount === 0) {
    this.$message.warning('未修改任何数据!')
    return
  }
  this.saveLoading = true
  const validations = []
  // 第一层 key 下面有 el-form 遍历所有表单进行验证
  Object.entries(this.updateConfigs).forEach((bizEntry) => {
    const [bizKey, bizValue] = bizEntry
    if (!bizValue.updateCount)
      return
    validations.push({
      bizKey,
      catalogKey: bizValue.catalogKey,
      validatePromise: bizValue.form.validate()
    })
  })
  let errorForm
  // 为每个 validate promise 添加 catch 方法
  const validationPromises = validations.map((validation) => {
    return validation.validatePromise.catch(() => {
      !errorForm && (errorForm = validation)
    })
  })
  await Promise.all(validationPromises).catch(() => {
    console.error('表单验证错误!')
  }).finally(() => {
    this.saveLoading = false
  })
  if (errorForm) {
    this.catalogKey = errorForm.catalogKey
    const first = this.$refs[errorForm.catalogKey] && this.$refs[errorForm.catalogKey][0]
    first && first.toBiz(errorForm.bizKey)
    this.$message.warning('请根据表单提示填写信息!')
    return
  }

  this.$confirm('修改配置可能导致系统重启,请确认是否修改!', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    this.saveLoading = true
    const paramsList = []
    // 便利两层 key 生成最后的参数列表
    Object.entries(this.updateConfigs).forEach((bizEntry) => {
      const [bizId, bizValue] = bizEntry
      if (!bizValue.updateCount)
        return
      Object.entries(bizValue.sections).forEach((sectionEntry) => {
        const [section, sectionValue] = sectionEntry
        Object.entries(sectionValue.configs).forEach((confEntry) => {
          const [keyName, conf] = confEntry
          const { updated, strValue } = conf
          if (!updated)
            return
          paramsList.push({ bizId, section, keyName, strValue })
        })
      })
    })
    let length = paramsList.length
    let success = true
    // 这里接口只能保存一个 key value 所以批量调用
    paramsList.forEach(async (params) => {
      const res = await api.rewriteValue(params).finally(() => {
        // 最后结束的时候,看,是不是所有的接口都返回正确了,如果是则成功,不然的话就是失败
        if (--length === 0) {
          this.saveLoading = false
          if (success) {
            const first = this.$refs[this.catalogKey] && this.$refs[this.catalogKey][0]
            const lastBizKey = first && first.getBizKey()
            lastBizKey && (this.lastSelectPane = [this.catalogKey, lastBizKey])
            this.tabList = []
            this.updateConfigs = {}
            this.catalogUpdateCount = {}
            this.updateCount = 0
            this.getTable()
            this.$message.success('保存配置成功!')
          }
          else {
            this.$message.error('保存配置失败!')
          }
        }
      }).catch((e) => {
        console.error(e)
        success = false
      })
      if (res.code !== 0) {
        success = false
        res.message && this.$message.error(res.message)
      }
    })
  })
}
// 这里有两层 key
// 子组件传来的 key、value
// 更新 updateConfigs 来保存所有的变动信息
function handleUpdateConf({ catalogKey, bizId, section, keyName, strValue, oldStrValue, form }) {
  const updated = strValue !== oldStrValue
  if (!this.updateConfigs[bizId]) {
    this.$set(this.updateConfigs, bizId, {
      catalogKey,
      form,
      updateCount: 0,
      sections: {}
    })
  }
  const bizConf = this.updateConfigs[bizId]
  if (!bizConf.sections[section]) {
    this.$set(bizConf.sections, section, {
      configs: {}
    })
  }
  const sectionConf = bizConf.sections[section]
  const noUpdateCount = sectionConf.configs[keyName] && sectionConf.configs[keyName].updated === updated
  if (!noUpdateCount) {
    if (updated) {
      this.updateCount++
      this.catalogUpdateCount[this.catalogKey]++
      bizConf.updateCount++
    }
    else {
      this.updateCount--
      this.catalogUpdateCount[this.catalogKey]--
      bizConf.updateCount--
    }
  }
  this.$set(sectionConf.configs, keyName, {
    updated,
    strValue,
    oldStrValue
  })
}

// 保存接口,便利所有的 key
async function saveAllConfigs() {
  if (this.updateCount === 0) {
    this.$message.warning('未修改任何数据!')
    return
  }
  this.saveLoading = true
  const validations = []
  // 第一层 key 下面有 el-form 遍历所有表单进行验证
  Object.entries(this.updateConfigs).forEach((bizEntry) => {
    const [bizKey, bizValue] = bizEntry
    if (!bizValue.updateCount)
      return
    validations.push({
      bizKey,
      catalogKey: bizValue.catalogKey,
      validatePromise: bizValue.form.validate()
    })
  })
  let errorForm
  // 为每个 validate promise 添加 catch 方法
  const validationPromises = validations.map((validation) => {
    return validation.validatePromise.catch(() => {
      !errorForm && (errorForm = validation)
    })
  })
  await Promise.all(validationPromises).catch(() => {
    console.error('表单验证错误!')
  }).finally(() => {
    this.saveLoading = false
  })
  if (errorForm) {
    this.catalogKey = errorForm.catalogKey
    const first = this.$refs[errorForm.catalogKey] && this.$refs[errorForm.catalogKey][0]
    first && first.toBiz(errorForm.bizKey)
    this.$message.warning('请根据表单提示填写信息!')
    return
  }

  this.$confirm('修改配置可能导致系统重启,请确认是否修改!', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    this.saveLoading = true
    const paramsList = []
    // 便利两层 key 生成最后的参数列表
    Object.entries(this.updateConfigs).forEach((bizEntry) => {
      const [bizId, bizValue] = bizEntry
      if (!bizValue.updateCount)
        return
      Object.entries(bizValue.sections).forEach((sectionEntry) => {
        const [section, sectionValue] = sectionEntry
        Object.entries(sectionValue.configs).forEach((confEntry) => {
          const [keyName, conf] = confEntry
          const { updated, strValue } = conf
          if (!updated)
            return
          paramsList.push({ bizId, section, keyName, strValue })
        })
      })
    })
    let length = paramsList.length
    let success = true
    // 这里接口只能保存一个 key value 所以批量调用
    paramsList.forEach(async (params) => {
      const res = await api.rewriteValue(params).finally(() => {
        // 最后结束的时候,看,是不是所有的接口都返回正确了,如果是则成功,不然的话就是失败
        if (--length === 0) {
          this.saveLoading = false
          if (success) {
            const first = this.$refs[this.catalogKey] && this.$refs[this.catalogKey][0]
            const lastBizKey = first && first.getBizKey()
            lastBizKey && (this.lastSelectPane = [this.catalogKey, lastBizKey])
            this.tabList = []
            this.updateConfigs = {}
            this.catalogUpdateCount = {}
            this.updateCount = 0
            this.getTable()
            this.$message.success('保存配置成功!')
          }
          else {
            this.$message.error('保存配置失败!')
          }
        }
      }).catch((e) => {
        console.error(e)
        success = false
      })
      if (res.code !== 0) {
        success = false
        res.message && this.$message.error(res.message)
      }
    })
  })
}

el-from 使用后端接口校验

function validateByApi(url, tip) {
  return (_, value, callback) => {
    if (value !== '') {
      api.validateCron(url, value).then((res) => {
        res.code === 0 ? callback() : callback(new Error(tip))
      }).catch((_) => {
        callback(new Error('校验接口请求失败'))
      })
    }
    else {
      callback()
    }
  }
}
function validateByApi(url, tip) {
  return (_, value, callback) => {
    if (value !== '') {
      api.validateCron(url, value).then((res) => {
        res.code === 0 ? callback() : callback(new Error(tip))
      }).catch((_) => {
        callback(new Error('校验接口请求失败'))
      })
    }
    else {
      callback()
    }
  }
}

带有列表的表单的校验

比如有一个表单,是船舶合集,数据结构就像

const form = {
  collectionName: '',
  shipList: [
    { shipId: '', shipName: '' },
    { shipId: '', shipName: '' },
  ]
}
const form = {
  collectionName: '',
  shipList: [
    { shipId: '', shipName: '' },
    { shipId: '', shipName: '' },
  ]
}
<script>
const shipFormItems: CurdFormItems = [
  { label: '船舶名称', prop: 'shipId', map: enteredShipMap },
  { label: '船舶名称', prop: 'shipName', slot: 'shipName' },
]
function getFormItemsShip(index: number): CurdFormItems[] {
  const prefix = `shipList.${index}.`
  return shipFormItems.map(item => ({ ...item, itemProp: prefix + item.prop }))
}

const shipRules: FormRules = {
  shipId: [{ required: true, message: '请选择船舶名称' }],
  shipName: [{ required: true, message: '请选择船舶名称' }],
}

const rules = computed<FormRules>(() => ({
  collectionName: [{ required: true, message: '请输入船舶合集名称' }],
  ...form.value?.shipList?.reduce((acc: FormRules, cur, index) => {
    const prefix = `shipList.${index}.`
    return {
      ...acc,
      [`${prefix}shipId`]: shipRules.shipId,
      [`${prefix}shipName`]: shipRules.shipName,
    }
  }, {}),
}))
</script>
<script>
const shipFormItems: CurdFormItems = [
  { label: '船舶名称', prop: 'shipId', map: enteredShipMap },
  { label: '船舶名称', prop: 'shipName', slot: 'shipName' },
]
function getFormItemsShip(index: number): CurdFormItems[] {
  const prefix = `shipList.${index}.`
  return shipFormItems.map(item => ({ ...item, itemProp: prefix + item.prop }))
}

const shipRules: FormRules = {
  shipId: [{ required: true, message: '请选择船舶名称' }],
  shipName: [{ required: true, message: '请选择船舶名称' }],
}

const rules = computed<FormRules>(() => ({
  collectionName: [{ required: true, message: '请输入船舶合集名称' }],
  ...form.value?.shipList?.reduce((acc: FormRules, cur, index) => {
    const prefix = `shipList.${index}.`
    return {
      ...acc,
      [`${prefix}shipId`]: shipRules.shipId,
      [`${prefix}shipName`]: shipRules.shipName,
    }
  }, {}),
}))
</script>

缓存promise结果,并且配置在3小时内有效

export function useAsyncMemoizeStorage<Result extends Promise<unknown>, Args extends unknown[]>(resolver: (...args: Args) => Result, key: string, storage: Storage = localStorage) {
  const cache = ref(new Map<string, any>())
  const cacheStorage = useStorage(key, cache, storage)

  return async (...args: Args) => {
    const argKey = JSON.stringify(args)
    const lastModified = cacheStorage.value.get('last-modified') as number || 0
    if (cacheStorage.value.has(argKey) && Date.now() - lastModified < 3 * 60 * 60 * 1000)
      return cacheStorage.value.get(argKey) as Awaited<Result>
    const result = await resolver(...args)
    cacheStorage.value.set(argKey, result)
    cacheStorage.value.set('last-modified', Date.now())
    return result
  }
}
export function useAsyncMemoizeStorage<Result extends Promise<unknown>, Args extends unknown[]>(resolver: (...args: Args) => Result, key: string, storage: Storage = localStorage) {
  const cache = ref(new Map<string, any>())
  const cacheStorage = useStorage(key, cache, storage)

  return async (...args: Args) => {
    const argKey = JSON.stringify(args)
    const lastModified = cacheStorage.value.get('last-modified') as number || 0
    if (cacheStorage.value.has(argKey) && Date.now() - lastModified < 3 * 60 * 60 * 1000)
      return cacheStorage.value.get(argKey) as Awaited<Result>
    const result = await resolver(...args)
    cacheStorage.value.set(argKey, result)
    cacheStorage.value.set('last-modified', Date.now())
    return result
  }
}

test

function getDataAsync<T>(data: T, timeout: number): Promise<T> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(data)
    }, timeout)
  })
}

describe('useAsyncMemoizeStorage', () => {
  const getData = async <T>(data: T) => await getDataAsync(data, 100)
  const get = useAsyncMemoizeStorage(getData, 'test_useAsyncMemoizeStorage')
  it('should works', async () => {
    const time1 = Date.now()
    expect(await get(123)).toEqual(123)
    const time2 = Date.now()
    expect(time2 - time1 > 100).toBe(true)
    expect(await get(123)).toEqual(123)
    const time3 = Date.now()
    expect(time3 - time2 < 100).toBe(true)
  })
})
function getDataAsync<T>(data: T, timeout: number): Promise<T> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(data)
    }, timeout)
  })
}

describe('useAsyncMemoizeStorage', () => {
  const getData = async <T>(data: T) => await getDataAsync(data, 100)
  const get = useAsyncMemoizeStorage(getData, 'test_useAsyncMemoizeStorage')
  it('should works', async () => {
    const time1 = Date.now()
    expect(await get(123)).toEqual(123)
    const time2 = Date.now()
    expect(time2 - time1 > 100).toBe(true)
    expect(await get(123)).toEqual(123)
    const time3 = Date.now()
    expect(time3 - time2 < 100).toBe(true)
  })
})

返回