Compare commits

..

3 Commits

Author SHA1 Message Date
fb1cbbada4
升级 Vue 2 到 Vue 3
- 升级 vue 2.7.16 → 3.5.14
- 重构 slider.js:new Vue() 迁移到 createApp()
- data 改为函数形式,移除 filters 改用 methods
- 更新模板 tools.ejs:过滤器语法改为方法调用
- 代码风格优化:箭头函数、const/let、简化数组操作

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:46:48 +08:00
6f6fcebd18
移除 axios,改用原生 fetch 实现 HTTP 请求
- 从 package.json 移除 axios 依赖
- 重构 request.js:用原生 fetch API 替代 axios
- 实现参数序列化、超时控制(AbortController)、统一响应处理
- 保持 http.get/http.post API 兼容,减少 bundle 体积

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:18:57 +08:00
7d827d5db8
升级 yilia 主题构建工具链
- 升级 babel 相关依赖至 ^7.26.0,配置按需引入 core-js polyfill
- 升级 webpack 5.70.0 → 5.97.1,webpack-cli 4.9.2 → 5.1.4
- 升级 css-loader、postcss-loader、less-loader 等至最新版本
- 移除废弃的 file-loader/url-loader,改用 webpack5 内置 asset modules
- 移除 babel-polyfill 和 leancloud-storage,添加 @babel/runtime 和 core-js
- 升级 vue 2.6.14 → 2.7.16,axios 1.13.6 → 1.8.4
- 添加 browserslist 配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:12:52 +08:00
8 changed files with 2541 additions and 8055 deletions

View File

@ -1,6 +1,9 @@
{
"presets": [
"@babel/preset-env"
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}]
],
"plugins": [
"@babel/transform-runtime"

View File

@ -60,7 +60,7 @@
</div>
<ul class="search-ul">
<li class="search-li" v-for="(item,index) in items" :key="index" v-show="!item.isHide">
<a :href="item.path|urlformat" class="search-title"><i class="icon icon-quote-left"></i>
<a :href="urlformat(item.path)" class="search-title"><i class="icon icon-quote-left"></i>
<span v-text="item.title"></span>
</a>
<p class="search-time" v-if="item.date">

File diff suppressed because it is too large Load Diff

View File

@ -18,39 +18,36 @@
"author": "litten",
"license": "ISC",
"dependencies": {
"babel-polyfill": "^6.23.0",
"leancloud-storage": "^3.7.3",
"@babel/runtime": "^7.26.0",
"core-js": "^3.40.0",
"photoswipe": "^4.1.3",
"qrious": "^4.0.2",
"scrollreveal": "^4.0.5",
"vue": "^2.6.14"
"scrollreveal": "^4.0.9",
"vue": "^3.5.14"
},
"devDependencies": {
"@babel/core": "^7.17.5",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"autoprefixer": "^10.4.2",
"axios": "^1.13.6",
"babel-loader": "8.3.0",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.2.1",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^5.2.7",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"mini-css-extract-plugin": "^2.6.0",
"postcss-loader": "^6.2.1",
"terser-webpack-plugin": "^5.3.1",
"url-loader": "^4.1.1",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2"
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.3",
"less": "^4.2.2",
"less-loader": "^12.2.0",
"mini-css-extract-plugin": "^2.9.2",
"postcss": "^8.5.3",
"postcss-loader": "^8.1.1",
"terser-webpack-plugin": "^5.3.11",
"webpack": "^5.97.1",
"webpack-cli": "^5.1.4"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"iOS >= 7",
"Android >= 4",
"not ie <= 8"
"iOS >= 10",
"Android >= 5"
]
}

View File

@ -1,6 +1,5 @@
module.exports = {
plugins: [
"autoprefixer",
"postcss-preset-env"
"autoprefixer"
]
}

View File

