待审批区增加批量拒绝功能

This commit is contained in:
www.xueximeng.com
2025-10-22 19:54:01 +08:00
parent 3f015ae368
commit 8932ffa659
7 changed files with 535 additions and 80 deletions

View File

@@ -1,3 +1,3 @@
BASE_URL=https://dm.xueximeng.com
ASSETS_PATH=../assets
# ASSETS_PATH=../data/assets
# ASSETS_PATH=../assets
ASSETS_PATH=../data/assets

View File

@@ -2,19 +2,19 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://dm.xueximeng.com/</loc>
<lastmod>2025-08-08</lastmod>
<lastmod>2025-08-13</lastmod>
<changefreq>daily</changefreq>
<priority>1</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/submit</loc>
<lastmod>2025-08-08</lastmod>
<lastmod>2025-08-13</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/about</loc>
<lastmod>2025-08-08</lastmod>
<lastmod>2025-08-13</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
@@ -37,14 +37,14 @@
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/46</loc>
<lastmod>2025-07-25</lastmod>
<loc>https://dm.xueximeng.com/resource/50</loc>
<lastmod>2025-07-18</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/50</loc>
<lastmod>2025-07-18</lastmod>
<loc>https://dm.xueximeng.com/resource/46</loc>
<lastmod>2025-07-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -55,14 +55,14 @@
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/18</loc>
<lastmod>2025-07-19</lastmod>
<loc>https://dm.xueximeng.com/resource/24</loc>
<lastmod>2025-06-18</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/24</loc>
<lastmod>2025-06-18</lastmod>
<loc>https://dm.xueximeng.com/resource/18</loc>
<lastmod>2025-07-19</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -74,7 +74,7 @@
</url>
<url>
<loc>https://dm.xueximeng.com/resource/34</loc>
<lastmod>2025-06-27</lastmod>
<lastmod>2025-08-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -86,13 +86,7 @@
</url>
<url>
<loc>https://dm.xueximeng.com/resource/30</loc>
<lastmod>2025-06-27</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/26</loc>
<lastmod>2025-07-18</lastmod>
<lastmod>2025-08-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -102,9 +96,21 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/26</loc>
<lastmod>2025-07-18</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/161</loc>
<lastmod>2025-07-17</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/8</loc>
<lastmod>2025-06-20</lastmod>
<lastmod>2025-08-08</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -132,6 +138,12 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/9</loc>
<lastmod>2025-08-08</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/28</loc>
<lastmod>2025-06-27</lastmod>
@@ -175,20 +187,20 @@
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/9</loc>
<lastmod>2025-06-22</lastmod>
<loc>https://dm.xueximeng.com/resource/184</loc>
<lastmod>2025-07-18</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/14</loc>
<lastmod>2025-05-30</lastmod>
<lastmod>2025-08-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/38</loc>
<lastmod>2025-06-25</lastmod>
<lastmod>2025-08-12</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -204,18 +216,6 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/161</loc>
<lastmod>2025-07-17</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/184</loc>
<lastmod>2025-07-18</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/185</loc>
<lastmod>2025-07-16</lastmod>
@@ -228,6 +228,12 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/15</loc>
<lastmod>2025-07-16</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/16</loc>
<lastmod>2025-07-22</lastmod>
@@ -258,6 +264,12 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/89</loc>
<lastmod>2025-06-21</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/90</loc>
<lastmod>2025-06-23</lastmod>
@@ -288,12 +300,6 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/15</loc>
<lastmod>2025-07-16</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/21</loc>
<lastmod>2025-06-28</lastmod>
@@ -337,7 +343,7 @@
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/89</loc>
<loc>https://dm.xueximeng.com/resource/88</loc>
<lastmod>2025-06-21</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
@@ -420,6 +426,12 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/35</loc>
<lastmod>2025-06-27</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/39</loc>
<lastmod>2025-06-27</lastmod>
@@ -446,7 +458,7 @@
</url>
<url>
<loc>https://dm.xueximeng.com/resource/57</loc>
<lastmod>2025-06-10</lastmod>
<lastmod>2025-08-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -493,8 +505,8 @@
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/88</loc>
<lastmod>2025-06-21</lastmod>
<loc>https://dm.xueximeng.com/resource/97</loc>
<lastmod>2025-06-19</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -510,12 +522,24 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/118</loc>
<lastmod>2025-06-18</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/119</loc>
<lastmod>2025-07-18</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/121</loc>
<lastmod>2025-06-19</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/135</loc>
<lastmod>2025-06-22</lastmod>
@@ -534,6 +558,12 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/152</loc>
<lastmod>2025-07-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/153</loc>
<lastmod>2025-06-24</lastmod>
@@ -578,7 +608,7 @@
</url>
<url>
<loc>https://dm.xueximeng.com/resource/17</loc>
<lastmod>2025-06-15</lastmod>
<lastmod>2025-08-12</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -594,12 +624,6 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/35</loc>
<lastmod>2025-06-27</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/44</loc>
<lastmod>2025-06-15</lastmod>
@@ -662,7 +686,7 @@
</url>
<url>
<loc>https://dm.xueximeng.com/resource/78</loc>
<lastmod>2025-06-25</lastmod>
<lastmod>2025-08-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
@@ -696,12 +720,6 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/97</loc>
<lastmod>2025-06-19</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/98</loc>
<lastmod>2025-06-19</lastmod>
@@ -750,24 +768,12 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/118</loc>
<lastmod>2025-06-18</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/120</loc>
<lastmod>2025-07-03</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/121</loc>
<lastmod>2025-06-19</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/122</loc>
<lastmod>2025-06-19</lastmod>
@@ -870,12 +876,6 @@
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/152</loc>
<lastmod>2025-07-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dm.xueximeng.com/resource/155</loc>
<lastmod>2025-07-02</lastmod>

