feat: support announcement image upload
# Conflicts: # database.py # db/migrations.py # routes/admin_api/core.py # static/admin/.vite/manifest.json # static/admin/assets/AnnouncementsPage-Btl9JP7M.js # static/admin/assets/EmailPage-CwqlBGU2.js # static/admin/assets/FeedbacksPage-B_qDNL3q.js # static/admin/assets/LogsPage-DzdymdrQ.js # static/admin/assets/ReportPage-Bp26gOA-.js # static/admin/assets/SettingsPage-__r25pN8.js # static/admin/assets/SystemPage-C1OfxrU-.js # static/admin/assets/UsersPage-DhnABKcY.js # static/admin/assets/email-By53DCWv.js # static/admin/assets/email-ByiJ74rd.js # static/admin/assets/email-DkWacopQ.js # static/admin/assets/index-D5wU2pVd.js # static/admin/assets/tasks-1acmkoIX.js # static/admin/assets/update-DdQLVpC3.js # static/admin/assets/users-B1w166uc.js # static/admin/assets/users-CPJP5r-B.js # static/admin/assets/users-CnIyvFWm.js # static/admin/index.html # static/app/.vite/manifest.json # static/app/assets/AccountsPage-C48gJL8c.js # static/app/assets/AccountsPage-D387XNsv.js # static/app/assets/AccountsPage-DBJCAsJz.js # static/app/assets/LoginPage-BgK_Vl6X.js # static/app/assets/RegisterPage-CwADxWfe.js # static/app/assets/ResetPasswordPage-CVfZX_5z.js # static/app/assets/SchedulesPage-CWuZpJ5h.js # static/app/assets/SchedulesPage-Dw-mXbG5.js # static/app/assets/SchedulesPage-DwzGOBuc.js # static/app/assets/ScreenshotsPage-C6vX2U3V.js # static/app/assets/ScreenshotsPage-CreOSjVc.js # static/app/assets/ScreenshotsPage-DuTeRzLR.js # static/app/assets/VerifyResultPage-BzGlCgtE.js # static/app/assets/VerifyResultPage-CN_nr4V6.js # static/app/assets/VerifyResultPage-CNbQc83z.js # static/app/assets/accounts-BFaVMUve.js # static/app/assets/accounts-BYq3lLev.js # static/app/assets/accounts-Bc9j2moH.js # static/app/assets/auth-Dk_ApO4B.js # static/app/assets/index-BIng7uZJ.css # static/app/assets/index-CDxVo_1Z.js # static/app/index.html
This commit is contained in:
@@ -10,6 +10,13 @@ export async function createAnnouncement(payload) {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function uploadAnnouncementImage(file) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
const { data } = await api.post('/announcements/upload_image', formData)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
export async function activateAnnouncement(id) {
|
export async function activateAnnouncement(id) {
|
||||||
const { data } = await api.post(`/announcements/${id}/activate`)
|
const { data } = await api.post(`/announcements/${id}/activate`)
|
||||||
return data
|
return data
|
||||||
@@ -24,4 +31,3 @@ export async function deleteAnnouncement(id) {
|
|||||||
const { data } = await api.delete(`/announcements/${id}`)
|
const { data } = await api.delete(`/announcements/${id}`)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from 'vue'
|
import { h, onMounted, ref } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
activateAnnouncement,
|
activateAnnouncement,
|
||||||
@@ -8,10 +9,14 @@ import {
|
|||||||
deactivateAnnouncement,
|
deactivateAnnouncement,
|
||||||
deleteAnnouncement,
|
deleteAnnouncement,
|
||||||
fetchAnnouncements,
|
fetchAnnouncements,
|
||||||
|
uploadAnnouncementImage,
|
||||||
} from '../api/announcements'
|
} from '../api/announcements'
|
||||||
|
|
||||||
const formTitle = ref('')
|
const formTitle = ref('')
|
||||||
const formContent = ref('')
|
const formContent = ref('')
|
||||||
|
const formImageUrl = ref('')
|
||||||
|
const imageInputRef = ref(null)
|
||||||
|
const uploading = ref(false)
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const list = ref([])
|
const list = ref([])
|
||||||
@@ -30,18 +35,56 @@ async function load() {
|
|||||||
function clearForm() {
|
function clearForm() {
|
||||||
formTitle.value = ''
|
formTitle.value = ''
|
||||||
formContent.value = ''
|
formContent.value = ''
|
||||||
|
formImageUrl.value = ''
|
||||||
|
if (imageInputRef.value) imageInputRef.value.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function openImagePicker() {
|
||||||
|
imageInputRef.value?.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearImage() {
|
||||||
|
formImageUrl.value = ''
|
||||||
|
if (imageInputRef.value) imageInputRef.value.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onImageFileChange(event) {
|
||||||
|
const file = event.target?.files?.[0]
|
||||||
|
if (!file) return
|
||||||
|
if (file.type && !file.type.startsWith('image/')) {
|
||||||
|
ElMessage.error('请选择图片文件')
|
||||||
|
event.target.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploading.value = true
|
||||||
|
try {
|
||||||
|
const res = await uploadAnnouncementImage(file)
|
||||||
|
if (!res?.success || !res?.url) {
|
||||||
|
ElMessage.error(res?.error || '上传失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
formImageUrl.value = res.url
|
||||||
|
ElMessage.success('上传成功')
|
||||||
|
} catch {
|
||||||
|
// handled by interceptor
|
||||||
|
} finally {
|
||||||
|
uploading.value = false
|
||||||
|
event.target.value = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submit(isActive) {
|
async function submit(isActive) {
|
||||||
const title = formTitle.value.trim()
|
const title = formTitle.value.trim()
|
||||||
const content = formContent.value.trim()
|
const content = formContent.value.trim()
|
||||||
|
const image_url = formImageUrl.value.trim()
|
||||||
if (!title || !content) {
|
if (!title || !content) {
|
||||||
ElMessage.error('标题和内容不能为空')
|
ElMessage.error('标题和内容不能为空')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await createAnnouncement({ title, content, is_active: Boolean(isActive) })
|
const res = await createAnnouncement({ title, content, image_url, is_active: Boolean(isActive) })
|
||||||
if (!res?.success) {
|
if (!res?.success) {
|
||||||
ElMessage.error(res?.error || '保存失败')
|
ElMessage.error(res?.error || '保存失败')
|
||||||
return
|
return
|
||||||
@@ -55,7 +98,17 @@ async function submit(isActive) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function view(row) {
|
async function view(row) {
|
||||||
await ElMessageBox.alert(row.content || '', row.title || '公告', {
|
const body = h('div', { class: 'announcement-view' }, [
|
||||||
|
row.content ? h('div', { class: 'announcement-view-text' }, row.content) : null,
|
||||||
|
row.image_url
|
||||||
|
? h('img', {
|
||||||
|
class: 'announcement-view-image',
|
||||||
|
src: row.image_url,
|
||||||
|
alt: '公告图片',
|
||||||
|
})
|
||||||
|
: null,
|
||||||
|
])
|
||||||
|
await ElMessageBox.alert(body, row.title || '公告', {
|
||||||
confirmButtonText: '关闭',
|
confirmButtonText: '关闭',
|
||||||
dangerouslyUseHTMLString: false,
|
dangerouslyUseHTMLString: false,
|
||||||
})
|
})
|
||||||
@@ -162,8 +215,26 @@ onMounted(load)
|
|||||||
show-word-limit
|
show-word-limit
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="公告图片">
|
||||||
|
<div class="image-upload-row">
|
||||||
|
<el-button :icon="Plus" :loading="uploading" @click="openImagePicker">上传图片</el-button>
|
||||||
|
<el-button v-if="formImageUrl" @click="clearImage">移除</el-button>
|
||||||
|
<span v-if="formImageUrl" class="image-url">{{ formImageUrl }}</span>
|
||||||
|
<input
|
||||||
|
ref="imageInputRef"
|
||||||
|
class="image-input"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
@change="onImageFileChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<div v-if="formImageUrl" class="image-preview">
|
||||||
|
<img :src="formImageUrl" alt="公告图片预览" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<el-button type="primary" @click="submit(true)">发布并启用</el-button>
|
<el-button type="primary" @click="submit(true)">发布并启用</el-button>
|
||||||
<el-button @click="submit(false)">保存但不启用</el-button>
|
<el-button @click="submit(false)">保存但不启用</el-button>
|
||||||
@@ -193,6 +264,12 @@ onMounted(load)
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="图片" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.image_url" type="success" effect="light">有图</el-tag>
|
||||||
|
<span v-else class="app-muted">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="created_at" label="创建时间" width="180" />
|
<el-table-column prop="created_at" label="创建时间" width="180" />
|
||||||
<el-table-column label="操作" width="260" fixed="right">
|
<el-table-column label="操作" width="260" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@@ -234,6 +311,57 @@ onMounted(load)
|
|||||||
color: var(--app-muted);
|
color: var(--app-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-preview {
|
||||||
|
margin: 6px 0 2px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview img {
|
||||||
|
max-width: 280px;
|
||||||
|
max-height: 160px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--app-border);
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-upload-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-url {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--app-muted);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-view-text {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-view-image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 320px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--app-border);
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
.table-wrap {
|
.table-wrap {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
@@ -252,4 +380,3 @@ onMounted(load)
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -486,6 +486,9 @@ async function dismissAnnouncementPermanently() {
|
|||||||
<el-dialog v-model="announcementOpen" width="min(560px, 92vw)" :title="announcement?.title || '系统公告'">
|
<el-dialog v-model="announcementOpen" width="min(560px, 92vw)" :title="announcement?.title || '系统公告'">
|
||||||
<div class="announcement-body" v-loading="announcementLoading">
|
<div class="announcement-body" v-loading="announcementLoading">
|
||||||
<div class="announcement-content">{{ announcement?.content || '' }}</div>
|
<div class="announcement-content">{{ announcement?.content || '' }}</div>
|
||||||
|
<div v-if="announcement?.image_url" class="announcement-image">
|
||||||
|
<img :src="announcement.image_url" alt="公告图片" loading="lazy" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="closeAnnouncementOnce">当次关闭</el-button>
|
<el-button @click="closeAnnouncementOnce">当次关闭</el-button>
|
||||||
@@ -779,6 +782,20 @@ async function dismissAnnouncementPermanently() {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.announcement-image {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-image img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 320px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--app-border);
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
.feedback-title {
|
.feedback-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -123,6 +123,11 @@ class Config:
|
|||||||
SCREENSHOTS_DIR = os.environ.get('SCREENSHOTS_DIR', '截图')
|
SCREENSHOTS_DIR = os.environ.get('SCREENSHOTS_DIR', '截图')
|
||||||
COOKIES_DIR = os.environ.get('COOKIES_DIR', 'data/cookies')
|
COOKIES_DIR = os.environ.get('COOKIES_DIR', 'data/cookies')
|
||||||
|
|
||||||
|
# ==================== 公告图片上传配置 ====================
|
||||||
|
ANNOUNCEMENT_IMAGE_DIR = os.environ.get('ANNOUNCEMENT_IMAGE_DIR', 'static/announcements')
|
||||||
|
ALLOWED_ANNOUNCEMENT_IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp'}
|
||||||
|
MAX_ANNOUNCEMENT_IMAGE_SIZE = int(os.environ.get('MAX_ANNOUNCEMENT_IMAGE_SIZE', '5242880')) # 5MB
|
||||||
|
|
||||||
# ==================== 并发控制配置 ====================
|
# ==================== 并发控制配置 ====================
|
||||||
MAX_CONCURRENT_GLOBAL = int(os.environ.get('MAX_CONCURRENT_GLOBAL', '2'))
|
MAX_CONCURRENT_GLOBAL = int(os.environ.get('MAX_CONCURRENT_GLOBAL', '2'))
|
||||||
MAX_CONCURRENT_PER_ACCOUNT = int(os.environ.get('MAX_CONCURRENT_PER_ACCOUNT', '1'))
|
MAX_CONCURRENT_PER_ACCOUNT = int(os.environ.get('MAX_CONCURRENT_PER_ACCOUNT', '1'))
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ config = get_config()
|
|||||||
DB_FILE = config.DB_FILE
|
DB_FILE = config.DB_FILE
|
||||||
|
|
||||||
# 数据库版本 (用于迁移管理)
|
# 数据库版本 (用于迁移管理)
|
||||||
DB_VERSION = 15
|
DB_VERSION = 16
|
||||||
|
|
||||||
|
|
||||||
# ==================== 系统配置缓存(P1 / O-03) ====================
|
# ==================== 系统配置缓存(P1 / O-03) ====================
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import db_pool
|
|||||||
from db.utils import get_cst_now_str
|
from db.utils import get_cst_now_str
|
||||||
|
|
||||||
|
|
||||||
def create_announcement(title, content, is_active=True):
|
def create_announcement(title, content, image_url=None, is_active=True):
|
||||||
"""创建公告(默认启用;启用时会自动停用其他公告)"""
|
"""创建公告(默认启用;启用时会自动停用其他公告)"""
|
||||||
title = (title or "").strip()
|
title = (title or "").strip()
|
||||||
content = (content or "").strip()
|
content = (content or "").strip()
|
||||||
|
image_url = (image_url or "").strip()
|
||||||
|
image_url = image_url or None
|
||||||
if not title or not content:
|
if not title or not content:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -22,10 +24,10 @@ def create_announcement(title, content, is_active=True):
|
|||||||
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO announcements (title, content, is_active, created_at, updated_at)
|
INSERT INTO announcements (title, content, image_url, is_active, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(title, content, 1 if is_active else 0, cst_time, cst_time),
|
(title, content, image_url, 1 if is_active else 0, cst_time, cst_time),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
return cursor.lastrowid
|
return cursor.lastrowid
|
||||||
@@ -129,4 +131,3 @@ def dismiss_announcement_for_user(user_id, announcement_id):
|
|||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
return cursor.rowcount >= 0
|
return cursor.rowcount >= 0
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ def migrate_database(conn, target_version: int) -> None:
|
|||||||
if current_version < 15:
|
if current_version < 15:
|
||||||
_migrate_to_v15(conn)
|
_migrate_to_v15(conn)
|
||||||
current_version = 15
|
current_version = 15
|
||||||
|
if current_version < 16:
|
||||||
|
_migrate_to_v16(conn)
|
||||||
|
current_version = 16
|
||||||
|
|
||||||
if current_version != int(target_version):
|
if current_version != int(target_version):
|
||||||
set_current_version(conn, int(target_version))
|
set_current_version(conn, int(target_version))
|
||||||
@@ -672,3 +675,15 @@ def _migrate_to_v15(conn):
|
|||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_to_v16(conn):
|
||||||
|
"""迁移到版本16 - 公告支持图片字段"""
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("PRAGMA table_info(announcements)")
|
||||||
|
columns = [col[1] for col in cursor.fetchall()]
|
||||||
|
|
||||||
|
if "image_url" not in columns:
|
||||||
|
cursor.execute("ALTER TABLE announcements ADD COLUMN image_url TEXT")
|
||||||
|
conn.commit()
|
||||||
|
print(" ✓ 添加 announcements.image_url 字段")
|
||||||
|
|||||||
@@ -276,6 +276,7 @@ def ensure_schema(conn) -> None:
|
|||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
|
image_url TEXT,
|
||||||
is_active INTEGER DEFAULT 1,
|
is_active INTEGER DEFAULT 1,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import posixpath
|
||||||
|
import secrets
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -15,7 +17,9 @@ from app_logger import get_logger
|
|||||||
from app_security import (
|
from app_security import (
|
||||||
get_rate_limit_ip,
|
get_rate_limit_ip,
|
||||||
is_safe_outbound_url,
|
is_safe_outbound_url,
|
||||||
|
is_safe_path,
|
||||||
require_ip_not_locked,
|
require_ip_not_locked,
|
||||||
|
sanitize_filename,
|
||||||
validate_email,
|
validate_email,
|
||||||
validate_password,
|
validate_password,
|
||||||
)
|
)
|
||||||
@@ -91,6 +95,24 @@ def _require_admin_reauth():
|
|||||||
return jsonify({"error": "需要二次确认", "code": "reauth_required"}), 401
|
return jsonify({"error": "需要二次确认", "code": "reauth_required"}), 401
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_upload_dir():
|
||||||
|
rel_dir = getattr(config, "ANNOUNCEMENT_IMAGE_DIR", "static/announcements")
|
||||||
|
if not is_safe_path(current_app.root_path, rel_dir):
|
||||||
|
rel_dir = "static/announcements"
|
||||||
|
abs_dir = os.path.join(current_app.root_path, rel_dir)
|
||||||
|
os.makedirs(abs_dir, exist_ok=True)
|
||||||
|
return abs_dir, rel_dir
|
||||||
|
|
||||||
|
|
||||||
|
def _get_file_size(file_storage):
|
||||||
|
try:
|
||||||
|
file_storage.stream.seek(0, os.SEEK_END)
|
||||||
|
size = file_storage.stream.tell()
|
||||||
|
file_storage.stream.seek(0)
|
||||||
|
return size
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@admin_api_bp.route("/debug-config", methods=["GET"])
|
@admin_api_bp.route("/debug-config", methods=["GET"])
|
||||||
@admin_required
|
@admin_required
|
||||||
@@ -229,6 +251,42 @@ def admin_reauth():
|
|||||||
# ==================== 公告管理API(管理员) ====================
|
# ==================== 公告管理API(管理员) ====================
|
||||||
|
|
||||||
|
|
||||||
|
@admin_api_bp.route("/announcements/upload_image", methods=["POST"])
|
||||||
|
@admin_required
|
||||||
|
def admin_upload_announcement_image():
|
||||||
|
"""上传公告图片(返回可访问URL)"""
|
||||||
|
file = request.files.get("file")
|
||||||
|
if not file or not file.filename:
|
||||||
|
return jsonify({"error": "请选择图片"}), 400
|
||||||
|
|
||||||
|
filename = sanitize_filename(file.filename)
|
||||||
|
ext = os.path.splitext(filename)[1].lower()
|
||||||
|
allowed_exts = getattr(config, "ALLOWED_ANNOUNCEMENT_IMAGE_EXTENSIONS", {".png", ".jpg", ".jpeg"})
|
||||||
|
if not ext or ext not in allowed_exts:
|
||||||
|
return jsonify({"error": "不支持的图片格式"}), 400
|
||||||
|
if file.mimetype and not str(file.mimetype).startswith("image/"):
|
||||||
|
return jsonify({"error": "文件类型无效"}), 400
|
||||||
|
|
||||||
|
size = _get_file_size(file)
|
||||||
|
max_size = int(getattr(config, "MAX_ANNOUNCEMENT_IMAGE_SIZE", 5 * 1024 * 1024))
|
||||||
|
if size is not None and size > max_size:
|
||||||
|
max_mb = max_size // 1024 // 1024
|
||||||
|
return jsonify({"error": f"图片大小不能超过{max_mb}MB"}), 400
|
||||||
|
|
||||||
|
abs_dir, rel_dir = _get_upload_dir()
|
||||||
|
token = secrets.token_hex(6)
|
||||||
|
name = f"announcement_{int(time.time())}_{token}{ext}"
|
||||||
|
save_path = os.path.join(abs_dir, name)
|
||||||
|
file.save(save_path)
|
||||||
|
|
||||||
|
static_root = os.path.join(current_app.root_path, "static")
|
||||||
|
rel_to_static = os.path.relpath(abs_dir, static_root)
|
||||||
|
if rel_to_static.startswith(".."):
|
||||||
|
rel_to_static = "announcements"
|
||||||
|
url_path = posixpath.join(rel_to_static.replace(os.sep, "/"), name)
|
||||||
|
return jsonify({"success": True, "url": url_for("serve_static", filename=url_path)})
|
||||||
|
|
||||||
|
|
||||||
@admin_api_bp.route("/announcements", methods=["GET"])
|
@admin_api_bp.route("/announcements", methods=["GET"])
|
||||||
@admin_required
|
@admin_required
|
||||||
def admin_get_announcements():
|
def admin_get_announcements():
|
||||||
@@ -251,9 +309,13 @@ def admin_create_announcement():
|
|||||||
data = request.json or {}
|
data = request.json or {}
|
||||||
title = (data.get("title") or "").strip()
|
title = (data.get("title") or "").strip()
|
||||||
content = (data.get("content") or "").strip()
|
content = (data.get("content") or "").strip()
|
||||||
|
image_url = (data.get("image_url") or "").strip()
|
||||||
is_active = bool(data.get("is_active", True))
|
is_active = bool(data.get("is_active", True))
|
||||||
|
|
||||||
announcement_id = database.create_announcement(title, content, is_active=is_active)
|
if image_url and len(image_url) > 1000:
|
||||||
|
return jsonify({"error": "图片地址过长"}), 400
|
||||||
|
|
||||||
|
announcement_id = database.create_announcement(title, content, image_url=image_url, is_active=is_active)
|
||||||
if not announcement_id:
|
if not announcement_id:
|
||||||
return jsonify({"error": "标题和内容不能为空"}), 400
|
return jsonify({"error": "标题和内容不能为空"}), 400
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ def get_active_announcement():
|
|||||||
"id": announcement.get("id"),
|
"id": announcement.get("id"),
|
||||||
"title": announcement.get("title", ""),
|
"title": announcement.get("title", ""),
|
||||||
"content": announcement.get("content", ""),
|
"content": announcement.get("content", ""),
|
||||||
|
"image_url": announcement.get("image_url") or "",
|
||||||
"created_at": announcement.get("created_at"),
|
"created_at": announcement.get("created_at"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
{
|
{
|
||||||
"_email-ByiJ74rd.js": {
|
"_email-Ct3wXHpw.js": {
|
||||||
"file": "assets/email-ByiJ74rd.js",
|
"file": "assets/email-Ct3wXHpw.js",
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_system-CS-UmZao.js": {
|
"_system-BTlwVb4V.js": {
|
||||||
"file": "assets/system-CS-UmZao.js",
|
"file": "assets/system-BTlwVb4V.js",
|
||||||
"name": "system",
|
"name": "system",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_tasks-DM1Bwi9j.js": {
|
"_tasks-DjrYX1kb.js": {
|
||||||
"file": "assets/tasks-DM1Bwi9j.js",
|
"file": "assets/tasks-DjrYX1kb.js",
|
||||||
"name": "tasks",
|
"name": "tasks",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_users-B1w166uc.js": {
|
"_users-DXwhA9ak.js": {
|
||||||
"file": "assets/users-B1w166uc.js",
|
"file": "assets/users-DXwhA9ak.js",
|
||||||
"name": "users",
|
"name": "users",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"index.html": {
|
"index.html": {
|
||||||
"file": "assets/index-DL8gIzDq.js",
|
"file": "assets/index-CRihQT8G.js",
|
||||||
"name": "index",
|
"name": "index",
|
||||||
"src": "index.html",
|
"src": "index.html",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
@@ -44,11 +44,11 @@
|
|||||||
"src/pages/SettingsPage.vue"
|
"src/pages/SettingsPage.vue"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/index-_5Ec1Hmd.css"
|
"assets/index-DxTKnDeo.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/AnnouncementsPage.vue": {
|
"src/pages/AnnouncementsPage.vue": {
|
||||||
"file": "assets/AnnouncementsPage-Btl9JP7M.js",
|
"file": "assets/AnnouncementsPage-BVJSt6Za.js",
|
||||||
"name": "AnnouncementsPage",
|
"name": "AnnouncementsPage",
|
||||||
"src": "src/pages/AnnouncementsPage.vue",
|
"src": "src/pages/AnnouncementsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -56,16 +56,16 @@
|
|||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/AnnouncementsPage-CjcC-aWD.css"
|
"assets/AnnouncementsPage-BhIwmMSX.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/EmailPage.vue": {
|
"src/pages/EmailPage.vue": {
|
||||||
"file": "assets/EmailPage-Cp_IS7bH.js",
|
"file": "assets/EmailPage-BU-aLz58.js",
|
||||||
"name": "EmailPage",
|
"name": "EmailPage",
|
||||||
"src": "src/pages/EmailPage.vue",
|
"src": "src/pages/EmailPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_email-ByiJ74rd.js",
|
"_email-Ct3wXHpw.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/FeedbacksPage.vue": {
|
"src/pages/FeedbacksPage.vue": {
|
||||||
"file": "assets/FeedbacksPage-BqmEZmow.js",
|
"file": "assets/FeedbacksPage-BeAqV1pz.js",
|
||||||
"name": "FeedbacksPage",
|
"name": "FeedbacksPage",
|
||||||
"src": "src/pages/FeedbacksPage.vue",
|
"src": "src/pages/FeedbacksPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -85,13 +85,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/LogsPage.vue": {
|
"src/pages/LogsPage.vue": {
|
||||||
"file": "assets/LogsPage-CcePQXnM.js",
|
"file": "assets/LogsPage-C25hwKy7.js",
|
||||||
"name": "LogsPage",
|
"name": "LogsPage",
|
||||||
"src": "src/pages/LogsPage.vue",
|
"src": "src/pages/LogsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_users-B1w166uc.js",
|
"_users-DXwhA9ak.js",
|
||||||
"_tasks-DM1Bwi9j.js",
|
"_tasks-DjrYX1kb.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -99,22 +99,22 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/ReportPage.vue": {
|
"src/pages/ReportPage.vue": {
|
||||||
"file": "assets/ReportPage-CHVUWk0n.js",
|
"file": "assets/ReportPage-DcTJhEeS.js",
|
||||||
"name": "ReportPage",
|
"name": "ReportPage",
|
||||||
"src": "src/pages/ReportPage.vue",
|
"src": "src/pages/ReportPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_email-ByiJ74rd.js",
|
"_email-Ct3wXHpw.js",
|
||||||
"_tasks-DM1Bwi9j.js",
|
"_tasks-DjrYX1kb.js",
|
||||||
"_system-CS-UmZao.js"
|
"_system-BTlwVb4V.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/ReportPage-Q8rCsG8A.css"
|
"assets/ReportPage-Q8rCsG8A.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/SecurityPage.vue": {
|
"src/pages/SecurityPage.vue": {
|
||||||
"file": "assets/SecurityPage-DTjNosEF.js",
|
"file": "assets/SecurityPage-6yFvpr3a.js",
|
||||||
"name": "SecurityPage",
|
"name": "SecurityPage",
|
||||||
"src": "src/pages/SecurityPage.vue",
|
"src": "src/pages/SecurityPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/SettingsPage.vue": {
|
"src/pages/SettingsPage.vue": {
|
||||||
"file": "assets/SettingsPage-68qb85yN.js",
|
"file": "assets/SettingsPage-CixYahEQ.js",
|
||||||
"name": "SettingsPage",
|
"name": "SettingsPage",
|
||||||
"src": "src/pages/SettingsPage.vue",
|
"src": "src/pages/SettingsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -138,12 +138,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/SystemPage.vue": {
|
"src/pages/SystemPage.vue": {
|
||||||
"file": "assets/SystemPage-DHToRQFv.js",
|
"file": "assets/SystemPage-oGtrzs5v.js",
|
||||||
"name": "SystemPage",
|
"name": "SystemPage",
|
||||||
"src": "src/pages/SystemPage.vue",
|
"src": "src/pages/SystemPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_system-CS-UmZao.js",
|
"_system-BTlwVb4V.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -151,12 +151,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/UsersPage.vue": {
|
"src/pages/UsersPage.vue": {
|
||||||
"file": "assets/UsersPage-lUtZTPoX.js",
|
"file": "assets/UsersPage-1x6SHDoq.js",
|
||||||
"name": "UsersPage",
|
"name": "UsersPage",
|
||||||
"src": "src/pages/UsersPage.vue",
|
"src": "src/pages/UsersPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_users-B1w166uc.js",
|
"_users-DXwhA9ak.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
|
|||||||
1
static/admin/assets/AnnouncementsPage-BVJSt6Za.js
Normal file
1
static/admin/assets/AnnouncementsPage-BVJSt6Za.js
Normal file
File diff suppressed because one or more lines are too long
1
static/admin/assets/AnnouncementsPage-BhIwmMSX.css
Normal file
1
static/admin/assets/AnnouncementsPage-BhIwmMSX.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.page-stack[data-v-cad97d6b]{display:flex;flex-direction:column;gap:12px}.card[data-v-cad97d6b]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-cad97d6b]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-cad97d6b]{margin-top:10px;font-size:12px;color:var(--app-muted)}.image-preview[data-v-cad97d6b]{margin:6px 0 2px;display:flex;justify-content:flex-start}.image-preview img[data-v-cad97d6b]{max-width:280px;max-height:160px;border-radius:8px;border:1px solid var(--app-border);object-fit:contain}.image-upload-row[data-v-cad97d6b]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.image-input[data-v-cad97d6b]{display:none}.image-url[data-v-cad97d6b]{font-size:12px;color:var(--app-muted);word-break:break-all}.announcement-view[data-v-cad97d6b]{display:flex;flex-direction:column;gap:12px}.announcement-view-text[data-v-cad97d6b]{white-space:pre-wrap;line-height:1.6;font-size:14px}.announcement-view-image[data-v-cad97d6b]{max-width:100%;max-height:320px;border-radius:10px;border:1px solid var(--app-border);object-fit:contain}.table-wrap[data-v-cad97d6b]{overflow-x:auto}.ellipsis[data-v-cad97d6b]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.actions[data-v-cad97d6b]{display:flex;flex-wrap:wrap;gap:8px}
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.page-stack[data-v-a7b3418e]{display:flex;flex-direction:column;gap:12px}.card[data-v-a7b3418e]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-a7b3418e]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-a7b3418e]{margin-top:10px;font-size:12px;color:var(--app-muted)}.table-wrap[data-v-a7b3418e]{overflow-x:auto}.ellipsis[data-v-a7b3418e]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.actions[data-v-a7b3418e]{display:flex;flex-wrap:wrap;gap:8px}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{a as m,_ as B,r as p,f as u,g as T,h as P,j as r,m as a,w as l,q as x,L as i,K as b}from"./index-DL8gIzDq.js";async function C(o){const{data:s}=await m.put("/admin/username",{new_username:o});return s}async function S(o){const{data:s}=await m.put("/admin/password",{new_password:o});return s}async function U(){const{data:o}=await m.post("/logout");return o}const A={class:"page-stack"},E={__name:"SettingsPage",setup(o){const s=p(""),d=p(""),n=p(!1);function k(t){const e=String(t||"");return e.length<8?{ok:!1,message:"密码长度至少8位"}:e.length>128?{ok:!1,message:"密码长度不能超过128个字符"}:!/[a-zA-Z]/.test(e)||!/\d/.test(e)?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}async function f(){try{await U()}catch{}finally{window.location.href="/yuyx"}}async function V(){const t=s.value.trim();if(!t){i.error("请输入新用户名");return}try{await b.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await C(t),i.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function h(){const t=d.value;if(!t){i.error("请输入新密码");return}const e=k(t);if(!e.ok){i.error(e.message);return}try{await b.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await S(t),i.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=u("el-input"),w=u("el-form-item"),v=u("el-form"),y=u("el-button"),_=u("el-card");return P(),T("div",A,[e[7]||(e[7]=r("div",{class:"app-page-title"},[r("h2",null,"设置"),r("span",{class:"app-muted"},"管理员账号设置")],-1)),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(v,{"label-width":"120px"},{default:l(()=>[a(w,{label:"新用户名"},{default:l(()=>[a(g,{modelValue:s.value,"onUpdate:modelValue":e[0]||(e[0]=c=>s.value=c),placeholder:"输入新用户名",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:V},{default:l(()=>[...e[2]||(e[2]=[x("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(v,{"label-width":"120px"},{default:l(()=>[a(w,{label:"新密码"},{default:l(()=>[a(g,{modelValue:d.value,"onUpdate:modelValue":e[1]||(e[1]=c=>d.value=c),type:"password","show-password":"",placeholder:"输入新密码",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:h},{default:l(()=>[...e[4]||(e[4]=[x("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},M=B(E,[["__scopeId","data-v-12a26d11"]]);export{M as default};
|
import{a as m,_ as B,r as p,f as u,g as T,h as P,j as r,m as a,w as l,q as x,L as i,K as b}from"./index-CRihQT8G.js";async function C(o){const{data:s}=await m.put("/admin/username",{new_username:o});return s}async function S(o){const{data:s}=await m.put("/admin/password",{new_password:o});return s}async function U(){const{data:o}=await m.post("/logout");return o}const A={class:"page-stack"},E={__name:"SettingsPage",setup(o){const s=p(""),d=p(""),n=p(!1);function k(t){const e=String(t||"");return e.length<8?{ok:!1,message:"密码长度至少8位"}:e.length>128?{ok:!1,message:"密码长度不能超过128个字符"}:!/[a-zA-Z]/.test(e)||!/\d/.test(e)?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}async function f(){try{await U()}catch{}finally{window.location.href="/yuyx"}}async function V(){const t=s.value.trim();if(!t){i.error("请输入新用户名");return}try{await b.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await C(t),i.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function h(){const t=d.value;if(!t){i.error("请输入新密码");return}const e=k(t);if(!e.ok){i.error(e.message);return}try{await b.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await S(t),i.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=u("el-input"),w=u("el-form-item"),v=u("el-form"),y=u("el-button"),_=u("el-card");return P(),T("div",A,[e[7]||(e[7]=r("div",{class:"app-page-title"},[r("h2",null,"设置"),r("span",{class:"app-muted"},"管理员账号设置")],-1)),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(v,{"label-width":"120px"},{default:l(()=>[a(w,{label:"新用户名"},{default:l(()=>[a(g,{modelValue:s.value,"onUpdate:modelValue":e[0]||(e[0]=c=>s.value=c),placeholder:"输入新用户名",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:V},{default:l(()=>[...e[2]||(e[2]=[x("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(v,{"label-width":"120px"},{default:l(()=>[a(w,{label:"新密码"},{default:l(()=>[a(g,{modelValue:d.value,"onUpdate:modelValue":e[1]||(e[1]=c=>d.value=c),type:"password","show-password":"",placeholder:"输入新密码",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:h},{default:l(()=>[...e[4]||(e[4]=[x("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},M=B(E,[["__scopeId","data-v-12a26d11"]]);export{M as default};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import{f as te,u as D,e as oe}from"./system-CS-UmZao.js";import{a as H,_ as ne,r as u,c as ue,o as se,f as i,I as re,J as de,h as x,g as j,j as n,m as l,w as t,q as c,B as S,n as E,F as ie,v as me,p as pe,K as $,L as p}from"./index-DL8gIzDq.js";async function ce(){const{data:f}=await H.get("/proxy/config");return f}async function ve(f){const{data:v}=await H.post("/proxy/config",f);return v}async function ye(f){const{data:v}=await H.post("/proxy/test",f);return v}const _e={class:"page-stack"},fe={class:"app-page-title"},xe={class:"row-actions"},be={class:"row-actions"},Ve={__name:"SystemPage",setup(f){const v=u(!1),w=u(2),k=u(1),P=u(3),m=u(!1),C=u("02:00"),b=u("应读"),y=u(["1","2","3","4","5","6","7"]),V=u(!0),g=u(!1),_=u(""),h=u(3),B=u(!1),N=u(10),I=u(7),L=[{label:"周一",value:"1"},{label:"周二",value:"2"},{label:"周三",value:"3"},{label:"周四",value:"4"},{label:"周五",value:"5"},{label:"周六",value:"6"},{label:"周日",value:"7"}],O={1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"},W=ue(()=>(y.value||[]).map(a=>O[Number(a)]||a).join("、"));function q(a){return String(a)==="注册前未读"?"注册前未读":"应读"}async function F(){v.value=!0;try{const[a,e]=await Promise.all([te(),ce()]);w.value=a.max_concurrent_global??2,k.value=a.max_concurrent_per_account??1,P.value=a.max_screenshot_concurrent??3,m.value=(a.schedule_enabled??0)===1,C.value=a.schedule_time||"02:00",b.value=q(a.schedule_browse_type);const s=String(a.schedule_weekdays||"1,2,3,4,5,6,7").split(",").map(d=>d.trim()).filter(Boolean);y.value=s.length?s:["1","2","3","4","5","6","7"],V.value=(a.enable_screenshot??1)===1,B.value=(a.auto_approve_enabled??0)===1,N.value=a.auto_approve_hourly_limit??10,I.value=a.auto_approve_vip_days??7,g.value=(e.proxy_enabled??0)===1,_.value=e.proxy_api_url||"",h.value=e.proxy_expire_minutes??3}catch{}finally{v.value=!1}}async function z(){const a={max_concurrent_global:Number(w.value),max_concurrent_per_account:Number(k.value),max_screenshot_concurrent:Number(P.value)};try{await $.confirm(`确定更新并发配置吗?
|
import{f as te,u as D,e as oe}from"./system-BTlwVb4V.js";import{a as H,_ as ne,r as u,c as ue,o as se,f as i,I as re,J as de,h as x,g as j,j as n,m as l,w as t,q as c,B as S,n as E,F as ie,v as me,p as pe,K as $,L as p}from"./index-CRihQT8G.js";async function ce(){const{data:f}=await H.get("/proxy/config");return f}async function ve(f){const{data:v}=await H.post("/proxy/config",f);return v}async function ye(f){const{data:v}=await H.post("/proxy/test",f);return v}const _e={class:"page-stack"},fe={class:"app-page-title"},xe={class:"row-actions"},be={class:"row-actions"},Ve={__name:"SystemPage",setup(f){const v=u(!1),w=u(2),k=u(1),P=u(3),m=u(!1),C=u("02:00"),b=u("应读"),y=u(["1","2","3","4","5","6","7"]),V=u(!0),g=u(!1),_=u(""),h=u(3),B=u(!1),N=u(10),I=u(7),L=[{label:"周一",value:"1"},{label:"周二",value:"2"},{label:"周三",value:"3"},{label:"周四",value:"4"},{label:"周五",value:"5"},{label:"周六",value:"6"},{label:"周日",value:"7"}],O={1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"},W=ue(()=>(y.value||[]).map(a=>O[Number(a)]||a).join("、"));function q(a){return String(a)==="注册前未读"?"注册前未读":"应读"}async function F(){v.value=!0;try{const[a,e]=await Promise.all([te(),ce()]);w.value=a.max_concurrent_global??2,k.value=a.max_concurrent_per_account??1,P.value=a.max_screenshot_concurrent??3,m.value=(a.schedule_enabled??0)===1,C.value=a.schedule_time||"02:00",b.value=q(a.schedule_browse_type);const s=String(a.schedule_weekdays||"1,2,3,4,5,6,7").split(",").map(d=>d.trim()).filter(Boolean);y.value=s.length?s:["1","2","3","4","5","6","7"],V.value=(a.enable_screenshot??1)===1,B.value=(a.auto_approve_enabled??0)===1,N.value=a.auto_approve_hourly_limit??10,I.value=a.auto_approve_vip_days??7,g.value=(e.proxy_enabled??0)===1,_.value=e.proxy_api_url||"",h.value=e.proxy_expire_minutes??3}catch{}finally{v.value=!1}}async function z(){const a={max_concurrent_global:Number(w.value),max_concurrent_per_account:Number(k.value),max_screenshot_concurrent:Number(P.value)};try{await $.confirm(`确定更新并发配置吗?
|
||||||
|
|
||||||
全局并发数: ${a.max_concurrent_global}
|
全局并发数: ${a.max_concurrent_global}
|
||||||
单账号并发数: ${a.max_concurrent_per_account}
|
单账号并发数: ${a.max_concurrent_per_account}
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{a as n}from"./index-DL8gIzDq.js";async function i(){const{data:a}=await n.get("/email/settings");return a}async function e(a){const{data:t}=await n.post("/email/settings",a);return t}async function c(){const{data:a}=await n.get("/email/stats");return a}async function o(a){const{data:t}=await n.get("/email/logs",{params:a});return t}async function l(a){const{data:t}=await n.post("/email/logs/cleanup",{days:a});return t}export{o as a,i as b,l as c,c as f,e as u};
|
import{a as n}from"./index-CRihQT8G.js";async function i(){const{data:a}=await n.get("/email/settings");return a}async function e(a){const{data:t}=await n.post("/email/settings",a);return t}async function c(){const{data:a}=await n.get("/email/stats");return a}async function o(a){const{data:t}=await n.get("/email/logs",{params:a});return t}async function l(a){const{data:t}=await n.post("/email/logs/cleanup",{days:a});return t}export{o as a,i as b,l as c,c as f,e as u};
|
||||||
30
static/admin/assets/index-CRihQT8G.js
Normal file
30
static/admin/assets/index-CRihQT8G.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
static/admin/assets/index-DxTKnDeo.css
Normal file
1
static/admin/assets/index-DxTKnDeo.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{a}from"./index-DL8gIzDq.js";async function s(){const{data:t}=await a.get("/system/config");return t}async function c(t){const{data:e}=await a.post("/system/config",t);return e}async function o(){const{data:t}=await a.post("/schedule/execute",{});return t}export{o as e,s as f,c as u};
|
import{a}from"./index-CRihQT8G.js";async function s(){const{data:t}=await a.get("/system/config");return t}async function c(t){const{data:e}=await a.post("/system/config",t);return e}async function o(){const{data:t}=await a.post("/schedule/execute",{});return t}export{o as e,s as f,c as u};
|
||||||
@@ -1 +1 @@
|
|||||||
import{a}from"./index-DL8gIzDq.js";async function c(){const{data:t}=await a.get("/server/info");return t}async function e(){const{data:t}=await a.get("/docker_stats");return t}async function o(){const{data:t}=await a.get("/task/stats");return t}async function r(){const{data:t}=await a.get("/task/running");return t}async function i(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function f(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{r as a,c as b,e as c,i as d,f as e,o as f};
|
import{a}from"./index-CRihQT8G.js";async function c(){const{data:t}=await a.get("/server/info");return t}async function e(){const{data:t}=await a.get("/docker_stats");return t}async function o(){const{data:t}=await a.get("/task/stats");return t}async function r(){const{data:t}=await a.get("/task/running");return t}async function i(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function f(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{r as a,c as b,e as c,i as d,f as e,o as f};
|
||||||
@@ -1 +1 @@
|
|||||||
import{a as t}from"./index-DL8gIzDq.js";async function n(){const{data:s}=await t.get("/users");return s}async function o(s){const{data:a}=await t.post(`/users/${s}/approve`);return a}async function c(s){const{data:a}=await t.post(`/users/${s}/reject`);return a}async function i(s){const{data:a}=await t.delete(`/users/${s}`);return a}async function u(s,a){const{data:e}=await t.post(`/users/${s}/vip`,{days:a});return e}async function p(s){const{data:a}=await t.delete(`/users/${s}/vip`);return a}async function d(s,a){const{data:e}=await t.post(`/users/${s}/reset_password`,{new_password:a});return e}export{o as a,p as b,d as c,i as d,n as f,c as r,u as s};
|
import{a as t}from"./index-CRihQT8G.js";async function n(){const{data:s}=await t.get("/users");return s}async function o(s){const{data:a}=await t.post(`/users/${s}/approve`);return a}async function c(s){const{data:a}=await t.post(`/users/${s}/reject`);return a}async function i(s){const{data:a}=await t.delete(`/users/${s}`);return a}async function u(s,a){const{data:e}=await t.post(`/users/${s}/vip`,{days:a});return e}async function p(s){const{data:a}=await t.delete(`/users/${s}/vip`);return a}async function d(s,a){const{data:e}=await t.post(`/users/${s}/reset_password`,{new_password:a});return e}export{o as a,p as b,d as c,i as d,n as f,c as r,u as s};
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>后台管理 - 知识管理平台</title>
|
<title>后台管理 - 知识管理平台</title>
|
||||||
<script type="module" crossorigin src="./assets/index-DL8gIzDq.js"></script>
|
<script type="module" crossorigin src="./assets/index-CRihQT8G.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-_5Ec1Hmd.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-DxTKnDeo.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"_accounts-BYq3lLev.js": {
|
"_accounts-XHQJZlkc.js": {
|
||||||
"file": "assets/accounts-BYq3lLev.js",
|
"file": "assets/accounts-XHQJZlkc.js",
|
||||||
"name": "accounts",
|
"name": "accounts",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_auth-xwgKTSGk.js": {
|
"_auth-CXXxlo9_.js": {
|
||||||
"file": "assets/auth-xwgKTSGk.js",
|
"file": "assets/auth-CXXxlo9_.js",
|
||||||
"name": "auth",
|
"name": "auth",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"index.html": {
|
"index.html": {
|
||||||
"file": "assets/index-CGLq5RWF.js",
|
"file": "assets/index-B3Praptc.js",
|
||||||
"name": "index",
|
"name": "index",
|
||||||
"src": "index.html",
|
"src": "index.html",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
@@ -28,16 +28,16 @@
|
|||||||
"src/pages/ScreenshotsPage.vue"
|
"src/pages/ScreenshotsPage.vue"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/index-BIng7uZJ.css"
|
"assets/index-HG0IyblY.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/AccountsPage.vue": {
|
"src/pages/AccountsPage.vue": {
|
||||||
"file": "assets/AccountsPage-D387XNsv.js",
|
"file": "assets/AccountsPage-BWLUZGwv.js",
|
||||||
"name": "AccountsPage",
|
"name": "AccountsPage",
|
||||||
"src": "src/pages/AccountsPage.vue",
|
"src": "src/pages/AccountsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_accounts-BYq3lLev.js",
|
"_accounts-XHQJZlkc.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -45,51 +45,51 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/LoginPage.vue": {
|
"src/pages/LoginPage.vue": {
|
||||||
"file": "assets/LoginPage-D_X-BqcF.js",
|
"file": "assets/LoginPage-iG0xN46c.js",
|
||||||
"name": "LoginPage",
|
"name": "LoginPage",
|
||||||
"src": "src/pages/LoginPage.vue",
|
"src": "src/pages/LoginPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_auth-xwgKTSGk.js"
|
"_auth-CXXxlo9_.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/LoginPage-CnwOLKJz.css"
|
"assets/LoginPage-CnwOLKJz.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/RegisterPage.vue": {
|
"src/pages/RegisterPage.vue": {
|
||||||
"file": "assets/RegisterPage-DxPLUv5i.js",
|
"file": "assets/RegisterPage-DKtQs7_M.js",
|
||||||
"name": "RegisterPage",
|
"name": "RegisterPage",
|
||||||
"src": "src/pages/RegisterPage.vue",
|
"src": "src/pages/RegisterPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_auth-xwgKTSGk.js"
|
"_auth-CXXxlo9_.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/RegisterPage-BOcNcW5D.css"
|
"assets/RegisterPage-BOcNcW5D.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/ResetPasswordPage.vue": {
|
"src/pages/ResetPasswordPage.vue": {
|
||||||
"file": "assets/ResetPasswordPage-pZ9Ly5yR.js",
|
"file": "assets/ResetPasswordPage-DY3NSKCZ.js",
|
||||||
"name": "ResetPasswordPage",
|
"name": "ResetPasswordPage",
|
||||||
"src": "src/pages/ResetPasswordPage.vue",
|
"src": "src/pages/ResetPasswordPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_auth-xwgKTSGk.js"
|
"_auth-CXXxlo9_.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/ResetPasswordPage-DybfLMAw.css"
|
"assets/ResetPasswordPage-DybfLMAw.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/SchedulesPage.vue": {
|
"src/pages/SchedulesPage.vue": {
|
||||||
"file": "assets/SchedulesPage-CWuZpJ5h.js",
|
"file": "assets/SchedulesPage-CtGw4yPv.js",
|
||||||
"name": "SchedulesPage",
|
"name": "SchedulesPage",
|
||||||
"src": "src/pages/SchedulesPage.vue",
|
"src": "src/pages/SchedulesPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_accounts-BYq3lLev.js",
|
"_accounts-XHQJZlkc.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/ScreenshotsPage.vue": {
|
"src/pages/ScreenshotsPage.vue": {
|
||||||
"file": "assets/ScreenshotsPage-DuTeRzLR.js",
|
"file": "assets/ScreenshotsPage-BfoUmvOa.js",
|
||||||
"name": "ScreenshotsPage",
|
"name": "ScreenshotsPage",
|
||||||
"src": "src/pages/ScreenshotsPage.vue",
|
"src": "src/pages/ScreenshotsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/VerifyResultPage.vue": {
|
"src/pages/VerifyResultPage.vue": {
|
||||||
"file": "assets/VerifyResultPage-CNbQc83z.js",
|
"file": "assets/VerifyResultPage-QlobZKo3.js",
|
||||||
"name": "VerifyResultPage",
|
"name": "VerifyResultPage",
|
||||||
"src": "src/pages/VerifyResultPage.vue",
|
"src": "src/pages/VerifyResultPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{_ as M,r as j,a as d,c as B,o as A,b as U,d as l,w as o,e as v,u as H,f as b,g as n,h as N,i as E,j as P,t as q,k as S,E as c,v as z}from"./index-CGLq5RWF.js";import{g as F,f as G,b as J}from"./auth-xwgKTSGk.js";const O={class:"auth-wrap"},Q={class:"hint app-muted"},W={class:"captcha-row"},X=["src"],Y={class:"actions"},Z={__name:"RegisterPage",setup($){const T=H(),a=j({username:"",password:"",confirm_password:"",email:"",captcha:""}),f=d(!1),w=d(""),h=d(""),V=d(!1),t=d(""),_=d(""),k=d(""),K=B(()=>f.value?"邮箱 *":"邮箱(可选)"),R=B(()=>f.value?"必填,用于账号验证":"选填,用于找回密码和接收通知");async function y(){try{const u=await F();h.value=u?.session_id||"",w.value=u?.captcha_image||"",a.captcha=""}catch{h.value="",w.value=""}}async function D(){try{const u=await G();f.value=!!u?.register_verify_enabled}catch{f.value=!1}}function I(){t.value="",_.value="",k.value=""}async function C(){I();const u=a.username.trim(),e=a.password,g=a.confirm_password,s=a.email.trim(),i=a.captcha.trim();if(u.length<3){t.value="用户名至少3个字符",c.error(t.value);return}const p=z(e);if(!p.ok){t.value=p.message||"密码格式不正确",c.error(t.value);return}if(e!==g){t.value="两次输入的密码不一致",c.error(t.value);return}if(f.value&&!s){t.value="请填写邮箱地址用于账号验证",c.error(t.value);return}if(s&&!s.includes("@")){t.value="邮箱格式不正确",c.error(t.value);return}if(!i){t.value="请输入验证码",c.error(t.value);return}V.value=!0;try{const m=await J({username:u,password:e,email:s,captcha_session:h.value,captcha:i});_.value=m?.message||"注册成功",k.value=m?.need_verify?"请检查您的邮箱(包括垃圾邮件文件夹)":"",c.success("注册成功"),a.username="",a.password="",a.confirm_password="",a.email="",a.captcha="",setTimeout(()=>{window.location.href="/login"},3e3)}catch(m){const x=m?.response?.data;t.value=x?.error||"注册失败",c.error(t.value),await y()}finally{V.value=!1}}function L(){T.push("/login")}return A(async()=>{await y(),await D()}),(u,e)=>{const g=v("el-alert"),s=v("el-input"),i=v("el-form-item"),p=v("el-button"),m=v("el-form"),x=v("el-card");return b(),U("div",O,[l(x,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:o(()=>[e[11]||(e[11]=n("div",{class:"brand"},[n("div",{class:"brand-title"},"知识管理平台"),n("div",{class:"brand-sub app-muted"},"用户注册")],-1)),t.value?(b(),N(g,{key:0,type:"error",closable:!1,title:t.value,"show-icon":"",class:"alert"},null,8,["title"])):E("",!0),_.value?(b(),N(g,{key:1,type:"success",closable:!1,title:_.value,description:k.value,"show-icon":"",class:"alert"},null,8,["title","description"])):E("",!0),l(m,{"label-position":"top"},{default:o(()=>[l(i,{label:"用户名 *"},{default:o(()=>[l(s,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=r=>a.username=r),placeholder:"至少3个字符",autocomplete:"username"},null,8,["modelValue"]),e[5]||(e[5]=n("div",{class:"hint app-muted"},"至少3个字符",-1))]),_:1}),l(i,{label:"密码 *"},{default:o(()=>[l(s,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=r=>a.password=r),type:"password","show-password":"",placeholder:"至少8位且包含字母和数字",autocomplete:"new-password"},null,8,["modelValue"]),e[6]||(e[6]=n("div",{class:"hint app-muted"},"至少8位且包含字母和数字",-1))]),_:1}),l(i,{label:"确认密码 *"},{default:o(()=>[l(s,{modelValue:a.confirm_password,"onUpdate:modelValue":e[2]||(e[2]=r=>a.confirm_password=r),type:"password","show-password":"",placeholder:"请再次输入密码",autocomplete:"new-password",onKeyup:P(C,["enter"])},null,8,["modelValue"])]),_:1}),l(i,{label:K.value},{default:o(()=>[l(s,{modelValue:a.email,"onUpdate:modelValue":e[3]||(e[3]=r=>a.email=r),placeholder:"name@example.com",autocomplete:"email"},null,8,["modelValue"]),n("div",Q,q(R.value),1)]),_:1},8,["label"]),l(i,{label:"验证码 *"},{default:o(()=>[n("div",W,[l(s,{modelValue:a.captcha,"onUpdate:modelValue":e[4]||(e[4]=r=>a.captcha=r),placeholder:"请输入验证码",onKeyup:P(C,["enter"])},null,8,["modelValue"]),w.value?(b(),U("img",{key:0,class:"captcha-img",src:w.value,alt:"验证码",title:"点击刷新",onClick:y},null,8,X)):E("",!0),l(p,{onClick:y},{default:o(()=>[...e[7]||(e[7]=[S("刷新",-1)])]),_:1})])]),_:1})]),_:1}),l(p,{type:"primary",class:"submit-btn",loading:V.value,onClick:C},{default:o(()=>[...e[8]||(e[8]=[S("注册",-1)])]),_:1},8,["loading"]),n("div",Y,[e[10]||(e[10]=n("span",{class:"app-muted"},"已有账号?",-1)),l(p,{link:"",type:"primary",onClick:L},{default:o(()=>[...e[9]||(e[9]=[S("立即登录",-1)])]),_:1})])]),_:1})])}}},te=M(Z,[["__scopeId","data-v-a9d7804f"]]);export{te as default};
|
import{_ as M,r as j,a as d,c as B,o as A,b as U,d as l,w as o,e as v,u as H,f as b,g as n,h as N,i as E,j as P,t as q,k as S,E as c,v as z}from"./index-B3Praptc.js";import{g as F,f as G,b as J}from"./auth-CXXxlo9_.js";const O={class:"auth-wrap"},Q={class:"hint app-muted"},W={class:"captcha-row"},X=["src"],Y={class:"actions"},Z={__name:"RegisterPage",setup($){const T=H(),a=j({username:"",password:"",confirm_password:"",email:"",captcha:""}),f=d(!1),w=d(""),h=d(""),V=d(!1),t=d(""),_=d(""),k=d(""),K=B(()=>f.value?"邮箱 *":"邮箱(可选)"),R=B(()=>f.value?"必填,用于账号验证":"选填,用于找回密码和接收通知");async function y(){try{const u=await F();h.value=u?.session_id||"",w.value=u?.captcha_image||"",a.captcha=""}catch{h.value="",w.value=""}}async function D(){try{const u=await G();f.value=!!u?.register_verify_enabled}catch{f.value=!1}}function I(){t.value="",_.value="",k.value=""}async function C(){I();const u=a.username.trim(),e=a.password,g=a.confirm_password,s=a.email.trim(),i=a.captcha.trim();if(u.length<3){t.value="用户名至少3个字符",c.error(t.value);return}const p=z(e);if(!p.ok){t.value=p.message||"密码格式不正确",c.error(t.value);return}if(e!==g){t.value="两次输入的密码不一致",c.error(t.value);return}if(f.value&&!s){t.value="请填写邮箱地址用于账号验证",c.error(t.value);return}if(s&&!s.includes("@")){t.value="邮箱格式不正确",c.error(t.value);return}if(!i){t.value="请输入验证码",c.error(t.value);return}V.value=!0;try{const m=await J({username:u,password:e,email:s,captcha_session:h.value,captcha:i});_.value=m?.message||"注册成功",k.value=m?.need_verify?"请检查您的邮箱(包括垃圾邮件文件夹)":"",c.success("注册成功"),a.username="",a.password="",a.confirm_password="",a.email="",a.captcha="",setTimeout(()=>{window.location.href="/login"},3e3)}catch(m){const x=m?.response?.data;t.value=x?.error||"注册失败",c.error(t.value),await y()}finally{V.value=!1}}function L(){T.push("/login")}return A(async()=>{await y(),await D()}),(u,e)=>{const g=v("el-alert"),s=v("el-input"),i=v("el-form-item"),p=v("el-button"),m=v("el-form"),x=v("el-card");return b(),U("div",O,[l(x,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:o(()=>[e[11]||(e[11]=n("div",{class:"brand"},[n("div",{class:"brand-title"},"知识管理平台"),n("div",{class:"brand-sub app-muted"},"用户注册")],-1)),t.value?(b(),N(g,{key:0,type:"error",closable:!1,title:t.value,"show-icon":"",class:"alert"},null,8,["title"])):E("",!0),_.value?(b(),N(g,{key:1,type:"success",closable:!1,title:_.value,description:k.value,"show-icon":"",class:"alert"},null,8,["title","description"])):E("",!0),l(m,{"label-position":"top"},{default:o(()=>[l(i,{label:"用户名 *"},{default:o(()=>[l(s,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=r=>a.username=r),placeholder:"至少3个字符",autocomplete:"username"},null,8,["modelValue"]),e[5]||(e[5]=n("div",{class:"hint app-muted"},"至少3个字符",-1))]),_:1}),l(i,{label:"密码 *"},{default:o(()=>[l(s,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=r=>a.password=r),type:"password","show-password":"",placeholder:"至少8位且包含字母和数字",autocomplete:"new-password"},null,8,["modelValue"]),e[6]||(e[6]=n("div",{class:"hint app-muted"},"至少8位且包含字母和数字",-1))]),_:1}),l(i,{label:"确认密码 *"},{default:o(()=>[l(s,{modelValue:a.confirm_password,"onUpdate:modelValue":e[2]||(e[2]=r=>a.confirm_password=r),type:"password","show-password":"",placeholder:"请再次输入密码",autocomplete:"new-password",onKeyup:P(C,["enter"])},null,8,["modelValue"])]),_:1}),l(i,{label:K.value},{default:o(()=>[l(s,{modelValue:a.email,"onUpdate:modelValue":e[3]||(e[3]=r=>a.email=r),placeholder:"name@example.com",autocomplete:"email"},null,8,["modelValue"]),n("div",Q,q(R.value),1)]),_:1},8,["label"]),l(i,{label:"验证码 *"},{default:o(()=>[n("div",W,[l(s,{modelValue:a.captcha,"onUpdate:modelValue":e[4]||(e[4]=r=>a.captcha=r),placeholder:"请输入验证码",onKeyup:P(C,["enter"])},null,8,["modelValue"]),w.value?(b(),U("img",{key:0,class:"captcha-img",src:w.value,alt:"验证码",title:"点击刷新",onClick:y},null,8,X)):E("",!0),l(p,{onClick:y},{default:o(()=>[...e[7]||(e[7]=[S("刷新",-1)])]),_:1})])]),_:1})]),_:1}),l(p,{type:"primary",class:"submit-btn",loading:V.value,onClick:C},{default:o(()=>[...e[8]||(e[8]=[S("注册",-1)])]),_:1},8,["loading"]),n("div",Y,[e[10]||(e[10]=n("span",{class:"app-muted"},"已有账号?",-1)),l(p,{link:"",type:"primary",onClick:L},{default:o(()=>[...e[9]||(e[9]=[S("立即登录",-1)])]),_:1})])]),_:1})])}}},te=M(Z,[["__scopeId","data-v-a9d7804f"]]);export{te as default};
|
||||||
@@ -1 +1 @@
|
|||||||
import{_ as L,a as n,l as M,r as U,c as j,o as F,m as K,b as v,d as s,w as a,e as l,u as D,f as w,g as m,F as T,k,h as q,i as x,j as z,t as G,v as H,E as y}from"./index-CGLq5RWF.js";import{c as J}from"./auth-xwgKTSGk.js";const O={class:"auth-wrap"},Q={class:"actions"},W={class:"actions"},X={key:0,class:"app-muted"},Y={__name:"ResetPasswordPage",setup(Z){const B=M(),A=D(),r=n(String(B.params.token||"")),i=n(!0),b=n(""),t=U({newPassword:"",confirmPassword:""}),g=n(!1),_=n(""),d=n(0);let u=null;function C(){if(typeof window>"u")return null;const o=window.__APP_INITIAL_STATE__;return!o||typeof o!="object"?null:(window.__APP_INITIAL_STATE__=null,o)}const I=j(()=>!!(i.value&&r.value&&!_.value));function S(){A.push("/login")}function N(){d.value=3,u=window.setInterval(()=>{d.value-=1,d.value<=0&&(window.clearInterval(u),u=null,window.location.href="/login")},1e3)}async function V(){if(!I.value)return;const o=t.newPassword,e=t.confirmPassword,c=H(o);if(!c.ok){y.error(c.message);return}if(o!==e){y.error("两次输入的密码不一致");return}g.value=!0;try{await J({token:r.value,new_password:o}),_.value="密码重置成功!3秒后跳转到登录页面...",y.success("密码重置成功"),N()}catch(p){const f=p?.response?.data;y.error(f?.error||"重置失败")}finally{g.value=!1}}return F(()=>{const o=C();o?.page==="reset_password"?(r.value=String(o?.token||r.value||""),i.value=!!o?.valid,b.value=o?.error_message||(i.value?"":"重置链接无效或已过期,请重新申请密码重置")):r.value||(i.value=!1,b.value="重置链接无效或已过期,请重新申请密码重置")}),K(()=>{u&&window.clearInterval(u)}),(o,e)=>{const c=l("el-alert"),p=l("el-button"),f=l("el-input"),h=l("el-form-item"),R=l("el-form"),E=l("el-card");return w(),v("div",O,[s(E,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:a(()=>[e[5]||(e[5]=m("div",{class:"brand"},[m("div",{class:"brand-title"},"知识管理平台"),m("div",{class:"brand-sub app-muted"},"重置密码")],-1)),i.value?(w(),v(T,{key:1},[_.value?(w(),q(c,{key:0,type:"success",closable:!1,title:"重置成功",description:_.value,"show-icon":"",class:"alert"},null,8,["description"])):x("",!0),s(R,{"label-position":"top"},{default:a(()=>[s(h,{label:"新密码(至少8位且包含字母和数字)"},{default:a(()=>[s(f,{modelValue:t.newPassword,"onUpdate:modelValue":e[0]||(e[0]=P=>t.newPassword=P),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(h,{label:"确认密码"},{default:a(()=>[s(f,{modelValue:t.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=P=>t.confirmPassword=P),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:z(V,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(p,{type:"primary",class:"submit-btn",loading:g.value,disabled:!I.value,onClick:V},{default:a(()=>[...e[3]||(e[3]=[k(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),m("div",W,[s(p,{link:"",type:"primary",onClick:S},{default:a(()=>[...e[4]||(e[4]=[k("返回登录",-1)])]),_:1}),d.value>0?(w(),v("span",X,G(d.value)+" 秒后自动跳转…",1)):x("",!0)])],64)):(w(),v(T,{key:0},[s(c,{type:"error",closable:!1,title:"链接已失效",description:b.value,"show-icon":""},null,8,["description"]),m("div",Q,[s(p,{type:"primary",onClick:S},{default:a(()=>[...e[2]||(e[2]=[k("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},oe=L(Y,[["__scopeId","data-v-0bbb511c"]]);export{oe as default};
|
import{_ as L,a as n,l as M,r as U,c as j,o as F,m as K,b as v,d as s,w as a,e as l,u as D,f as w,g as m,F as T,k,h as q,i as x,j as z,t as G,v as H,E as y}from"./index-B3Praptc.js";import{c as J}from"./auth-CXXxlo9_.js";const O={class:"auth-wrap"},Q={class:"actions"},W={class:"actions"},X={key:0,class:"app-muted"},Y={__name:"ResetPasswordPage",setup(Z){const B=M(),A=D(),r=n(String(B.params.token||"")),i=n(!0),b=n(""),t=U({newPassword:"",confirmPassword:""}),g=n(!1),_=n(""),d=n(0);let u=null;function C(){if(typeof window>"u")return null;const o=window.__APP_INITIAL_STATE__;return!o||typeof o!="object"?null:(window.__APP_INITIAL_STATE__=null,o)}const I=j(()=>!!(i.value&&r.value&&!_.value));function S(){A.push("/login")}function N(){d.value=3,u=window.setInterval(()=>{d.value-=1,d.value<=0&&(window.clearInterval(u),u=null,window.location.href="/login")},1e3)}async function V(){if(!I.value)return;const o=t.newPassword,e=t.confirmPassword,c=H(o);if(!c.ok){y.error(c.message);return}if(o!==e){y.error("两次输入的密码不一致");return}g.value=!0;try{await J({token:r.value,new_password:o}),_.value="密码重置成功!3秒后跳转到登录页面...",y.success("密码重置成功"),N()}catch(p){const f=p?.response?.data;y.error(f?.error||"重置失败")}finally{g.value=!1}}return F(()=>{const o=C();o?.page==="reset_password"?(r.value=String(o?.token||r.value||""),i.value=!!o?.valid,b.value=o?.error_message||(i.value?"":"重置链接无效或已过期,请重新申请密码重置")):r.value||(i.value=!1,b.value="重置链接无效或已过期,请重新申请密码重置")}),K(()=>{u&&window.clearInterval(u)}),(o,e)=>{const c=l("el-alert"),p=l("el-button"),f=l("el-input"),h=l("el-form-item"),R=l("el-form"),E=l("el-card");return w(),v("div",O,[s(E,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:a(()=>[e[5]||(e[5]=m("div",{class:"brand"},[m("div",{class:"brand-title"},"知识管理平台"),m("div",{class:"brand-sub app-muted"},"重置密码")],-1)),i.value?(w(),v(T,{key:1},[_.value?(w(),q(c,{key:0,type:"success",closable:!1,title:"重置成功",description:_.value,"show-icon":"",class:"alert"},null,8,["description"])):x("",!0),s(R,{"label-position":"top"},{default:a(()=>[s(h,{label:"新密码(至少8位且包含字母和数字)"},{default:a(()=>[s(f,{modelValue:t.newPassword,"onUpdate:modelValue":e[0]||(e[0]=P=>t.newPassword=P),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(h,{label:"确认密码"},{default:a(()=>[s(f,{modelValue:t.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=P=>t.confirmPassword=P),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:z(V,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(p,{type:"primary",class:"submit-btn",loading:g.value,disabled:!I.value,onClick:V},{default:a(()=>[...e[3]||(e[3]=[k(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),m("div",W,[s(p,{link:"",type:"primary",onClick:S},{default:a(()=>[...e[4]||(e[4]=[k("返回登录",-1)])]),_:1}),d.value>0?(w(),v("span",X,G(d.value)+" 秒后自动跳转…",1)):x("",!0)])],64)):(w(),v(T,{key:0},[s(c,{type:"error",closable:!1,title:"链接已失效",description:b.value,"show-icon":""},null,8,["description"]),m("div",Q,[s(p,{type:"primary",onClick:S},{default:a(()=>[...e[2]||(e[2]=[k("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},oe=L(Y,[["__scopeId","data-v-0bbb511c"]]);export{oe as default};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{_ as U,a as o,c as I,o as E,m as R,b as k,d as i,w as s,e as d,u as W,f as _,g as l,i as B,h as $,k as T,t as v}from"./index-CGLq5RWF.js";const j={class:"auth-wrap"},z={class:"actions"},D={key:0,class:"countdown app-muted"},M={__name:"VerifyResultPage",setup(q){const x=W(),p=o(!1),f=o(""),m=o(""),w=o(""),y=o(""),r=o(""),u=o(""),c=o(""),n=o(0);let a=null;function C(){if(typeof window>"u")return null;const e=window.__APP_INITIAL_STATE__;return!e||typeof e!="object"?null:(window.__APP_INITIAL_STATE__=null,e)}function N(e){const t=!!e?.success;p.value=t,f.value=e?.title||(t?"验证成功":"验证失败"),m.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),w.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),r.value=e?.secondary_label||(t?"":"返回登录"),u.value=e?.secondary_url||(t?"":"/login"),c.value=e?.redirect_url||(t?"/login":""),n.value=Number(e?.redirect_seconds||(t?5:0))||0}const A=I(()=>!!(r.value&&u.value)),b=I(()=>!!(c.value&&n.value>0));async function g(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await x.push(e)}}function P(){b.value&&(a=window.setInterval(()=>{n.value-=1,n.value<=0&&(window.clearInterval(a),a=null,window.location.href=c.value)},1e3))}return E(()=>{const e=C();N(e),P()}),R(()=>{a&&window.clearInterval(a)}),(e,t)=>{const h=d("el-button"),V=d("el-result"),L=d("el-card");return _(),k("div",j,[i(L,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:s(()=>[t[2]||(t[2]=l("div",{class:"brand"},[l("div",{class:"brand-title"},"知识管理平台"),l("div",{class:"brand-sub app-muted"},"验证结果")],-1)),i(V,{icon:p.value?"success":"error",title:f.value,"sub-title":m.value,class:"result"},{extra:s(()=>[l("div",z,[i(h,{type:"primary",onClick:t[0]||(t[0]=S=>g(y.value))},{default:s(()=>[T(v(w.value),1)]),_:1}),A.value?(_(),$(h,{key:0,onClick:t[1]||(t[1]=S=>g(u.value))},{default:s(()=>[T(v(r.value),1)]),_:1})):B("",!0)]),b.value?(_(),k("div",D,v(n.value)+" 秒后自动跳转... ",1)):B("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},G=U(M,[["__scopeId","data-v-1fc6b081"]]);export{G as default};
|
import{_ as U,a as o,c as I,o as E,m as R,b as k,d as i,w as s,e as d,u as W,f as _,g as l,i as B,h as $,k as T,t as v}from"./index-B3Praptc.js";const j={class:"auth-wrap"},z={class:"actions"},D={key:0,class:"countdown app-muted"},M={__name:"VerifyResultPage",setup(q){const x=W(),p=o(!1),f=o(""),m=o(""),w=o(""),y=o(""),r=o(""),u=o(""),c=o(""),n=o(0);let a=null;function C(){if(typeof window>"u")return null;const e=window.__APP_INITIAL_STATE__;return!e||typeof e!="object"?null:(window.__APP_INITIAL_STATE__=null,e)}function N(e){const t=!!e?.success;p.value=t,f.value=e?.title||(t?"验证成功":"验证失败"),m.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),w.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),r.value=e?.secondary_label||(t?"":"返回登录"),u.value=e?.secondary_url||(t?"":"/login"),c.value=e?.redirect_url||(t?"/login":""),n.value=Number(e?.redirect_seconds||(t?5:0))||0}const A=I(()=>!!(r.value&&u.value)),b=I(()=>!!(c.value&&n.value>0));async function g(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await x.push(e)}}function P(){b.value&&(a=window.setInterval(()=>{n.value-=1,n.value<=0&&(window.clearInterval(a),a=null,window.location.href=c.value)},1e3))}return E(()=>{const e=C();N(e),P()}),R(()=>{a&&window.clearInterval(a)}),(e,t)=>{const h=d("el-button"),V=d("el-result"),L=d("el-card");return _(),k("div",j,[i(L,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:s(()=>[t[2]||(t[2]=l("div",{class:"brand"},[l("div",{class:"brand-title"},"知识管理平台"),l("div",{class:"brand-sub app-muted"},"验证结果")],-1)),i(V,{icon:p.value?"success":"error",title:f.value,"sub-title":m.value,class:"result"},{extra:s(()=>[l("div",z,[i(h,{type:"primary",onClick:t[0]||(t[0]=S=>g(y.value))},{default:s(()=>[T(v(w.value),1)]),_:1}),A.value?(_(),$(h,{key:0,onClick:t[1]||(t[1]=S=>g(u.value))},{default:s(()=>[T(v(r.value),1)]),_:1})):B("",!0)]),b.value?(_(),k("div",D,v(n.value)+" 秒后自动跳转... ",1)):B("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},G=U(M,[["__scopeId","data-v-1fc6b081"]]);export{G as default};
|
||||||
@@ -1 +1 @@
|
|||||||
import{p as c}from"./index-CGLq5RWF.js";async function o(t={}){const{data:a}=await c.get("/accounts",{params:t});return a}async function u(t){const{data:a}=await c.post("/accounts",t);return a}async function r(t,a){const{data:n}=await c.put(`/accounts/${t}`,a);return n}async function e(t){const{data:a}=await c.delete(`/accounts/${t}`);return a}async function i(t,a){const{data:n}=await c.put(`/accounts/${t}/remark`,a);return n}async function p(t,a){const{data:n}=await c.post(`/accounts/${t}/start`,a);return n}async function d(t){const{data:a}=await c.post(`/accounts/${t}/stop`,{});return a}async function f(t){const{data:a}=await c.post("/accounts/batch/start",t);return a}async function w(t){const{data:a}=await c.post("/accounts/batch/stop",t);return a}async function y(){const{data:t}=await c.post("/accounts/clear",{});return t}async function A(t,a={}){const{data:n}=await c.post(`/accounts/${t}/screenshot`,a);return n}export{w as a,f as b,y as c,d,e,o as f,u as g,i as h,p as s,A as t,r as u};
|
import{p as c}from"./index-B3Praptc.js";async function o(t={}){const{data:a}=await c.get("/accounts",{params:t});return a}async function u(t){const{data:a}=await c.post("/accounts",t);return a}async function r(t,a){const{data:n}=await c.put(`/accounts/${t}`,a);return n}async function e(t){const{data:a}=await c.delete(`/accounts/${t}`);return a}async function i(t,a){const{data:n}=await c.put(`/accounts/${t}/remark`,a);return n}async function p(t,a){const{data:n}=await c.post(`/accounts/${t}/start`,a);return n}async function d(t){const{data:a}=await c.post(`/accounts/${t}/stop`,{});return a}async function f(t){const{data:a}=await c.post("/accounts/batch/start",t);return a}async function w(t){const{data:a}=await c.post("/accounts/batch/stop",t);return a}async function y(){const{data:t}=await c.post("/accounts/clear",{});return t}async function A(t,a={}){const{data:n}=await c.post(`/accounts/${t}/screenshot`,a);return n}export{w as a,f as b,y as c,d,e,o as f,u as g,i as h,p as s,A as t,r as u};
|
||||||
@@ -1 +1 @@
|
|||||||
import{p as s}from"./index-CGLq5RWF.js";async function r(){const{data:a}=await s.get("/email/verify-status");return a}async function o(){const{data:a}=await s.post("/generate_captcha",{});return a}async function e(a){const{data:t}=await s.post("/login",a);return t}async function i(a){const{data:t}=await s.post("/register",a);return t}async function c(a){const{data:t}=await s.post("/resend-verify-email",a);return t}async function f(a){const{data:t}=await s.post("/forgot-password",a);return t}async function u(a){const{data:t}=await s.post("/reset-password-confirm",a);return t}export{f as a,i as b,u as c,r as f,o as g,e as l,c as r};
|
import{p as s}from"./index-B3Praptc.js";async function r(){const{data:a}=await s.get("/email/verify-status");return a}async function o(){const{data:a}=await s.post("/generate_captcha",{});return a}async function e(a){const{data:t}=await s.post("/login",a);return t}async function i(a){const{data:t}=await s.post("/register",a);return t}async function c(a){const{data:t}=await s.post("/resend-verify-email",a);return t}async function f(a){const{data:t}=await s.post("/forgot-password",a);return t}async function u(a){const{data:t}=await s.post("/reset-password-confirm",a);return t}export{f as a,i as b,u as c,r as f,o as g,e as l,c as r};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
static/app/assets/index-HG0IyblY.css
Normal file
1
static/app/assets/index-HG0IyblY.css
Normal file
File diff suppressed because one or more lines are too long
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
||||||
<title>知识管理平台</title>
|
<title>知识管理平台</title>
|
||||||
<script type="module" crossorigin src="./assets/index-CGLq5RWF.js"></script>
|
<script type="module" crossorigin src="./assets/index-B3Praptc.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-BIng7uZJ.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-HG0IyblY.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>该页面需要启用 JavaScript 才能使用。</noscript>
|
<noscript>该页面需要启用 JavaScript 才能使用。</noscript>
|
||||||
|
|||||||
@@ -1233,6 +1233,18 @@
|
|||||||
<label>公告内容</label>
|
<label>公告内容</label>
|
||||||
<textarea id="announcementContent" rows="5" placeholder="请输入公告内容(将以弹窗形式展示)"></textarea>
|
<textarea id="announcementContent" rows="5" placeholder="请输入公告内容(将以弹窗形式展示)"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>公告图片(可选)</label>
|
||||||
|
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
|
||||||
|
<button class="btn btn-secondary" onclick="triggerAnnouncementImageUpload()">+ 上传图片</button>
|
||||||
|
<button class="btn" onclick="clearAnnouncementImage()" style="background: #eee;">移除</button>
|
||||||
|
<input type="file" id="announcementImageFile" accept="image/*" style="display: none;" onchange="uploadAnnouncementImageFile()">
|
||||||
|
<input type="text" id="announcementImageUrl" placeholder="上传后自动填充" readonly style="flex: 1; min-width: 220px;">
|
||||||
|
</div>
|
||||||
|
<div id="announcementImagePreview" style="display: none; margin-top: 8px;">
|
||||||
|
<img id="announcementImagePreviewImg" src="" alt="公告图片预览" style="max-width: 260px; max-height: 160px; border-radius: 8px; border: 1px solid #e5e7eb; object-fit: contain;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||||
<button class="btn btn-primary" onclick="createAnnouncement(true)">发布并启用</button>
|
<button class="btn btn-primary" onclick="createAnnouncement(true)">发布并启用</button>
|
||||||
<button class="btn btn-secondary" onclick="createAnnouncement(false)">保存但不启用</button>
|
<button class="btn btn-secondary" onclick="createAnnouncement(false)">保存但不启用</button>
|
||||||
@@ -1632,6 +1644,7 @@
|
|||||||
<th style="width: 70px;">ID</th>
|
<th style="width: 70px;">ID</th>
|
||||||
<th>标题</th>
|
<th>标题</th>
|
||||||
<th style="width: 90px;">状态</th>
|
<th style="width: 90px;">状态</th>
|
||||||
|
<th style="width: 70px;">图片</th>
|
||||||
<th style="width: 170px;">创建时间</th>
|
<th style="width: 170px;">创建时间</th>
|
||||||
<th style="width: 220px;">操作</th>
|
<th style="width: 220px;">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1646,6 +1659,7 @@
|
|||||||
${a.is_active ? '启用' : '停用'}
|
${a.is_active ? '启用' : '停用'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td>${a.image_url ? '有图' : '-'}</td>
|
||||||
<td>${a.created_at || '-'}</td>
|
<td>${a.created_at || '-'}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
@@ -1670,17 +1684,82 @@
|
|||||||
const content = document.getElementById('announcementContent');
|
const content = document.getElementById('announcementContent');
|
||||||
if (title) title.value = '';
|
if (title) title.value = '';
|
||||||
if (content) content.value = '';
|
if (content) content.value = '';
|
||||||
|
clearAnnouncementImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerAnnouncementImageUpload() {
|
||||||
|
const input = document.getElementById('announcementImageFile');
|
||||||
|
if (input) input.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadAnnouncementImageFile() {
|
||||||
|
const input = document.getElementById('announcementImageFile');
|
||||||
|
const urlInput = document.getElementById('announcementImageUrl');
|
||||||
|
const file = input?.files?.[0];
|
||||||
|
if (!file || !urlInput) return;
|
||||||
|
|
||||||
|
if (file.type && !String(file.type).startsWith('image/')) {
|
||||||
|
showNotification('请选择图片文件', 'error');
|
||||||
|
input.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
try {
|
||||||
|
const response = await fetch('/yuyx/api/announcements/upload_image', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (!response.ok || !data?.success) {
|
||||||
|
showNotification(data?.error || '上传失败', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
urlInput.value = data.url || '';
|
||||||
|
updateAnnouncementImagePreview();
|
||||||
|
showNotification('上传成功', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
showNotification('上传失败', 'error');
|
||||||
|
} finally {
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAnnouncementImage() {
|
||||||
|
const imageUrl = document.getElementById('announcementImageUrl');
|
||||||
|
const imageFile = document.getElementById('announcementImageFile');
|
||||||
|
if (imageUrl) imageUrl.value = '';
|
||||||
|
if (imageFile) imageFile.value = '';
|
||||||
|
updateAnnouncementImagePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAnnouncementImagePreview() {
|
||||||
|
const imageUrl = document.getElementById('announcementImageUrl');
|
||||||
|
const previewWrap = document.getElementById('announcementImagePreview');
|
||||||
|
const previewImg = document.getElementById('announcementImagePreviewImg');
|
||||||
|
if (!imageUrl || !previewWrap || !previewImg) return;
|
||||||
|
const url = String(imageUrl.value || '').trim();
|
||||||
|
if (url) {
|
||||||
|
previewImg.src = url;
|
||||||
|
previewWrap.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
previewImg.removeAttribute('src');
|
||||||
|
previewWrap.style.display = 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewAnnouncement(id) {
|
function viewAnnouncement(id) {
|
||||||
const announcement = announcements.find(a => a.id === id);
|
const announcement = announcements.find(a => a.id === id);
|
||||||
if (!announcement) return;
|
if (!announcement) return;
|
||||||
alert(`标题:${announcement.title || ''}\n\n内容:\n${announcement.content || ''}`);
|
const imageLine = announcement.image_url ? `\n图片:${announcement.image_url}` : '';
|
||||||
|
alert(`标题:${announcement.title || ''}${imageLine}\n\n内容:\n${announcement.content || ''}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createAnnouncement(isActive) {
|
async function createAnnouncement(isActive) {
|
||||||
const title = (document.getElementById('announcementTitle')?.value || '').trim();
|
const title = (document.getElementById('announcementTitle')?.value || '').trim();
|
||||||
const content = (document.getElementById('announcementContent')?.value || '').trim();
|
const content = (document.getElementById('announcementContent')?.value || '').trim();
|
||||||
|
const image_url = (document.getElementById('announcementImageUrl')?.value || '').trim();
|
||||||
if (!title || !content) {
|
if (!title || !content) {
|
||||||
showNotification('标题和内容不能为空', 'error');
|
showNotification('标题和内容不能为空', 'error');
|
||||||
return;
|
return;
|
||||||
@@ -1690,7 +1769,7 @@
|
|||||||
const response = await fetch('/yuyx/api/announcements', {
|
const response = await fetch('/yuyx/api/announcements', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ title, content, is_active: !!isActive })
|
body: JSON.stringify({ title, content, image_url, is_active: !!isActive })
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -628,6 +628,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div id="announcementModalContent" style="white-space: pre-wrap;"></div>
|
<div id="announcementModalContent" style="white-space: pre-wrap;"></div>
|
||||||
|
<div id="announcementModalImageWrap" style="display:none; margin-top: 12px; text-align: center;">
|
||||||
|
<img id="announcementModalImage" src="" alt="公告图片" style="max-width: 100%; max-height: 320px; border-radius: 10px; border: 1px solid #e5e7eb; object-fit: contain;">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-text" onclick="closeAnnouncementOnce()">当次关闭</button>
|
<button class="btn btn-text" onclick="closeAnnouncementOnce()">当次关闭</button>
|
||||||
@@ -1090,8 +1093,20 @@
|
|||||||
currentAnnouncementId = announcement.id;
|
currentAnnouncementId = announcement.id;
|
||||||
const titleEl = document.getElementById('announcementModalTitle');
|
const titleEl = document.getElementById('announcementModalTitle');
|
||||||
const contentEl = document.getElementById('announcementModalContent');
|
const contentEl = document.getElementById('announcementModalContent');
|
||||||
|
const imageWrap = document.getElementById('announcementModalImageWrap');
|
||||||
|
const imageEl = document.getElementById('announcementModalImage');
|
||||||
if (titleEl) titleEl.textContent = announcement.title || '系统公告';
|
if (titleEl) titleEl.textContent = announcement.title || '系统公告';
|
||||||
if (contentEl) contentEl.textContent = announcement.content || '';
|
if (contentEl) contentEl.textContent = announcement.content || '';
|
||||||
|
if (imageWrap && imageEl) {
|
||||||
|
const imageUrl = String(announcement.image_url || '').trim();
|
||||||
|
if (imageUrl) {
|
||||||
|
imageEl.src = imageUrl;
|
||||||
|
imageWrap.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
imageEl.removeAttribute('src');
|
||||||
|
imageWrap.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
openModal('announcementModal');
|
openModal('announcementModal');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
|
|||||||
Reference in New Issue
Block a user