Commit f97f6052 by Sweet Zhang

搜索条件表单封装

parent 220d00dc
......@@ -36,7 +36,12 @@
.el-dialog__header.dialog-header.show-close {
padding-right: 0 ;
}
.statistics-container {
padding: 10px;
background-color: rgba(0,82,217,0.03);
border-radius: 4px;
margin-bottom: 5px;
}
#loader-wrapper {
position: fixed;
top: 0;
......
......@@ -226,7 +226,7 @@ export function policyData(data) {
// 应付款管理列表
export function expectedFortuneList(data) {
return request({
url: '/csf/api/expectedFortune/page',
url: '/csf/api/expectedFortune/list',
method: 'post',
data: data
})
......
// src/api/search.ts
import request from '@/utils/request'
// 特定模块搜索
// 搜索保险公司
export function searchInsurers(params) {
return request({
url: '/insurance/base/api/insuranceCompany/page',
method: 'POST',
params
})
}
// 搜索产品
export function searchProducts(params) {
return request({
url: '/product/base/api/product/search',
method: 'POST',
params
})
}
// 获取对账公司
export function searchReconciliationCompanies(params) {
return request({
url: '/insurance/base/api/insuranceReconciliationCompany/page',
method: 'POST',
params
})
}
// 获取签单员列表
export function searchSigners(params) {
return request({
url: '/insurance/base/api/userSignExpand/page',
method: 'POST',
params
})
}
// 获取转介人列表
export function searchIntermediaries(params) {
return request({
url: '/insurance/base/api/userSaleExpand/page',
method: 'POST',
params
})
}
<!-- src/components/RemoteMultiSelect.vue -->
<template>
<el-select
v-model="selectedValues"
:placeholder="placeholder"
:multiple="multiple"
:filterable="remote || filterable"
:remote="remote"
:remote-method="handleRemoteSearch"
:loading="loading"
:reserve-keyword="false"
:clearable="clearable"
:collapse-tags="collapseTags"
:max-collapse-tags="maxCollapseTags"
:size="size"
:popper-class="['remote-multi-select', popperClass]"
:disabled="disabled"
@change="handleChange"
@visible-change="handleVisibleChange"
>
<!-- 自定义下拉头部:全选功能 -->
<template #header v-if="showCheckAll && multiple">
<div class="select-header">
<el-checkbox
v-model="checkAll"
:indeterminate="indeterminate"
@change="handleCheckAllChange"
:disabled="disabled"
>
{{ checkAllLabel }}
</el-checkbox>
</div>
</template>
<!-- 选项列表 -->
<el-option
v-for="option in options"
:key="getOptionKey(option)"
:label="getOptionLabel(option)"
:value="getOptionValue(option)"
:disabled="option.disabled"
/>
<!-- 无数据时的提示 -->
<template #empty>
<div class="empty-options">
<span v-if="loading">加载中...</span>
<span v-else-if="options.length === 0 && !hasSearched">请输入关键词搜索</span>
<span v-else>无匹配数据</span>
</div>
</template>
</el-select>
</template>
<script setup lang="ts">
import { computed, ref, watch, toRefs, defineProps, defineEmits } from 'vue'
import { useRemoteSearch } from '@/hooks/useRemoteSearch'
import type { RemoteSearchConfig } from '@/hooks/useRemoteSearch'
interface OptionItem {
label: string
value: string | number
disabled?: boolean
[key: string]: any
}
interface Props {
modelValue: (string | number)[] | string | number
config: RemoteSearchConfig
placeholder?: string
multiple?: boolean
clearable?: boolean
collapseTags?: boolean
maxCollapseTags?: number
size?: 'large' | 'default' | 'small'
disabled?: boolean
showCheckAll?: boolean
checkAllLabel?: string
popperClass?: string
// 自定义选项键名
labelKey?: string
valueKey?: string
optionKey?: string
}
const props = withDefaults(defineProps<Props>(), {
modelValue: () => ([]),
placeholder: '请选择',
multiple: true,
clearable: true,
collapseTags: true,
maxCollapseTags: 1,
size: 'large',
disabled: false,
showCheckAll: true,
checkAllLabel: '全选',
labelKey: 'label',
valueKey: 'value',
optionKey: 'value'
})
const emit = defineEmits<{
'update:modelValue': [value: (string | number)[] | string | number]
'change': [value: (string | number)[] | string | number]
'search': [query: string]
}>()
const {
config,
multiple,
showCheckAll,
checkAllLabel,
labelKey,
valueKey,
optionKey
} = toRefs(props)
// 远程搜索实例
const remoteSearch = useRemoteSearch(config.value)
// 本地状态
const selectedValues = ref<(string | number)[] | string | number>(
Array.isArray(props.modelValue) ? [...props.modelValue] : props.modelValue
)
const hasSearched = ref(false)
const currentQuery = ref('')
// 计算属性
const loading = computed(() => remoteSearch.state.loading)
const options = computed(() => remoteSearch.state.options)
const checkAll = computed({
get() {
if (!props.multiple || !Array.isArray(selectedValues.value)) return false
const enabledOptions = options.value.filter(opt => !opt.disabled)
return selectedValues.value.length === enabledOptions.length && enabledOptions.length > 0
},
set(val: boolean) {
handleCheckAllChange(val)
}
})
const indeterminate = computed(() => {
if (!props.multiple || !Array.isArray(selectedValues.value)) return false
const enabledOptions = options.value.filter(opt => !opt.disabled)
return selectedValues.value.length > 0 && selectedValues.value.length < enabledOptions.length
})
// 方法
const getOptionKey = (option: OptionItem) => {
return option[optionKey.value] ?? option.value
}
const getOptionLabel = (option: OptionItem) => {
return option[labelKey.value] ?? option.label
}
const getOptionValue = (option: OptionItem) => {
return option[valueKey.value] ?? option.value
}
const handleRemoteSearch = async (query: string) => {
currentQuery.value = query
hasSearched.value = true
emit('search', query)
await remoteSearch.search(query)
}
const handleChange = (value: (string | number)[] | string | number) => {
selectedValues.value = value
emit('update:modelValue', value)
emit('change', value)
}
const handleCheckAllChange = (checked: boolean) => {
if (!props.multiple) return
if (checked) {
// 全选:只选择非禁用的选项
selectedValues.value = options.value
.filter(opt => !opt.disabled)
.map(opt => getOptionValue(opt))
} else {
selectedValues.value = []
}
emit('update:modelValue', selectedValues.value)
emit('change', selectedValues.value)
}
const handleVisibleChange = (visible: boolean) => {
if (visible && options.value.length === 0 && !hasSearched.value) {
// 下拉框打开时,如果没有搜索过,执行一次搜索
handleRemoteSearch('')
}
}
// 监听外部值变化
watch(() => props.modelValue, (newVal) => {
if (JSON.stringify(newVal) !== JSON.stringify(selectedValues.value)) {
selectedValues.value = newVal
}
}, { deep: true })
// 监听配置变化
watch(() => props.config, (newConfig) => {
// 重新初始化远程搜索实例(实际实现中可能需要更复杂的处理)
Object.assign(remoteSearch, useRemoteSearch(newConfig))
}, { deep: true })
// 初始化
if (config.value.defaultOptions?.length > 0) {
remoteSearch.setDefaultOptions(config.value.defaultOptions)
}
// 暴露方法给父组件
defineExpose({
search: handleRemoteSearch,
clearCache: remoteSearch.clearCache,
preload: remoteSearch.preload
})
</script>
<style scoped>
.select-header {
padding: 8px 12px;
border-bottom: 1px solid var(--el-border-color-lighter);
background-color: var(--el-bg-color);
.el-checkbox {
width: 100%;
.el-checkbox__label {
font-weight: 500;
color: var(--el-color-primary);
}
}
}
.empty-options {
padding: 8px 12px;
text-align: center;
color: var(--el-text-color-secondary);
font-size: 14px;
}
</style>
<style>
.remote-multi-select .el-select-dropdown__list {
padding-top: 0;
}
.remote-multi-select .el-select-dropdown__item {
padding: 8px 12px;
}
.remote-multi-select .el-select-dropdown__item.selected {
background-color: var(--el-color-primary-light-9);
}
.remote-multi-select .el-select-dropdown__item.is-disabled {
opacity: 0.6;
}
</style>
\ No newline at end of file
<template>
<el-select
ref="selectRef"
v-model="selectedValue"
:placeholder="placeholder"
:clearable="clearable"
:filterable="true"
:remote="true"
:remote-method="handleRemoteSearch"
:loading="loading"
:reserve-keyword="false"
:disabled="disabled"
:size="size"
:popper-class="['remote-select', popperClass]"
@change="handleChange"
@visible-change="handleVisibleChange"
>
<el-option
v-for="option in options"
:key="getOptionKey(option)"
:label="getOptionLabel(option)"
:value="getOptionValue(option)"
:disabled="option.disabled"
/>
<template #empty>
<div class="empty-options">
<span v-if="loading">加载中...</span>
<span v-else-if="options.length === 0 && !hasSearched">请输入关键词搜索</span>
<span v-else>无匹配数据</span>
</div>
</template>
</el-select>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { useRemoteSearch } from '@/hooks/useRemoteSearch'
import type { RemoteSearchConfig, FormOption } from '@/types/search-form'
interface Props {
modelValue: string | number
config: RemoteSearchConfig
placeholder?: string
clearable?: boolean
disabled?: boolean
size?: 'large' | 'default' | 'small'
popperClass?: string
optionLabel?: string
optionValue?: string
}
const props = withDefaults(defineProps<Props>(), {
modelValue: '',
placeholder: '请搜索选择',
clearable: true,
disabled: false,
size: 'default',
popperClass: '',
optionLabel: 'label',
optionValue: 'value'
})
const emit = defineEmits<{
'update:modelValue': [value: string | number]
'change': [value: string | number]
}>()
// 远程搜索实例
const remoteSearch = useRemoteSearch(props.config)
// 本地状态
const selectRef = ref()
const selectedValue = ref(props.modelValue)
const hasSearched = ref(false)
const currentQuery = ref('')
// 计算属性
const loading = computed(() => remoteSearch.state.loading)
const options = computed(() => remoteSearch.state.options)
// 方法
const getOptionKey = (option: FormOption) => {
return option[props.optionValue] ?? option.value
}
const getOptionLabel = (option: FormOption) => {
return option[props.optionLabel] ?? option.label
}
const getOptionValue = (option: FormOption) => {
return option[props.optionValue] ?? option.value
}
const handleRemoteSearch = async (query: string) => {
currentQuery.value = query
hasSearched.value = true
await remoteSearch.search(query)
}
const handleChange = (value: string | number) => {
selectedValue.value = value
emit('update:modelValue', value)
emit('change', value)
}
const handleVisibleChange = (visible: boolean) => {
if (visible && options.value.length === 0 && !hasSearched.value) {
handleRemoteSearch('')
}
}
// 监听外部值变化
watch(() => props.modelValue, (newVal) => {
if (newVal !== selectedValue.value) {
selectedValue.value = newVal
}
})
// 暴露方法
defineExpose({
search: handleRemoteSearch,
clearCache: remoteSearch.clearCache,
focus: () => selectRef.value?.focus()
})
// 初始化
onMounted(() => {
if (props.config.defaultOptions?.length > 0) {
remoteSearch.setDefaultOptions(props.config.defaultOptions)
}
})
</script>
\ No newline at end of file
<!-- components/SearchForm.vue -->
<template>
<el-form :model="formModel" label-width="100px" size="default" label-position="top">
<el-row :gutter="20">
<template v-for="item in config" :key="item.prop">
<!-- 所有字段都用相同的响应式布局 -->
<el-col :xs="24" :sm="12" :md="6" :lg="6" v-if="item.type === 'input'">
<el-form-item :label="item.label" :prop="item.prop">
<el-input
v-model="formModel[item.prop]"
:placeholder="item.placeholder || `请输入${item.label}`"
clearable
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="6" v-else-if="item.type === 'select' && item.api">
<el-form-item :label="item.label" :prop="item.prop">
<el-select
v-model="formModel[item.prop]"
:placeholder="item.placeholder || `请选择${item.label}`"
clearable
filterable
:filter-method="getFilterMethod(item)"
:loading="item.loading"
@focus="handleFocus(item)"
>
<el-option
v-for="opt in item.options"
:key="opt[item.valueKey || 'value']"
:label="opt[item.labelKey || 'label']"
:value="opt[item.valueKey || 'value']"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="6" v-else-if="item.type === 'select'">
<el-form-item :label="item.label" :prop="item.prop">
<el-select
v-model="formModel[item.prop]"
:placeholder="item.placeholder || `请选择${item.label}`"
clearable
:filterable="item.filterable !== false"
:multiple="item.multiple"
>
<el-option
v-for="opt in item.options"
:key="opt[item.valueKey || 'value']"
:label="opt[item.labelKey || 'label']"
:value="opt[item.valueKey || 'value']"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="6" v-else-if="item.type === 'checkbox-group'">
<el-form-item :label="item.label" :prop="item.prop">
<el-checkbox-group v-model="formModel[item.prop]">
<el-checkbox
v-for="opt in item.options"
:key="opt[item.valueKey || 'value']"
:label="opt[item.valueKey || 'value']"
>
{{ opt[item.labelKey || 'label'] }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="6" v-else-if="item.type === 'date'">
<el-form-item :label="item.label" :prop="item.prop">
<el-date-picker
v-model="formModel[item.prop]"
type="date"
:placeholder="item.placeholder || `请选择${item.label}`"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
clearable
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="6" v-else-if="item.type === 'daterange'">
<el-form-item :label="item.label" :prop="item.prop">
<el-date-picker
v-model="formModel[item.prop]"
type="daterange"
:range-separator="item.rangeSeparator || '至'"
:start-placeholder="item.startPlaceholder || '开始日期'"
:end-placeholder="item.endPlaceholder || '结束日期'"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
clearable
/>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
</template>
<script setup>
import { reactive, onMounted } from 'vue'
import request from '@/utils/request'
function debounce(func, wait) {
let timeout
return function (...args) {
clearTimeout(timeout)
timeout = setTimeout(() => func.apply(this, args), wait)
}
}
const props = defineProps({
config: { type: Array, required: true }
})
const formModel = reactive({})
// ========================
// 公共请求方法
// ========================
async function fetchOptions(item, keyword = '') {
const payload = {
...(item.requestParams || {}),
pageNo: item.pageNo || 1,
pageSize: item.pageSize || 20
}
if (keyword) {
const keyField = item.keywordField || 'keyword'
payload[keyField] = keyword.trim()
}
const res = await request({
url: item.api,
method: item.method || 'POST',
data: payload
})
let list = []
if (typeof item.transform === 'function') {
list = item.transform(res)
} else if (res?.data?.records) {
list = res.data.records
} else if (res?.data?.list) {
list = res.data.list
} else if (Array.isArray(res?.data)) {
list = res.data
} else if (res?.data?.data && Array.isArray(res.data.data)) {
list = res.data.data
}
return list
}
// ========================
// 创建搜索函数(带防抖)
// ========================
function createSearchFn(item) {
const search = async (keyword) => {
item.loading = true
try {
if (!keyword) {
// 关键词清空 → 恢复默认选项(从缓存)
item.options = item._defaultOptions ? [...item._defaultOptions] : []
} else {
// 有关键词 → 远程搜索
const list = await fetchOptions(item, keyword)
item.options = list
}
} catch (e) {
console.error(`${item.label} 搜索失败`, e)
item.options = []
} finally {
item.loading = false
}
}
return debounce(search, item.debounceWait ?? 300)
}
// ========================
// 初始化
// ========================
onMounted(async () => {
for (const item of props.config) {
// 初始化表单值
if (['checkbox-group', 'daterange'].includes(item.type)) {
formModel[item.prop] = item.defaultValue ?? []
} else {
formModel[item.prop] = item.defaultValue ?? ''
}
// 处理带 api 的 select(hybrid 模式)
if (item.type === 'select' && item.api) {
item.options = []
item.loading = true
try {
// 1. 加载默认数据(不传 keyword)
const defaultList = await fetchOptions(item, '')
item._defaultOptions = [...defaultList] // 缓存
item.options = defaultList
} catch (e) {
console.error(`加载 ${item.label} 默认选项失败`, e)
item.options = []
} finally {
item.loading = false
}
}
}
})
// ========================
// 提供给 el-select 的 filter-method
// ========================
function getFilterMethod(item) {
if (!item._searchFn) {
item._searchFn = createSearchFn(item)
}
return (keyword) => {
item._searchFn(keyword)
return true // 必须返回 true,否则下拉框会关闭
}
}
// 可选:聚焦时确保有数据(一般不需要,因为 onMounted 已加载)
function handleFocus(item) {
// 如果意外为空,可重试
if (item.type === 'select' && item.api && (!item.options || item.options.length === 0)) {
// 可加 loading 重试逻辑(略)
}
}
// 暴露方法
defineExpose({
getSearchParams() {
const params = {}
for (const key in formModel) {
const val = formModel[key]
if (val === '' || val === null || val === undefined) continue
if (Array.isArray(val) && val.length === 0) continue
params[key] = val
}
return params
},
resetForm() {
props.config.forEach(item => {
if (['checkbox-group', 'daterange'].includes(item.type)) {
formModel[item.prop] = []
} else {
formModel[item.prop] = ''
}
})
},
getRawForm() {
return { ...formModel }
}
})
</script>
\ No newline at end of file
// src/hooks/useRemoteSearch.ts
import { ref, reactive, onUnmounted } from 'vue'
import type { Ref } from 'vue'
export interface SearchOption {
label: string
value: string | number
[key: string]: any
}
export interface RemoteSearchConfig {
type: string
apiMethod: (params: any) => Promise<any>
formatResult?: (data: any[]) => SearchOption[]
cacheKey?: string
debounceDelay?: number
defaultOptions?: SearchOption[]
}
export interface RemoteSearchState {
loading: boolean
options: SearchOption[]
cache: Map<string, SearchOption[]>
}
export function useRemoteSearch(config: RemoteSearchConfig) {
const {
type,
apiMethod,
formatResult = defaultFormatResult,
cacheKey = type,
debounceDelay = 500,
defaultOptions = []
} = config
// 状态
const state = reactive<RemoteSearchState>({
loading: false,
options: [...defaultOptions],
cache: new Map()
})
// 防抖相关
let debounceTimer: NodeJS.Timeout | null = null
let lastQuery = ''
// 默认格式化函数
function defaultFormatResult(data: any[]): SearchOption[] {
return data.map(item => ({
label: item.name || item.label || item.text || String(item.value),
value: item.id || item.value || item.key,
...item
}))
}
// 从缓存获取
function getFromCache(query: string): SearchOption[] | null {
const cacheKeyWithQuery = `${cacheKey}:${query || 'all'}`
return state.cache.get(cacheKeyWithQuery) || null
}
// 保存到缓存
function saveToCache(query: string, data: SearchOption[]) {
const cacheKeyWithQuery = `${cacheKey}:${query || 'all'}`
state.cache.set(cacheKeyWithQuery, data)
// 限制缓存大小(最多100条记录)
if (state.cache.size > 100) {
const firstKey = state.cache.keys().next().value
state.cache.delete(firstKey)
}
}
// 执行搜索
async function performSearch(query: string): Promise<SearchOption[]> {
// 检查缓存
const cached = getFromCache(query)
if (cached && cached.length > 0) {
return cached
}
state.loading = true
try {
// 调用API
const params = query ? { keyword: query, pageSize: 50 } : { pageSize: 100 }
const response = await apiMethod(params)
// 格式化结果
const result = formatResult(response.data || response.list || response.records || [])
// 保存到缓存
saveToCache(query, result)
return result
} catch (error) {
console.error(`远程搜索失败 [${type}]:`, error)
throw error
} finally {
state.loading = false
}
}
// 搜索方法(带防抖)
async function search(query: string = ''): Promise<SearchOption[]> {
// 清除之前的定时器
if (debounceTimer) {
clearTimeout(debounceTimer)
debounceTimer = null
}
// 如果查询相同,直接返回当前选项
if (query === lastQuery) {
return state.options
}
lastQuery = query
// 如果是空查询且有默认选项,直接返回
if (!query && defaultOptions.length > 0) {
state.options = defaultOptions
return defaultOptions
}
return new Promise((resolve) => {
debounceTimer = setTimeout(async () => {
try {
const result = await performSearch(query)
state.options = result
resolve(result)
} catch (error) {
state.options = []
resolve([])
}
}, debounceDelay)
})
}
// 预加载数据(初始化时调用)
async function preload(): Promise<void> {
if (state.options.length === 0) {
await search('')
}
}
// 清空缓存
function clearCache(): void {
state.cache.clear()
state.options = [...defaultOptions]
}
// 设置默认选项
function setDefaultOptions(options: SearchOption[]): void {
defaultOptions.length = 0
defaultOptions.push(...options)
if (state.options.length === 0) {
state.options = [...options]
}
}
// 组件卸载时清理
onUnmounted(() => {
if (debounceTimer) {
clearTimeout(debounceTimer)
}
})
return {
state,
search,
preload,
clearCache,
setDefaultOptions,
loading: () => state.loading,
options: () => state.options
}
}
// 创建多搜索实例的管理器
export function useRemoteSearchManager() {
const instances = new Map<string, ReturnType<typeof useRemoteSearch>>()
function getInstance(config: RemoteSearchConfig) {
const { type } = config
if (!instances.has(type)) {
instances.set(type, useRemoteSearch(config))
}
return instances.get(type)!
}
function clearAllCache() {
instances.forEach(instance => {
instance.clearCache()
})
}
return {
getInstance,
clearAllCache
}
}
\ No newline at end of file
......@@ -210,46 +210,79 @@ export function getTime(type) {
}
/**
* @param {Function} func
* @param {number} wait
* @param {boolean} immediate
* @return {*}
* 修复后的防抖函数(支持立即执行、手动取消、返回值)
* @param {Function} func 要防抖的函数
* @param {number} [wait=300] 延迟时间(毫秒)
* @param {boolean} [immediate=false] 是否立即执行
* @returns {Function} 包装后的防抖函数(包含cancel方法)
*/
export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result
export function debounce(func, wait = 300, immediate = false) {
let timeout = null; // 定时器标识
let args = null; // 缓存函数参数
let context = null; // 缓存函数上下文
let timestamp = 0; // 最后一次触发的时间戳
let result = undefined; // 函数执行结果
// 延迟执行的核心逻辑
const later = function() {
// 据上一次触发时间间隔
const last = +new Date() - timestamp
// 计算最后一次触发与现在的时间差
const last = Date.now() - timestamp;
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
// 如果时间差小于wait且大于0,说明还在防抖窗口期,重新设置定时器
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
timeout = setTimeout(later, wait - last);
} else {
timeout = null
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
// 窗口期结束,清空定时器
timeout = null;
// 非立即执行的情况,在此处执行原函数
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
result = func.apply(context, args);
// 执行后清空上下文和参数,避免内存泄漏
if (!timeout) context = args = null;
}
}
};
// 包装后的防抖函数
const debounced = function(...params) {
// 实时捕获当前的上下文和参数
context = this;
args = params;
// 记录最后一次触发的时间戳
timestamp = Date.now();
// 判断是否需要立即执行
const callNow = immediate && !timeout;
// 清除旧的定时器,避免多次执行
if (timeout) clearTimeout(timeout);
// 设置新的定时器
timeout = setTimeout(later, wait);
return function(...args) {
context = this
timestamp = +new Date()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
// 立即执行的情况,直接调用原函数
if (callNow) {
result = func.apply(context, args)
context = args = null
result = func.apply(context, args);
// 清空上下文和参数
context = args = null;
}
return result
}
// 返回函数执行结果
return result;
};
// 手动取消防抖的方法
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
context = args = null;
timestamp = 0;
};
return debounced;
}
/**
* This is just a simple version of deep copy
* Has a lot of edge cases bug
......@@ -390,6 +423,6 @@ export function isNumberStr(str) {
// 数字千分位格式化,保留2位小数
export function numberWithCommas(x, fixed = 2) {
if (!x) return '0.00'
return x.toFixed(fixed).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment