This commit is contained in:
2025-09-29 21:38:32 +08:00
parent ed26244cdb
commit 19117de6c8
182 changed files with 11590 additions and 2156 deletions

View File

@@ -4,13 +4,32 @@ const storageBase = (() => { try { return localStorage.getItem('API_BASE_URL') |
const API_BASE_URL = (storageBase || import.meta.env.VITE_APP_API_BASE_URL || 'http://127.0.0.1:8080').replace(/\/$/, '')
export const http = axios.create({ baseURL: API_BASE_URL, timeout: 15000 })
const SHOP_ID = (() => { try { const v = localStorage.getItem('SHOP_ID'); if (v) return Number(v) } catch {} return Number(import.meta.env.VITE_APP_SHOP_ID || '1') })()
const USER_ID = (() => { try { const v = localStorage.getItem('USER_ID'); if (v) return Number(v) } catch {} return Number(import.meta.env.VITE_APP_USER_ID || '') })()
function readNumber(key: string): number { try { const v = localStorage.getItem(key); if (v) return Number(v) } catch {} return NaN }
const SHOP_ID = Number.isFinite(readNumber('SHOP_ID')) ? readNumber('SHOP_ID') : Number(import.meta.env.VITE_APP_SHOP_ID || '1')
const USER_ID = Number.isFinite(readNumber('USER_ID')) ? readNumber('USER_ID') : Number(import.meta.env.VITE_APP_USER_ID || '')
function parseJwtClaims(token: string): any {
try {
const parts = String(token || '').split('.')
if (parts.length < 2) return {}
const payload = JSON.parse(decodeURIComponent(escape(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')))))
return payload || {}
} catch { return {} }
}
http.interceptors.request.use(cfg => {
cfg.headers = cfg.headers || {}
if (!cfg.headers['X-Shop-Id']) cfg.headers['X-Shop-Id'] = String(SHOP_ID)
if (USER_ID) cfg.headers['X-User-Id'] = String(USER_ID)
const token = ((): string => { try { return localStorage.getItem('TOKEN') || '' } catch { return '' } })()
if (token && !cfg.headers['Authorization']) cfg.headers['Authorization'] = `Bearer ${token}`
// 从 Token 自动解析 userId/shopId 作为兜底
if (token) {
const claims = parseJwtClaims(token)
if (claims && claims.userId && !cfg.headers['X-User-Id']) cfg.headers['X-User-Id'] = String(claims.userId)
if (claims && claims.shopId && (!cfg.headers['X-Shop-Id'] || cfg.headers['X-Shop-Id'] === 'NaN')) cfg.headers['X-Shop-Id'] = String(claims.shopId)
}
// 最后再使用本地 USER_ID 覆盖(如已明确设置)
if (USER_ID && !cfg.headers['X-User-Id']) cfg.headers['X-User-Id'] = String(USER_ID)
cfg.headers['Accept'] = cfg.headers['Accept'] || 'application/json'
return cfg
})

View File

@@ -2,11 +2,18 @@ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{ path: '/', redirect: '/parts/submissions' },
{ path: '/login', component: () => import('../views/Login.vue') },
{ path: '/parts/submissions', component: () => import('../views/parts/Submissions.vue') },
{ path: '/my', component: () => import('../views/My.vue') }
]
const router = createRouter({ history: createWebHistory(), routes })
router.beforeEach((to, _from, next) => {
const token = ((): string => { try { return localStorage.getItem('TOKEN') || '' } catch { return '' } })()
if (!token && to.path !== '/login') return next('/login')
next()
})
export default router

View File

@@ -0,0 +1,58 @@
<template>
<div class="login-wrap">
<el-card class="login-card">
<div class="title">普通管理端登录</div>
<el-form :model="form" @keyup.enter.native="onLogin">
<el-form-item label="邮箱/手机号">
<el-input v-model="form.account" placeholder="邮箱或手机号" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password" placeholder="密码" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="onLogin">登录</el-button>
</el-form-item>
</el-form>
<div class="tips">说明登录成功后将把 USER_ID/SHOP_ID/ROLE/TOKEN 写入本地</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { post } from '../api/http'
import { useRouter } from 'vue-router'
const router = useRouter()
const form = reactive({ account: '', password: '' })
const loading = ref(false)
async function onLogin(){
if (!form.account.trim() || !form.password) return ElMessage.warning('请输入账号与密码')
loading.value = true
try {
const resp:any = await post('/api/auth/password/login', { account: form.account, password: form.password })
const u = resp?.user || {}
try {
localStorage.setItem('TOKEN', String(resp?.token||''))
if (u?.userId) localStorage.setItem('USER_ID', String(u.userId))
if (u?.shopId) localStorage.setItem('SHOP_ID', String(u.shopId))
localStorage.setItem('ROLE', 'normal_admin')
} catch {}
ElMessage.success('登录成功')
router.replace('/parts/submissions')
} catch (e:any) {
ElMessage.error(e.message || '登录失败')
} finally { loading.value=false }
}
</script>
<style scoped>
.login-wrap { height: 100vh; display:flex; align-items:center; justify-content:center; padding: 16px; }
.login-card { width: 380px; }
.title { font-weight: 800; font-size: 18px; margin-bottom: 12px; }
.tips { color:#888; font-size:12px; margin-top:8px; }
</style>