【调整】更新API表单输入,新增密码类型和显示密码功能

【调整】登录页表单校验调整
【调整】新增阿里云dcdn部署类型
This commit is contained in:
chenzhihua
2025-07-14 10:26:51 +08:00
parent 8bdf9d6b46
commit 2a50e9c4ad
65 changed files with 382 additions and 50 deletions

View File

@@ -11370,5 +11370,39 @@
"arDZ": "صيغة اسم المجال خاطئة"
},
"timestamp": "2025-06-21T01:26:22.154Z"
},
"阿里云DCDN": {
"text": "阿里云DCDN",
"key": "t_0_1752230148946",
"translations": {
"zhCN": "阿里云DCDN",
"zhTW": "阿里雲DCDN",
"enUS": "Alibaba Cloud DCDN",
"jaJP": "アリババクラウドDCDN",
"koKR": "알리바바 클라우드 DCDN",
"ruRU": "Alibaba Cloud DCDN",
"ptBR": "Alibaba Cloud DCDN",
"frFR": "Alibaba Cloud DCDN",
"esAR": "Alibaba Cloud DCDN",
"arDZ": "Alibaba Cloud DCDN"
},
"timestamp": "2025-07-11T10:35:48.946Z"
},
"阿里云ESA": {
"text": "阿里云ESA",
"key": "t_1_1752230146379",
"translations": {
"zhCN": "阿里云ESA",
"zhTW": "阿里雲ESA",
"enUS": "Aliyun ESA",
"jaJP": "アリババクラウドESA",
"koKR": "알리바바 클라우드 ESA",
"ruRU": "Алибаба Клауд ESA",
"ptBR": "Aliyun ESA",
"frFR": "Aliyun ESA",
"esAR": "Aliyun ESA",
"arDZ": "علي بابا كلاود ESA"
},
"timestamp": "2025-07-11T10:35:48.948Z"
}
}

View File

@@ -244,6 +244,7 @@ export interface DeployConfig<
| 'tencentcloud-waf'
| 'tencentcloud-teo'
| 'aliyun-cdn'
| 'aliyun-dcdn'
| 'aliyun-oss'
| 'aliyun-waf'
| 'aliyun-esa'

View File

