@@ -50,15 +50,27 @@
< td class = "date" data -label = " 创建时间 " > { { formatDate ( demand . date ) } } < / td >
< td class = "reply-count" data -label = " 回复数量 " > { { getReplyCount ( demand . sendcontent ) } } < / td >
< td class = "status" data -label = " 状态 " >
< span : class = "['tag', isDeleted(demand.content) ? 'deleted' : 'active' ]" >
{ { isDeleted ( demand . content ) ? '已删除' : '正常' } }
< span : class = "['tag', getStatusClass(demand) ]" >
{ { getStatusText ( demand ) } }
< / span >
< / td >
< td class = "actions" data -label = " 操作 " >
< button class = "btn-common btn-small btn-gradient" @click ="showDetail(demand)" > 查看 < / button >
<button v-if = "!isDeleted(demand.content)" class="btn-common btn-small btn-warning" @click="handleHide(demand)" > 隐藏 < / button >
< button v-else class = "btn-common btn-small btn-restore" @click ="handleRestore(demand)" > 恢复 < / button >
< button v-if = "showDeletedItems" class="btn-common btn-small btn-danger" @click="handleDelete(demand)" > 删除 < / button >
<! - - 检查是否为已删除状态 - - >
< template v-if = "isDeleted(demand)" >
< ! - - 已删除的需求 : 只有在显示已删除时才显示恢复按钮 - - >
< button v-if = "showDeletedItems" class="btn-common btn-small btn-restore" @click="handleRestore(demand)" > 恢复 < / button >
< / template >
< template v-else >
< ! - - 正常状态的需求 : 根据格式显示对应按钮 - - >
< template v-if = "demand.status" >
< ! - - 新格式数据 - - >
< button v-if = "demand.status === 'NORMAL'" class="btn-common btn-small btn-warning" @click="handleHide(demand)" > 隐藏 < / button >
< button v-if = "demand.status === 'NORMAL' && showDeletedItems" class="btn-common btn-small btn-danger" @click="handleDelete(demand)" > 删除 < / button >
< button v-if = "demand.status === 'HIDE'" class="btn-common btn-small btn-restore" @click="handleRestore(demand)" > 恢复 < / button >
< / template >
< ! - - 旧格式数据 : 不显示任何操作按钮 - - >
< / template >
< / td >
< / tr >
< / tbody >
@@ -99,23 +111,27 @@
< / div >
< div class = "detail-item" >
< span class = "label" > 状态 : < / span >
< span : class = "['tag', isDeleted (selectedDemand?.content) ? 'deleted' : 'active' ]" >
{ { isDeleted ( selectedDemand ? . content ) ? '已删除' : '正常' } }
< span : class = "['tag', getStatusClass (selectedDemand) ]" >
{ { getStatusText ( selectedDemand ) } }
< / span >
< / div >
< div class = "reply-section" >
< h3 > 回复内容 < / h3 >
< div class = "reply-list" >
< div v-if = "selectedDemand?.sendcontent" >
< div v-for = "(r eply, index) in selectedDemand.sendcontent.split('|') " :key="index" class="reply-content" >
< div v-if = "selectedDemand?.sendcontent && replyParsedTexts.length > 0 " >
< div v-for = "(parsedR eply, index) in replyParsedTexts " :key="index" class="reply-content" >
< div class = "reply-with-avatar" >
< img :src = "`https://q1.qlogo.cn/g?b=qq&nk=${extractQQ(reply )}&s=40`" alt = "User Avatar" class = "reply-avatar" / >
< img :src = "`https://q1.qlogo.cn/g?b=qq&nk=${extractQQ(selectedDemand.sendcontent.split('|')[index] )}&s=40`" alt = "User Avatar" class = "reply-avatar" / >
< div class = "reply-text" >
< b > { { parseReplyText ( reply ) . user } } < / b > { { parseReplyText ( reply ) . content } }
< b > { { parsed Reply . user } } < / b >
< span class = "reply-content-with-newlines" > { { parsedReply . content } } < / span >
< / div >
< / div >
< / div >
< / div >
< div v-else-if = "selectedDemand?.sendcontent && replyParsedTexts.length === 0" >
加载中...
< / div >
< div v-else class = "no-reply" >
暂无回复
< / div >
@@ -201,7 +217,9 @@
< script setup >
import { ref , onMounted , computed } from 'vue'
import { getDemandsList , updateDemand , deleteDemand , addDemand } from '../../api/demands'
import { getAll DemandsList , updateDemand , deleteDemand , addDemand , getDemandsList } from '../../api/demands'
import { getUserByInfo } from '../../api/login'
import { getStoredUser } from '@/utils/jwt'
// 响应式数据
const demands = ref ( [ ] )
@@ -209,6 +227,8 @@ const loading = ref(false)
const error = ref ( null )
const showModal = ref ( false )
const selectedDemand = ref ( null )
const replyParsedTexts = ref ( [ ] )
const usernameCache = ref ( { } )
const showDeleteConfirm = ref ( false )
const showRestoreConfirm = ref ( false )
const pendingDeleteId = ref ( null )
@@ -225,12 +245,25 @@ const addForm = ref({
const addError = ref ( '' )
const addLoading = ref ( false )
// 判断是否已删除(兼容新旧格式)
const isDeleted = ( demand ) => {
// 优先检查旧格式: content是否以&DEL开头
if ( demand . content ? . startsWith ( '&DEL' ) ) {
return true
}
// 检查新格式: status字段
if ( demand . status === 'DEL' ) {
return true
}
return false
}
// 计算属性:根据显示设置过滤需求
const displayDemands = computed ( ( ) => {
if ( showDeletedItems . value ) {
return demands . value
} else {
return demands . value . filter ( demand => ! isDeleted ( demand . content ))
return demands . value . filter ( demand => ! isDeleted ( demand ) )
}
} )
@@ -239,17 +272,77 @@ const isNoReward = (reward) => {
return ! reward || reward === '无赏金'
}
// 判断是否已删除
const isDeleted = ( content ) => {
return content ? . startsWith ( '&DEL' )
// 获取状态样式类
const getStatusClass = ( demand ) => {
// 兼容旧格式: 优先检查content是否以&DEL开头
if ( demand . content ? . startsWith ( '&DEL' ) ) {
return 'status-deleted'
}
// 新格式: 使用status字段
if ( demand . status ) {
switch ( demand . status ) {
case 'NORMAL' :
return 'status-normal'
case 'DEL' :
return 'status-deleted'
case 'HIDE' :
return 'status-hidden'
default :
return 'status-normal'
}
}
return 'status-normal'
}
// 获取显示内容(去掉删除标记)
// 获取状态显示文本
const getStatusText = ( demand ) => {
// 兼容旧格式: 优先检查content是否以&DEL开头
if ( demand . content ? . startsWith ( '&DEL' ) ) {
return '已删除'
}
// 新格式: 使用status字段
if ( demand . status ) {
switch ( demand . status ) {
case 'NORMAL' :
return '正常'
case 'DEL' :
return '已删除'
case 'HIDE' :
return '已隐藏'
default :
return '正常'
}
}
return '正常'
}
// 获取显示内容
const getDisplayContent = ( content ) => {
if ( ! content ) return ''
// 兼容旧格式:去掉&DEL标记
return content . replace ( /^&DEL/ , '' )
}
// 判断是否为正常状态
const isNormalStatus = ( demand ) => {
// 新格式: 检查status字段
if ( demand . status ) {
return demand . status === 'NORMAL'
}
// 兼容旧格式: content不以&DEL开头
return ! demand . content ? . startsWith ( '&DEL' )
}
// 判断是否为隐藏状态
const isHiddenStatus = ( demand ) => {
// 新格式: 检查status字段
if ( demand . status ) {
return demand . status === 'HIDE'
}
// 旧格式没有隐藏状态, 只返回false
return false
}
// 获取回复数量
const getReplyCount = ( sendcontent ) => {
if ( ! sendcontent ) return 0
@@ -278,8 +371,14 @@ const fetchDemands = async () => {
loading . value = true
error . value = null
try {
const data = await getDemandsList ( )
const data = await getAll DemandsList ( )
demands . value = data
// 输出当前用户信息用于调试
const currentUser = getStoredUser ( )
console . log ( '当前用户信息:' , currentUser )
console . log ( '用户权限:' , currentUser ? . privilege )
console . log ( '临时权限:' , currentUser ? . temp _privilege )
} catch ( err ) {
console . error ( '加载需求列表失败:' , err )
if ( err . response ? . status === 403 ) {
@@ -295,9 +394,19 @@ const fetchDemands = async () => {
}
// 显示详情弹窗
const showDetail = ( demand ) => {
const showDetail = async ( demand ) => {
selectedDemand . value = demand
showModal . value = true
// 如果有sendcontent, 解析所有回复文本
if ( demand . sendcontent ) {
replyParsedTexts . value = [ ]
const replies = demand . sendcontent . split ( '|' )
for ( let i = 0 ; i < replies . length ; i ++ ) {
const parsed = await parseReplyText ( replies [ i ] )
replyParsedTexts . value [ i ] = parsed
}
}
}
// 关闭弹窗
@@ -316,25 +425,35 @@ const handleHide = (demand) => {
// 确认操作
const confirmOperation = async ( ) => {
try {
if ( currentOperation . value === 'hide' ) {
const demand = demands . value . find ( d => d . id === pendingDeleteId . value )
if ( ! demand ) return
const demand = demands . value . find ( d => d . id === pendingDeleteId . value )
if ( ! demand ) return
const updatedContent = ` &DEL ${ demand . content } `
await updateD emand( pendingDeleteId . value , {
requester : demand . requester ,
qq _code : demand . qq _code ,
content : updatedContent ,
reward : demand . reward ,
date : demand . date ,
sendcontent : demand . sendcontent
} )
} else if ( currentOperation . value === 'delete' ) {
await deleteDemand ( pendingDeleteId . value )
const updateData = {
requester : d emand. requester ,
qq _code : demand . qq _code ,
content : demand . content ,
reward : demand . reward ,
date : demand . date ,
sendcontent : demand . sendcontent
}
if ( currentOperation . value === 'hide' ) {
updateData . status = 'HIDE'
} else if ( currentOperation . value === 'delete' ) {
updateData . status = 'DEL'
}
console . log ( '管理员操作 - 更新需求的数据:' , updateData )
console . log ( '管理员操作 - 需求ID:' , pendingDeleteId . value )
const result = await updateDemand ( pendingDeleteId . value , updateData )
console . log ( '管理员操作 - 更新结果:' , result )
fetchDemands ( )
} catch ( e ) {
console . error ( '操作失败:' , e )
console . error ( '管理员 操作失败:' , e )
console . error ( '错误详情:' , e . response ? . data )
error . value = ` 操作失败: ${ e . response ? . data ? . detail || e . message } `
} finally {
showDeleteConfirm . value = false
pendingDeleteId . value = null
@@ -367,18 +486,35 @@ const confirmRestore = async () => {
const demand = demands . value . find ( d => d . id === pendingRestoreId . value )
if ( ! demand ) return
const restoredContent = demand . content . replace ( /^&DEL/ , '' )
await updateDemand ( pendingRestoreId . value , {
let updateData = {
requester : demand . requester ,
qq _code : demand . qq _code ,
content : restoredContent ,
reward : demand . reward ,
date : demand . date ,
sendcontent : demand . sendcontent
} )
sendcontent : demand . sendcontent ,
status : 'NORMAL'
}
// 判断是新格式还是旧格式
if ( demand . status ) {
// 新格式: 保持原content
updateData . content = demand . content
} else {
// 旧格式: 去掉content的&DEL前缀, 并转换为新格式
updateData . content = demand . content . replace ( /^&DEL/ , '' )
}
console . log ( '管理员恢复 - 更新需求的数据:' , updateData )
console . log ( '管理员恢复 - 需求ID:' , pendingRestoreId . value )
const result = await updateDemand ( pendingRestoreId . value , updateData )
console . log ( '管理员恢复 - 更新结果:' , result )
fetchDemands ( )
} catch ( e ) {
console . error ( '恢复失败:' , e )
console . error ( '错误详情:' , e . response ? . data )
error . value = ` 恢复失败: ${ e . response ? . data ? . detail || e . message } `
} finally {
showRestoreConfirm . value = false
pendingRestoreId . value = null
@@ -391,6 +527,7 @@ const cancelRestore = () => {
pendingRestoreId . value = null
}
// 刷新
const onRefresh = ( ) => {
fetchDemands ( )
@@ -448,42 +585,83 @@ const submitAddForm = async () => {
}
// 解析回复文本
function parseReplyText ( str ) {
// 首先尝试解析 qq:内容 格式
const qq Match = str . match ( /^(\d+):(.+ )$/ )
if ( qq Match) {
async function parseReplyText ( str ) {
// 首先尝试解析新格式: qq:内容 格式
const newFormat Match = str . match ( /^(\d+):(.* )$/ )
if ( newFormat Match) {
const qqNumber = newFormatMatch [ 1 ]
let username = qqNumber // 默认显示QQ号
// 尝试从缓存获取用户名
if ( usernameCache . value [ qqNumber ] ) {
username = usernameCache . value [ qqNumber ]
} else {
// 通过API查询用户名
try {
const userInfo = await getUserByInfo ( { qq _code : qqNumber } )
if ( userInfo && userInfo . username ) {
username = ` ${ userInfo . username } ( ${ qqNumber } ) `
usernameCache . value [ qqNumber ] = username
}
} catch ( error ) {
console . log ( '获取用户信息失败:' , error )
// 失败时使用QQ号作为显示名
username = qqNumber
}
}
return {
user : qqMatch [ 1 ] ,
content : qq Match[ 2 ] . trim ( )
user : username ,
content : newFormat Match[ 2 ] . trim ( ) . replace ( /\\n/g , ' ' ) . replace ( /\\r/g , '' ) . replace ( /\\t/g , ' ' )
}
}
// 兼容原有的 昵称( QQ号) 格式,允许有空格
const m atch = str . match ( /^(.+?[( (][1-9][0-9]{4,}[) )]) (.*)$/ )
if ( m atch) {
// 兼容旧格式: 昵称( QQ号) :内容 格式
const oldFormatM atch = str . match ( /^(.+?)\((\d+)\): (.*)$/ )
if ( oldFormatM atch) {
const nickname = oldFormatMatch [ 1 ] . trim ( )
const qqNumber = oldFormatMatch [ 2 ]
return {
user : match [ 1 ] . trim ( ) ,
content : m atch[ 2 ] . replace ( /^: |^:/ , '' ) . trim ( )
user : ` ${ nickname } ( ${ qqNumber } ) ` , // 保持旧格式的显示样式
content : oldFormatM atch[ 3 ] . trim ( ) . replace( /\\n/g , ' ' ) . replace ( /\\r/g , '' ) . replace ( /\\t/g , ' ' )
}
}
// fallback
// 更宽松的旧格式匹配:处理可能的变体
const flexibleOldMatch = str . match ( /^(.+?)[( (](\d+)[) )][:: ](.*)$/ )
if ( flexibleOldMatch ) {
const nickname = flexibleOldMatch [ 1 ] . trim ( )
const qqNumber = flexibleOldMatch [ 2 ]
return {
user : ` ${ nickname } ( ${ qqNumber } ) ` ,
content : flexibleOldMatch [ 3 ] . trim ( ) . replace ( /\\n/g , ' ' ) . replace ( /\\r/g , '' ) . replace ( /\\t/g , ' ' )
}
}
// fallback: 如果都不匹配, 返回原始内容
return {
user : '' ,
content : str
content : str . replace ( /\\n/g , ' ' ) . replace ( /\\r/g , '' ) . replace ( /\\t/g , ' ' )
}
}
// 提取QQ号
function extractQQ ( str ) {
// 首先尝试解析 qq:内容 格式
const qq Match = str . match ( /^(\d+):(.+ )$/ )
if ( qq Match) {
return qq Match[ 1 ]
// 首先尝试解析新格式: qq:内容 格式
const newFormat Match = str . match ( /^(\d+):(.* )$/ )
if ( newFormat Match) {
return newFormat Match[ 1 ]
}
// 兼容原有的 昵称( QQ号) 格式
const m atch = str . match ( /[( (]([1-9][0-9]{4,})[) )] / )
return match ? match [ 1 ] : ''
// 兼容旧格式: 昵称( QQ号) :内容 格式
const oldFormatM atch = str . match ( /^(.+?)\((\d+)\): (.*)$ / )
if ( oldFormatMatch ) {
return oldFormatMatch [ 2 ] // QQ号部分
}
// 更宽松的旧格式匹配
const flexibleOldMatch = str . match ( /[( (](\d+)[) )]/ ) ;
return flexibleOldMatch ? flexibleOldMatch [ 1 ] : ''
}
// 组件挂载时自动加载数据
@@ -652,18 +830,24 @@ onMounted(() => {
border : 1 px solid # d9d9d9 ;
}
. active {
. status - normal {
background : # f0f9ff ;
color : # 0369 a1 ;
border : 1 px solid # 7 dd3fc ;
}
. deleted {
. status - deleted {
background : # fef2f2 ;
color : # dc2626 ;
border : 1 px solid # fca5a5 ;
}
. status - hidden {
background : # fefce8 ;
color : # a16207 ;
border : 1 px solid # fcd34d ;
}
. actions {
display : flex ;
gap : 5 px ;