Files
danyang-match/admin-web/src/modules/audit/AuditLogView.vue
T
WorkBuddy ca0db57f24 chore(release): 冻结 v1.1.0 版本
- 同步版本号:VERSION / backend pom / admin-web / miniapp 均升至 1.1.0
- 合并本轮代码审查修复(详见 docs/CODE-REVIEW-2026-09-24.md):
  - 后端:微信登录真实实现 RealWechatLoginProvider、实名核验真实实现 RealIdentityVerificationProvider(本地强校验 + 可配置第三方 + fail-closed/fail-fast)
  - 照片直传落库端点 POST /me/photos/confirm(fileKey 归属校验 + 对象存在性校验后 savePhoto)
  - 刷新 Token Redis TTL 修正(getExpirationMs()*2 的 4h → refreshExpirationMs 的 14 天)
  - 管理端 token 改为 HttpOnly Cookie + silent-refresh,移除 localStorage 明文存储与 30+ 处手工注入
  - 小程序:红娘接单鉴权头错位修复、serviceType 中文映射、生产域名可配置
- 隔离本地产物(.workbuddy/、*.bundle、*.dump、secrets、application-dev.yml 等已被 .gitignore 覆盖)

未验证项(打标签 ≠ 生产发布):
- 后端编译 / 单测未在本环境执行(Maven 慢、Docker DNS 受限)
- 管理端 登录→刷新→登出 端到端、小程序真机走查待人工验收
- 生产部署需注入 WECHAT_APP_ID / WECHAT_APP_SECRET、VERIFICATION_API_URL / VERIFICATION_API_KEY 与小程序生产域名
2026-09-30 12:10:00 +08:00

97 lines
3.6 KiB
Vue

<template>
<div class="page-container">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>审计日志</span>
<el-radio-group v-model="resourceFilter" size="small" @change="loadData">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button label="user">用户</el-radio-button>
<el-radio-button label="operator">Operator</el-radio-button>
<el-radio-button label="report">举报</el-radio-button>
<el-radio-button label="appeal">申诉</el-radio-button>
<el-radio-button label="activity">活动</el-radio-button>
</el-radio-group>
</div>
</template>
<el-table :data="tableData" v-loading="loading" border stripe>
<el-table-column prop="operatorId" label="操作人ID" width="140" />
<el-table-column prop="action" label="操作" width="160">
<template #default="{row}">{{ actionText(row.action) }}</template>
</el-table-column>
<el-table-column prop="resourceType" label="资源类型" width="120">
<template #default="{row}">{{ row.resourceType }}</template>
</el-table-column>
<el-table-column prop="resourceId" label="资源ID" width="140" show-overflow-tooltip />
<el-table-column prop="ip" label="IP地址" width="140" />
<el-table-column prop="createdAt" label="时间" width="180">
<template #default="{row}">{{ formatTime(row.createdAt) }}</template>
</el-table-column>
</el-table>
<el-pagination v-if="total > 0" style="margin-top:16px;text-align:right"
layout="total, prev, pager, next" :total="total" :page-size="pageSize"
:current-page="page" @current-change="handlePageChange" />
</el-card>
</div>
</template>
<script setup lang="ts">
import { getAccessToken } from '@/utils/token'
import { ref, onMounted } from 'vue'
import { get } from '@/api/client'
const loading = ref(false)
const tableData = ref<any[]>([])
const total = ref(0)
const page = ref(0)
const pageSize = ref(20)
const resourceFilter = ref('')
const ACTION_TEXT: Record<string, string> = {
CREATE_OPERATOR: '创建操作员',
DISABLE_OPERATOR: '停用操作员',
ENABLE_OPERATOR: '启用操作员',
RESET_PASSWORD: '重置密码',
VIEW_USER_DETAIL: '查看用户详情',
UPDATE_USER_STATUS: '修改用户状态',
SUBMIT_REPORT: '提交举报',
PROCESS_REPORT: '处理举报',
SUBMIT_APPEAL: '提交申诉',
PROCESS_APPEAL: '处理申诉',
CREATE_ORGANIZATION: '创建机构',
DISABLE_ORGANIZATION: '停用机构',
ENABLE_ORGANIZATION: '启用机构',
CREATE_ACTIVITY: '创建活动',
APPROVE_REGISTRATION: '审核报名',
REJECT_REGISTRATION: '拒绝报名'
}
async function loadData() {
loading.value = true
try {
const token = getAccessToken()
const res: any = await get('/operator/audit/logs', {
resourceType: resourceFilter.value || undefined,
page: page.value,
pageSize: pageSize.value
}, { Authorization: `Bearer ${token}` })
tableData.value = res.list || []
total.value = res.total || 0
} catch (e) { console.error(e) }
finally { loading.value = false }
}
function handlePageChange(p: number) { page.value = p - 1; loadData() }
function actionText(a: string) { return ACTION_TEXT[a] || a }
function formatTime(t?: string) { return t ? new Date(t).toLocaleString('zh-CN') : '-' }
onMounted(loadData)
</script>
<style scoped>
.page-container { padding: 24px; }
.card-header { display: flex; justify-content: space-between; align-items: center; }
</style>