release: integrate dev into main #28

Merged
saint merged 60 commits from release/main-dev-integration into main 2026-10-16 22:43:45 +08:00
5 changed files with 142 additions and 6 deletions
Showing only changes of commit ab70678f3f - Show all commits
+23 -1
View File
@@ -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
View File
@@ -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"
}
}
+1
View File
@@ -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
View File
@@ -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>