@@ -85,9 +85,10 @@ export const ApiProjectConfig: Record<string, ApiProjectType> = {
type: ['host', 'dns'],
hostRelated: {
cdn: { name: $t('t_16_1745735766712') },
dcdn: { name: $t('t_0_1752230148946') },
oss: { name: $t('t_2_1746697487164') },
waf: { name: $t('t_10_1744958860078') },
esa: { name: '阿里云ESA' },
esa: { name: $t('t_1_1752230146379') },
},
sort: 6,
},
@@ -255,5 +256,3 @@ export const ApiProjectConfig: Record<string, ApiProjectType> = {
sort: 29,
},
}

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "Alibaba Cloud DCDN",
"t_1_1752230146379": "علي بابا كلاود ESA",
"t_0_1744098811152": "تحذير: لقد دخلتم منطقة غير معروفة، الصفحة التي تحاول زيارتها غير موجودة، يرجى الضغط على الزر للعودة إلى الصفحة الرئيسية.",
"t_1_1744098801860": "رجوع إلى الصفحة الرئيسية",
"t_2_1744098804908": "نصيحة أمنية: إذا كنت تعتقد أن هذا خطأ، يرجى الاتصال بالمدير على الفور",

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "Alibaba Cloud DCDN",
"t_1_1752230146379": "Aliyun ESA",
"t_0_1744098811152": "Warning: You have entered an unknown area, the page you are visiting does not exist, please click the button to return to the homepage.",
"t_1_1744098801860": "Return Home",
"t_2_1744098804908": "Safety Tip: If you think this is an error, please contact the administrator immediately",

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "Alibaba Cloud DCDN",
"t_1_1752230146379": "Aliyun ESA",
"t_0_1744098811152": "Advertencia: Ha ingresado a una zona desconocida, la página que intenta visitar no existe, por favor, haga clic en el botón para regresar a la página de inicio.",
"t_1_1744098801860": "Volver al inicio",
"t_2_1744098804908": "Consejo de seguridad: Si piensa que es un error, póngase en contacto con el administrador inmediatamente",

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "Alibaba Cloud DCDN",
"t_1_1752230146379": "Aliyun ESA",
"t_0_1744098811152": "Avertissement : Vous avez entré dans une zone inconnue, la page que vous visitez n'existe pas, veuillez cliquer sur le bouton pour revenir à la page d'accueil.",
"t_1_1744098801860": "Retour à l'accueil",
"t_2_1744098804908": "Avis de sécurité : Si vous pensez que c'est une erreur, veuillez contacter l'administrateur immédiatement",

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "アリババクラウドDCDN",
"t_1_1752230146379": "アリババクラウドESA",
"t_0_1744098811152": "警告:未知のエリアに進入しました。アクセスしようとしたページは存在しません。ボタンをクリックしてホームページに戻ってください。",
"t_1_1744098801860": "ホームに戻る",
"t_2_1744098804908": "安全注意:これが誤りだと思われる場合は、すぐに管理者に連絡してください",

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "알리바바 클라우드 DCDN",
"t_1_1752230146379": "알리바바 클라우드 ESA",
"t_0_1744098811152": "경고: 알 수 없는 영역에 진입했습니다. 방문하려는 페이지가 존재하지 않습니다. 버튼을 클릭하여 홈페이지로 돌아가세요。",
"t_1_1744098801860": "홈으로 돌아가기",
"t_2_1744098804908": "안전 유의사항: 이가 오류라면 즉시 관리자에게 연락하십시오",

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "Alibaba Cloud DCDN",
"t_1_1752230146379": "Aliyun ESA",
"t_0_1744098811152": "Aviso: Você entrou em uma área desconhecida, a página que você está visitando não existe, por favor, clique no botão para voltar para a página inicial.",
"t_1_1744098801860": "Voltar para a homepage",
"t_2_1744098804908": "Dica de Segurança: Se você acha que isso é um erro, entre em contato com o administrador imediatamente",

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "Alibaba Cloud DCDN",
"t_1_1752230146379": "Алибаба Клауд ESA",
"t_0_1744098811152": "Предупреждение: Вы вошли в неизвестную зону, посещаемая страница не существует, пожалуйста, нажмите кнопку, чтобы вернуться на главную страницу.",
"t_1_1744098801860": "Вернуться на главную",
"t_2_1744098804908": "Совет по безопасности: Если вы считаете, что это ошибка, немедленно свяжитесь с администратором",

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "阿里云DCDN",
"t_1_1752230146379": "阿里云ESA",
"t_0_1744098811152": "警告:您已进入未知区域,所访问的页面不存在,请点击按钮返回首页。",
"t_1_1744098801860": "返回首页",
"t_2_1744098804908": "安全提示:如果您认为这是个错误,请立即联系管理员",

View File

