整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

头条文章-Excel中带农历的万年历设计方法一

头条文章-Excel中带农历的万年历设计方法一

位好,在前面有一期作品中,我们曾经在Excel中实现了万年历的制作,当时也有很多网友看过我的那期头条文章或头条视频,附带有评价和收藏,在此向你们表示感谢。

另外,有个名叫“斑斓虎zcy”的粉丝评论说我做的万年历如果含有农历就完美了,对这位粉丝的提议我欣然接受,另外,虽然我没采用“斑斓虎zcy”粉丝提供的公历转农历的关键技术模版,但对“斑斓虎zcy”粉丝的热心表示深深的谢意!

刚刚上一期,我为大家分享了自己弄的公历<---->农历互转的技术与方法,也有很多网友看了这期头条文章或视频,说明大家对这个方法也很认可的,谢谢各位啦!接下来,我准备用两种公历<---->农历互转的方法实现带农历的万年历设计吧!为了区分起见,我们暂定本期的题目为“头条文章--Excel中带农历的万年历设计方法一”、下期作品的题目为“头条文章--Excel中带农历的万年历设计方法二”。

本期,我们先来用第一种方法实现吧。

一、Excel前端带农历万年历界面设计

关于界面的设计,这里和上次那一期万年历的界面一样,这里不做过多描述,这里就只以截图直接呈现给各位吧。如下图所示

图1 带农历的万年历界面

二、用方法一实现带农历万年历的功能代码

模块1中代码如下:

'强势自定义“公历”<---->“农历”互转函数

'原创:互联网

'修正:号作者“跟我学Office高级办公应用” 2019/10/12

'---农历数据定义---

'先以 Hexadecimal_To_Binary 函数还原成长度为 18 的字符串,其定义如下:

'前12个字节代表1-12月:1为大月,0为小月;压缩成十六进制(1-3位)

'第13位为闰月的情况,1为大月30天,0为小月29天;(4位)

'第14位为闰月的月份,如果不是闰月为0,否则给出月份(5位)

'最后4位为当年农历新年的公历日期,如0131代表1月31日;当作数值转十六进制(6-7位)

'定义如下农历(阴历)日期常量(1899~2100,共202年,但是事实上我们只需要用到1900~2100这201年即可)

Private Const ylData="AB500D2,4BD0883," _

& "4AE00DB,A5700D0,54D0581,D2600D8,D9500CC,655147D,56A00D5,9AD00CA,55D027A,4AE00D2," _

& "A5B0682,A4D00DA,D2500CE,D25157E,B5500D6,56A00CC,ADA027B,95B00D3,49717C9,49B00DC," _

& "A4B00D0,B4B0580,6A500D8,6D400CD,AB5147C,2B600D5,95700CA,52F027B,49700D2,6560682," _

& "D4A00D9,EA500CE,6A9157E,5AD00D6,2B600CC,86E137C,92E00D3,C8D1783,C9500DB,D4A00D0," _

& "D8A167F,B5500D7,56A00CD,A5B147D,25D00D5,92D00CA,D2B027A,A9500D2,B550781,6CA00D9," _

& "B5500CE,535157F,4DA00D6,A5B00CB,457037C,52B00D4,A9A0883,E9500DA,6AA00D0,AEA0680," _

& "AB500D7,4B600CD,AAE047D,A5700D5,52600CA,F260379,D9500D1,5B50782,56A00D9,96D00CE," _

& "4DD057F,4AD00D7,A4D00CB,D4D047B,D2500D3,D550883,B5400DA,B6A00CF,95A1680,95B00D8," _

& "49B00CD,A97047D,A4B00D5,B270ACA,6A500DC,6D400D1,AF40681,AB600D9,93700CE,4AF057F," _

& "49700D7,64B00CC,74A037B,EA500D2,6B50883,5AC00DB,AB600CF,96D0580,92E00D8,C9600CD," _

& "D95047C,D4A00D4,DA500C9,755027A,56A00D1,ABB0781,25D00DA,92D00CF,CAB057E,A9500D6," _

& "B4A00CB,BAA047B,B5500D2,55D0983,4BA00DB,A5B00D0,5171680,52B00D8,A9300CD,795047D," _

& "6AA00D4,AD500C9,5B5027A,4B600D2,96E0681,A4E00D9,D2600CE,EA6057E,D5300D5,5AA00CB," _

& "76A037B,96D00D3,4AB0B83,4AD00DB,A4D00D0,D0B1680,D2500D7,D5200CC,DD4057C,B5A00D4," _

& "56D00C9,55B027A,49B00D2,A570782,A4B00D9,AA500CE,B25157E,6D200D6,ADA00CA,4B6137B," _

& "93700D3,49F08C9,49700DB,64B00D0,68A1680,EA500D7,6AA00CC,A6C147C,AAE00D4,92E00CA," _

& "D2E0379,C9600D1,D550781,D4A00D9,DA400CD,5D5057E,56A00D6,A6C00CB,55D047B,52D00D3," _

& "A9B0883,A9500DB,B4A00CF,B6A067F,AD500D7,55A00CD,ABA047C,A5A00D4,52B00CA,B27037A," _

& "69300D1,7330781,6AA00D9,AD500CE,4B5157E,4B600D6,A5700CB,54E047C,D1600D2,E960882," _

