init
This commit is contained in:
61
src/stores/about.js
Normal file
61
src/stores/about.js
Normal 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
189
src/stores/cron.js
Normal 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
66
src/stores/posts.js
Normal 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
263
src/stores/settings.js
Normal 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
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user