View File

@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试设置API</title>
<style>
body {
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
h1 {
color: #333;
text-align: center;
}
.card {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
textarea {
width: 100%;
height: 200px;
padding: 10px;
border-radius: 4px;
border: 1px solid #ddd;
margin-bottom: 10px;
font-family: monospace;
resize: vertical;
}
button {
background: #4f46e5;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
button:hover {
background: #4338ca;
}
.result {
margin-top: 20px;
padding: 15px;
border-radius: 4px;
white-space: pre-wrap;
font-family: monospace;
background-color: #f8f8f8;
border: 1px solid #ddd;
max-height: 300px;
overflow: auto;
}
.success {
background-color: #d1fae5;
border-color: #10b981;
}
.error {
background-color: #fee2e2;
border-color: #ef4444;
}
</style>
</head>
<body>
<h1>设置API测试工具</h1>
<div class="card">
<h2>测试页脚设置API</h2>
<p>这个工具可以帮助您测试页脚设置API发送PUT请求到 <code>/api/settings/footer</code> 端点。</p>
<div>
<label for="token"><strong>认证令牌 (Bearer Token)</strong></label>
<input type="text" id="token" style="width: 100%; padding: 8px; margin: 5px 0 15px;" placeholder="粘贴您的认证令牌">
</div>
<div>
<label for="payload"><strong>请求数据 (JSON)</strong></label>
<textarea id="payload" placeholder="输入JSON格式的请求数据">{
"setting_value": {
"links": [
{ "text": "关于我们", "url": "/about", "type": "internal" },
{ "text": "GitHub", "url": "https://github.com/fish2018/GoComicMosaic", "icon": "bi bi-github", "type": "external", "title": "查看GitHub源码" }
],
"copyright": "© 2025 美漫资源共建. 保留所有权利",
"show_visitor_count": true
}
}</textarea>
</div>
<div>
<button id="sendRequest">发送PUT请求</button>
<button id="checkToken" style="background: #059669;">检查令牌</button>
</div>
<div id="result" class="result" style="display: none;">
待发送请求...
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 从localStorage中获取token
try {
const savedToken = localStorage.getItem('accessToken');
if (savedToken) {
document.getElementById('token').value = savedToken;
console.log('已从localStorage加载令牌');
}
} catch (e) {
console.error('读取localStorage失败:', e);
}
// 发送请求按钮
document.getElementById('sendRequest').addEventListener('click', async function() {
const token = document.getElementById('token').value.trim();
const payload = document.getElementById('payload').value.trim();
const resultElement = document.getElementById('result');
resultElement.style.display = 'block';
resultElement.className = 'result';
resultElement.textContent = '发送请求中...';
if (!token) {
resultElement.textContent = '错误: 请提供认证令牌';
resultElement.className = 'result error';
return;
}
try {
let payloadObj = JSON.parse(payload);
// 创建一个XHR请求
const xhr = new XMLHttpRequest();
xhr.open('PUT', '/api/settings/footer', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.withCredentials = true;
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resultElement.textContent = `请求成功 (${xhr.status} ${xhr.statusText}):\n\n${xhr.responseText}`;
resultElement.className = 'result success';
} else {
resultElement.textContent = `请求失败 (${xhr.status} ${xhr.statusText}):\n\n${xhr.responseText}`;
resultElement.className = 'result error';
}
};
xhr.onerror = function() {
resultElement.textContent = '网络错误,请检查控制台';
resultElement.className = 'result error';
};
const requestStart = new Date();
xhr.send(payload);
console.log('已发送请求:', payload);
console.log('请求头:', {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token.substring(0, 10)}...` // 只显示部分令牌
});
} catch (e) {
resultElement.textContent = `请求错误: ${e.message}`;
resultElement.className = 'result error';
}
});
// 检查令牌按钮
document.getElementById('checkToken').addEventListener('click', function() {
const token = document.getElementById('token').value.trim();
const resultElement = document.getElementById('result');
resultElement.style.display = 'block';
if (!token) {
resultElement.textContent = '错误: 请提供认证令牌';
resultElement.className = 'result error';
return;
}
try {
// 尝试解码令牌
const tokenParts = token.split('.');
if (tokenParts.length !== 3) {
resultElement.textContent = '无效的JWT令牌格式';
resultElement.className = 'result error';
return;
}
// 解码payload部分
const payload = JSON.parse(atob(tokenParts[1]));
const expDate = new Date(payload.exp * 1000);
const now = new Date();
const isExpired = expDate < now;
resultElement.textContent = `令牌信息:\n\n`;
resultElement.textContent += `有效期至: ${expDate.toLocaleString()}\n`;
resultElement.textContent += `当前时间: ${now.toLocaleString()}\n`;
resultElement.textContent += `是否过期: ${isExpired ? '已过期' : '有效'}\n\n`;
resultElement.textContent += `载荷数据:\n${JSON.stringify(payload, null, 2)}`;
resultElement.className = isExpired ? 'result error' : 'result success';
} catch (e) {
resultElement.textContent = `令牌解析错误: ${e.message}\n可能不是有效的JWT格式`;
resultElement.className = 'result error';
}
});
});
</script>
</body>
</html>

View File

@@ -3031,4 +3031,31 @@ textarea.custom-input {
font-size: 0.85rem;
color: var(--gray-color);
word-break: break-all;
}
/* 批量操作相关样式 */
.checkbox-column {
width: 40px;
text-align: center;
}
.checkbox-column input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.batch-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.selected-count {
font-size: 0.9rem;
color: var(--gray-color);
font-weight: 500;
padding: 0.25rem 0.75rem;
background: rgba(99, 102, 241, 0.1);
border-radius: 0.5rem;
}

View File

@@ -1367,6 +1367,22 @@
<div v-if="pendingResources.length > 0" class="badge-count badge-inline">{{ pendingResources.length }}</div>
</h4>
</div>
<!-- 批量操作按钮 -->
<div v-if="pendingResources.length > 0" class="header-actions">
<div v-if="selectedPendingResources.length > 0" class="batch-actions">
<span class="selected-count">已选择 {{ selectedPendingResources.length }} </span>
<button
type="button"
class="btn-custom btn-accent btn-sm"
@click="batchRejectResources"
:disabled="batchRejectLoading"
>
<div v-if="batchRejectLoading" class="spinner small-spinner"></div>
<i v-else class="bi bi-x-circle"></i>
<span class="btn-text">{{ batchRejectLoading ? '处理中...' : '批量拒绝' }}</span>
</button>
</div>
</div>
</div>
<div class="card-body">
<div v-if="loadingPending" class="loading-inline">
@@ -1381,6 +1397,14 @@
<table class="custom-table">
<thead>
<tr>
<th class="checkbox-column">
<input
type="checkbox"
:checked="isAllPendingSelected"
@change="toggleAllPending"
:indeterminate="isIndeterminate"
/>
</th>
<th>ID</th>
<th>标题</th>
<th>类型</th>
@@ -1392,6 +1416,13 @@
</thead>
<tbody>
<tr v-for="resource in pendingResources" :key="resource.id">
<td class="checkbox-column">
<input
type="checkbox"
:value="resource.id"
v-model="selectedPendingResources"
/>
</td>
<td><span class="id-badge">#{{ resource.id }}</span></td>
<td>{{ resource.title || resource.title_en }}</td>
<td><span class="type-badge">{{ resource.resource_type }}</span></td>
@@ -1789,6 +1820,8 @@ import { ElMessage } from 'element-plus'
const router = useRouter()
const resources = ref([])
const pendingResources = ref([])
const selectedPendingResources = ref([])
const batchRejectLoading = ref(false)
const loading = ref(true)
const loadingPending = ref(true)
const loadingUsers = ref(true)
@@ -1862,6 +1895,67 @@ const formatDate = (dateString) => {
}).format(date)
}
// 批量操作相关计算属性
const isAllPendingSelected = computed(() => {
if (pendingResources.value.length === 0) return false
return selectedPendingResources.value.length === pendingResources.value.length
})
const isIndeterminate = computed(() => {
return selectedPendingResources.value.length > 0 && selectedPendingResources.value.length < pendingResources.value.length
})
// 全选/取消全选
const toggleAllPending = (event) => {
if (event.target.checked) {
selectedPendingResources.value = pendingResources.value.map(r => r.id)
} else {
selectedPendingResources.value = []
}
}
// 批量拒绝资源
const batchRejectResources = async () => {
if (selectedPendingResources.value.length === 0) {
ElMessage.warning('请选择要拒绝的资源')
return
}
const confirmMsg = `确定要拒绝选中的 ${selectedPendingResources.value.length} 个资源吗?`
if (!confirm(confirmMsg)) {
return
}
batchRejectLoading.value = true
try {
// 使用新的批量拒绝API
const response = await axios.post('/api/resources/batch-reject', {
resource_ids: selectedPendingResources.value,
notes: '批量拒绝'
})
const data = response.data
if (data.success_count > 0) {
ElMessage.success(`成功拒绝 ${data.success_count} 个资源`)
// 重新加载待审批资源列表
await fetchPendingResources()
// 清空选中项
selectedPendingResources.value = []
}
if (data.failed_ids && data.failed_ids.length > 0) {
ElMessage.error(`${data.failed_ids.length} 个资源拒绝失败`)
}
} catch (err) {
console.error('批量拒绝失败:', err)
ElMessage.error('批量拒绝失败,请稍后重试')
} finally {
batchRejectLoading.value = false
}
}
// 获取所有已审批资源
const fetchResources = async () => {
loading.value = true

View File

@@ -1292,4 +1292,121 @@ func GetResourceApprovalRecords(c *gin.Context) {
"resource": resource,
"records": records,
})
}
// BatchRejectResources 批量拒绝资源 - 仅管理员可访问
func BatchRejectResources(c *gin.Context) {
// 解析请求体中的资源ID列表
var request struct {
ResourceIDs []int `json:"resource_ids" binding:"required"`
Notes string `json:"notes"`
}
if errBind := c.ShouldBindJSON(&request); errBind != nil {
log.Printf("解析请求体失败: %v", errBind)
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求参数"})
return
}
if len(request.ResourceIDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "资源ID列表为空"})
return
}
log.Printf("批量拒绝资源ID数量: %d, IDs: %v", len(request.ResourceIDs), request.ResourceIDs)
// 批量处理资源
var successCount int
var failedIDs []int
for _, resourceID := range request.ResourceIDs {
// 检查资源是否存在且为待审批状态
var resource models.Resource
errGet := models.DB.Get(&resource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if errGet != nil {
log.Printf("资源 %d 未找到: %v", resourceID, errGet)
failedIDs = append(failedIDs, resourceID)
continue
}
// 检查资源状态
if resource.Status != models.ResourceStatusPending {
log.Printf("资源 %d 不是待审批状态: %s", resourceID, resource.Status)
failedIDs = append(failedIDs, resourceID)
continue
}
// 更新资源状态为拒绝
resource.Status = models.ResourceStatusRejected
resource.UpdatedAt = time.Now()
// 创建审批记录
approvalRecord := models.ApprovalRecord{
ResourceID: resourceID,
Status: models.ResourceStatusRejected,
FieldApprovals: models.JsonMap{},
FieldRejections: models.JsonMap{},
ApprovedImages: []string{},
RejectedImages: []string{},
Notes: request.Notes,
ApprovedLinks: models.JsonMap{},
RejectedLinks: models.JsonMap{},
CreatedAt: time.Now(),
}
// 开始事务
tx := models.DB.MustBegin()
// 更新资源状态
_, errUpdate := tx.Exec(
`UPDATE resources SET status = ?, updated_at = ? WHERE id = ?`,
resource.Status, resource.UpdatedAt, resourceID,
)
if errUpdate != nil {
log.Printf("更新资源 %d 状态失败: %v", resourceID, errUpdate)
tx.Rollback()
failedIDs = append(failedIDs, resourceID)
continue
}
// 插入审批记录
_, errInsert := tx.Exec(
`INSERT INTO approval_records (
resource_id, status, field_approvals, field_rejections,
approved_images, rejected_images, poster_image, notes,
approved_links, rejected_links, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
approvalRecord.ResourceID, approvalRecord.Status,
approvalRecord.FieldApprovals, approvalRecord.FieldRejections,
models.JsonList(approvalRecord.ApprovedImages), models.JsonList(approvalRecord.RejectedImages),
"", approvalRecord.Notes,
approvalRecord.ApprovedLinks, approvalRecord.RejectedLinks,
approvalRecord.CreatedAt,
)
if errInsert != nil {
log.Printf("创建资源 %d 的审批记录失败: %v", resourceID, errInsert)
tx.Rollback()
failedIDs = append(failedIDs, resourceID)
continue
}
// 提交事务
if errCommit := tx.Commit(); errCommit != nil {
log.Printf("提交资源 %d 的事务失败: %v", resourceID, errCommit)
failedIDs = append(failedIDs, resourceID)
continue
}
successCount++
log.Printf("成功拒绝资源 %d", resourceID)
}
log.Printf("批量拒绝完成,成功: %d, 失败: %d", successCount, len(failedIDs))
c.JSON(http.StatusOK, gin.H{
"success_count": successCount,
"failed_ids": failedIDs,
"total": len(request.ResourceIDs),
})
}

View File

@@ -136,6 +136,7 @@ func SetupRoutes(router *gin.Engine) {
adminResources.GET("/:id/supplement", GetResourceSupplement)
adminResources.PUT("/:id", UpdateResource)
adminResources.PUT("/:id/approve", ApproveResource)
adminResources.POST("/batch-reject", BatchRejectResources)
adminResources.DELETE("/:id", DeleteResource)
adminResources.DELETE("/:id/record", DeleteApprovalRecord)
adminResources.DELETE("/batch-delete-records", DeleteApprovalRecords)