feat: 赛事1.09

This commit is contained in:
cst
2026-11-06 16:36:10 +08:00
parent 03ec325318
commit 1f356a1112
6 changed files with 42 additions and 36 deletions
+4 -4
View File
@@ -52,10 +52,10 @@ system.roles.add # 角色权限-新增(筛选栏中的"新增
system.roles.edit # 角色权限-编辑(表格操作列中的"编辑"按钮) system.roles.edit # 角色权限-编辑(表格操作列中的"编辑"按钮)
system.roles.delete # 角色权限-删除(表格操作列中的"删除"按钮,红色危险样式) system.roles.delete # 角色权限-删除(表格操作列中的"删除"按钮,红色危险样式)
system.notices.view # 公告管理-查看(页面默认查看权限) system.announcement.view # 公告管理-查看(页面默认查看权限)
system.notices.add # 公告管理-新增(筛选栏中的"新增"按钮) system.announcement.save # 公告管理-新增(筛选栏中的"新增"按钮)
system.notices.edit # 公告管理-编辑(表格操作列中的"修改"按钮) system.announcement.edit # 公告管理-编辑(表格操作列中的"修改"按钮)
system.notices.delete # 公告管理-删除(表格操作列中的"删除"按钮,红色危险样式) system.announcement.del # 公告管理-删除(表格操作列中的"删除"按钮,红色危险样式)
system.logs.view # 操作日志-查看(页面默认查看权限,无操作列按钮) system.logs.view # 操作日志-查看(页面默认查看权限,无操作列按钮)
+2 -2
View File
@@ -48,7 +48,7 @@ export function updateNotice(params: NoticeSaveParams): Promise<ApiResult<null>>
return post(ANNOUNCEMENT_UPDATE, params); return post(ANNOUNCEMENT_UPDATE, params);
} }
/** 删除公告(物理删除,单个)— POST /admin/sys/announcement/delete?id=xxx */ /** 删除公告(物理删除,单个)— POST /admin/sys/announcement/delete(id 放在请求体) */
export function deleteNotice(id: number): Promise<ApiResult<null>> { export function deleteNotice(id: number): Promise<ApiResult<null>> {
return post(ANNOUNCEMENT_DELETE, undefined, { id: String(id) }); return post(ANNOUNCEMENT_DELETE, { id });
} }
+1 -3
View File
@@ -165,15 +165,13 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
permission: 'system.roles.view', permission: 'system.roles.view',
}, },
{ {
// 公告管理:后端 /op/permission 尚未下发 system.notices.view 权限码,
// 暂不挂 permission(否则整条菜单会被权限过滤掉);后端配置后可在下方补回
// permission: 'system.notices.view',
id: 'system_notices', id: 'system_notices',
name: '公告管理', name: '公告管理',
path: 'system/notices', path: 'system/notices',
component: 'system/notices', component: 'system/notices',
componentName: 'SystemNotices', componentName: 'SystemNotices',
icon: 'AiOutlineSound', icon: 'AiOutlineSound',
permission: 'system.announcement.view',
}, },
{ {
id: 'system_params', id: 'system_params',
@@ -7,6 +7,7 @@ import { getNoticeList, type NoticeVO } from '../model/services';
import { import {
TITLE_MAX, TITLE_MAX,
CONTENT_MAX, CONTENT_MAX,
DISPLAY_TIME_FORMAT,
SUBMIT_TIME_FORMAT, SUBMIT_TIME_FORMAT,
typeRules, typeRules,
timeRangeRules, timeRangeRules,
@@ -56,10 +57,11 @@ export default defineComponent({
Object.assign(formData, getDefaultForm()); Object.assign(formData, getDefaultForm());
return; return;
} }
const startTime = record.startTime ? dayjs(record.startTime) : null; // 编辑回填时秒统一归零,界面只精确到分钟
const endTime = record.endTime ? dayjs(record.endTime) : null; const startTime = record.startTime ? dayjs(record.startTime).second(0) : null;
const endTime = record.endTime ? dayjs(record.endTime).second(0) : null;
Object.assign(formData, { Object.assign(formData, {
type: record.type || 1, type: Number(record.type) || 1,
timeRange: startTime?.isValid() && endTime?.isValid() ? [startTime, endTime] : null, timeRange: startTime?.isValid() && endTime?.isValid() ? [startTime, endTime] : null,
title: record.title || '', title: record.title || '',
context: record.context || '', context: record.context || '',
@@ -81,7 +83,11 @@ export default defineComponent({
const validateTimeRange = async (startTime: Dayjs, endTime: Dayjs): Promise<boolean> => { const validateTimeRange = async (startTime: Dayjs, endTime: Dayjs): Promise<boolean> => {
timeRangeChecking.value = true; timeRangeChecking.value = true;
try { try {
const res = await getNoticeList({ page: '1', limit: '999', type: formData.type }); const res = await getNoticeList({
page: '1',
limit: '999',
type: Number(formData.type) || 1,
});
if (res.code != 200) return true; if (res.code != 200) return true;
const editingId = props.record ? String(props.record.id) : ''; const editingId = props.record ? String(props.record.id) : '';
@@ -119,7 +125,7 @@ export default defineComponent({
if (!isTimeRangeValid) return; if (!isTimeRangeValid) return;
props.onSave({ props.onSave({
type: formData.type, type: Number(formData.type) || 1,
title: formData.title.trim(), title: formData.title.trim(),
context: formData.context.trim(), context: formData.context.trim(),
startTime: startTime.format(SUBMIT_TIME_FORMAT), startTime: startTime.format(SUBMIT_TIME_FORMAT),
@@ -146,24 +152,28 @@ export default defineComponent({
{/* 类型 */} {/* 类型 */}
<Form.Item label="类型" name="type" rules={typeRules}> <Form.Item label="类型" name="type" rules={typeRules}>
<Select <Select
value={formData.type} value={Number(formData.type)}
options={NOTICE_TYPE_OPTIONS as any} options={NOTICE_TYPE_OPTIONS as any}
placeholder="请选择" placeholder="请选择"
onUpdate:value={(val: any) => (formData.type = val as number)} onUpdate:value={(val: any) => (formData.type = Number(val) || 1)}
/> />
</Form.Item> </Form.Item>
{/* 生效时间 */} {/* 生效时间 */}
<Form.Item label="生效时间" name="timeRange" rules={timeRangeRules}> <Form.Item label="生效时间" name="timeRange" rules={timeRangeRules}>
<RangePicker <RangePicker
showTime showTime={{ format: 'HH:mm' }}
format={SUBMIT_TIME_FORMAT} format={DISPLAY_TIME_FORMAT}
value={formData.timeRange as any} value={formData.timeRange as any}
placeholder={['开始时间', '结束时间']} placeholder={['开始时间', '结束时间']}
style={{ width: '100%' }} style={{ width: '100%' }}
allowClear allowClear
onUpdate:value={(val: any) => { onUpdate:value={(val: any) => {
formData.timeRange = (val as [Dayjs, Dayjs]) || null; const range =
Array.isArray(val) && val.length === 2
? ([val[0].second(0), val[1].second(0)] as [Dayjs, Dayjs])
: null;
formData.timeRange = range;
formRef.value?.clearValidate('timeRange'); formRef.value?.clearValidate('timeRange');
}} }}
/> />
+9 -9
View File
@@ -63,7 +63,7 @@ function confirmDelete({ record, onOk }: { record: any; onOk: (record: any) => v
Modal.confirm({ Modal.confirm({
title: '删除确认', title: '删除确认',
class: 'deleteConfirmModal', class: 'deleteConfirmModal',
content: `确认要删除“${record.title}”吗?`, content: `确认删除“${record.title}”吗?`,
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
okButtonProps: { danger: true } as ModalProps['okButtonProps'], okButtonProps: { danger: true } as ModalProps['okButtonProps'],
@@ -100,17 +100,17 @@ export default defineComponent({
const { hasPermission, hasAnyPermission } = usePermissionStore(); const { hasPermission, hasAnyPermission } = usePermissionStore();
// 公告模块的权限码后端尚未配置时(/op/permission 未下发),按钮默认放行, // 公告模块的权限码后端尚未配置时(/op/permission 未下发),按钮默认放行,
// 待后端下发任意一个 system.notices.* 权限码后自动切换为按码控制 // 待后端下发任意一个 system.announcement.* 权限码后自动切换为按码控制
const noticePermsConfigured = hasAnyPermission([ const noticePermsConfigured = hasAnyPermission([
'system.notices.view', 'system.announcement.view',
'system.notices.add', 'system.announcement.save',
'system.notices.edit', 'system.announcement.edit',
'system.notices.delete', 'system.announcement.del',
]); ]);
const canAdd = !noticePermsConfigured || hasPermission('system.notices.add'); const canAdd = !noticePermsConfigured || hasPermission('system.announcement.save');
const operationPermissions = { const operationPermissions = {
canEdit: !noticePermsConfigured || hasPermission('system.notices.edit'), canEdit: !noticePermsConfigured || hasPermission('system.announcement.edit'),
canDelete: !noticePermsConfigured || hasPermission('system.notices.delete'), canDelete: !noticePermsConfigured || hasPermission('system.announcement.del'),
}; };
const triggerDelete = (record: any) => confirmDelete({ record, onOk: handleDelete }); const triggerDelete = (record: any) => confirmDelete({ record, onOk: handleDelete });
@@ -19,11 +19,8 @@ import {
// 常量 // 常量
// ============================================================ // ============================================================
/** 公告类型选项(筛选 / 表单共用) */ /** 公告类型选项(筛选 / 表单共用):目前仅开放「首页公告提示」 */
export const NOTICE_TYPE_OPTIONS = [ export const NOTICE_TYPE_OPTIONS = [{ value: 1, label: '首页公告提示' }] as const;
{ value: 1, label: '首页公告提示' },
{ value: 2, label: '产品更新公告' },
] as const;
/** 公告类型文案映射(兜底,后端 typeName 为空时使用) */ /** 公告类型文案映射(兜底,后端 typeName 为空时使用) */
export const NOTICE_TYPE_MAP: Record<number, string> = { export const NOTICE_TYPE_MAP: Record<number, string> = {
@@ -67,9 +64,10 @@ export function useNoticeModel() {
formatResult: (res) => res, formatResult: (res) => res,
}); });
const dataSource = computed(() => { const dataSource = computed<NoticeVO[]>(() => {
const res = data.value; const list = (data.value as any)?.data?.list || [];
return res ? (res as any).data?.list || [] : []; // 后端 type 可能返回字符串,统一归一化为数字,保证编辑弹窗 Select 能匹配选项
return list.map((item: NoticeVO) => ({ ...item, type: Number(item.type) }));
}); });
useEffect(() => { useEffect(() => {