feat(project): 初始化项目结构和配置

This commit is contained in:
2026-06-10 04:09:02 +08:00
parent 2d301cf1c3
commit 45f883b772
1815 changed files with 315166 additions and 0 deletions
+210
View File
@@ -0,0 +1,210 @@
import { defineStore } from 'pinia'
import API from '@api'
import type {
BaseBook,
Book,
BookChapter,
BookProgress,
SeachBook,
} from '@/book'
import type { webReadConfig } from '@/web'
import { ElMessage } from 'element-plus/es'
const default_config: webReadConfig = {
theme: 0,
font: 0,
fontSize: 18,
readWidth: 800,
infiniteLoading: false,
customFontName: '',
jumpDuration: 1000,
spacing: {
paragraph: 1,
line: 0.8,
letter: 0,
},
}
let webReadConfigLoadedDate: Date | undefined
export const useBookStore = defineStore('book', {
state: () => {
return {
searchBooks: [] as SeachBook[],
shelf: [] as Book[],
catalog: [] as BookChapter[],
readingBook: { chapterPos: 0, chapterIndex: 0 } as BaseBook & {
chapterPos: number
chapterIndex: number
isSeachBook?: boolean
},
popCataVisible: false,
contentLoading: true,
showContent: false,
config: default_config,
miniInterface: false,
readSettingsVisible: false,
}
},
getters: {
bookProgress: (state): BookProgress | undefined => {
if (state.catalog.length == 0) return
const { chapterIndex, chapterPos, name, author } = state.readingBook
const title = state.catalog[chapterIndex]?.title
if (!title) return
return {
name,
author,
durChapterIndex: chapterIndex,
durChapterPos: chapterPos,
durChapterTime: new Date().getTime(),
durChapterTitle: title,
}
},
theme: state => {
return state.config.theme
},
isNight: state => state.config.theme == 6,
},
actions: {
/** 从后端加载书架书籍,优先返回内存缓存 */
async loadBookShelf(): Promise<Book[]> {
const fetchBookshellf_promise = API.getBookShelf().then(resp => {
console.log('API.getBookShelf数据返回')
const { isSuccess, data, errorMsg } = resp.data
if (isSuccess === true) {
if (
this.shelf.length !== data.length &&
this.shelf.length > 0 &&
data.length > 0
) {
ElMessage.info(`书架数据已更新`)
}
this.shelf = data.sort((a, b) => {
const x = a['durChapterTime'] || 0
const y = b['durChapterTime'] || 0
return y - x
})
} else {
if (errorMsg.includes('还没有添加小说') && this.shelf.length > 0) {
ElMessage.info('当前书架上的书籍已经被删除')
return (this.shelf = [])
}
ElMessage.error(errorMsg ?? '后端返回格式错误!')
}
console.log('书架数据已更新')
return this.shelf
})
if (this.shelf.length > 0) {
// bookshelf data fetched before:do not await
console.log('返回缓存书架数据')
return this.shelf
} else {
console.log('从阅读后端获取书架数据...')
return await fetchBookshellf_promise
}
},
/** 从后端加载书籍目录,优先返回内存缓存 */
async loadWebCatalog(
book: typeof this.readingBook,
): Promise<BookChapter[]> {
const { bookUrl, name, chapterIndex } = book
const fetchChapterList_promise = API.getChapterList(
bookUrl as string,
).then(res => {
const { isSuccess, data, errorMsg } = res.data
if (isSuccess === false) {
ElMessage.error(errorMsg)
throw new Error()
}
if (
bookUrl === this.readingBook.bookUrl &&
data.length !== this.catalog.length &&
data.length > 0 &&
this.catalog.length > 0
) {
ElMessage.info(`书籍${name}: 章节目录已更新`)
}
this.catalog = data
console.log(`书籍${name}: 章节目录已更新`)
return this.catalog
})
if (
bookUrl === this.readingBook.bookUrl &&
this.catalog.length > 0 &&
this.catalog.length - 1 >= chapterIndex
) {
console.log(`返回书籍《${name}》 缓存的章节目录`)
return this.catalog
} else {
console.log(`从阅读后端获取书籍《${name}》 章节目录数据...`)
return await fetchChapterList_promise
}
},
setPopCataVisible(visible: boolean) {
this.popCataVisible = visible
},
setContentLoading(loading: boolean) {
this.contentLoading = loading
},
setReadingBook(readingBook: typeof this.readingBook) {
this.readingBook = readingBook
},
/** 只从从后端加载一次web阅读配置 */
async loadWebConfig() {
if (webReadConfigLoadedDate === undefined) {
const _config = await API.getReadConfig()
webReadConfigLoadedDate = new Date()
console.log(
`${this.$id}.loadWebConfig: ${webReadConfigLoadedDate.toLocaleString()}成功加载阅读配置`,
)
return this.setConfig(_config)
}
console.log(
`${this.$id}.loadWebConfig: 已于${webReadConfigLoadedDate.toLocaleString()}成功加载`,
)
},
setConfig(config?: webReadConfig) {
this.config = Object.assign({}, this.config, config)
},
setReadSettingsVisible(visible: boolean) {
this.readSettingsVisible = visible
},
setShowContent(visible: boolean) {
this.showContent = visible
},
setMiniInterface(mini: boolean) {
this.miniInterface = mini
},
async setSearchBooks(books: SeachBook[]) {
books.forEach(book => {
const isSeachBook = this.shelf.every(
item => item.bookUrl !== book.bookUrl,
)
if (isSeachBook === true) {
this.searchBooks.push(book)
}
})
},
clearSearchBooks() {
this.searchBooks = []
},
/** 1.保存进度到app 2.修改内存中的数据*/
async saveBookProgress() {
if (!this.bookProgress) return Promise.resolve()
const { bookUrl } = this.readingBook
const shelfRaw = toRaw(this.shelf)
const findIndex = shelfRaw.findIndex(book => book.bookUrl === bookUrl)
if (findIndex > -1) {
this.shelf[findIndex] = Object.assign(
{},
shelfRaw[findIndex],
this.bookProgress,
)
}
// 直接关闭浏览器时 http请求可能被取消
// return API.saveBookProgress(this.bookProgress)
return API.saveBookProgressWithBeacon(this.bookProgress)
},
},
})
+24
View File
@@ -0,0 +1,24 @@
import { defineStore } from 'pinia'
export const useConnectionStore = defineStore('connection', {
state: () => {
return {
connectStatus: '正在连接后端服务器……',
connectType: 'primary' as 'primary' | 'success' | 'danger',
newConnect: false,
}
},
actions: {
setConnectStatus(connectStatus: string) {
if (this.newConnect === true) return
this.connectStatus = connectStatus
},
setConnectType(connectType: 'primary' | 'success' | 'danger') {
if (this.newConnect === true) return
this.connectType = connectType
},
setNewConnect(newConnect: boolean) {
this.newConnect = newConnect
},
},
})
+6
View File
@@ -0,0 +1,6 @@
import { createPinia } from 'pinia'
export * from './bookStore'
export * from './sourceStore'
export * from './connectionStore'
export default createPinia()
+134
View File
@@ -0,0 +1,134 @@
import { defineStore } from 'pinia'
import {
emptyBookSource,
emptyRssSource,
getSourceUniqueKey,
convertSourcesToMap,
} from '@utils/souce'
import type { BookSoure, RssSource, Source } from '@/source'
const isBookSource = /bookSource/i.test(location.href)
const emptySource = isBookSource ? emptyBookSource : emptyRssSource
export const useSourceStore = defineStore('source', {
state: () => {
return {
bookSources: shallowRef([] as BookSoure[]), // 临时存放所有书源,
rssSources: shallowRef([] as RssSource[]), // 临时存放所有订阅源
savedSources: [] as Source[], // 批量保存到阅读app成功的源
currentSource: JSON.parse(JSON.stringify(emptySource)) as Source, // 当前编辑的源
currentTab: localStorage.getItem('tabName') || 'editTab',
editTabSource: {} as Source, // 生成序列化的json数据
isDebuging: false,
}
},
getters: {
sources: (state): Source[] =>
isBookSource ? state.bookSources : state.rssSources,
sourcesMap: function (): Map<string, Source> {
return convertSourcesToMap(this.sources)
},
savedSourcesMap: (state): Map<string, Source> =>
convertSourcesToMap(state.savedSources),
currentSourceUrl: state =>
isBookSource
? (state.currentSource as BookSoure).bookSourceUrl
: (state.currentSource as RssSource).sourceUrl,
searchKey: (state): string =>
isBookSource
? (state.currentSource as BookSoure)?.ruleSearch?.checkKeyWord || '我的'
: '',
},
actions: {
startDebug() {
this.currentTab = 'editDebug'
this.isDebuging = true
},
debugFinish() {
this.isDebuging = false
},
//拉取源后保存
saveSources(data: Source[]) {
if (isBookSource) {
this.bookSources = markRaw(data) as BookSoure[]
} else {
this.rssSources = markRaw(data) as RssSource[]
}
},
//批量推送
setPushReturnSources(returnSoures: Source[]) {
this.savedSources = returnSoures
},
//删除源
deleteSources(data: Source[]) {
const sources: Source[] = isBookSource
? this.bookSources
: this.rssSources
data.forEach(source => {
const index = sources.indexOf(source)
if (index > -1) sources.splice(index, 1)
})
},
//保存当前编辑源
saveCurrentSource() {
const source = this.currentSource,
map = this.sourcesMap
map.set(getSourceUniqueKey(source), JSON.parse(JSON.stringify(source)))
this.saveSources(Array.from(map.values()))
},
// 更改当前编辑的源qq
changeCurrentSource(source: Source) {
this.currentSource = JSON.parse(JSON.stringify(source))
},
// update editTab tabName and editTab info
changeTabName(tabName: string) {
this.currentTab = tabName
localStorage.setItem('tabName', tabName)
},
changeEditTabSource(source: Source) {
this.editTabSource = JSON.parse(JSON.stringify(source))
},
editHistory(history: Source) {
let historyObj
if (localStorage.getItem('history')) {
historyObj = JSON.parse(localStorage.getItem('history')!)
historyObj.new.push(history)
if (historyObj.new.length > 50) {
historyObj.new.shift()
}
if (historyObj.old.length > 50) {
historyObj.old.shift()
}
localStorage.setItem('history', JSON.stringify(historyObj))
} else {
const arr = { new: [history], old: [] }
localStorage.setItem('history', JSON.stringify(arr))
}
},
editHistoryUndo() {
if (localStorage.getItem('history')) {
const historyObj = JSON.parse(localStorage.getItem('history')!)
historyObj.old.push(this.currentSource)
if (historyObj.new.length) {
this.currentSource = historyObj.new.pop()
}
localStorage.setItem('history', JSON.stringify(historyObj))
}
},
clearAllHistory() {
localStorage.setItem('history', JSON.stringify({ new: [], old: [] }))
},
clearEdit() {
this.editTabSource = {} as Source
this.currentSource = JSON.parse(JSON.stringify(emptySource)) //复制一份新对象
},
// clear all source
clearAllSource() {
this.bookSources = []
this.rssSources = []
this.savedSources = []
},
},
})