This commit is contained in:
dcr_xuxgc
2026-06-12 17:49:54 +08:00
commit d759a9e740
69 changed files with 14243 additions and 0 deletions

61
src/stores/about.js Normal file
View File

@@ -0,0 +1,61 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { api } from '@/services/api'
export const useAboutStore = defineStore('about', () => {
const title = ref('')
const intro = ref('')
const blog = ref('')
const tech = ref('')
const features = ref([])
const contact = ref({})
const filingNumber = ref('')
const loading = ref(false)
async function loadAbout() {
loading.value = true
try {
const data = await api.getAbout()
title.value = data.title || ''
intro.value = data.intro || ''
blog.value = data.blog || ''
tech.value = data.tech || ''
features.value = data.featuresList || []
contact.value = data.contactObj || {}
filingNumber.value = data.filing_number || ''
} catch (error) {
console.error('加载关于内容失败:', error)
} finally {
loading.value = false
}
}
async function saveAbout(data) {
try {
await api.updateAbout(data)
title.value = data.title || title.value
intro.value = data.intro ?? intro.value
blog.value = data.blog ?? blog.value
tech.value = data.tech ?? tech.value
features.value = data.features ?? features.value
contact.value = data.contact ?? contact.value
filingNumber.value = data.filing_number ?? filingNumber.value
} catch (error) {
console.error('保存关于内容失败:', error)
throw error
}
}
return {
title,
intro,
blog,
tech,
features,
contact,
filingNumber,
loading,
loadAbout,
saveAbout
}
})

189
src/stores/cron.js Normal file
View File

@@ -0,0 +1,189 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { api } from '@/services/api'
export const useCronStore = defineStore('cron', () => {
const tasks = ref([])
const loading = ref(false)
const error = ref(null)
const runningTasks = computed(() => {
return tasks.value.filter(t => taskStatus.value[t.id] === 'running')
})
const taskStatus = ref({})
async function loadTasks() {
loading.value = true
error.value = null
try {
tasks.value = await api.getCronTasks()
} catch (err) {
error.value = err.message
console.error('加载定时任务失败:', err)
// 降级到 localStorage
const saved = localStorage.getItem('cron-tasks')
if (saved) {
try {
tasks.value = JSON.parse(saved)
} catch {
tasks.value = []
}
}
} finally {
loading.value = false
}
}
async function saveTasks() {
try {
// 同步到服务器
for (const task of tasks.value) {
await api.updateCronTask(task.id, {
name: task.name,
cron: task.cron,
url: task.url,
method: task.method,
headers: task.headers,
body: task.body,
enabled: task.enabled
})
}
} catch (err) {
console.warn('同步到服务器失败,保留在本地:', err.message)
// 备用 localStorage
try {
localStorage.setItem('cron-tasks', JSON.stringify(tasks.value))
} catch (e) {
console.warn('localStorage 保存失败:', e)
}
}
}
async function createTask(data) {
try {
const newTask = await api.createCronTask(data)
tasks.value.unshift(newTask)
return newTask
} catch (err) {
// 后备方案:本地创建
const localTask = {
...data,
id: Date.now().toString(),
lastRun: null,
nextRun: calculateNextRun(data.cron),
runCount: 0
}
tasks.value.unshift(localTask)
localStorage.setItem('cron-tasks', JSON.stringify(tasks.value))
return localTask
}
}
async function updateTask(id, data) {
try {
const updated = await api.updateCronTask(id, data)
const index = tasks.value.findIndex(t => t.id === id)
if (index !== -1) {
tasks.value[index] = updated
}
return updated
} catch (err) {
const index = tasks.value.findIndex(t => t.id === id)
if (index !== -1) {
tasks.value[index] = { ...tasks.value[index], ...data }
}
localStorage.setItem('cron-tasks', JSON.stringify(tasks.value))
return tasks.value[index]
}
}
async function deleteTask(id) {
try {
await api.deleteCronTask(id)
tasks.value = tasks.value.filter(t => t.id !== id)
} catch (err) {
tasks.value = tasks.value.filter(t => t.id !== id)
localStorage.setItem('cron-tasks', JSON.stringify(tasks.value))
}
}
async function toggleTask(id) {
const task = tasks.value.find(t => t.id === id)
if (task) {
task.enabled = !task.enabled
await updateTask(id, { enabled: task.enabled })
}
}
async function executeTask(id) {
if (taskStatus.value[id] === 'running') return
taskStatus.value[id] = 'running'
try {
const result = await api.runCronTask(id)
const task = tasks.value.find(t => t.id === id)
if (task) {
task.lastRun = result.lastRun
task.nextRun = result.nextRun
task.runCount = result.runCount
}
taskStatus.value[id] = result.success ? 'success' : 'error'
setTimeout(() => {
taskStatus.value[id] = null
}, 3000)
} catch (error) {
taskStatus.value[id] = 'error'
console.error('执行任务失败:', error)
setTimeout(() => {
taskStatus.value[id] = null
}, 3000)
}
}
function calculateNextRun(cron) {
const parts = cron.trim().split(/\s+/)
if (parts.length < 5) return null
const now = new Date()
const next = new Date(now)
const minute = parts[0]
const hour = parts[1]
if (minute === '*') {
next.setMinutes(now.getMinutes() + 1)
} else if (minute.includes('/')) {
const step = parseInt(minute.split('/')[1])
next.setMinutes(Math.ceil(now.getMinutes() / step) * step)
} else {
next.setMinutes(parseInt(minute))
if (next <= now) next.setHours(next.getHours() + 1)
}
if (hour !== '*') {
next.setHours(parseInt(hour), 0, 0, 0)
if (next <= now) next.setDate(next.getDate() + 1)
}
return next
}
return {
tasks,
loading,
error,
taskStatus,
runningTasks,
loadTasks,
createTask,
updateTask,
deleteTask,
toggleTask,
executeTask,
calculateNextRun
}
})

