This commit is contained in:
邹方成 2025-10-10 16:34:02 +08:00
commit f298acb431
30 changed files with 1509 additions and 74 deletions

View File

@ -1,10 +1,10 @@
<p align="center">
<a href="https://github.com/mizhexiaoxiao/vue-fastapi-admin">
<img alt="Vue FastAPI Admin Logo" width="200" src="https://github.com/mizhexiaoxiao/vue-fastapi-admin/blob/main/deploy/sample-picture/logo.svg">
<!-- <img alt="Vue FastAPI Admin Logo" width="200" src="https://github.com/mizhexiaoxiao/vue-fastapi-admin/blob/main/deploy/sample-picture/logo.svg"> -->
</a>
</p>
<h1 align="center">vue-fastapi-admin</h1>
<!-- <h1 align="center">vue-fastapi-admin</h1> -->
English | [简体中文](./README.md)

View File

@ -1,10 +1,10 @@
<p align="center">
<a href="https://github.com/mizhexiaoxiao/vue-fastapi-admin">
<img alt="Vue FastAPI Admin Logo" width="200" src="https://github.com/mizhexiaoxiao/vue-fastapi-admin/blob/main/deploy/sample-picture/logo.svg">
<!-- <img alt="Vue FastAPI Admin Logo" width="200" src="https://github.com/mizhexiaoxiao/vue-fastapi-admin/blob/main/deploy/sample-picture/logo.svg"> -->
</a>
</p>
<h1 align="center">vue-fastapi-admin</h1>
<!-- <h1 align="center">vue-fastapi-admin</h1> -->
[English](./README-en.md) | 简体中文

View File

@ -17,7 +17,7 @@ export const PROXY_CONFIG = {
* @转发路径 http://localhost:9999/api/v1/user
*/
'/api/v1': {
target: 'http://127.0.0.1:9999',
target: 'http://124.222.245.240:8080',
changeOrigin: true,
},
}

View File

@ -2,6 +2,7 @@
"name": "vue-fastapi-admin-web",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",

View File