& "D5200DA,DAA00CF,6AA167F,56D00D7,4AE00CD,A9D047D,A2D00D4,D1500C9,F250279,D5200D1"

'定义农历 (阴历)每月的汉字大写日期“天”

Private Const ylMd0="初一初二初三初四初五初六初七初八初九初十十一十二十三十四十五" _

& "十六十七十八十九二十廿一廿二廿三廿四廿五廿六廿七廿八廿九三十 "

'定义农历 (阴历)一年中的汉字大写日期“月”

Private Const ylMn0="正二三四五六七八九十冬腊"

'定义农历 (阴历)年中的“天干”(如:甲乙丙丁......等)

Private Const ylTianGan0="甲乙丙丁戊已庚辛壬癸"

'定义农历 (阴历)年中的“地支”(如:子丑寅卯辰......等)

Private Const ylDiZhi0="子丑寅卯辰巳午未申酉戌亥"

'定义农历 (阴历)年中的“属相”(如:鼠牛虎兔龙......等)

Private Const ylShu0="鼠牛虎兔龙蛇马羊猴鸡狗猪"

Public shp_year_select As Shape, y '定义公有全局变量年份选择组合框shp_year_select和用于存储选择的年份变量y,以便所有的过程都可以调用和回传数据

Sub Run_Fill_Calender() '运行填充日历

[b4].Select

n=shp_year_select.ControlFormat.Value

y=shp_year_select.ControlFormat.List(n)

[O1]=y & " 年历" & "[" & Mid(GetYLDate(y & "-6-1"), 4, 6) & "]"

Fill_Calender_Datas '调用“填充日历数据”过程

[a65535]=y '将选择过的年份存储在单元格"A65535"中

End Sub

Sub Fill_Calender_Datas() '填充日历数据

Dim rg(1 To 12) As Range '定义12个元素的的范围区域对象数组

'为区域对象数组的每个区域对象元素对象指派这12个区域对象具体的实体

Set rg(1)=[b5:h10]: Set rg(2)=[j5:p10]: Set rg(3)=[r5:x10]: Set rg(4)=[z5:af10]

Set rg(5)=[b15:h20]: Set rg(6)=[j15:p20]: Set rg(7)=[r15:x20]: Set rg(8)=[z15:af20]

Set rg(9)=[b25:h30]: Set rg(10)=[j25:p30]: Set rg(11)=[r25:x30]: Set rg(12)=[z25:af30]

For i=1 To 12

Select Case i

Case 1, 3, 5, 7, 8, 10, 12: days_31 y, i, rg(i)

Case 4, 6, 9, 11: days_30 y, i, rg(i)

Case 2: days_29_Or_28 y, i, rg(i)

End Select

Next

End Sub

Sub Erse_Calender_Datas() '清空日历数据

Dim rg As Range

Set rg=[5:10,15:20,25:30]

[b4].Select

rg.ClearContents

[O1]="---- 年历[-----年]"

yr=Year(Date)

'以下是定位当今日期的年份在表单组合框中显示

For i=1 To shp_year_select.ControlFormat.ListCount

If yr=Val(shp_year_select.ControlFormat.List(i)) Then

n=i

Exit For

End If

Next

shp_year_select.ControlFormat.ListIndex=n

End Sub

Sub days_31(y, m, r As Range) '月大--31天

Dim da As Date, d

r.ClearContents

week_str="日一二三四五六"

d=1

da=CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期

ws=Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中

First_Day_Pos_In_Week_Area=InStr(week_str, ws) '每月初始的1号在日历星期区域的定位位置

For d=1 To 31

da=CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期

ws=Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中

Other_Day_Pos_In_Week_Area=InStr(week_str, ws)

'实际的每月的号数应该加上每月初始的1号在日历星期区域的定位位置减去1“”d + (First_Day_Pos_In_Week_Area - 1),为了在第7个位置仍然将该号 _

数放在该行,所以还得再减去1“d + (First_Day_Pos_In_Week_Area - 1) - 1”,然后再除7取整,同时乘以7后加上该号数在日历中星期区域的实际列数 _

位置,即可得到该号数在日历区域的设计位置

p=Int((d + (First_Day_Pos_In_Week_Area - 1) - 1) / 7) * 7 + Other_Day_Pos_In_Week_Area

yl_md=Right(GetYLDate(da), 4) '调用转农历(阴历)函数,取后四个汉字月日日期字符

yl_m=Left(yl_md, 2) '拆解阴历月日中的月份

yl_d=Right(yl_md, 2) '拆解阴历月日中的日子

If yl_d="初一" Then yl_d=yl_m '若拆解的日子是“初一”,则即刻用该月的月份替代该阴历月份的首个日子

r(p)=d & Chr(10) & yl_d '将公历日期和对应的农历日期合在一起填入到p处正确位置

If da=Date Then r(p).Select '若选择年份后不断瞬时生成的日期da和现在的日期匹配,则将当前填充的日期单元格选择成活动状态

Next

End Sub

Sub days_30(y, m, r As Range) '月小--30天

Dim da As Date, d

r.ClearContents

week_str="日一二三四五六"

d=1

da=CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期

ws=Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中

First_Day_Pos_In_Week_Area=InStr(week_str, ws) '每月初始的1号在日历星期区域的定位位置

