fix lam bai
This commit is contained in:
291
client/app.go
291
client/app.go
@@ -10,12 +10,13 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -304,12 +305,68 @@ func (a *App) startLocalServer() {
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/exam-view", func(w http.ResponseWriter, r *http.Request) {
|
||||
target := strings.TrimSpace(r.URL.Query().Get("url"))
|
||||
title := strings.TrimSpace(r.URL.Query().Get("title"))
|
||||
if target == "" {
|
||||
http.Error(w, "missing url", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if title == "" {
|
||||
title = "Xem trong Simple Care"
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = fmt.Fprintf(w, examViewHTML, html.EscapeString(title), html.EscapeString(title), html.EscapeString(target))
|
||||
})
|
||||
mux.HandleFunc("/exam-close", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
if r.Method == "OPTIONS" {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
return
|
||||
}
|
||||
go runtime.WindowReloadApp(a.ctx)
|
||||
w.Write([]byte("ok"))
|
||||
})
|
||||
|
||||
go func() {
|
||||
_ = server.ListenAndServe()
|
||||
}()
|
||||
log.Println("[LOCALSERVER] Callback server started on http://127.0.0.1:34115")
|
||||
}
|
||||
|
||||
const examViewHTML = `<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>%s</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:Segoe UI,system-ui,sans-serif;background:#1e293b;color:#f8fafc;height:100vh;display:flex;flex-direction:column}
|
||||
.bar{display:flex;align-items:center;gap:12px;padding:10px 14px;background:#0f172a;border-bottom:1px solid #334155;flex-shrink:0}
|
||||
.bar button{background:#7c3aed;color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:14px;cursor:pointer;font-weight:600}
|
||||
.bar button:hover{background:#6d28d9}
|
||||
.bar span{font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.frame-wrap{flex:1;min-height:0;background:#fff}
|
||||
iframe{width:100%%;height:100%%;border:0;display:block}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bar">
|
||||
<button type="button" onclick="goBack()">← Quay lại</button>
|
||||
<span>%s</span>
|
||||
</div>
|
||||
<div class="frame-wrap">
|
||||
<iframe src="%s" title="exam-content"></iframe>
|
||||
</div>
|
||||
<script>
|
||||
function goBack(){
|
||||
fetch('http://127.0.0.1:34115/exam-close').catch(function(){});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
// loadSession nạp session từ file cục bộ
|
||||
func (a *App) loadSession() {
|
||||
a.mu.Lock()
|
||||
@@ -1215,72 +1272,210 @@ func (a *App) SendChatMessage(body string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) OpenExamQuiz() error {
|
||||
func (a *App) examStudentContext() (int64, *StudentExamSnapshot, error) {
|
||||
a.mu.Lock()
|
||||
var url string
|
||||
if a.dashboard.Exam != nil {
|
||||
url = strings.TrimSpace(a.dashboard.Exam.QuizURL)
|
||||
defer a.mu.Unlock()
|
||||
if a.student == nil {
|
||||
return 0, nil, errors.New("chưa đăng nhập")
|
||||
}
|
||||
a.mu.Unlock()
|
||||
if url == "" {
|
||||
return errors.New("Phòng thi chưa cấu hình link trắc nghiệm")
|
||||
if a.dashboard.Exam == nil {
|
||||
return 0, nil, errors.New("không trong giờ thi")
|
||||
}
|
||||
return openPrivateBrowser(url)
|
||||
return a.student.StudentID, a.dashboard.Exam, nil
|
||||
}
|
||||
|
||||
func openPrivateBrowser(url string) error {
|
||||
tries := [][]string{
|
||||
{"cmd", "/c", "start", "", "chrome", "--incognito", "--new-window", "--disk-cache-size=1", url},
|
||||
{"cmd", "/c", "start", "", "msedge", "--inprivate", "--new-window", url},
|
||||
{"cmd", "/c", "start", "", url},
|
||||
func (a *App) openExamWebView(targetURL, title string) error {
|
||||
targetURL = strings.TrimSpace(targetURL)
|
||||
if targetURL == "" {
|
||||
return errors.New("không có nội dung để mở")
|
||||
}
|
||||
var lastErr error
|
||||
for _, args := range tries {
|
||||
if err := exec.Command(args[0], args[1:]...).Start(); err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if lastErr != nil {
|
||||
return lastErr
|
||||
}
|
||||
return errors.New("không mở được trình duyệt")
|
||||
wrapper := fmt.Sprintf(
|
||||
"http://127.0.0.1:34115/exam-view?url=%s&title=%s",
|
||||
url.QueryEscape(targetURL),
|
||||
url.QueryEscape(title),
|
||||
)
|
||||
runtime.WindowExecJS(a.ctx, fmt.Sprintf("window.location.href = %q", wrapper))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) OpenExamPaper() error {
|
||||
a.mu.Lock()
|
||||
student := a.student
|
||||
exam := a.dashboard.Exam
|
||||
a.mu.Unlock()
|
||||
if student == nil {
|
||||
return errors.New("chưa đăng nhập")
|
||||
}
|
||||
if exam == nil || !exam.PaperSent {
|
||||
return errors.New("Giảng viên chưa gửi đề")
|
||||
}
|
||||
dlURL := fmt.Sprintf("http://127.0.0.1:8080/api/student/exam/download?studentRkId=%d&kind=pdf", student.StudentID)
|
||||
resp, err := http.Get(dlURL)
|
||||
func (a *App) GetExamPaperViewURL() (string, error) {
|
||||
studentID, exam, err := a.examStudentContext()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if !exam.PaperSent {
|
||||
return "", errors.New("Giảng viên chưa gửi đề")
|
||||
}
|
||||
return fmt.Sprintf("http://127.0.0.1:8080/api/student/exam/download?studentRkId=%d&kind=pdf", studentID), nil
|
||||
}
|
||||
|
||||
func (a *App) GetExamQuizViewURL() (string, error) {
|
||||
_, exam, err := a.examStudentContext()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
quizURL := strings.TrimSpace(exam.QuizURL)
|
||||
if quizURL == "" {
|
||||
return "", errors.New("Phòng thi chưa cấu hình link trắc nghiệm")
|
||||
}
|
||||
return quizURL, nil
|
||||
}
|
||||
|
||||
func (a *App) GetExamPaperFiles() (map[string]any, error) {
|
||||
studentID, exam, err := a.examStudentContext()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exam.PaperSent {
|
||||
return nil, errors.New("Giảng viên chưa gửi đề")
|
||||
}
|
||||
apiURL := fmt.Sprintf("http://127.0.0.1:8080/api/student/exam/paper-files?studentRkId=%d", studentID)
|
||||
resp, err := http.Get(apiURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("không tải được đề")
|
||||
return nil, errors.New("không tải được danh sách tài nguyên")
|
||||
}
|
||||
dir := filepath.Join(os.TempDir(), "SimpleCareExam")
|
||||
_ = os.MkdirAll(dir, 0755)
|
||||
dest := filepath.Join(dir, fmt.Sprintf("de_thi_%d.pdf", exam.ExamRoomID))
|
||||
f, err := os.Create(dest)
|
||||
var payload map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base := "http://127.0.0.1:8080"
|
||||
if pdf, ok := payload["pdfUrl"].(string); ok && pdf != "" && !strings.HasPrefix(pdf, "http") {
|
||||
payload["pdfUrl"] = base + pdf
|
||||
}
|
||||
if raw, ok := payload["resources"].([]any); ok {
|
||||
for i, item := range raw {
|
||||
m, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if u, ok := m["url"].(string); ok && u != "" && !strings.HasPrefix(u, "http") {
|
||||
m["url"] = base + u
|
||||
}
|
||||
raw[i] = m
|
||||
}
|
||||
payload["resources"] = raw
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (a *App) OpenExamPaper() error {
|
||||
viewURL, err := a.GetExamPaperViewURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(f, resp.Body); err != nil {
|
||||
f.Close()
|
||||
title := "Đề thi"
|
||||
a.mu.Lock()
|
||||
if a.dashboard.Exam != nil && strings.TrimSpace(a.dashboard.Exam.PaperTitle) != "" {
|
||||
title = strings.TrimSpace(a.dashboard.Exam.PaperTitle)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a.openExamWebView(viewURL, title)
|
||||
}
|
||||
|
||||
func (a *App) OpenExamQuiz() error {
|
||||
viewURL, err := a.GetExamQuizViewURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.openExamWebView(viewURL, "Làm trắc nghiệm")
|
||||
}
|
||||
|
||||
func (a *App) OpenExamResource(fileID uint) error {
|
||||
studentID, _, err := a.examStudentContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fileID == 0 {
|
||||
return errors.New("tài nguyên không hợp lệ")
|
||||
}
|
||||
viewURL := fmt.Sprintf(
|
||||
"http://127.0.0.1:8080/api/student/exam/download?studentRkId=%d&kind=resource&fileId=%d",
|
||||
studentID, fileID,
|
||||
)
|
||||
title := "Tài nguyên đề thi"
|
||||
files, err := a.GetExamPaperFiles()
|
||||
if err == nil {
|
||||
if raw, ok := files["resources"].([]any); ok {
|
||||
for _, item := range raw {
|
||||
m, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
id, _ := m["id"].(float64)
|
||||
if uint(id) == fileID {
|
||||
if name, ok := m["fileName"].(string); ok && name != "" {
|
||||
title = name
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return a.openExamWebView(viewURL, title)
|
||||
}
|
||||
|
||||
func (a *App) DownloadExamResource(fileID uint) (string, error) {
|
||||
studentID, exam, err := a.examStudentContext()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if fileID == 0 {
|
||||
return "", errors.New("tài nguyên không hợp lệ")
|
||||
}
|
||||
fileName := fmt.Sprintf("resource_%d", fileID)
|
||||
files, err := a.GetExamPaperFiles()
|
||||
if err == nil {
|
||||
if raw, ok := files["resources"].([]any); ok {
|
||||
for _, item := range raw {
|
||||
m, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
id, _ := m["id"].(float64)
|
||||
if uint(id) == fileID {
|
||||
if name, ok := m["fileName"].(string); ok && name != "" {
|
||||
fileName = filepath.Base(name)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dlURL := fmt.Sprintf(
|
||||
"http://127.0.0.1:8080/api/student/exam/download?studentRkId=%d&kind=resource&fileId=%d",
|
||||
studentID, fileID,
|
||||
)
|
||||
resp, err := http.Get(dlURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", errors.New("không tải được tài nguyên")
|
||||
}
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dir := filepath.Join(configDir, "SimpleCare", "ExamResources", fmt.Sprintf("room_%d", exam.ExamRoomID))
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
dest := filepath.Join(dir, fileName)
|
||||
f, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := io.Copy(f, resp.Body); err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
}
|
||||
f.Close()
|
||||
return exec.Command("cmd", "/c", "start", "", dest).Start()
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
func (a *App) SubmitExamWork() (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user