Compare commits

..

No commits in common. "master" and "route" have entirely different histories.

15 changed files with 275 additions and 562 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
.DS_Store

View File

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

8
api.py
View File

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

View File

@ -1,137 +0,0 @@
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,39 +4,33 @@ import os
import csv
from bs4 import BeautifulSoup as bs
USERNAME = ""
PASSWORD = ""
YEAR = 1111
session = requests.Session()
header = {
'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/"
courses = []
generalCourse = []
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')
def getGeneralCourseData(year):
'''
透過年份取得 通識課程分類的csv檔
供後續課程對應
# 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'
}
)
先儲存到 generalCourse list後續再用 courseID 對應通識分類
'''
# 成功的話 return http 302, redirect
if len(response.history)!=0:
return True
else:
return False
# 教務系統有開放 年度的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)
def curlDepartmentCourseTable(year):
'''
@ -45,94 +39,74 @@ def curlDepartmentCourseTable(year):
'''
print("取得所有課程資料:")
# 切換年度,應該是用 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))
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")
# 取得 所有課程的 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)
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(year):
def extractDepartmentCourseTable(departmentName, link):
'''
透過各科系連結取得課程資訊
若為通識類別還要跟csv檔資料做對應取得正確通識類別
對應後存取到 output.json
'''
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('","')
response = requests.get(link, headers=header)
data = response.text
root = bs(data, "html.parser")
courseTR = root.findAll('tr')[1:] # 清除 thead
for tr in courseTR:
courseObj = {}
tds = tr.find_all('td')
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]
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
ans.append(courseObj)
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']))
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)
courses.append(courseObj)
with open('output.json', 'w') as fp:
json.dump(courses, fp)
if __name__ == "__main__":
while True:
username = USERNAME
password = PASSWORD
if login(username, password):
print("登入成功!")
break
else:
print("登入失敗!")
year = input("年份: ")
curlDepartmentCourseTable(YEAR)
extractDepartmentCourseTable(YEAR)
updateGeneralCourse()
getGeneralCourseData(year)
curlDepartmentCourseTable(year)
print("\n\n=====================")
print("未列入追蹤的通識課程")
print("=====================\n")
for notIn in generalCourse:
if "體育:" not in notIn[5]:
print(" - 未列入追蹤的新通識課程: {}".format(notIn))

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="你還在用紙筆或Excel在安排下學期的課表嗎「暨大排課表」幫你篩選衝堂、科系分類、通識課程分類讓你輕鬆排課表" />
<meta name="author" content="snsd0805" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>暨大排課表</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" href="assets/img/favicon.ico" />
@ -28,8 +28,6 @@
<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,15 +19,10 @@ var chooseDepartment = {
},
template: `
<div class="mx-auto">
<div>
<h5>1. 課程名稱直接搜尋</h5>
<input class="form-control" type='text' v-model='initFounded'>
</div>
<div class="text-center my-2">
<h5></h5>
</div>
<div>
<br><br><br>
<h5>1. 選擇類別</h5>
<select class="custom-select mr-sm-2" v-model="initSelect">
<option v-for="(item, index) in departments" :key="index"
@ -36,7 +31,7 @@ var chooseDepartment = {
{{ item }}
</option>
</select>
</div>
</div>
`,
}

View File

