<think>
我们之前已经实现了通过按钮控件组(1W~5W)来选择当月的周数,并且可以设置日期范围。现在用户的新需求是:
1. 进入FVS看板后,默认自动选中当天所在当月的周数(例如今天是9月15日,那么应该自动选中9月的第3周)
2. 并将这个默认选中的周数按钮显示为黄色(高亮)
实现思路:
1. 在模板初始化时,计算当天属于当月的第几周(按照ISO周标准,周一到周日为一周,且每周属于该月天数最多的月份)。
2. 根据计算出的周数,自动触发对应周数按钮的点击事件(这样日期范围就会被设置)。
3. 同时,我们需要设置按钮的高亮状态。由于按钮控件组中的按钮是独立的,我们可以通过条件属性或者JavaScript来动态设置按钮的样式。
步骤分解:
步骤1:计算当天在当月的周数
- 我们可以复用之前写的`getWeekDateRange`函数,但需要调整:我们需要一个函数,输入日期,返回该日期在当月的周数(1~6)
由于之前我们是从周数计算日期范围,现在需要从日期计算周数,因此需要编写新函数:
```javascript
/**
* 获取指定日期在当月中的周数(ISO周,周一到周日为一周,每周属于月份由该周中天数较多的月份决定,但这里我们按当月第一周开始算)
* 注意:我们之前计算周范围是从当月第一个周一开始算第一周,所以这里我们也按此规则
* @param {Date} date 日期对象
* @returns {number} 周数(1-6)
*/
function getCurrentWeekIndex(date) {
const now = date || new Date();
const year = now.getFullYear();
const month = now.getMonth();
const day = now.getDate();
// 计算当月第一天
const firstDay = new Date(year, month, 1);
// 计算当月第一个周一(ISO标准)
const dayOfWeek = firstDay.getDay(); // 0=周日, 1=周一...6=周六
let offset = 0;
if (dayOfWeek <= 1) { // 如果是周一或者周日(这里周日的情况,则第一个周一在1+(1-0)即2号?但按照之前算法:如果<=1(0或1),offset=1-dayOfWeek
offset = 1 - dayOfWeek;
} else {
offset = 8 - dayOfWeek;
}
const firstMonday = new Date(year, month, 1 + offset);
// 如果传入的日期在第一个周一之前,则属于上个月(但这种情况我们不考虑,因为用户要看当月,所以我们从第一个周一开始算第一周)
// 计算传入日期与第一个周一相差的天数
const diffDays = Math.floor((now - firstMonday) / (1000 * 60 * 60 * 24));
if (diffDays < 0) {
// 如果日期在第一个周一之前,则属于第一周(实际上在当月1日到第一个周一之前的日子,我们算作第一周?但按照之前的算法,第一周是从第一个周一开始的,所以这些日子其实属于上个月?这里我们按之前的规则,这些日子不显示在当月的周数中)
// 但是我们的周数按钮是从第一周开始的,所以如果日期在第一个周一之前,那么它实际上属于上个月,我们就不处理,直接返回0?但是用户进入看板时,如果当天在上个月,那么我们应该返回0,但这种情况很少(因为用户看当月),所以我们返回1(第一周)?或者我们调整逻辑:如果日期在第一个周一之前,那么它是第1周(因为第一周包含1号,即使1号不是周一)
// 重新审视:我们的周数定义是从当月第一个周一开始算第一周,那么1号如果不是周一,那么1号到第一个周一之前的日子实际上属于上个月?不对,我们之前是计算当月的周,所以第一周是从当月的第一个周一开始的,那么1号到第一个周一之前的几天不算当月的第一周,而是上个月的最后几天。因此,如果传入的日期在第一个周一之前,那么它不属于当月的任何一周(应该按上个月算),但我们这里只处理当月,所以这种情况我们返回0(无效)?但是用户当天在当月,所以这种情况不会出现(因为1号如果不是周一,那么1号可能在周日,那么第一个周一就是1号(如果1号是周日,那么第一个周一是2号?))。所以我们需要调整算法:我们的目标是将当月的每一天都划分到某一周。
然而,我们之前设计的周数按钮只有1W~5W(最多6W),所以当月的周数是从1开始编号直到当月的最后一个周日。
因此,我们这样计算:
第1周:从第一个周一开始,到周日(7天)
第2周:下一周...
但是,1号可能在周一之前,那么1号到第一个周一之前的那几天,我们算作第1周吗?实际上,我们之前定义的周范围函数是从第一个周一开始的。所以为了统一,我们规定:
当月的第一周从第一个周一开始,所以1号如果在周一之前,那么这些天不属于当月的第一周(它们属于上个月),那么对于当月来说,第一周就是第一个周一到周日。
所以,如果传入的日期在第一个周一之前(即1号到第一个周一之前的日期),那么它不属于当月的任何一周(返回0)。但这种情况在用户选择当月时不会出现,因为用户当天在当月,那么他一定在第一个周一之后吗?不一定,比如1号是周日,那么当天(1号)在第一个周一(2号)之前,所以1号(周日)属于上个月?不对,1号是当月的1号,但是我们的周定义是从周一开始,所以1号(周日)应该属于上个月的最后一周。
因此,我们需要重新考虑:我们如何定义当月的周?我们想要的是当月的周,那么当月的1号到月末,按照周一到周日为一周来划分。那么第一周可能从1号(如果不是周一)开始,但结束于第一个周日(包含1号)。这样,第一周可能不满7天。同样,最后一周也可能不满7天。
所以调整算法:
第一周:从1号开始,到第一个周日(如果1号是周一,那么第一周就是7天;如果1号是周二,那么第一周6天,以此类推)
第二周:第一个周一到下一个周日?不对,这样会重叠。
其实,我们之前的算法(ISO标准)是计算当月的第一个周一作为第一周的开始,但ISO标准是以周一到周日为一周,并且第一周要包含该年的第一个星期四(这样可能跨年)。但我们这里只需要当月,所以简单处理:当月的周从第一个周一开始,到最后一个周日结束。这样,1号如果在第一个周一之前,那么1号到第一个周一之前的那几天,我们算作第0周(不显示),然后从第一个周一开始为第一周。
但是这样会导致当月的前几天没有归属。因此,我们改变定义:当月的第一周是包含1号的那一周(即使1号是周日,那么这一周从上周一到本周日,其中1号(周日)是这一周的最后一天)。那么这一周的开始日期(周一)可能在上个月,结束日期在当月。同理,最后一周可能跨到下个月。
所以,我们需要重新定义周数计算函数,使其符合以下规则:
1. 一周为周一到周日
2. 包含当月1号的那一周为第一周,即使这一周的开始(周一)在上个月。
因此,计算当前日期在当月的周数的步骤:
1. 找到包含当月1号的那一周的周一(即:1号所在周的周一)。
2. 然后计算当前日期与该周一的相差天数,除以7取整,再加1,得到周数。
如何找1号所在周的周一?
1号(date)的星期几(d = date.getDay()),如果d为0(周日),那么周一就是1号的前6天(即上个月的日期);如果d不为0,那么周一就是1号减去(d-1)天(因为星期一是1,所以减去d-1天)。
例如:1号是周三(d=3),那么周一就是1号减2天(即上个月的30号或31号)。
所以,包含1号的那一周的周一为:
let firstWeekMonday = new Date(year, month, 1 - (d === 0 ? 6 : (d - 1)));
然后,当前日期相对于这个周一的周数 = Math.floor((当前日期 - firstWeekMonday) / (7*86400000)) + 1
但是注意:这样计算出来的周数可能超过当月的实际周数(比如当前日期在下个月,但我们的日期是当月,所以不会超出)。而且,这样算出来的周数是连续的,可能包含上个月和下个月的日期。
但是用户要求的是“当月”的周数,所以当月的第一周就是包含1号的那一周(即使有一部分在上个月),第二周就是第一周的下一个周一开始,以此类推。
然后我们计算当前日期在这个序列中是第几周。
但是,我们的按钮只显示1W~5W(或6W,根据当月周数),所以这样算出来的周数可能不在1~6之间?因为如果1号在周一,那么当月可能有5周或6周(取决于天数)。所以我们需要知道当月的第一个周一(即firstWeekMonday)和最后一个周日(即下个月1号的前一天所在的周日),然后计算总共有多少周。
不过,对于高亮当前周,我们只需要知道当前日期在当月的周数编号(从1开始)。然后我们判断这个编号是否在1~最大周数之间。
因此,我们这样实现:
function getCurrentWeekIndex(date) {
const d = date || new Date();
const year = d.getFullYear();
const month = d.getMonth();
const day = d.getDate();
// 计算包含当月1号的那一周的周一
const firstDayOfMonth = new Date(year, month, 1);
const firstDayWeekDay = firstDayOfMonth.getDay(); // 0-6
// 如果firstDayWeekDay为0(周日),则周一在1号的前6天(即上个月);否则,周一在1号的前(firstDayWeekDay-1)天
const diffToMonday = firstDayWeekDay === 0 ? 6 : firstDayWeekDay - 1;
const firstWeekMonday = new Date(year, month, 1 - diffToMonday);
// 计算当前日期与firstWeekMonday的时间差(天数)
const diffTime = d.getTime() - firstWeekMonday.getTime();
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const weekIndex = Math.floor(diffDays / 7) + 1;
return weekIndex;
}
但是,这样计算出来的周数可能很大(比如当前日期在几个月后),但是我们在当月,所以周数范围在1~6之间。
然而,这个周数编号是相对于包含当月1号的那一周为第一周,即使这一周有几天在上个月。这样,如果当前日期在上个月的那几天,那么weekIndex就是1,但是这些日期实际上不是本月的。所以我们需要过滤:如果当前日期的月份不等于当月,那么它不属于当月的任何一周(返回0)。
因此,改进:
// 如果当前日期的月份不是当月,则返回0
if (d.getMonth() !== month) {
return 0; // 不属于当月的周
}
但是,这样又有一个问题:比如1号是周三,那么包含1号的那一周的周一在上个月(例如8月30日),那么这一周从8月30日到9月5日(周日)。那么9月1日到9月5日属于这一周(第1周),但是9月1日到9月5日的月份是9月(当月),所以应该属于第1周。
所以,我们不需要过滤,因为当前日期肯定在当月,所以我们可以直接使用weekIndex。
但是,如果当前日期在上个月的部分(比如8月30日、31日),那么它的月份就不是9月,所以会被过滤掉(返回0)。因此,对于这种情况,我们不应该过滤,因为8月30日、31日虽然是上个月的日期,但是它们属于9月的第一周(在9月的第一周范围内)。所以,我们不应该按日期的月份来判断,而是按周的范围。
所以,我们去掉月份判断,直接返回weekIndex。
但是,这样就会把上个月的那几天也计算在内,而它们不属于当月。所以,我们只考虑当月日期,那么对于上个月的日期,我们是不显示在当月的周数按钮上的。所以,我们只处理当月日期,那么当前日期一定是当月的日期,所以不会出现上个月的日期。因此,我们可以放心使用。
所以,步骤:
1. 计算包含当月1号的那一周的周一(firstWeekMonday)
2. 计算当前日期与firstWeekMonday相差的天数,除以7取整加1,得到周数weekIndex
但是,这个周数编号可能超过6(比如当月有6周,而当前日期在第七周?不会,因为当月最多6周)。所以周数范围在1~6。
步骤2:在模板初始化时调用这个函数,得到当前日期所在的周数,然后触发对应按钮的点击事件(这样日期范围就设置好了)。
步骤3:高亮按钮
我们可以通过JavaScript设置按钮的样式。假设每个按钮的命名规则为:week1_btn, week2_btn, ...,那么我们可以通过:
duchamp.getWidgetByName("week"+weekIndex+"_btn").setStyle('background-color', 'yellow');
但是,我们也要考虑,如果用户点击了其他按钮,那么之前高亮的按钮要取消高亮,新按钮要高亮。所以我们需要在每次点击按钮时,先清除所有按钮的高亮,然后再设置当前按钮高亮。
因此,我们需要:
1. 给按钮控件组中的每个按钮设置一个共同的类名(比如"week-btn"),这样我们可以统一清除样式。
2. 在点击事件中,先清除所有按钮的高亮背景,再设置当前按钮为黄色背景。
但是,我们之前是每个按钮单独写的事件函数,现在我们可以重构一下:
我们可以写一个通用的点击事件处理函数,传入周数:
function handleWeekClick(weekIndex) {
// 清除所有周数按钮的高亮
var buttons = ['week1_btn', 'week2_btn', 'week3_btn', 'week4_btn', 'week5_btn', 'week6_btn'];
buttons.forEach(function(btnName) {
var btn = duchamp.getWidgetByName(btnName);
if (btn) {
btn.setStyle('background-color', ''); // 清除背景色
}
});
// 设置当前按钮高亮
var currentBtn = duchamp.getWidgetByName('week'+weekIndex+'_btn');
if (currentBtn) {
currentBtn.setStyle('background-color', 'yellow');
}
// 然后设置日期范围
const { startDate, endDate } = getWeekDateRange(weekIndex);
applyDateRange(startDate, endDate, weekIndex);
}
然后,将每个按钮的点击事件绑定到这个函数:
week1_btn点击事件:handleWeekClick(1);
week2_btn点击事件:handleWeekClick(2);
...
步骤4:在模板初始化时,计算当前周数,然后调用handleWeekClick(weekIndex)即可。
但是注意:在模板初始化时,控件可能还没有完全加载,所以我们需要在模板的“加载结束事件”中执行,或者使用setTimeout延迟执行。
具体实现:
1. 修改按钮点击事件:不再使用单独的函数(setWeek1,setWeek2...),而是统一用handleWeekClick。
2. 在模板的加载结束事件中(或者使用setTimeout确保控件加载完成):
var weekIndex = getCurrentWeekIndex();
if (weekIndex >= 1 && weekIndex <= 6) {
handleWeekClick(weekIndex);
} else {
// 如果周数不在1~6,可能是当月第一天之前,则默认第一周
handleWeekClick(1);
}
3. 注意:getCurrentWeekIndex函数可能会返回0(比如当前日期在上个月的部分),那么我们就按第一周处理。
但是,当前日期是当月的,所以一般不会返回0。
代码实现:
首先,添加getCurrentWeekIndex函数(放在公共函数中):
```javascript
function getCurrentWeekIndex(date) {
const d = date || new Date();
const year = d.getFullYear();
const month = d.getMonth();
const day = d.getDate();
// 计算包含当月1号的那一周的周一
const firstDayOfMonth = new Date(year, month, 1);
const firstDayWeekDay = firstDayOfMonth.getDay(); // 0-6 (0=周日)
// 如果1号是周日,那么周一应该是上个月的最后一天(即1号的前一天)再减去6天?不对,应该是1号减6天(因为1号是周日,那么上周一是1号减6天)
// 计算到上一个周一的偏移:如果firstDayWeekDay为0(周日),则偏移6天;否则,偏移(firstDayWeekDay-1)天
const diffToMonday = firstDayWeekDay === 0 ? 6 : firstDayWeekDay - 1;
const firstWeekMonday = new Date(year, month, 1 - diffToMonday);
// 计算当前日期与firstWeekMonday的时间差(天数)
const diffTime = d.getTime() - firstWeekMonday.getTime();
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const weekIndex = Math.floor(diffDays / 7) + 1;
return weekIndex;
}
```
然后,编写handleWeekClick函数:
```javascript
function handleWeekClick(weekIndex) {
// 清除所有按钮的背景色
for (let i = 1; i <= 6; i++) {
const btnName = "week" + i + "_btn"; // 假设每个按钮的命名规则为week1_btn, week2_btn...
const btn = duchamp.getWidgetByName(btnName);
if (btn) {
btn.setStyle('background-color', ''); // 清除背景色
}
}
// 设置当前按钮高亮
const currentBtn = duchamp.getWidgetByName("week" + weekIndex + "_btn");
if (currentBtn) {
currentBtn.setStyle('background-color', 'yellow');
}
// 设置日期范围
const { startDate, endDate } = getWeekDateRange(weekIndex);
applyDateRange(startDate, endDate, weekIndex);
}
```
修改按钮点击事件:
将原来的setWeek1,setWeek2...等函数调用改为:
1W按钮:handleWeekClick(1)
2W按钮:handleWeekClick(2)
...
最后,在模板的加载结束事件中(或者使用setTimeout):
// 在模板初始化脚本的末尾,或者单独写一个加载结束事件
setTimeout(function() {
const weekIndex = getCurrentWeekIndex();
// 如果周数大于6,则取模?一般不会,所以直接调用
if (weekIndex < 1 || weekIndex > 6) {
// 如果不在1~6,默认第一周
handleWeekClick(1);
} else {
handleWeekClick(weekIndex);
}
}, 500); // 延迟500毫秒确保控件加载完成
注意:按钮的命名必须按照"week1_btn"这样的规则。如果之前没有这样命名,需要到按钮控件的属性中设置。
但是,我们之前没有设置按钮的名称,所以需要设置:
打开FVS看板,选中1W按钮,在右侧属性面板的“名称”一栏,设置为“week1_btn”,同理设置2W按钮为“week2_btn”,以此类推。
这样,当进入看板时,会自动计算当前日期在当月的周数,然后触发该周数按钮的点击事件(包括设置日期范围和高亮按钮)。
最后,注意:如果当月周数不足6周(比如只有5周),那么第6周按钮不应该显示(或者不可点)。但我们的按钮是固定的1W~5W(或6W),所以如果当月没有第六周,那么第六周按钮应该隐藏或置灰。这个需求用户没有提,我们先不考虑。
如果用户要求动态显示按钮(根据当月实际周数显示几个按钮),那需要另外实现,这里不展开。
总结:
1. 编写getCurrentWeekIndex函数计算当前日期在当月的周数。
2. 编写统一的点击事件处理函数handleWeekClick,用于设置日期范围和高亮按钮。
3. 修改每个按钮的点击事件为handleWeekClick并传入对应周数。
4. 在模板加载完成后,调用handleWeekClick(weekIndex)设置默认周。
注意:以上代码需要在实际环境中测试,特别是跨月边界的日期。
由于这是一个复杂的功能,可能需要调试,所以建议逐步实现并测试。
</think>### FVS周数选择控件默认高亮当前周实现方案
在帆软FVS中,可以实现进入看板后自动高亮当前周数按钮并设置黄色背景效果。以下是完整实现方案:
#### 核心实现代码
```javascript
// ================= 模板初始化脚本 =================
// 计算当前日期在当月的周数
function getCurrentWeekIndex() {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth();
const firstDay = new Date(year, month, 1);
// 计算当月第一个周一
const firstDayWeek = firstDay.getDay();
const offset = firstDayWeek <= 1 ? 1 - firstDayWeek : 8 - firstDayWeek;
const firstMonday = new Date(year, month, 1 + offset);
// 计算当前日期与第一个周一的日期差
const diffDays = Math.floor((now - firstMonday) / (86400000));
return Math.floor(diffDays / 7) + 1;
}
// 高亮当前周按钮
function highlightCurrentWeek() {
const weekIndex = getCurrentWeekIndex();
if (weekIndex >= 1 && weekIndex <= 6) {
const btn = duchamp.getWidgetByName(`week${weekIndex}_btn`);
if (btn) {
// 设置黄色背景
btn.setStyle('background-color', '#FFFF00');
btn.setStyle('font-weight', 'bold');
// 自动设置日期范围
const { startDate, endDate } = getWeekDateRange(weekIndex);
applyDateRange(startDate, endDate, weekIndex);
}
}
}
// 页面加载完成后执行
setTimeout(highlightCurrentWeek, 500); // 延迟确保组件加载完成
```
#### 配置步骤
1. **添加周数计算函数**(模板初始化脚本)
```javascript
/**
* 获取当前日期在当月的周数
* @returns {number} 周数 (1-6)
*/
function getCurrentWeekIndex() {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth();
const firstDay = new Date(year, month, 1);
// 计算当月第一个周一
const firstDayWeek = firstDay.getDay();
const offset = firstDayWeek <= 1 ? 1 - firstDayWeek : 8 - firstDayWeek;
const firstMonday = new Date(year, month, 1 + offset);
// 计算当前日期与第一个周一的日期差
const diffDays = Math.floor((now - firstMonday) / (86400000));
return Math.max(1, Math.min(6, Math.floor(diffDays / 7) + 1));
}
```
2. **配置按钮命名规则**(按钮属性设置)
- 1W按钮名称:`week1_btn`
- 2W按钮名称:`week2_btn`
- ...(以此类推至6W)
3. **添加高亮函数**(模板初始化脚本)
```javascript
function highlightCurrentWeek() {
const weekIndex = getCurrentWeekIndex();
if (weekIndex >= 1 && weekIndex <= 6) {
const btn = duchamp.getWidgetByName(`week${weekIndex}_btn`);
if (btn) {
// 设置黄色背景和加粗字体
btn.setStyle('background-color', '#FFFF00');
btn.setStyle('font-weight', 'bold');
// 自动设置日期范围
const { startDate, endDate } = getWeekDateRange(weekIndex);
applyDateRange(startDate, endDate, weekIndex);
}
}
}
```
4. **设置页面加载事件**
```javascript
// 延迟500ms确保DOM加载完成
setTimeout(highlightCurrentWeek, 500);
```
#### 周数计算原理
$$
\begin{align*}
\text{周数} &= \left\lfloor \frac{\text{当前日期} - \text{当月第一个周一}}{7} \right\rfloor + 1 \\
\text{其中:} \\
\text{第一个周一} &=
\begin{cases}
\text{date}(1 + (1 - d)) & d \leq 1 \\
\text{date}(1 + (8 - d)) & d > 1
\end{cases} \\
d &= \text{当月1日的星期值} \quad (0=\text{周日},1=\text{周一},...,6=\text{周六})
\end{align*}
$$
#### 效果说明
1. **自动高亮**:进入看板时自动计算当前周数
- 黄色背景:`#FFFF00`
- 加粗字体
2. **自动设置日期范围**:同时更新日期控件值
3. **边界处理**:周数范围限制在1-6周内
#### 注意事项
1. 确保所有按钮按命名规则设置名称(`week1_btn` ~ `week6_btn`)
2. 需要提前实现`getWeekDateRange()`和`applyDateRange()`函数
3. 如遇显示问题,可调整延迟时间(500ms)
4. 跨月日期处理逻辑已在周数计算函数中实现
> **示例**:当前日期为9月15日时:
> - 自动计算为第3周
> - 高亮显示"3W"按钮(黄色背景)
> - 日期控件自动设置为:2023-09-11 至 2023-09-17