feat: 前后台静态资源路径冲突修复 + 用户管理功能
- 修改 admin/vite.config.js 添加 base: '/admin/' 解决静态资源路径问题 - 修复后台 index.html 资源引用从 /assets/ → /admin/assets/ - 更新优化器默认 API 地址为服务器地址 - 添加友链管理相关代码 - 修复多处分类管理页面
This commit is contained in:
+3
-1
@@ -1,3 +1,5 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
export const getLinks = () => request.get('/links')
|
||||
export const getLinks = () => request.get('/links')
|
||||
|
||||
export const applyLink = (data) => request.post('/links/apply', data)
|
||||
@@ -7,11 +7,28 @@
|
||||
<span class="tag tag-crowd" v-for="(name, i) in (joke.crowd_names||[])" :key="'c'+i">{{ name }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 标题 -->
|
||||
<h1 class="detail-title">{{ joke.title }}</h1>
|
||||
<!-- 标题 + AI评价 -->
|
||||
<div class="title-row">
|
||||
<h1 class="detail-title">{{ joke.title }}</h1>
|
||||
<span v-if="joke.ai_level" class="ai-badge" :class="'ai-' + joke.ai_level">
|
||||
{{ getLevelText(joke.ai_level) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="detail-content">{{ joke.content }}</div>
|
||||
<!-- 内容:优先显示润色内容 -->
|
||||
<div class="detail-content">
|
||||
<template v-if="joke.polished_content">
|
||||
<div class="polished-label">✨ AI 润色版</div>
|
||||
{{ joke.polished_content }}
|
||||
</template>
|
||||
<template v-else>{{ joke.content }}</template>
|
||||
</div>
|
||||
|
||||
<!-- 原始内容(如果有润色) -->
|
||||
<details v-if="joke.polished_content" class="original-content">
|
||||
<summary>查看原文</summary>
|
||||
{{ joke.content }}
|
||||
</details>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="detail-actions">
|
||||
@@ -33,6 +50,8 @@
|
||||
</button>
|
||||
<span class="sep">|</span>
|
||||
<span>{{ formatDate(joke.created_at) }}</span>
|
||||
<span v-if="joke.ai_score" class="sep">|</span>
|
||||
<span v-if="joke.ai_score" class="ai-score">评分: {{ joke.ai_score.toFixed(1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,6 +125,16 @@ const formatDate = (dateStr) => {
|
||||
return new Date(dateStr).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })
|
||||
}
|
||||
|
||||
function getLevelText(level) {
|
||||
const map = {
|
||||
'excellent': '⭐⭐⭐ 精品',
|
||||
'good': '⭐⭐ 良好',
|
||||
'ordinary': '⭐ 普通',
|
||||
'poor': '⚠️ 待优化'
|
||||
}
|
||||
return map[level] || ''
|
||||
}
|
||||
|
||||
onMounted(() => loadJoke())
|
||||
|
||||
watch(() => route.params.id, () => loadJoke())
|
||||
@@ -131,7 +160,60 @@ watch(() => route.params.id, () => loadJoke())
|
||||
transition: background 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.detail-tags { display: flex; gap: 10px; }
|
||||
.detail-tags { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
|
||||
|
||||
/* 标题行:标题 + AI评价右对齐 */
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* AI 评价徽章 */
|
||||
.ai-badge {
|
||||
font-size: 13px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 14px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ai-excellent { background: linear-gradient(135deg, #fef3c7, #fde68a); color: #92400e; }
|
||||
.ai-good { background: linear-gradient(135deg, #d1fae5, #a7f3d0); color: #065f46; }
|
||||
.ai-ordinary { background: #f3f4f6; color: #4b5563; }
|
||||
.ai-poor { background: #fee2e2; color: #991b1b; }
|
||||
|
||||
/* AI 润色标签 */
|
||||
.polished-label {
|
||||
font-size: 12px;
|
||||
color: #9333ea;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* 原文折叠 */
|
||||
.original-content {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.original-content summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* AI 评分 */
|
||||
.ai-score {
|
||||
color: #9333ea;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 26px;
|
||||
@@ -140,6 +222,8 @@ watch(() => route.params.id, () => loadJoke())
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.5px;
|
||||
transition: color 0.3s;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
<span class="tag tag-crowd" v-for="(name, i) in (joke.crowd_names||[])" :key="'c'+i">{{ name }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 标题 -->
|
||||
<h3 class="card-title">{{ joke.title }}</h3>
|
||||
<!-- 标题 + AI评价等级 -->
|
||||
<div class="title-row">
|
||||
<h3 class="card-title">{{ joke.title }}</h3>
|
||||
<span v-if="joke.ai_level" class="ai-badge" :class="'ai-' + joke.ai_level">
|
||||
{{ getLevelText(joke.ai_level) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<p class="card-content">{{ joke.content }}</p>
|
||||
<!-- 内容:优先显示润色内容 -->
|
||||
<p class="card-content">{{ joke.polished_content || joke.content }}</p>
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<div class="card-footer">
|
||||
@@ -33,6 +38,16 @@
|
||||
defineProps({
|
||||
joke: { type: Object, required: true }
|
||||
})
|
||||
|
||||
function getLevelText(level) {
|
||||
const map = {
|
||||
'excellent': '⭐⭐⭐ 精品',
|
||||
'good': '⭐⭐ 良好',
|
||||
'ordinary': '⭐ 普通',
|
||||
'poor': '⚠️ 待优化'
|
||||
}
|
||||
return map[level] || ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -68,7 +83,15 @@ defineProps({
|
||||
.joke-card:hover::before { transform: scaleY(1); }
|
||||
|
||||
/* 标签 */
|
||||
.card-header { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.card-header { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
||||
|
||||
/* 标题行:标题 + AI评价右对齐 */
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 标题 */
|
||||
.card-title {
|
||||
@@ -80,8 +103,24 @@ defineProps({
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* AI 评价徽章 */
|
||||
.ai-badge {
|
||||
font-size: 11px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ai-excellent { background: linear-gradient(135deg, #fef3c7, #fde68a); color: #92400e; }
|
||||
.ai-good { background: linear-gradient(135deg, #d1fae5, #a7f3d0); color: #065f46; }
|
||||
.ai-ordinary { background: #f3f4f6; color: #4b5563; }
|
||||
.ai-poor { background: #fee2e2; color: #991b1b; }
|
||||
|
||||
/* 内容 */
|
||||
.card-content {
|
||||
font-size: 14px;
|
||||
|
||||
@@ -26,15 +26,62 @@
|
||||
<div class="link-desc" v-if="link.description">{{ link.description }}</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 自助申请 -->
|
||||
<div class="apply-section">
|
||||
<button class="apply-btn" @click="showDialog = true">
|
||||
<span class="apply-icon">+</span>
|
||||
自助申请友链
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 申请弹窗 -->
|
||||
<div v-if="showDialog" class="dialog-overlay" @click.self="showDialog = false">
|
||||
<div class="dialog">
|
||||
<h2 class="dialog-title">申请友链</h2>
|
||||
<form @submit.prevent="handleApply" class="apply-form">
|
||||
<div class="form-item">
|
||||
<label>网站名称 <span class="required">*</span></label>
|
||||
<input v-model="form.name" type="text" placeholder="请输入网站名称" required />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>网站地址 <span class="required">*</span></label>
|
||||
<input v-model="form.url" type="url" placeholder="https://example.com" required />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>网站描述</label>
|
||||
<input v-model="form.description" type="text" placeholder="简短描述您的网站" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>联系方式</label>
|
||||
<input v-model="form.contact" type="text" placeholder="邮箱或社交账号(选填)" />
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button type="button" class="btn-cancel" @click="showDialog = false">取消</button>
|
||||
<button type="submit" class="btn-submit" :disabled="submitting">
|
||||
{{ submitting ? '提交中...' : '提交申请' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getLinks } from '@/api/link'
|
||||
import { getLinks, applyLink } from '@/api/link'
|
||||
|
||||
const links = ref([])
|
||||
const loading = ref(true)
|
||||
const showDialog = ref(false)
|
||||
const submitting = ref(false)
|
||||
const form = ref({
|
||||
name: '',
|
||||
url: '',
|
||||
description: '',
|
||||
contact: '',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
@@ -45,6 +92,21 @@ onMounted(async () => {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const handleApply = async () => {
|
||||
submitting.value = true
|
||||
try {
|
||||
await applyLink(form.value)
|
||||
alert('申请已提交,等待审核!')
|
||||
showDialog.value = false
|
||||
form.value = { name: '', url: '', description: '', contact: '' }
|
||||
} catch (e) {
|
||||
alert('提交失败,请重试')
|
||||
console.error(e)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -101,4 +163,146 @@ onMounted(async () => {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 自助申请 */
|
||||
.apply-section {
|
||||
margin-top: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
background: var(--bg-card);
|
||||
border: 2px dashed var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-secondary);
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.apply-btn:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.apply-icon {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 弹窗 */
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background: var(--bg-card);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
width: 90%;
|
||||
max-width: 420px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 20px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.apply-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.form-item label {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.form-item input {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.form-item input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.form-item input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-submit {
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
background: var(--primary);
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-submit:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user