newfront/src/components/user_center/SubmissionList.vue
2025-04-14 17:58:33 +08:00

882 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="submission-container">
<div class="header">
<h3 class="title">我的投稿记录</h3>
<div class="header-buttons">
<button class="key-manage-btn" @click="showKeyManagement = true">密钥管理</button>
<button class="manage-btn" @click="handleManage">我要投稿</button>
</div>
</div>
<!-- 密钥管理模态框 -->
<div v-if="showKeyManagement" class="modal-overlay" @click.self="showKeyManagement = false">
<div class="modal-content key-management-modal">
<div class="modal-header">
<h3>密钥管理</h3>
<button class="close-btn" @click="showKeyManagement = false">×</button>
</div>
<div class="modal-body">
<button class="exchange-key-btn" @click="showExchangeKey = true">兑换密钥</button>
<div v-if="keyList.length > 0" class="key-table-wrapper">
<table class="key-table">
<thead>
<tr>
<th>密钥</th>
<th>状态</th>
<th>创建时间</th>
<th>最后使用时间</th>
</tr>
</thead>
<tbody>
<tr v-for="(key, index) in keyList" :key="index">
<td class="key-cell">
<div class="key-content">
<span>{{ visibleKeyIndex === index ? key.key_plaintext : '******' }}</span>
<button class="toggle-visibility" @click="toggleKeyVisibility(index)">
<svg v-if="visibleKeyIndex !== index" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="1.1">
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path>
<path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6"></path>
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="1.1">
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828"></path>
<path d="M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87"></path>
<path d="M3 3l18 18"></path>
</svg>
</button>
</div>
</td>
<td>
<span :class="key.is_valid ? 'status-valid' : 'status-invalid'">
{{ key.is_valid ? '有效' : '无效' }}
</span>
</td>
<td>{{ formatDate(key.created_at) }}</td>
<td>{{ key.last_used_at ? formatDate(key.last_used_at) : '未使用' }}</td>
</tr>
</tbody>
</table>
</div>
<div v-else-if="loading" class="loading-keys">
加载中...
</div>
<div v-else class="empty-keys">
<p>暂无密钥记录</p>
</div>
</div>
</div>
</div>
<!-- 兑换密钥模态框 -->
<div v-if="showExchangeKey" class="modal-overlay" @click.self="showExchangeKey = false">
<div class="modal-content exchange-key-modal">
<div class="modal-header">
<h3>兑换密钥</h3>
<button class="close-btn" @click="showExchangeKey = false">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="invite-code">邀请码</label>
<input
id="invite-code"
v-model="exchangeForm.invite_code"
type="text"
placeholder="输入邀请码"
/>
</div>
<div class="form-group">
<label for="custom-key">自定义密钥</label>
<input
id="custom-key"
v-model="exchangeForm.key"
type="text"
placeholder="输入您的自定义密钥"
/>
</div>
<div class="form-actions">
<button
class="submit-btn"
:disabled="!exchangeForm.invite_code || !exchangeForm.key || submitting"
@click="exchangeKey"
>
{{ submitting ? '处理中...' : '确认兑换' }}
</button>
</div>
</div>
</div>
</div>
<div class="submission-table-wrapper">
<table class="submission-table" v-if="submissions.length > 0">
<thead>
<tr>
<th>标题</th>
<th>板块</th>
<th>审核状态</th>
<th>审核备注</th>
<th>投稿时间</th>
<th>状态更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in submissions" :key="item.id"
:class="{ 'clickable': item.article_id }"
@click="item.article_id && navigateToArticle(item.article_id)">
<td class="title-cell">{{ item.title }}</td>
<td>{{ item.section }}</td>
<td>
<span :class="getStatusClass(item.status)">{{ item.status }}</span>
</td>
<td>{{ item.note || '无' }}</td>
<td>{{ formatDate(item.created_at) }}</td>
<td>{{ formatDate(item.updated_at) }}</td>
<td>
<button
v-if="item.status === '已通过' && item.article_id"
class="edit-btn"
@click.stop="navigateToEdit(item.article_id)"
>
编辑
</button>
</td>
</tr>
</tbody>
</table>
<div class="empty-state" v-else>
<p>暂无投稿记录</p>
</div>
<!-- 分页控件 -->
<div v-if="totalPages > 1" class="pagination">
<button
:disabled="currentPage === 1"
@click="loadSubmissions(currentPage - 1)"
class="page-btn"
>
上一页
</button>
<span class="page-info">{{ currentPage }} / {{ totalPages }}</span>
<button
:disabled="currentPage === totalPages"
@click="loadSubmissions(currentPage + 1)"
class="page-btn"
>
下一页
</button>
</div>
</div>
</div>
</template>
<script>
import Cookies from 'js-cookie';
import messageBox from '../../utils/messageBox.js';
export default {
name: 'SubmissionList',
data() {
return {
submissions: [],
currentPage: 1,
totalPages: 1,
totalSubmissions: 0,
loading: false,
// 密钥管理相关数据
showKeyManagement: false,
showExchangeKey: false,
keyList: [],
visibleKeyIndex: -1, // -1表示没有可见的密钥
exchangeForm: {
invite_code: '',
key: ''
},
submitting: false
}
},
created() {
this.loadSubmissions(1);
},
methods: {
async loadSubmissions(pageNum) {
this.loading = true;
try {
const token = Cookies.get('token');
if (!token) {
console.error('未找到登录token');
return;
}
const response = await fetch('https://newfront.xn--xhq44jb2fzpc.com/user/submission', {
method: 'POST',
headers: {
'Authorization': token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ page_num: pageNum })
});
if (!response.ok) {
throw new Error('获取投稿记录失败');
}
const result = await response.json();
this.submissions = result.submissions;
this.currentPage = result.current_page;
this.totalPages = result.total_pages;
this.totalSubmissions = result.total_submissions;
} catch (error) {
console.error('获取投稿记录出错:', error);
} finally {
this.loading = false;
}
},
// 作品管理按钮点击处理
handleManage() {
// 这里添加作品管理的入口函数逻辑
console.log('点击了作品管理按钮');
// 可以触发父组件事件或执行其他操作
this.$emit('manage-works');
},
// 跳转到文章页面
navigateToArticle(articleId) {
// window.location.href = `/article/${articleId}`;
// 或者使用Vue Router:
this.$router.push(`/article/${articleId}`);
},
// 跳转到编辑页面
navigateToEdit(articleId) {
// 检测是否为移动设备
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth < 768;
if (isMobile) {
// 移动设备显示确认对话框
messageBox.confirm("建议在电脑端进行编辑以获得最佳体验。仍要继续吗?", "提示")
.then(() => {
// 用户确认,跳转到编辑页面
this.$router.push(`/edit?article=${articleId}`);
})
.catch(() => {
// 用户取消,不执行任何操作
console.log("用户取消了在移动端编辑");
});
} else {
// 电脑端直接跳转到编辑页面
this.$router.push(`/edit?article=${articleId}`);
}
},
// 格式化日期
formatDate(dateString) {
if (!dateString) return '未知';
const date = new Date(dateString);
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`;
},
// 获取状态样式类
getStatusClass(status) {
switch (status) {
case '已通过':
return 'status-approved';
case '审核中':
return 'status-pending';
case '未通过':
return 'status-rejected';
default:
return '';
}
},
// 加载密钥列表
async loadKeyList() {
this.loading = true;
try {
const token = Cookies.get('token');
if (!token) {
console.error('未找到登录token');
return;
}
const response = await fetch('https://newfront.xn--xhq44jb2fzpc.com/encrypt/getcode', {
method: 'GET',
headers: {
'Authorization': token,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('获取密钥列表失败');
}
const result = await response.json();
if (result.success) {
this.keyList = result.data || [];
} else {
throw new Error(result.message || '获取密钥列表失败');
}
} catch (error) {
console.error('获取密钥列表出错:', error);
messageBox.alert(error.message || '获取密钥列表失败,请稍后再试', '错误');
} finally {
this.loading = false;
}
},
// 切换密钥可见性
toggleKeyVisibility(index) {
if (this.visibleKeyIndex === index) {
this.visibleKeyIndex = -1; // 隐藏密钥
} else {
this.visibleKeyIndex = index; // 显示指定索引的密钥
}
},
// 兑换密钥
async exchangeKey() {
if (!this.exchangeForm.invite_code || !this.exchangeForm.key) {
messageBox.alert('请输入邀请码和密钥', '提示');
return;
}
this.submitting = true;
try {
const token = Cookies.get('token');
if (!token) {
console.error('未找到登录token');
return;
}
const response = await fetch('https://newfront.xn--xhq44jb2fzpc.com/encrypt/setcode', {
method: 'POST',
headers: {
'Authorization': token,
'Content-Type': 'application/json'
},
body: JSON.stringify(this.exchangeForm)
});
const result = await response.json();
if (result.success) {
messageBox.alert('密钥兑换成功', '提示');
this.showExchangeKey = false;
this.exchangeForm.invite_code = '';
this.exchangeForm.key = '';
// 重新加载密钥列表
this.loadKeyList();
} else {
throw new Error(result.message || '密钥兑换失败');
}
} catch (error) {
console.error('密钥兑换出错:', error);
messageBox.alert(error.message || '密钥兑换失败,请稍后再试', '错误');
} finally {
this.submitting = false;
}
}
},
watch: {
showKeyManagement(val) {
if (val) {
// 当显示密钥管理模态框时,加载密钥列表
this.loadKeyList();
} else {
// 当关闭密钥管理模态框时,重置可见密钥索引
this.visibleKeyIndex = -1;
}
}
}
}
</script>
<style scoped>
.submission-container {
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
padding: 20px;
margin-bottom: 24px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #e2e8f0;
}
.title {
font-size: 18px;
font-weight: 600;
color: #2d3748;
margin: 0;
}
.header-buttons {
display: flex;
gap: 12px;
}
.key-manage-btn {
background-color: #6b7280;
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.key-manage-btn:hover {
background-color: #4b5563;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.manage-btn {
background-color: #3182ce;
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.manage-btn:hover {
background-color: #2c5282;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.submission-table-wrapper {
overflow-x: auto;
}
.submission-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
white-space: nowrap;
}
.submission-table th,
.submission-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
font-size: 14px;
}
.submission-table th {
background-color: #f7fafc;
color: #4a5568;
font-weight: 600;
position: sticky;
top: 0;
}
.submission-table tr:last-child td {
border-bottom: none;
}
.submission-table tbody tr {
transition: background-color 0.2s ease;
}
.submission-table tbody tr:hover {
background-color: #f7fafc;
}
.clickable {
cursor: pointer;
position: relative;
}
.clickable:hover {
background-color: #ebf8ff !important;
}
.clickable:after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 3px;
height: 100%;
background-color: #3182ce;
}
.title-cell {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.empty-state {
padding: 40px 20px;
text-align: center;
color: #718096;
font-size: 15px;
background-color: #f7fafc;
border-radius: 8px;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
gap: 12px;
}
.page-btn {
background-color: white;
border: 1px solid #e2e8f0;
color: #4a5568;
padding: 6px 12px;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.page-btn:hover:not(:disabled) {
background-color: #f7fafc;
border-color: #cbd5e0;
}
.page-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.page-info {
color: #4a5568;
font-size: 14px;
font-weight: 500;
}
.status-approved {
color: #38a169;
background-color: #f0fff4;
padding: 4px 8px;
border-radius: 4px;
font-weight: 500;
font-size: 13px;
}
.status-pending {
color: #d69e2e;
background-color: #fffaf0;
padding: 4px 8px;
border-radius: 4px;
font-weight: 500;
font-size: 13px;
}
.status-rejected {
color: #e53e3e;
background-color: #fff5f5;
padding: 4px 8px;
border-radius: 4px;
font-weight: 500;
font-size: 13px;
}
.edit-btn {
background-color: #4299e1;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s ease;
}
.edit-btn:hover {
background-color: #3182ce;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 模态框样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background-color: white;
border-radius: 12px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.15);
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #e2e8f0;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #2d3748;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
color: #718096;
cursor: pointer;
transition: color 0.2s ease;
}
.close-btn:hover {
color: #4a5568;
}
.modal-body {
padding: 20px;
overflow-y: auto;
}
/* 密钥管理模态框特定样式 */
.key-management-modal {
min-height: 300px;
}
.exchange-key-btn {
background-color: #3182ce;
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
margin-bottom: 20px;
}
.exchange-key-btn:hover {
background-color: #2c5282;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.key-table-wrapper {
margin-top: 16px;
overflow-x: auto;
width: 100%;
}
.key-table {
width: 100%;
min-width: 500px;
border-collapse: separate;
border-spacing: 0;
}
.key-table th,
.key-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
font-size: 14px;
}
.key-table th {
background-color: #f7fafc;
color: #4a5568;
font-weight: 600;
position: sticky;
top: 0;
}
.key-content {
display: flex;
align-items: center;
gap: 10px;
}
.toggle-visibility {
background: none;
border: none;
cursor: pointer;
color: #718096;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.2s ease;
}
.toggle-visibility:hover {
color: #4a5568;
}
.status-valid {
color: #38a169;
background-color: #f0fff4;
padding: 4px 8px;
border-radius: 4px;
font-weight: 500;
font-size: 13px;
}
.status-invalid {
color: #e53e3e;
background-color: #fff5f5;
padding: 4px 8px;
border-radius: 4px;
font-weight: 500;
font-size: 13px;
}
.loading-keys, .empty-keys {
padding: 40px 20px;
text-align: center;
color: #718096;
font-size: 15px;
background-color: #f7fafc;
border-radius: 8px;
margin-top: 16px;
}
/* 兑换密钥模态框样式 */
.exchange-key-modal {
max-width: 450px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #4a5568;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 10px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.2s ease;
}
.form-group input:focus {
outline: none;
border-color: #3182ce;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.15);
}
.form-actions {
margin-top: 24px;
display: flex;
justify-content: flex-end;
}
.submit-btn {
background-color: #3182ce;
color: white;
border: none;
border-radius: 6px;
padding: 10px 20px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.submit-btn:hover:not(:disabled) {
background-color: #2c5282;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.submit-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
@media (max-width: 768px) {
.submission-container {
padding: 16px;
}
.header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.header-buttons {
display: flex;
width: 100%;
justify-content: space-between;
}
.key-manage-btn,
.manage-btn {
flex: 1;
}
.modal-content {
width: 95%;
max-height: 90vh;
}
.key-table-wrapper {
margin: 0 -10px;
padding: 0 10px;
width: calc(100% + 20px);
}
.key-table th,
.key-table td {
padding: 10px 12px;
font-size: 13px;
white-space: nowrap;
}
}
</style>