release: integrate dev into main #28
Generated
+23
-1
@@ -9,7 +9,8 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"lucide-vue-next": "^0.468.0",
|
||||
"vue": "^3.5.13"
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.2",
|
||||
@@ -1043,6 +1044,12 @@
|
||||
"he": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/language-core": {
|
||||
"version": "2.2.12",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.12.tgz",
|
||||
@@ -1565,6 +1572,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.6.4",
|
||||
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz",
|
||||
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.6.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-tsc": {
|
||||
"version": "2.2.12",
|
||||
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.2.12.tgz",
|
||||
|
||||
+21
-4
@@ -1,6 +1,23 @@
|
||||
{
|
||||
"name": "nalati-rag-frontend", "private": true, "version": "0.1.0", "type": "module",
|
||||
"scripts": {"dev": "vite", "build": "vue-tsc -b && vite build", "preview": "vite preview"},
|
||||
"dependencies": {"lucide-vue-next": "^0.468.0", "vue": "^3.5.13"},
|
||||
"devDependencies": {"@types/node": "^22.10.2", "@vitejs/plugin-vue": "^5.2.1", "typescript": "~5.7.2", "vite": "^6.0.5", "vue-tsc": "^2.2.0"}
|
||||
"name": "nalati-rag-frontend",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-vue-next": "^0.468.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.2",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.0.5",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ const router=createRouter({history:createWebHistory(),routes:[
|
||||
{path:'/login',component:()=>import('./views/LoginView.vue'),meta:{public:true}},
|
||||
{path:'/register',component:()=>import('./views/RegisterView.vue'),meta:{public:true}},
|
||||
{path:'/profile',component:()=>import('./views/ProfileView.vue')},
|
||||
{path:'/settings',component:()=>import('./views/AccountSettingsView.vue')},
|
||||
{path:'/admin',component:()=>import('./views/AdminView.vue'),meta:{role:'admin'}},
|
||||
]})
|
||||
router.beforeEach(async to=>{if(to.meta.public)return true;try{role=(await getCurrentUser()).role}catch{return {path:'/login',query:{redirect:to.fullPath}}}if(to.meta.role&&role!==to.meta.role)return {path:'/profile',query:{forbidden:'1'}};return true})
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { changePassword, getCurrentUser, updateMyProfile } from '../services/auth'
|
||||
import type { SessionUser } from '../types/auth'
|
||||
|
||||
const router = useRouter()
|
||||
const user = ref<SessionUser>()
|
||||
const profile = ref({ nickname: '', email: '' })
|
||||
const password = ref({ current_password: '', new_password: '', confirm_password: '' })
|
||||
const loading = ref(true)
|
||||
const savingProfile = ref(false)
|
||||
const savingPassword = ref(false)
|
||||
const error = ref('')
|
||||
const success = ref('')
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
user.value = await getCurrentUser()
|
||||
profile.value = { nickname: user.value.nickname ?? '', email: user.value.email ?? '' }
|
||||
} catch (cause) {
|
||||
error.value = cause instanceof Error ? cause.message : '无法读取账户资料'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
async function saveProfile() {
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
if (profile.value.email && !/^\S+@\S+\.\S+$/.test(profile.value.email)) {
|
||||
error.value = '请输入有效邮箱地址。'
|
||||
return
|
||||
}
|
||||
savingProfile.value = true
|
||||
try {
|
||||
const updated = await updateMyProfile({ nickname: profile.value.nickname || null, email: profile.value.email || null })
|
||||
if (user.value) user.value = { ...user.value, ...updated }
|
||||
success.value = '基本资料已保存。'
|
||||
} catch (cause) {
|
||||
error.value = cause instanceof Error ? cause.message : '保存资料失败'
|
||||
} finally {
|
||||
savingProfile.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function savePassword() {
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
if (password.value.new_password.length < 8) {
|
||||
error.value = '新密码至少需要 8 个字符。'
|
||||
return
|
||||
}
|
||||
if (password.value.new_password !== password.value.confirm_password) {
|
||||
error.value = '两次输入的新密码不一致。'
|
||||
return
|
||||
}
|
||||
savingPassword.value = true
|
||||
try {
|
||||
await changePassword({ current_password: password.value.current_password, new_password: password.value.new_password })
|
||||
password.value = { current_password: '', new_password: '', confirm_password: '' }
|
||||
router.replace('/login?changed=1')
|
||||
} catch (cause) {
|
||||
error.value = cause instanceof Error ? cause.message : '修改密码失败'
|
||||
} finally {
|
||||
savingPassword.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<RouterLink to="/profile">返回个人中心</RouterLink>
|
||||
<h1>账户设置</h1>
|
||||
<p v-if="loading">正在读取账户资料…</p>
|
||||
<template v-else>
|
||||
<p v-if="error" role="alert">{{ error }}</p>
|
||||
<p v-if="success" role="status">{{ success }}</p>
|
||||
<form @submit.prevent="saveProfile">
|
||||
<h2>基本资料</h2>
|
||||
<label>用户名<input :value="user?.username" disabled /></label>
|
||||
<label>昵称<input v-model.trim="profile.nickname" maxlength="80" autocomplete="nickname" /></label>
|
||||
<label>邮箱<input v-model.trim="profile.email" type="email" maxlength="320" autocomplete="email" /></label>
|
||||
<button :disabled="savingProfile">{{ savingProfile ? '保存中…' : '保存资料' }}</button>
|
||||
</form>
|
||||
<form @submit.prevent="savePassword">
|
||||
<h2>修改密码</h2>
|
||||
<label>当前密码<input v-model="password.current_password" type="password" required autocomplete="current-password" /></label>
|
||||
<label>新密码<input v-model="password.new_password" type="password" required minlength="8" autocomplete="new-password" /></label>
|
||||
<label>确认新密码<input v-model="password.confirm_password" type="password" required minlength="8" autocomplete="new-password" /></label>
|
||||
<p>修改密码后会退出当前会话,请使用新密码重新登录。</p>
|
||||
<button :disabled="savingPassword">{{ savingPassword ? '提交中…' : '修改密码' }}</button>
|
||||
</form>
|
||||
</template>
|
||||
</main>
|
||||
</template>
|
||||
@@ -1 +1 @@
|
||||
<script setup lang="ts">import { onMounted,ref } from 'vue';import { getCurrentUser,logout } from '../services/auth';import { useRouter } from 'vue-router';const user=ref<any>(),error=ref(''),router=useRouter();onMounted(async()=>{try{user.value=await getCurrentUser()}catch(e){error.value=e instanceof Error?e.message:'无法读取资料'}});async function signOut(){await logout();router.replace('/login')}</script><template><main><h1>个人中心</h1><p v-if="error">{{error}}</p><dl v-else-if="user"><dt>用户名</dt><dd>{{user.username}}</dd><dt>昵称</dt><dd>{{user.nickname||'未设置'}}</dd><dt>邮箱</dt><dd>{{user.email||'未设置'}}</dd><dt>角色</dt><dd>{{user.role}}</dd></dl><button @click="signOut">退出登录</button></main></template>
|
||||
<script setup lang="ts">import { onMounted,ref } from 'vue';import { getCurrentUser,logout } from '../services/auth';import { useRouter } from 'vue-router';import type { SessionUser } from '../types/auth';const user=ref<SessionUser>(),error=ref(''),router=useRouter();onMounted(async()=>{try{user.value=await getCurrentUser()}catch(e){error.value=e instanceof Error?e.message:'无法读取资料'}});async function signOut(){try{await logout()}finally{router.replace('/login')}}</script><template><main><h1>个人中心</h1><p v-if="error">{{error}}</p><dl v-else-if="user"><dt>用户名</dt><dd>{{user.username}}</dd><dt>昵称</dt><dd>{{user.nickname||'未设置'}}</dd><dt>邮箱</dt><dd>{{user.email||'未设置'}}</dd><dt>角色</dt><dd>{{user.role}}</dd><dt>注册时间</dt><dd>{{new Date(user.created_at).toLocaleString()}}</dd></dl><div><RouterLink to="/settings">账户设置</RouterLink><button @click="signOut">退出登录</button></div></main></template>
|
||||
|
||||
Reference in New Issue
Block a user