Files
crawler-plugin/admin-frontend-vue/src/pages/shop/ShopKeysPage.vue
T

170 lines
6.5 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.
<script setup lang="ts">
import { formatDateTime } from '@/utils/datetime'
/** 店铺密钥管理页:分页 + 新增/编辑/删除;紫鸟令牌仅掩码展示。 */
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fetchShopKeyList, submitCreateShopKey, updateShopKey, deleteShopKey } from './shop-key-api.ts'
import { emptyShopKeyForm } from './shop-key-form-model.ts'
import type { ShopKeyItem } from './shop-dto.ts'
import { toSensitiveString, type IpWhitelistStatus } from './shop-dto.ts'
const loading = ref(false)
const rows = ref<ShopKeyItem[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = 15
const dialogVisible = ref(false)
const editingId = ref<number | null>(null)
const saving = ref(false)
const form = reactive(emptyShopKeyForm())
function maskToken(token: string): string {
const value = (token || '')
if (value.length <= 8) return '••••••••'
return `${value.slice(0, 4)}••••••••${value.slice(-4)}`
}
function statusText(status: IpWhitelistStatus): string {
return status === 'ALLOWED' ? '已白名单' : status === 'BLOCKED' ? '被拦截' : '未知'
}
async function load() {
loading.value = true
try {
const result = await fetchShopKeyList({ page: page.value, pageSize })
rows.value = result.items
total.value = result.total
page.value = result.page
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '密钥列表加载失败')
} finally {
loading.value = false
}
}
function openCreate() {
editingId.value = null
Object.assign(form, emptyShopKeyForm())
dialogVisible.value = true
}
function openEdit(row: ShopKeyItem) {
editingId.value = row.id
Object.assign(form, emptyShopKeyForm())
form.remarkName = row.remarkName
form.ziniaoAccountName = row.ziniaoAccountName
dialogVisible.value = true
}
async function submit() {
saving.value = true
try {
if (editingId.value == null) {
await submitCreateShopKey({ ...form })
} else {
await updateShopKey(editingId.value, { ...form })
}
ElMessage.success(editingId.value == null ? '已新增' : '已保存')
dialogVisible.value = false
load()
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '保存失败')
} finally {
saving.value = false
}
}
async function remove(row: ShopKeyItem) {
try {
await ElMessageBox.confirm(`确认删除密钥「${row.ziniaoAccountName}」?`, '删除密钥', { type: 'warning' })
} catch {
return
}
try {
await deleteShopKey(row.id)
ElMessage.success('已删除')
load()
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '删除失败')
}
}
onMounted(load)
</script>
<template>
<div class="page-stack">
<div class="page-heading">
<div>
<h2>店铺密钥管理</h2>
<p>管理各店铺的紫鸟浏览器账号与令牌</p>
</div>
<el-button type="primary" @click="openCreate">新增密钥</el-button>
</div>
<el-card shadow="never">
<el-table v-loading="loading" :data="rows" stripe border>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="remarkName" label="备注名" min-width="140">
<template #default="{ row }">{{ (row as ShopKeyItem).remarkName || '—' }}</template>
</el-table-column>
<el-table-column prop="ziniaoAccountName" label="紫鸟账号" min-width="160" />
<el-table-column label="紫鸟令牌" min-width="200">
<template #default="{ row }">
<el-tooltip :content="(row as ShopKeyItem).ziniaoToken" placement="top">
<code class="mono-mask">{{ maskToken((row as ShopKeyItem).ziniaoToken) }}</code>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="IP白名单" width="130" align="center">
<template #default="{ row }">
<el-tag :type="(row as ShopKeyItem).ipWhitelistStatus === 'ALLOWED' ? 'success' : (row as ShopKeyItem).ipWhitelistStatus === 'BLOCKED' ? 'danger' : 'info'" size="small">
{{ statusText((row as ShopKeyItem).ipWhitelistStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" min-width="170">
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<el-button text type="primary" size="small" @click="openEdit(row as ShopKeyItem)">编辑</el-button>
<el-button text type="danger" size="small" @click="remove(row as ShopKeyItem)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-footer">
<span> <b>{{ total.toLocaleString() }}</b> </span>
<el-pagination background layout="prev, pager, next, jumper" :total="total" :page-size="pageSize" :current-page="page" @current-change="(p: number) => { page = p; load() }" />
</div>
</el-card>
<el-dialog v-model="dialogVisible" :title="editingId == null ? '新增密钥' : '编辑密钥'" width="560px">
<el-form label-width="110px">
<el-form-item label="备注名">
<el-input v-model="form.remarkName" placeholder="可选" />
</el-form-item>
<el-form-item label="紫鸟账号" required>
<el-input v-model="form.ziniaoAccountName" placeholder="紫鸟浏览器账号名称" />
</el-form-item>
<el-form-item label="紫鸟令牌" required>
<el-input v-model="form.ziniaoToken" type="textarea" :rows="3" placeholder="粘贴紫鸟令牌将自动去除 Bearer 前缀" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="submit">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped>
.page-heading { display: flex; justify-content: space-between; align-items: center; }
.table-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 14px; }
.table-footer span { color: var(--el-text-color-secondary); font-size: 12.5px; }
.table-footer b { color: var(--el-text-color-primary); }
.mono-mask { font-family: Consolas, monospace; font-size: 12px; background: var(--el-fill-color-light); padding: 2px 6px; border-radius: 4px; }
</style>