hyx_brand
hyx 3 weeks ago
parent 7533450cc6
commit bbb8eba5d1

@ -15,7 +15,7 @@
</el-container>
</div>
<div v-else class="loading-container">
<el-icon class="is-loading"><Loading /></el-icon>
<el-loading-spinner />
<p>正在初始化...</p>
</div>
</template>
@ -23,7 +23,6 @@
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
import { Loading } from '@element-plus/icons-vue'
import HeaderBar from './components/HeaderBar.vue'
const store = useStore()

@ -92,13 +92,6 @@ watch(() => store.state.sessionInitialized, async (newVal) => {
}
})
//
watch(() => store.getters.isAuthenticated, async (newVal) => {
if (newVal && store.state.sessionInitialized) {
await fetchUserInfo()
}
})
//
async function fetchUserInfo() {
try {

@ -17,17 +17,9 @@ app.use(ElementPlus)
app.use(store)
app.use(router)
// 初始化会话并挂载应用
async function initializeApp() {
try {
await store.dispatch('initSession')
} catch (error) {
console.error('会话初始化失败:', error)
} finally {
app.mount('#app')
}
}
// 初始化会话
store.dispatch('initSession')
initializeApp()
app.mount('#app')
window.store = store

@ -101,7 +101,7 @@ const router = createRouter({
})
// 路由守卫
router.beforeEach(async (to, from, next) => {
router.beforeEach((to, from, next) => {
// 等待会话初始化完成
if (!store.state.sessionInitialized) {
return next()

@ -1,13 +1,12 @@
import { createStore } from 'vuex'
import service from '../utils/request'
import { safeGetItem, safeSetItem, safeRemoveItem } from '../utils/storage'
import service from '../utils/request'
export default createStore({
state: {
user: safeGetItem('user', null),
balance: safeGetItem('balance', 0),
vipLevel: safeGetItem('vipLevel', 0),
borrowedBooks: safeGetItem('borrowedBooks', []),
user: JSON.parse(sessionStorage.getItem('user')) || null,
balance: JSON.parse(sessionStorage.getItem('balance')) || 0,
vipLevel: JSON.parse(sessionStorage.getItem('vipLevel')) || 0,
borrowedBooks: JSON.parse(sessionStorage.getItem('borrowedBooks')) || [],
sessionInitialized: false
},
getters: {
@ -15,115 +14,71 @@ export default createStore({
isAdmin: state => state.user?.admin || false
},
mutations: {
setUser(state, user) {
const admin = user.admin === 1;
const userData = {
...user,
admin
};
setUser(state, user) {
const admin = user.admin === 1;
const userData = {
...user,
admin
};
state.user = userData
safeSetItem('user', userData)
state.user = user
sessionStorage.setItem('user', JSON.stringify(user))
},
setBalanceAndVip(state, { balance, vip }) {
state.balance = balance
state.vipLevel = vip
safeSetItem('balance', balance)
safeSetItem('vipLevel', vip)
sessionStorage.setItem('balance', JSON.stringify(balance))
sessionStorage.setItem('vipLevel', JSON.stringify(vip))
},
setBorrowedBooks(state, books) {
state.borrowedBooks = books
safeSetItem('borrowedBooks', books)
sessionStorage.setItem('borrowedBooks', JSON.stringify(books))
},
clearUser(state) {
state.user = null
state.balance = 0
state.vipLevel = 0
state.borrowedBooks = []
safeRemoveItem('user')
safeRemoveItem('balance')
safeRemoveItem('vipLevel')
safeRemoveItem('borrowedBooks')
// 不清除sessionInitialized保持应用状态
sessionStorage.removeItem('user')
sessionStorage.removeItem('balance')
sessionStorage.removeItem('vipLevel')
sessionStorage.removeItem('borrowedBooks')
},
removeBorrowedBook(state, title) {
state.borrowedBooks = state.borrowedBooks.filter(book => book.title !== title)
safeSetItem('borrowedBooks', state.borrowedBooks)
},
setSessionInitialized(state, value) {
state.sessionInitialized = value
}
},
actions: {
async initSession({ commit, dispatch, state }) {
async initSession({ commit, dispatch }) {
try {
// 首先检查本地是否有保存的用户信息
const savedUser = safeGetItem('user', null)
if (savedUser) {
// 静默获取用户信息
const userData = await service.get('/user/getinfo', {
silent: true // 避免未登录时显示错误
})
if (userData && userData.username) {
const admin = userData.admin === 1;
commit('setUser', {
username: userData.username,
pic: userData.pic || '',
admin
})
// 获取关联信息
try {
const userData = savedUser
if (userData && userData.username) {
// 验证服务器端会话是否仍然有效
const response = await service.get('/user/getinfo', {
silent: true // 避免未登录时显示错误
})
if (response.data && response.data.username) {
// 服务器会话有效,更新用户信息
const admin = response.data.admin === 1;
commit('setUser', {
username: response.data.username,
pic: response.data.pic || '',
admin
})
// 获取关联信息
try {
await dispatch('fetchBalanceAndVip')
} catch (balanceError) {
console.warn('获取余额信息失败:', balanceError)
}
try {
await dispatch('fetchBorrowedBooks')
} catch (booksError) {
console.warn('获取借阅书籍失败:', booksError)
}
} else {
// 服务器会话无效,清除本地数据
commit('clearUser')
}
}
} catch (parseError) {
console.error('解析保存的用户信息失败:', parseError)
commit('clearUser')
await dispatch('fetchBalanceAndVip')
} catch (balanceError) {
console.warn('获取余额信息失败:', balanceError)
}
} else {
// 没有保存的用户信息,尝试静默获取
const response = await service.get('/user/getinfo', {
silent: true
})
if (response.data && response.data.username) {
const admin = response.data.admin === 1;
commit('setUser', {
username: response.data.username,
pic: response.data.pic || '',
admin
})
// 获取关联信息
try {
await dispatch('fetchBalanceAndVip')
} catch (balanceError) {
console.warn('获取余额信息失败:', balanceError)
}
try {
await dispatch('fetchBorrowedBooks')
} catch (booksError) {
console.warn('获取借阅书籍失败:', booksError)
}
try {
await dispatch('fetchBorrowedBooks')
} catch (booksError) {
console.warn('获取借阅书籍失败:', booksError)
}
}
} catch (error) {
@ -161,37 +116,32 @@ export default createStore({
return response.data
},
// 获取当前用户信息 - 符合接口文档1.3
// 获取当前用户信息 - 符合接口文档
async fetchUser({ commit, dispatch }) {
try {
const response = await service.get('/user/getinfo')
const userData = await service.get('/user/getinfo')
const admin = userData.admin === 1;
// 用户信息接口直接返回用户对象
commit('setUser', {
username: userData.username || '',
pic: userData.pic || '',
admin
})
// 根据接口文档1.3,直接返回用户对象
if (response.data && response.data.username) {
const admin = response.data.admin === 1;
commit('setUser', {
username: response.data.username || '',
pic: response.data.pic || '',
admin
})
// 获取关联信息
try {
await dispatch('fetchBalanceAndVip')
} catch (balanceError) {
console.error('获取余额信息失败:', balanceError)
}
try {
await dispatch('fetchBorrowedBooks')
} catch (booksError) {
console.error('获取借阅书籍失败:', booksError)
}
return response.data
} else {
throw new Error('获取用户信息失败')
}
// 获取关联信息
try {
await dispatch('fetchBalanceAndVip')
} catch (balanceError) {
console.error('获取余额信息失败:', balanceError)
}
try {
await dispatch('fetchBorrowedBooks')
} catch (booksError) {
console.error('获取借阅书籍失败:', booksError)
}
return userData
} catch (error) {
commit('clearUser')
throw error
@ -249,45 +199,37 @@ export default createStore({
return { code: 200, message: '已退出登录' }
},
// 查询个人借书记录 - 符合接口文档1.6
// 查询个人借书记录
async fetchBorrowRecords() {
const response = await service.get('/user/findone')
return response.data
},
// 获取当前用户已借书籍 - 符合接口文档2.5
// 获取当前用户已借书籍
async fetchBorrowedBooks({ commit }) {
const response = await service.get('/user/borrow/books')
// 按照接口文档处理响应
if (response.data.code === 200) {
commit('setBorrowedBooks', response.data.data || [])
} else {
throw new Error(response.data.message || '获取已借书籍失败')
}
return response.data
},
const response = await service.get('/user/borrow/books')
// 按照接口文档处理响应
if (response.data.code === 200) {
commit('setBorrowedBooks', response.data.data || [])
} else {
throw new Error(response.data.message || '获取已借书籍失败')
}
return response.data
},
// 租借书籍 - 符合接口文档3.1
async borrowBook({ dispatch }, { title }) {
const response = await service.post('/borrow/borrowbook',
`title=${encodeURIComponent(title)}`,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
// 根据接口文档3.1,检查响应
if (response.data && response.data.code === 200) {
// 借书后刷新用户信息
await dispatch('fetchUser')
return response.data
} else if (response.data && response.data.code === 1) {
throw new Error(response.data.message || '借书失败')
} else {
throw new Error('借书失败')
}
},
// 租借书籍
async borrowBook({ dispatch }, { title }) {
const response = await service.post('/borrow/borrowbook',
`title=${encodeURIComponent(title)}`,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
// 借书后刷新用户信息
await dispatch('fetchUser')
return response.data
},
// 归还书籍 - 符合接口文档3.2
async returnBook({ dispatch }, { title }) {
@ -297,23 +239,16 @@ async returnBook({ dispatch }, { title }) {
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
// 根据接口文档3.2,检查响应
if (response.data && response.data.code === 200) {
// 还书成功后刷新借阅书籍列表
await dispatch('fetchBorrowedBooks')
return response.data
} else if (response.data && response.data.code === 1) {
throw new Error(response.data.message || '还书失败')
} else {
throw new Error('还书失败')
}
// 还书成功后刷新借阅书籍列表
await dispatch('fetchBorrowedBooks')
return response.data
} catch (error) {
console.error('还书失败:', error)
throw error
}
},
// 查询全部书籍 - 符合接口文档
async fetchBooks(_, params = {}) {
const config = {
@ -362,11 +297,11 @@ async returnBook({ dispatch }, { title }) {
params: { title }
})
// 根据接口文档2.3,返回单条对象
if (response.data && response.data.title) {
return { data: response.data }
// 根据接口文档处理响应
if (response.data && response.data.code === 200) {
return { data: response.data.data }
} else {
throw new Error('获取书籍信息失败')
throw new Error(response.data?.message || '获取书籍信息失败')
}
} catch (error) {
console.error('API请求失败:', error)
@ -374,66 +309,35 @@ async returnBook({ dispatch }, { title }) {
}
},
// 新增书籍 - 符合接口文档2.1
// 新增书籍
async addBook(_, bookData) {
const response = await service.post('/api/add', bookData, {
headers: { 'Content-Type': 'application/json' }
})
// 根据接口文档2.1,成功返回整本书信息
if (response.data && response.data.code === 200) {
return response.data
} else if (response.data && response.data.code === 1) {
throw new Error(response.data.message || '添加书籍失败')
} else {
throw new Error('添加书籍失败')
}
return response.data
},
// 管理员删除书籍 - 符合接口文档2.4
// 管理员删除书籍
async deleteBook(_, { title }) {
const response = await service.post('/user/delete',
`title=${encodeURIComponent(title)}`,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
// 根据接口文档修改响应处理
if (response.data && response.data.code === 200) {
return response.data
} else {
// 统一处理错误消息
const errorMsg = response.data?.message || '删除书籍失败'
throw new Error(errorMsg)
}
},
const response = await service.post('/user/delete',
`title=${encodeURIComponent(title)}`,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
return response.data
},
// 本周热租榜 - 符合接口文档4.1
// 本周热租榜 - 符合接口文档
async fetchWeeklyRank() {
const response = await service.get('/api/rank/weekly')
// 根据接口文档4.1,直接返回数组
if (response.data && Array.isArray(response.data)) {
return { data: response.data }
} else {
return { data: [] }
}
return { data: Array.isArray(response) ? response : response.data || [] }
},
// 本月热租榜 - 符合接口文档4.2
// 本月热租榜 - 符合接口文档
async fetchMonthlyRank() {
const response = await service.get('/api/rank/monthly')
// 根据接口文档4.2结构与4.1相同
if (response.data && Array.isArray(response.data)) {
return { data: response.data }
} else {
return { data: [] }
}
},
return { data: Array.isArray(response) ? response : response.data || [] }
}
}
})

@ -24,21 +24,49 @@ service.interceptors.request.use(
// 响应拦截器
service.interceptors.response.use(
response => {
// 统一处理成功响应
if (response.data && response.data.code === 200) {
return response.data
} else {
// 处理业务逻辑错误
const errorMsg = response.data?.message || '请求处理失败'
return Promise.reject(new Error(errorMsg))
// 处理成功响应
const res = response.data
// 处理业务错误 (code !== 200)
if (res && typeof res === 'object' && res.code !== undefined && res.code !== 200) {
// 检查是否为静默请求
if (!response.config.silent) {
ElMessage.error(res.message || '请求失败')
}
return Promise.reject(new Error(res.message || 'Error'))
}
// 返回整个响应对象,确保组件可以访问响应头等信息
return response
},
error => {
// 处理HTTP错误
const errorMsg = error.response?.data?.message ||
error.message ||
'网络请求失败'
return Promise.reject(new Error(errorMsg))
if (error.response) {
switch (error.response.status) {
case 401:
// 只有在非静默请求时才显示错误信息
if (!error.config?.silent) {
store.dispatch('logout')
router.push('/login')
ElMessage.error('请先登录')
}
break
case 403:
if (!error.config?.silent) {
ElMessage.error('没有操作权限')
}
break
default:
if (!error.config?.silent) {
ElMessage.error(error.response.data?.message || '请求失败')
}
}
} else {
if (!error.config?.silent) {
ElMessage.error('网络错误,请检查连接')
}
}
return Promise.reject(error)
}
)

@ -1,43 +0,0 @@
// 安全地解析JSON字符串
export function safeParseJSON(str, defaultValue = null) {
if (!str) return defaultValue
try {
return JSON.parse(str)
} catch (error) {
console.error('JSON解析失败:', error)
return defaultValue
}
}
// 安全地存储数据到sessionStorage
export function safeSetItem(key, value) {
try {
sessionStorage.setItem(key, JSON.stringify(value))
return true
} catch (error) {
console.error('存储数据失败:', error)
return false
}
}
// 安全地从sessionStorage获取数据
export function safeGetItem(key, defaultValue = null) {
try {
const item = sessionStorage.getItem(key)
return item ? JSON.parse(item) : defaultValue
} catch (error) {
console.error('获取数据失败:', error)
return defaultValue
}
}
// 安全地从sessionStorage删除数据
export function safeRemoveItem(key) {
try {
sessionStorage.removeItem(key)
return true
} catch (error) {
console.error('删除数据失败:', error)
return false
}
}

@ -12,33 +12,23 @@
<el-option label="借阅时间从旧到新" value="oldest" />
</el-select>
<el-input
v-model="searchUsername"
placeholder="搜索用户名"
clearable
@clear="fetchAllRecords"
@keyup.enter="fetchAllRecords"
class="search-input">
<template #append>
<el-button :icon="Search" @click="fetchAllRecords" />
</template>
</el-input>
</div>
<el-table :data="records" border style="width: 100%">
<el-table-column prop="borrower" label="用户名" width="150" />
<el-table-column prop="book_title" label="书名" width="200" />
<el-table-column prop="borrow_time" label="借阅时间" width="200">
<el-table-column prop="borrow_time" label="借阅时间" width="400">
<template #default="scope">
{{ formatDate(scope.row.borrow_time) }}
</template>
</el-table-column>
<el-table-column prop="return_time" label="归还时间" width="200">
<el-table-column prop="return_time" label="归还时间" >
<template #default="scope">
{{ scope.row.return_time ? formatDate(scope.row.return_time) : '尚未归还' }}
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<el-table-column label="状态" width="150">
<template #default="scope">
<el-tag :type="scope.row.return_time ? 'success' : 'warning'">
{{ scope.row.return_time ? '已归还' : '借阅中' }}
@ -70,6 +60,7 @@
import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { formatDate } from '@/utils/date'
import axios from 'axios'
const store = useStore()
const records = ref([])
@ -81,7 +72,7 @@
const searchUsername = ref('')
// 便
const presetRecords = [
const presetrecords = [
{
id: 1,
borrower: 'zhangsan',
@ -103,19 +94,13 @@
})
const fetchAllRecords = async () => {
try {
loading.value = true
//
const recordsData = await store.dispatch('fetchBorrowRecords')
try {
const response = await store.dispatch('fetchBorrowRecords')
records.value = response.data
} catch (error) {
console.error('获取借阅记录失败:', error)
ElMessage.error('获取借阅记录失败,使用预设数据')
records.value = presetRecords
total.value = presetRecords.length
} finally {
loading.value = false
ElMessage.error('获取借阅记录失败')
records.value = presetrecords//使
}
}

@ -74,12 +74,11 @@
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus } from '@element-plus/icons-vue'
import axios from 'axios'
const router = useRouter()
const store = useStore()
const books = ref([])
const loading = ref(false)
const total = ref(0)
@ -91,91 +90,31 @@
await fetchBooks()
})
// const fetchBooks = async () => {
// try {
// loading.value = true
// const params = {
// page: currentPage.value,
// pageSize: pageSize.value,
// keyword: searchKeyword.value
// }
// const response = await axios.get('/api/select', { params })
// if (response.data.code === 200) {
// books.value = response.data.data.list
// total.value = response.data.data.total
// } else {
// ElMessage.error('')
// total.value = books.value.length
// }
// } catch (error) {
// console.error(':', error)
// ElMessage.error('')
// } finally {
// loading.value = false
// }
// }
const fetchBooks = async () => {
try {
loading.value = true
const params = {
page: currentPage.value,
pageSize: pageSize.value,
title: searchKeyword.value // 使 title keyword
}
// Vuex action
const response = await store.dispatch('fetchBooks', params)
//
if (response.data && Array.isArray(response.data.list)) {
books.value = response.data.list
total.value = response.data.total
} else {
//
console.warn('非标准响应格式:', response)
books.value = response.data || []
total.value = books.value.length
try {
loading.value = true
const params = {
page: currentPage.value,
pageSize: pageSize.value,
keyword: searchKeyword.value
}
const response = await axios.get('/api/select', { params })
if (response.data.code === 200) {
books.value = response.data.data.list
total.value = response.data.data.total
} else {
ElMessage.error('获取图书列表失败')
total.value = books.value.length
}
} catch (error) {
console.error('获取图书列表失败:', error)
ElMessage.error('获取图书列表失败')
} finally {
loading.value = false
}
} catch (error) {
console.error('获取书籍列表失败:', error)
ElMessage.error('获取书籍列表失败')
// 使退
books.value = [
{
id: 1,
title: '三体',
url: 'https://picsum.photos/id/24/200/300',
money: 5,
number: 12,
state: '正常',
content: '科幻'
},
{
id: 2,
title: '人类简史',
url: 'https://picsum.photos/id/25/200/300',
money: 4,
number: 10,
state: '正常',
content: '历史'
},
{
id: 3,
title: '百年孤独',
url: 'https://picsum.photos/id/26/200/300',
money: 6,
number: 0,
state: '维护中',
content: '文学'
},
]
total.value = books.value.length
} finally {
loading.value = false
}
}
const handlePageChange = (page) => {
currentPage.value = page
fetchBooks()
@ -197,7 +136,7 @@
}
)
await store.dispatch('deleteBook', { title: book.title })
await axios.delete(`/api/select/${book.id}`)
ElMessage.success('删除成功')
fetchBooks()
} catch (error) {

@ -59,7 +59,7 @@ const store = useStore()
const book = ref(null)
const loading = ref(true)
const isAdmin = computed(() => store.state.user?.admin || false)
const isAdmin = computed(() => store.getters.isAdmin)
onMounted(async () => {
await fetchBook()

@ -15,7 +15,7 @@
<el-button
type="primary"
@click="goToAddBook"
v-if="user && user.admin"
v-if="isAdmin"
class="add-button">
<el-icon><Plus /></el-icon>
添加书籍
@ -75,14 +75,15 @@ import { ElMessage } from 'element-plus'
const store = useStore()
const router = useRouter()
const user = computed(() => store.state.user)
const isAdmin = computed(() => user.value?.admin || false)
const books = ref([])
const searchKeyword = ref('')
const currentPage = ref(1)
const pageSize = ref(12)
const total = ref(0)
const isAdmin = computed(() => store.getters.isAdmin)
onMounted(() => {
fetchBooks()
})

@ -13,7 +13,7 @@
<el-image :src="book.url" class="book-cover" fit="cover" />
<div class="book-details">
<h3 class="book-title">{{ book.title }}</h3>
<p class="borrow-time">借阅时间: {{ formatDate(book.borrow_time) }}</p>
<p class="borrow-time">借阅: {{ book.borrow_time }}</p>
<el-button
type="primary"
@click="handleReturn(book)"

Loading…
Cancel
Save