For d=1 To 30

da=CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期

ws=Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中

Other_Day_Pos_In_Week_Area=InStr(week_str, ws)

'实际的每月的号数应该加上每月初始的1号在日历星期区域的定位位置减去1“”d + (First_Day_Pos_In_Week_Area - 1),为了在第7个位置仍然将该号 _

数放在该行,所以还得再减去1“d + (First_Day_Pos_In_Week_Area - 1) - 1”,然后再除7取整,同时乘以7后加上该号数在日历中星期区域的实际列数 _

位置,即可得到该号数在日历区域的设计位置

p=Int((d + (First_Day_Pos_In_Week_Area - 1) - 1) / 7) * 7 + Other_Day_Pos_In_Week_Area

yl_md=Right(GetYLDate(da), 4) '调用转农历(阴历)函数,取后四个汉字月日日期字符

yl_m=Left(yl_md, 2) '拆解阴历月日中的月份

yl_d=Right(yl_md, 2) '拆解阴历月日中的日子

If yl_d="初一" Then yl_d=yl_m '若拆解的日子是“初一”,则即刻用该月的月份替代该阴历月份的首个日子

r(p)=d & Chr(10) & yl_d '将公历日期和对应的农历日期合在一起填入到p处正确位置

If da=Date Then r(p).Select '若选择年份后不断瞬时生成的日期da和现在的日期匹配,则将当前填充的日期单元格选择成活动状态

Next

End Sub

Sub days_29_Or_28(y, m, r As Range) '闰年2月份29天,平年2月份28天(例如2020年就是闰年)

Dim da As Date, d

r.ClearContents

week_str="日一二三四五六"

d=1

da=CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期

ws=Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中

First_Day_Pos_In_Week_Area=InStr(week_str, ws) '每月初始的1号在日历星期区域的定位位置

If Is_LeepYear(y) Then '闰年2月份天数

For d=1 To 29

da=CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期

ws=Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中

Other_Day_Pos_In_Week_Area=InStr(week_str, ws)

'实际的每月的号数应该加上每月初始的1号在日历星期区域的定位位置减去1“”d + (First_Day_Pos_In_Week_Area - 1),为了在第7个位置仍然将该 _

号数放在该行,所以还得再减去1“d + (First_Day_Pos_In_Week_Area - 1) - 1”,然后再除7取整,同时乘以7后加上该号数在日历中星期区域的实 _

际列数位置,即可得到该号数在日历区域的设计位置

p=Int((d + (First_Day_Pos_In_Week_Area - 1) - 1) / 7) * 7 + Other_Day_Pos_In_Week_Area

yl_md=Right(GetYLDate(da), 4) '调用转农历(阴历)函数,取后四个汉字月日日期字符

yl_m=Left(yl_md, 2) '拆解阴历月日中的月份

yl_d=Right(yl_md, 2) '拆解阴历月日中的日子

If yl_d="初一" Then yl_d=yl_m '若拆解的日子是“初一”,则即刻用该月的月份替代该阴历月份的首个日子

r(p)=d & Chr(10) & yl_d '将公历日期和对应的农历日期合在一起填入到p处正确位置

If da=Date Then r(p).Select '若选择年份后不断瞬时生成的日期da和现在的日期匹配,则将当前填充的日期单元格选择成活动状态

Next

Else '平年2月份天数

For d=1 To 28

da=CDate(y & "-" & m & "-" & d) '将字符串动态转换为真正的日期

ws=Mid(Format(da, "[$-804]aaaa"), 3) '从转换为星期XX的字符串中提取大写星期几的汉字保存在ws中

Other_Day_Pos_In_Week_Area=InStr(week_str, ws)

'实际的每月的号数应该加上每月初始的1号在日历星期区域的定位位置减去1“”d + (First_Day_Pos_In_Week_Area - 1),为了在第7个位置仍然将该 _

号数放在该行,所以还得再减去1“d + (First_Day_Pos_In_Week_Area - 1) - 1”,然后再除7取整,同时乘以7后加上该号数在日历中星期区域的实 _

际列数位置,即可得到该号数在日历区域的设计位置

p=Int((d + (First_Day_Pos_In_Week_Area - 1) - 1) / 7) * 7 + Other_Day_Pos_In_Week_Area

yl_md=Right(GetYLDate(da), 4) '调用转农历(阴历)函数,取后四个汉字月日日期字符

yl_m=Left(yl_md, 2) '拆解阴历月日中的月份

yl_d=Right(yl_md, 2) '拆解阴历月日中的日子

If yl_d="初一" Then yl_d=yl_m '若拆解的日子是“初一”,则即刻用该月的月份替代该阴历月份的首个日子

r(p)=d & Chr(10) & yl_d '将公历日期和对应的农历日期合在一起填入到p处正确位置

If da=Date Then r(p).Select '若选择年份后不断瞬时生成的日期da和现在的日期匹配,则将当前填充的日期单元格选择成活动状态

Next

End If

End Sub

Function Is_LeepYear(y) As Boolean '给定的年份是否为闰年LeepYear的判断

If (y Mod 400=0) Or (y Mod 100 <> 0 And y Mod 4=0) Then

Is_LeepYear=True

Else

Is_LeepYear=False

End If

End Function

