证书,项目,积分管理

This commit is contained in:
马梓彭 2026-04-12 13:13:02 +08:00
parent 1cb0ac2978
commit 20b04d9124
4 changed files with 213 additions and 35 deletions

View File

@ -70,9 +70,9 @@
<!-- 选择有效期 -->
<el-form-item label="选择有效期" prop="startTime">
<el-date-picker type="daterange" v-model="formData.time" start-placeholder="开始日期"
end-placeholder="结束日期" style="width: 100%;" value-format="YYYY-MM-DD" @change="(val) => {
console.log('选择的日期范围:', val);
<el-date-picker type="datetimerange" v-model="formData.time" start-placeholder="开始日期时间"
end-placeholder="结束日期时间" style="width: 100%;" value-format="YYYY-MM-DD HH:mm" @change="(val) => {
console.log('选择的日期时间范围:', val);
formData.startTime = val[0];
formData.endTime = val[1];
}"></el-date-picker>
@ -120,14 +120,39 @@ import { auths } from '/@/utils/authFunction';
import ProTable from '/@/components/ProTable/index.vue';
import { couponPage, couponAdd, couponEdit, couponDelete, couponEventlistGet, couponGeneratecodePost } from '/@/api/match/coupon';
// 格式化日期,只显示年月日
// 格式化日期,显示完整时间(YYYY-MM-DD HH:MM)
const formatDate = (dateStr) => {
if (!dateStr) return '';
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
// 如果已经是日期时间格式,直接返回(或调整格式)
try {
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
} catch (e) {
return dateStr;
}
};
// 格式化日期为选择器用的格式
const formatDateTimePicker = (dateStr) => {
if (!dateStr) return '';
try {
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
} catch (e) {
return dateStr;
}
// 截取前10个字符(YYYY-MM-DD)
return dateStr.slice(0, 10);
};
// 定义表单引用类型
@ -209,9 +234,9 @@ const onOpenMenu = async (row) => {
if (row) {
formData.value = {
...row,
startTime: formatDate(row.startTime),
endTime: formatDate(row.endTime),
time: [formatDate(row.startTime), formatDate(row.endTime)],
startTime: formatDateTimePicker(row.startTime),
endTime: formatDateTimePicker(row.endTime),
time: [formatDateTimePicker(row.startTime), formatDateTimePicker(row.endTime)],
};
} else {
formData.value = {

View File

@ -22,6 +22,7 @@
<el-button size="small" v-auth="'integral:config:delete'" text type="danger" icon="ele-Delete"> 删除 </el-button>
</template>
</el-popconfirm>
<el-button size="small" v-auth="'integral:config:detail'" text type="primary" icon="ele-View" @click="onOpenDialog(scope.row, true)"> 查看详情 </el-button>
</template>
</ProTable>
@ -29,37 +30,34 @@
<el-dialog :title="dialogTitle" v-model="isShowDialog" width="900px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-if="isShowDialog">
<el-form-item label="规则名称" prop="ruleName">
<el-input v-model="formData.ruleName" placeholder="请输入规则名称" />
<el-input v-model="formData.ruleName" placeholder="请输入规则名称" :disabled="isDetailMode" />
</el-form-item>
<el-form-item label="参与积分" prop="participationPoints">
<el-input-number v-model="formData.participationPoints" :min="0" :step="1" style="width: 100%" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可不填" />
<el-input-number v-model="formData.participationPoints" :min="0" :step="1" style="width: 100%" :disabled="isDetailMode" />
</el-form-item>
<el-form-item label="名次积分">
<div style="width: 100%">
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px">
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px" v-if="!isDetailMode">
<el-button type="primary" plain icon="ele-Plus" @click="onAddDetailRow">新增区间</el-button>
</div>
<el-table :data="formData.details" border style="width: 100%" >
<el-table-column label="起始排名" width="180">
<template #default="scope">
<el-input-number v-model="scope.row.startRank" :min="1" :step="1" />
<el-input-number v-model="scope.row.startRank" :min="1" :step="1" :disabled="isDetailMode" />
</template>
</el-table-column>
<el-table-column label="结束排名" width="180">
<template #default="scope">
<el-input-number v-model="scope.row.endRank" :min="1" :step="1" />
<el-input-number v-model="scope.row.endRank" :min="1" :step="1" :disabled="isDetailMode" />
</template>
</el-table-column>
<el-table-column label="对应积分" width="180">
<template #default="scope">
<el-input-number v-model="scope.row.points" :min="0" :step="1" />
<el-input-number v-model="scope.row.points" :min="0" :step="1" :disabled="isDetailMode" />
</template>
</el-table-column>
<el-table-column label="操作" width="140" align="center">
<el-table-column label="操作" width="140" align="center" v-if="!isDetailMode">
<template #default="scope">
<el-button text type="danger" icon="ele-Delete" @click="onRemoveDetailRow(scope.$index)">
删除
@ -69,11 +67,15 @@
</el-table>
</div>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可不填" :disabled="isDetailMode" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel">取消</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
<el-button type="primary" @click="onSubmit" v-if="!isDetailMode">确定</el-button>
</span>
</template>
</el-dialog>
@ -105,6 +107,7 @@ const buildEmptyForm = () => ({
});
const formData = ref(buildEmptyForm());
const isDetailMode = ref(false);
const formRules = ref({
ruleName: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
@ -125,14 +128,15 @@ const columns = ref([
// search: { el: 'select' },
// },
{ prop: 'createdTime', label: '创建时间', align: 'center' },
{ prop: 'action', label: '操作', align: 'center', fixed: 'right', width: 220 },
{ prop: 'action', label: '操作', align: 'center', fixed: 'right', width: 320 },
]);
const isShowDialog = ref(false);
const dialogTitle = ref('新增');
const onOpenDialog = async (row?: any) => {
dialogTitle.value = row ? '编辑' : '新增';
const onOpenDialog = async (row?: any, isDetail = false) => {
isDetailMode.value = isDetail;
dialogTitle.value = isDetail ? '查看详情' : row ? '编辑' : '新增';
if (row?.id) {
const res: any = await pointsruleDetailGet({ id: row.id });
formData.value = {
@ -150,7 +154,7 @@ const onOpenDialog = async (row?: any) => {
} else {
formData.value = buildEmptyForm();
}
if (!formData.value.details?.length) onAddDetailRow();
if (!formData.value.details?.length && !isDetail) onAddDetailRow();
isShowDialog.value = true;
};
@ -162,6 +166,30 @@ const onRemoveDetailRow = (index: number) => {
formData.value.details.splice(index, 1);
};
// 验证名次范围是否重叠
const validateRankOverlap = () => {
const details = formData.value.details;
if (details.length <= 1) return true;
for (let i = 0; i < details.length; i++) {
const item1 = details[i];
if (!item1.startRank || !item1.endRank) continue;
for (let j = i + 1; j < details.length; j++) {
const item2 = details[j];
if (!item2.startRank || !item2.endRank) continue;
// 检查是否重叠
const isOverlap = !(item1.endRank < item2.startRank || item2.endRank < item1.startRank);
if (isOverlap) {
ElMessage.error(`名次范围 ${item1.startRank}-${item1.endRank} 与 ${item2.startRank}-${item2.endRank} 存在重叠,请调整!`);
return false;
}
}
}
return true;
};
const onDelete = async (id: number) => {
await pointsruleDeleteDelete({ id });
ElMessage.success('删除成功');
@ -185,6 +213,10 @@ const onSubmit = () => {
ElMessage.warning('请至少配置一个排名区间');
return;
}
// 验证名次范围是否重叠
if (!validateRankOverlap()) {
return;
}
const payload = {
id: formData.value.id,
ruleName: formData.value.ruleName,
@ -211,6 +243,7 @@ const onSubmit = () => {
// 取消
const onCancel = () => {
isShowDialog.value = false;
isDetailMode.value = false;
};
</script>
<style scoped lang="scss">

View File

@ -30,9 +30,8 @@
</el-button>
</template>
</el-popconfirm>
<el-button v-auth="'project:detail'" size="small" text type="primary" @click="onOpenMenu(scope.row)">
查看详情 </el-button>
<el-button v-auth="'project:detail'" size="small" text type="primary"
@click="onViewDetail(scope.row)"> 查看详情 </el-button>
</template>
</ProTable>
@ -74,6 +73,8 @@
callback(new Error('请选择出生日期开始'));
} else if (!value.birthDateEnd) {
callback(new Error('请选择出生日期结束'));
} else if (new Date(value.birthDateStart) > new Date(value.birthDateEnd)) {
callback(new Error('出生日期结束必须大于或等于出生日期开始'));
} else if (!value.gender) {
callback(new Error('请选择性别'));
} else {
@ -85,9 +86,9 @@
<el-input v-model="group.groupName" placeholder="输入组别名"
style="width: 100px; margin-right: 18px"></el-input>
<el-date-picker v-model="group.birthDateStart" type="date" placeholder="出生日期开始"
style="margin-right: 18px"></el-date-picker>
style="margin-right: 18px" value-format="YYYY-MM-DD"></el-date-picker>
<el-date-picker v-model="group.birthDateEnd" type="date" placeholder="出生日期结束"
style="margin-right: 18px"></el-date-picker>
style="margin-right: 18px" value-format="YYYY-MM-DD"></el-date-picker>
<el-select v-model="group.gender" placeholder="性别" style="width: 100px">
<el-option label="男" value="0"></el-option>
<el-option label="女" value="1"></el-option>
@ -125,6 +126,76 @@
</span>
</template>
</el-dialog>
<!-- 详情弹窗 - 复用编辑页面布局 -->
<el-dialog title="项目详情" v-model="isShowDetailDialog" width="1100px">
<el-form :model="detailData" label-width="150px"
style="width: 90%; margin-bottom: 30px" v-if="isShowDetailDialog">
<!-- 项目名称 -->
<el-form-item label="项目名称:">
<el-input v-model="detailData.name" disabled></el-input>
</el-form-item>
<!-- 项目类型 -->
<el-form-item label="项目类型:">
<el-select v-model="detailData.projectTypeId" disabled style="width: 100%">
<el-option label="个人速度" :value="1003"></el-option>
<el-option label="个人花样" :value="1004"></el-option>
<el-option label="团队速度" :value="1005"></el-option>
<el-option label="团队花样" :value="1006"></el-option>
</el-select>
</el-form-item>
<!-- 完成人数 -->
<el-form-item label="完成人数:">
<el-input-number v-model="detailData.completionCount" disabled :min="0" :step="1" style="width: 100%"></el-input-number>
</el-form-item>
<!-- 组别 -->
<div class="detail-groups-section">
<el-form-item :label="'组别' + (index + 1) + ':'"
v-for="(group, index) in (detailData.groups || [])" :key="index">
<el-input v-model="group.groupName" disabled
style="width: 100px; margin-right: 18px"></el-input>
<el-date-picker v-model="group.birthDateStart" type="date" disabled
style="margin-right: 18px" value-format="YYYY-MM-DD"></el-date-picker>
<el-date-picker v-model="group.birthDateEnd" type="date" disabled
style="margin-right: 18px" value-format="YYYY-MM-DD"></el-date-picker>
<el-select v-model="group.gender" disabled style="width: 100px">
<el-option label="男" value="0"></el-option>
<el-option label="女" value="1"></el-option>
<el-option label="不限" value="3"></el-option>
</el-select>
</el-form-item>
</div>
<!-- 视觉分隔线 -->
<div class="detail-section-divider"></div>
<!-- 费用 -->
<el-form-item label="费用:">
<el-input v-model="detailData.fee" disabled class="fee-input">
<template slot="append">元</template>
</el-input>
</el-form-item>
<!-- 时长 -->
<el-form-item label="时长:">
<div class="duration-row">
<el-input v-model="detailData.durationMinutes" disabled type="number"></el-input>
<span class="duration-label">分</span>
<span class="duration-separator">--</span>
<el-input v-model="detailData.durationSeconds" disabled type="number"></el-input>
<span class="duration-label">秒</span>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="isShowDetailDialog = false">关闭</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
@ -142,6 +213,20 @@ const initParam = ref({
refresh: true,
});
// 详情弹窗相关
const isShowDetailDialog = ref(false);
const detailData = ref(null);
// 日期格式化函数
const formatDate = (date) => {
if (!date) return '';
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const addGroup = () => {
formData.value.groups.push({
groupName: '',
@ -262,7 +347,7 @@ const columns = ref([
{ prop: 'time', label: '时长' },
{ prop: 'action', label: '操作', align: 'center', fixed: 'right', isShow: auths(['project:delete', 'project:edit', 'project:add']) },
{ prop: 'action', label: '操作', align: 'center', fixed: 'right' },
]);
const isShowDialog = ref(false);
@ -300,13 +385,13 @@ const onOpenMenu = async (row) => {
},
];
} else {
// 过滤无效的 group 并补充缺失的属性
// 过滤无效的 group 并补充缺失的属性,同时格式化日期
formData.value.groups = formData.value.groups
.filter(group => group)
.map(group => ({
groupName: group.groupName || '',
birthDateStart: group.birthDateStart || '',
birthDateEnd: group.birthDateEnd || '',
birthDateStart: formatDate(group.birthDateStart),
birthDateEnd: formatDate(group.birthDateEnd),
gender: group.gender !== undefined && group.gender !== null ? group.gender.toString() : '',
}));
// 如果过滤后没有组,添加一个空组
@ -325,6 +410,31 @@ const onOpenMenu = async (row) => {
isShowDialog.value = true;
};
// 查看详情
const onViewDetail = async (row) => {
const { succeeded, data } = await projectDetailGet({ id: row.id });
if (succeeded) {
// 确保 groups 数组存在且有效,并格式化日期
let groupsData = [];
if (data.groups && Array.isArray(data.groups)) {
groupsData = data.groups
.filter(group => group)
.map(group => ({
groupName: group.groupName || '',
birthDateStart: formatDate(group.birthDateStart),
birthDateEnd: formatDate(group.birthDateEnd),
gender: group.gender !== undefined && group.gender !== null ? group.gender.toString() : '',
}));
}
detailData.value = {
...data,
groups: groupsData
};
isShowDetailDialog.value = true;
}
};
//删除菜单
const onDeleteMenu = async (id) => {
const { succeeded, errors } = await projectDeleteDelete({ id });
@ -429,4 +539,9 @@ const onCancel = () => {
color: #909399;
font-size: 14px;
}
/* 详情页面组别区域样式 */
.detail-groups-section {
margin-bottom: 20px;
}
</style>

View File

@ -169,7 +169,12 @@ const formRules = ref({
{ required: true, message: '请上传营业执照', trigger: 'blur' }
],
contactPhone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' }
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号格式',
trigger: 'blur'
}
],
});