All checks were successful
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 4s
Test / build-check (push) Successful in 3s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 16s
Test / e2e-test (push) Successful in 1m5s
权限: - viewer 不能编辑公共配方(前端+后端双重限制) - viewer 管理配方页只显示"我的配方" - 取消 token 链接登录,改为自注册+管理员分配角色 - 用户管理页去掉创建用户和复制链接,禁止设管理员 - 修复改权限 API 路径错误 搜索: - 模糊匹配+同义词扩展(37组),精确/相似分层 - 精确匹配不搜精油成分(避免"西班牙牛至"污染) - 所有搜索结果底部加"通知编辑添加"按钮 UI: - 顶部 tab 栏按用户角色显示,切换时居中滚动 - 左右滑动按 visibleTabs 顺序切换 tab - 用户名旁红色通知数 badge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
98 lines
2.3 KiB
JavaScript
98 lines
2.3 KiB
JavaScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import { api } from '../composables/useApi'
|
|
|
|
const DEFAULT_USER = {
|
|
id: null,
|
|
role: 'viewer',
|
|
username: 'anonymous',
|
|
display_name: '匿名',
|
|
has_password: false,
|
|
business_verified: false,
|
|
}
|
|
|
|
export const useAuthStore = defineStore('auth', () => {
|
|
const token = ref(localStorage.getItem('oil_auth_token') || '')
|
|
const user = ref({ ...DEFAULT_USER })
|
|
|
|
// Getters
|
|
const isLoggedIn = computed(() => user.value.id !== null)
|
|
const isAdmin = computed(() => user.value.role === 'admin')
|
|
const canManage = computed(() =>
|
|
['senior_editor', 'admin'].includes(user.value.role)
|
|
)
|
|
const canEdit = computed(() =>
|
|
['editor', 'senior_editor', 'admin'].includes(user.value.role)
|
|
)
|
|
const isBusiness = computed(() => user.value.business_verified)
|
|
|
|
// Actions
|
|
async function initToken() {
|
|
if (token.value) {
|
|
await loadMe()
|
|
}
|
|
}
|
|
|
|
async function loadMe() {
|
|
try {
|
|
const data = await api.get('/api/me')
|
|
user.value = {
|
|
id: data.id,
|
|
role: data.role,
|
|
username: data.username,
|
|
display_name: data.display_name,
|
|
has_password: data.has_password ?? false,
|
|
business_verified: data.business_verified ?? false,
|
|
}
|
|
} catch {
|
|
logout()
|
|
}
|
|
}
|
|
|
|
async function login(username, password) {
|
|
const data = await api.post('/api/login', { username, password })
|
|
token.value = data.token
|
|
localStorage.setItem('oil_auth_token', data.token)
|
|
await loadMe()
|
|
}
|
|
|
|
async function register(username, password, displayName) {
|
|
const data = await api.post('/api/register', {
|
|
username,
|
|
password,
|
|
display_name: displayName,
|
|
})
|
|
token.value = data.token
|
|
localStorage.setItem('oil_auth_token', data.token)
|
|
await loadMe()
|
|
}
|
|
|
|
function logout() {
|
|
token.value = ''
|
|
localStorage.removeItem('oil_auth_token')
|
|
user.value = { ...DEFAULT_USER }
|
|
}
|
|
|
|
function canEditRecipe(recipe) {
|
|
if (isAdmin.value || user.value.role === 'senior_editor') return true
|
|
if (canEdit.value && recipe._owner_id === user.value.id) return true
|
|
return false
|
|
}
|
|
|
|
return {
|
|
token,
|
|
user,
|
|
isLoggedIn,
|
|
isAdmin,
|
|
canManage,
|
|
canEdit,
|
|
isBusiness,
|
|
initToken,
|
|
loadMe,
|
|
login,
|
|
register,
|
|
logout,
|
|
canEditRecipe,
|
|
}
|
|
})
|