'自定义“公历转农历”日期函数

Function GetYLDate(ByVal strDate As String) As String

On Error GoTo ExitFunction_Label

If Not IsDate(strDate) Then Exit Function '如果参数strDate非日期的无效字符串,则退出本函数工作

'定义setDate--设置的未来日期,tYear--未来日期的本年份,tMonth--本月份,tDay--本日子

Dim setDate As Date, tYear As Integer, tMonth As Integer, tDay As Integer

setDate=CDate(strDate) '为该GetYLDate()函数参数的字符串转换后的日期赋予设定的日期

tYear=Year(setDate): tMonth=Month(setDate): tDay=Day(setDate) '年、月、日分别取值

'如果不是有效有日期,退出

If tYear > 2100 Or tYear < 1900 Then Exit Function

'定义daList()--是元素为18位日期二进制字符串数组,conDate--农历新年日期,thisMonths--本年的二进制 _

月份信息(可能包含闰月)

Dim daList() As String * 18, conDate As Date, thisMonths As String

'定义AddYear--是相对1900年递增的年,AddMonth--月份增量,AddDay--天数增量,getDay--农历新年和设 _

之日期相差天数

Dim AddYear As Integer, AddMonth As Integer, AddDay As Integer, getDay As Integer

'定义YLyear--农历(阴历)年的字符串,YLShuXing--农历(阴历)年的属相

Dim YLyear As String, YLShuXing As String

'定义dd0--农历(阴历)年的阴历日子,mm0--农历(阴历)年的阴历月,ganzhi()--每个元素为2个字符的天干地 _

支数组

Dim dd0 As String, mm0 As String, ganzhi(0 To 59) As String * 2

'定义RunYue--农历(阴历)年是否闰月的布尔型标志,RunYue1--农历(阴历)年闰月月份

Dim RunYue As Boolean, RunYue1 As Integer, mDays As Integer, i As Integer

'加载2年内的农历数据

ReDim daList(tYear - 1 To tYear)

daList(tYear - 1)=Hexadecimal_To_Binary(Mid(ylData, (tYear - 1900) * 8 + 1, 7))

daList(tYear)=Hexadecimal_To_Binary(Mid(ylData, (tYear - 1900 + 1) * 8 + 1, 7))

AddYear=tYear

initYL:

AddMonth=CInt(Mid(daList(AddYear), 15, 2))

AddDay=CInt(Mid(daList(AddYear), 17, 2))

conDate=DateSerial(AddYear, AddMonth, AddDay) '农历新年日期

getDay=DateDiff("d", conDate, setDate) + 1 '相差天数

If getDay < 1 Then AddYear=AddYear - 1: GoTo initYL

thisMonths=Left(daList(AddYear), 14) '前14位为本年的二进制月份信息(可能有闰月)存于thisMonths中

RunYue1=Val("&H" & Right(thisMonths, 1)) '闰月月份

If RunYue1 > 0 Then '如果有闰月,则立即修正本年的二进制月份信息thisMonths,形成真正有效的二进制序 _

列信息

thisMonths=Left(thisMonths, RunYue1) & Mid(thisMonths, 13, 1) & Mid(thisMonths, RunYue1 + 1)

End If

thisMonths=Left(thisMonths, 13) '最后一次修正本年的二进制月份信息thisMonths,直接取13个月的情况

For i=1 To 13 '遍历1~13个月,找到并计算含闰月的有效天数,同时退出循环

mDays=29 + CInt(Mid(thisMonths, i, 1))

If getDay > mDays Then

getDay=getDay - mDays

Else

If RunYue1 > 0 Then '如果有闰月,则进一步根据i的值情况做如下处理

If i=RunYue1 + 1 Then RunYue=True '若i确系为闰月,则将闰月标志置为真

If i > RunYue1 Then i=i - 1 '若i大于闰月月份,则将将i回退修正

End If

AddMonth=i '最终记录下i作为真正的增量月份存入AddMonth

AddDay=getDay '同时,将得到的天数差作为增量天数

Exit For

End If

Next

dd0=Mid(ylMd0, (AddDay - 1) * 2 + 1, 2) '用查找表的形式定位当前日期对应的农历(阴历)日子

mm0=Mid(ylMn0, AddMonth, 1) + "月" '用查找表的形式定位当前日期对应的农历(阴历)月份

For i=0 To 59 '0~59表示60年一个甲子,表示以60年一个轮回的形式,通过查找表精准定位每年的天干地支

ganzhi(i)=Mid(ylTianGan0, (i Mod 10) + 1, 1) + Mid(ylDiZhi0, (i Mod 12) + 1, 1)

Next

YLyear=ganzhi((AddYear - 4) Mod 60) '通过查找表形式得出阴历年的天干地支表示形式

YLShuXing=Mid(ylShu0, ((AddYear - 4) Mod 12) + 1, 1) '通过查找表形式得出阴历年的属相表示形式

If RunYue Then mm0="闰" & mm0 '如果某阴历月份有闰月,特别加上“闰X月”的形式

GetYLDate="农历:" & YLyear & "(" & YLShuXing & ")年" & mm0 & dd0 '拼接当前日期的完整农历信息

ExitFunction_Label:

End Function

'将压缩的阴历字符还原

