Compare commits

..

51 Commits

Author SHA1 Message Date
17e6e1421e
fix: filter selected courses
在手機端使用時,「預覽排課」的功能會導致未經點選+號的「預覽課程」被選入

經儲存於資料庫後會導致學分數計算錯誤。

solution: 在前端先篩選出正確的課程再送給後端API儲存
2022-08-30 20:53:55 +08:00
f8d2147fc9
fix: run as non-sudoer 2022-08-29 18:55:20 +08:00
6b80254e0b
feat: systemd service unit file 2022-08-28 23:02:59 +08:00
70d41cc4bc
fix: change URL 2022-07-26 23:19:52 +08:00
1cf4193ecb
fix: merge py file 2022-01-10 13:44:56 +08:00
4485c14503
feat: 自動從 generalCourse.in 讀取資料,修改通識課程分類(#20) 2022-01-10 13:22:01 +08:00
f351e73844
fix: exception handling when timeout (#22) 2021-08-28 23:49:22 +08:00
b6d20406f8
fix: add timeout parameter(issue #22) 2021-08-28 16:17:20 +08:00
snsd0805
8ed796a2a5
docs: 更新爬蟲程式說明 2021-08-23 18:03:01 +08:00
4ea60b6a9e
Merge branch 'master' of github.com:snsd0805/NCNU_Course 2021-08-23 17:55:27 +08:00
bea92a83f0
fix: update notification 2021-08-23 17:55:11 +08:00
856a44ad61
feat: webpage description 2021-08-23 17:49:22 +08:00
ed7bad423c
feat: 顯示連結在table上 2021-07-25 18:16:05 +08:00
d289a7af55
feat: 新增學分數計算 2021-07-25 17:22:02 +08:00
b9c2089514
fix: 修正格式錯誤的時間 2021-07-17 15:14:04 +08:00
acbc3296f2
fix: 更新api.py 的 cors URL 2021-07-16 22:28:02 +08:00
e4ebb36d4e
fix: update output.json 2021-07-16 21:32:47 +08:00
c24681aa1c
fix: update URL 2021-07-16 21:32:29 +08:00
2afa08efce
feat: 新增1101學年度課表資料 2021-07-16 21:11:43 +08:00
c61489ed9c
feat: 修改爬蟲方式 2021-07-16 21:10:58 +08:00
snsd0805
db020f765f
Merge pull request #18 from x3388638/patch-getTime
Adjust getTime & isOK logic
2021-01-24 23:17:29 +08:00
snsd0805
a9e6330cce
Merge pull request #19 from vincentinttsh/fix_delete
Course Detail & use course number to delete
2021-01-24 21:48:52 +08:00
vincentinttsh
d161c10647 use course number and class to delete 2021-01-24 20:13:27 +08:00
vincentinttsh
5dc87e0ffc Course Detail & use course number to delete 2021-01-24 18:14:37 +08:00
YY
bf880ae585 Merge branch 'master' into patch-getTime 2021-01-24 18:03:14 +08:00
YY
6ea1bbf9c0 Fix course conflic logic 2021-01-24 18:02:02 +08:00
4873e9fa66
fix: 在新分頁開啟課程連結(#15) 2021-01-24 17:55:17 +08:00
YY
a3471b4f1d Filter invalid time 2021-01-24 17:51:37 +08:00
snsd0805
fc9b237dcf
Merge pull request #14 from vincentinttsh/fix_pic
去除圖檔的「刪除」按鈕
2021-01-24 17:51:14 +08:00
vincentinttsh
1651428f05 去除圖檔的「刪除」按鈕 2021-01-24 17:49:54 +08:00
vincentinttsh
c9094b465b 去除圖檔的「刪除」按鈕 2021-01-24 17:46:15 +08:00
snsd0805
6d9a9a55db
Merge pull request #16 from x3388638/patch-search-case
Search courses case-insensitively
2021-01-24 17:19:49 +08:00
vincentinttsh
a22a092ebd 去除圖檔的「刪除」按鈕 2021-01-24 17:12:38 +08:00
YY
2875c78b73 Search courses case-insensitively 2021-01-24 16:51:34 +08:00
vincentinttsh
fd4b28c930 去除圖檔的「刪除」按鈕 2021-01-24 02:14:20 +08:00
f5825cc263
fix: 去除courseList的邊線 2021-01-24 02:03:58 +08:00
snsd0805
ce7f79bf1c
Merge pull request #13 from vincentinttsh/dev
前端修改
2021-01-24 02:00:04 +08:00
vincentinttsh
37fd6d5ce5 hope make it look good 2021-01-24 01:28:37 +08:00
snsd0805
d2f34d8a69
Merge pull request #12 from vincentinttsh/fix_js
Fix issue (#9)
2021-01-23 23:21:41 +08:00
vincentinttsh
559272ba6a fix JavaScript error 2021-01-23 23:15:08 +08:00
vincentinttsh
a7daa7cda4 fix JavaScript error 2021-01-23 23:10:27 +08:00
b4d2f5c943
fix: 新增公告&修正chrome瀏覽器無法複製分享網址問題(#10) 2021-01-23 21:37:00 +08:00
dcd83c4aa4
docs: 新增1092通識分類 2021-01-23 21:23:01 +08:00
3dd54e0eb2
docs: 更新README 2021-01-18 22:16:18 +08:00
e863b2dbb7
fix: 修正產生圖檔的錯誤 2021-01-18 22:08:52 +08:00
d3fc3cc4a8
fix: 修正無法取得user id問題 2021-01-18 22:00:31 +08:00
af524311bf
fix: 修正按鈕位置 2021-01-18 21:58:47 +08:00
4ce47a6b6c
fix: typo 2021-01-18 21:53:28 +08:00
427a56eb2e
merge: 新增分享及匯出圖片 2021-01-18 21:47:11 +08:00
2290855b2f
feat: 新增可回傳分享網址的按鈕 2021-01-17 23:48:59 +08:00
9b594d008e
feat: 下載圖檔功能 2021-01-17 18:34:10 +08:00
15 changed files with 564 additions and 277 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.DS_Store

View File

@ -1,7 +1,9 @@
# 暨大排課表
只是一個Vue練習題目[DEMO](https://snsd0805.com/NCNU_Course/),建議用電腦開
![](https://i.imgur.com/Zbyall6.png)
![](https://i.imgur.com/945cpZm.png)
![](https://i.imgur.com/nBISayh.png)
# 題目
暨大生在學期前在苦惱選課的時候都會使用 @x3388638 學長開發的 [自己的課表自己排 2.0](https://github.com/x3388638/KeBiau)
@ -26,15 +28,22 @@
- 可選擇科系、**可篩選通識領域**
- 選課預覽
- 可安排假日課程
- 分享
- 產生專屬連結跟同學分享自己的課表
- 匯出jpg
- 產生課表.jpg放成桌布、印出來永遠不會忘記去上課
# 可能會新增的功能(非常可能不會)
- [x] 儲存
- [ ] 匯出
- [ ] 分享
- [x] 匯出
- [x] 分享
- [ ] 時間為「另訂」,額外處理
- [x] 把版排好(選課框框改成可下拉(才可以同時看到課表))
# 課程爬蟲使用說明
> 因為學校教務系統更新通識分類的部份很慢,因此目前的程式碼已經修改成無法對應「通識課程分類」的版本,
> 實際上線的 data 是依靠「工人智慧」,
> 如果有需要爬取資料,建議使用較舊版本的 python code
安裝所需套件
```

8
api.py
View File

@ -5,12 +5,16 @@ import sqlite3
from flask_cors import CORS
app = Flask(__name__)
CORS(app, resources={r"/.*": {"origins": ["https://snsd0805.com"]}})
CORS(app, resources={r"/.*": {"origins": ["https://course.snsd0805.com"]}})
def facebookAuth(token):
url = "https://graph.facebook.com/v9.0/me?access_token={}"
response = requests.get(url.format(token))
try:
response = requests.get(url.format(token), timeout=5)
except:
return False, None, None
else:
data = json.loads(response.text)
# 若 access code 通過 facebook 驗證

137
generalCourse.in Normal file
View File

@ -0,0 +1,137 @@
department 特色通識—在地實踐
994017
994057
994065
994068
994071
994075
994076
994077
994078
994080
994086
994089
994112
994113
994114
department 特色通識—綠概念
993062
994001
994012
994020
994024
994074
994027
department 特色通識—東南亞
992106
994030
994096
994098
994099
994102
994103
994105
994108
994109
994110
994111
994010
department 自然—生命與科學
993001
993002
993022
993054
993086
993093
993106
993126
993131
993132
993133
993137
993145
993008
department 自然—工程與科技
993023
993052
993055
993060
993064
993075
993116
993156
993157
993013
993066
993111
993120
993143
department 社會—社經與管理
991094
992033
992035
992110
992120
992129
992141
992143
992177
992191
992193
992203
992205
992213
992214
992216
992217
992223
992062
992211
992232
department 社會—法政與教育
984003
992076
992108
992112
992178
992179
992180
992188
992206
992234
992185
department 人文—歷史哲學與文化
991068
991075
991087
991140
991144
991154
991163
991192
991199
991212
992073
992087
992171
994044
department 人文—文學與藝術
460135
991040
991062
991065
991069
991167
991170
991183
991190
991193
991201
991203
991207
991209
991210
991211
992176
991032
991071

View File

@ -4,33 +4,39 @@ import os
import csv
from bs4 import BeautifulSoup as bs
USERNAME = ""
PASSWORD = ""
YEAR = 1111
header = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0',
'Cookie': '輸入登入暨大教務系統後所得到的cookie'
}
session = requests.Session()
mainURL = "https://ccweb.ncnu.edu.tw/student/"
courses = []
generalCourse = []
def getGeneralCourseData(year):
'''
透過年份取得 通識課程分類的csv檔
供後續課程對應
def login(username, password):
global session
response = session.get('https://ccweb.ncnu.edu.tw/student/login.php')
root = bs(response.text, 'html.parser')
loginToken = root.find('input', {'name': 'token'}).get('value')
先儲存到 generalCourse list後續再用 courseID 對應通識分類
'''
# request login page
response = session.post(
"https://ccweb.ncnu.edu.tw/student/login.php",
data={
'token': loginToken,
'modal': '0',
'username': username,
'password': password,
'type': 'a'
}
)
# 教務系統有開放 年度的query
# 但實際操作後似乎僅開放當前學年度
response = requests.get(mainURL+"aspmaker_student_common_rank_courses_viewlist.php?x_studentid=0&z_studentid=LIKE&x_year={}&z_year=%3D&cmd=search&export=csv".format(year), headers=header)
data = response.text
courses = data.split('\r\n')[1:-1]
for course in courses:
course = course.split(',')
generalCourse.append(course)
# 成功的話 return http 302, redirect
if len(response.history)!=0:
return True
else:
return False
def curlDepartmentCourseTable(year):
'''
@ -39,74 +45,94 @@ def curlDepartmentCourseTable(year):
'''
print("取得所有課程資料:")
response = requests.get(mainURL+"aspmaker_course_opened_semester_stat_viewlist.php?x_year={}&recperpage=ALL".format(year), headers=header)
data = response.text
root = bs(data, "html.parser")
# 切換年度,應該是用 cookie 儲存當前閱覽的年份
url = 'https://ccweb6.ncnu.edu.tw/student/aspmaker_course_opened_detail_viewlist.php?cmd=search&t=aspmaker_course_opened_detail_view&z_year=%3D&x_year={}&z_courseid=%3D&x_courseid=&z_cname=LIKE&x_cname=&z_deptid=%3D&x_deptid=&z_division=LIKE&x_division=&z_grade=%3D&x_grade=&z_teachers=LIKE&x_teachers=&z_not_accessible=LIKE&x_not_accessible='
response = session.get(url.format(year))
count = 1
departmentsTR = root.findAll('tr')[1:] # 清除 thead
for tr in departmentsTR:
name = tr.findAll('td')[4].find('span').find('span').string # 取得 科系名稱
link = mainURL + tr.find('a').get('data-url').replace('amp;', '') # 清除不必要符號, 取得 連結
print("擷取{}課程... ({}/{})...".format(name, count, len(departmentsTR)))
count += 1
extractDepartmentCourseTable(name, link) # 透過連結 開始擷取 各科系課程
# 取得 所有課程的 csv
response = session.get('https://ccweb6.ncnu.edu.tw/student/aspmaker_course_opened_detail_viewlist.php?export=csv')
with open("allCourses.csv", "wb") as fp:
fp.write(response.content)
def extractDepartmentCourseTable(departmentName, link):
def extractDepartmentCourseTable(year):
'''
透過各科系連結取得課程資訊
若為通識類別還要跟csv檔資料做對應取得正確通識類別
對應後存取到 output.json
'''
response = requests.get(link, headers=header)
data = response.text
root = bs(data, "html.parser")
with open("allCourses.csv") as fp:
csvData = fp.read()
ans = []
courses = csvData.split('"\n')[1:-1]
for course in courses:
course = course.replace('\n', '.')
# print(course)
data = course[1:].split('","')
courseTR = root.findAll('tr')[1:] # 清除 thead
for tr in courseTR:
courseObj = {}
tds = tr.find_all('td')
courseObj['link'] = mainURL + tds[0].find('a').get('href')
courseObj['year'] = tds[1].find('span').string
courseObj['number'] = tds[2].find('span').string
courseObj['class'] = tds[3].find('span').string
courseObj['name'] = tds[4].find('span').string
courseObj['department'] = tds[5].find('span').string
courseObj['graduated'] = tds[6].find('span').string
courseObj['grade'] = tds[7].find('span').string
courseObj['teacher'] = tds[8].find('span').string
courseObj['place'] = tds[9].find('span').string
courseObj['time'] = tds[11].find('span').string
baseLink = "https://ccweb6.ncnu.edu.tw/student/aspmaker_course_opened_detail_viewlist.php?cmd=search&t=aspmaker_course_opened_detail_view&z_year=%3D&x_year={}&x_courseid={}"
courseObj['link'] = baseLink.format(year, data[1].zfill(6))
courseObj['year'] = data[0]
courseObj['number'] = data[1]
courseObj['class'] = data[2]
courseObj['name'] = data[3]
courseObj['department'] = data[4]
courseObj['graduated'] = data[6]
courseObj['grade'] = data[7]
courseObj['teacher'] = data[8]
courseObj['place'] = data[9]
courseObj['time'] = data[13].replace(' ', '')
courseObj['credit'] = data[14]
if courseObj['department']=="99, 通識" :
flag = False
for row in generalCourse:
if row[2] == '"{}"'.format(courseObj['number']):
courseObj['department'] = row[0].replace('"', '')
generalCourse.remove(row)
flag = True
break
if not flag:
print(" - 找不到對應的通識類別: {} ( {} )".format(courseObj['name'], courseObj['number']))
ans.append(courseObj)
courses.append(courseObj)
with open("歷年課程資料/{}_output.json".format(year), 'w') as fp:
json.dump(ans, fp, ensure_ascii=False)
def updateGeneralCourse():
with open("歷年課程資料/{}_output.json".format(YEAR)) as fp:
courses = json.load(fp)
with open("generalCourse.in") as fp:
line = fp.readline()
while line:
count = 0
line = line.split()
if len(line) == 2:
department = line[1]
else:
for course in courses:
if course['number'] == line[0]:
course['department'] = department
count += 1
if count == 0 and len(line) != 2:
print("{} 可能輸入錯誤 - {}".format(line[0], department))
line = fp.readline()
print("還沒有對應到的課程:")
for course in courses:
if course['department'] == "99, 通識":
course['department'] = "99, 通識(未分類)"
print("{} {}".format(course['number'], course['name']))
with open("歷年課程資料/{}_output.json".format(YEAR), "w") as fp:
json.dump(courses, fp, ensure_ascii=False)
with open('output.json', 'w') as fp:
json.dump(courses, fp)
if __name__ == "__main__":
year = input("年份: ")
while True:
username = USERNAME
password = PASSWORD
if login(username, password):
print("登入成功!")
break
else:
print("登入失敗!")
getGeneralCourseData(year)
curlDepartmentCourseTable(year)
print("\n\n=====================")
print("未列入追蹤的通識課程")
print("=====================\n")
for notIn in generalCourse:
if "體育:" not in notIn[5]:
print(" - 未列入追蹤的新通識課程: {}".format(notIn))
curlDepartmentCourseTable(YEAR)
extractDepartmentCourseTable(YEAR)
updateGeneralCourse()

View File

@ -3,8 +3,8 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<meta name="description" content="你還在用紙筆或Excel在安排下學期的課表嗎「暨大排課表」幫你篩選衝堂、科系分類、通識課程分類讓你輕鬆排課表" />
<meta name="author" content="snsd0805" />
<title>暨大排課表</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" href="assets/img/favicon.ico" />
@ -28,6 +28,8 @@
<div class="container"><small>Copyright © 暨大排課表 2020</small></div>
</div>
<!-- html2canvas -->
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
<!-- Bootstrap core JS-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js"></script>

View File

@ -19,10 +19,15 @@ var chooseDepartment = {
},
template: `
<div class="mx-auto">
<div>
<h5>1. 課程名稱直接搜尋</h5>
<input class="form-control" type='text' v-model='initFounded'>
<br><br><br>
</div>
<div class="text-center my-2">
<h5></h5>
</div>
<div>
<h5>1. 選擇類別</h5>
<select class="custom-select mr-sm-2" v-model="initSelect">
<option v-for="(item, index) in departments" :key="index"
@ -31,7 +36,7 @@ var chooseDepartment = {
{{ item }}
</option>
</select>
</div>
</div>
`,
}

View File

@ -8,35 +8,25 @@ var coursesList = {
},
methods: {
'getTime': function (timeString) {
if(timeString==null){
return ""
let num;
const timeRegex = new RegExp(/^\d[\da-z]*[a-z]$/);
return timeRegex.test(timeString)
? [...timeString].reduce((res, c) => {
if (Number.isInteger(+c)) {
num = c;
return res;
} else {
return [...res, num + c];
}
ans = []
number = ""
for(var i of timeString){
if(i>="0" && i<="9"){
number = i
}else if(i>="a" && i<="z"){
ans.push(number+i)
}
else{
ans.push(timeString)
break
}
}
return ans
}, [])
: [];
},
'isOK': function (course) {
var time = this.getTime(course.time)
// console.log(course.name, " ", time)
for(t of time){
for(st of this.selectedTime){
if(t==st)
return false
}
}
return true
const isConflict = time.some((t) => this.selectedTime.includes(t))
return time.length && !isConflict
},
'log': function (name, data) {
console.log(name, data)
@ -53,17 +43,12 @@ var coursesList = {
this.selectedTime = temp
},
'find_name': function () {
var temp = []
for(var c of this.courses){
if(c.name.indexOf(this.find_name) != -1){
temp.push(c)
}
}
this.foundedCourses = temp
const target = this.find_name.toLowerCase();
this.foundedCourses = this.courses.filter((c) => c.name.toLowerCase().includes(target));
}
},
template: `
<div>
<div class="mx-auto mb-4">
<h5>2. 安排課程</h5>
<p style="color: orange" v-if="find_name"> 已套用名稱搜尋 <br>{{find_name}}</p>
<div style="width:275px;height:500px;overflow:auto">
@ -72,22 +57,20 @@ var coursesList = {
<tr v-for="(course, index) in foundedCourses" :key="index"
v-on:mouseenter="$emit('show-temp', course)" v-on:mouseleave="$emit('delete-temp', course)">
<td>
<div class="container">
<div class="row">
<b>{{ course.name }} (<a v-bind:href="course.link"></a>)</b>
<div class="container row py-2 px-0">
<div class="col-12 pr-1">
<b>{{ course.name }} (<a v-bind:href="course.link" target="_blank"></a>)</b>
{{ (course.department.indexOf(', ')!=-1) ?(course.department.split(', ')[1]) :(course.department) }}
</div>
<div class="row">
<div class="col-sm-8">
<div class="col-sm-8 pr-1">
{{ course.teacher }} {{ course.time }}
</div>
<div class="col-sm-4">
<div class="col-sm-4 pr-1">
<button v-if="isOK(course)" type="button" v-on:click="$emit('add-course', course)" class="btn btn-primary">
<span>&#43;</span>
</button>
</div>
</div>
</div>
</td>
</tr>
</template>
@ -96,21 +79,19 @@ var coursesList = {
v-if="course.department == selected_d"
v-on:mouseenter="$emit('show-temp', course)" v-on:mouseleave="$emit('delete-temp', course)">
<td>
<div class="container">
<div class="row">
<b>{{ course.name }} (<a v-bind:href="course.link"></a>)</b>
<div class="container row py-2 px-0">
<div class="col-12 pr-1">
<b>{{ course.name }} (<a v-bind:href="course.link" target="_blank"></a>)</b>
</div>
<div class="row">
<div class="col-sm-8">
<div class="col-sm-8 pr-1">
{{ course.teacher }} {{ course.time }}
</div>
<div class="col-sm-4">
<div class="col-sm-4 pr-1">
<button v-if="isOK(course)" type="button" v-on:click="$emit('add-course', course)" class="btn btn-primary">
<span>&#43;</span>
</button>
</div>
</div>
</div>
</td>
</tr>
</template>

View File

@ -7,7 +7,9 @@ var mainWindow = {
'selectDepartment': '',
'foundName': "",
"user": "",
'token': ""
'token': "",
'is_print': false,
'creditNum': 0,
}
},
created() {
@ -49,6 +51,18 @@ var mainWindow = {
main.departments.sort()
main.selectDepartment = main.departments[15]
})
// Collapse Navbar
var navbarCollapse = function () {
if ($("#mainNav").offset().top > 100) {
$("#mainNav").addClass("navbar-shrink");
} else {
$("#mainNav").removeClass("navbar-shrink");
}
};
// Collapse now if page is not at top
navbarCollapse();
// Collapse the navbar when page is scrolled
$(window).scroll(navbarCollapse);
},
methods: {
'login': function () {
@ -75,7 +89,7 @@ var mainWindow = {
this.token = response.authResponse.accessToken
var main = this
FB.api('/me', function(response){main.user = response.name})
FB.api('/me', function (response) { main.user = response })
fetch('https://api.snsd0805.com/courseTable?token=' + this.token)
.then(function (response) {
@ -83,6 +97,14 @@ var mainWindow = {
}).then(function (jsonData) {
console.log(jsonData)
main.selectCourses = JSON.parse(jsonData['data'])
var courseSet = new Set()
for (var course of main.selectCourses) {
if (!courseSet.has(course.number+course.class)) { // courseID +
main.creditNum += parseFloat(course.credit)
courseSet.add(course)
}
}
})
.catch(function (err) {
alert("錯誤: " + err)
@ -92,6 +114,12 @@ var mainWindow = {
'saveCourseTable': function () {
var main = this
if (this.token != "") {
filteredCourses = []
for(var tempCourse of main.selectCourses){
if(tempCourse.temp == false){
filteredCourses.push(tempCourse);
}
}
fetch('https://api.snsd0805.com/courseTable', {
method: 'POST',
headers: {
@ -99,7 +127,7 @@ var mainWindow = {
},
body: JSON.stringify({
'token': main.token,
'data': main.selectCourses
'data': filteredCourses
})
})
.then(function (response) {
@ -121,20 +149,18 @@ var mainWindow = {
}
},
'getTime': function (timeString) {
ans = []
number = ""
for(var i of timeString){
if(i>="0" && i<="9"){
number = i
}else if(i>="a" && i<="z"){
ans.push(number+i)
let num;
const timeRegex = new RegExp(/^\d[\da-z]*[a-z]$/);
return timeRegex.test(timeString)
? [...timeString].reduce((res, c) => {
if (Number.isInteger(+c)) {
num = c;
return res;
} else {
return [...res, num + c];
}
else{
ans.push(timeString)
break
}
}
return ans
}, [])
: [];
},
'select': function (department) {
this.selectDepartment = department
@ -148,17 +174,23 @@ var mainWindow = {
this.selectCourses.push({
'time': t,
'name': course.name,
'temp': false
'temp': false,
'number': course.number,
'class': course.class,
'credit': course.credit,
'link': course.link
})
}
this.creditNum += parseFloat(course.credit)
},
'removeCourse': function (course) {
console.log("remove "+course)
console.log("remove " + course.name)
for (var i = this.selectCourses.length - 1; i >= 0; i--) {
if(this.selectCourses[i].name == course){
if (this.selectCourses[i].number === course.number && this.selectCourses[i].class === course.class) {
this.selectCourses.splice(i, 1)
}
}
this.creditNum -= parseFloat(course.credit)
},
'saveTemp': function (course) {
if (course == null) {
@ -181,6 +213,31 @@ var mainWindow = {
}
}
},
'generatePic': function () {
var main = this
const doPrint = new Promise((resolve, reject) => {
main.is_print = true;
resolve();
});
doPrint
.then(() =>{
html2canvas(document.getElementById('course-table-div')).then(function (canvas) {
var a = document.createElement('a');
a.href = canvas.toDataURL("image/jpeg").replace("image/jpeg", "image/octet-stream");
a.download = '課表.jpg';
a.click();
});
})
.then(() => {
main.is_print = false;
})
},
'share': function () {
if (this.user != "")
$('#share').modal('show');
else
this.login()
},
},
components: {
'course-table': courseTable,
@ -202,7 +259,7 @@ var mainWindow = {
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded js-scroll-trigger" href="https://github.com/snsd0805/NCNU_Course">Github</a></li>
<li v-if="token==''" class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded js-scroll-trigger" href='#' v-on:click="login()">Facebook登入</a></li>
<li v-if="token!=''" class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded js-scroll-trigger" href="#" v-on:click="logout()">登出Facebook{{user}}</a></li>
<li v-if="token!=''" class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded js-scroll-trigger" href="#" v-on:click="logout()">登出Facebook{{user.name}}</a></li>
</ul>
</div>
@ -222,21 +279,33 @@ var mainWindow = {
<div class="divider-custom-line"></div>
</div>
<div class="divider-custom">
<div v-if="token!=''"><button class="btn btn-info" @click="saveCourseTable()">儲存</button></div>
<div v-if="token==''"><button class="btn btn-info" @click="saveCourseTable()">儲存(必須登入Facebook帳號)</button></div>
<div class="row">
<div class="col-4">
<div v-if="token!=''"><button class="btn btn-danger" @click="saveCourseTable()">儲存</button></div>
<div v-if="token==''"><button class="btn btn-danger" @click="saveCourseTable()">儲存(登入FB)</button></div>
</div>
<div class="col-4">
<div><button class="btn btn-success" @click="generatePic()">下載圖檔</button></div>
</div>
<div class="col-4">
<div><button class="btn btn-primary" @click="share()">分享課表</button></div>
</div>
</div>
</div>
<br>
<div class="row">
<div class="col-lg-3">
<div class="row">
<div class="row mx-auto mb-2">
<choose-department
v-bind:departments="departments"
v-bind:selected="selectDepartment"
v-on:selectok="select"
v-on:foundedok="founded"
>
</choose-department><br>
</div><br><br>
<div class="row">
</choose-department>
</div>
<br>
<div class="row mx-auto mb-2">
<course-anslist
v-bind:courses="courses"
v-bind:selected_d="selectDepartment"
@ -247,13 +316,26 @@ var mainWindow = {
v-on:delete-temp="deleteTemp"
>
</course-anslist>
</div><br><br>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-body">
已經選了 {{ creditNum }} 學分
</div>
</div>
</div>
</div>
</div>
<br>
<div class="col-lg-9 table-responsive " >
<course-table
id="course-table-div"
v-bind:selectCourses="selectCourses"
v-bind:select_c="selectCourses"
v-bind:is_print="is_print"
v-bind:is_shared="false"
v-on:remove-course="removeCourse"
></course-table>
@ -274,9 +356,32 @@ var mainWindow = {
</button>
</div>
<div class="modal-body">
已經更新為 1092 新課表<br>
但因學校未更新通識課資料因此還沒有通識課程分類<br><br>
2021 01/14 更新
<ul>
<li>已經更新為 1101 新學期課表(包含通識課分類)</li>
<li>有發現 Bug 可以到 <a href='https://github.com/snsd0805/NCNU_Course/issues'>GitHub</a> issue <a href='mailto: levi900227@gmail.com'>mail</a></li>
<li>請善用連接 Facebook功能來儲存課表</li>
</ul>
2021 07/16 更新
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">我知道了</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="share" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">分享課表</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
請複製以下網址給你的朋友跟他分享你的課表<br><br>
https://course.snsd0805.com/#/share/{{user.id}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">我知道了</button>

View File

@ -1,12 +1,14 @@
var courseDiv = {
props: ['course', 'is_shared'],
props: ['course', 'is_shared', 'is_print'],
template: `
<div style='border: 5px #1abc9c solid; text-align: center;'>
{{ course.name }}
<a v-bind:href="course.link" target="_blank"><i class="fas fa-info-circle"></i></a>
<button type="button"
v-if="!is_shared"
v-on:click="$emit('remove-course', course.name)"
v-on:click="$emit('remove-course', course)"
class="btn btn-danger btn-sm"
:style="{'display': is_print ? 'none' : 'inline-block'}"
>
</button>
@ -22,7 +24,7 @@ var tempDiv = {
`
}
var courseTable = {
props: ['select_c', 'is_shared'],
props: ['select_c', 'is_shared', 'is_print'],
data: function(){
return {
'courses': {},
@ -51,7 +53,11 @@ var courseTable = {
for(var c of this.select_c){
this.courses[c.time] = {
'name': c.name,
'temp': c.temp
'number': c.number,
'class': c.class,
'temp': c.temp,
'credit': c.credit,
'link': c.link
}
if(c.time[0]==6 || c.time[0]==7){
@ -112,6 +118,7 @@ var courseTable = {
v-if="exist(week+String.fromCharCode(97+((hour<5)?(hour-1):(hour-2)))) && !courses[week+String.fromCharCode(97+((hour<5)?(hour-1):(hour-2)))].temp"
v-bind:course="courses[week+String.fromCharCode(97+((hour<5)?(hour-1):(hour-2)))]"
v-bind:is_shared="is_shared"
v-bind:is_print="is_print"
v-on:remove-course="removeCourseHandler"
></course-div>
<temp-div

View File

@ -41,18 +41,7 @@
offset: 80
});
// Collapse Navbar
var navbarCollapse = function() {
if ($("#mainNav").offset().top > 100) {
$("#mainNav").addClass("navbar-shrink");
} else {
$("#mainNav").removeClass("navbar-shrink");
}
};
// Collapse now if page is not at top
navbarCollapse();
// Collapse the navbar when page is scrolled
$(window).scroll(navbarCollapse);
// Floating label headings for the contact form
$(function() {

20
ncnu-course-api.service Normal file
View File

@ -0,0 +1,20 @@
[Unit]
Description=NCNU-Course Python Backend API
After=network.target
[Service]
Type=simple
ExecStart=python3 api.py
Restart=always
WorkingDirectory=/var/www/html/NCNU_Course
User=course
RestartSec=10s
StandardOutput=syslog
StandardOutput=syslog
SyslogIdentifier=ncnu-course
[Install]
WantedBy=multi-user.target

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long