feat: 补全赛事管理下所有页面 补全系统管理下所有页面
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 修改密码弹窗 - 表单域控制器
|
||||
*
|
||||
* 集中管理弹窗内的所有规则与提示文案。
|
||||
*/
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import { isValidPassword } from '@/utils/form';
|
||||
|
||||
// ============================================================
|
||||
// 文案
|
||||
// ============================================================
|
||||
|
||||
/** 弹窗底部提示 */
|
||||
export const TIPS_TEXT = '修改后立即生效,请妥善保管新密码';
|
||||
|
||||
// ============================================================
|
||||
// 表单规则
|
||||
// ============================================================
|
||||
|
||||
/** 新密码规则:必填 + 至少 8 位 + 字母 + 数字 */
|
||||
export const newPasswordRules: Rule[] = [
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{
|
||||
validator: (_rule: unknown, value: string) => {
|
||||
if (!value) return Promise.resolve();
|
||||
if (!isValidPassword(value)) {
|
||||
return Promise.reject(new Error('密码至少8位,需包含字母与数字'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/** 确认密码规则:必填 + 必须与新密码一致 */
|
||||
export function confirmNewPasswordRules(getPassword: () => string): Rule[] {
|
||||
return [
|
||||
{ required: true, message: '请再次输入新密码' },
|
||||
{
|
||||
validator: (_rule: unknown, value: string) => {
|
||||
if (!value) return Promise.resolve();
|
||||
if (value !== getPassword()) {
|
||||
return Promise.reject(new Error('两次输入的密码不一致'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { defineComponent, ref, reactive, watch } from 'vue';
|
||||
import { Modal, Form, Input, Button, message } from 'ant-design-vue';
|
||||
import { TIPS_TEXT, newPasswordRules, confirmNewPasswordRules } from './controller';
|
||||
import styles from './style.module.less';
|
||||
|
||||
interface ChangePasswordModalProps {
|
||||
visible: boolean;
|
||||
submitting?: boolean;
|
||||
onClose: () => void;
|
||||
/** 提交回调:父组件负责调用 API,loading 状态由 submitting 控制 */
|
||||
onSave: (payload: { newPassword: string }) => void;
|
||||
}
|
||||
|
||||
/** 默认表单数据 */
|
||||
const getDefaultForm = () => ({
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ChangePasswordModal',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
submitting: { type: Boolean, default: false },
|
||||
onClose: { type: Function, required: true },
|
||||
onSave: { type: Function, required: true },
|
||||
},
|
||||
setup(props: ChangePasswordModalProps) {
|
||||
const formRef = ref<any>();
|
||||
const formData = reactive(getDefaultForm());
|
||||
|
||||
/** 监听 visible 重置表单 */
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
Object.assign(formData, getDefaultForm());
|
||||
setTimeout(() => formRef.value?.clearValidate(), 0);
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** 提交 */
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
props.onSave({ newPassword: formData.newPassword });
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
return () => (
|
||||
<Modal
|
||||
title="修改密码"
|
||||
visible={props.visible}
|
||||
onCancel={handleClose}
|
||||
width={420}
|
||||
destroyOnClose
|
||||
footer={null}
|
||||
centered
|
||||
class={styles.modal}
|
||||
>
|
||||
<Form ref={formRef} layout="vertical" model={formData} class={styles.form} requiredMark>
|
||||
<Form.Item label="新密码" name="newPassword" rules={newPasswordRules}>
|
||||
<Input.Password
|
||||
placeholder="至少8位,含字母+数字"
|
||||
allowClear
|
||||
v-model:value={formData.newPassword}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="确认新密码"
|
||||
name="confirmPassword"
|
||||
rules={confirmNewPasswordRules(() => formData.newPassword)}
|
||||
>
|
||||
<Input.Password
|
||||
placeholder="再次输入新密码"
|
||||
allowClear
|
||||
v-model:value={formData.confirmPassword}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div class={styles.tip}>{TIPS_TEXT}</div>
|
||||
</Form>
|
||||
|
||||
{/* 底部按钮 */}
|
||||
<div class={styles.footer}>
|
||||
<Button onClick={handleClose}>取消</Button>
|
||||
<Button type="primary" loading={props.submitting} onClick={handleSubmit}>
|
||||
确认修改
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
.form {
|
||||
:global {
|
||||
.ant-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.ant-form-item-explain-error {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部提示文案 */
|
||||
.tip {
|
||||
margin-top: -4px;
|
||||
margin-bottom: 24px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 底部按钮 */
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 微调 Modal 内部样式 */
|
||||
.modal {
|
||||
:global {
|
||||
.ant-modal-body {
|
||||
padding: 16px 24px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,3 +5,4 @@
|
||||
export { default as HelloWorld } from './HelloWorld';
|
||||
export { default as ImageCropper } from './ImageCropper';
|
||||
export type { ImageCropperRef, ImageCropperProps } from './ImageCropper';
|
||||
export { default as ChangePasswordModal } from './ChangePasswordModal';
|
||||
|
||||
Reference in New Issue
Block a user