From 07eafb568419e83a369586dc1597c96c0ba7d55d Mon Sep 17 00:00:00 2001 From: zhouyonggao <1971162852@qq.com> Date: Mon, 22 Dec 2025 16:38:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(export):=20=E4=BC=98=E5=8C=96=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E8=8C=83=E5=9B=B4=E9=80=89=E6=8B=A9=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E9=99=90=E5=88=B6=E8=B7=A8=E5=BA=A6=E4=B8=8D=E8=B6=85?= =?UTF-8?q?=E8=BF=871=E5=B9=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改后端时间跨度验证逻辑,定义1年为从开始日期往后推1年 - 前端日期选择组件新增选择过程监听,记录首选日期以限制第二个日期范围 - 实现禁用时间函数,禁止选择超过1年跨度的时间范围 - 添加时间跨度变化检测,超出1年时清空选择并弹窗提示 - 前端导入中文语言包,支持ElementPlus中文本地化 - API模块统一业务错误码处理,非0码抛出带错误信息异常 --- server/internal/api/exports.go | 13 +++-- web/index.html | 4 ++ web/main.js | 93 +++++++++++++++++++++++++------- web/modules/api.js | 13 ++++- web/vendor/element-plus-zh-cn.js | 86 +++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 web/vendor/element-plus-zh-cn.js diff --git a/server/internal/api/exports.go b/server/internal/api/exports.go index 54d04e6..47b33f1 100644 --- a/server/internal/api/exports.go +++ b/server/internal/api/exports.go @@ -23,6 +23,7 @@ import ( ) // validateTimeRange 验证时间范围不超过1年 +// 1年的定义:从开始日期往后推1年 func validateTimeRange(startVal, endVal interface{}) error { var startStr, endStr string @@ -60,12 +61,14 @@ func validateTimeRange(startVal, endVal interface{}) error { return errors.New("结束时间必须晚于开始时间") } - // 计算时间跨度 - duration := endTime.Sub(startTime) - oneYear := 365 * 24 * time.Hour + // 计算开始日期往后推1年的时间点 + oneYearLater := startTime.AddDate(1, 0, 0) - if duration > oneYear { - return fmt.Errorf("时间跨度超过1年限制,当前跨度为 %.1f 天,最多允许 365 天", duration.Hours()/24) + // 如果结束时间超过这个时间点,则超过1年限制 + if endTime.After(oneYearLater) { + duration := endTime.Sub(startTime) + daysCount := duration.Hours() / 24 + return fmt.Errorf("时间跨度超过1年限制,当前跨度为 %.1f 天,最多允许 365 天", daysCount) } return nil diff --git a/web/index.html b/web/index.html index 726566e..3a0fb54 100644 --- a/web/index.html +++ b/web/index.html @@ -235,12 +235,15 @@ @@ -308,6 +311,7 @@ + diff --git a/web/main.js b/web/main.js index 26657d6..feb3cc3 100644 --- a/web/main.js +++ b/web/main.js @@ -190,34 +190,44 @@ const app = createApp({ new Date(2000, 0, 1, 23, 59, 59), ]; + // 用于记录用户正在选择的第一个日期(用于限制第二个日期的可选范围) + const pickerMinDate = Vue.ref(null); + + /** + * 日历面板变化事件 - 当用户点击日历时触发 + * 用于记录用户选择的第一个日期 + * @param {Array} dates - 已选日期数组 + */ + const onCalendarChange = (dates) => { + if (dates && dates.length === 1) { + // 用户选择了第一个日期 + pickerMinDate.value = dates[0]; + } else { + // 选择完成或清空 + pickerMinDate.value = null; + } + }; + /** * 禁用超过一年的日期 + * 当用户选择了第一个日期后,自动限制第二个日期的可选范围 * @param {Date} date - 要检查的日期 * @returns {boolean} 是否禁用 */ const disabledDate = (date) => { if (!date) return false; - // 如果已经选择了开始时间,计算结束时间限制 - if (state.exportForm.dateRange && state.exportForm.dateRange[0]) { - const startTime = new Date(state.exportForm.dateRange[0]); - const oneYearLater = new Date(startTime); - oneYearLater.setFullYear(startTime.getFullYear() + 1); - oneYearLater.setDate(oneYearLater.getDate() - 1); // 减去一天以确保不超过一年 + // 如果用户正在选择第二个日期,限制范围不超过1年 + if (pickerMinDate.value) { + const selectedDate = new Date(pickerMinDate.value); + const oneYearMs = 365 * 24 * 60 * 60 * 1000; - // 只限制结束时间不能超过开始时间一年后 - if (date > oneYearLater) return true; - } - - // 如果已经选择了结束时间,计算开始时间限制 - if (state.exportForm.dateRange && state.exportForm.dateRange[1]) { - const endTime = new Date(state.exportForm.dateRange[1]); - const oneYearEarlier = new Date(endTime); - oneYearEarlier.setFullYear(endTime.getFullYear() - 1); - oneYearEarlier.setDate(oneYearEarlier.getDate() + 1); // 加上一天 + // 计算允许的日期范围 + const minAllowed = new Date(selectedDate.getTime() - oneYearMs); + const maxAllowed = new Date(selectedDate.getTime() + oneYearMs); - // 只限制开始时间不能早于结束时间一年前 - if (date < oneYearEarlier) return true; + // 禁用超出范围的日期 + return date < minAllowed || date > maxAllowed; } return false; @@ -288,6 +298,41 @@ const app = createApp({ }, ]; + /** + * 验证时间跨度是否超过1年 + * 1年的定义:从开始日期往后推1年 + * @param {Array} dateRange - 时间范围 [开始时间, 结束时间] + * @returns {boolean} 是否超过1年 + */ + const isDateRangeExceedOneYear = (dateRange) => { + if (!dateRange || dateRange.length !== 2 || !dateRange[0] || !dateRange[1]) { + return false; + } + const start = new Date(dateRange[0]); + const end = new Date(dateRange[1]); + + // 计算开始日期往后推1年的时间点 + const oneYearLater = new Date(start); + oneYearLater.setFullYear(start.getFullYear() + 1); + + // 如果结束时间超过这个时间点,则超过1年 + return end > oneYearLater; + }; + + /** + * 时间范围变化处理函数 + * 如果时间跨度超过1年,清空并提示 + * @param {Array} val - 时间范围 + */ + const onDateRangeChange = (val) => { + if (isDateRangeExceedOneYear(val)) { + // 清空时间范围 + state.exportForm.dateRange = null; + // 显示提示 + showMessage('时间跨度不能超过1年(365天),请重新选择', 'warning'); + } + }; + // ==================== 表单引用 ==================== const createFormRef = Vue.ref(null); const editFormRef = Vue.ref(null); @@ -820,7 +865,11 @@ const app = createApp({ showMessage('任务创建返回异常', 'error'); } } catch (error) { - showMessage(error.message || '导出失败', 'error'); + // 使用模态框显示错误信息 + ElementPlus.ElMessageBox.alert(error.message || '导出失败', '提示', { + type: 'error', + confirmButtonText: '确定' + }); } finally { state.exportSubmitting = false; } @@ -1093,6 +1142,8 @@ const app = createApp({ dateDefaultTime, dateShortcuts, disabledDate, + onCalendarChange, + onDateRangeChange, // 表单引用 createFormRef, editFormRef, @@ -1132,7 +1183,9 @@ const app = createApp({ } }); -app.use(ElementPlus); +app.use(ElementPlus, { + locale: window.ElementPlusLocaleZhCn +}); app.mount('#app'); })(); diff --git a/web/modules/api.js b/web/modules/api.js index b638673..e48717e 100644 --- a/web/modules/api.js +++ b/web/modules/api.js @@ -157,7 +157,18 @@ const post = async (endpoint, body, options = {}) => { const errorText = await response.text(); throw new Error(errorText || `请求失败: ${response.status}`); } - return response.json(); + + const result = await response.json(); + + // 检查业务错误码 + if (result && result.code !== undefined && result.code !== 0) { + const error = new Error(result.msg || '操作失败'); + error.code = result.code; + error.data = result.data; + throw error; + } + + return result; }; /** diff --git a/web/vendor/element-plus-zh-cn.js b/web/vendor/element-plus-zh-cn.js new file mode 100644 index 0000000..a821b94 --- /dev/null +++ b/web/vendor/element-plus-zh-cn.js @@ -0,0 +1,86 @@ +/*! Element Plus v2.13.0 - Chinese (zh-cn) */ +(function(global) { + var zhCn = { + name: "zh-cn", + el: { + breadcrumb: { label: "面包屑" }, + colorpicker: { + confirm: "确定", + clear: "清空", + defaultLabel: "颜色选择器", + description: "当前颜色 {color},按 Enter 键选择新颜色" + }, + datepicker: { + now: "此刻", + today: "今天", + cancel: "取消", + clear: "清空", + confirm: "确定", + dateTablePrompt: "使用方向键与 Enter 键可选择日期", + monthTablePrompt: "使用方向键与 Enter 键可选择月份", + yearTablePrompt: "使用方向键与 Enter 键可选择年份", + selectedDate: "已选日期", + selectDate: "选择日期", + selectTime: "选择时间", + startDate: "开始日期", + startTime: "开始时间", + endDate: "结束日期", + endTime: "结束时间", + prevYear: "前一年", + nextYear: "后一年", + prevMonth: "上个月", + nextMonth: "下个月", + year: "年", + month1: "1 月", + month2: "2 月", + month3: "3 月", + month4: "4 月", + month5: "5 月", + month6: "6 月", + month7: "7 月", + month8: "8 月", + month9: "9 月", + month10: "10 月", + month11: "11 月", + month12: "12 月", + weeks: { sun: "日", mon: "一", tue: "二", wed: "三", thu: "四", fri: "五", sat: "六" }, + weeksFull: { sun: "星期日", mon: "星期一", tue: "星期二", wed: "星期三", thu: "星期四", fri: "星期五", sat: "星期六" }, + months: { jan: "一月", feb: "二月", mar: "三月", apr: "四月", may: "五月", jun: "六月", jul: "七月", aug: "八月", sep: "九月", oct: "十月", nov: "十一月", dec: "十二月" } + }, + inputNumber: { decrease: "减少数值", increase: "增加数值" }, + select: { loading: "加载中", noMatch: "无匹配数据", noData: "无数据", placeholder: "请选择" }, + mention: { loading: "加载中" }, + dropdown: { toggleDropdown: "切换下拉选项" }, + cascader: { noMatch: "无匹配数据", loading: "加载中", placeholder: "请选择", noData: "暂无数据" }, + pagination: { + goto: "前往", + pagesize: "条/页", + total: "共 {total} 条", + pageClassifier: "页", + page: "页", + prev: "上一页", + next: "下一页", + currentPage: "第 {pager} 页", + prevPages: "向前 {pager} 页", + nextPages: "向后 {pager} 页" + }, + dialog: { close: "关闭此对话框" }, + drawer: { close: "关闭此对话框" }, + messagebox: { title: "提示", confirm: "确定", cancel: "取消", error: "输入的数据不合法!", close: "关闭此对话框" }, + upload: { deleteTip: "按 Delete 键可删除", delete: "删除", preview: "查看图片", continue: "继续上传" }, + slider: { defaultLabel: "滑块介于 {min} 至 {max}", defaultRangeStartLabel: "选择起始值", defaultRangeEndLabel: "选择结束值" }, + table: { emptyText: "暂无数据", confirmFilter: "筛选", resetFilter: "重置", clearFilter: "全部", sumText: "合计" }, + tag: { close: "关闭此标签" }, + tour: { next: "下一步", previous: "上一步", finish: "结束导览", close: "关闭此对话框" }, + tree: { emptyText: "暂无数据" }, + transfer: { noMatch: "无匹配数据", noData: "无数据", titles: ["列表 1", "列表 2"], filterPlaceholder: "请输入搜索内容", noCheckedFormat: "共 {total} 项", hasCheckedFormat: "已选 {checked}/{total} 项" }, + image: { error: "加载失败" }, + pageHeader: { title: "返回" }, + popconfirm: { confirmButtonText: "确定", cancelButtonText: "取消" }, + carousel: { leftArrow: "上一张幻灯片", rightArrow: "下一张幻灯片", indicator: "幻灯片切换至索引 {index}" } + } + }; + + // 暴露到全局 + global.ElementPlusLocaleZhCn = zhCn; +})(typeof window !== 'undefined' ? window : this);