Private Function Hexadecimal_To_Binary(ByVal strHex As String) As String '十六进制转二进制

Dim i As Integer, i1 As Integer, tmpV As String

Const hStr="0123456789ABCDEF"

Const bStr="0000000100100011010001010110011110001001101010111100110111101111"

tmpV=UCase(Left(strHex, 3))

'以下是十六进制转二进制的具体操作

For i=1 To Len(tmpV)

i1=InStr(hStr, Mid(tmpV, i, 1))

Hexadecimal_To_Binary=Hexadecimal_To_Binary & Mid(bStr, (i1 - 1) * 4 + 1, 4)

Next

Hexadecimal_To_Binary=Hexadecimal_To_Binary & Mid(strHex, 4, 2)

'十六进制转十进制

Hexadecimal_To_Binary=Hexadecimal_To_Binary & "0" & CStr(Val("&H" & Right(strHex, 2)))

End Function

ThisWorkbook中代码如下:

Private Sub Workbook_Open() '工作簿一打开即刻初始化表单组合框数据并且在组合框中显示之前选择过的年份

Set shp_year_select=Sheets(1).Shapes("年份选择")

shp_year_select.ControlFormat.RemoveAllItems

'万年历的年份范围初步设定为“1900~2100”

For i=1900 To 2100

shp_year_select.ControlFormat.AddItem i

Next

'以下是重新还原表单组合框控件之前选定过的年份显示

yr=[a65535]

For i=1 To shp_year_select.ControlFormat.ListCount

If yr=Val(shp_year_select.ControlFormat.List(i)) Then

n=i '遍历整个表单组合框所有元素,查找与yr是否相匹配的元素,若找到即刻记下该编号并存于n中

Exit For

End If

Next

shp_year_select.ControlFormat.ListIndex=n '让表单组合框显示找到的之前选择过的年份

End Sub

三、用方法一实现带农历万年历运行效果测试

(一)选择年份,呈待生成带农历万年历状态。如下图所示

图2 选择年份准备生成带农历万年历

(二)点击选择的年份,生成实实在在的带农历的万年历。如下图所示

图3 生成带农历万年历效果

(三)压下<清除日历数据>按钮,准备进行带农历的万年历数据清除。如下图所示

图4 准备清除带农历万年历数据

(四)压下状态下的<清除日历数据>按钮情况下点击该按钮,完成带农历万年历数据的清除,并将年份组合框内的显示提示年份置为最新当前时间的年份。如下图所示

图5 清除带农历万年历数据结果

四、技术亮点小结

(一)充分利用寻找农历闰月方法和压缩的农历字符还原方法完成公历转农历

(二)在定位Excel的万年历数据填充单元格时,用字符串处理函数处理农历生成的数据

(三)存储记忆上次打开万年历的数据

好了,本期我们就分享到这里吧,希望大家喜欢和收藏哦!

最后,还是感谢大家的持续关注(头条号:跟我学Office高级办公)、推广、点评哦!谢谢大家继续关注下期第二中方法实现带农历的万年历设计!

金流量表(Cash Flow Statement),是指反映企业在一定会计期间现金和现金等价物流入和流出的报表。现金流量表是企业财务报表的三个基本报告之一(另外两个是资产负债表和损益表)。

为了全面系统地揭示企业一定时期的财务状况、经营成果和现金流量,财务报表需按财政部会计准则的标准格式设计,因此,财务报表的典型特征是数据更新频繁、分析维度多、数据来源复杂,常规的报表工具很难同时满足上述所有需求。

借助葡萄城 纯前端表格控件 SpreadJS 和 服务端表格组件 GcExcel 来设计财务报表模板,可以在满足财务数据展示、计算、决策分析的同时,提供如 Excel 一般的使用体验,并可直接复用财务系统原始的 Excel 报表模板,减少从本地到线上的数据迁移工作量。

葡萄城表格技术的优势

葡萄城表格技术产品 SpreadJS 和 GcExcel 兼容 Excel 数据格式,在设计财务报表模板时,可以为用户提供高度类似 Excel 的使用体验;在分析财务数据时,可以提供超过 450 种计算公式和 32 种图表类型,既可满足用户自定义、跨表格引用、异步调用等多场景计算需求,又可实现丰富的数据可视化效果,建立如 Excel 般强大的数据分析能力。

模块化组件,支持跨平台应用嵌入

葡萄城表格组件突破了 Excel 对数据处理性能的限制,通过接口调用,很容易就可以实现实时数据更新。

高度类似 Excel 的使用体验

SpreadJS 组件包含的在线表格编辑器提供了类 Excel 的 UI 设计元素,可直接在 Angular、 React、 Vue 等前端框架中调用,实现高效的模板设计、在线编辑和数据绑定,为最终用户带来更为流畅的使用体验。

不依赖第三方组件和后台代码

无需借助后台代码和第三方组件,SpreadJS 和 GcExcel 均支持导入导出 Excel 格式的报表,单元格级别的操作颗粒度使报表自定义变得更加轻松。

高度兼容 Excel 模板

财务报表设计过程中,可继续沿用 Excel 的设计模式,可同步识别 Excel 的样式(字体、字号、颜色等)、数据结构、数据透视表等内容,模板设计的成本更低、财务系统更易用。

扩展能力更强