@ -5,6 +5,13 @@ export default {
getUserInfo: () => request.get('/base/userinfo'),
getUserMenu: () => request.get('/base/usermenu'),
getUserApi: () => request.get('/base/userapi'),
// 手机号
registerPhone: (data) => request.post('/app-user/register', data, { noNeedToken: true }),
loginPhone: (data) => request.post('/app-user/login', data, { noNeedToken: true }),
// pages
getIndustryList: () => request.get('/industry/list'),
getHistoryList: (params) => request.get('/app-valuations', { params }),
valuations: (data = {}) => request.post('/app-valuations', data),
// profile
updatePassword: (data = {}) => request.post('/base/update_password', data),
// users

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -6,7 +6,7 @@ const Layout = () => import('@/layout/index.vue')
export const basicRoutes = [
{
path: '/',
redirect: '/workbench', // 默认跳转到首页
redirect: '/pages', // 默认跳转到首页
meta: { order: 0 },
},
{
@ -95,6 +95,12 @@ export const basicRoutes = [
},
],
},
{
name: 'pages',
path: '/pages',
component: () => import('@/views/pages/index.vue'),
isHidden: true,
},
{
name: '403',
path: '/403',

View File

@ -77,13 +77,13 @@ export const usePermissionStore = defineStore('permission', {
},
actions: {
async generateRoutes() {
const res = await api.getUserMenu() // 调用接口获取后端传来的菜单路由
this.accessRoutes = buildRoutes(res.data) // 处理成前端路由格式
// const res = await api.getUserMenu() // 调用接口获取后端传来的菜单路由
// this.accessRoutes = buildRoutes(res.data) // 处理成前端路由格式
return this.accessRoutes
},
async getAccessApis() {
const res = await api.getUserApi()
this.accessApis = res.data
// const res = await api.getUserApi()
// this.accessApis = res.data
return this.accessApis
},
resetPermission() {

View File

@ -36,14 +36,15 @@ export const useUserStore = defineStore('user', {
actions: {
async getUserInfo() {
try {
const res = await api.getUserInfo()
if (res.code === 401) {
this.logout()
return
}
const { id, username, email, avatar, roles, is_superuser, is_active } = res.data
this.userInfo = { id, username, email, avatar, roles, is_superuser, is_active }
return res.data
// const res = await api.getUserInfo()
// if (res.code === 401) {
// this.logout()
// return
// }
// const { id, username, email, avatar, roles, is_superuser, is_active } = res.data
// this.userInfo = { id, username, email, avatar, roles, is_superuser, is_active }
// return res.data
return {}
} catch (error) {
return error
}

View File

@ -16,4 +16,5 @@ export function createAxios(options = {}) {
export const request = createAxios({
baseURL: import.meta.env.VITE_BASE_API,
Authorization: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxOCwicGhvbmUiOiIxNTg1MDIwMTEzOSIsImV4cCI6MTc2MDYzNTc0NX0.Z-2oCgVYlLo4JVuFLwNWqhj2iYyAvkZxWQp0h6AlhuI'
})

View File

@ -2,49 +2,40 @@
<AppPage :show-footer="true" bg-cover :style="{ backgroundImage: `url(${bgImg})` }">
<div
style="transform: translateY(25px)"
class="m-auto max-w-1500 min-w-345 f-c-c rounded-10 bg-white bg-opacity-60 p-15 card-shadow"
class="m-auto max-w-1500 min-w-750 f-c-c rounded-12 bg-white bg-opacity-80"
dark:bg-dark
>
<div hidden w-380 px-20 py-35 md:block>
<icon-custom-front-page pt-10 text-300 color-primary></icon-custom-front-page>
</div>
<div w-320 flex-col px-20 py-35>
<h5 f-c-c text-24 font-normal color="#6a6a6a">
<icon-custom-logo mr-10 text-50 color-primary />{{ $t('app_name') }}
</h5>
<div mt-30>
<n-input
v-model:value="loginInfo.username"
autofocus
class="h-50 items-center pl-10 text-16"
placeholder="admin"
:maxlength="20"
/>
<div w-750 px-20 style="height: 400px; padding-top: 50px; text-align: center;">
<img style="width: 371px; height: 60px; margin: auto;" src="@/assets/images/logo.png" alt="">
<div mt-50 style="text-align: center; font-size: 48px; color: #303133; line-height: 48px; font-weight: 600; ">
非遗IP价值评估系统
</div>
<div style="text-align: center; margin-top: 16px; color: #606266;">
基于深度学习算法的智能评估系统为您的知识产权和非物质文化遗产提供专业的价值评估服务
</div>
<div mt-30>
<n-input
v-model:value="loginInfo.password"
class="h-50 items-center pl-10 text-16"
type="password"
show-password-on="mousedown"
placeholder="123456"
v-model:value="loginInfo.phone"
style="display: inline-block; width: 260px; height: 42px; text-align: left; line-height: 42px;"
placeholder="请输入手机号"
:maxlength="20"
@keypress.enter="handleLogin"
/>
</div>
<div mt-20>
<n-button
h-50
w-full
rounded-5
text-16
type="primary"
:loading="loading"
@click="handleLogin"
@keypress.enter="handleRegister"
>
{{ $t('views.login.text_login') }}
<template #prefix>
<img style="width: 18px; height: 18px; margin-right: 8px;" src="@/assets/images/phone.png" alt="">
</template>
</n-input>
<n-button
w-126
h-42
rounded-5
type="primary"
style="background: linear-gradient( 93deg, #880C22 0%, #A30113 100%); margin-left: 20px; font-size: 16px; border: none !important;"
@click="handleRegister"
>
立即登录
<img style="width: 18px; height: 18px; margin-left: 2px;" src="@/assets/images/go.png" alt="">
</n-button>
</div>
</div>
@ -54,7 +45,7 @@
<script setup>
import { lStorage, setToken } from '@/utils'
import bgImg from '@/assets/images/login_bg.webp'
import bgImg from '@/assets/images/login_bg.png'
import api from '@/api'
import { addDynamicRoutes } from '@/router'
import { useI18n } from 'vue-i18n'
@ -64,8 +55,7 @@ const { query } = useRoute()
const { t } = useI18n({ useScope: 'global' })
const loginInfo = ref({
username: '',
password: '',
phone: '',
})
initLoginInfo()
@ -73,25 +63,34 @@ initLoginInfo()
function initLoginInfo() {
const localLoginInfo = lStorage.get('loginInfo')
if (localLoginInfo) {
loginInfo.value.username = localLoginInfo.username || ''
loginInfo.value.phone = localLoginInfo.phone || ''
loginInfo.value.password = localLoginInfo.password || ''
}
}
const loading = ref(false)
async function handleLogin() {
const { username, password } = loginInfo.value
if (!username || !password) {
$message.warning(t('views.login.message_input_username_password'))
async function handleRegister() {
const { phone } = loginInfo.value
if (!phone) {
$message.warning('请输入手机号')
return
}
try {
loading.value = true
$message.loading(t('views.login.message_verifying'))
const res = await api.login({ username, password: password.toString() })
$message.success(t('views.login.message_login_success'))
setToken(res.data.access_token)
await addDynamicRoutes()
loading.value = true
await api.registerPhone({ phone })
.then(res=>{
handleLogin()
})
.catch(res=>{
handleLogin()
})
}
async function handleLogin() {
const { phone } = loginInfo.value
loading.value = true
await api.loginPhone({ phone, password: phone.slice(5,11) }).catch(res=>{
setToken(res.error.access_token)
if (query.redirect) {
const path = query.redirect
console.log('path', { path, query })
@ -100,9 +99,8 @@ async function handleLogin() {
} else {
router.push('/')
}
} catch (e) {
console.error('login error', e.error)
}
loading.value = false
loading.value = false
})
}
</script>

View File

@ -0,0 +1,149 @@
<template>
<div class="step-progress-bar">
<div class="step-container">
<div
v-for="(step, index) in steps"
:key="index"
class="step-item"
>
<div
class="step-circle"
:class="{
'completed': currentStep > index,
'current': currentStep === index,
'pending': currentStep < index
}"
>
<span>{{ index + 1 }}</span>
</div>
<div class="step-title">{{ step.title }}</div>
<!-- 虚线连接线 -->
<div v-if="index < steps.length - 1" class="step-line">
<div
class="line-progress"
:style="{ width: lineProgress(index) }"
></div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
interface Step {
title: string;
}
const props = defineProps({
steps: {
type: Array as PropType<Step[]>,
required: true
},
currentStep: {
type: Number,
default: 0,
validator: (val: number) => val >= 0
}
});
// 线
const lineProgress = (index: number) => {
if (props.currentStep > index + 1) {
return '100%';
} else if (props.currentStep === index + 1) {
return '50%';
}
return '0%';
};
</script>
<style scoped>
.step-progress-bar {
width: 100%;
padding: 20px 0;
}
.step-container {
position: relative;
display: flex;
justify-content: space-between;
}
.step-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.step-circle {
z-index: 1;
display: flex;
width: 32px;
height: 32px;
font-size: 14px;
font-weight: 500;
border-radius: 50%;
align-items: center;
justify-content: center;
}
.completed {
color: #fff;
background: #A30113;
}
.current {
color: #fff;
background: #A30113;
}
.pending {
color: #000;
background-color: #f5f5f5;
/* border: 1px solid #d9d9d9; */
}
.check-icon {
font-weight: bold;
}
.step-title {
margin-top: 8px;
font-size: 14px;
text-align: center;
}
.step-line {
position: absolute;
top: 16px;
left: 50%;
z-index: 0;
width: 100%;
height: 1px;
}
.line-progress {
width: 0;
height: 100%;
background-color: #fff;
transition: width 1s ease;
}
/* 虚线背景 */
.step-line::before {
position: absolute;
top: 0;
left: 15%;
width: 70%;
height: 2px;
background-image: linear-gradient(to right, #A30113 50%, transparent 50%);
background-repeat: repeat-x;
background-size: 9px 3px;
content: '';
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@
:key="i"
class="mb-10 mt-10 w-300 cursor-pointer"
hover:card-shadow
title="Vue FastAPI Admin"
title=""
size="small"
>
<p op-60>{{ dummyText }}</p>