409aac8486
部署开发环境 / deploy-dev (push) Failing after 26s
- 新增 AddDataDialog 组件用于数据批量上传 - 在 TokenManageView 中集成数据上传对话框 - 实现按行分割数据并逐条上传的功能 - 添加上传进度显示和计数功能 - 集成 API 接口进行数据提交 - 添加上传状态控制和错误处理机制
538 lines
14 KiB
Vue
538 lines
14 KiB
Vue
<script setup lang="ts">
|
|
import {ref, onMounted} from "vue"
|
|
import axios from "@/axios.ts"
|
|
import {ElMessage} from 'element-plus'
|
|
import {useCounterStore} from "@/stores/counter.ts"
|
|
import {useRouter} from "vue-router"
|
|
import {Plus, View, Edit, Delete, Key, Document, Memo, Search, Lock, DocumentAdd} from '@element-plus/icons-vue'
|
|
import AddDataDialog from '@/components/AddDataDialog.vue'
|
|
|
|
const store = useCounterStore()
|
|
const router = useRouter()
|
|
|
|
const tableData = ref<any[]>([])
|
|
const loading = ref(false)
|
|
const input = ref('')
|
|
const value = ref('')
|
|
const dataFormat = ref('')
|
|
const inputNotes = ref('')
|
|
const inputPassWord = ref('')
|
|
const passwordVisible = ref(true)
|
|
|
|
const rowOut = ref<any>(null)
|
|
const addDataDialogVisible = ref(false)
|
|
const dedupObjectVisible = ref(false)
|
|
const dataFormatVisible = ref(false)
|
|
const inputNotesVisible = ref(false)
|
|
|
|
const options = [
|
|
{value: 'uid', label: 'uid'},
|
|
{value: 'secid', label: 'secid'},
|
|
{value: 'pid', label: 'pid'},
|
|
{value: 'comment_id', label: 'comment_id'},
|
|
{value: 'dyid', label: 'dyid'}
|
|
]
|
|
|
|
const dataFormatOptions = [
|
|
{value: 'uid----secid----pid----comment_id', label: 'uid----secid----pid----comment_id'},
|
|
{value: 'uid----secid', label: 'uid----secid'},
|
|
{value: 'dyid', label: 'dyid'}
|
|
]
|
|
|
|
const fetchTokens = async () => {
|
|
loading.value = true
|
|
try {
|
|
const res = await axios.get("/api/token")
|
|
tableData.value = res.data.result || []
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (!store.isAdmin) {
|
|
passwordVisible.value = true
|
|
} else {
|
|
fetchTokens()
|
|
}
|
|
})
|
|
|
|
const checkPassword = () => {
|
|
if (inputPassWord.value === "haha") {
|
|
ElMessage({message: '登录成功', type: 'success'})
|
|
store.isAdmin = true
|
|
passwordVisible.value = false
|
|
fetchTokens()
|
|
} else {
|
|
ElMessage.error('密码错误')
|
|
}
|
|
}
|
|
|
|
const addToken = async () => {
|
|
if (!input.value || !value.value || !dataFormat.value) {
|
|
ElMessage.warning('请填写完整信息')
|
|
return
|
|
}
|
|
try {
|
|
await axios.post('/api/token', {}, {
|
|
params: {
|
|
token: input.value,
|
|
dedup_object: value.value,
|
|
data_format: dataFormat.value,
|
|
notes: inputNotes.value,
|
|
}
|
|
})
|
|
ElMessage({message: '添加成功', type: 'success'})
|
|
input.value = ''
|
|
value.value = ''
|
|
dataFormat.value = ''
|
|
inputNotes.value = ''
|
|
fetchTokens()
|
|
} catch (error: any) {
|
|
ElMessage.error(error.response?.data?.error || '添加失败')
|
|
}
|
|
}
|
|
|
|
const viewDetails = (row: any) => {
|
|
store.token = row.token
|
|
router.push({name: "TokenDetail"})
|
|
}
|
|
|
|
const dialogDedupObjectVisible = (row: any) => {
|
|
rowOut.value = row
|
|
value.value = row.dedup_object
|
|
dedupObjectVisible.value = true
|
|
}
|
|
|
|
const dialogDataFormatVisible = (row: any) => {
|
|
rowOut.value = row
|
|
dataFormat.value = row.data_format
|
|
dataFormatVisible.value = true
|
|
}
|
|
|
|
const dialogNotesVisible = (row: any) => {
|
|
rowOut.value = row
|
|
inputNotes.value = row.notes
|
|
inputNotesVisible.value = true
|
|
}
|
|
|
|
const dialogDataADDVisible = (row: any) => {
|
|
rowOut.value = row
|
|
addDataDialogVisible.value = true
|
|
}
|
|
|
|
const updateDedupObject = async () => {
|
|
try {
|
|
await axios.put('/api/token', {}, {
|
|
params: {
|
|
token: rowOut.value.token,
|
|
dedup_object: value.value,
|
|
data_format: rowOut.value.data_format,
|
|
notes: rowOut.value.notes,
|
|
}
|
|
})
|
|
ElMessage({message: '更新成功', type: 'success'})
|
|
dedupObjectVisible.value = false
|
|
fetchTokens()
|
|
} catch (error: any) {
|
|
ElMessage.error(error.response?.data?.error || '更新失败')
|
|
}
|
|
}
|
|
|
|
const updateDataFormat = async () => {
|
|
try {
|
|
await axios.put('/api/token', {}, {
|
|
params: {
|
|
token: rowOut.value.token,
|
|
dedup_object: rowOut.value.dedup_object,
|
|
data_format: dataFormat.value,
|
|
notes: rowOut.value.notes,
|
|
}
|
|
})
|
|
ElMessage({message: '更新成功', type: 'success'})
|
|
dataFormatVisible.value = false
|
|
fetchTokens()
|
|
} catch (error: any) {
|
|
ElMessage.error(error.response?.data?.error || '更新失败')
|
|
}
|
|
}
|
|
|
|
const updateNotes = async () => {
|
|
try {
|
|
await axios.put('/api/token', {}, {
|
|
params: {
|
|
token: rowOut.value.token,
|
|
dedup_object: rowOut.value.dedup_object,
|
|
data_format: rowOut.value.data_format,
|
|
notes: inputNotes.value
|
|
}
|
|
})
|
|
ElMessage({message: '更新成功', type: 'success'})
|
|
inputNotesVisible.value = false
|
|
fetchTokens()
|
|
} catch (error: any) {
|
|
ElMessage.error(error.response?.data?.error || '更新失败')
|
|
}
|
|
}
|
|
|
|
const deleteToken = async (row: any) => {
|
|
try {
|
|
await axios.delete('/api/token', {
|
|
params: {token: row.token}
|
|
})
|
|
ElMessage({message: '删除成功', type: 'success'})
|
|
fetchTokens()
|
|
} catch (error: any) {
|
|
ElMessage.error(error.response?.data?.error || '删除失败')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="token-manage">
|
|
<!-- 登录页面 -->
|
|
<div v-if="!store.isAdmin" class="login-page">
|
|
<div class="login-card">
|
|
<div class="login-icon">
|
|
<el-icon size="48" color="#409eff">
|
|
<Lock/>
|
|
</el-icon>
|
|
</div>
|
|
<div class="login-title">管理员登录</div>
|
|
<div class="login-subtitle">请输入管理员密码访问管理后台</div>
|
|
|
|
<el-input
|
|
v-model="inputPassWord"
|
|
type="password"
|
|
placeholder="请输入管理员密码"
|
|
size="large"
|
|
show-password
|
|
@keyup.enter="checkPassword"
|
|
>
|
|
<template #prefix>
|
|
<el-icon>
|
|
<Lock/>
|
|
</el-icon>
|
|
</template>
|
|
</el-input>
|
|
|
|
<el-button type="primary" size="large" class="login-btn" @click="checkPassword">
|
|
登录
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 管理页面 -->
|
|
<div v-else class="manage-page">
|
|
<div class="content-card">
|
|
<div class="page-title">Token 管理</div>
|
|
|
|
<!-- 添加表单 -->
|
|
<div class="add-form">
|
|
<div class="form-title">添加新 Token</div>
|
|
<div class="form-grid">
|
|
<el-input v-model="input" placeholder="Token 名称" clearable>
|
|
<template #prefix>
|
|
<el-icon>
|
|
<Key/>
|
|
</el-icon>
|
|
</template>
|
|
</el-input>
|
|
|
|
<el-select v-model="value" placeholder="去重对象" clearable>
|
|
<el-option v-for="item in options" :key="item.value" :value="item.value" :label="item.label"/>
|
|
</el-select>
|
|
|
|
<el-select v-model="dataFormat" placeholder="数据格式" clearable>
|
|
<el-option v-for="item in dataFormatOptions" :key="item.value" :value="item.value" :label="item.label"/>
|
|
</el-select>
|
|
|
|
<el-input v-model="inputNotes" placeholder="备注(可选)" clearable>
|
|
<template #prefix>
|
|
<el-icon>
|
|
<Memo/>
|
|
</el-icon>
|
|
</template>
|
|
</el-input>
|
|
|
|
<el-button type="primary" @click="addToken">
|
|
<el-icon>
|
|
<Plus/>
|
|
</el-icon>
|
|
添加
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Token 列表 -->
|
|
<div class="table-section">
|
|
<div class="section-header">
|
|
<span class="section-title">Token 列表</span>
|
|
<span class="table-count">共 {{ tableData.length }} 条</span>
|
|
</div>
|
|
|
|
<el-table :data="tableData" v-loading="loading" stripe style="width: 100%">
|
|
<el-table-column prop="token" label="Token" min-width="100" show-overflow-tooltip>
|
|
<template #default="{ row }">
|
|
<div class="token-cell">
|
|
<el-icon>
|
|
<Key/>
|
|
</el-icon>
|
|
<span>{{ row.token }}</span>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column prop="dedup_object" label="去重对象" width="150">
|
|
<template #default="{ row }">
|
|
<el-tag type="primary" effect="plain">{{ row.dedup_object }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column prop="data_format" label="数据格式" min-width="200" show-overflow-tooltip>
|
|
<template #default="{ row }">
|
|
<div class="format-cell">{{ row.data_format }}</div>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column prop="notes" label="备注" min-width="150" show-overflow-tooltip>
|
|
<template #default="{ row }">
|
|
<span class="notes-text">{{ row.notes || '-' }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="操作" width="500" fixed="right">
|
|
<template #default="{ row }">
|
|
<div class="action-buttons">
|
|
<el-button type="primary" size="small" text @click="viewDetails(row)">
|
|
<el-icon>
|
|
<View/>
|
|
</el-icon>
|
|
详情
|
|
</el-button>
|
|
<el-button size="small" text @click="dialogDataADDVisible(row)">
|
|
<el-icon>
|
|
<Plus/>
|
|
</el-icon>
|
|
数据
|
|
</el-button>
|
|
<el-button size="small" text @click="dialogDedupObjectVisible(row)">
|
|
<el-icon>
|
|
<Edit/>
|
|
</el-icon>
|
|
去重对象
|
|
</el-button>
|
|
<el-button size="small" text @click="dialogDataFormatVisible(row)">
|
|
<el-icon>
|
|
<Edit/>
|
|
</el-icon>
|
|
数据格式
|
|
</el-button>
|
|
<el-button size="small" text @click="dialogNotesVisible(row)">
|
|
<el-icon>
|
|
<Edit/>
|
|
</el-icon>
|
|
备注
|
|
</el-button>
|
|
<el-popconfirm
|
|
title="确认删除此 Token 吗?"
|
|
confirm-button-text="确认"
|
|
cancel-button-text="取消"
|
|
@confirm="deleteToken(row)"
|
|
>
|
|
<template #reference>
|
|
<el-button type="danger" size="small" text>
|
|
<el-icon>
|
|
<Delete/>
|
|
</el-icon>
|
|
删除
|
|
</el-button>
|
|
</template>
|
|
</el-popconfirm>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 编辑对话框 -->
|
|
<el-dialog v-model="dedupObjectVisible" title="更改去重对象" width="400">
|
|
<el-select v-model="value" placeholder="选择去重对象" style="width: 100%">
|
|
<el-option v-for="item in options" :key="item.value" :value="item.value" :label="item.label"/>
|
|
</el-select>
|
|
<template #footer>
|
|
<el-button @click="dedupObjectVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="updateDedupObject">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<el-dialog v-model="dataFormatVisible" title="更改数据格式" width="400">
|
|
<el-select v-model="dataFormat" placeholder="选择数据格式" style="width: 100%">
|
|
<el-option v-for="item in dataFormatOptions" :key="item.value" :value="item.value" :label="item.label"/>
|
|
</el-select>
|
|
<template #footer>
|
|
<el-button @click="dataFormatVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="updateDataFormat">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<el-dialog v-model="inputNotesVisible" title="更改备注" width="400">
|
|
<el-input v-model="inputNotes" placeholder="请输入备注"/>
|
|
<template #footer>
|
|
<el-button @click="inputNotesVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="updateNotes">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<AddDataDialog
|
|
v-model="addDataDialogVisible"
|
|
:token="rowOut?.token"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.token-manage {
|
|
max-width: 1600px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
padding: 0;
|
|
}
|
|
|
|
/* 登录页面 */
|
|
.login-page {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 60vh;
|
|
}
|
|
|
|
.login-card {
|
|
width: 100%;
|
|
max-width: 400px;
|
|
text-align: center;
|
|
padding: 48px 40px;
|
|
background: var(--bg-card);
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.login-icon {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.login-title {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.login-subtitle {
|
|
font-size: 14px;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.login-card :deep(.el-input) {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.login-btn {
|
|
width: 100%;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
/* 管理页面 */
|
|
.add-form {
|
|
margin-bottom: 32px;
|
|
padding: 24px;
|
|
background: var(--bg-page);
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.form-title {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.form-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
.form-grid .el-input {
|
|
width: 220px;
|
|
}
|
|
|
|
.form-grid .el-select {
|
|
width: 280px;
|
|
}
|
|
|
|
/* 表格 */
|
|
.table-section {
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.table-count {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.token-cell {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.format-cell {
|
|
font-size: 12px;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.notes-text {
|
|
color: var(--text-secondary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 4px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.form-grid {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.form-grid .el-input,
|
|
.form-grid .el-select {
|
|
width: 100%;
|
|
}
|
|
|
|
.form-grid .el-button {
|
|
width: 100%;
|
|
}
|
|
|
|
.action-buttons {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</style> |