问题: - Vue文件中直接使用错误的API路径 /api/v1/xxx - 导致请求URL重复包含/api/v1,被当作静态资源处理 修复: 1. 重构src/api/index.ts,按模块分类集中定义所有API - 用户认证:login, getUserInfo, logout - 项目管理:getProjectList, getProjectById - 客户管理:getCustomerList - 支出管理:createExpense, getExpenseList, getExpenseTypeTree, getTodayExpense - 应收款管理:getReceivableList, getUpcomingDueList, getTodayIncome, getUnpaidAmount, getOverdueCount 2. 修复各Vue文件,使用集中的API定义 - Home.vue: 使用getTodayIncome, getTodayExpense, getUnpaidAmount - receivable/List.vue: 使用getReceivableList - expense/Add.vue: 使用createExpense, getExpenseTypeTree - Login.vue: 使用login 正确的API路径: - 前端请求: /fund/receipt/receivable/page - Gateway转发: /api/v1/receipt/receivable/page
216 lines
4.3 KiB
Vue
216 lines
4.3 KiB
Vue
<template>
|
|
<div class="page login">
|
|
<div class="login-header">
|
|
<div class="logo">
|
|
<van-icon name="gold-coin-o" />
|
|
</div>
|
|
<h2>资金服务平台</h2>
|
|
<p>Financial Platform</p>
|
|
</div>
|
|
|
|
<div class="login-card mac-card fade-in-up">
|
|
|
|
<div class="form-group">
|
|
<label>用户名</label>
|
|
<div class="input-wrapper">
|
|
<van-icon name="user-o" class="input-icon" />
|
|
<input
|
|
v-model="form.username"
|
|
type="text"
|
|
placeholder="用户名"
|
|
clearable
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>密码</label>
|
|
<div class="input-wrapper">
|
|
<van-icon name="lock" class="input-icon" />
|
|
<input
|
|
v-model="form.password"
|
|
:type="showPassword ? 'text' : 'password'"
|
|
placeholder="密码"
|
|
/>
|
|
<van-icon
|
|
:name="showPassword ? 'eye-o' : 'closed-eye'"
|
|
class="toggle-password"
|
|
@click="showPassword = !showPassword"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="login-btn mac-btn" :disabled="loading" @click="handleLogin">
|
|
{{ loading ? '登录中...' : '登录' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { showToast, showSuccessToast } from 'vant'
|
|
import { login } from '@/api'
|
|
|
|
const router = useRouter()
|
|
const loading = ref(false)
|
|
const showPassword = ref(false)
|
|
|
|
const form = reactive({
|
|
username: '',
|
|
password: ''
|
|
})
|
|
|
|
const handleLogin = async () => {
|
|
if (!form.username) {
|
|
showToast('请输入用户名')
|
|
return
|
|
}
|
|
if (!form.password) {
|
|
showToast('请输入密码')
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
try {
|
|
const res: any = await login(form)
|
|
const data = res.data
|
|
localStorage.setItem('token', data.token)
|
|
localStorage.setItem('userInfo', JSON.stringify({
|
|
userId: data.userId,
|
|
username: data.username,
|
|
tenantId: data.tenantId
|
|
}))
|
|
localStorage.setItem('tenantId', String(data.tenantId || '1'))
|
|
showSuccessToast('登录成功')
|
|
router.push('/')
|
|
} catch (e: any) {
|
|
showToast(e.message || '登录失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login {
|
|
min-height: 100vh;
|
|
padding: 0 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
.login-header {
|
|
text-align: center;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.logo {
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 24px;
|
|
background: linear-gradient(135deg, var(--mac-primary), #5AC8FA);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin: 0 auto 20px;
|
|
box-shadow: 0 10px 40px rgba(0, 122, 255, 0.25);
|
|
}
|
|
|
|
.logo .van-icon {
|
|
font-size: 40px;
|
|
color: #fff;
|
|
}
|
|
|
|
.login-header h2 {
|
|
margin: 0;
|
|
font-size: 26px;
|
|
font-weight: 700;
|
|
color: var(--mac-text);
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
.login-header p {
|
|
margin: 8px 0 0;
|
|
color: var(--mac-text-secondary);
|
|
font-size: 13px;
|
|
letter-spacing: 2px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.login-card {
|
|
padding: 32px 24px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
color: var(--mac-text);
|
|
margin-bottom: 28px;
|
|
text-align: center;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--mac-text-secondary);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.input-wrapper {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
background: rgba(0, 0, 0, 0.03);
|
|
border-radius: 12px;
|
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.input-wrapper:focus-within {
|
|
border-color: var(--mac-primary);
|
|
background: #fff;
|
|
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
|
|
}
|
|
|
|
.input-icon {
|
|
font-size: 18px;
|
|
color: var(--mac-text-secondary);
|
|
padding-left: 14px;
|
|
}
|
|
|
|
.input-wrapper input {
|
|
flex: 1;
|
|
border: none;
|
|
background: transparent;
|
|
padding: 14px;
|
|
font-size: 15px;
|
|
color: var(--mac-text);
|
|
outline: none;
|
|
}
|
|
|
|
.input-wrapper input::placeholder {
|
|
color: var(--mac-text-secondary);
|
|
}
|
|
|
|
.toggle-password {
|
|
font-size: 18px;
|
|
color: var(--mac-text-secondary);
|
|
padding-right: 14px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.login-btn {
|
|
width: 100%;
|
|
margin-top: 32px;
|
|
}
|
|
</style>
|