66
src/stores/posts.js Normal file
View File

@@ -0,0 +1,66 @@
import { defineStore } from 'pinia'
import { api } from '@/services/api'
export const usePostsStore = defineStore('posts', {
state: () => ({
posts: [],
loading: false,
error: null
}),
getters: {
getPostById: (state) => (id) => {
return state.posts.find(post => post.id === Number(id))
}
},
actions: {
async fetchPosts() {
this.loading = true
this.error = null
try {
this.posts = await api.getPosts()
} catch (error) {
this.error = error.message
console.error('获取文章失败:', error)
} finally {
this.loading = false
}
},
async createPost(data) {
try {
const newPost = await api.createPost(data)
this.posts.unshift(newPost)
return newPost
} catch (error) {
this.error = error.message
throw error
}
},
async updatePost(id, data) {
try {
const updated = await api.updatePost(id, data)
const index = this.posts.findIndex(p => p.id === Number(id))
if (index !== -1) {
this.posts[index] = updated
}
return updated
} catch (error) {
this.error = error.message
throw error
}
},
async deletePost(id) {
try {
await api.deletePost(id)
this.posts = this.posts.filter(p => p.id !== Number(id))
} catch (error) {
this.error = error.message
throw error
}
}
}
})

263
src/stores/settings.js Normal file
View File

