f5254f6a27
Vue 3 + Ant Design Vue SPA scaffold for the internal ops console. Ships the auth, account list, and audit log views that consume the new ops-api endpoints, plus the nginx/vite/typecheck plumbing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
82 lines
3.0 KiB
Vue
82 lines
3.0 KiB
Vue
<template>
|
|
<h2 class="ops-page-title">个人资料</h2>
|
|
|
|
<div class="ops-card" style="max-width: 640px; margin-bottom: 24px;">
|
|
<h3 style="margin-top: 0; margin-bottom: 16px; font-size: 16px; font-weight: 600;">账号信息</h3>
|
|
<a-descriptions :column="1" bordered>
|
|
<a-descriptions-item label="ID">{{ auth.operator?.id }}</a-descriptions-item>
|
|
<a-descriptions-item label="账号">{{ auth.operator?.username }}</a-descriptions-item>
|
|
<a-descriptions-item label="姓名">{{ auth.operator?.display_name }}</a-descriptions-item>
|
|
<a-descriptions-item label="邮箱">{{ auth.operator?.email || "—" }}</a-descriptions-item>
|
|
<a-descriptions-item label="角色">{{ auth.operator?.role }}</a-descriptions-item>
|
|
<a-descriptions-item label="状态">
|
|
<a-tag :color="auth.operator?.status === 'active' ? 'green' : 'red'">
|
|
{{ auth.operator?.status === "active" ? "启用" : "停用" }}
|
|
</a-tag>
|
|
</a-descriptions-item>
|
|
</a-descriptions>
|
|
</div>
|
|
|
|
<div class="ops-card" style="max-width: 640px">
|
|
<h3 style="margin-top: 0; margin-bottom: 16px; font-size: 16px; font-weight: 600;">修改密码</h3>
|
|
<a-form layout="vertical" :model="form" @submit.prevent="onSubmit">
|
|
<a-form-item label="原密码" required>
|
|
<a-input-password v-model:value="form.oldPassword" autocomplete="current-password" />
|
|
</a-form-item>
|
|
<a-form-item label="新密码" required help="至少 8 位">
|
|
<a-input-password v-model:value="form.newPassword" autocomplete="new-password" />
|
|
</a-form-item>
|
|
<a-form-item label="确认新密码" required>
|
|
<a-input-password v-model:value="form.confirmPassword" autocomplete="new-password" />
|
|
</a-form-item>
|
|
<a-button type="primary" html-type="submit" :loading="loading">提交</a-button>
|
|
</a-form>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { message } from "ant-design-vue";
|
|
import { reactive, ref } from "vue";
|
|
|
|
import { OpsApiError, http } from "@/lib/http";
|
|
import { useAuthStore } from "@/stores/auth";
|
|
|
|
const auth = useAuthStore();
|
|
|
|
const form = reactive({
|
|
oldPassword: "",
|
|
newPassword: "",
|
|
confirmPassword: "",
|
|
});
|
|
|
|
const loading = ref(false);
|
|
|
|
async function onSubmit() {
|
|
if (!form.oldPassword || form.newPassword.length < 8) {
|
|
message.warning("请输入原密码,并将新密码设为至少 8 位");
|
|
return;
|
|
}
|
|
if (form.newPassword !== form.confirmPassword) {
|
|
message.warning("两次输入的新密码不一致");
|
|
return;
|
|
}
|
|
loading.value = true;
|
|
try {
|
|
await http.post("/auth/password", {
|
|
old_password: form.oldPassword,
|
|
new_password: form.newPassword,
|
|
});
|
|
message.success("密码已更新,请使用新密码重新登录");
|
|
auth.logout();
|
|
// router redirect handled by 401 interceptor or next route guard
|
|
if (typeof window !== "undefined") window.location.href = "/login";
|
|
} catch (error) {
|
|
if (error instanceof OpsApiError) {
|
|
message.error(error.detail || error.message);
|
|
}
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
</script>
|