@ -1,14 +1,19 @@
import axios from 'axios'
const DEFAULT_TIMEOUT = 10000
const http = axios.create({
timeout: 10000,
paramsSerializer: {
indexes: null
function serializeParams(params) {
if (!params) return ''
const query = Object.entries(params)
.filter(([, v]) => v !== undefined && v !== null)
.flatMap(([k, v]) => Array.isArray(v) ? v.map(item => `${encodeURIComponent(k)}=${encodeURIComponent(item)}`) : [`${encodeURIComponent(k)}=${encodeURIComponent(v)}`])
.join('&')
return query ? '?' + query : ''
}
function handleResponse(res) {
if (!res.ok) {
return Promise.reject(new Error(`HTTP ${res.status}`))
}
})
http.interceptors.response.use(res => {
const responseBody = res.data
return res.json().then(responseBody => {
// 统一响应格式处理
switch (responseBody.code) {
case 0:
@ -18,8 +23,30 @@ http.interceptors.response.use(res => {
return Promise.reject(new Error(responseBody.message || '请求失败'))
default:
// 其他情况,兼容没有包装格式的响应
return res.data
return responseBody
}
}, err => Promise.reject(err))
})
}
async function request(url, options = {}) {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT)
return fetch(url, { ...options, signal: controller.signal })
.then(res => { clearTimeout(timer); return handleResponse(res) })
.catch(err => { clearTimeout(timer); return Promise.reject(err) })
}
const http = {
get(url, { params } = {}) {
return request(url + serializeParams(params))
},
post(url, data) {
return request(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
}
}
export default http

View File

@ -1,17 +1,22 @@
import http from './request'
import Vue from 'vue/dist/vue.esm'
import { createApp } from 'vue'
import waifuTips from '../config/waifu-tip.json'
function setScrollZero() {
let $sct = document.querySelectorAll('.tools-section')
Array.prototype.forEach.call($sct, (em) => {
document.querySelectorAll('.tools-section').forEach(em => {
em.scrollTop = 0
})
}
function urlformat(str) {
return (window.themeConfig && window.themeConfig.root) ? window.themeConfig.root + str : '/' + str
}
var waifuTipTimer = null, fullTextSearchTimer = null
const vm = new Vue({
el: '#container',
data: {
const vm = createApp({
data() {
return {
isCtnShow: false,
isShow: undefined,
items: [],
@ -37,13 +42,15 @@ const vm = new Vue({
showTools: false // 显示工具栏
},
themeConfig: window.themeConfig
}
},
methods: {
stop (event) {
urlformat,
stop(event) {
event.stopPropagation()
},
openSlider (event, type, isMobile) {
if(isMobile && this.isShow) {
openSlider(event, type, isMobile) {
if (isMobile && this.isShow) {
this.hideSlider()
return
}
@ -56,32 +63,32 @@ const vm = new Vue({
this.isCtnShow = true
setScrollZero()
},
hideSlider () {
hideSlider() {
if (this.isShow) {
this.isShow = false
setTimeout(() => {this.isCtnShow = false}, 300)
setTimeout(() => { this.isCtnShow = false }, 300)
}
},
linkMouseover(name) {
if(name === 'waifu' && waifuTipTimer) return
if (name === 'waifu' && waifuTipTimer) return
this.showMessage(waifuTips.mouseover[name], 3000)
},
toolsClick(name) {
this.showMessage(waifuTips.click[name])
if(name in waifuTools) {
if (name in waifuTools) {
waifuTools[name].call(this)
}
},
addSearchItem(query, type='title') {
if(query) {
addSearchItem(query, type = 'title') {
if (query) {
query = query.trim()
}
// 如果已存在相同的查询条件, 则不加入
var isExist = Array.prototype.some.call(this.searchItems, searchItem => {
const isExist = this.searchItems.some(searchItem => {
return searchItem.query === query && searchItem.type === type
})
if(!isExist && query) {
this.searchItems.push({query, type})
if (!isExist && query) {
this.searchItems.push({ query, type })
}
this.search = null
},
@ -91,18 +98,18 @@ const vm = new Vue({
this.$refs.mask.classList.add('in')
},
loadSearchResult() {
this.fullTextSearch.pageNum ++
this.fullTextSearch.pageNum++
this.fullTextSearch.isLoading = true
this.fullTextSearch.tip = undefined
let params = {
const params = {
pageNum: this.fullTextSearch.pageNum,
limit: this.fullTextSearch.limit,
words: this.fullTextSearchWords
}
http.get('/api/v2/common/search', {params}).then(res => {
http.get('/api/v2/common/search', { params }).then(res => {
this.fullTextSearch.isLoading = false
fullTextSearchTimer = null
if(!Array.isArray(res.list) || !res.list.length) {
if (!Array.isArray(res.list) || !res.list.length) {
this.fullTextSearch.tip = '未搜索到匹配文章'
} else {
this.fullTextSearchItems.push(...res.list)
@ -115,62 +122,53 @@ const vm = new Vue({
})
},
searchKeydown(event) {
if(event.keyCode == 13){ // 回车键
if (event.keyCode == 13) { // 回车键
this.addSearchItem(this.search)
} else if(event.keyCode == 8 && !this.search) { // 退格键
} else if (event.keyCode == 8 && !this.search) { // 退格键
this.searchItems.pop()
}
},
showMessage (text, time) {
if(!text) return
if(Array.isArray(text)) text = text[Math.floor(Math.random() * text.length + 1)-1]
showMessage(text, time) {
if (!text) return
if (Array.isArray(text)) text = text[Math.floor(Math.random() * text.length + 1) - 1]
this.waifu.tip = text
this.waifu.tipOpacity = 1
if(waifuTipTimer) {
if (waifuTipTimer) {
clearTimeout(waifuTipTimer)
waifuTipTimer = null
}
waifuTipTimer = setTimeout(()=>{
waifuTipTimer = setTimeout(() => {
this.waifu.tipOpacity = 0
waifuTipTimer = null
}, time || 5000)
}
},
filters: {
urlformat (str) {
return (window.themeConfig && window.themeConfig.root) ? window.themeConfig.root + str : '/' + str
}
},
watch: {
searchItems (newVal, oldVal) {
if(newVal && newVal.length) {
searchItems(newVal) {
if (newVal && newVal.length) {
handleSearch.call(this, newVal)
} else {
this.items.forEach(function(item){
item.isHide = false
})
this.items.forEach(item => { item.isHide = false })
}
},
fullTextSearchWords (newVal, oldVal) {
fullTextSearchWords(newVal) {
this.fullTextSearch.hasMore = false
this.fullTextSearchItems.isLoading = false
this.fullTextSearch.tip = undefined
this.fullTextSearchItems.splice(0, this.fullTextSearchItems.length)
if(fullTextSearchTimer) {
if (fullTextSearchTimer) {
clearTimeout(fullTextSearchTimer)
fullTextSearchTimer = null
}
if(!newVal) {
return
}
if (!newVal) return
this.fullTextSearch.pageNum = 0
fullTextSearchTimer = setTimeout(this.loadSearchResult.bind(this), 500)
}
},
mounted () {
fetch(window.themeConfig.root + 'content.json').then(res => res.json()).then(resJson=>{
mounted() {
fetch(window.themeConfig.root + 'content.json').then(res => res.json()).then(resJson => {
this.items = resJson
}).catch(err => {
}).catch(() => {
console.warn('加载文章列表失败')
})
welcomeMessage().then(msg => {
@ -179,44 +177,39 @@ const vm = new Vue({
document.addEventListener('copy', () => {
this.showMessage('你都复制了些什么呀,转载要记得加上出处哦')
})
// 隐藏模态框
let hideModal = (function() {
let modals = document.querySelectorAll('.page-modal')
Array.prototype.forEach.call(modals, modal => {
const hideModal = () => {
document.querySelectorAll('.page-modal').forEach(modal => {
modal.classList.remove('in')
})
this.$refs.mask.classList.remove('in')
}).bind(this)
}
// 隐藏模态框
this.$refs.mask.addEventListener('click', hideModal)
Array.prototype.forEach.call(document.querySelectorAll('.js-modal-close'), modalClose => {
document.querySelectorAll('.js-modal-close').forEach(modalClose => {
modalClose.addEventListener('click', hideModal)
})
},
created() {
// 夜间模式
let night = localStorage.getItem('night')
try {
if(night && eval(night)) document.querySelector('body').classList.add('night')
} catch (e){}
const night = localStorage.getItem('night')
if (night === 'true') {
document.querySelector('body').classList.add('night')
}
})
}
}).mount('#container')
function handleSearch(searchItems) {
this.items.forEach(articleItem => {
articleItem.isHide = !Array.prototype.every.call(searchItems, searchItem => {
switch(searchItem.type) {
articleItem.isHide = !searchItems.every(searchItem => {
switch (searchItem.type) {
case 'title':
return articleItem.title.toLowerCase().indexOf(searchItem.query.toLowerCase()) !== -1
case 'tag' :
return Array.prototype.some.call(articleItem.tags, tag => {
return tag.name === searchItem.query
})
case 'category' :
return Array.prototype.some.call(articleItem.categories, category => {
return category.name === searchItem.query
})
case 'date' :
return articleItem.date && ( articleItem.date.substr(0,7) === searchItem.query )
case 'tag':
return articleItem.tags.some(tag => tag.name === searchItem.query)
case 'category':
return articleItem.categories.some(category => category.name === searchItem.query)
case 'date':
return articleItem.date && (articleItem.date.substr(0, 7) === searchItem.query)
}
})
})
@ -226,22 +219,14 @@ async function welcomeMessage() {
let now = new Date().getHours()
return http.get('/api/v2/common/config/waifu_tip').then(textTimes => {
let text = null
Array.prototype.sort.call(textTimes, (item1, item2) => {
if(item1.start>item2.start) {
return 1
} else if(item1.start<item2.start) {
return -1
} else {
return 0
}
})
Array.prototype.forEach.call(textTimes, textTime => {
if(now > textTime.start && now <= textTime.end) {
textTimes.sort((a, b) => a.start - b.start)
textTimes.forEach(textTime => {
if (now > textTime.start && now <= textTime.end) {
text = textTime.text
}
})
if(!text) {
text = textTimes[textTimes.length-1].text
if (!text) {
text = textTimes[textTimes.length - 1].text
}
return text
})
@ -271,8 +256,8 @@ const waifuTools = {
},
'tools.chart'() {
// 一言
http.get('/api/v2/common/hitokoto', { params: {format: 'json'} }).then(res => {
this.showMessage(res.hitokoto + (res.from?`  ——${res.from}`:''))
http.get('/api/v2/common/hitokoto', { params: { format: 'json' } }).then(res => {
this.showMessage(res.hitokoto + (res.from ? `  ——${res.from}` : ''))
})
},
'tools.search'() {

View File

@ -24,7 +24,7 @@ module.exports = function(env, argv) {
})]
},
entry: {
main: ['babel-polyfill', './source-src/js/main.js'],
main: './source-src/js/main.js',
slider: './source-src/js/slider.js',
mobile: './source-src/js/mobile.js',
viewer: './source-src/js/viewer.js'
@ -37,39 +37,25 @@ module.exports = function(env, argv) {
module: {
rules: [{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-class-properties'],
},
},
use: 'babel-loader',
exclude: /node_modules/
},{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']
},{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
},{
test: /\.(png|jpe?g|gif|ico)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[contenthash:6].[ext]',
outputPath: 'images',
esModule: false // 不使用es6的模块语法
}
type: 'asset/resource',
generator: {
filename: 'images/[name].[contenthash:6][ext]'
}
},{
test: /\.(svg|eot|ttf|woff2?|otf)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[contenthash:6].[ext]',
outputPath: 'fonts',
esModule: false // 不使用es6的模块语法
}
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[contenthash:6][ext]'
}
}
]