通过二次开发,组件可支持报表多 Sheet 页展示,可将一个期间内的多张不同报表展现在同一个报表文件中,同步数据、同步查看和同步存档。

本博客将学习如何使用类似 Excel 的 JavaScript 电子表格解决方案 SpreadJS 在前端创建现金流日历。此日历将广泛使用以下强大功能:

1. 动态数组公式 - 根据一个公式将多个结果返回到一系列单元格。此示例使用 SEQUENCE 和 FILTER 函数。

2. RANGEBLOCKSPARKLINE(template_range, data_expr) - 此迷你图允许开发人员将单元格范围模板 (template_range) 定义为单个单元格类型,并将该模板应用于单元格以将一组数据 (data_expr) 加载到模板中。该模板可以包括多行和/或多列。

最终效果如图所示:

要创建我们的现金流日历,我们需要创建如下所述的三张表:

1. 数据源表

2. 模板表

3. 现金流日历:渲染表

数据源表

我们示例的数据源是交易列表。

我们创建了一个更动态的表格,当我们需要数据而不是单元格范围时,我们可以引用 Table1。

此表包含有关 TransactionID、交易类型、交易日期、公司名称、帐户名称、存款金额和取款的信息。

模板表

此页面包含我们将用来呈现现金流日历中发生的交易的模板范围。

此处的此单元格范围将用作包含现金流日历中所需信息的单元格的模板。

我们要做的第一件事是排列单元格,然后设置单元格的绑定路径。

它可以通过 Javascript 使用 SpreadJS setBindingPath 方法来完成。

templateSheet.setBindingPath(0, 1, "month");
templateSheet.setBindingPath(1, 2, "date");
templateSheet.setBindingPath(2, 2, "start");
templateSheet.setBindingPath(3, 2, "withdrawals");
templateSheet.setBindingPath(4, 2, "deposits");
templateSheet.setBindingPath(5, 2, "end");

当然,上边这步操作也有不用写代码的方法——用SpreadJS设计器,下载SpreadJS安装包,在下载的安装包中,从“\SpreadJS.Release.x.x.x\Designer\Designer Runtime”路径下找到设计器的安装包,完成安装后,按照下列步骤操作:

1. 单击数据选项卡上的模板菜单 - 字段列表面板将出现在右侧

2. 将鼠标悬停在 Start 分支上并通过单击绿色 + 按钮添加字段 *请注意,你可以使用“x”按钮删除字段并使用位于分支右侧的设置修改这些字段

3. 拖动模板范围所需单元格中的字段

为了使现金短缺(期末余额为负)的日子可以用红色着色,期末余额为正的日子用绿色着色,中性的用黑色着色,我们可以使用条件格式。在设计器上可以这样操作:

1. 在合并时选择日期单元格“A2:D2”

2. 条件格式 → 新规则

3. 通常,键入并选择使用公式来确定要格式化的单元格

4. 输入你的公式,在我们的例子中='Cell Template'!$C>0

5. 单击格式→填充→选择绿色作为字体颜色

6. 重复相同的步骤,但使用公式:='Cell Template'!$C<0 *请注意,对于余额为负的情况,颜色应设置为红色

现金流日历:渲染表

第 1 步:添加 MonthPicker 元素

我们日历的第一个元素是可变月份元素。要添加它,请使用 MonthPicker,这是 SpreadJS 中的一种下拉单元格样式。

JavaScript:
var monthPickerStyle=new GC.Spread.Sheets.Style();
monthPickerStyle.dropDowns=[
{
type: GC.Spread.Sheets.DropDownType.monthPicker,
option: {
startYear: 2019,
stopYear: 2021,
height: 300,
}
}
];
sheet.setStyle(2, 5, monthPickerStyle);

SpreadJS设计器:

选择单元格(在我们的例子中为 B2)

1. 主页选项卡 → 单元格下拉菜单 → 月份选择器

2. 在命令右侧,单击...

3. 设置选取器的开始、结束年份和高度

然后,我们在进行计算时为包含月份的单元格指定一个名称。

1. 在公式选项卡上,选择名称管理器

2. 在弹出窗口中,单击新建按钮

3. 设置单元格的名称。在我们的示例中:name: currentMonth

参考:$D。你还可以添加评论并更改引用对象

第 2 步:创建现金流日历

使用 SEQUENCE(rows,columns,start,step) 函数来分配我们日历中的日期。这允许我们稍后在 CellClick 上检索单元格值。 B4 单元格的公式为:

=SEQUENCE(6,7,currentMonth-WEEKDAY(currentMonth)+1,1)

JavaScript:

cashflowSheet.setFormula(3, 1, '=SEQUENCE(6,7,currentMonth-WEEKDAY(currentMonth)+1,1)');

我们还没有为这些单元格使用格式化程序。

下一步是使用条件格式来使属于其他月份的日期成为可能,但所选日期为空白:

1. 选择 B4:H9 然后选择日历的日期 → 条件格式

2. 从下拉列表中选择新规则,然后选择“使用公式确定要格式化为规则类型的单元格”

3. 输入你的公式,在我们的例子中为“=MONTH(B4)<>MONTH(currentMonth)” - 此格式仅适用于月份与下拉列表中选择的月份不同的单元格

4. 单击格式

5. 编号 → 自定义

6. 输入”;;;”作为格式化程序将所有正确的单元格设为空白

下面的步骤包括使用 RANGEBLOCKSPARKLINE,它将 TemplateSheet 中的单元格范围用作单个单元格类型,并使用 OBJECT 函数将模板应用于代表我们现金流日历中日期的所有单元格中。

由于我们使用 SEQUENCE 为这些单元格设置值,因此我们将使用 RANGEBLOCKSPARKLINE 作为格式。

1. 选择单元格区域 B4:H9

2. 格式→更多数字格式→自定义

3. 将格式化程序设置为:

=RANGEBLOCKSPARKLINE('Cell Template'!$A:$D,OBJECT("date",@,"start",IFERROR(SUM(FILTER(Table1[Deposit],Table1[Date]<@))-SUM(FILTER(Table1[Withdrawal],Table1[Date]<@)),0),"withdrawals",IFERROR(SUM(FILTER(Table1[Withdrawal],Table1[Date]=@)),0),"deposits",IFERROR(SUM(FILTER(Table1[Deposit],Table1[Date]=@)),0),"month",MONTH($A)))

作为第一个参数,它将单元格范围作为 TemplateSheet 中的模板。

作为第二个参数,它需要一个 OBJECT,该 OBJECT 从位于数据源表的 Table1 中获取数据。

1. [日期]:单元格的当前值

2. [开始]:之前所有存款的总和 - 之前所有提款的总和

3. [提款]:当前提款的总和

4. [存款]:当前存款的总和

5. [end]:[start] + 所有当前存款的总和 - 所有当前提款的总和

使用公式是绑定并返回一个范围模板,以便更轻松地使用范围模板。

这是最终输出:

如上图所示,包含日历天数的单元格提供有关开始/结束余额、存款总额和提款总额的信息。

第 3 步:获取每日交易

如果我们想从 DataSource 页面中提取所有交易的列表,我们可以借助 SelectionChanged 事件。当这些事件发生时,SpreadJS 中的工作表将其事件绑定到特定操作。

在我们的示例中,当用户从日历中选择日期时,我们使用了这个方便的 SpreadJS 功能来提取所有交易的列表。

我们为包含所选日期、存款和取款的单元格指定一个名称,因为它更容易进行计算,并且表格将包含有关交易的信息。为 currentMonth 创建名称范围的步骤是:

1. 在公式选项卡上,选择名称管理器

2. 在弹出窗口中,单击新建按钮

3. 设置单元格的名称

在我们的示例中:

name:当前选择;refer to:='Cash-Flow'!$B
name:当前存款;refer to:=FILTER(tblTransactions[Type]:tblTransactions[Withdrawal],(tblTransactions[Date]=CurrentSelection)*(tblTransactions[Deposit]>0))
name:当前取款;refer to:=FILTER(tblTransactions[Type]:tblTransactions[Withdrawal],(tblTransactions[Date]=CurrentSelection)*(tblTransactions[Withdrawal]>0))

设置不同的公式来获取所有存款列表、所有提款列表、结束和开始余额。

1. 起始余额(之前所有存款的总和 - 之前所有取款的总和):=IFERROR((SUM(FILTER(tblTransactions[Deposit],tblTransactions[Date]<$B))-SUM(FILTER(tblTransactions[Withdrawal],tblTransactions [日期]<$B))),0)

2. 结束余额(起始余额 + 当前存款的总和 - 当前提款的总和):=IFERROR(D13+(SUM(FILTER(tblTransactions[Deposit],tblTransactions[Date]=$B))-SUM(FILTER(tblTransactions[Withdrawal] ,tblTransactions[日期]=$B))),0)

其中 D13 是起始余额:

1. 存款:=IFERROR(FILTER(currentDeposits,{1,0,1,1,0}),"")

2. 取款:=IFERROR(FILTER(currentWithdrawals,{1,0,1,0,1}),"")

目前手动插入 currentSelection。要根据用户日期选择进行更改,请执行下一步。

在 JavaScript 中创建事件处理函数(见下文):

// on day selection, update a cell used in filtering the data to show detailed transaction list
cashflowSheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (sender, args) {
const sheet=args.sheet;
const row=args.newSelections[0].row;
const col=args.newSelections[0].col;

if ((row < 3 || row >=3 + 6)
|| (col < 1 || col >=1 + 7))
return;
// set the current date cell so that FILTER would update.
sheet.setValue(10, 1, sheet.getValue(row, col));
});

一旦用户单击单元格,上面的代码就会检查单元格是否在日历边界内 (B4:H9)。否则,它会更新 currentSelection,因此,所有用于获取余额和有关交易信息的公式都会在它们指向更改的选定日期时给出正确的结果。

上面的示例是使用 SpreadJS 功能增强你的应用程序并将你的内容从一组简单的数据转换为一个引人入胜、超级有用的类似 Excel 的仪表板的众多方法之一。

这个 JavaScript 组件提供了数百个统计和财务函数和公式,可帮助你在财务应用程序中轻松创建各种元素。


了解更多纯前端表格在线demo示例,请搜索并访问“葡萄城官网”!

士 发自 凹非寺

量子位 | 公众号 QbitAI

除了吃灰,Kindle还能干什么?

不是泡面盖,而是你的智能管家,每天出门前,提醒你天气情况、即将到达的包裹。

这玩意有人已经造出来了。

一位做前端与设计的美国小哥,将一块10多年前的老Kindle改造成了一块能显示日历、天气、网购包裹、家务提醒的智能显示板。

整个过程中,无需再买任何硬件,操作流程也简单。

在reddit上,该项目已获得500+个赞,有网友表示自己已改造了好几块了。

具体如何操作的?

往下看。

成本比树莓派还便宜

之所以上手改造,始于小哥希望在手机之外能有个显示工具挂墙上,随时提醒自己时间日程、天气、快递哪天到……等等信息。

考虑到Kindle价格比树莓派还便宜,基于Linux操作系统,易破解调试,且墨水屏比发光屏幕更自然地融入家中环境,他果断从网上花30美元购买了一台二手Kindle4,约合211.5人民币。

但仍有三个问题要解决:

Kindle会自动进入睡眠状态并显示屏保程序;

如果想更新屏幕数据,每次浏览器页面都会重新加载,观感很差;

Kindle浏览器还有一些难看又冗余的元素,比如标题、URL栏,且并不好看;

基于上述原因,小哥设计了一套解决思路:

部署一个能获取网页屏幕截图的服务器,让Kindle作为终端显示屏,每分钟下载一次该截图。

鉴于市面上已有部分平台提供定时截屏服务,但收费不低,小哥决定自己编写一个小型服务器,使用 Puppeteer截图,再依靠ImageMagick转换格式。

带着这一规划,他开动了。

先是设置图像服务器。

小哥自己编写了代码并在Heroku创建一个免费账户,在平台填好项目名,就能一键部署。(现已开源,人人可用,链接见文末

退出前,别忘了记下URL。

接着,回到Kindle本体。

连上WiFi,对其进行越狱,网上相关教程有很多,跟着步骤走就行,注意下载文件及流程与版本号相符就行。(相关资料已附在参考链接

然后,安装USBNetwork插件。复制.bin文件进去就行,接着通过Settings设置->更新Kindle,完成安装。

断开Kindle与电脑的连接,通过设备键盘,输入;debugOn

回车,启动调试模式。

继续打开键盘,输入~usbNetwork,回车,等待几秒,再输入;debugOff,回车。

重新连接电脑。在桌面打开命令行,输入ssh root@192.168.15.244并回车。然后输入默认密码——mario。

现在,你已经可对Kindle进行编程了。

接着,调整驱动从「只读」变成「可写」状态,输入mntroot rw并回车。

再输入下方代码,创建图像下载脚本,让你能打开并编辑。

nano /mnt/us/script.sh

在编辑器中输入下面代码,注意「Insert_your_URL_from_step_2_here」部分,替换为前面第二步复制的URL:

curl Insert_your_URL_from_step_2_here -o status.png
eips -c
eips -c
eips -g status.png

上述代码中,第一行是保存图像到status.png文件,两个eips-c命令是为了清除屏幕,最后一行为显示图像。

完成后,输入control-O,再输入control-X,保存并退出编辑器。

然后运行脚本测试一下,输入/script.sh,就能看见屏幕上能看见图像显示。

最后一步, 设置定时程序,让其每分钟刷新。

由于Kindle系统与其他Linux设备一样,其中有个cron工具,可按计划定时运行。

只需打开配置文件:

nano /etc/crontab/root

将下面命令添加到底部:

*****/mnt/us/script.sh

同样,输入?O,回车,再输入?X,最后重启:

/etc/init.d/cron restart

BINGO!搞定!

很多人都在魔改墨水屏

上述魔改的小哥名叫Matt Healy,是一位设计师兼前端工程师。

他目前也是一家SaaS平台的联合创始人,主要为其他企业提供用户/客户喜好研究与需求洞察服务,闲暇时间,他还做了个自己的主页。

不止他一人,其实改造墨水屏设备的玩家还有不少。

比如一位芬兰程序员Kimmo做了个墨水屏天气显示器,每天出门前提醒自己穿什么衣服合适。

这两天,他的改造帖子冲上了HackerNews前3。

更早时候,还有一位荷兰小哥,女票是英国文学老师,他用Kindle做了个文学时钟送给了对方。

当程序启动后,Kindle将会以文学名著段落显示当前时间,此外,还能猜测该句来自哪本书。按下翻书按钮,屏幕就会显示答案,深得女票喜爱。

最后问问, 你觉得Kindle还能用来干什么?

参考链接:
[1]https://matthealy.com/Kindle
[2]https://wiki.mobileread.com/wiki/Kindle_Hacks_Information#Jail_break_JB
[3]https://blog.adafruit.com/2021/01/29/your-next-smart-home-device-is-a-30-used-Kindle-iot-internetofthings-eink-epaper-lankybutmacho/
[4]https://www.reddit.com/r/Kindle/comments/l75hjz/your_next_smart_home_device_is_a_30_used_Kindle/
[5]https://github.com/lankybutmacho/web-to-Kindle-heroku
[6]https://kimmo.blog/posts/7-building-eink-weather-display-for-our-home/
[7]https://www.instructables.com/Literary-Clock-Made-From-E-reader/

— 完 —

量子位 QbitAI · 头条号签约

关注我们,第一时间获知前沿科技动态