feat: admin-web新增feedback工具和配置优化
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,23 @@ import './styles/theme.css'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.config.errorHandler = (err, vm, info) => {
|
||||
console.error('[vue error]', info, err)
|
||||
}
|
||||
|
||||
Vue.config.warnHandler = (msg, vm, trace) => {
|
||||
console.warn('[vue warn]', msg, trace)
|
||||
}
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.warn('[unhandled rejection]', event.reason)
|
||||
event.preventDefault()
|
||||
})
|
||||
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error('[window error]', event.error || event.message)
|
||||
})
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: (h) => h(App)
|
||||
|
||||
@@ -57,3 +57,42 @@ export function previewImage(urls, current) {
|
||||
if (!urls || !urls.length) return
|
||||
ensurePreview().open({ urls, current })
|
||||
}
|
||||
|
||||
export async function copyText(text) {
|
||||
if (text === undefined || text === null) return false
|
||||
const value = String(text)
|
||||
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value)
|
||||
return true
|
||||
} catch (err) {
|
||||
console.warn('[copyText] navigator.clipboard 不可用,回退到 execCommand', err)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = value
|
||||
textarea.setAttribute('readonly', '')
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.top = '0'
|
||||
textarea.style.left = '0'
|
||||
textarea.style.width = '1px'
|
||||
textarea.style.height = '1px'
|
||||
textarea.style.opacity = '0'
|
||||
textarea.style.pointerEvents = 'none'
|
||||
document.body.appendChild(textarea)
|
||||
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
textarea.setSelectionRange(0, value.length)
|
||||
|
||||
const ok = document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
return !!ok
|
||||
} catch (err) {
|
||||
console.error('[copyText] execCommand 也失败了', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
|
||||
<script>
|
||||
import { request } from '@/utils/request'
|
||||
import { toast, confirm } from '@/utils/feedback'
|
||||
import { toast, confirm, copyText } from '@/utils/feedback'
|
||||
|
||||
export default {
|
||||
name: 'BatchView',
|
||||
@@ -176,7 +176,10 @@ export default {
|
||||
this.lineCount = lines.length
|
||||
toast(`已读取 ${lines.length} 条文本`, 'success')
|
||||
}
|
||||
reader.onerror = () => toast('文件读取失败', 'error')
|
||||
reader.onerror = (e) => {
|
||||
console.error('[batch] 文件读取失败', e)
|
||||
toast('文件读取失败', 'error')
|
||||
}
|
||||
reader.readAsText(file, 'utf-8')
|
||||
e.target.value = ''
|
||||
},
|
||||
@@ -267,6 +270,7 @@ export default {
|
||||
}, 0)
|
||||
toast('导出成功', 'success')
|
||||
} catch (err) {
|
||||
console.error('[batch] 导出失败', err)
|
||||
toast('导出失败', 'error')
|
||||
} finally {
|
||||
this.exporting = false
|
||||
@@ -278,10 +282,11 @@ export default {
|
||||
return
|
||||
}
|
||||
const csv = this.generateCSV()
|
||||
try {
|
||||
await navigator.clipboard.writeText(csv)
|
||||
const ok = await copyText(csv)
|
||||
if (ok) {
|
||||
toast('CSV 内容已复制到剪贴板', 'success')
|
||||
} catch (err) {
|
||||
} else {
|
||||
console.error('[batch] 复制失败,CSV 内容打印到控制台供手动复制\n' + csv)
|
||||
toast('复制失败,请手动选择文本', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,8 @@ export default {
|
||||
detect_spam_probability_text: this.formatPercent((result.detect || {}).spam_probability, 2)
|
||||
}
|
||||
toast(result.publish_allowed ? '发布成功' : '已拦截,可申诉', result.publish_allowed ? 'success' : 'error')
|
||||
} catch (err) {
|
||||
console.error('[detect] 发布失败', err)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@@ -118,11 +118,14 @@ export default {
|
||||
async bootstrap() {
|
||||
this.loading = true
|
||||
try {
|
||||
const [, modelInfo] = await Promise.all([
|
||||
refreshUser(),
|
||||
request({ url: '/spam/model/info' })
|
||||
])
|
||||
this.modelInfo = modelInfo
|
||||
await refreshUser()
|
||||
const modelInfo = await request({ url: '/spam/model/info' }).catch((err) => {
|
||||
console.warn('[home] 模型信息加载失败', err)
|
||||
return null
|
||||
})
|
||||
if (modelInfo) this.modelInfo = modelInfo
|
||||
} catch (err) {
|
||||
console.warn('[home] bootstrap 异常', err)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
<script>
|
||||
import { request } from '@/utils/request'
|
||||
import { toast } from '@/utils/feedback'
|
||||
import { toast, copyText } from '@/utils/feedback'
|
||||
|
||||
export default {
|
||||
name: 'AdminDashboard',
|
||||
@@ -256,10 +256,12 @@ export default {
|
||||
`【近 7 日趋势】`,
|
||||
(report.spam_trend || []).slice(-7).map((t) => `${t.label}: 拦截${t.blocked}条,发布${t.published}条`).join('\n')
|
||||
]
|
||||
try {
|
||||
await navigator.clipboard.writeText(lines.join('\n'))
|
||||
const content = lines.join('\n')
|
||||
const ok = await copyText(content)
|
||||
if (ok) {
|
||||
toast('报告已复制到剪贴板', 'success')
|
||||
} catch (err) {
|
||||
} else {
|
||||
console.error('[dashboard] 复制失败,报告内容打印到控制台供手动复制\n' + content)
|
||||
toast('复制失败,请手动选择', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ export default {
|
||||
try {
|
||||
items = JSON.parse(this.importText)
|
||||
} catch (err) {
|
||||
console.error('[samples] JSON 格式错误', err)
|
||||
toast('JSON 格式错误', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -179,6 +179,7 @@ export default {
|
||||
try {
|
||||
items = JSON.parse(this.importText)
|
||||
} catch (err) {
|
||||
console.error('[users] JSON 格式错误', err)
|
||||
toast('JSON 格式错误', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ module.exports = defineConfig({
|
||||
port: 8080,
|
||||
host: '0.0.0.0',
|
||||
historyApiFallback: true,
|
||||
client: {
|
||||
overlay: false,
|
||||
logging: 'warn'
|
||||
},
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:5000',
|
||||
|
||||
Reference in New Issue
Block a user