提交代码
This commit is contained in:
parent
4c8b0ca7e2
commit
66d4850ad5
|
@ -29,6 +29,9 @@ type Service interface {
|
|||
|
||||
// GetUser 获取用户信息
|
||||
GetUser(ctx context.Context, userID int64) (*User, error)
|
||||
|
||||
// EditUser 编辑用户信息
|
||||
EditUser(ctx context.Context, user *User) (*User, error)
|
||||
}
|
||||
|
||||
func RegisterType(typeRegister contract.TypeRegisterService) {
|
||||
|
@ -38,14 +41,26 @@ func RegisterType(typeRegister contract.TypeRegisterService) {
|
|||
// User 代表一个用户,注意这里的用户信息字段在不同接口和参数可能为空
|
||||
type User struct {
|
||||
ID int64 `gorm:"column:id;primary_key;auto_increment" json:"id"` // 代表用户id, 只有注册成功之后才有这个id,唯一表示一个用户
|
||||
UserName string `gorm:"column:username;type:varchar(255);not null" json:"username"`
|
||||
Password string `gorm:"column:password;type:varchar(255);not null" json:"password"`
|
||||
Email string `gorm:"column:email;type:varchar(255);not null" json:"email"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null;<-:create" json:"createdAt"`
|
||||
|
||||
UserName string `gorm:"column:username;type:varchar(255);comment:用户名;not null" json:"username"`
|
||||
NickName string `gorm:"column:username;type:varchar(255);comment:昵称;not null" json:"nickname"`
|
||||
Avatar string `gorm:"column:username;type:varchar(255);comment:头像" json:"avatar"`
|
||||
Password string `gorm:"column:password;type:varchar(255);comment:密码;not null" json:"password"`
|
||||
Email string `gorm:"column:email;type:varchar(255);comment:邮箱;not null" json:"email"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:datetime;comment:创建时间;not null;<-:create" json:"createdAt"`
|
||||
Accounts []Account `gorm:"foreignKey:UserID"`
|
||||
Token string `gorm:"-"` // token 可以用作注册token或者登录token
|
||||
}
|
||||
|
||||
// Account 代表一个用户账户信息,有可能有多种登录方式
|
||||
type Account struct {
|
||||
ID int64 `gorm:"column:id;primary_key;auto_increment" json:"id"`
|
||||
UserID int64 `gorm:"column:user_id;index;comment:用户ID;not null;default:0" json:"userId"`
|
||||
AccountType int64 `gorm:"column:account;type:varchar(255);comment:账户类型;not null;default:0" json:"account"`
|
||||
Password string `gorm:"column:password;type:varchar(255);comment:密码;not null" json:"password"`
|
||||
Remark string `gorm:"column:remark;type:varchar(255);comment:备注;not null" json:"remark"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:datetime;comment:创建时间;not null" json:"createdAt"`
|
||||
}
|
||||
|
||||
// MarshalBinary 实现BinaryMarshaler 接口
|
||||
func (b *User) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(b)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,14 +10,17 @@
|
|||
"typecheck": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"element-plus": "^2.6.2",
|
||||
"vue": "^3.4.21"
|
||||
"element-plus": "^2.8.6",
|
||||
"js-cookie": "^3.0.5",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.4.5",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/ep": "^1.1.15",
|
||||
"@types/node": "^20.11.30",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"sass": "^1.72.0",
|
||||
"sass": "^1.56.0",
|
||||
"typescript": "^5.4.3",
|
||||
"unocss": "^0.58.6",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
|
|
18
src/App.vue
18
src/App.vue
|
@ -1,23 +1,17 @@
|
|||
<template>
|
||||
|
||||
<el-config-provider namespace="ep">
|
||||
<BaseHeader />
|
||||
<div class="flex main-container">
|
||||
<BaseSide />
|
||||
<div w="full" py="4">
|
||||
<Logos my="4" />
|
||||
<HelloWorld msg="Hello Vue 3 + Element Plus + Vite" />
|
||||
</div>
|
||||
<!-- 使用 router-view 渲染匹配的路由组件 -->
|
||||
<router-view />
|
||||
</div>
|
||||
</el-config-provider>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
text-align: center;
|
||||
color: var(--ep-text-color-primary);
|
||||
}
|
||||
|
||||
.main-container {
|
||||
height: calc(100vh - var(--ep-menu-item-height) - 3px);
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
</script>
|
|
@ -10,17 +10,25 @@ declare module 'vue' {
|
|||
BaseHeader: typeof import('./components/layouts/BaseHeader.vue')['default']
|
||||
BaseSide: typeof import('./components/layouts/BaseSide.vue')['default']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
||||
Logos: typeof import('./components/Logos.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,71 @@
|
|||
<script lang="ts" setup>
|
||||
import { toggleDark } from "~/composables";
|
||||
import {toggleDark} from '~/composables';
|
||||
import {computed, PropType, ref} from 'vue';
|
||||
import {useStore} from 'vuex';
|
||||
|
||||
// 使用 Vuex store
|
||||
const store = useStore();
|
||||
|
||||
// 获取登录状态
|
||||
const isLoggedIn = computed(() => store.state.user.isLoggedIn);
|
||||
|
||||
// 当前激活的菜单项索引
|
||||
const activeIndex = ref('1');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu class="el-menu-demo" mode="horizontal">
|
||||
<el-menu-item index="1">Element Plus</el-menu-item>
|
||||
<el-sub-menu index="2">
|
||||
<template #title>Workspace</template>
|
||||
<el-menu-item index="2-1">item one</el-menu-item>
|
||||
<el-menu-item index="2-2">item two</el-menu-item>
|
||||
<el-menu-item index="2-3">item three</el-menu-item>
|
||||
<el-sub-menu index="2-4">
|
||||
<template #title>item four</template>
|
||||
<el-menu-item index="2-4-1">item one</el-menu-item>
|
||||
<el-menu-item index="2-4-2">item two</el-menu-item>
|
||||
<el-menu-item index="2-4-3">item three</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-sub-menu>
|
||||
<el-menu-item index="3" disabled>Info</el-menu-item>
|
||||
<el-menu-item index="4">Orders</el-menu-item>
|
||||
<el-menu-item h="full" @click="toggleDark()">
|
||||
<button
|
||||
class="border-none w-full bg-transparent cursor-pointer"
|
||||
style="height: var(--ep-menu-item-height)"
|
||||
<el-menu
|
||||
:default-active="activeIndex"
|
||||
class="el-menu-demo"
|
||||
:ellipsis="false"
|
||||
mode="horizontal"
|
||||
>
|
||||
<i inline-flex i="dark:ep-moon ep-sunny" />
|
||||
<div class="menu-left">
|
||||
<!-- 渲染左侧菜单项 -->
|
||||
<el-menu-item index="0">Processing Center</el-menu-item>
|
||||
<el-menu-item v-if="isLoggedIn" index="1">Dashboard</el-menu-item>
|
||||
<el-menu-item v-if="isLoggedIn" index="2">Orders</el-menu-item>
|
||||
</div>
|
||||
|
||||
<!-- 右侧按钮 -->
|
||||
<div class="menu-right">
|
||||
<el-menu-item index="3" @click="toggleDark()">
|
||||
<button
|
||||
class="border-none w-full bg-transparent cursor-pointer">
|
||||
<i inline-flex i="dark:ep-moon ep-sunny"/>
|
||||
</button>
|
||||
</el-menu-item>
|
||||
</div>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.el-menu-demo {
|
||||
display: flex; /* 使用 Flex 布局 */
|
||||
justify-content: space-between; /* 左右对齐 */
|
||||
align-items: center; /* 垂直居中 */
|
||||
width: 100%;
|
||||
padding: 0 16px; /* 添加一些内边距 */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
display: flex; /* 左侧菜单项的布局 */
|
||||
gap: 16px; /* 菜单项之间的间距 */
|
||||
}
|
||||
|
||||
.menu-right {
|
||||
display: flex; /* 右侧按钮的布局 */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toggle-dark-btn {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -11,10 +11,13 @@ import App from "./App.vue";
|
|||
|
||||
import "~/styles/index.scss";
|
||||
import "uno.css";
|
||||
|
||||
import router from "./router/index"; // 导入 router
|
||||
import store from './store/index'
|
||||
// If you want to use ElMessage, import it.
|
||||
import "element-plus/theme-chalk/src/message.scss";
|
||||
|
||||
const app = createApp(App);
|
||||
// app.use(ElementPlus);
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
app.mount("#app");
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
// src/router/index.ts
|
||||
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import ViewLogin from '../views/login/index.vue';
|
||||
import ViewRegister from '../views/register/index.vue';
|
||||
import View404 from '../views/404.vue';
|
||||
import ViewContainer from '../views/layout/container.vue';
|
||||
import ViewList from '../views/list/index.vue';
|
||||
import ViewDetail from '../views/detail/index.vue';
|
||||
import ViewCreate from '../views/create/index.vue';
|
||||
import ViewEdit from '../views/edit/index.vue';
|
||||
|
||||
/**
|
||||
* constantRoutes
|
||||
* 基础路由,不需要权限控制
|
||||
* 所有角色都可以访问
|
||||
*/
|
||||
export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/login',
|
||||
component: ViewLogin,
|
||||
meta: { hidden: true },
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: ViewRegister,
|
||||
meta: { hidden: true },
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: View404,
|
||||
meta: { hidden: true },
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: ViewContainer,
|
||||
redirect: '/list',
|
||||
children: [
|
||||
{
|
||||
path: 'list',
|
||||
name: 'List',
|
||||
component: ViewList,
|
||||
},
|
||||
{
|
||||
path: 'detail',
|
||||
name: 'Detail',
|
||||
component: ViewDetail,
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
name: 'Create',
|
||||
component: ViewCreate,
|
||||
},
|
||||
{
|
||||
path: 'edit',
|
||||
name: 'Edit',
|
||||
component: ViewEdit,
|
||||
},
|
||||
],
|
||||
},
|
||||
// 404 页面必须放在最后
|
||||
{ path: '/:pathMatch(.*)*', redirect: '/404', meta: { hidden: true } },
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: constantRoutes,
|
||||
});
|
||||
|
||||
export function resetRouter() {
|
||||
const newRouter = createRouter()
|
||||
router.matcher = newRouter.matcher // reset router
|
||||
}
|
||||
|
||||
export default router;
|
|
@ -1,5 +1,26 @@
|
|||
import {
|
||||
AnswerCreateParam,
|
||||
AnswerDeleteParam,
|
||||
LoginParam,
|
||||
QuestionCreateParam,
|
||||
QuestionDeleteParam,
|
||||
QuestionDetailParam,
|
||||
QuestionEditParam,
|
||||
QuestionListParam,
|
||||
RegisterParam
|
||||
} from "~/services/apiTypes"
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
// 定义基础 URL
|
||||
const BASE_URL = '/';
|
||||
const BASE_URL = 'http://127.0.0.1:8888';
|
||||
|
||||
// 定义后端返回的数据结构类型
|
||||
interface ApiResponse<T> {
|
||||
code: number;
|
||||
success: boolean;
|
||||
message: string;
|
||||
data?: T;
|
||||
}
|
||||
|
||||
// 通用的 fetch 请求封装函数
|
||||
async function request<T>(
|
||||
|
@ -16,15 +37,19 @@ async function request<T>(
|
|||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
};
|
||||
|
||||
console.log(options)
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}${endpoint}`, options);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`API Error: ${response.status} ${errorText}`);
|
||||
const result: ApiResponse<T> = await response.json();
|
||||
if (!result.success) {
|
||||
ElMessage.error(result.message)
|
||||
}
|
||||
// 返回成功的数据
|
||||
return result.data as T;
|
||||
} catch (error) {
|
||||
console.error('请求出错:', error);
|
||||
ElMessage.error("网络开小差")
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// 用户相关 API 封装
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { GetterTree } from 'vuex';
|
||||
import { RootState } from './index';
|
||||
|
||||
const getters: GetterTree<RootState, RootState> = {
|
||||
sidebar: (state) => state.app.sidebar,
|
||||
device: (state) => state.app.device,
|
||||
size: (state) => state.app.size,
|
||||
token: (state) => state.user.token,
|
||||
avatar: (state) => state.user.avatar,
|
||||
name: (state) => state.user.name,
|
||||
visitedViews: (state) => state.tagsView.visitedViews,
|
||||
cachedViews: (state) => state.tagsView.cachedViews,
|
||||
};
|
||||
|
||||
export default getters;
|
|
@ -0,0 +1,28 @@
|
|||
// src/store/index.ts
|
||||
|
||||
import { createStore, Store, ModuleTree } from 'vuex';
|
||||
import getters from './getters';
|
||||
|
||||
// 定义 RootState 的类型
|
||||
export interface RootState {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 自动导入 `modules` 文件夹中的 Vuex 模块
|
||||
const modulesFiles = import.meta.glob('./modules/**/*.ts', { eager: true });
|
||||
|
||||
// 遍历所有模块,并动态注册
|
||||
const modules: ModuleTree<RootState> = Object.keys(modulesFiles).reduce((modules, modulePath) => {
|
||||
const moduleName = modulePath.replace(/^\.\/modules\/(.*)\.\w+$/, '$1');
|
||||
const value = modulesFiles[modulePath] as { default: any };
|
||||
modules[moduleName] = value.default;
|
||||
return modules;
|
||||
}, {} as ModuleTree<RootState>);
|
||||
|
||||
// 创建 Vuex Store
|
||||
const store: Store<RootState> = createStore({
|
||||
modules,
|
||||
getters,
|
||||
});
|
||||
|
||||
export default store;
|
|
@ -0,0 +1,93 @@
|
|||
// src/store/modules/user.ts
|
||||
|
||||
import {Module} from 'vuex';
|
||||
import {getToken, removeToken, setToken} from '~/utils/auth';
|
||||
import {resetRouter} from '~/router';
|
||||
import {userService} from '~/services/apiServices';
|
||||
|
||||
// 定义 UserState 的类型
|
||||
export interface UserState {
|
||||
token: string | null;
|
||||
name: string;
|
||||
avatar: string;
|
||||
isLoggedIn: boolean; // 是否已登录
|
||||
}
|
||||
|
||||
// 获取默认状态
|
||||
const getDefaultState = (): UserState => ({
|
||||
token: getToken(),
|
||||
name: 'Superdandan',
|
||||
avatar: 'https://www.bing.com/images/search?view=detailV2&ccid=UyaBji0A&id=215CD76D0E1089B1CC80B1DC80500B19262DC18C&thid=OIP.UyaBji0AU_6M3VDA2F1RvgAAAA&mediaurl=https%3a%2f%2fgd-hbimg.huaban.com%2fe7b770bf874c9ae0a90976608d0ea889b889d4017ed22-0hmCwW_fw236&cdnurl=https%3a%2f%2fth.bing.com%2fth%2fid%2fR.5326818e2d0053fe8cdd50c0d85d51be%3frik%3djMEtJhkLUIDcsQ%26pid%3dImgRaw%26r%3d0&exph=236&expw=236&q=%e5%a4%b4%e5%83%8f&simid=608001262209275221&FORM=IRPRST&ck=D7DEB083F3CBBF51E0A49A21A7E0E213&selectedIndex=27&itb=1',
|
||||
isLoggedIn: !!getToken(), // 根据 token 判断是否已登录
|
||||
});
|
||||
|
||||
// 初始状态
|
||||
const state: UserState = getDefaultState();
|
||||
|
||||
// Mutations
|
||||
const mutations = {
|
||||
RESET_STATE(state: UserState) {
|
||||
Object.assign(state, getDefaultState());
|
||||
state.isLoggedIn = false
|
||||
},
|
||||
SET_TOKEN(state: UserState, token: string) {
|
||||
state.token = token;
|
||||
state.isLoggedIn = true
|
||||
},
|
||||
SET_NAME(state: UserState, name: string) {
|
||||
state.name = name;
|
||||
},
|
||||
SET_AVATAR(state: UserState, avatar: string) {
|
||||
state.avatar = avatar;
|
||||
},
|
||||
};
|
||||
|
||||
// Actions
|
||||
const actions = {
|
||||
// 用户登录
|
||||
async login({commit}: any, userInfo: { username: string; password: string }) {
|
||||
const {username, password} = userInfo;
|
||||
try {
|
||||
const response = await userService.login({
|
||||
username: username.trim(),
|
||||
password,
|
||||
});
|
||||
const token = response.data;
|
||||
commit('SET_TOKEN', token);
|
||||
setToken(token);
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
|
||||
// 用户登出
|
||||
async logout({commit, state}: any) {
|
||||
try {
|
||||
await userService.logout();
|
||||
removeToken(); // 必须先移除 token
|
||||
resetRouter();
|
||||
commit('RESET_STATE');
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
|
||||
// 重置 Token
|
||||
async resetToken({commit}: any) {
|
||||
removeToken(); // 必须先移除 token
|
||||
commit('RESET_STATE');
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
|
||||
// 定义模块
|
||||
const user: Module<UserState, any> = {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
};
|
||||
|
||||
export default user;
|
|
@ -0,0 +1,31 @@
|
|||
// src/utils/auth.ts
|
||||
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
// 定义 Token 在 Cookie 中的键名
|
||||
const TokenKey = 'hade_bbs';
|
||||
|
||||
/**
|
||||
* 获取 Token
|
||||
* @returns {string | undefined} 返回 Token 或 undefined
|
||||
*/
|
||||
export function getToken(): string | undefined {
|
||||
return Cookies.get(TokenKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Token
|
||||
* @param token 要存储的 Token
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setToken(token: string): void {
|
||||
Cookies.set(TokenKey, token, { expires: 7 }); // 过期时间 7 天
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 Token
|
||||
* @returns {void}
|
||||
*/
|
||||
export function removeToken(): void {
|
||||
Cookies.remove(TokenKey);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
// 使用 reactive 定义 model 对象
|
||||
const model = reactive({
|
||||
username: '',
|
||||
password: ''
|
||||
});
|
||||
|
||||
// loading 状态使用 ref
|
||||
const loading = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="notfound">
|
||||
<el-card>
|
||||
<h2>页面找不到了</h2>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.notfound {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 240px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,115 @@
|
|||
<script setup lang="ts">
|
||||
import {reactive} from 'vue';
|
||||
import {useRouter} from 'vue-router';
|
||||
import {userService} from "~/services/apiServices";
|
||||
import store from "~/store";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
const handleLogin = () => {
|
||||
userService.login({
|
||||
username: form.username,
|
||||
password: form.password
|
||||
})
|
||||
.then((response) => {
|
||||
// 成功获取 Token 后,更新 Vuex 状态并存储 Token
|
||||
const token = response;
|
||||
console.log("收到 token:" + token)
|
||||
if (token == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
store.commit('user/SET_TOKEN', token);
|
||||
store.commit('user/SET_NAME', token);
|
||||
store.commit('user/SET_AVATAR', token);
|
||||
|
||||
// 验证 token 是否成功存储到 Vuex 中
|
||||
const storedToken = store.state.user.token;
|
||||
if (storedToken === token) {
|
||||
console.log('Token 已成功存储在 Vuex 中:', storedToken);
|
||||
} else {
|
||||
console.error('Token 存储失败');
|
||||
}
|
||||
|
||||
router.push('/');
|
||||
}).catch((error) => {
|
||||
console.error('登录错误:', error);
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-card>
|
||||
<h2>登录</h2>
|
||||
<el-form
|
||||
class="login-form"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="form.username" placeholder="用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
placeholder="密码"
|
||||
type="password"
|
||||
v-model="form.password"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col class="register">
|
||||
还没有账号?请点击
|
||||
<router-link class="to-link" :to="{path: '/register'}">
|
||||
<el-link type="primary">注册</el-link>
|
||||
</router-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
class="login-button"
|
||||
type="primary"
|
||||
@click="handleLogin"
|
||||
block
|
||||
>登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.to-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.register {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 390px;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<div class="register">
|
||||
<el-card>
|
||||
<h2>注册</h2>
|
||||
<el-form :model="form" class="register-form">
|
||||
<el-form-item>
|
||||
<el-input v-model="form.username" placeholder="用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="form.email" placeholder="邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
type="password"
|
||||
v-model="form.password"
|
||||
placeholder="密码"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
type="password"
|
||||
v-model="form.repassword"
|
||||
placeholder="确认密码"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
:loading="loading"
|
||||
class="login-button"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
block
|
||||
>
|
||||
注册
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { userService } from '~/services/apiServices'; // 引入封装的请求方法
|
||||
|
||||
const router = useRouter(); // 路由实例
|
||||
|
||||
// 定义表单的响应式数据
|
||||
const form = reactive({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
repassword: '',
|
||||
});
|
||||
|
||||
// 定义加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单处理逻辑
|
||||
const submitForm = async () => {
|
||||
if (form.password !== form.repassword) {
|
||||
ElMessage.error('两次输入密码不一致');
|
||||
return;
|
||||
}
|
||||
loading.value = true; // 启用加载状态
|
||||
try {
|
||||
const response = await userService.register(form)
|
||||
ElMessage.success(response.message); // 显示成功消息
|
||||
// 注册成功后跳转到登录页面
|
||||
await router.push('/login');
|
||||
} finally {
|
||||
loading.value = false; // 关闭加载状态
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.register {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.register-form {
|
||||
width: 390px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue