1 Star 2 Fork 0

范德萨/HTMLCourse

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
index.html 104.80 KB
一键复制 编辑 原始数据 按行查看 历史
范德萨 提交于 2023-05-28 12:04 +08:00 . update
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>一课表</title>
<link href="./images/icon.ico" rel="icon" />
</head>
<script src="https://cdn.bootcss.com/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<link rel="stylesheet" href="./swiper.min.css">
<script src="./swiper.min.js"></script>
<script src="https://cdn.staticfile.org/pako/1.0.10/pako.min.js"></script>
<script src="./HwAuth.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<script src="./Toast.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jszip/3.6.0/jszip.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<body style="background-color: #F0F0F0;">
<div id="app" style="width: 100%;height: 100%;position: relative;">
<img v-if="backgroundImg != null" v-bind:src="backgroundImg"
style="position: absolute;width: 100%;height: 100%;object-fit: cover;z-index: -1;pointer-events: none;">
<div style="display: flex;width: 100%;height: 100%;flex-direction: column;padding: 8px;">
<p style="font-size: 12px;color: black;opacity: 0.6;margin-bottom: 8px;width: 100%;text-align: center;
flex-shrink: 0;">温馨提示:请勿清理浏览器数据,否则数据将丢失</p>
<div style="width: 100%;height: 100%;">
<div style="width: 50%;flex-direction: column;" id="observeTarget"
v-bind:style="{width: (closeTab ? 'calc(100% - 24px)' : '50%')}">
<!-- 周次栏 -->
<div style="width: 100%;align-items: center;flex-direction: column;">
<p style="font-size: 14px;font-weight: bold;" class="clickEffect"
v-on:click="openWeekChooseDialog">
{{showingWeek}}
</p>
<p style="font-size: 12px;height: 8px;color: #0066ff;transform: scale(0.7);text-shadow: white 0 0 5px"
v-if="realWeek !== -1 && realWeek != showingWeek" v-on:click="toNowWeek"
class="clickEffect">
本周为第
{{realWeek}} 周</p>
<div style="height: 8px;" v-else></div>
</div>
<div style="width: 100%;height: 100%;margin-top: 8px;position: relative;">
<!-- 课表 -->
<div style="width: 100%; height: 100%;border-radius: 8px;overflow: hidden;background-color: #FFFFFF;"
v-bind:style="{'background-color': backgroundImg != null ? '#FFFFFF50' : '#FFFFFF' }"
class="swiper-container" id="courseView">
<div class="swiper-wrapper" style="width: 100%;">
<div v-for="page in pages" class="swiper-slide"
style="height: 100%;width: 100%;flex-direction: column;">
<!-- 星期栏 -->
<div style="height: 44px;flex-shrink: 0;width: 100%;background-color: #80808010;align-items: center;"
v-bind:style="{'padding-left': timeBarWidth}">
<div v-for="(ws, i) in page.weekdayShow" class="weekItem"
style="width: 100%;flex-direction: column;align-items: center;position: relative;"
v-bind:data-day="i">
<p
style="font-size: 12px;transform: scale(0.8);height: 12px;flex-shrink: 0;">
{{ws.text}}</p>
<p
style="font-size: 12px;transform: scale(0.6);height: 12px;flex-shrink: 0;">
{{ws.day}}
</p>
<div style="background-color: #0066ff;border-radius: 8px;width: 60%;height: 3px;flex-shrink: 0;margin-top: 2px;"
v-if="ws.day === today">
</div>
<div v-else style="height: 3px;margin-top: 2px;flex-shrink: 0;"></div>
<div style="position: absolute;width: 100%;height: 100%;background-color: #FFFFFF80;flex-direction: column;
align-items: center;overflow: hidden;" class="menu">
<div
style="flex-direction: row;align-items: center;justify-content: center;width: 100%;flex-grow: 1;overflow: hidden;">
<img src="./images/copy.svg" class="lighten"
onclick="copyOrMoveDay('copy', event)"
style="border-radius: 50%;max-width: 32px;width: 50%;aspect-ratio: 1;padding: 8px;margin: 2px;box-sizing: border-box;" />
<img src="./images/move.svg" class="lighten"
onclick="copyOrMoveDay('move', event)"
style="border-radius: 50%;max-width: 32px;width: 50%;aspect-ratio: 1;padding: 8px;margin: 2px;box-sizing: border-box;" />
</div>
</div>
</div>
</div>
<div style="flex-grow: 1;width: 100%;position: relative;">
<!-- 时间线 -->
<div style="display: grid;width: 100%;height: 100%;position: absolute;
grid-template-columns: 1fr"
v-bind:style="{'grid-template-rows': 'repeat(' + rowNum + ', 1fr)'}">
<div style="background-color: #0066ff10;width: 100%;height: 100%;"
v-bind:style="{'grid-area': `${nBlock.sb} / 1 / ${nBlock.eb} / 1`}">
</div>
</div>
<!-- 星期线 -->
<div v-if="realWeek === page.week" style="display: grid;width: 100%;height: 100%;position: absolute;
grid-template-rows: 1fr"
v-bind:style="{'grid-template-columns': `${timeBarWidth} repeat(${columnNum}, 1fr)`}">
<div style="background-color: #0066ff10;width: 100%;height: 100%;"
v-bind:style="{'grid-area': `1 / ${weekdayLine+1} / 1 / ${weekdayLine+1}`}"
v-if="weekdayLine <= columnNum">
</div>
</div>
<!-- 时间 -->
<div style="display: grid;width: 100%;height: 100%;position: absolute;"
v-bind:style="{'grid-template-rows': 'repeat(' + rowNum + ', 1fr)',
'grid-template-columns': `1fr`}">
<div v-for="ts in timesShow" style="align-items: center;"
v-bind:style="{'grid-area': `${ts.s} / 1 / ${ts.e} / 1`, 'background-color': ts.color}">
<p
style="white-space: pre-line;width: 50px;text-align: center;font-size: 12px;">
{{ts.text}}</p>
</div>
</div>
<!-- 课表内容 -->
<div id="courseBg"
style="display: grid;width: 100%;height: 100%;position: absolute;"
v-bind:style="{'grid-template-rows': 'repeat(' + rowNum + ', 1fr)',
'grid-template-columns': `${timeBarWidth} repeat(${columnNum}, 1fr)`}"
onmousedown="touchSurfaceStart()" onmousemove="touchSurfaceMove()"
onmouseup="touchSurfaceEnd()" ondrop="dropCourse(event)"
ondragover="event.dataTransfer.dropEffect='move';event.preventDefault()">
<div v-for="c in page.courses" v-bind:data-day="c.realDay"
v-bind:data-p="c.p.toString()" onclick="itemClick(this)"
style="width: 100%;height: 100%;padding: 2px;overflow: hidden;"
v-show="showing" onmousedown="event.stopPropagation()"
onmouseup="event.stopPropagation()" draggable="false"
ondragstart="dragCourseStart(this)"
v-bind:style="{'grid-area': `${c.sb+1} / ${c.day+1} / ${c.eb+1} / ${c.day+1}`}">
<div style="width: 100%;height: 100%;border-radius: 8px;position: relative;overflow: hidden;"
class="clickEffect courseItem"
v-bind:style="{'background-color': c.color}">
<div
style="flex-direction: column;overflow: hidden;padding: 4px;width: 100%;height: 100%;">
<p style="font-size: 12px;width: 100%;text-align: center;text-overflow: ellipsis;-webkit-line-clamp: 1;overflow: hidden;flex-shrink: 0;opacity: 0.8;"
v-bind:style="{'color': c.textDark}">{{c.teachers}}</p>
<div
style="font-size: 16px;flex-grow: 1;text-align: center;align-items: center;justify-content: center;text-overflow: ellipsis;font-weight: bold;overflow: hidden;">
<p v-bind:style="{'color': c.textDark}">{{c.name}}</p>
</div>
<p style="font-size: 12px;width: 100%;text-align: center;text-overflow: ellipsis;-webkit-line-clamp: 3;overflow: hidden;flex-shrink: 0;opacity: 0.8;"
v-bind:style="{'color': c.textDark}">{{c.places}}</p>
</div>
<div style="position: absolute;width: 100%;height: 100%;background-color: #FFFFFF80;flex-direction: column;align-items: center;overflow: hidden;"
class="menu">
<img src="./images/drag.svg" onmouseenter="handlerEnter(this)"
onmouseleave="handlerLeave(this)" draggable="false"
style="width: 20px;aspect-ratio: 1;cursor: grab;flex-shrink: 0;margin-top: 8px;" />
<div
style="flex-direction: row;align-items: center;justify-content: center;width: 100%;flex-grow: 1;overflow: hidden;flex-wrap: wrap;">
<img src="./images/copy.svg" class="lighten"
onclick="copyOrMoveCourse('copy', event)"
style="border-radius: 50%;max-height: 16px;aspect-ratio: 1;padding: 8px;margin: 2px;" />
<img src="./images/move.svg" class="lighten"
onclick="copyOrMoveCourse('move', event)"
style="border-radius: 50%;max-height: 16px;aspect-ratio: 1;padding: 8px;margin: 2px;" />
</div>
</div>
</div>
</div>
</div>
<!-- 悬浮加号 -->
<div style="display: grid;width: 100%;height: 100%;position: absolute;pointer-events: none;z-index: 50;"
v-bind:style="{'grid-template-rows': 'repeat(' + rowNum + ', 1fr)','grid-template-columns': `${timeBarWidth} repeat(${columnNum}, 1fr)`}">
<div v-if="addFloatShow && page.week == showingWeek"
style="width: 100%;height: 100%;padding: 2px;" v-bind:id="Math.random()"
v-bind:style="{'grid-area': floatArea}">
<div class="clickEffect" v-on:click.stop="wantToAddCourse"
style="width: 100%;height: 100%;border-radius: 8px;background-color: #0066ff30;align-items: center;justify-content: center;"
v-bind:style="{'pointer-events': floatParam == null ? 'none' : 'auto'}">
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="24"
height="24" style="width: 50%;aspect-ratio: 1;"
viewBox="0 0 24 24" version="1.1">
<path fill="#000000"
d="M12.75,21.25 C12.75,21.6642136 12.4142136,22 12,22 C11.5857864,22 11.25,21.6642136 11.25,21.25 L11.25,12.75 L2.75,12.75 C2.33578644,12.75 2,12.4142136 2,12 C2,11.5857864 2.33578644,11.25 2.75,11.25 L11.25,11.25 L11.25,2.75 C11.25,2.33578644 11.5857864,2 12,2 C12.4142136,2 12.75,2.33578644 12.75,2.75 L12.75,21.25 Z M21.25,11.25 C21.6642136,11.25 22,11.5857864 22,12 C22,12.4142136 21.6642136,12.75 21.25,12.75 L13.75,12.75 L13.75,11.25 L21.25,11.25 Z" />
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="nowMethod != null"
style="width: 100%;position: absolute;left: 0;right: 0;bottom: 40px;z-index: 50;align-items: center;justify-content: center;">
<button
style="padding: 8px 40px;border-radius: 30px;background-color: #dd0000;color: white;border: none;outline: none;box-shadow: 2px 2px 8px #dd000040;"
class="clickEffect" onclick="cancel()">取消</button>
</div>
</div>
<div v-if="nowMethod != null && nowMethod.holdTime"
style="width: 100%;height: 100%;display: grid;position: absolute;grid-template-rows: 1fr;z-index: 30;"
v-bind:style="{'grid-template-columns': `${timeBarWidth} repeat(${columnNum}, 1fr)`}">
<div v-for="i in columnNum" class="weekdayLine"
style="width: 100%;height: 100%;align-items: center;justify-content: center;"
v-bind:style="{'grid-area': `1 / ${i+1} / 1 / ${i+1}`}" v-bind:data-day="i"
onclick="weekdayLineClick(this.dataset.day)">
<img src="./images/drop.svg" />
</div>
</div>
</div>
</div>
<div
style="width: 16px;flex-shrink: 0;margin-left: 8px;margin-right: 4px;align-items: center;justify-content: center;flex-shrink: 0;">
<div style="height: 200px;width: 100%;align-items: center;justify-content: center;background-color: #FFFFFF;border-radius: 8px;"
class="move" v-on:click="moveTab(undefined)">
{{closeTab ? '&lt;' : '&gt;' }}
</div>
</div>
<div style="width: 50%;overflow: hidden;" v-bind:style="{'width': (closeTab ? '0' : '50%')}">
<div style="flex-direction: column;min-width: 300px;flex-shrink: 0;flex-grow: 1;">
<div style="flex-shrink: 0;align-items: center;">
<!-- 课表选择 -->
<div style="align-items: center;padding: 6px 16px;border-radius: 30px;background-color: #FFFFFF;width: fit-content;flex-shrink: 0;"
class="clickEffect" v-on:click="openChooseTable">
<p style="color: black;font-size: 14px;user-select: none;">{{courseName}}</p>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="24" height="24" viewBox="0 0 24 12" version="1.1">
<path
d="M18.6239478,4.2482689 L13.6439396,9.23007755 C12.97476,9.8992572 11.8984674,9.91319844 11.2123247,9.27190128 L6.21931844,4.28033009 C6.09044543,4.15145707 6.01827654,3.98735263 6.00281177,3.81902041 L6,3.7269699 C6.00562355,3.54277571 6.0787297,3.36025866 6.21931844,3.21966991 C6.50175119,2.93723717 6.95339796,2.92715028 7.24791742,3.18940926 L11.8761727,7.81586399 C12.1586054,8.09829674 12.6102522,8.10838362 12.9047717,7.84612464 L17.5330269,3.21966991 C17.8259202,2.9267767 18.3007939,2.9267767 18.5936871,3.21966991 C18.7349035,3.36088629 18.8080334,3.54440617 18.8130768,3.72943689 L18.8129192,3.77570243 C18.8071403,3.94528507 18.7441498,4.11328081 18.6239478,4.2482689 Z"
fill="black" />
</svg>
</div>
<svg style="margin-left: 8px;" width="24" height="24" class="clickEffect"
v-on:click="toSetting" viewBox="0 0 24 24" version="1.1">
<path
d="M13.4894964,1.25 C14.1901033,1.25 14.8231865,1.66785112 15.0986042,2.31205231 L15.0986042,2.31205231 L15.92125,4.23625 L16.77175,4.72725 L18.8556101,4.47607817 C19.5285537,4.39491431 20.1856012,4.70961774 20.5451004,5.27951678 L20.5451004,5.27951678 L20.5800481,5.33735213 L22.0680131,7.91013134 C22.4194652,8.51781272 22.3737758,9.27669833 21.9519545,9.83782407 L21.9519545,9.83782407 L20.69375,11.51125 L20.69375,12.4885 L21.9519801,14.16219 C22.3597241,14.70461 22.4160026,15.4318142 22.1018413,16.0285992 L22.1018413,16.0285992 L22.0680196,16.0898574 L20.5800481,18.6626479 C20.2293867,19.2689622 19.5509852,19.6077912 18.8556101,19.5239218 L18.8556101,19.5239218 L16.77175,19.2725 L15.92125,19.7635 L15.0986042,21.6879477 C14.832071,22.3113682 14.2305752,22.7228063 13.5570765,22.748702 L13.5570765,22.748702 L13.4894964,22.75 L10.5105451,22.75 C9.80990141,22.75 9.17679241,22.3321056 8.90139751,21.6878548 L8.90139751,21.6878548 L8.0785,19.7635 L7.228,19.2725 L5.14438986,19.5239218 C4.47144629,19.6050857 3.81439883,19.2903823 3.45489956,18.7204832 L3.45489956,18.7204832 L3.41995189,18.6626479 L1.9319804,16.0898574 C1.58053408,15.482186 1.62621577,14.7233142 2.04801988,14.16219 L2.04801988,14.16219 L3.306,12.4885 L3.306,11.51125 L2.04804545,9.83782407 C1.64028496,9.29540252 1.58399872,8.56818536 1.89816473,7.97139057 L1.89816473,7.97139057 L1.93198692,7.91013134 L3.41995189,5.33735213 C3.77061333,4.73103782 4.44901483,4.39220884 5.14438986,4.47607817 L5.14438986,4.47607817 L7.228,4.72725 L8.0785,4.23625 L8.90139751,2.31214525 C9.1679087,1.68867667 9.7694279,1.27719647 10.4429614,1.25129813 L10.4429614,1.25129813 L10.5105451,1.25 Z M13.4894964,2.75 L10.5105451,2.75 C10.4104531,2.75 10.320009,2.8096992 10.2806668,2.90173504 L10.2806668,2.90173504 L9.42547944,4.90233575 C9.31921851,5.1509195 9.13523096,5.35835416 8.90110986,5.49353025 L8.90110986,5.49353025 L7.90597116,6.06810022 C7.67169142,6.20336791 7.39985123,6.25898058 7.13127161,6.22658714 L7.13127161,6.22658714 L4.96477622,5.96528566 C4.86543693,5.95330433 4.76852243,6.00170847 4.71842794,6.0883248 L4.71842794,6.0883248 L3.23046296,8.66110401 C3.18025552,8.74791564 3.18678258,8.85632787 3.24704275,8.93648869 L3.24704275,8.93648869 L4.55532037,10.6768191 C4.71812619,10.8933908 4.80615731,11.1569971 4.80615595,11.4279382 L4.80615595,11.4279382 L4.80615019,12.5719965 C4.80614882,12.8429255 4.71812287,13.1065199 4.55532821,13.3230849 L4.55532821,13.3230849 L3.24703351,15.0635037 C3.18677578,15.1436643 3.18024983,15.2520745 3.23045644,15.3388847 L3.23045644,15.3388847 L4.71842794,17.9116752 C4.76852243,17.9982915 4.86543693,18.0466957 4.96477622,18.0347143 L4.96477622,18.0347143 L7.13127161,17.7734129 C7.39985123,17.7410194 7.67169142,17.7966321 7.90597116,17.9318998 L7.90597116,17.9318998 L8.90110986,18.5064697 C9.13523096,18.6416458 9.31921851,18.8490805 9.42547944,19.0976643 L9.42547944,19.0976643 L10.2806668,21.098265 C10.320009,21.1903008 10.4104531,21.25 10.5105451,21.25 L10.5105451,21.25 L13.4894964,21.25 C13.5895831,21.25 13.6800236,21.190307 13.7193689,21.0982782 L13.7193689,21.0982782 L14.5747715,19.097494 C14.6810343,18.8489455 14.8650036,18.6415401 15.0990951,18.506376 L15.0990951,18.506376 L16.0940158,17.9319104 C16.3282993,17.7966355 16.6001464,17.7410191 16.8687329,17.7734134 L16.8687329,17.7734134 L19.0352238,18.0347143 C19.1345631,18.0466957 19.2314776,17.9982915 19.2815721,17.9116752 L19.2815721,17.9116752 L20.7695436,15.3388847 C20.8197502,15.2520745 20.8132242,15.1436643 20.7529665,15.0635037 L20.7529665,15.0635037 L19.4446718,13.3230849 C19.2818771,13.1065199 19.1938512,12.8429255 19.1938498,12.5719965 L19.1938498,12.5719965 L19.1938441,11.4279382 C19.1938427,11.1569971 19.2818738,10.8933908 19.4446796,10.6768191 L19.4446796,10.6768191 L20.7529572,8.93648869 C20.8132174,8.85632787 20.8197445,8.74791564 20.769537,8.66110401 L20.769537,8.66110401 L19.2815721,6.0883248 C19.2314776,6.00170847 19.1345631,5.95330433 19.0352238,5.96528566 L19.0352238,5.96528566 L16.8687329,6.2265866 C16.6001464,6.25898087 16.3282993,6.2033645 16.0940158,6.06808955 L16.0940158,6.06808955 L15.0990951,5.49362397 C14.8650036,5.3584599 14.6810343,5.15105452 14.5747715,4.90250599 L14.5747715,4.90250599 L13.7193689,2.90172176 C13.6800236,2.80969302 13.5895831,2.75 13.4894964,2.75 L13.4894964,2.75 Z M12,7.5 C14.4852814,7.5 16.5,9.51471863 16.5,12 C16.5,14.4852814 14.4852814,16.5 12,16.5 C9.51471863,16.5 7.5,14.4852814 7.5,12 C7.5,9.51471863 9.51471863,7.5 12,7.5 Z M12,9 C10.3431458,9 9,10.3431458 9,12 C9,13.6568542 10.3431458,15 12,15 C13.6568542,15 15,13.6568542 15,12 C15,10.3431458 13.6568542,9 12,9 Z"
fill="black" />
</svg>
<div style="flex-grow: 1;min-width: 8px;flex-shrink: 0;"></div>
<!-- 二维码生成 -->
<img src="./images/qrcode.svg" title="qrcode" class="clickEffect" v-on:click="createQrCode"
v-if="courseName != null" />
<div style="width: 8px;flex-shrink: 0;" v-if="courseName != null"></div>
<!-- 教务导入 -->
<div style="padding: 6px 16px;background-color: #0066ff;color: white;border-radius: 30px;font-size: 14px;align-items: center;justify-content: center;"
class="clickEffect"
onclick="window.location.href = 'https\:\/\/gitee.com/ffffffds/htmlcourse/blob/master/download.md'">
教务导入</div>
<div style="width: 8px;flex-shrink: 0;"></div>
<!-- 上传课程表 -->
<input type="file" style="display: none;" id="selectFile" oninput="uploadCourse(event)" />
<div style="padding: 6px 16px;background-color: #0066ff;color: white;border-radius: 30px;font-size: 14px;align-items: center;justify-content: center;"
class="clickEffect" onclick="document.getElementById('selectFile').click()">上传课程表</div>
<div style="width: 8px;flex-shrink: 0;"></div>
<!-- 下载课程表 -->
<div style="padding: 6px 16px;background-color: #0066ff;color: white;border-radius: 30px;font-size: 14px;align-items: center;justify-content: center;"
class="clickEffect" v-on:click="downloadCourse" v-if="courseName != null">下载课程表</div>
<div style="width: 8px;flex-shrink: 0;" v-if="courseName != null"></div>
<!-- 华为登录按钮 -->
<div style="padding: 6px 16px;background-color: red;color: white;border-radius: 30px;font-size: 14px;align-items: center;justify-content: center;"
class="clickEffect" v-on:click="openPlatformChooseDialog(0)">从云备份中获取</div>
<div style="width: 8px;flex-shrink: 0;"></div>
<div style="padding: 6px 16px;background-color: red;color: white;border-radius: 30px;font-size: 14px;align-items: center;justify-content: center;"
class="clickEffect" v-on:click="openPlatformChooseDialog(1)">上传至云备份</div>
</div>
<div style="margin-top: 8px;width: 100%;flex-grow: 1;padding: 16px;border-radius: 16px;flex-direction: column;min-height: min-content;flex-shrink: 0;"
v-bind:style="{'background-color': backgroundImg != null ? '#FFFFFF50' : '#FFFFFF' }">
<div style="align-items: center;margin: 8px 0;">
<p style="width: 200px;flex-shrink: 0;">课程名称</p>
<input style="margin-left: 0;" placeholder="请输入课程名称" v-bind:value="itemName"
v-on:input="changeItem('itemName', $event)"
v-bind:style="{'background-color': itemNameValid ? '#80808020' : '#ff000020'}" />
</div>
<div style="align-items: center;margin: 8px 0;">
<p style="width: 200px;flex-shrink: 0;">上课星期</p>
<p v-for="(day, i) in dayName"
style="flex-grow: 1;margin: 0 8px;padding: 4px 8px;border-radius: 30px;text-align: center;font-size: 12px;"
class="clickEffect"
v-bind:style="{'background-color': (i == itemDay ? '#0066ff30' : '#80808020')}"
v-on:click="changeItemDay(i)">周{{day}}</p>
</div>
<div style="align-items: center;margin: 8px 0;">
<p style="width: 200px;flex-shrink: 0;">上课时间</p>
<input type="time" min="00:00" max="23:59" step="300" pattern="\d{2}:\d{2}"
style="margin-left: 0;text-align: center;font-weight: bold;"
v-bind:value="itemStart" v-on:input="changeItem('itemStart', $event)"
v-bind:style="{'background-color': itemStartValid ? '#80808020' : '#ff000020'}" />
</div>
<div style="align-items: center;margin: 8px 0;">
<p style="width: 200px;flex-shrink: 0;">上课时长</p>
<input type="time" min="00:00" max="23:59" step="300" pattern="\d{2}:\d{2}"
style="margin-left: 0;text-align: center;font-weight: bold;" v-bind:value="itemLast"
v-on:input="changeItem('itemLast', $event)"
v-bind:style="{'background-color': itemLastValid ? '#80808020' : '#ff000020'}" />
</div>
<p style="font-size: 10px;opacity: 0.7;margin-bottom: 8px;">注意:上课时间+上课时长就等于下课时间噢</p>
<div style="align-items: center;margin: 8px 0;">
<p style="width: 200px;flex-shrink: 0;">上课周次</p>
<input style="margin-left: 0;" placeholder="请输入上课周次" v-bind:value="itemWeek" type="text"
v-on:input="changeItem('itemWeek', $event)"
pattern="^\[?(\d+([-——]\d+[单双]?)?([ ,,、]\d+([-——]\d+[单双]?)?)*)?\]?$"
v-bind:style="{'background-color': itemWeekValid ? '#80808020' : '#ff000020'}" />
</div>
<p style="font-size: 10px;opacity: 0.7;margin-bottom: 8px;">
上课周次输入规则<br>[1]表示第一周上课<br>[1-6]表示第一周到第六周都上<br>[1-6双]表示第一周到第六周的双周都上<br>你可以把他们组合起来如:[1,2-6,8-14单]表示第一周、第二周到第六周、第八周到第十四周的单周都上
</p>
<div style="align-items: center;margin: 8px 0;">
<p style="width: 200px;flex-shrink: 0;">上课教师</p>
<input style="margin-left: 0;" placeholder="请输入课程名称" v-bind:value="itemTeacher"
v-on:input="changeItem('itemTeacher', $event)" />
</div>
<div style="align-items: center;margin: 8px 0;">
<p style="width: 200px;flex-shrink: 0;">上课地点</p>
<input style="margin-left: 0;" placeholder="请输入课程名称" v-bind:value="itemPlace"
v-on:input="changeItem('itemPlace', $event)" />
</div>
<p style="align-items: center;margin: 8px 0;border-radius: 30px;background-color: #0066ff;text-align: center;padding: 8px;color: white;"
v-if="nowEdit == null" class="clickEffect" v-on:click="addCourse">
添加
</p>
<div style="align-items: center;margin: 8px 0;" v-else>
<p style="align-items: center;border-radius: 30px;background-color: #0066ff;text-align: center;padding: 8px;color: white;flex-grow: 1;"
class="clickEffect" v-on:click="saveCourse"
v-bind:style="{'opacity': saveAble ? 1 : 0.5, 'pointer-events': saveAble ? 'auto' : 'none'}">
保存
</p>
<div style="width: 16px;flex-shrink: 0;"></div>
<p style="align-items: center;border-radius: 30px;background-color: #dd0000;text-align: center;padding: 8px;color: white;flex-grow: 1;"
class="clickEffect" v-on:click="deleteCourse">
删除
</p>
</div>
</div>
<div style="flex-shrink: 0;align-items: center;margin-top: 8px;">
<div style="flex-grow: 1;min-width: 8px;flex-shrink: 0;"></div>
<div style="padding: 6px 16px;background-color: #0066ff;color: white;border-radius: 30px;font-size: 14px;align-items: center;justify-content: center;"
class="clickEffect" v-on:click="zipAll">打包所有数据</div>
</div>
</div>
</div>
</div>
</div>
<dialog id="selectTable"
style="background-color: #FFFFFF;outline: none;border: none;padding: 16px;border-radius: 16px;box-shadow: 4px 4px 10px 0 #00000030;">
<div style="width: 500px;max-width: 300px;height: 250px;flex-direction: column;">
<p style="font-size: 16px;width: 100%;margin-bottom: 8px;flex-shrink: 0;text-align: center;">请选择课程表
</p>
<div style="flex-grow: 1;width: 100%;flex-direction: column;overflow-y: auto;overflow-x: hidden;">
<div v-for="name in courseNames" class="clickEffect"
style="width: 100%;padding: 6px 16px;align-items: center;justify-content: center;margin: 4px 0;background-color: #80808020;border-radius: 30px;font-size: 12px;"
v-on:click="selectTable(name)">
<p style="flex-grow: 1;text-align: center;">{{name}}</p>
<svg width="24" height="24" style="flex-shrink: 0;transform: scale(0.75)" class="clickEffect"
v-on:click="deleteTable(name)">
<path
d="M19.7781746,4.22182541 C20.0710678,4.51471863 20.0710678,4.98959236 19.7781746,5.28248558 L5.28248558,19.7781746 C4.98959236,20.0710678 4.51471863,20.0710678 4.22182541,19.7781746 C3.92893219,19.4852814 3.92893219,19.0104076 4.22182541,18.7175144 L10.9395166,11.9994697 L4.22182541,5.28248558 C3.92893219,4.98959236 3.92893219,4.51471863 4.22182541,4.22182541 C4.51471863,3.92893219 4.98959236,3.92893219 5.28248558,4.22182541 L12,10.9389863 L18.7175144,4.22182541 C19.0104076,3.92893219 19.4852814,3.92893219 19.7781746,4.22182541 Z M19.7781746,18.7175144 C20.0710678,19.0104076 20.0710678,19.4852814 19.7781746,19.7781746 C19.4852814,20.0710678 19.0104076,20.0710678 18.7175144,19.7781746 L12.7072836,13.7675902 L13.767767,12.7071068 L19.7781746,18.7175144 Z" />
</svg>
</div>
</div>
<div class="clickEffect"
style="border-radius: 30px;background-color: #dd0000;color: white;padding: 6px 16px;border: none;text-align: center;width: 100%;align-items: center;justify-content: center;flex-shrink: 0;font-size: 16px;"
v-on:click="closeSelectTable">取消</div>
<div class="clickEffect"
style="border-radius: 30px;background-color: #0066ff;color: white;padding: 6px 16px;border: none;text-align: center;width: 100%;align-items: center;justify-content: center;flex-shrink: 0;font-size: 16px;margin-top: 8px;"
v-on:click="createTable">新建课程表</div>
</div>
</dialog>
<dialog id="PlatformChooseDialog"
style="background-color: #FFFFFF;outline: none;border: none;padding: 16px;border-radius: 16px;box-shadow: 4px 4px 10px 0 #00000030;">
<div style="width: 500px;max-width: 300px;height: 200px;flex-direction: column;">
<p style="font-size: 16px;width: 100%;margin-bottom: 8px;flex-shrink: 0;text-align: center;">
请选择云空间数据归属
</p>
<div style="flex-grow: 1;width: 100%;flex-direction: column;overflow-y: auto;overflow-x: hidden;">
<div class="clickEffect"
style="width: 100%;padding: 12px 16px;align-items: center;justify-content: center;margin: 4px 0;background-color: #80808020;border-radius: 30px;font-size: 12px;"
v-on:click="choosePlatform(true)">一课表应用(应用市场)
</div>
<div class="clickEffect"
style="width: 100%;padding: 12px 16px;align-items: center;justify-content: center;margin: 4px 0;background-color: #80808020;border-radius: 30px;font-size: 12px;"
v-on:click="choosePlatform(false)">一课表服务(服务中心/负一屏)
</div>
</div>
<div class="clickEffect"
style="border-radius: 30px;background-color: #dd0000;color: white;padding: 8px 16px;border: none;text-align: center;width: 100%;align-items: center;justify-content: center;flex-shrink: 0;font-size: 16px;"
v-on:click="PlatformChooseDialog_cancel">取消</div>
</div>
</dialog>
<dialog id="ItemCheckboxDialog"
style="background-color: #FFFFFF;outline: none;border: none;padding: 16px;border-radius: 16px;box-shadow: 4px 4px 10px 0 #00000030;">
<div style="width: 500px;max-width: 300px;height: 200px;flex-direction: column;">
<p style="font-size: 16px;width: 100%;margin-bottom: 8px;flex-shrink: 0;text-align: center;">
{{itemCheckboxDialog_title}}</p>
<div style="flex-grow: 1;width: 100%;flex-direction: column;overflow-y: auto;overflow-x: hidden;">
<div v-for="(item, idx) in itemCheckboxDialog_items"
style="width: 100%;padding: 12px 16px;align-items: center;justify-content: center;margin: 4px 0;background-color: #80808020;border-radius: 30px;font-size: 12px;">
<input type="checkbox" v-bind:checked="item.checked" style="flex-shrink: 0;" v-bind:value="idx"
v-on:change="itemCheckboxDialog_itemCheck" />
<p style="flex-grow: 1;">{{item.name}}</p>
</div>
</div>
<div class="clickEffect"
style="border-radius: 30px;background-color: #0066ff;color: white;padding: 8px 16px;border: none;text-align: center;width: 100%;align-items: center;justify-content: center;flex-shrink: 0;font-size: 16px;"
v-on:click="itemCheckboxDialog_finish">确定</div>
<div style="height: 8px;flex-shrink: 0;"></div>
<div class="clickEffect"
style="border-radius: 30px;background-color: #dd0000;color: white;padding: 8px 16px;border: none;text-align: center;width: 100%;align-items: center;justify-content: center;flex-shrink: 0;font-size: 16px;"
v-on:click="itemCheckboxDialog_cancel">取消</div>
</div>
</dialog>
<dialog id="WeekChooseDialog"
style="background-color: #FFFFFF;outline: none;border: none;padding: 16px;border-radius: 16px;box-shadow: 4px 4px 10px 0 #00000030;">
<div style="width: 480px;height: 200px;flex-direction: column;">
<p style="font-size: 16px;width: 100%;margin-bottom: 8px;flex-shrink: 0;text-align: center;">
请选择要跳转到的周次</p>
<div
style="flex-grow: 1;width: 100%;flex-direction: row;overflow-y: auto;overflow-x: hidden;flex-wrap: wrap;align-items: center;">
<div v-for="(item, idx) in weekChooseDialog_items"
style="width: 80px;padding: 8px 16px;align-items: center;justify-content: center;margin: 4px 8px;background-color: #0066ff;border-radius: 30px;font-size: 12px;color: white;"
class="clickEffect" v-on:click="toWeek(idx)">
<p style="color: white">{{item.week}}</p>
</div>
</div>
<div class="clickEffect"
style="border-radius: 30px;background-color: #dd0000;color: white;padding: 8px 16px;border: none;text-align: center;width: 100%;align-items: center;justify-content: center;flex-shrink: 0;font-size: 16px;margin-top: 8px;"
v-on:click="weekChooseDialog_cancel">取消</div>
</div>
</dialog>
<dialog id="NewTableDialog"
style="background-color: #FFFFFF;outline: none;border: none;padding: 16px;border-radius: 16px;box-shadow: 4px 4px 10px 0 #00000030;">
<div style="width: 500px;max-width: 300px;flex-direction: column;">
<p style="font-size: 16px;width: 100%;margin-bottom: 8px;flex-shrink: 0;text-align: center;">新建课程表
</p>
<div style="align-items: center;margin: 4px 0;">
<p>名称</p>
<input placeholder="请输入课程表名称" v-bind:value="newCourseName" />
</div>
<div style="align-items: center;margin: 4px 0;">
<p>开学日期</p>
<input placeholder="请输入开学日期" v-bind:value="newCourseDate" v-on:input="newCourseDateChange" />
</div>
<p style="font-size: 12px;opacity: 0.7;">开学日期为第一周周一或周日:XXXX-XX-XX</p>
<p style="font-size: 10px;" v-bind:style="{'color': dateError ? 'red' : 'black'}" v-if="dateError">
请注意格式
</p>
<div class="clickEffect"
style="border-radius: 30px;background-color: #0066ff;color: white;padding: 12px 16px;border: none;text-align: center;width: 100%;align-items: center;justify-content: center;flex-shrink: 0;font-size: 16px;margin-top: 16px;"
v-on:click="createTableConfirm">确定</div>
<div class="clickEffect"
style="border-radius: 30px;background-color: #dd0000;color: white;padding: 12px 16px;border: none;text-align: center;width: 100%;align-items: center;justify-content: center;flex-shrink: 0;font-size: 16px;margin-top: 16px;"
v-on:click="closeNewTableDialog">取消</div>
</div>
</dialog>
<dialog id="qrcodeDialog"
style="background-color: #FFFFFF;outline: none;border: none;padding: 16px;border-radius: 16px;box-shadow: 4px 4px 10px 0 #00000030;">
<div style="width: 500px;max-width: 400px;flex-direction: column;align-items: center;">
<p style="font-size: 16px;width: 100%;margin-bottom: 8px;flex-shrink: 0;text-align: center;">
请在华为手机桌面下滑使用智慧搜索扫码</p>
<div id="qrcode" style="height: 400px;width: 400px;"></div>
<div class="clickEffect"
style="border-radius: 30px;background-color: #dd0000;color: white;padding: 12px 16px;border: none;text-align: center;width: 100%;align-items: center;justify-content: center;flex-shrink: 0;font-size: 16px;margin-top: 16px;"
v-on:click="closeQrcodeDialog">关闭</div>
</div>
</dialog>
<div style="display: flex;align-items: center;justify-content: center;position: absolute;width: 100%;height: 100%;z-index: 100;"
v-if="isLoading">
<svg width="52" height="52" class="loading">
<path d="M24,2 a24,24 0 0,1 26,26" stroke-linecap="round" fill="none" stroke="black" stroke-width="4" />
</svg>
</div>
</div>
</body>
<script>
let tc = window.localStorage.getItem('textColor')
document.body.style.setProperty('--textColor', tc == null ? '#000000' : tc)
var $this = null
var $utils = null
var platFormIsApp = true
const decode = function (str) {
let strData = atob(str);
const charData = strData.split('').map(function (x) {
return x.charCodeAt(0);
});
const binData = new Uint8Array(charData);
const data = pako.inflate(binData);
strData = String.fromCharCode.apply(null, new Uint16Array(data));
return decodeURIComponent(strData);
}
const encode = function (str) {
return btoa(pako.gzip(encodeURIComponent(str), { to: 'string' }))
}
const hexEncode = function (buffer) {
const map = '0123456789ABCDEF'
return Array.from(buffer).map(byte => {
return map[byte >> 4] + map[byte & 0x0F]
}).join('')
}
const weekCombine = function (Week) {
Week.forEach((v, i) => {
console.log(v)
if (typeof v === 'number') v = String(v)
if (v.indexOf('-') !== -1 || v.indexOf('——') !== -1) {
let t = v.split(/[\-——]/)
if (t[0] == t[1])
Week[i] = t[0]
}
})
let newWeek = []
Week.forEach(w => {
let t = w.split(/[\-——]/)
if (t.length == 1) {
if (Number(w) <= 50 && newWeek.indexOf(Number(w)) == -1)
newWeek.push(Number(w))
} else {
for (let i = Number(t[0]); i <= Number(t[1]) && i <= 50; i++) {
if (newWeek.indexOf(i) == -1)
newWeek.push(i)
}
}
})
newWeek.sort((a, b) => { return a - b })
if (newWeek.length != 0 && newWeek[0] == 0) newWeek.splice(0, 1)
let wt = []
if (newWeek.length != 0) {
let s = newWeek[0]
let e = s
for (let i = 1; i < newWeek.length; i++) {
if (newWeek[i] != e + 1) {
if (s == e)
wt.push(String(s))
else
wt.push(s + '-' + e)
s = newWeek[i]
}
e = newWeek[i]
}
if (s == e)
wt.push(String(s))
else
wt.push(s + '-' + e)
}
return wt
}
const getWeek = function (str) {
str = str.replace(/[\[\]]/g, '').replace(/——/g, '-').replace(/[, 、]/g, ',').replace(/[^\d-,单双]/, '')
console.log(str)
let weeks = str.split(',')
let res = []
for (let w = 0; w < weeks.length; w++) {
let week = weeks[w]
if (typeof week === 'number') week = String(week)
if (week === '') continue;
let type = 2
if (week.indexOf('') !== -1) type = 1
if (week.indexOf('') !== -1) type = 0
week = week.replace(/[^\d\-——]/g, '')
let s, e
if (week.indexOf('-') !== -1 || week.indexOf('——') !== -1) {
let t = week.split(/[\-——]/)
if (t[0] == '' || t[1] == '') continue;
s = parseInt(t[0])
e = parseInt(t[1])
} else {
try {
s = e = parseInt(week)
} catch (e) { continue }
}
if (s < 1 || e > 50) return '周次不可小于1或大于50';
console.log(`${s},${e},${type}`)
if (type == 2) res.unshift(week)
else {
for (let i = s; i <= e; i++) {
if (i % 2 == type) res.unshift(String(i))
}
}
}
return weekCombine(res.sort((w1, w2) => {
let t1 = w1.split('-')
let t2 = w2.split('-')
return parseInt(t1[0]) - parseInt(t2[0])
}))
}
const push = function (arr, item, compare_fn) {
for (let i = 0; i < arr.length; i++) {
let ret = compare_fn(arr[i], item)
if (ret > 0) {
arr.splice(i, 0, item)
return i
}
}
arr.push(item)
return arr.length - 1
}
</script>
<script>
window.localStorage.removeItem('AuthToken')
window.localStorage.removeItem('AuthTokenService')
var loadTime = null
var timeCombine = null
var dayToCol = [0, 1, 2, 3, 4, 5, 6]
var showWeekend = true
var swiperManager = null
var touchParam = null
var touchingTimer = null
var storage = null, set_storage = null
function itemClick(ele) {
const dataset = ele.dataset
const day = dataset.day
const p = dataset.p.split(',')
const e = event
if (e) e.stopPropagation()
$this.addFloatShow = false
$this.floatParam = null
if ($this.showingParam == null || $this.showingParam[0] != day || $this.showingParam[1].toString() != p.toString()) {
$this.nowEdit = [day, p[0]]
$this.showingParam = [day, p, 0]
} else {
$this.showingParam[2]++
$this.showingParam[2] %= p.length
$this.nowEdit[1] = p[$this.showingParam[2]]
}
let course = $this.courses.courses[$this.nowEdit[0]][$this.nowEdit[1]]
$this.itemName = course.name
$this.itemNameValid = true
$this.itemStart = course.s
$this.itemLast = course.l
$this.itemWeek = course.week.join(',')
$this.itemTeacher = course.teacher || ''
$this.itemPlace = course.place || ''
$this.itemLock = course.lock || false
$this.itemDay = day
$this.moveTab(false)
}
function touchSurfaceStart() {
const e = event
console.log('touch')
let x = e.offsetX, y = e.offsetY
let w = e.currentTarget.clientWidth, h = e.currentTarget.clientHeight
let bh = h / $this.rowNum
if (x < $utils.px2num($this.timeBarWidth)) return;
let day = ((x - $utils.px2num($this.timeBarWidth)) * 7 / (w - $utils.px2num($this.timeBarWidth))) | 0
let block = 1
while (block + 1 < loadTime.getBlockLength() + 1 && y > loadTime.countBlock(block + 1) * bh) {
block++
}
touchParam = {
day: day,
sBlock: block,
eBlock: block,
x: x, y: y
}
let u = Math.min(touchParam.sBlock, touchParam.eBlock)
let d = Math.max(touchParam.sBlock, touchParam.eBlock)
$this.floatArea = `${loadTime.countBlock(u) + 1} / ${touchParam.day + 2} / ${loadTime.countBlockAtEnd(d) + 1} / ${touchParam.day + 2}`
$this.addFloatShow = false
$this.floatParam = null
touchingTimer = setTimeout(() => {
touchingTimer = null
$this.addFloatShow = true
$this.showing = false
$this.swiper.disableTouchControl()
Toast.show('移动鼠标选取节次', 1000)
}, 300)
}
function touchSurfaceMove() {
const e = event
if (touchParam == null) return;
let x = e.offsetX, y = e.offsetY
let w = e.currentTarget.clientWidth, h = e.currentTarget.clientHeight
let bh = h / $this.rowNum
let day = ((x - $utils.px2num($this.timeBarWidth)) * 7 / (w - $utils.px2num($this.timeBarWidth))) | 0
let dist = Math.sqrt((x - touchParam.x) ** 2 + (y - touchParam.y) ** 2)
if (touchingTimer != null && dist > 3) {
clearTimeout(touchingTimer)
touchingTimer = null
$this.swiper.enableTouchControl()
$this.showing = true
return;
}
let block = 1
while (block + 1 < loadTime.getBlockLength() + 1 && y > loadTime.countBlock(block + 1) * bh) {
block++
}
touchParam.eBlock = block
let u = Math.min(touchParam.sBlock, touchParam.eBlock)
let d = Math.max(touchParam.sBlock, touchParam.eBlock)
$this.floatArea = `${loadTime.countBlock(u) + 1} / ${touchParam.day + 2} / ${loadTime.countBlockAtEnd(d) + 1} / ${touchParam.day + 2}`
}
function touchSurfaceEnd() {
const e = event
console.log('touchEnd')
if (touchingTimer != null) {
clearInterval(touchingTimer)
touchingTimer = null
$this.swiper.enableTouchControl()
if (touchParam != null && touchParam.sBlock == touchParam.eBlock) {
if ($this.nowMethod) {
copyOrMove($this.nowMethod.all, $this.nowMethod.type, $this.nowMethod.data.day, $this.nowMethod.data.block, $this.nowMethod.data.week,
$this.nowMethod.data.p, touchParam.day, touchParam.sBlock, $this.showingWeek, $this.nowMethod.holdTime)
$this.save()
cancel()
return;
}
$this.addFloatShow = true
$this.floatArea = `${loadTime.countBlock(touchParam.sBlock) + 1} / ${touchParam.day + 2} / ${loadTime.countBlockAtEnd(touchParam.sBlock) + 1} / ${touchParam.day + 2}`
}
}
if ($this.addFloatShow) {
$this.floatParam = touchParam
$this.wantToAddCourse()
$this.moveTab(false)
}
touchParam = null
$this.showing = true
}
function getRelativePosition(element, event) {
return {
x: event.pageX - $(element).offset().left,
y: event.pageY - $(element).offset().top
}
}
function dragCourseStart() {
const e = event
e.dataTransfer.dropEffect = 'move';
e.dataTransfer.setData('application/course', JSON.stringify({
day: parseInt(e.currentTarget.dataset.day),
p: e.currentTarget.dataset.p.split(',')
}))
let ele = document.querySelectorAll('#courseBg')[$this.swiper.activeIndex]
let mp = getRelativePosition(ele, event)
let { x, y } = mp
let w = $(ele).width(), h = $(ele).height()
let bh = h / $this.rowNum
let block = 1
while (block + 1 < loadTime.getBlockLength() + 1 && y > loadTime.countBlock(block + 1) * bh) {
block++
}
e.dataTransfer.setData('mouse/block', block)
e.dataTransfer.setData('mouse/position', JSON.stringify(mp))
$this.itemName = ''
$this.itemTeacher = ''
$this.itemPlace = ''
$this.itemWeek = ''
$this.nowEdit = null
$this.addFloatShow = false
$this.floatArea = null
}
function removeWeek(weeks, week) {
if (!(weeks instanceof Array)) return;
let nweeks = []
weeks.forEach(w => {
if (typeof w == 'number') w = String(w)
let t = w.split('-')
let s = parseInt(t[0]), e = parseInt(t[t.length - 1])
if (week >= s && week <= e) {
if (s <= week - 1) nweeks.push(s == week - 1 ? String(s) : String(s) + '-' + String(week - 1))
if (e >= week + 1) nweeks.push(e == week + 1 ? String(e) : String(week + 1) + '-' + String(e))
} else {
nweeks.push(w)
}
})
return nweeks
}
function dropCourse(e) {
console.log(e)
e.preventDefault()
let data = JSON.parse(e.dataTransfer.getData('application/course'))
let p = data.p.map(v => { return parseInt(v) }).sort().reverse()
let pd = data.day
let originalBlock = JSON.parse(e.dataTransfer.getData('mouse/block'))
console.log(data, originalBlock)
let mp = JSON.parse(e.dataTransfer.getData('mouse/position'))
let w = e.currentTarget.clientWidth, h = e.currentTarget.clientHeight
let bh = h / $this.rowNum
let bw = (w - $utils.px2num($this.timeBarWidth)) / 7
let centerX = $utils.px2num($this.timeBarWidth) + (pd + 0.5) * bw, centerY = (loadTime.countBlock(originalBlock) + loadTime.countBlockAtEnd(originalBlock)) * bh / 2
let fixOffsetX = centerX - mp.x, fixOffsetY = centerY - mp.y
let x = e.offsetX + fixOffsetX, y = e.offsetY + fixOffsetY
if (x < $utils.px2num($this.timeBarWidth)) return;
let day = ((x - $utils.px2num($this.timeBarWidth)) * 7 / (w - $utils.px2num($this.timeBarWidth))) | 0
let block = 1
while (block + 1 < loadTime.getBlockLength() + 1 && y > loadTime.countBlock(block + 1) * bh) {
block++
}
if (pd == day && block == originalBlock) return;
var isMoving = confirm('是否仅移动课程(否则复制课程)?')
var isAll = confirm(`是否${isMoving ? '移动' : '复制'}所有周次(否则${isMoving ? '移动' : '复制'}单节)?`)
console.log('new', day, block)
copyOrMove(isAll, isMoving ? 'move' : 'copy', pd, originalBlock, $this.showingWeek, p, day, block, $this.showingWeek, false)
$this.save()
}
function handlerEnter(element) {
element.parentElement.parentElement.parentElement.setAttribute('draggable', 'true')
}
function handlerLeave(element) {
element.parentElement.parentElement.parentElement.setAttribute('draggable', 'false')
}
function copyOrMoveCourse(type, e) {
e.stopPropagation()
let isAll = confirm(`是否${type == 'move' ? '移动' : '复制'}全部周次(否则${type == 'move' ? '移动' : '复制'}单节)?`)
let ele = document.querySelectorAll('#courseBg')[$this.swiper.activeIndex]
let mp = getRelativePosition(ele, event)
let { x, y } = mp
let w = $(ele).width(), h = $(ele).height()
let bh = h / $this.rowNum
let block = 1
while (block + 1 < loadTime.getBlockLength() + 1 && y > loadTime.countBlock(block + 1) * bh) {
block++
}
$this.showing = false
$this.nowMethod = {
type: type,
all: isAll,
data: {
day: parseInt(e.currentTarget.parentElement.parentElement.parentElement.parentElement.dataset.day),
block: block,
week: $this.showingWeek,
p: e.currentTarget.parentElement.parentElement.parentElement.parentElement.dataset.p.split(',')
},
holdTime: false
}
Toast.show(`选择你要${type == 'move' ? '移动' : '复制'}到的星期`)
}
function copyOrMoveDay(type, e) {
e.stopPropagation()
let isAll = confirm(`是否${type == 'move' ? '移动' : '复制'}全部周次(否则${type == 'move' ? '移动' : '复制'}单节)?`)
let day = parseInt(e.currentTarget.parentElement.parentElement.parentElement.dataset.day)
let p = []
$this.courses.courses[day].forEach((c, i) => {
if ($utils.CheckWeek(c.week, $this.showingWeek)) {
p.push(i)
}
})
p.sort().reverse()
$this.showing = false
$this.nowMethod = {
type: type,
all: isAll,
data: {
day: day,
block: null,
week: $this.showingWeek,
p: p
},
holdTime: true
}
Toast.show(`选择你要${type == 'move' ? '移动' : '复制'}到的位置`)
}
function weekdayLineClick(day) {
day = parseInt(day) - 1
console.log($this.nowMethod)
copyOrMove($this.nowMethod.all, $this.nowMethod.type, $this.nowMethod.data.day, null, $this.nowMethod.data.week, $this.nowMethod.data.p, day, null, $this.showingWeek, $this.nowMethod.holdTime)
cancel()
$this.save()
}
function cancel() {
$this.nowMethod = null
$this.showing = true
}
function copyOrMove(isAll, type, fromDay, fromBlock, fromWeek, p, toDay, toBlock, toWeek, holdTime) {
let courses = []
p.forEach(i => {
let course = $this.courses.courses[fromDay][parseInt(i)]
courses.push(course)
if (course.week.length == 0 && !isAll) {
cancel()
alert('当前课程没有周次信息,无法单节移动')
throw ('当前课程没有周次信息,无法单节移动')
return;
}
})
if (type == 'move') {
p.forEach(i => {
if (isAll) {
$this.courses.courses[fromDay].splice(i, 1)
} else {
let c = $this.courses.courses[fromDay][i]
c.week = removeWeek(c.week, fromWeek)
if (c.week.length == 0) {
$this.courses.courses[fromDay].splice(i, 1)
}
}
})
}
courses.forEach(course => {
console.log(course)
let s = $utils.time2num_float(course.s)
let l = $utils.time2num_float(course.l)
let blockRate = [], len = loadTime.getBlockLength() + 1
for (let i = 1; i < len; i++) {
let s_ = Math.max(s, loadTime.getStartV(i))
let e_ = Math.min(s + l, loadTime.getStartV(i) + loadTime.getLastV(i))
blockRate.push((e_ - s_) / loadTime.getLastV(i))
}
let sb = -1, eb = -1
blockRate.forEach((v, i) => {
if (v >= 0.5) {
if (sb == -1) sb = i + 1
eb = i + 1
}
})
console.log('old', sb, eb)
console.log('new', toBlock + sb - fromBlock, toBlock + eb - fromBlock)
let newOne = {}
if (!holdTime) {
newOne.s = loadTime.getStart(toBlock + sb - fromBlock)
newOne.l = $utils.num2time_float(loadTime.getStartV(toBlock + eb - fromBlock) - loadTime.getStartV(toBlock + sb - fromBlock) + loadTime.getLastV(toBlock + eb - fromBlock))
}
if (!isAll) {
newOne.week = [String(toWeek)]
}
push($this.courses.courses[toDay], { ...course, ...newOne }, (c1, c2) => {
return $utils.time2num(c1.s) - $utils.time2num(c2.s)
})
})
}
function uploadCourse(e) {
console.log(e)
let files = e.currentTarget.files
if (files.length > 0) {
let reader = new FileReader()
reader.readAsText(files[0])
reader.onload = async () => {
let text = reader.result
try {
let coursesTable = JSON.parse(text)
let name = coursesTable.name
if (loadTime == null) {
let res = (await TimeTable.loadTimeTable(null))
loadTime = res[0]
}
let max = -1;
for (let day of coursesTable.courses) {
for (let c of day) {
if (c.index != undefined && c.s == undefined) {
if (c.index > loadTime.getBlockLength()) {
max = Math.max(c.index, max)
continue;
}
c.s = loadTime.getStart(c.index)
}
if (c.index != undefined && c.last != undefined && c.l == undefined) {
if (c.index + c.last - 1 > loadTime.getBlockLength()) {
max = Math.max(c.index + c.last - 1, max)
continue;
}
c.l = $utils.num2time_float(loadTime.getStartV(c.index + c.last - 1) - loadTime.getStartV(c.index) + loadTime.getLastV(c.index + c.last))
}
delete c.index
delete c.last
}
}
if (max != -1) {
alert(`错误:时间表节次数目不够,需要节次${max},但时间表仅有${loadTime.getBlockLength()}节`)
return;
}
console.log(coursesTable)
storage.keys().then(keys => {
while (keys.indexOf(name) != -1) {
let cover = confirm('当前名称课程表已存在,是否覆盖?')
if (!cover) {
name = prompt('请输入新的课程表名称', name)
if (name == null || name == '') {
return;
}
continue
} else {
break;
}
}
coursesTable.name = name
let data = encode(JSON.stringify(coursesTable))
storage.set(coursesTable.name, data).then(() => {
set_storage.set('nCourse', coursesTable.name).then(() => {
window.location.reload()
})
})
})
} catch (e) {
console.error(e)
Toast.show('文件格式异常')
}
}
}
}
</script>
<script type="module">
import * as utils from './utils.js'
import { TimeTable } from './TimeTable.js'
import { TimeCombine } from './TimeCombine.js'
import { SwiperManager } from './Swiper.js'
$utils = utils
storage = utils.getStorage({
priority: ['indexeddb'],
name: 'Course'
})
set_storage = utils.getStorage({
priority: ['indexeddb'],
name: 'Set'
})
var vm = new Vue({
el: '#app',
data: {
courseNames: [],
courseName: null,
rowNum: 100,
timesShow: [],
weekday: 0,
courses: [],
pages: [],
timeBarWidth: '50px',
showingWeek: 1,
realWeek: 1,
swiper: null,
startDate: new Date(),
today: utils.formatDate(new Date()),
nBlock: {
sb: 1,
eb: 10
},
addFloatShow: false,
floatArea: '',
floatParam: null,
showing: true,
showItems: false,
selectItems: [],
startForSunday: false,
weekdayLine: 0,
platformNext: 0,
itemCheckboxDialog_title: '标题',
itemCheckboxDialog_items: [],
weekChooseDialog_items: [],
columnNum: 7,
newCourseName: '课程表',
newCourseDate: '2023-01-02',
dateError: false,
itemName: '',
itemDay: 2,
itemStart: '08:30',
itemLast: '01:30',
itemTeacher: '',
itemPlace: '',
itemWeek: '',
itemLock: false,
itemNameValid: false,
itemStartValid: true,
itemLastValid: true,
itemWeekValid: true,
dayName: '一二三四五六日'.split(''),
nowEdit: null,
showingParam: null,
saveAble: false,
closeTab: true,
nowMethod: null,
backgroundImg: null,
isLoading: false
},
async created() {
$this = this
this.backgroundImg = window.localStorage.getItem('backgroundImg')
setTimeout(() => {
let self = this
self.swiper = new Swiper('.swiper-container', {
loop: true,
mousewheelControl: true,
updateOnWindowResize: false,
preventClicks: false,
onSlideChangeEnd(swiper) {
let nowIndex = swiper.realIndex
let a = swiper.activeIndex
let p = swiper.previousIndex
if (Math.abs(a - p) == 1) {
self.weekChange(nowIndex)
}
}
})
}, 300)
await this.loadSetting()
await this.readCourseName()
console.log(this.courseName)
if (this.courseName == null) {
return;
}
await this.readCourse()
this.loadCourse()
new ResizeObserver(() => {
if (this.swiper) {
this.swiper.onResize()
}
}).observe(document.getElementById('observeTarget'))
this.closeTab = window.localStorage.closeTab == null ? false : window.localStorage.closeTab == 'true'
const update = function () {
this.$nextTick(() => {
$this.swiper.reLoop()
})
}
this.$watch('addFloatShow', update)
this.$watch('floatArea', update)
this.$watch('showing', update)
this.$watch('pages', update)
},
methods: {
loading(loading) {
$this.isLoading = loading
},
async loadSetting() {
let _showWeekend = (await set_storage.get('showWeekend')).showWeekend
showWeekend = _showWeekend != undefined ? _showWeekend : true
},
getRowNum() {
let rn = 0
for (let i = 0; i < loadTime.getBlockLength(); i++) {
let l = utils.time2num(loadTime.getLast(i + 1))
console.log(l)
rn += l
}
this.rowNum = ((rn / 5) | 0)
},
getNowBlock() {
let n = new Date()
n = n.getHours() + n.getMinutes() / 60
let i = 1;
while (i < loadTime.getBlockLength() && n > loadTime.getStartV(i) + loadTime.getLastV(i)) {
i++
}
console.log('nblock', i)
if (i > loadTime.getBlockLength() + 1) {
this.nBlock = null
} else {
let s = loadTime.countBlock(i) + 1
let e = loadTime.countBlockAtEnd(i) + 1
this.nBlock = {
sb: s,
eb: e
}
}
},
RefreshWeek(idx) {
console.log('init:' + idx)
let res = {
weekdayShow: [],
courses: [],
week: -1
}
res.weekdayShow = this.buildWeekdayShow(idx)
res.courses = this.GetBlockCourse(idx)
res.week = idx
console.log(res)
return res
},
GetBlockCourse(week) {
let weekShow = new Array(7)
for (let i = 0; i < 7; i++) {
weekShow[i] = new Array(loadTime.getBlockLength())
}
let res = []
const blockHeight = $('#courseView').height() / (this.rowNum);
this.courses.courses.forEach((day, idx) => {
if (dayToCol[idx] == -1) return;
day.forEach((item, p) => {
let thisWeekOn = week == 0 || utils.CheckWeek(week, item.week)
let nextWeekOn = utils.CheckWeek(week + 1, item.week)
if (!thisWeekOn && (!nextWeekOn || !this.showNextWeek)) return;
let s = utils.time2num_float(item.s)
let l = utils.time2num_float(item.l)
let c = {
...item, ...{
s: s, l: l
}
}
let blockRate = [], len = loadTime.getBlockLength() + 1
for (let i = 1; i < len; i++) {
let s_ = Math.max(s, loadTime.getStartV(i))
let e_ = Math.min(s + l, loadTime.getStartV(i) + loadTime.getLastV(i))
blockRate.push((e_ - s_) / loadTime.getLastV(i))
}
if (blockRate.length == 0) return;
blockRate.forEach((rate, i) => {
if (rate >= 0.5) {
if (weekShow[idx][i] == undefined || weekShow[idx][i] == null) {
weekShow[idx][i] = {
s: i,
l: 1,
name: [],
fakeName: [],
items: [],
fakeItems: [],
p: [],
fakeP: [],
teachers: [],
fakeTeachers: [],
places: [],
fakePlaces: [],
color: c.color == undefined ? '#0066ff' : c.color,
textDark: c.textDark ? '#101010' : '#ffffff'
}
}
let t = weekShow[idx][i]
// console.error(JSON.stringify(t))
if (thisWeekOn) {
c.notThisWeek = false
t.name.push(c.name)
t.items.push(c)
t.p.push(p)
if (c.teacher != undefined && c.teacher != '') t.teachers.push(c.teacher)
if (c.place != undefined && c.place != '') t.places.push(c.place)
} else {
c.notThisWeek = true
t.fakeName.push(c.name)
t.fakeItems.push(c)
t.fakeP.push(p)
if (c.teacher != undefined && c.teacher != '') t.fakeTeachers.push(c.teacher)
if (c.place != undefined && c.place != '') t.fakePlaces.push(c.place)
}
}
})
})
for (let i = loadTime.getBlockLength(); i >= 0; i--) {
let cb = weekShow[idx][i]
if (cb != undefined && cb != null && cb.p.length !== 0) {
cb.fakeP = []
}
}
for (let i = loadTime.getBlockLength(); i >= 0; i--) {
let cb = weekShow[idx][i]
if (cb == undefined || cb == null) weekShow[idx].splice(i, 1)
else {
if (i > 0 && weekShow[idx][i - 1] && cb.p.join(' ') + cb.fakeP.join(' ') ===
weekShow[idx][i - 1].p.join(' ') + weekShow[idx][i - 1].fakeP.join(' ')) {
weekShow[idx][i - 1].l += cb.l
weekShow[idx].splice(i, 1)
continue
}
cb.sb = loadTime.countBlock(cb.s + 1)
cb.eb = loadTime.countBlockAtEnd(cb.s + cb.l)
let l = cb.eb - cb.sb
if (cb.name.length === 0) {
cb.name = cb.fakeName
cb.items = cb.fakeItems
cb.p = cb.fakeP
cb.teachers = cb.fakeTeachers
cb.places = cb.fakePlaces
cb.color = '#C0808080'
cb.textDark = '#d0d0d0'
}
cb.name = cb.name.join(' && ')
cb.teachers = cb.teachers.join(',')
cb.places = cb.places.join(',')
if (this.concatPlace && this.showPlace && cb.places !== '') {
cb.name += '' + cb.places
}
cb.sT = this.showTeacher && cb.teachers !== ''
cb.sP = this.showPlace && !this.concatPlace && cb.places !== ''
cb.realDay = idx
cb.day = dayToCol[idx] + 1
if (l * blockHeight < 15) {
cb.name = '...'
}
res.push(cb)
}
}
})
return res
},
buildTimeShow() {
let ts = []
for (let i = 0; i < timeCombine.combine.length; i++) {
let blocks = timeCombine.combine[i]
let s = loadTime.countBlock(blocks[0]) + 1
let e = loadTime.countBlockAtEnd(blocks[blocks.length - 1]) + 1
let st = loadTime.getStart(blocks[0])
let et = utils.num2time_float(loadTime.getStartV(blocks[blocks.length - 1]) + loadTime.getLastV(blocks[blocks.length - 1]))
let name = ''
if (blocks.length === 1) {
name = loadTime.getName(blocks[0])
} else {
name = loadTime.getName(blocks[0]) + '-' + loadTime.getName(blocks[blocks.length - 1])
}
ts.push({
text: name + '\n' + st + '\n' + et,
s: s,
e: e,
color: i % 2 === 0 ? '#00000000' : '#80808010'
})
}
console.log(ts)
this.timesShow = ts
},
buildWeekdayShow(week) {
let n = new Date(this.startDate.getTime())
n.setDate(n.getDate() + (week - 1) * 7)
let ws = []
for (let i = 0; i < 7; i++) {
let weekday = this.startForSunday ? (i == 0 ? 6 : i - 1) : i
if (!showWeekend && weekday > 4) continue;
ws.push({
text: utils.wcn[weekday],
day: utils.fill(n.getMonth() + 1) + '/' + utils.fill(n.getDate())
})
n.setDate(n.getDate() + 1)
}
return ws
},
async readCourseName() {
return new Promise(resolve => {
set_storage.get('nCourse').then(data => {
if (data.nCourse == undefined) {
storage.keys().then(keys => {
if (keys.length == 0) {
alert('没有课程表')
this.courseName = null
this.closeTab = false
} else {
this.courseName = keys[0]
set_storage.set('nCourse', this.courseName)
}
resolve()
})
} else {
this.courseName = data.nCourse
resolve()
}
})
})
},
async readCourse() {
return new Promise(resolve => {
if (this.courseName == null) {
resolve()
return;
}
storage.get(this.courseName).then(async data => {
console.log(data)
if (data[this.courseName] == undefined) {
await set_storage.del('nCourse')
await this.readCourseName()
await this.readCourse()
resolve()
} else {
this.courses = JSON.parse(decode(data[this.courseName]))
let uid = (this.courses.timeTable && this.courses.timeTable.uid) ? this.courses.timeTable.uid : null
console.log(this.courses)
let res = (await TimeTable.loadTimeTable(uid))
loadTime = res[0]
timeCombine = new TimeCombine(res[1])
timeCombine.fixTimeCombine(loadTime.getBlockLength() + 1)
this.getRowNum()
this.buildTimeShow()
this.getNowBlock()
resolve()
}
})
})
},
weekChange(nowIndex) {
console.log(nowIndex)
this.addFloatShow = false
this.floatParam = null
let o = swiperManager.current[1]
let n = nowIndex
if (o == n) return;
let t = n - o
if (t < 0) t += swiperManager.getCacheSize()
let nt = new Date().getTime()
if (t == 1) // scroll right
{
if (swiperManager.scrollRight()) this.pages.push()
}
else if (t == swiperManager.getCacheSize() - 1) // scroll left
{
if (swiperManager.scrollLeft()) this.pages.push()
}
swiperManager.printCurrent()
this.showingWeek = swiperManager.current[0]
// this.RefreshNowTime()
this.$nextTick(() => {
this.swiper.update()
this.swiper.reLoop()
})
console.log('load...' + (new Date().getTime() - nt))
},
loadCourse() {
this.floatParam = null
this.addFloatShow = false
this.startDate = utils.parseDate(this.courses.start)
this.startForSunday = this.startDate.getDay() == 0
if (this.startForSunday) {
dayToCol = [1, 2, 3, 4, 5, 6, 0]
} else {
dayToCol = [0, 1, 2, 3, 4, 5, 6]
}
if (!showWeekend) {
dayToCol[5] = -1
dayToCol[6] = -1
this.columnNum = 5
} else {
this.columnNum = 7
}
let n = new Date()
let weekday = n.getDay()
weekday = weekday == 0 ? 6 : weekday - 1
this.weekday = weekday
this.weekdayLine = this.startForSunday ? (weekday == 6 ? 0 : weekday + 1) + 1 : weekday + 1
n.setDate(n.getDate() - (this.startForSunday ? (weekday == 6 ? 0 : weekday + 1) : weekday))
let betDay = (n.getTime() - this.startDate.getTime()) / 1000 / 60 / 60 / 24
this.realWeek = ((betDay / 7) | 0) + 1
if (this.realWeek < 0) {
this.realWeek = -1
}
this.showingWeek = this.realWeek == -1 ? 1 : this.realWeek
if (swiperManager == null) {
swiperManager = new SwiperManager({
cacheSize: 3,
buffer: this.pages,
buildData: this.RefreshWeek
})
swiperManager.init(this.showingWeek)
} else {
swiperManager.setBuffer(this.pages)
swiperManager.init(this.showingWeek)
}
if (this.swiper) {
this.$nextTick(() => {
this.swiper.update()
this.swiper.reLoop()
this.swiper.slideTo(swiperManager.current[1] + 1)
})
}
},
toNowWeek() {
this.addFloatShow = false
this.floatParam = null
let swiperView = $('#courseView')
swiperView.fadeOut(300, () => {
this.showingWeek = this.realWeek
swiperManager.scrollTo(this.realWeek, true)
swiperView.fadeIn(300)
})
},
closeSelectTable() {
console.log('closeDialog')
document.getElementById('selectTable').close()
},
openChooseTable() {
storage.keys().then(keys => {
this.courseNames = keys
$('#selectTable')[0].showModal()
})
},
async selectTable(name) {
this.closeSelectTable()
this.courseName = name
await this.readCourse()
if (this.courseName == null) {
return;
}
this.loadCourse()
set_storage.set('nCourse', name)
},
chooseItem(item, i) {
if (i == this.showingParam[2]) return;
this.showingParam[2] = i
this.nowEdit[1] = item.p
let course = this.courses.courses[this.nowEdit[0]][this.nowEdit[1]]
this.itemName = course.name
this.itemNameValid = true
this.itemStart = course.s
this.itemLast = course.l
this.itemWeek = course.week.join(',')
this.itemTeacher = course.teacher || ''
this.itemPlace = course.place || ''
this.itemLock = course.lock || false
this.itemDay = day
},
itemCheckboxDialog_itemCheck(e) {
let v = e.currentTarget.value
this.itemCheckboxDialog_items[parseInt(v)].checked = e.currentTarget.checked
console.log(this.itemCheckboxDialog_items)
},
itemCheckboxDialog_finish() {
document.getElementById('ItemCheckboxDialog').close()
if (this.platformNext === 0) {
this.loadFromCloud(platFormIsApp, this.itemCheckboxDialog_items)
} else {
this.uploadToCloud(platFormIsApp, this.itemCheckboxDialog_items)
}
},
itemCheckboxDialog_cancel() {
document.getElementById('ItemCheckboxDialog').close()
},
openPlatformChooseDialog(type) {
this.platformNext = type
document.getElementById('PlatformChooseDialog').showModal()
},
choosePlatform(isApp) {
document.getElementById('PlatformChooseDialog').close()
platFormIsApp = isApp
if (this.platformNext === 0) {
this.itemCheckboxDialog_title = '请选择要下载的课表'
this.loading(true)
checkCloud(isApp, res => {
if (res.code == 0) {
console.log('success')
Hw_getFilesFromCloud(isApp, 'Course', (items) => {
this.itemCheckboxDialog_items = items.map(item => {
return {
name: item.fileName.split('-')[1],
id: item.id,
checked: false
}
})
this.loading(false)
document.getElementById('ItemCheckboxDialog').showModal()
})
} else {
alert(res.error)
this.loading(false)
}
}, false)
} else {
this.itemCheckboxDialog_title = '请选择要上传的课表'
storage.keys().then(keys => {
this.itemCheckboxDialog_items = keys.map(v => {
return {
name: v,
checked: false
}
})
document.getElementById('ItemCheckboxDialog').showModal()
})
}
},
PlatformChooseDialog_cancel() {
document.getElementById('PlatformChooseDialog').close()
},
loadFromCloud(isApp, items) {
Toast.show('导入中...')
this.loading(true)
Hw_loadFromCloud(isApp, items, storage, () => {
alert('导入完成')
this.loading(false)
})
},
uploadToCloud(isApp, items) {
checkCloud(isApp, res => {
if (res.code == 0) {
Toast.show('上传中...')
let promises = []
items.forEach(item => {
if (item.checked) {
promises.push(new Promise(resolve => {
storage.get(item.name).then(data => {
item.data = data[item.name]
resolve()
}).catch(e => {
resolve()
})
}))
}
})
Promise.all(promises).then(() => {
this.loading(true)
Hw_uploadToCloud(isApp, items, (success) => {
if (success) {
alert('上传完成')
} else {
alert('部分课表上传失败')
}
this.loading(false)
})
})
} else {
alert(res.error)
}
})
},
openWeekChooseDialog() {
let items = []
for (let i = 0; i < 30; i++) {
items.push({
week: `第${i + 1}周`
})
}
this.weekChooseDialog_items = items
document.getElementById('WeekChooseDialog').showModal()
},
weekChooseDialog_cancel() {
document.getElementById('WeekChooseDialog').close()
},
toWeek(week) {
document.getElementById('WeekChooseDialog').close()
this.addFloatShow = false
this.floatParam = null
let swiperView = $('#courseView')
swiperView.fadeOut(300, () => {
this.showingWeek = week + 1
swiperManager.scrollTo(week + 1, true)
swiperView.fadeIn(300)
})
},
toSetting() {
window.location.href = 'Setting.html'
},
createTable() {
storage.keys().then(keys => {
let i = 1
while (keys.indexOf('课程表' + i) != -1) {
i++
}
this.newCourseName = '课程表' + i
let date = new Date()
date.setMonth(0, 1)
let day = date.getDay()
date.setDate(1 + (7 - (day == 0 ? 6 : day - 1)) % 7)
this.newCourseDate = date.getFullYear() + '-' + utils.fill(date.getMonth() + 1) + '-' + utils.fill(date.getDate())
document.getElementById('selectTable').close()
document.getElementById('NewTableDialog').showModal()
})
},
newCourseDateChange(e) {
let v = e.currentTarget.value
this.newCourseDate = v
console.log(v)
let r = /^\d{4}-\d{1,2}-\d{1,2}$/
if (r.test(v)) {
this.dateError = false
} else {
this.dateError = true
return
}
let p = v.split('-')
p = [parseInt(p[0]), parseInt(p[1]), parseInt(p[2])]
if (p[0] > 0 && p[1] > 0 && p[2] > 0) {
this.dateError = false
} else {
this.dateError = true
return
}
let date = new Date(p[0], p[1] - 1, p[2])
if (date.getDay() < 2) {
this.dateError = false
} else {
this.dateError = true
return
}
},
createTableConfirm() {
if (this.dateError) {
alert('请检查上述信息正确')
} else {
let nc = {
name: this.newCourseName,
start: this.newCourseDate,
courses: [[], [], [], [], [], [], []],
timeTable: null
}
let data = encode(JSON.stringify(nc))
storage.set(nc.name, data).then(async () => {
document.getElementById('NewTableDialog').close()
set_storage.set('nCourse', nc.name).then(() => {
window.location.reload()
})
})
}
},
closeNewTableDialog() {
document.getElementById('NewTableDialog').close()
},
wantToAddCourse() {
this.itemName = ''
this.itemNameValid = false
this.itemTeacher = ''
this.itemPlace = ''
this.itemWeek = ''
this.itemDay = dayToCol.indexOf(this.floatParam.day)
this.itemStart = loadTime.getStart(this.floatParam.sBlock)
this.itemLast = utils.num2time_float(loadTime.getStartV(this.floatParam.eBlock) - loadTime.getStartV(this.floatParam.sBlock) + loadTime.getLastV(this.floatParam.eBlock))
this.selectItems = null
this.showItems = false
this.nowEdit = null
this.showingParam = null
},
changeItemDay(i) {
this.itemDay = i
if (this.nowEdit != null) {
this.saveAble = true
}
},
changeItem(key, e) {
this[key] = e.currentTarget.value
this[key + 'Valid'] = e.currentTarget.validity.valid
if (key == 'itemName' && e.currentTarget.value == '') {
this.itemNameValid = false
}
if (this.nowEdit != null) {
this.saveAble = true
}
},
save() {
let data = encode(JSON.stringify(this.courses))
storage.set(this.courses.name, data).then(async () => {
swiperManager.refresh()
})
},
addCourse() {
if (this.itemNameValid & this.itemStartValid & this.itemLastValid & this.itemWeekValid) {
let weeks = getWeek(this.itemWeek)
if (typeof weeks == 'string') {
Toast.show(weeks)
return;
}
let sv = utils.time2num_float(this.itemStart)
let lv = utils.time2num_float(this.itemLast)
if (sv + lv > 24) {
Toast.show('该课程时长已超过24点')
return;
}
let p = push(this.courses.courses[this.itemDay], {
name: this.itemName,
s: this.itemStart,
l: this.itemLast,
week: weeks,
teacher: this.itemTeacher,
place: this.itemPlace,
lock: this.itemLock
}, (c1, c2) => {
return utils.time2num(c1.s) - utils.time2num(c2.s)
})
this.save()
this.nowEdit = [this.itemDay, p]
this.addFloatShow = false
this.floatParam = null
} else {
alert('请正确填写信息')
}
},
saveCourse() {
let weeks = getWeek(this.itemWeek)
if (typeof weeks == 'string') {
alert(weeks)
return;
}
let sv = utils.time2num_float(this.itemStart)
let lv = utils.time2num_float(this.itemLast)
if (sv + lv > 24) {
Toast.show('该课程时长已超过24点')
return;
}
let original = this.courses.courses[this.nowEdit[0]][this.nowEdit[1]]
let newOne = {
name: this.itemName,
s: this.itemStart,
l: this.itemLast,
week: weeks,
teacher: this.itemTeacher,
place: this.itemPlace,
lock: this.itemLock
}
this.courses.courses[this.nowEdit[0]].splice(this.nowEdit[1], 1)
let p = push(this.courses.courses[this.itemDay], { ...original, ...newOne }, (c1, c2) => {
return utils.time2num(c1.s) - utils.time2num(c2.s)
})
this.save()
this.saveAble = false
this.showingParam = null
this.nowEdit = [this.itemDay, p]
},
deleteCourse() {
if (confirm('确定删除该课程吗?')) {
this.courses.courses[this.nowEdit[0]].splice(this.nowEdit[1], 1)
this.nowEdit = null
this.save()
this.saveAble = false
this.showingParam = null
this.itemName = ''
this.itemNameValid = false
this.itemTeacher = ''
this.itemPlace = ''
this.itemWeek = ''
}
},
deleteTable(name) {
if (this.courseNames.length == 1) {
alert('这是最后一个课表了')
return;
}
if (confirm(`确定删除课程表[${name}]吗?`)) {
storage.del(name)
if (name == this.courseName) {
this.readCourseName().then(() => {
this.readCourse().then(() => {
this.loadCourse()
})
})
}
}
document.getElementById('selectTable').close()
},
downloadCourse(e) {
const file = new File([JSON.stringify(this.courses)], this.courseName + '.cos', {
type: 'text/plain'
})
const url = URL.createObjectURL(file)
let a = document.createElement('a')
a.href = url
a.download = this.courseName + '.cos'
a.style = 'display: none'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
Toast.show('创建下载中...', 500)
},
createQrCode() {
let data = hexEncode(pako.deflate(JSON.stringify(this.courses)))
let str = 'onecourse://com.example.course/scan?type=course&data=' + data
document.getElementById('qrcodeDialog').showModal()
$('#qrcode')[0].innerHTML = ''
$('#qrcode').qrcode({
text: str,
width: 400,
height: 400
})
},
closeQrcodeDialog() {
document.getElementById('qrcodeDialog').close()
},
moveTab(state) {
if (state == undefined) {
this.closeTab = !this.closeTab
} else if (state != this.closeTab) {
this.closeTab = state
}
window.localStorage.setItem('closeTab', this.closeTab)
},
async zipAll() {
let r = confirm('这会打包所有课表以及时间表等数据为一个压缩包,用于一课表快应用导入,是否继续')
if (!r) return;
let res = {
course: {},
set: {},
timeTable: {}
}
let keys = await storage.keys()
for (let key of keys) {
let data = await storage.get(key)
let text = decode(data[key])
res.course[key] = text
}
res.set = await set_storage.all()
let time_storage = utils.getStorage({
priority: ['indexeddb'],
name: 'Time'
})
res.timeTable = await time_storage.all()
saveAs(new File([JSON.stringify(res)], 'all.onecourse', { type: 'text/plain' }), 'all.onecourse');
}
}
})
</script>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
}
::-webkit-scrollbar {
display: none;
}
div,
button {
display: flex;
box-sizing: border-box;
user-select: none;
}
p {
padding: 0;
margin: 0;
display: -webkit-box;
-webkit-box-orient: vertical;
color: var(--textColor)
}
.select {
transition-duration: 200ms;
transition-timing-function: ease-in;
}
.select:hover {
transform: scale(0.95);
}
.select:active {
transform: scale(0.9);
}
.swiper {
width: 100%;
height: 300px;
}
.clickEffect {
transition: transform 300ms;
}
.clickEffect:active {
transform: scale(0.95);
}
input {
padding: 8px 16px;
border-radius: 30px;
outline: none;
border: none;
background-color: #80808020;
flex-grow: 1;
margin-left: 16px;
}
.move {
opacity: 0.5;
transition-duration: 300ms;
}
.move:hover {
opacity: 1;
}
.courseItem>.menu,
.weekItem>.menu {
opacity: 0;
pointer-events: none;
transition: opacity 300ms;
}
.courseItem:hover>.menu,
.weekItem:hover>.menu {
opacity: 1;
pointer-events: auto;
}
.lighten {
background-color: #FFFFFF40;
transition: background-color 300ms;
}
.lighten:hover {
background-color: #FFFFFF80;
}
.weekdayLine {
background-color: #0066ff80;
opacity: 0;
transition: opacity 300ms;
}
.weekdayLine:hover {
opacity: 1;
}
.loading{
animation: roling 800ms linear infinite;
/* transform: rotate(30deg); */
}
@keyframes roling {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</html>
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/ffffffds/htmlcourse.git
git@gitee.com:ffffffds/htmlcourse.git
ffffffds
htmlcourse
HTMLCourse
master

搜索帮助