feat: 补全赛事管理下所有页面 补全系统管理下所有页面

This commit is contained in:
ZhuRui
2026-07-25 18:00:22 +08:00
parent 34d9a778a8
commit b86c22e7d2
25 changed files with 2282 additions and 6 deletions
@@ -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;
/** 提交回调:父组件负责调用 APIloading 状态由 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;
}
}
}
+1
View File
@@ -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';