Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17e6e1421e | |||
| f8d2147fc9 | |||
| 6b80254e0b | |||
| 70d41cc4bc | |||
| 1cf4193ecb | |||
| 4485c14503 | |||
| f351e73844 | |||
| b6d20406f8 | |||
|
|
8ed796a2a5 | ||
| 4ea60b6a9e | |||
| bea92a83f0 | |||
| 856a44ad61 | |||
| ed7bad423c | |||
| d289a7af55 | |||
| b9c2089514 | |||
| acbc3296f2 | |||
| e4ebb36d4e | |||
| c24681aa1c | |||
| 2afa08efce | |||
| c61489ed9c | |||
|
|
db020f765f | ||
|
|
a9e6330cce | ||
|
|
d161c10647 | ||
|
|
5dc87e0ffc | ||
|
|
bf880ae585 | ||
|
|
6ea1bbf9c0 | ||
| 4873e9fa66 | |||
|
|
a3471b4f1d | ||
|
|
fc9b237dcf | ||
|
|
1651428f05 | ||
|
|
c9094b465b | ||
|
|
6d9a9a55db | ||
|
|
a22a092ebd | ||
|
|
2875c78b73 | ||
|
|
fd4b28c930 | ||
| f5825cc263 | |||
|
|
ce7f79bf1c | ||
|
|
37fd6d5ce5 | ||
|
|
d2f34d8a69 | ||
|
|
559272ba6a | ||
|
|
a7daa7cda4 | ||
| b4d2f5c943 | |||
| dcd83c4aa4 | |||
| 3dd54e0eb2 | |||
| e863b2dbb7 | |||
| d3fc3cc4a8 | |||
| af524311bf | |||
| 4ce47a6b6c | |||
| 427a56eb2e | |||
| 2290855b2f | |||
| 9b594d008e |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.DS_Store
|
||||||
15
README.md
15
README.md
@ -1,7 +1,9 @@
|
|||||||
# 暨大排課表
|
# 暨大排課表
|
||||||
只是一個Vue練習題目,[DEMO](https://snsd0805.com/NCNU_Course/),建議用電腦開
|
只是一個Vue練習題目,[DEMO](https://snsd0805.com/NCNU_Course/),建議用電腦開
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
# 題目
|
# 題目
|
||||||
暨大生在學期前在苦惱選課的時候都會使用 @x3388638 學長開發的 [自己的課表自己排 2.0](https://github.com/x3388638/KeBiau)
|
暨大生在學期前在苦惱選課的時候都會使用 @x3388638 學長開發的 [自己的課表自己排 2.0](https://github.com/x3388638/KeBiau)
|
||||||
@ -26,15 +28,22 @@
|
|||||||
- 可選擇科系、**可篩選通識領域**
|
- 可選擇科系、**可篩選通識領域**
|
||||||
- 選課預覽
|
- 選課預覽
|
||||||
- 可安排假日課程
|
- 可安排假日課程
|
||||||
|
- 分享
|
||||||
|
- 產生專屬連結跟同學分享自己的課表
|
||||||
|
- 匯出jpg
|
||||||
|
- 產生課表.jpg,放成桌布、印出來,永遠不會忘記去上課
|
||||||
|
|
||||||
# 可能會新增的功能(非常可能不會)
|
# 可能會新增的功能(非常可能不會)
|
||||||
- [x] 儲存
|
- [x] 儲存
|
||||||
- [ ] 匯出
|
- [x] 匯出
|
||||||
- [ ] 分享
|
- [x] 分享
|
||||||
- [ ] 時間為「另訂」,額外處理
|
- [ ] 時間為「另訂」,額外處理
|
||||||
- [x] 把版排好(選課框框改成可下拉(才可以同時看到課表))
|
- [x] 把版排好(選課框框改成可下拉(才可以同時看到課表))
|
||||||
|
|
||||||
# 課程爬蟲使用說明
|
# 課程爬蟲使用說明
|
||||||
|
> 因為學校教務系統更新通識分類的部份很慢,因此目前的程式碼已經修改成無法對應「通識課程分類」的版本,
|
||||||
|
> 實際上線的 data 是依靠「工人智慧」,
|
||||||
|
> 如果有需要爬取資料,建議使用較舊版本的 python code
|
||||||
|
|
||||||
安裝所需套件
|
安裝所需套件
|
||||||
```
|
```
|
||||||
|
|||||||
20
api.py
20
api.py
@ -5,19 +5,23 @@ import sqlite3
|
|||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app, resources={r"/.*": {"origins": ["https://snsd0805.com"]}})
|
CORS(app, resources={r"/.*": {"origins": ["https://course.snsd0805.com"]}})
|
||||||
|
|
||||||
def facebookAuth(token):
|
def facebookAuth(token):
|
||||||
url = "https://graph.facebook.com/v9.0/me?access_token={}"
|
url = "https://graph.facebook.com/v9.0/me?access_token={}"
|
||||||
|
|
||||||
response = requests.get(url.format(token))
|
try:
|
||||||
data = json.loads(response.text)
|
response = requests.get(url.format(token), timeout=5)
|
||||||
|
except:
|
||||||
# 若 access code 通過 facebook 驗證
|
|
||||||
if response.status_code == 200:
|
|
||||||
return True, data['id'], data['name']
|
|
||||||
else:
|
|
||||||
return False, None, None
|
return False, None, None
|
||||||
|
else:
|
||||||
|
data = json.loads(response.text)
|
||||||
|
|
||||||
|
# 若 access code 通過 facebook 驗證
|
||||||
|
if response.status_code == 200:
|
||||||
|
return True, data['id'], data['name']
|
||||||
|
else:
|
||||||
|
return False, None, None
|
||||||
|
|
||||||
@app.route('/courseTable', methods=["GET"])
|
@app.route('/courseTable', methods=["GET"])
|
||||||
def get():
|
def get():
|
||||||
|
|||||||
137
generalCourse.in
Normal file
137
generalCourse.in
Normal 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
|
||||||
182
getData.py
182
getData.py
@ -4,33 +4,39 @@ import os
|
|||||||
import csv
|
import csv
|
||||||
from bs4 import BeautifulSoup as bs
|
from bs4 import BeautifulSoup as bs
|
||||||
|
|
||||||
|
USERNAME = ""
|
||||||
|
PASSWORD = ""
|
||||||
|
YEAR = 1111
|
||||||
|
|
||||||
header = {
|
session = requests.Session()
|
||||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0',
|
|
||||||
'Cookie': '輸入登入暨大教務系統後所得到的cookie'
|
|
||||||
}
|
|
||||||
|
|
||||||
mainURL = "https://ccweb.ncnu.edu.tw/student/"
|
mainURL = "https://ccweb.ncnu.edu.tw/student/"
|
||||||
courses = []
|
courses = []
|
||||||
generalCourse = []
|
generalCourse = []
|
||||||
|
|
||||||
def getGeneralCourseData(year):
|
def login(username, password):
|
||||||
'''
|
global session
|
||||||
透過年份取得 通識課程分類的csv檔
|
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 對應通識分類
|
|
||||||
'''
|
|
||||||
|
|
||||||
# 教務系統有開放 年度的query
|
# request login page
|
||||||
# 但實際操作後似乎僅開放當前學年度
|
response = session.post(
|
||||||
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)
|
"https://ccweb.ncnu.edu.tw/student/login.php",
|
||||||
data = response.text
|
data={
|
||||||
|
'token': loginToken,
|
||||||
|
'modal': '0',
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
'type': 'a'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
courses = data.split('\r\n')[1:-1]
|
# 成功的話 return http 302, redirect
|
||||||
for course in courses:
|
if len(response.history)!=0:
|
||||||
course = course.split(',')
|
return True
|
||||||
generalCourse.append(course)
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def curlDepartmentCourseTable(year):
|
def curlDepartmentCourseTable(year):
|
||||||
'''
|
'''
|
||||||
@ -39,74 +45,94 @@ def curlDepartmentCourseTable(year):
|
|||||||
'''
|
'''
|
||||||
print("取得所有課程資料:")
|
print("取得所有課程資料:")
|
||||||
|
|
||||||
response = requests.get(mainURL+"aspmaker_course_opened_semester_stat_viewlist.php?x_year={}&recperpage=ALL".format(year), headers=header)
|
# 切換年度,應該是用 cookie 儲存當前閱覽的年份
|
||||||
data = response.text
|
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='
|
||||||
root = bs(data, "html.parser")
|
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) # 透過連結 開始擷取 各科系課程
|
|
||||||
|
|
||||||
def extractDepartmentCourseTable(departmentName, 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(year):
|
||||||
'''
|
'''
|
||||||
透過各科系連結取得課程資訊
|
透過各科系連結取得課程資訊
|
||||||
若為通識類別還要跟csv檔資料做對應,取得正確通識類別
|
若為通識類別還要跟csv檔資料做對應,取得正確通識類別
|
||||||
|
|
||||||
對應後存取到 output.json
|
對應後存取到 output.json
|
||||||
'''
|
'''
|
||||||
response = requests.get(link, headers=header)
|
with open("allCourses.csv") as fp:
|
||||||
data = response.text
|
csvData = fp.read()
|
||||||
root = bs(data, "html.parser")
|
|
||||||
|
|
||||||
courseTR = root.findAll('tr')[1:] # 清除 thead
|
ans = []
|
||||||
for tr in courseTR:
|
courses = csvData.split('"\n')[1:-1]
|
||||||
courseObj = {}
|
for course in courses:
|
||||||
tds = tr.find_all('td')
|
course = course.replace('\n', '.')
|
||||||
|
# print(course)
|
||||||
courseObj['link'] = mainURL + tds[0].find('a').get('href')
|
data = course[1:].split('","')
|
||||||
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
|
|
||||||
|
|
||||||
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']))
|
|
||||||
|
|
||||||
courses.append(courseObj)
|
courseObj = {}
|
||||||
|
|
||||||
with open('output.json', 'w') as fp:
|
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={}"
|
||||||
json.dump(courses, fp)
|
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]
|
||||||
|
|
||||||
|
ans.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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
year = input("年份: ")
|
while True:
|
||||||
|
username = USERNAME
|
||||||
getGeneralCourseData(year)
|
password = PASSWORD
|
||||||
curlDepartmentCourseTable(year)
|
if login(username, password):
|
||||||
|
print("登入成功!")
|
||||||
print("\n\n=====================")
|
break
|
||||||
print("未列入追蹤的通識課程")
|
else:
|
||||||
print("=====================\n")
|
print("登入失敗!")
|
||||||
|
|
||||||
for notIn in generalCourse:
|
curlDepartmentCourseTable(YEAR)
|
||||||
if "體育:" not in notIn[5]:
|
extractDepartmentCourseTable(YEAR)
|
||||||
print(" - 未列入追蹤的新通識課程: {}".format(notIn))
|
updateGeneralCourse()
|
||||||
@ -3,9 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
<meta name="description" content="" />
|
<meta name="description" content="你還在用紙筆或Excel在安排下學期的課表嗎?「暨大排課表」幫你篩選衝堂、科系分類、通識課程分類,讓你輕鬆排課表!" />
|
||||||
<meta name="author" content="" />
|
<meta name="author" content="snsd0805" />
|
||||||
<title>暨大排課表</title>
|
<title>暨大排課表</title>
|
||||||
<!-- Favicon-->
|
<!-- Favicon-->
|
||||||
<link rel="icon" type="image/x-icon" href="assets/img/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="assets/img/favicon.ico" />
|
||||||
<!-- Font Awesome icons (free version)-->
|
<!-- Font Awesome icons (free version)-->
|
||||||
@ -28,6 +28,8 @@
|
|||||||
<div class="container"><small>Copyright © 暨大排課表 2020</small></div>
|
<div class="container"><small>Copyright © 暨大排課表 2020</small></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- html2canvas -->
|
||||||
|
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
|
||||||
<!-- Bootstrap core JS-->
|
<!-- Bootstrap core JS-->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
<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>
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|||||||
@ -19,19 +19,24 @@ var chooseDepartment = {
|
|||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
|
|
||||||
<div>
|
<div class="mx-auto">
|
||||||
<h5>1. 「課程名稱」直接搜尋</h5>
|
<div>
|
||||||
<input class="form-control" type='text' v-model='initFounded'>
|
<h5>1. 「課程名稱」直接搜尋</h5>
|
||||||
<br>或<br><br>
|
<input class="form-control" type='text' v-model='initFounded'>
|
||||||
<h5>1. 選擇類別</h5>
|
</div>
|
||||||
<select class="custom-select mr-sm-2" v-model="initSelect">
|
<div class="text-center my-2">
|
||||||
<option v-for="(item, index) in departments" :key="index"
|
<h5>或</h5>
|
||||||
v-bind:value="item"
|
</div>
|
||||||
>
|
<div>
|
||||||
{{ item }}
|
<h5>1. 選擇類別</h5>
|
||||||
</option>
|
<select class="custom-select mr-sm-2" v-model="initSelect">
|
||||||
</select>
|
<option v-for="(item, index) in departments" :key="index"
|
||||||
|
v-bind:value="item"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
@ -1,69 +1,54 @@
|
|||||||
var coursesList = {
|
var coursesList = {
|
||||||
props: ['courses', 'selected_d', 'selected_c', 'find_name'],
|
props: ['courses', 'selected_d', 'selected_c', 'find_name'],
|
||||||
data: function(){
|
data: function () {
|
||||||
return {
|
return {
|
||||||
selectedTime: [],
|
selectedTime: [],
|
||||||
foundedCourses: []
|
foundedCourses: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
'getTime': function(timeString){
|
'getTime': function (timeString) {
|
||||||
if(timeString==null){
|
let num;
|
||||||
return ""
|
const timeRegex = new RegExp(/^\d[\da-z]*[a-z]$/);
|
||||||
}
|
return timeRegex.test(timeString)
|
||||||
|
? [...timeString].reduce((res, c) => {
|
||||||
ans = []
|
if (Number.isInteger(+c)) {
|
||||||
number = ""
|
num = c;
|
||||||
for(var i of timeString){
|
return res;
|
||||||
if(i>="0" && i<="9"){
|
} else {
|
||||||
number = i
|
return [...res, num + c];
|
||||||
}else if(i>="a" && i<="z"){
|
}
|
||||||
ans.push(number+i)
|
}, [])
|
||||||
}
|
: [];
|
||||||
else{
|
|
||||||
ans.push(timeString)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ans
|
|
||||||
},
|
},
|
||||||
'isOK': function(course){
|
'isOK': function (course) {
|
||||||
var time = this.getTime(course.time)
|
var time = this.getTime(course.time)
|
||||||
// console.log(course.name, " ", time)
|
// console.log(course.name, " ", time)
|
||||||
for(t of time){
|
const isConflict = time.some((t) => this.selectedTime.includes(t))
|
||||||
for(st of this.selectedTime){
|
|
||||||
if(t==st)
|
return time.length && !isConflict
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
},
|
||||||
'log': function(name, data){
|
'log': function (name, data) {
|
||||||
console.log(name, data)
|
console.log(name, data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'selected_c': function(){
|
'selected_c': function () {
|
||||||
var temp = []
|
var temp = []
|
||||||
for(var c of this.selected_c){
|
for (var c of this.selected_c) {
|
||||||
if(c.temp==false){
|
if (c.temp == false) {
|
||||||
temp.push(c.time)
|
temp.push(c.time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.selectedTime = temp
|
this.selectedTime = temp
|
||||||
},
|
},
|
||||||
'find_name': function(){
|
'find_name': function () {
|
||||||
var temp = []
|
const target = this.find_name.toLowerCase();
|
||||||
for(var c of this.courses){
|
this.foundedCourses = this.courses.filter((c) => c.name.toLowerCase().includes(target));
|
||||||
if(c.name.indexOf(this.find_name) != -1){
|
|
||||||
temp.push(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.foundedCourses = temp
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<div>
|
<div class="mx-auto mb-4">
|
||||||
<h5>2. 安排課程</h5>
|
<h5>2. 安排課程</h5>
|
||||||
<p style="color: orange" v-if="find_name"> ※ 已套用「名稱」搜尋: <br>{{find_name}}</p>
|
<p style="color: orange" v-if="find_name"> ※ 已套用「名稱」搜尋: <br>{{find_name}}</p>
|
||||||
<div style="width:275px;height:500px;overflow:auto">
|
<div style="width:275px;height:500px;overflow:auto">
|
||||||
@ -72,20 +57,18 @@ var coursesList = {
|
|||||||
<tr v-for="(course, index) in foundedCourses" :key="index"
|
<tr v-for="(course, index) in foundedCourses" :key="index"
|
||||||
v-on:mouseenter="$emit('show-temp', course)" v-on:mouseleave="$emit('delete-temp', course)">
|
v-on:mouseenter="$emit('show-temp', course)" v-on:mouseleave="$emit('delete-temp', course)">
|
||||||
<td>
|
<td>
|
||||||
<div class="container">
|
<div class="container row py-2 px-0">
|
||||||
<div class="row">
|
<div class="col-12 pr-1">
|
||||||
<b>{{ course.name }} (<a v-bind:href="course.link">詳</a>)</b>
|
<b>{{ course.name }} (<a v-bind:href="course.link" target="_blank">詳</a>)</b>
|
||||||
—— {{ (course.department.indexOf(', ')!=-1) ?(course.department.split(', ')[1]) :(course.department) }}
|
—— {{ (course.department.indexOf(', ')!=-1) ?(course.department.split(', ')[1]) :(course.department) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="col-sm-8 pr-1">
|
||||||
<div class="col-sm-8">
|
{{ course.teacher }} ‧ {{ course.time }}
|
||||||
{{ course.teacher }} ‧ {{ course.time }}
|
</div>
|
||||||
</div>
|
<div class="col-sm-4 pr-1">
|
||||||
<div class="col-sm-4">
|
<button v-if="isOK(course)" type="button" v-on:click="$emit('add-course', course)" class="btn btn-primary">
|
||||||
<button v-if="isOK(course)" type="button" v-on:click="$emit('add-course', course)" class="btn btn-primary">
|
<span>+</span>
|
||||||
<span>+</span>
|
</button>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -96,21 +79,19 @@ var coursesList = {
|
|||||||
v-if="course.department == selected_d"
|
v-if="course.department == selected_d"
|
||||||
v-on:mouseenter="$emit('show-temp', course)" v-on:mouseleave="$emit('delete-temp', course)">
|
v-on:mouseenter="$emit('show-temp', course)" v-on:mouseleave="$emit('delete-temp', course)">
|
||||||
<td>
|
<td>
|
||||||
<div class="container">
|
<div class="container row py-2 px-0">
|
||||||
<div class="row">
|
<div class="col-12 pr-1">
|
||||||
<b>{{ course.name }} (<a v-bind:href="course.link">詳</a>)</b>
|
<b>{{ course.name }} (<a v-bind:href="course.link" target="_blank">詳</a>)</b>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="col-sm-8 pr-1">
|
||||||
<div class="col-sm-8">
|
{{ course.teacher }} ‧ {{ course.time }}
|
||||||
{{ course.teacher }} ‧ {{ course.time }}
|
</div>
|
||||||
</div>
|
<div class="col-sm-4 pr-1">
|
||||||
<div class="col-sm-4">
|
<button v-if="isOK(course)" type="button" v-on:click="$emit('add-course', course)" class="btn btn-primary">
|
||||||
<button v-if="isOK(course)" type="button" v-on:click="$emit('add-course', course)" class="btn btn-primary">
|
<span>+</span>
|
||||||
<span>+</span>
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
var mainWindow = {
|
var mainWindow = {
|
||||||
data: function(){
|
data: function () {
|
||||||
return {
|
return {
|
||||||
'courses': [],
|
'courses': [],
|
||||||
'selectCourses': [],
|
'selectCourses': [],
|
||||||
@ -7,29 +7,31 @@ var mainWindow = {
|
|||||||
'selectDepartment': '',
|
'selectDepartment': '',
|
||||||
'foundName': "",
|
'foundName': "",
|
||||||
"user": "",
|
"user": "",
|
||||||
'token': ""
|
'token': "",
|
||||||
|
'is_print': false,
|
||||||
|
'creditNum': 0,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
var main = this
|
var main = this
|
||||||
window.fbAsyncInit = function() {
|
window.fbAsyncInit = function () {
|
||||||
FB.init({
|
FB.init({
|
||||||
appId : '',
|
appId: '',
|
||||||
cookie : true,
|
cookie: true,
|
||||||
xfbml : true,
|
xfbml: true,
|
||||||
version : 'v9.0'
|
version: 'v9.0'
|
||||||
});
|
});
|
||||||
|
|
||||||
FB.AppEvents.logPageView();
|
FB.AppEvents.logPageView();
|
||||||
main.getCourseTable()
|
main.getCourseTable()
|
||||||
};
|
};
|
||||||
|
|
||||||
(function(d, s, id){
|
(function (d, s, id) {
|
||||||
var js, fjs = d.getElementsByTagName(s)[0];
|
var js, fjs = d.getElementsByTagName(s)[0];
|
||||||
if (d.getElementById(id)) {return;}
|
if (d.getElementById(id)) { return; }
|
||||||
js = d.createElement(s); js.id = id;
|
js = d.createElement(s); js.id = id;
|
||||||
js.src = "https://connect.facebook.net/en_US/sdk.js";
|
js.src = "https://connect.facebook.net/en_US/sdk.js";
|
||||||
fjs.parentNode.insertBefore(js, fjs);
|
fjs.parentNode.insertBefore(js, fjs);
|
||||||
}(document, 'script', 'facebook-jssdk'));
|
}(document, 'script', 'facebook-jssdk'));
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -37,135 +39,165 @@ var mainWindow = {
|
|||||||
axios
|
axios
|
||||||
.get("./output.json")
|
.get("./output.json")
|
||||||
.then(response => (main.courses = response.data))
|
.then(response => (main.courses = response.data))
|
||||||
.then(function(){
|
.then(function () {
|
||||||
for(var course of main.courses){
|
for (var course of main.courses) {
|
||||||
// console.log(course.name)
|
// console.log(course.name)
|
||||||
if(main.departments.indexOf(course.department)==-1){
|
if (main.departments.indexOf(course.department) == -1) {
|
||||||
main.departments.push(course.department)
|
main.departments.push(course.department)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(function(){
|
.then(function () {
|
||||||
main.departments.sort()
|
main.departments.sort()
|
||||||
main.selectDepartment = main.departments[15]
|
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: {
|
methods: {
|
||||||
'login': function(){
|
'login': function () {
|
||||||
var main = this
|
var main = this
|
||||||
FB.login(function(){
|
FB.login(function () {
|
||||||
main.getCourseTable()
|
main.getCourseTable()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
'logout': function(){
|
'logout': function () {
|
||||||
var main = this
|
var main = this
|
||||||
FB.logout(function(response) {
|
FB.logout(function (response) {
|
||||||
main.user = ""
|
main.user = ""
|
||||||
main.token = ""
|
main.token = ""
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
'getCourseTable': function(){
|
'getCourseTable': function () {
|
||||||
var main = this
|
var main = this
|
||||||
FB.getLoginStatus(function(response) {
|
FB.getLoginStatus(function (response) {
|
||||||
main.statusChangeCallback(response);
|
main.statusChangeCallback(response);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
'statusChangeCallback': function(response){
|
'statusChangeCallback': function (response) {
|
||||||
if(response.status == "connected"){
|
if (response.status == "connected") {
|
||||||
this.token = response.authResponse.accessToken
|
this.token = response.authResponse.accessToken
|
||||||
|
|
||||||
var main = this
|
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)
|
fetch('https://api.snsd0805.com/courseTable?token=' + this.token)
|
||||||
.then(function(response){
|
.then(function (response) {
|
||||||
return response.json()
|
return response.json()
|
||||||
}).then(function(jsonData){
|
}).then(function (jsonData) {
|
||||||
console.log(jsonData)
|
console.log(jsonData)
|
||||||
main.selectCourses = JSON.parse(jsonData['data'])
|
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){
|
.catch(function (err) {
|
||||||
alert("錯誤: "+err)
|
alert("錯誤: " + err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'saveCourseTable': function(){
|
'saveCourseTable': function () {
|
||||||
var main = this
|
var main = this
|
||||||
if(this.token!=""){
|
if (this.token != "") {
|
||||||
fetch('https://api.snsd0805.com/courseTable',{
|
filteredCourses = []
|
||||||
|
for(var tempCourse of main.selectCourses){
|
||||||
|
if(tempCourse.temp == false){
|
||||||
|
filteredCourses.push(tempCourse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetch('https://api.snsd0805.com/courseTable', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
'token': main.token,
|
'token': main.token,
|
||||||
'data': main.selectCourses
|
'data': filteredCourses
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(function(response){
|
.then(function (response) {
|
||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then(function(response){
|
.then(function (response) {
|
||||||
if(response.status=="saved"){
|
if (response.status == "saved") {
|
||||||
alert("已儲存")
|
alert("已儲存")
|
||||||
}else{
|
} else {
|
||||||
alert("錯誤")
|
alert("錯誤")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function(err){
|
.catch(function (err) {
|
||||||
alert("錯誤: "+err)
|
alert("錯誤: " + err)
|
||||||
})
|
})
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
this.login()
|
this.login()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'getTime': function(timeString){
|
'getTime': function (timeString) {
|
||||||
ans = []
|
let num;
|
||||||
number = ""
|
const timeRegex = new RegExp(/^\d[\da-z]*[a-z]$/);
|
||||||
for(var i of timeString){
|
return timeRegex.test(timeString)
|
||||||
if(i>="0" && i<="9"){
|
? [...timeString].reduce((res, c) => {
|
||||||
number = i
|
if (Number.isInteger(+c)) {
|
||||||
}else if(i>="a" && i<="z"){
|
num = c;
|
||||||
ans.push(number+i)
|
return res;
|
||||||
}
|
} else {
|
||||||
else{
|
return [...res, num + c];
|
||||||
ans.push(timeString)
|
}
|
||||||
break
|
}, [])
|
||||||
}
|
: [];
|
||||||
}
|
|
||||||
return ans
|
|
||||||
},
|
},
|
||||||
'select': function(department){
|
'select': function (department) {
|
||||||
this.selectDepartment = department
|
this.selectDepartment = department
|
||||||
},
|
},
|
||||||
'founded': function(courseName){
|
'founded': function (courseName) {
|
||||||
this.foundName = courseName
|
this.foundName = courseName
|
||||||
},
|
},
|
||||||
'addCourse': function(course){
|
'addCourse': function (course) {
|
||||||
var time = this.getTime(course.time)
|
var time = this.getTime(course.time)
|
||||||
for(var t of time){
|
for (var t of time) {
|
||||||
this.selectCourses.push({
|
this.selectCourses.push({
|
||||||
'time': t,
|
'time': t,
|
||||||
'name': course.name,
|
'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){
|
'removeCourse': function (course) {
|
||||||
console.log("remove "+course)
|
console.log("remove " + course.name)
|
||||||
for(var i=this.selectCourses.length-1;i>=0;i--){
|
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.selectCourses.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.creditNum -= parseFloat(course.credit)
|
||||||
},
|
},
|
||||||
'saveTemp': function(course){
|
'saveTemp': function (course) {
|
||||||
if(course==null){
|
if (course == null) {
|
||||||
}else{
|
} else {
|
||||||
this.tempCourse = []
|
this.tempCourse = []
|
||||||
var time = this.getTime(course.time)
|
var time = this.getTime(course.time)
|
||||||
for(var t of time){
|
for (var t of time) {
|
||||||
this.selectCourses.push({
|
this.selectCourses.push({
|
||||||
'time': t,
|
'time': t,
|
||||||
'name': course.name,
|
'name': course.name,
|
||||||
@ -174,13 +206,38 @@ var mainWindow = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'deleteTemp': function(course){
|
'deleteTemp': function (course) {
|
||||||
for(var i=this.selectCourses.length-1;i>=0;i--){
|
for (var i = this.selectCourses.length - 1; i >= 0; i--) {
|
||||||
if(this.selectCourses[i].name == course.name && this.selectCourses[i].temp == true){
|
if (this.selectCourses[i].name == course.name && this.selectCourses[i].temp == true) {
|
||||||
this.selectCourses.splice(i, 1)
|
this.selectCourses.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'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: {
|
components: {
|
||||||
'course-table': courseTable,
|
'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 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="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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -222,21 +279,33 @@ var mainWindow = {
|
|||||||
<div class="divider-custom-line"></div>
|
<div class="divider-custom-line"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider-custom">
|
<div class="divider-custom">
|
||||||
<div v-if="token!=''"><button class="btn btn-info" @click="saveCourseTable()">儲存</button></div>
|
<div class="row">
|
||||||
<div v-if="token==''"><button class="btn btn-info" @click="saveCourseTable()">儲存(必須登入Facebook帳號)</button></div>
|
<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>
|
</div>
|
||||||
|
<br>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-3">
|
||||||
<div class="row">
|
<div class="row mx-auto mb-2">
|
||||||
<choose-department
|
<choose-department
|
||||||
v-bind:departments="departments"
|
v-bind:departments="departments"
|
||||||
v-bind:selected="selectDepartment"
|
v-bind:selected="selectDepartment"
|
||||||
v-on:selectok="select"
|
v-on:selectok="select"
|
||||||
v-on:foundedok="founded"
|
v-on:foundedok="founded"
|
||||||
>
|
>
|
||||||
</choose-department><br>
|
</choose-department>
|
||||||
</div><br><br>
|
</div>
|
||||||
<div class="row">
|
<br>
|
||||||
|
<div class="row mx-auto mb-2">
|
||||||
<course-anslist
|
<course-anslist
|
||||||
v-bind:courses="courses"
|
v-bind:courses="courses"
|
||||||
v-bind:selected_d="selectDepartment"
|
v-bind:selected_d="selectDepartment"
|
||||||
@ -247,13 +316,26 @@ var mainWindow = {
|
|||||||
v-on:delete-temp="deleteTemp"
|
v-on:delete-temp="deleteTemp"
|
||||||
>
|
>
|
||||||
</course-anslist>
|
</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>
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
<div class="col-lg-9 table-responsive " >
|
<div class="col-lg-9 table-responsive " >
|
||||||
<course-table
|
<course-table
|
||||||
|
id="course-table-div"
|
||||||
v-bind:selectCourses="selectCourses"
|
v-bind:selectCourses="selectCourses"
|
||||||
v-bind:select_c="selectCourses"
|
v-bind:select_c="selectCourses"
|
||||||
|
v-bind:is_print="is_print"
|
||||||
v-bind:is_shared="false"
|
v-bind:is_shared="false"
|
||||||
v-on:remove-course="removeCourse"
|
v-on:remove-course="removeCourse"
|
||||||
></course-table>
|
></course-table>
|
||||||
@ -274,9 +356,32 @@ var mainWindow = {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
已經更新為 1092 新課表<br>
|
<ul>
|
||||||
但因學校未更新通識課資料,因此還沒有「通識課程分類」<br><br>
|
<li>已經更新為 1101 新學期課表(包含通識課分類)</li>
|
||||||
2021 01/14 更新
|
<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>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">我知道了</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">我知道了</button>
|
||||||
@ -287,4 +392,4 @@ var mainWindow = {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
var courseDiv = {
|
var courseDiv = {
|
||||||
props: ['course', 'is_shared'],
|
props: ['course', 'is_shared', 'is_print'],
|
||||||
template: `
|
template: `
|
||||||
<div style='border: 5px #1abc9c solid; text-align: center;'>
|
<div style='border: 5px #1abc9c solid; text-align: center;'>
|
||||||
{{ course.name }}
|
{{ course.name }}
|
||||||
|
<a v-bind:href="course.link" target="_blank"><i class="fas fa-info-circle"></i></a>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
v-if="!is_shared"
|
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"
|
class="btn btn-danger btn-sm"
|
||||||
|
:style="{'display': is_print ? 'none' : 'inline-block'}"
|
||||||
>
|
>
|
||||||
刪
|
刪
|
||||||
</button>
|
</button>
|
||||||
@ -22,7 +24,7 @@ var tempDiv = {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
var courseTable = {
|
var courseTable = {
|
||||||
props: ['select_c', 'is_shared'],
|
props: ['select_c', 'is_shared', 'is_print'],
|
||||||
data: function(){
|
data: function(){
|
||||||
return {
|
return {
|
||||||
'courses': {},
|
'courses': {},
|
||||||
@ -51,7 +53,11 @@ var courseTable = {
|
|||||||
for(var c of this.select_c){
|
for(var c of this.select_c){
|
||||||
this.courses[c.time] = {
|
this.courses[c.time] = {
|
||||||
'name': c.name,
|
'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){
|
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-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:course="courses[week+String.fromCharCode(97+((hour<5)?(hour-1):(hour-2)))]"
|
||||||
v-bind:is_shared="is_shared"
|
v-bind:is_shared="is_shared"
|
||||||
|
v-bind:is_print="is_print"
|
||||||
v-on:remove-course="removeCourseHandler"
|
v-on:remove-course="removeCourseHandler"
|
||||||
></course-div>
|
></course-div>
|
||||||
<temp-div
|
<temp-div
|
||||||
|
|||||||
@ -41,18 +41,7 @@
|
|||||||
offset: 80
|
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
|
// Floating label headings for the contact form
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|||||||
20
ncnu-course-api.service
Normal file
20
ncnu-course-api.service
Normal 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
1
歷年課程資料/1101_output.json
Normal file
1
歷年課程資料/1101_output.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user