@ -1,54 +1,69 @@
var coursesList = {
props: ['courses', 'selected_d', 'selected_c', 'find_name'],
data: function () {
data: function(){
return {
selectedTime: [],
foundedCourses: []
}
},
methods: {
'getTime': function (timeString) {
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];
'getTime': function(timeString){
if(timeString==null){
return ""
}
}, [])
: [];
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) {
'isOK': function(course){
var time = this.getTime(course.time)
// console.log(course.name, " ", time)
const isConflict = time.some((t) => this.selectedTime.includes(t))
return time.length && !isConflict
for(t of time){
for(st of this.selectedTime){
if(t==st)
return false
}
}
return true
},
'log': function (name, data) {
'log': function(name, data){
console.log(name, data)
}
},
watch: {
'selected_c': function () {
'selected_c': function(){
var temp = []
for (var c of this.selected_c) {
if (c.temp == false) {
for(var c of this.selected_c){
if(c.temp==false){
temp.push(c.time)
}
}
this.selectedTime = temp
},
'find_name': function () {
const target = this.find_name.toLowerCase();
this.foundedCourses = this.courses.filter((c) => c.name.toLowerCase().includes(target));
'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
}
},
template: `
<div class="mx-auto mb-4">
<div>
<h5>2. 安排課程</h5>
<p style="color: orange" v-if="find_name"> 已套用名稱搜尋 <br>{{find_name}}</p>
<div style="width:275px;height:500px;overflow:auto">
@ -57,20 +72,22 @@ 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 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 class="container">
<div class="row">
<b>{{ course.name }} (<a v-bind:href="course.link"></a>)</b>
{{ (course.department.indexOf(', ')!=-1) ?(course.department.split(', ')[1]) :(course.department) }}
</div>
<div class="col-sm-8 pr-1">
<div class="row">
<div class="col-sm-8">
{{ course.teacher }} {{ course.time }}
</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">
<span>&#43;</span>
</button>
</div>
</div>
</div>
</td>
</tr>
</template>
@ -79,19 +96,21 @@ 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 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 class="container">
<div class="row">
<b>{{ course.name }} (<a v-bind:href="course.link"></a>)</b>
</div>
<div class="col-sm-8 pr-1">
<div class="row">
<div class="col-sm-8">
{{ course.teacher }} {{ course.time }}
</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">
<span>&#43;</span>
</button>
</div>
</div>
</div>
</td>
</tr>
</template>

View File

@ -1,5 +1,5 @@
var mainWindow = {
data: function () {
data: function(){
return {
'courses': [],
'selectCourses': [],
@ -7,28 +7,26 @@ var mainWindow = {
'selectDepartment': '',
'foundName': "",
"user": "",
'token': "",
'is_print': false,
'creditNum': 0,
'token': ""
}
},
created() {
var main = this
window.fbAsyncInit = function () {
window.fbAsyncInit = function() {
FB.init({
appId: '',
cookie: true,
xfbml: true,
version: 'v9.0'
appId : '',
cookie : true,
xfbml : true,
version : 'v9.0'
});
FB.AppEvents.logPageView();
main.getCourseTable()
};
(function (d, s, id) {
(function(d, s, id){
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.src = "https://connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
@ -39,165 +37,135 @@ var mainWindow = {
axios
.get("./output.json")
.then(response => (main.courses = response.data))
.then(function () {
for (var course of main.courses) {
.then(function(){
for(var course of main.courses){
// console.log(course.name)
if (main.departments.indexOf(course.department) == -1) {
if(main.departments.indexOf(course.department)==-1){
main.departments.push(course.department)
}
}
})
.then(function () {
.then(function(){
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 () {
'login': function(){
var main = this
FB.login(function () {
FB.login(function(){
main.getCourseTable()
})
},
'logout': function () {
'logout': function(){
var main = this
FB.logout(function (response) {
FB.logout(function(response) {
main.user = ""
main.token = ""
});
},
'getCourseTable': function () {
'getCourseTable': function(){
var main = this
FB.getLoginStatus(function (response) {
FB.getLoginStatus(function(response) {
main.statusChangeCallback(response);
});
},
'statusChangeCallback': function (response) {
if (response.status == "connected") {
'statusChangeCallback': function(response){
if(response.status == "connected"){
this.token = response.authResponse.accessToken
var main = this
FB.api('/me', function (response) { main.user = response })
FB.api('/me', function(response){main.user = response.name})
fetch('https://api.snsd0805.com/courseTable?token=' + this.token)
.then(function (response) {
fetch('https://api.snsd0805.com/courseTable?token='+this.token)
.then(function(response){
return response.json()
}).then(function (jsonData) {
}).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)
.catch(function(err){
alert("錯誤: "+err)
})
}
},
'saveCourseTable': function () {
'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', {
if(this.token!=""){
fetch('https://api.snsd0805.com/courseTable',{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'token': main.token,
'data': filteredCourses
'data': main.selectCourses
})
})
.then(function (response) {
.then(function(response){
return response.json()
})
.then(function (response) {
if (response.status == "saved") {
.then(function(response){
if(response.status=="saved"){
alert("已儲存")
} else {
}else{
alert("錯誤")
}
})
.catch(function (err) {
alert("錯誤: " + err)
.catch(function(err){
alert("錯誤: "+err)
})
} else {
}else{
this.login()
}
},
'getTime': function (timeString) {
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];
'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)
}
}, [])
: [];
else{
ans.push(timeString)
break
}
}
return ans
},
'select': function (department) {
'select': function(department){
this.selectDepartment = department
},
'founded': function (courseName) {
'founded': function(courseName){
this.foundName = courseName
},
'addCourse': function (course) {
'addCourse': function(course){
var time = this.getTime(course.time)
for (var t of time) {
for(var t of time){
this.selectCourses.push({
'time': t,
'name': course.name,
'temp': false,
'number': course.number,
'class': course.class,
'credit': course.credit,
'link': course.link
'temp': false
})
}
this.creditNum += parseFloat(course.credit)
},
'removeCourse': function (course) {
console.log("remove " + course.name)
for (var i = this.selectCourses.length - 1; i >= 0; i--) {
if (this.selectCourses[i].number === course.number && this.selectCourses[i].class === course.class) {
'removeCourse': function(course){
console.log("remove "+course)
for(var i=this.selectCourses.length-1;i>=0;i--){
if(this.selectCourses[i].name == course){
this.selectCourses.splice(i, 1)
}
}
this.creditNum -= parseFloat(course.credit)
},
'saveTemp': function (course) {
if (course == null) {
} else {
'saveTemp': function(course){
if(course==null){
}else{
this.tempCourse = []
var time = this.getTime(course.time)
for (var t of time) {
for(var t of time){
this.selectCourses.push({
'time': t,
'name': course.name,
@ -206,38 +174,13 @@ var mainWindow = {
}
}
},
'deleteTemp': function (course) {
for (var i = this.selectCourses.length - 1; i >= 0; i--) {
if (this.selectCourses[i].name == course.name && this.selectCourses[i].temp == true) {
'deleteTemp': function(course){
for(var i=this.selectCourses.length-1;i>=0;i--){
if(this.selectCourses[i].name == course.name && this.selectCourses[i].temp == true){
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: {
'course-table': courseTable,
@ -259,7 +202,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.name}}</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>
</ul>
</div>
@ -279,33 +222,21 @@ var mainWindow = {
<div class="divider-custom-line"></div>
</div>
<div class="divider-custom">
<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 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>
<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 mx-auto mb-2">
<div class="row">
<choose-department
v-bind:departments="departments"
v-bind:selected="selectDepartment"
v-on:selectok="select"
v-on:foundedok="founded"
>
</choose-department>
</div>
<br>
<div class="row mx-auto mb-2">
</choose-department><br>
</div><br><br>
<div class="row">
<course-anslist
v-bind:courses="courses"
v-bind:selected_d="selectDepartment"
@ -316,26 +247,13 @@ 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>
@ -356,32 +274,9 @@ var mainWindow = {
</button>
</div>
<div class="modal-body">
<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}}
已經更新為 1092 新課表<br>
但因學校未更新通識課資料因此還沒有通識課程分類<br><br>
2021 01/14 更新
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">我知道了</button>

View File

@ -1,14 +1,12 @@
var courseDiv = {
props: ['course', 'is_shared', 'is_print'],
props: ['course', 'is_shared'],
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)"
v-on:click="$emit('remove-course', course.name)"
class="btn btn-danger btn-sm"
:style="{'display': is_print ? 'none' : 'inline-block'}"
>
</button>
@ -24,7 +22,7 @@ var tempDiv = {
`
}
var courseTable = {
props: ['select_c', 'is_shared', 'is_print'],
props: ['select_c', 'is_shared'],
data: function(){
return {
'courses': {},
@ -53,11 +51,7 @@ var courseTable = {
for(var c of this.select_c){
this.courses[c.time] = {
'name': c.name,
'number': c.number,
'class': c.class,
'temp': c.temp,
'credit': c.credit,
'link': c.link
'temp': c.temp
}
if(c.time[0]==6 || c.time[0]==7){
@ -118,7 +112,6 @@ 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,7 +41,18 @@
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() {

View File

@ -1,20 +0,0 @@
[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