tam
This commit is contained in:
139
client/app.go
139
client/app.go
@@ -8,6 +8,7 @@ import (
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -85,9 +87,10 @@ type App struct {
|
||||
ctx context.Context
|
||||
student *StudentData
|
||||
mu sync.Mutex
|
||||
sessionPath string
|
||||
statsPath string
|
||||
wsConn *websocket.Conn
|
||||
sessionPath string
|
||||
statsPath string
|
||||
webviewDataPath string
|
||||
wsConn *websocket.Conn
|
||||
wsConnected bool
|
||||
wifiSSID string
|
||||
wifiBSSID string
|
||||
@@ -165,11 +168,14 @@ func NewApp() *App {
|
||||
configDir = filepath.Dir(exePath)
|
||||
}
|
||||
appDir := filepath.Join(configDir, "SimpleCare")
|
||||
webviewDir := filepath.Join(appDir, "WebView2")
|
||||
_ = os.MkdirAll(appDir, 0755)
|
||||
_ = os.MkdirAll(webviewDir, 0755)
|
||||
|
||||
return &App{
|
||||
sessionPath: filepath.Join(appDir, "student_session.json"),
|
||||
statsPath: filepath.Join(appDir, "student_stats.json"),
|
||||
sessionPath: filepath.Join(appDir, "student_session.json"),
|
||||
statsPath: filepath.Join(appDir, "student_stats.json"),
|
||||
webviewDataPath: webviewDir,
|
||||
lastSyncTime: time.Now(),
|
||||
expectingLogin: false,
|
||||
}
|
||||
@@ -1284,17 +1290,36 @@ func (a *App) examStudentContext() (int64, *StudentExamSnapshot, error) {
|
||||
return a.student.StudentID, a.dashboard.Exam, nil
|
||||
}
|
||||
|
||||
// ReturnToDashboard quay lại giao diện chính của app (giữ cookie WebView2).
|
||||
func (a *App) ReturnToDashboard() {
|
||||
runtime.WindowReloadApp(a.ctx)
|
||||
}
|
||||
|
||||
func isLocalExamURL(raw string) bool {
|
||||
u, err := url.Parse(strings.TrimSpace(raw))
|
||||
if err != nil || u.Host == "" {
|
||||
return false
|
||||
}
|
||||
host := strings.ToLower(u.Hostname())
|
||||
return host == "127.0.0.1" || host == "localhost"
|
||||
}
|
||||
|
||||
func (a *App) openExamWebView(targetURL, title string) error {
|
||||
targetURL = strings.TrimSpace(targetURL)
|
||||
if targetURL == "" {
|
||||
return errors.New("không có nội dung để mở")
|
||||
}
|
||||
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))
|
||||
if isLocalExamURL(targetURL) {
|
||||
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
|
||||
}
|
||||
// Trang thi bên ngoài: mở trực tiếp trong WebView (first-party cookies → giữ phiên đăng nhập).
|
||||
runtime.WindowExecJS(a.ctx, fmt.Sprintf("window.location.href = %q", targetURL))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1306,7 +1331,7 @@ func (a *App) GetExamPaperViewURL() (string, error) {
|
||||
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
|
||||
return fmt.Sprintf("http://127.0.0.1:8080/api/student/exam/download?studentRkId=%d&kind=pdf&view=1", studentID), nil
|
||||
}
|
||||
|
||||
func (a *App) GetExamQuizViewURL() (string, error) {
|
||||
@@ -1355,6 +1380,9 @@ func (a *App) GetExamPaperFiles() (map[string]any, error) {
|
||||
if u, ok := m["url"].(string); ok && u != "" && !strings.HasPrefix(u, "http") {
|
||||
m["url"] = base + u
|
||||
}
|
||||
if u, ok := m["downloadUrl"].(string); ok && u != "" && !strings.HasPrefix(u, "http") {
|
||||
m["downloadUrl"] = base + u
|
||||
}
|
||||
raw[i] = m
|
||||
}
|
||||
payload["resources"] = raw
|
||||
@@ -1393,7 +1421,7 @@ func (a *App) OpenExamResource(fileID uint) error {
|
||||
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",
|
||||
"http://127.0.0.1:8080/api/student/exam/download?studentRkId=%d&kind=resource&fileId=%d&view=1",
|
||||
studentID, fileID,
|
||||
)
|
||||
title := "Tài nguyên đề thi"
|
||||
@@ -1418,8 +1446,47 @@ func (a *App) OpenExamResource(fileID uint) error {
|
||||
return a.openExamWebView(viewURL, title)
|
||||
}
|
||||
|
||||
func examResourceDownloadDir() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
candidates := []string{
|
||||
filepath.Join(home, "Downloads"),
|
||||
filepath.Join(home, "Desktop"),
|
||||
home,
|
||||
}
|
||||
for _, dir := range candidates {
|
||||
if st, err := os.Stat(dir); err == nil && st.IsDir() {
|
||||
return dir, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("không tìm thấy thư mục Downloads hoặc Desktop")
|
||||
}
|
||||
|
||||
func uniqueFilePath(path string) string {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return path
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
base := strings.TrimSuffix(filepath.Base(path), ext)
|
||||
dir := filepath.Dir(path)
|
||||
for i := 1; i < 100; i++ {
|
||||
candidate := filepath.Join(dir, fmt.Sprintf("%s (%d)%s", base, i, ext))
|
||||
if _, err := os.Stat(candidate); os.IsNotExist(err) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return filepath.Join(dir, fmt.Sprintf("%s_%d%s", base, time.Now().Unix(), ext))
|
||||
}
|
||||
|
||||
func openFolderInExplorer(filePath string) {
|
||||
dir := filepath.Dir(filePath)
|
||||
_ = exec.Command("explorer", dir).Start()
|
||||
}
|
||||
|
||||
func (a *App) DownloadExamResource(fileID uint) (string, error) {
|
||||
studentID, exam, err := a.examStudentContext()
|
||||
studentID, _, err := a.examStudentContext()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -1457,15 +1524,11 @@ func (a *App) DownloadExamResource(fileID uint) (string, error) {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", errors.New("không tải được tài nguyên")
|
||||
}
|
||||
configDir, err := os.UserConfigDir()
|
||||
dir, err := examResourceDownloadDir()
|
||||
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)
|
||||
dest := uniqueFilePath(filepath.Join(dir, fileName))
|
||||
f, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -1475,9 +1538,45 @@ func (a *App) DownloadExamResource(fileID uint) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
f.Close()
|
||||
openFolderInExplorer(dest)
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
// LoadExamViewFile tải file đề/tài nguyên qua Go (không mở URL trực tiếp trong WebView).
|
||||
func (a *App) LoadExamViewFile(fileURL string) (map[string]any, error) {
|
||||
fileURL = strings.TrimSpace(fileURL)
|
||||
if !strings.HasPrefix(fileURL, "http://127.0.0.1:8080/api/student/exam/download") {
|
||||
return nil, errors.New("URL không hợp lệ")
|
||||
}
|
||||
if _, _, err := a.examStudentContext(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := http.Get(fileURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("không tải được file")
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mime := strings.TrimSpace(resp.Header.Get("Content-Type"))
|
||||
if mime == "" {
|
||||
mime = "application/octet-stream"
|
||||
}
|
||||
// Bỏ charset nếu có
|
||||
if i := strings.Index(mime, ";"); i >= 0 {
|
||||
mime = strings.TrimSpace(mime[:i])
|
||||
}
|
||||
return map[string]any{
|
||||
"data": base64.StdEncoding.EncodeToString(body),
|
||||
"mime": mime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) SubmitExamWork() (string, error) {
|
||||
a.mu.Lock()
|
||||
student := a.student
|
||||
|
||||
Reference in New Issue
Block a user