fix: 优化表单检测逻辑
This commit is contained in:
@@ -13,10 +13,25 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
import { JUMP_TYPE_OPTIONS } from '../model/useBannerModel';
|
import { JUMP_TYPE_OPTIONS } from '../model/useBannerModel';
|
||||||
|
import {
|
||||||
|
TITLE_MAX,
|
||||||
|
PATH_MAX,
|
||||||
|
URL_MAX,
|
||||||
|
titleRules,
|
||||||
|
imageUrlRules,
|
||||||
|
typeRules,
|
||||||
|
linkUrlRules,
|
||||||
|
miniProgramPathRules,
|
||||||
|
areaRules,
|
||||||
|
startTimeRules,
|
||||||
|
endTimeRules,
|
||||||
|
sortRules,
|
||||||
|
} from '../model/form';
|
||||||
import { getProvinceCityCascaderOptions, cityNameToCascaderPath } from '@/utils/areaData';
|
import { getProvinceCityCascaderOptions, cityNameToCascaderPath } from '@/utils/areaData';
|
||||||
import { ImageCropper } from '@yp-component/root';
|
import { ImageCropper } from '@yp-component/root';
|
||||||
import { FileImageOutlined } from '@ant-design/icons-vue';
|
import { FileImageOutlined } from '@ant-design/icons-vue';
|
||||||
import { uploadFile, OssUploadType } from '@/utils/oss';
|
import { uploadFile, OssUploadType } from '@/utils/oss';
|
||||||
|
import { isImageFile } from '@/utils/form';
|
||||||
import { getEventList, getEventDetail, type TournamentAdminVO } from '../../list/model/services';
|
import { getEventList, getEventDetail, type TournamentAdminVO } from '../../list/model/services';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import styles from './BannerFormModal.module.less';
|
import styles from './BannerFormModal.module.less';
|
||||||
@@ -40,11 +55,6 @@ const getDefaultForm = () => ({
|
|||||||
sort: 1,
|
sort: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 表单字段最大长度限制 */
|
|
||||||
const TITLE_MAX = 50;
|
|
||||||
const PATH_MAX = 200;
|
|
||||||
const URL_MAX = 500;
|
|
||||||
|
|
||||||
/** 图片上传限制:最大 10MB */
|
/** 图片上传限制:最大 10MB */
|
||||||
const IMAGE_MAX_SIZE = 10 * 1024 * 1024;
|
const IMAGE_MAX_SIZE = 10 * 1024 * 1024;
|
||||||
|
|
||||||
@@ -351,7 +361,7 @@ export default defineComponent({
|
|||||||
target.value = '';
|
target.value = '';
|
||||||
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
if (!/^image\//.test(file.type)) {
|
if (!isImageFile(file)) {
|
||||||
message.error('请选择图片文件');
|
message.error('请选择图片文件');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -468,14 +478,7 @@ export default defineComponent({
|
|||||||
requiredMark
|
requiredMark
|
||||||
>
|
>
|
||||||
{/* ===== Banner 标题 ===== */}
|
{/* ===== Banner 标题 ===== */}
|
||||||
<Form.Item
|
<Form.Item label="Banner标题" name="title" rules={titleRules}>
|
||||||
label="Banner标题"
|
|
||||||
name="title"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: '请输入' },
|
|
||||||
{ max: TITLE_MAX, message: `标题最多 ${TITLE_MAX} 个字符` },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
<Input
|
||||||
placeholder="请输入标题"
|
placeholder="请输入标题"
|
||||||
maxlength={TITLE_MAX}
|
maxlength={TITLE_MAX}
|
||||||
@@ -488,23 +491,10 @@ export default defineComponent({
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label="上传图片"
|
label="上传图片"
|
||||||
name="imageUrl"
|
name="imageUrl"
|
||||||
rules={[
|
rules={imageUrlRules(
|
||||||
{ required: true, message: '请上传 Banner 图片' },
|
() => formData.imageStatus,
|
||||||
{
|
() => formData.imageUrl,
|
||||||
validator: async () => {
|
)}
|
||||||
if (formData.imageStatus === 'uploading') {
|
|
||||||
return Promise.reject(new Error('图片仍在上传中,请稍候'));
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
formData.imageUrl?.startsWith('blob:') ||
|
|
||||||
formData.imageUrl?.startsWith('data:')
|
|
||||||
) {
|
|
||||||
return Promise.reject(new Error('请等待图片上传完成'));
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<div class={styles.uploader}>
|
<div class={styles.uploader}>
|
||||||
<input
|
<input
|
||||||
@@ -549,11 +539,7 @@ export default defineComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{/* ===== 跳转类型 ===== */}
|
{/* ===== 跳转类型 ===== */}
|
||||||
<Form.Item
|
<Form.Item label="跳转类型" name="type" rules={typeRules}>
|
||||||
label="跳转类型"
|
|
||||||
name="type"
|
|
||||||
rules={[{ required: true, message: '请选择跳转类型' }]}
|
|
||||||
>
|
|
||||||
<Select
|
<Select
|
||||||
options={JUMP_TYPE_OPTIONS as any}
|
options={JUMP_TYPE_OPTIONS as any}
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
@@ -584,14 +570,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
{/* ===== 联动字段:自定义链接 ===== */}
|
{/* ===== 联动字段:自定义链接 ===== */}
|
||||||
{formData.type === '2' && (
|
{formData.type === '2' && (
|
||||||
<Form.Item
|
<Form.Item label="跳转地址" name="linkUrl" rules={linkUrlRules}>
|
||||||
label="跳转地址"
|
|
||||||
name="linkUrl"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: '请输入跳转地址' },
|
|
||||||
{ max: URL_MAX, message: `链接最多 ${URL_MAX} 个字符` },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
<Input
|
||||||
placeholder="https://xxx"
|
placeholder="https://xxx"
|
||||||
maxlength={URL_MAX}
|
maxlength={URL_MAX}
|
||||||
@@ -603,14 +582,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
{/* ===== 联动字段:小程序页面 ===== */}
|
{/* ===== 联动字段:小程序页面 ===== */}
|
||||||
{formData.type === '3' && (
|
{formData.type === '3' && (
|
||||||
<Form.Item
|
<Form.Item label="小程序路径" name="linkUrl" rules={miniProgramPathRules}>
|
||||||
label="小程序路径"
|
|
||||||
name="linkUrl"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: '请输入小程序页面路径' },
|
|
||||||
{ max: PATH_MAX, message: `路径最多 ${PATH_MAX} 个字符` },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
<Input
|
||||||
placeholder="/pages/index/index"
|
placeholder="/pages/index/index"
|
||||||
maxlength={PATH_MAX}
|
maxlength={PATH_MAX}
|
||||||
@@ -621,11 +593,7 @@ export default defineComponent({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ===== 所属区域(省市树种选择 + 全国) ===== */}
|
{/* ===== 所属区域(省市树种选择 + 全国) ===== */}
|
||||||
<Form.Item
|
<Form.Item label="所属区域" name="area" rules={areaRules}>
|
||||||
label="所属区域"
|
|
||||||
name="area"
|
|
||||||
rules={[{ required: true, message: '请选择所属区域' }]}
|
|
||||||
>
|
|
||||||
<Cascader
|
<Cascader
|
||||||
v-model:value={areaPath.value}
|
v-model:value={areaPath.value}
|
||||||
options={areaCascaderOptions}
|
options={areaCascaderOptions}
|
||||||
@@ -641,16 +609,7 @@ export default defineComponent({
|
|||||||
label="开始时间"
|
label="开始时间"
|
||||||
name="displayStartTime"
|
name="displayStartTime"
|
||||||
class={styles.timeItem}
|
class={styles.timeItem}
|
||||||
rules={[
|
rules={startTimeRules(() => formData.displayTimeRange)}
|
||||||
{
|
|
||||||
validator: async () => {
|
|
||||||
if (!formData.displayTimeRange?.[0]) {
|
|
||||||
return Promise.reject(new Error('请选择开始时间'));
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
showTime
|
showTime
|
||||||
@@ -679,16 +638,7 @@ export default defineComponent({
|
|||||||
label="结束时间"
|
label="结束时间"
|
||||||
name="displayEndTime"
|
name="displayEndTime"
|
||||||
class={styles.timeItem}
|
class={styles.timeItem}
|
||||||
rules={[
|
rules={endTimeRules(() => formData.displayTimeRange)}
|
||||||
{
|
|
||||||
validator: async () => {
|
|
||||||
if (!formData.displayTimeRange?.[1]) {
|
|
||||||
return Promise.reject(new Error('请选择结束时间'));
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
showTime
|
showTime
|
||||||
@@ -715,14 +665,7 @@ export default defineComponent({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ===== 排序 ===== */}
|
{/* ===== 排序 ===== */}
|
||||||
<Form.Item
|
<Form.Item label="排序" name="sort" rules={sortRules}>
|
||||||
label="排序"
|
|
||||||
name="sort"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: '请输入排序' },
|
|
||||||
{ type: 'number', min: 1, max: 999, message: '排序范围 1-999' },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber
|
<InputNumber
|
||||||
placeholder="数字越小越靠前"
|
placeholder="数字越小越靠前"
|
||||||
min={1}
|
min={1}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Banner 表单域控制器
|
||||||
|
*
|
||||||
|
* 集中管理 Banner 新增/编辑弹窗的所有表单规则校验与常量。
|
||||||
|
* 各规则数组 / 工厂函数可直接传给 antd Form.Item 的 rules prop。
|
||||||
|
*/
|
||||||
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 常量
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/** 标题最大长度 */
|
||||||
|
export const TITLE_MAX = 50;
|
||||||
|
/** 小程序路径最大长度 */
|
||||||
|
export const PATH_MAX = 200;
|
||||||
|
/** 跳转链接最大长度 */
|
||||||
|
export const URL_MAX = 500;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 表单规则
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/** Banner 标题:必填 + 50 字符上限 */
|
||||||
|
export const titleRules: Rule[] = [
|
||||||
|
{ required: true, message: '请输入' },
|
||||||
|
{ max: TITLE_MAX, message: `标题最多 ${TITLE_MAX} 个字符` },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Banner 图片:必填 + 上传状态校验
|
||||||
|
*
|
||||||
|
* @param getImageStatus 获取当前上传状态的 getter
|
||||||
|
* @param getImageUrl 获取当前图片 URL 的 getter
|
||||||
|
*/
|
||||||
|
export function imageUrlRules(getImageStatus: () => string, getImageUrl: () => string): Rule[] {
|
||||||
|
return [
|
||||||
|
{ required: true, message: '请上传 Banner 图片' },
|
||||||
|
{
|
||||||
|
validator: async () => {
|
||||||
|
if (getImageStatus() === 'uploading') {
|
||||||
|
return Promise.reject(new Error('图片仍在上传中,请稍候'));
|
||||||
|
}
|
||||||
|
const url = getImageUrl();
|
||||||
|
if (url.startsWith('blob:') || url.startsWith('data:')) {
|
||||||
|
return Promise.reject(new Error('请等待图片上传完成'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跳转类型:必选 */
|
||||||
|
export const typeRules: Rule[] = [{ required: true, message: '请选择跳转类型' }];
|
||||||
|
|
||||||
|
/** 跳转地址(自定义链接 scene=2):必填 + 最大长度 */
|
||||||
|
export const linkUrlRules: Rule[] = [
|
||||||
|
{ required: true, message: '请输入跳转地址' },
|
||||||
|
{ max: URL_MAX, message: `链接最多 ${URL_MAX} 个字符` },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 小程序页面路径(scene=3):必填 + 最大长度 */
|
||||||
|
export const miniProgramPathRules: Rule[] = [
|
||||||
|
{ required: true, message: '请输入小程序页面路径' },
|
||||||
|
{ max: PATH_MAX, message: `路径最多 ${PATH_MAX} 个字符` },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 所属区域:必选 */
|
||||||
|
export const areaRules: Rule[] = [{ required: true, message: '请选择所属区域' }];
|
||||||
|
|
||||||
|
/** 排序:必填 + 1-999 整数 */
|
||||||
|
export const sortRules: Rule[] = [
|
||||||
|
{ required: true, message: '请输入排序' },
|
||||||
|
{ type: 'number', min: 1, max: 999, message: '排序范围 1-999' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展示开始时间规则:校验 displayTimeRange[0] 非空
|
||||||
|
*
|
||||||
|
* @param getDisplayTimeRange 获取展示时间范围的 getter
|
||||||
|
*/
|
||||||
|
export function startTimeRules(getDisplayTimeRange: () => [string, string] | null): Rule[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
validator: async () => {
|
||||||
|
const range = getDisplayTimeRange();
|
||||||
|
if (!range?.[0]) {
|
||||||
|
return Promise.reject(new Error('请选择开始时间'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展示结束时间规则:校验 displayTimeRange[1] 非空
|
||||||
|
*
|
||||||
|
* @param getDisplayTimeRange 获取展示时间范围的 getter
|
||||||
|
*/
|
||||||
|
export function endTimeRules(getDisplayTimeRange: () => [string, string] | null): Rule[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
validator: async () => {
|
||||||
|
const range = getDisplayTimeRange();
|
||||||
|
if (!range?.[1]) {
|
||||||
|
return Promise.reject(new Error('请选择结束时间'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defineComponent, reactive } from 'vue';
|
import { defineComponent, reactive } from 'vue';
|
||||||
import { Modal, Descriptions, Image, Radio, Input, Form } from 'ant-design-vue';
|
import { Modal, Descriptions, Image, Radio, Input, Form, message } from 'ant-design-vue';
|
||||||
|
import { validateEventAuditSubmit } from '../model/form';
|
||||||
import styles from './EventAuditModal.module.less';
|
import styles from './EventAuditModal.module.less';
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@@ -58,11 +59,13 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
if (!auditForm.comment.trim()) {
|
const check = validateEventAuditSubmit({
|
||||||
// 不通过时强制要求填写审核内容
|
approved: auditForm.approved,
|
||||||
if (!auditForm.approved) {
|
comment: auditForm.comment,
|
||||||
// 这里交给校验,简化版本不做强制校验
|
});
|
||||||
}
|
if (!check.valid) {
|
||||||
|
message.warning(check.error!);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (props.onSubmit) {
|
if (props.onSubmit) {
|
||||||
props.onSubmit({ approved: auditForm.approved, comment: auditForm.comment });
|
props.onSubmit({ approved: auditForm.approved, comment: auditForm.comment });
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 赛事审核 - 表单域控制器
|
||||||
|
*
|
||||||
|
* 集中管理赛事审核弹窗的提交校验逻辑。
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 提交校验
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
export interface EventAuditForm {
|
||||||
|
approved: boolean;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventAuditSubmitCheckResult {
|
||||||
|
valid: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验赛事审核提交数据
|
||||||
|
*
|
||||||
|
* @param form 审核表单 { approved, comment }
|
||||||
|
*/
|
||||||
|
export function validateEventAuditSubmit(form: EventAuditForm): EventAuditSubmitCheckResult {
|
||||||
|
// 不通过时强制要求填写审核内容
|
||||||
|
if (!form.approved && !form.comment.trim()) {
|
||||||
|
return { valid: false, error: '审核不通过时请填写审核内容' };
|
||||||
|
}
|
||||||
|
return { valid: true };
|
||||||
|
}
|
||||||
@@ -50,24 +50,24 @@ export function useLogColumns() {
|
|||||||
width: 200,
|
width: 200,
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: '操作对象',
|
// title: '操作对象',
|
||||||
dataIndex: 'obj',
|
// dataIndex: 'obj',
|
||||||
key: 'obj',
|
// key: 'obj',
|
||||||
width: 240,
|
// width: 240,
|
||||||
align: 'center' as const,
|
// align: 'center' as const,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: '操作内容',
|
title: '操作内容',
|
||||||
dataIndex: 'content',
|
dataIndex: 'obj',
|
||||||
key: 'content',
|
key: 'obj',
|
||||||
width: 360,
|
width: 360,
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作时间',
|
title: '操作时间',
|
||||||
dataIndex: 'createDate',
|
dataIndex: 'createTime',
|
||||||
key: 'createDate',
|
key: 'createTime',
|
||||||
width: 170,
|
width: 170,
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ import { PlusOutlined } from '@ant-design/icons-vue';
|
|||||||
import { uploadFile, OssUploadType } from '@/utils/oss';
|
import { uploadFile, OssUploadType } from '@/utils/oss';
|
||||||
import { getWithdrawInfo, postWithdrawAudit, type WithdrawInfoVO } from '../model/services';
|
import { getWithdrawInfo, postWithdrawAudit, type WithdrawInfoVO } from '../model/services';
|
||||||
import { getAuditStatusText, getPayStatusText } from '../model/useWithdrawModel';
|
import { getAuditStatusText, getPayStatusText } from '../model/useWithdrawModel';
|
||||||
|
import {
|
||||||
|
MAX_IMAGE_COUNT,
|
||||||
|
MAX_IMAGE_SIZE,
|
||||||
|
validateImageFile,
|
||||||
|
validateWithdrawAuditSubmit,
|
||||||
|
} from '../model/form';
|
||||||
import styles from './WithdrawDetailModal.module.less';
|
import styles from './WithdrawDetailModal.module.less';
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
const MAX_IMAGE_COUNT = 3;
|
|
||||||
const MAX_IMAGE_SIZE = 5 * 1024 * 1024;
|
|
||||||
|
|
||||||
interface WithdrawDetailModalProps {
|
interface WithdrawDetailModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -67,16 +71,9 @@ export default defineComponent({
|
|||||||
const beforeUpload = async (file: UploadFile) => {
|
const beforeUpload = async (file: UploadFile) => {
|
||||||
const rawFile = file as unknown as File;
|
const rawFile = file as unknown as File;
|
||||||
|
|
||||||
if (imageList.value.length >= MAX_IMAGE_COUNT) {
|
const check = validateImageFile(rawFile, imageList.value.length);
|
||||||
message.warning(`最多上传 ${MAX_IMAGE_COUNT} 张图片`);
|
if (!check.valid) {
|
||||||
return false;
|
message.error(check.error!);
|
||||||
}
|
|
||||||
if (!/^image\//.test(rawFile.type)) {
|
|
||||||
message.error('请选择图片文件');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rawFile.size >= MAX_IMAGE_SIZE) {
|
|
||||||
message.error('图片大小不能超过 5MB');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,21 +111,16 @@ export default defineComponent({
|
|||||||
|
|
||||||
// ===== 提交审核 =====
|
// ===== 提交审核 =====
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
// 驳回:审核内容必填
|
// 提交前校验
|
||||||
if (!auditForm.pass && !auditForm.remark.trim()) {
|
const hasDoneImage = imageList.value.some((item) => item.status === 'done' && item.url);
|
||||||
message.warning('驳回时请填写审核内容');
|
const isUploading = imageList.value.some((item) => item.status === 'uploading');
|
||||||
return;
|
const check = validateWithdrawAuditSubmit(
|
||||||
}
|
{ pass: auditForm.pass, remark: auditForm.remark },
|
||||||
// 通过:必须上传图片
|
hasDoneImage,
|
||||||
if (auditForm.pass) {
|
isUploading,
|
||||||
const hasDoneImage = imageList.value.some((item) => item.status === 'done' && item.url);
|
);
|
||||||
if (!hasDoneImage) {
|
if (!check.valid) {
|
||||||
message.warning('审核通过时必须上传审核图片');
|
message.warning(check.error!);
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (imageList.value.some((item) => item.status === 'uploading')) {
|
|
||||||
message.warning('图片仍在上传中,请稍候');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* 提现审核 - 表单域控制器
|
||||||
|
*
|
||||||
|
* 集中管理提现审核弹窗的图片上传校验、提交校验逻辑与常量。
|
||||||
|
*/
|
||||||
|
import { isImageFile } from '@/utils/form';
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 常量
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/** 审核图片最大上传张数 */
|
||||||
|
export const MAX_IMAGE_COUNT = 3;
|
||||||
|
/** 审核图片单张最大体积:5MB */
|
||||||
|
export const MAX_IMAGE_SIZE = 5 * 1024 * 1024;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 图片上传校验
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
export interface ImageUploadCheckResult {
|
||||||
|
valid: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验待上传的审核图片
|
||||||
|
* @param file 待上传的文件对象
|
||||||
|
* @param currentCount 当前已上传的图片数量
|
||||||
|
* @returns 校验结果
|
||||||
|
*/
|
||||||
|
export function validateImageFile(file: File, currentCount: number): ImageUploadCheckResult {
|
||||||
|
if (currentCount >= MAX_IMAGE_COUNT) {
|
||||||
|
return { valid: false, error: `最多上传 ${MAX_IMAGE_COUNT} 张图片` };
|
||||||
|
}
|
||||||
|
if (!isImageFile(file)) {
|
||||||
|
return { valid: false, error: '请选择图片文件' };
|
||||||
|
}
|
||||||
|
if (file.size >= MAX_IMAGE_SIZE) {
|
||||||
|
return { valid: false, error: '图片大小不能超过 5MB' };
|
||||||
|
}
|
||||||
|
return { valid: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 提交审核校验
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
export interface WithdrawAuditForm {
|
||||||
|
pass: boolean;
|
||||||
|
remark: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WithdrawAuditSubmitCheckResult {
|
||||||
|
valid: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验提现审核提交数据
|
||||||
|
*
|
||||||
|
* @param form 审核表单 { pass, remark }
|
||||||
|
* @param hasDoneImage 是否有上传成功的图片
|
||||||
|
* @param isUploading 是否有图片仍在上传中
|
||||||
|
*/
|
||||||
|
export function validateWithdrawAuditSubmit(
|
||||||
|
form: WithdrawAuditForm,
|
||||||
|
hasDoneImage: boolean,
|
||||||
|
isUploading: boolean,
|
||||||
|
): WithdrawAuditSubmitCheckResult {
|
||||||
|
// 驳回:审核内容必填
|
||||||
|
if (!form.pass && !form.remark.trim()) {
|
||||||
|
return { valid: false, error: '驳回时请填写审核内容' };
|
||||||
|
}
|
||||||
|
// 通过:必须上传图片
|
||||||
|
if (form.pass && !hasDoneImage) {
|
||||||
|
return { valid: false, error: '审核通过时必须上传审核图片' };
|
||||||
|
}
|
||||||
|
// 图片上传未完成
|
||||||
|
if (isUploading) {
|
||||||
|
return { valid: false, error: '图片仍在上传中,请稍候' };
|
||||||
|
}
|
||||||
|
return { valid: true };
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { auth } from '@/hooks/useAuth';
|
|||||||
import { useEffect, useState } from '@/hooks';
|
import { useEffect, useState } from '@/hooks';
|
||||||
import { useUserStore } from '@/stores/userStore';
|
import { useUserStore } from '@/stores/userStore';
|
||||||
import { login as loginApi } from '@/api/login';
|
import { login as loginApi } from '@/api/login';
|
||||||
|
import { loginFormRules } from './model/form';
|
||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
|
|
||||||
interface LoginForm {
|
interface LoginForm {
|
||||||
@@ -40,11 +41,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const rules = {
|
|
||||||
username: [{ required: true, message: '请输入用户名' }],
|
|
||||||
password: [{ required: true, message: '请输入密码' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!form.phone || !form.password) {
|
if (!form.phone || !form.password) {
|
||||||
message.warning('请输入用户名和密码');
|
message.warning('请输入用户名和密码');
|
||||||
@@ -214,7 +210,7 @@ export default defineComponent({
|
|||||||
<Form
|
<Form
|
||||||
class={styles.loginForm}
|
class={styles.loginForm}
|
||||||
model={form}
|
model={form}
|
||||||
rules={rules}
|
rules={loginFormRules}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
onFinish={handleSubmit}
|
onFinish={handleSubmit}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* 登录页 - 表单域控制器
|
||||||
|
*
|
||||||
|
* 集中管理登录表单的校验规则。
|
||||||
|
*/
|
||||||
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 表单规则
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/** 登录表单的整体规则(form-level rules,key 对应 FormItem 的 name) */
|
||||||
|
export const loginFormRules: Record<string, Rule[]> = {
|
||||||
|
phone: [{ required: true, message: '请输入用户名' }],
|
||||||
|
password: [{ required: true, message: '请输入密码' }],
|
||||||
|
};
|
||||||
@@ -27,6 +27,12 @@ export const LOGIN_PASSWORD_REGEX = /^(?=.*[A-Za-z])(?=.*\d)[\w!@#$%^&*()_+\-=]{
|
|||||||
*/
|
*/
|
||||||
export const REAL_NAME_REGEX = /^[\u4e00-\u9fa5A-Za-z0-9·]{1,8}$/;
|
export const REAL_NAME_REGEX = /^[\u4e00-\u9fa5A-Za-z0-9·]{1,8}$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片 MIME 类型前缀
|
||||||
|
* - 用于上传前校验文件是否为图片
|
||||||
|
*/
|
||||||
|
export const IMAGE_TYPE_REGEX = /^image\//;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 校验函数
|
// 校验函数
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -45,3 +51,8 @@ export function isValidPassword(value: unknown): boolean {
|
|||||||
export function isValidRealName(value: unknown): boolean {
|
export function isValidRealName(value: unknown): boolean {
|
||||||
return typeof value === 'string' && value.length > 0 && REAL_NAME_REGEX.test(value);
|
return typeof value === 'string' && value.length > 0 && REAL_NAME_REGEX.test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 校验文件是否为图片类型 */
|
||||||
|
export function isImageFile(file: { type: string }): boolean {
|
||||||
|
return IMAGE_TYPE_REGEX.test(file.type);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user