@@ -1,4 +1,6 @@
{
"t_0_1752230148946": "阿里雲DCDN",
"t_1_1752230146379": "阿里雲ESA",
"t_0_1744098811152": "警告:您已進入未知區域,所訪問的頁面不存在,請點擊按鈕返回首頁。",
"t_1_1744098801860": "返回首頁",
"t_2_1744098804908": "安全提示:如果您認為這是個錯誤,請立即聯繫管理員",

View File

@@ -618,6 +618,8 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
]),
(param.value.config as SshAccessConfig)?.mode === 'password'
? useFormInput($t('t_48_1745289355714'), 'config.password', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
})
: useFormTextarea($t('t_1_1746667588689'), 'config.key', {
@@ -655,6 +657,8 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
allowInput: noSideSpace,
}),
useFormInput($t('t_55_1745289355715'), 'config.api_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
useFormSwitch(
@@ -676,6 +680,8 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
param.value.type === 'safeline' ? $t('t_1_1747617105179') : $t('t_55_1745289355715'),
param.value.type === 'safeline' ? 'config.api_token' : 'config.api_key',
{
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
},
),
@@ -690,13 +696,21 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
case 'aliyun':
items.push(
useFormInput('AccessKeyId', 'config.access_key_id', { allowInput: noSideSpace }),
useFormInput('AccessKeySecret', 'config.access_key_secret', { allowInput: noSideSpace }),
useFormInput('AccessKeySecret', 'config.access_key_secret', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'tencentcloud':
items.push(
useFormInput('SecretId', 'config.secret_id', { allowInput: noSideSpace }),
useFormInput('SecretKey', 'config.secret_key', { allowInput: noSideSpace }),
useFormInput('SecretKey', 'config.secret_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'huaweicloud':
@@ -705,13 +719,21 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
case 'doge':
items.push(
useFormInput('AccessKey', 'config.access_key', { allowInput: noSideSpace }),
useFormInput('SecretKey', 'config.secret_key', { allowInput: noSideSpace }),
useFormInput('SecretKey', 'config.secret_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'cloudflare':
items.push(
useFormInput('邮箱', 'config.email', { allowInput: noSideSpace }, { showRequireMark: false }),
useFormInput('APIKey', 'config.api_key', { allowInput: noSideSpace }),
useFormInput('APIKey', 'config.api_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
useFormCustom(() => {
return (
<NAlert type="error" class="mt-[1.2rem] whitespace-nowrap" showIcon={false}>
@@ -724,76 +746,144 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
case 'westcn':
items.push(
useFormInput('Username', 'config.username', { allowInput: noSideSpace }),
useFormInput('Password', 'config.password', { allowInput: noSideSpace }),
useFormInput('Password', 'config.password', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'godaddy':
items.push(
useFormInput('API Key', 'config.api_key', { allowInput: noSideSpace }),
useFormInput('API Secret', 'config.api_secret', { allowInput: noSideSpace }),
useFormInput('API Key', 'config.api_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
useFormInput('API Secret', 'config.api_secret', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'qiniu':
items.push(
useFormInput('AccessKey', 'config.access_key', { allowInput: noSideSpace }),
useFormInput('AccessSecret', 'config.access_secret', { allowInput: noSideSpace }),
useFormInput('AccessSecret', 'config.access_secret', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'namecheap':
items.push(
useFormInput('API User', 'config.api_user', { allowInput: noSideSpace }),
useFormInput('API Key', 'config.api_key', { allowInput: noSideSpace }),
useFormInput('API Key', 'config.api_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'ns1':
items.push(useFormInput('API Key', 'config.api_key', { allowInput: noSideSpace }))
items.push(
useFormInput('API Key', 'config.api_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'cloudns':
items.push(
useFormInput('Auth ID', 'config.auth_id', { allowInput: noSideSpace }),
useFormInput('Auth Password', 'config.auth_password', { allowInput: noSideSpace }),
useFormInput('Auth Password', 'config.auth_password', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'aws':
items.push(
useFormInput('Access Key ID', 'config.access_key_id', { allowInput: noSideSpace }),
useFormInput('Secret Access Key', 'config.secret_access_key', { allowInput: noSideSpace }),
useFormInput('Secret Access Key', 'config.secret_access_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'azure':
items.push(
useFormInput('Tenant ID', 'config.tenant_id', { allowInput: noSideSpace }),
useFormInput('Client ID', 'config.client_id', { allowInput: noSideSpace }),
useFormInput('Client Secret', 'config.client_secret', { allowInput: noSideSpace }),
useFormInput('Client Secret', 'config.client_secret', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
useFormInput('Environment', 'config.environment', { allowInput: noSideSpace, placeholder: 'public' }),
)
break
case 'namesilo':
items.push(useFormInput('API Key', 'config.api_key', { allowInput: noSideSpace }))
items.push(
useFormInput('API Key', 'config.api_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'namedotcom':
items.push(
useFormInput('Username', 'config.username', { allowInput: noSideSpace }),
useFormInput('API Token', 'config.api_token', { allowInput: noSideSpace }),
useFormInput('API Token', 'config.api_token', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'bunny':
items.push(useFormInput('API Key', 'config.api_key', { allowInput: noSideSpace }))
items.push(
useFormInput('API Key', 'config.api_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'gcore':
items.push(useFormInput('API Token', 'config.api_token', { allowInput: noSideSpace }))
items.push(
useFormInput('API Token', 'config.api_token', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'jdcloud':
items.push(
useFormInput('Access Key ID', 'config.access_key_id', { allowInput: noSideSpace }),
useFormInput('Secret Access Key', 'config.secret_access_key', { allowInput: noSideSpace }),
useFormInput('Secret Access Key', 'config.secret_access_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'lecdn':
items.push(
useFormInput('URL', 'config.url', { allowInput: noSideSpace }),
useFormInput('Username', 'config.username', { allowInput: noSideSpace }),
useFormInput('Password', 'config.password', { allowInput: noSideSpace }),
useFormInput('Password', 'config.password', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
useFormSwitch(
$t('t_3_1746667592270'),
'config.ignore_ssl',
@@ -804,8 +894,16 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
break
case 'constellix':
items.push(
useFormInput('API Key', 'config.api_key', { allowInput: noSideSpace }),
useFormInput('Secret Key', 'config.secret_key', { allowInput: noSideSpace }),
useFormInput('API Key', 'config.api_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
useFormInput('Secret Key', 'config.secret_key', {
type: 'password',
showPasswordOn: 'click',
allowInput: noSideSpace,
}),
)
break
case 'plugin':

View File

@@ -205,6 +205,7 @@ export default defineComponent({
case 'tencentcloud-waf':
case 'tencentcloud-teo':
case 'aliyun-cdn':
case 'aliyun-dcdn':
case 'baidu-cdn':
case 'qiniu-cdn':
case 'qiniu-oss':

View File

@@ -12,6 +12,7 @@ import {
} from '@baota/naive-ui/hooks'
import { useError } from '@baota/hooks/error'
import { $t } from '@locales/index'
import { getDaysDiff } from '@baota/utils/date'
import { useStore } from './useStore'
@@ -22,6 +23,41 @@ const { useFormTextarea } = useFormHooks()
const { fetchCertList, downloadExistingCert, deleteExistingCert, uploadNewCert, uploadForm, resetUploadForm } =
useStore()
const { confirm } = useModalHooks()
/**
* 计算证书剩余天数
* @param cert 证书项
* @returns 剩余天数,如果无法计算则返回 null
*/
const calculateRemainingDays = (cert: CertItem): number | null => {
// 首先尝试使用后端提供的 end_day 字段
const endDay = Number(cert.end_day)
if (!isNaN(endDay) && endDay !== 0) {
return endDay
}
// 如果 end_day 无效,则根据 end_time 计算
if (cert.end_time) {
try {
const endTime = new Date(cert.end_time)
const currentTime = new Date()
// 检查日期是否有效
if (isNaN(endTime.getTime())) {
return null
}
// 计算剩余天数
const endDay = getDaysDiff(currentTime, endTime)
return endDay
} catch (error) {
console.warn('计算证书剩余天数失败:', error)
return null
}
}
return null
}
/**
* useController
* @description 证书管理业务逻辑控制器
@@ -60,13 +96,27 @@ export const useController = () => {
key: 'end_day',
width: 100,
render: (row: CertItem) => {
const endDay = Number(row.end_day)
const endDay = calculateRemainingDays(row)
// 如果无法计算剩余天数,显示获取失败
if (endDay === null) {
return (
<NTag type="error" size="small">
</NTag>
)
}
// 根据剩余天数确定显示样式和文本
const config = [
[endDay <= 0, 'error', $t('t_0_1746001199409')],
[endDay < 30, 'warning', $t('t_1_1745999036289', { days: row.end_day })],
[endDay > 30, 'success', $t('t_0_1745999035681', { days: row.end_day })],
[endDay < 30, 'warning', $t('t_1_1745999036289', { days: endDay })],
[endDay >= 30, 'success', $t('t_0_1745999035681', { days: endDay })],
] as [boolean, 'success' | 'error' | 'warning' | 'default' | 'info' | 'primary', string][]
const [_, type, text] = config.find((item) => item[0]) ?? ['default', 'error', '获取失败']
const matchedConfig = config.find((item) => item[0])
const [, type, text] = matchedConfig ?? ['default', 'error', '获取失败']
return (
<NTag type={type} size="small">
{text}
@@ -116,7 +166,13 @@ export const useController = () => {
* - 空字符串:其他情况。
*/
const getRowClassName = (row: CertItem): string => {
const endDay = Number(row.end_day)
const endDay = calculateRemainingDays(row)
// 如果无法计算剩余天数,不应用特殊样式
if (endDay === null) {
return ''
}
if (endDay <= 0) {
return 'bg-red-500/10' // Tailwind class for light red background
}

View File

@@ -15,7 +15,7 @@ import styles from './index.module.css'
export default defineComponent({
name: 'LoginView',
setup() {
const { loading, error, rememberMe, handleSubmit, handleKeyup, loginData, handleGetCode, codeImg, mustCode } =
const { loading, error, rememberMe, handleSubmit, handleKeyup, loginData, handleGetCode, codeImg, mustCode, formRef } =
useController()
const { isDark } = useTheme()
const cssVar = useThemeCssVar(['textColor2', 'actionColor', 'errorColor', 'primaryColor', 'primaryColorSuppl'])
@@ -39,7 +39,7 @@ export default defineComponent({
<div class={styles.rightSection}>
<div class={styles.formContainer}>
<h1 class={styles.title}>{$t('t_2_1744164839713')}</h1>
<NForm onSubmit={handleSubmit} class={styles.formWrapper}>
<NForm ref={formRef} model={loginData.value} onSubmit={handleSubmit} class={styles.formWrapper}>
<div class={styles.formContent}>
<div class={styles.formInputs}>
<NFormItem
@@ -84,7 +84,11 @@ export default defineComponent({
<NFormItem
show-label={false}
path="code"
rule={{ required: true, message: $t('t_25_1745289355721'), trigger: ['input', 'blur'] }}
rule={{
required: mustCode.value,
message: $t('t_25_1745289355721'),
trigger: ['input', 'blur']
}}
>
<NInput
onKeyup={handleKeyup}

View File

@@ -1,12 +1,12 @@
// External Libraries
import md5 from 'crypto-js/md5'
import type { FormInst } from 'naive-ui'
// Type Imports
import type { LoginParams } from '@/types/login'
// Absolute Internal Imports
import { useError } from '@baota/hooks/error'
import { $t } from '@locales/index'
// Relative Internal Imports
import { useStore } from './useStore'
@@ -51,6 +51,7 @@ const setRememberData = (username: string, password: string): void => {
*/
interface LoginControllerExposes extends ReturnType<typeof useStore> {
// 继承自 useStore 的返回类型
formRef: Ref<FormInst | null>
handleSubmit: (event: Event) => Promise<void>
handleKeyup: (event: KeyboardEvent) => void
handleLogin: (params: LoginParams) => Promise<void> // 覆盖 store 中的 handleLogin
@@ -69,25 +70,14 @@ export const useController = (): LoginControllerExposes => {
// 从 store 中解构需要的状态和方法
const { error, loginData, handleLogin: storeHandleLogin, rememberMe, checkMustCode, mustCode, handleGetCode } = store
// 表单引用
const formRef = ref<FormInst | null>(null)
/**
* @description 处理登录业务逻辑,包括表单验证和密码加密
* @param params - 登录参数 (用户名、密码等)
*/
const handleLoginBusiness = async (params: LoginParams): Promise<void> => {
// 表单验证
if (!params.username.trim()) {
error.value = $t('t_3_1744164839524') // 请输入用户名
return
}
if (!params.password.trim()) {
error.value = $t('t_4_1744164840458') // 请输入密码
return
}
if (mustCode.value && !params.code?.trim()) {
error.value = $t('t_25_1745289355721') // 请输入验证码
return
}
try {
const encryptedPassword = encryptPassword(params.password)
await storeHandleLogin({ ...params, password: encryptedPassword }) // 调用 store 中的登录方法
@@ -117,7 +107,18 @@ export const useController = (): LoginControllerExposes => {
*/
const handleSubmit = async (event: Event): Promise<void> => {
event.preventDefault()
await handleLoginBusiness(loginData.value)
// 使用 NForm 的校验机制
if (!formRef.value) return
try {
await formRef.value.validate()
// 校验通过,执行登录逻辑
await handleLoginBusiness(loginData.value)
} catch (validationErrors) {
// 校验失败NForm 会自动显示错误信息
console.log('表单校验失败:', validationErrors)
}
}
/**
@@ -163,6 +164,7 @@ export const useController = (): LoginControllerExposes => {
// ==================== 返回值 ====================
return {
...store, // 暴露 store 中的所有属性和方法
formRef,
handleSubmit,
handleKeyup,
handleLogin: handleLoginBusiness, // 控制器封装的登录逻辑

View File

@@ -96,6 +96,10 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
const { page, pageSize } = alias
const pageSizeOptionsRef = ref([10, 20, 50, 100, 200]) // 分页选项
// 防重复请求相关状态
const lastDirectRequestTime = ref(0) // 记录最后一次直接请求的时间
const REQUEST_DEBOUNCE_DELAY = 100 // 防抖延迟时间(毫秒)
// 初始化分页参数
if ((param.value as Record<string, unknown>)[page]) {
;(param.value as Record<string, unknown>)[page] = 1 // 当前页码
@@ -111,6 +115,8 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
* @param currentPage 当前页码
*/
const handlePageChange = (currentPage: number) => {
// 记录直接请求时间,防止 watch 重复触发
lastDirectRequestTime.value = Date.now()
;(param.value as Record<string, unknown>)[page] = currentPage
fetchData()
}
@@ -120,6 +126,8 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
* @param size 每页条数
*/
const handlePageSizeChange = (size: number) => {
// 记录直接请求时间,防止 watch 重复触发
lastDirectRequestTime.value = Date.now()
// 保存到本地存储
savePageSizeToStorage(storage, size)
;(param.value as Record<string, unknown>)[page] = 1 // 重置页码为1
@@ -211,7 +219,20 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
if (Array.isArray(watchValue)) {
// 只监听指定的字段
const source = computed(() => watchValue.map((key) => param.value[key]))
watch(source, (value) => fetchData(), { deep: true })
watch(
source,
() => {
// 检查是否刚刚有直接请求,如果是则跳过此次 watch 触发的请求
const timeSinceLastDirectRequest = Date.now() - lastDirectRequestTime.value
if (timeSinceLastDirectRequest < REQUEST_DEBOUNCE_DELAY) {
console.log('跳过 watch 触发的重复请求,距离上次直接请求:', timeSinceLastDirectRequest, 'ms')
return
}
console.log('watch 触发请求,距离上次直接请求:', timeSinceLastDirectRequest, 'ms')
fetchData()
},
{ deep: true },
)
}
onUnmounted(() => {