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);