hyx_brand
hyx 3 weeks ago
parent 7533450cc6
commit bbb8eba5d1

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

@ -17,17 +17,9 @@ app.use(ElementPlus)
app.use(store) app.use(store)
app.use(router) app.use(router)
// 初始化会话并挂载应用 // 初始化会话
async function initializeApp() { store.dispatch('initSession')
try {
await store.dispatch('initSession')
} catch (error) {
console.error('会话初始化失败:', error)
} finally {
app.mount('#app')
}
}
initializeApp() app.mount('#app')
window.store = store 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) { if (!store.state.sessionInitialized) {
return next() return next()

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

@ -24,21 +24,49 @@ service.interceptors.request.use(
// 响应拦截器 // 响应拦截器
service.interceptors.response.use( service.interceptors.response.use(
response => { response => {
// 统一处理成功响应 // 处理成功响应
if (response.data && response.data.code === 200) { const res = response.data
return response.data
} else { // 处理业务错误 (code !== 200)
// 处理业务逻辑错误 if (res && typeof res === 'object' && res.code !== undefined && res.code !== 200) {
const errorMsg = response.data?.message || '请求处理失败' // 检查是否为静默请求
return Promise.reject(new Error(errorMsg)) if (!response.config.silent) {
ElMessage.error(res.message || '请求失败')
}
return Promise.reject(new Error(res.message || 'Error'))
} }
// 返回整个响应对象,确保组件可以访问响应头等信息
return response
}, },
error => { error => {
// 处理HTTP错误 // 处理HTTP错误
const errorMsg = error.response?.data?.message || if (error.response) {
error.message || switch (error.response.status) {
'网络请求失败' case 401:
return Promise.reject(new Error(errorMsg)) // 只有在非静默请求时才显示错误信息
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-option label="借阅时间从旧到新" value="oldest" />
</el-select> </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> </div>
<el-table :data="records" border style="width: 100%"> <el-table :data="records" border style="width: 100%">
<el-table-column prop="borrower" label="用户名" width="150" /> <el-table-column prop="borrower" label="用户名" width="150" />
<el-table-column prop="book_title" label="书名" width="200" /> <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"> <template #default="scope">
{{ formatDate(scope.row.borrow_time) }} {{ formatDate(scope.row.borrow_time) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="return_time" label="归还时间" width="200"> <el-table-column prop="return_time" label="归还时间" >
<template #default="scope"> <template #default="scope">
{{ scope.row.return_time ? formatDate(scope.row.return_time) : '尚未归还' }} {{ scope.row.return_time ? formatDate(scope.row.return_time) : '尚未归还' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="状态" width="100"> <el-table-column label="状态" width="150">
<template #default="scope"> <template #default="scope">
<el-tag :type="scope.row.return_time ? 'success' : 'warning'"> <el-tag :type="scope.row.return_time ? 'success' : 'warning'">
{{ scope.row.return_time ? '已归还' : '借阅中' }} {{ scope.row.return_time ? '已归还' : '借阅中' }}
@ -70,6 +60,7 @@
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import { formatDate } from '@/utils/date' import { formatDate } from '@/utils/date'
import axios from 'axios'
const store = useStore() const store = useStore()
const records = ref([]) const records = ref([])
@ -81,7 +72,7 @@
const searchUsername = ref('') const searchUsername = ref('')
// 便 // 便
const presetRecords = [ const presetrecords = [
{ {
id: 1, id: 1,
borrower: 'zhangsan', borrower: 'zhangsan',
@ -103,19 +94,13 @@
}) })
const fetchAllRecords = async () => { const fetchAllRecords = async () => {
try { try {
loading.value = true const response = await store.dispatch('fetchBorrowRecords')
records.value = response.data
//
const recordsData = await store.dispatch('fetchBorrowRecords')
} catch (error) { } catch (error) {
console.error('获取借阅记录失败:', error) console.error('获取借阅记录失败:', error)
ElMessage.error('获取借阅记录失败,使用预设数据') ElMessage.error('获取借阅记录失败')
records.value = presetrecords//使
records.value = presetRecords
total.value = presetRecords.length
} finally {
loading.value = false
} }
} }

@ -74,12 +74,11 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus } from '@element-plus/icons-vue' import { Search, Plus } from '@element-plus/icons-vue'
import axios from 'axios'
const router = useRouter() const router = useRouter()
const store = useStore()
const books = ref([]) const books = ref([])
const loading = ref(false) const loading = ref(false)
const total = ref(0) const total = ref(0)
@ -91,91 +90,31 @@
await fetchBooks() 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 () => { const fetchBooks = async () => {
try { try {
loading.value = true loading.value = true
const params = { const params = {
page: currentPage.value, page: currentPage.value,
pageSize: pageSize.value, pageSize: pageSize.value,
title: searchKeyword.value // 使 title keyword keyword: searchKeyword.value
} }
// Vuex action const response = await axios.get('/api/select', { params })
const response = await store.dispatch('fetchBooks', params) if (response.data.code === 200) {
books.value = response.data.data.list
// total.value = response.data.data.total
if (response.data && Array.isArray(response.data.list)) { } else {
books.value = response.data.list ElMessage.error('获取图书列表失败')
total.value = response.data.total total.value = books.value.length
} else { }
// } catch (error) {
console.warn('非标准响应格式:', response) console.error('获取图书列表失败:', error)
books.value = response.data || [] ElMessage.error('获取图书列表失败')
total.value = books.value.length } 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) => { const handlePageChange = (page) => {
currentPage.value = page currentPage.value = page
fetchBooks() fetchBooks()
@ -197,7 +136,7 @@
} }
) )
await store.dispatch('deleteBook', { title: book.title }) await axios.delete(`/api/select/${book.id}`)
ElMessage.success('删除成功') ElMessage.success('删除成功')
fetchBooks() fetchBooks()
} catch (error) { } catch (error) {

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

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

@ -13,7 +13,7 @@
<el-image :src="book.url" class="book-cover" fit="cover" /> <el-image :src="book.url" class="book-cover" fit="cover" />
<div class="book-details"> <div class="book-details">
<h3 class="book-title">{{ book.title }}</h3> <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 <el-button
type="primary" type="primary"
@click="handleReturn(book)" @click="handleReturn(book)"

Loading…
Cancel
Save