feat: 前后台静态资源路径冲突修复 + 用户管理功能
- 修改 admin/vite.config.js 添加 base: '/admin/' 解决静态资源路径问题 - 修复后台 index.html 资源引用从 /assets/ → /admin/assets/ - 更新优化器默认 API 地址为服务器地址 - 添加友链管理相关代码 - 修复多处分类管理页面
This commit is contained in:
@@ -18,7 +18,7 @@ request.interceptors.response.use(
|
||||
err => {
|
||||
if (err.response?.status === 401) {
|
||||
localStorage.removeItem('token')
|
||||
window.location.href = '/login'
|
||||
window.location.href = '/admin/login'
|
||||
}
|
||||
return Promise.reject(err)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<el-menu-item index="/settings">AI 配置</el-menu-item>
|
||||
<el-menu-item index="/links">友链管理</el-menu-item>
|
||||
<el-menu-item index="/feedbacks">反馈管理</el-menu-item>
|
||||
<el-menu-item index="/users">用户管理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
@@ -4,7 +4,23 @@
|
||||
<h2>人群管理</h2>
|
||||
<el-button type="primary" @click="dialogVisible = true; form = {}">添加</el-button>
|
||||
</div>
|
||||
<el-table :data="crowds" style="margin-top: 20px">
|
||||
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索名称..."
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<span>🔍</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="result-count">共 {{ filteredCrowds.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="filteredCrowds" style="margin-top: 16px">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="sort_order" label="排序" width="100" />
|
||||
@@ -34,15 +50,23 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getCrowds, createCrowd, updateCrowd, deleteCrowd } from '@/api/category'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const crowds = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const form = ref({})
|
||||
const searchKeyword = ref('')
|
||||
|
||||
const filteredCrowds = computed(() => {
|
||||
if (!searchKeyword.value) return crowds.value
|
||||
const kw = searchKeyword.value.toLowerCase()
|
||||
return crowds.value.filter(c => c.name.toLowerCase().includes(kw))
|
||||
})
|
||||
|
||||
const loadData = async () => { crowds.value = await getCrowds() }
|
||||
const handleSearch = () => { /* computed property handles filtering */ }
|
||||
const handleEdit = (row) => { form.value = { ...row }; dialogVisible.value = true }
|
||||
const handleSave = async () => {
|
||||
form.value.id ? await updateCrowd(form.value.id, form.value) : await createCrowd(form.value)
|
||||
@@ -60,4 +84,14 @@ onMounted(() => loadData())
|
||||
|
||||
<style scoped>
|
||||
.header { display: flex; justify-content: space-between; align-items: center; }
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.result-count {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,23 @@
|
||||
<h2>类型管理</h2>
|
||||
<el-button type="primary" @click="dialogVisible = true; form = {}">添加</el-button>
|
||||
</div>
|
||||
<el-table :data="types" style="margin-top: 20px">
|
||||
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索名称..."
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<span>🔍</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="result-count">共 {{ filteredTypes.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="paginatedTypes" style="margin-top: 16px">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="sort_order" label="排序" width="100" />
|
||||
@@ -34,15 +50,26 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getTypes, createType, updateType, deleteType } from '@/api/category'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const types = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const form = ref({})
|
||||
const searchKeyword = ref('')
|
||||
const searchResults = ref([])
|
||||
|
||||
const filteredTypes = computed(() => {
|
||||
if (!searchKeyword.value) return types.value
|
||||
const kw = searchKeyword.value.toLowerCase()
|
||||
return types.value.filter(t => t.name.toLowerCase().includes(kw))
|
||||
})
|
||||
|
||||
const paginatedTypes = computed(() => filteredTypes.value)
|
||||
|
||||
const loadData = async () => { types.value = await getTypes() }
|
||||
const handleSearch = () => { /* computed property handles filtering */ }
|
||||
const handleEdit = (row) => { form.value = { ...row }; dialogVisible.value = true }
|
||||
const handleSave = async () => {
|
||||
form.value.id ? await updateType(form.value.id, form.value) : await createType(form.value)
|
||||
@@ -60,4 +87,14 @@ onMounted(() => loadData())
|
||||
|
||||
<style scoped>
|
||||
.header { display: flex; justify-content: space-between; align-items: center; }
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.result-count {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,20 @@
|
||||
<div class="header">
|
||||
<h2>反馈管理</h2>
|
||||
</div>
|
||||
<el-table :data="feedbacks" v-loading="loading" style="margin-top: 20px">
|
||||
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索名称/内容..."
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<template #prefix><span>🔍</span></template>
|
||||
</el-input>
|
||||
<span class="result-count">共 {{ filteredFeedbacks.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="filteredFeedbacks" v-loading="loading" style="margin-top: 16px">
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column prop="name" label="名称" width="120" />
|
||||
<el-table-column prop="email" label="邮箱" width="200" />
|
||||
@@ -19,12 +32,22 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getFeedbacks, deleteFeedback } from '@/api/feedback'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const feedbacks = ref([])
|
||||
const loading = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
const filteredFeedbacks = computed(() => {
|
||||
if (!searchKeyword.value) return feedbacks.value
|
||||
const kw = searchKeyword.value.toLowerCase()
|
||||
return feedbacks.value.filter(f =>
|
||||
(f.name && f.name.toLowerCase().includes(kw)) ||
|
||||
(f.content && f.content.toLowerCase().includes(kw))
|
||||
)
|
||||
})
|
||||
|
||||
const loadFeedbacks = async () => {
|
||||
loading.value = true
|
||||
@@ -51,4 +74,14 @@ onMounted(() => loadFeedbacks())
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.result-count {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,20 @@
|
||||
<h2>友链管理</h2>
|
||||
<el-button type="primary" @click="$router.push('/links/edit')">添加友链</el-button>
|
||||
</div>
|
||||
<el-table :data="links" v-loading="loading" style="margin-top: 20px">
|
||||
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索名称/URL..."
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<template #prefix><span>🔍</span></template>
|
||||
</el-input>
|
||||
<span class="result-count">共 {{ filteredLinks.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="filteredLinks" v-loading="loading" style="margin-top: 16px">
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="url" label="URL" min-width="200">
|
||||
@@ -26,12 +39,21 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getLinks, deleteLink } from '@/api/link'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const links = ref([])
|
||||
const loading = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
const filteredLinks = computed(() => {
|
||||
if (!searchKeyword.value) return links.value
|
||||
const kw = searchKeyword.value.toLowerCase()
|
||||
return links.value.filter(l =>
|
||||
l.name.toLowerCase().includes(kw) || l.url.toLowerCase().includes(kw)
|
||||
)
|
||||
})
|
||||
|
||||
const loadLinks = async () => {
|
||||
loading.value = true
|
||||
@@ -58,4 +80,14 @@ onMounted(() => loadLinks())
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.result-count {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
@@ -9,6 +9,7 @@ export default defineConfig({
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
},
|
||||
base: '/admin/',
|
||||
server: {
|
||||
port: 3001,
|
||||
proxy: {
|
||||
|
||||
Reference in New Issue
Block a user