@@ -0,0 +1,263 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { api } from '@/services/api'
export const useSettingsStore = defineStore('settings', () => {
const bgType = ref('color')
const bgColor = ref('#f5f5f5')
const bgImage = ref('')
const bgOpacity = ref(1)
const language = ref('zh')
const favicon = ref('')
// 现在存储 {id, url} 而不是 {id, data: base64}
const uploadedImages = ref([])
const uploadedIcons = ref([])
const loading = ref(false)
let initialized = false
async function loadSettings() {
loading.value = true
try {
const data = await api.getSettings()
bgType.value = data.bgType || 'color'
bgColor.value = data.bgColor || '#f5f5f5'
bgImage.value = data.bgImage || ''
bgOpacity.value = data.bgOpacity ?? 1
language.value = data.language || 'zh'
favicon.value = data.favicon || ''
uploadedImages.value = data.uploadedImages || []
uploadedIcons.value = data.uploadedIcons || []
applyFavicon(favicon.value)
initialized = true
} catch (error) {
console.error('加载设置失败:', error)
initialized = true
// 降级到 localStorage
const saved = localStorage.getItem('site-settings')
if (saved) {
try {
const settings = JSON.parse(saved)
bgType.value = settings.bgType || 'color'
bgColor.value = settings.bgColor || '#f5f5f5'
bgImage.value = settings.bgImage || ''
bgOpacity.value = settings.bgOpacity || 1
language.value = settings.language || 'zh'
favicon.value = settings.favicon || ''
uploadedImages.value = settings.uploadedImages || []
uploadedIcons.value = settings.uploadedIcons || []
} catch {
// ignore
}
}
} finally {
loading.value = false
}
}
async function saveSettings() {
if (!initialized) {
console.warn('saveSettings 跳过:还未初始化')
return
}
try {
await api.updateSettings({
bgType: bgType.value,
bgColor: bgColor.value,
bgImage: bgImage.value,
bgOpacity: bgOpacity.value,
language: language.value,
favicon: favicon.value,
uploadedImages: uploadedImages.value,
uploadedIcons: uploadedIcons.value
})
} catch (error) {
console.warn('保存到服务器失败:', error.message)
}
}
// 上传图片到 RUSTFS返回 {id, url}
async function uploadImageToRustfs(imageData, type = 'background') {
try {
const result = await api.uploadImage(imageData, type)
return { id: result.id, url: result.url }
} catch (error) {
console.error('图片上传失败:', error)
throw error
}
}
// 批量上传图片到 RUSTFS
async function uploadImagesToRustfs(imageDataList, type = 'background') {
try {
const images = imageDataList.map((data, index) => ({
id: `${Date.now()}-${index}`,
data
}))
const result = await api.uploadImages(images, type)
return result.images
} catch (error) {
console.error('批量图片上传失败:', error)
throw error
}
}
// 添加单张图片(上传到 RUSTFS
async function addUploadedImage(imageData) {
const result = await uploadImageToRustfs(imageData, 'background')
uploadedImages.value.push(result)
await saveSettings()
return result.id
}
// 批量添加图片(不上传到服务器)
function addUploadedImagesBatchLocal(imageDataList) {
const baseId = Date.now()
const tempImages = imageDataList.map((imageData, index) => ({
id: `${baseId}-${index}`,
data: imageData
}))
return tempImages
}
// 批量上传图片
async function addUploadedImagesBatch(imageDataList) {
const uploaded = await uploadImagesToRustfs(imageDataList, 'background')
for (const img of uploaded) {
if (img.url) {
uploadedImages.value.push({ id: img.id, url: img.url })
}
}
await saveSettings()
}
// 设置背景图片(使用 URL
async function setBgImage(value) {
bgImage.value = value
await saveSettings()
}
// 设置背景图片(上传 base64 后设置)
async function setBgImageFromData(imageData) {
const result = await uploadImageToRustfs(imageData, 'background')
bgImage.value = result.url
await saveSettings()
}
function setBgType(value) {
bgType.value = value
saveSettings()
}
function setBgColor(value) {
bgColor.value = value
saveSettings()
}
function setBgOpacity(value) {
bgOpacity.value = value
saveSettings()
}
function setLanguage(value) {
language.value = value
saveSettings()
}
// 设置图标(上传到 RUSTFS
async function setFaviconFromData(imageData) {
const result = await uploadImageToRustfs(imageData, 'icon')
favicon.value = result.url
applyFavicon(result.url)
await saveSettings()
}
// 设置图标(使用已有 URL
async function setFavicon(value) {
favicon.value = value
applyFavicon(value)
await saveSettings()
}
// 添加图标到图标库(上传到 RUSTFS
async function addUploadedIcon(imageData) {
const result = await uploadImageToRustfs(imageData, 'icon')
uploadedIcons.value.push(result)
await saveSettings()
return result.id
}
// 批量添加图标
async function addUploadedIconsBatch(imageDataList) {
const uploaded = await uploadImagesToRustfs(imageDataList, 'icon')
for (const icon of uploaded) {
if (icon.url) {
uploadedIcons.value.push({ id: icon.id, url: icon.url })
}
}
await saveSettings()
}
function removeUploadedImage(id) {
uploadedImages.value = uploadedImages.value.filter(img => img.id !== id)
// bgImage 现在是 URL检查是否是被删除图片的 URL
const img = uploadedImages.value.find(img => img.url === bgImage.value)
if (!img && bgImage.value) {
// 当前背景图片被删除了,重置为默认
bgImage.value = ''
}
saveSettings()
}
function removeUploadedIcon(id) {
uploadedIcons.value = uploadedIcons.value.filter(icon => icon.id !== id)
// favicon 现在是 URL检查是否是被删除图标的 URL
const icon = uploadedIcons.value.find(icon => icon.url === favicon.value)
if (!icon && favicon.value) {
favicon.value = ''
applyFavicon('')
}
saveSettings()
}
function applyFavicon(value) {
const link = document.querySelector("link[rel='icon']")
if (link) {
if (value) {
link.href = value
} else {
link.href = '/favicon.ico'
}
}
}
return {
bgType,
bgColor,
bgImage,
bgOpacity,
language,
favicon,
uploadedImages,
uploadedIcons,
loading,
initialized,
loadSettings,
saveSettings,
setBgImage,
setBgImageFromData,
setBgType,
setBgColor,
setBgOpacity,
setLanguage,
setFavicon,
setFaviconFromData,
addUploadedImage,
addUploadedImagesBatch,
addUploadedImagesBatchLocal,
removeUploadedImage,
addUploadedIcon,
addUploadedIconsBatch,
removeUploadedIcon,
applyFavicon
}
})