提交代码
This commit is contained in:
parent
4c8b0ca7e2
commit
66d4850ad5
|
@ -29,6 +29,9 @@ type Service interface {
|
||||||
|
|
||||||
// GetUser 获取用户信息
|
// GetUser 获取用户信息
|
||||||
GetUser(ctx context.Context, userID int64) (*User, error)
|
GetUser(ctx context.Context, userID int64) (*User, error)
|
||||||
|
|
||||||
|
// EditUser 编辑用户信息
|
||||||
|
EditUser(ctx context.Context, user *User) (*User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterType(typeRegister contract.TypeRegisterService) {
|
func RegisterType(typeRegister contract.TypeRegisterService) {
|
||||||
|
@ -38,14 +41,26 @@ func RegisterType(typeRegister contract.TypeRegisterService) {
|
||||||
// User 代表一个用户,注意这里的用户信息字段在不同接口和参数可能为空
|
// User 代表一个用户,注意这里的用户信息字段在不同接口和参数可能为空
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64 `gorm:"column:id;primary_key;auto_increment" json:"id"` // 代表用户id, 只有注册成功之后才有这个id,唯一表示一个用户
|
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"`
|
UserName string `gorm:"column:username;type:varchar(255);comment:用户名;not null" json:"username"`
|
||||||
Password string `gorm:"column:password;type:varchar(255);not null" json:"password"`
|
NickName string `gorm:"column:username;type:varchar(255);comment:昵称;not null" json:"nickname"`
|
||||||
Email string `gorm:"column:email;type:varchar(255);not null" json:"email"`
|
Avatar string `gorm:"column:username;type:varchar(255);comment:头像" json:"avatar"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null;<-:create" json:"createdAt"`
|
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
|
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 接口
|
// MarshalBinary 实现BinaryMarshaler 接口
|
||||||
func (b *User) MarshalBinary() ([]byte, error) {
|
func (b *User) MarshalBinary() ([]byte, error) {
|
||||||
return json.Marshal(b)
|
return json.Marshal(b)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,14 +10,17 @@
|
||||||
"typecheck": "vue-tsc --noEmit"
|
"typecheck": "vue-tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"element-plus": "^2.6.2",
|
"element-plus": "^2.8.6",
|
||||||
"vue": "^3.4.21"
|
"js-cookie": "^3.0.5",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-router": "^4.4.5",
|
||||||
|
"vuex": "^4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/ep": "^1.1.15",
|
"@iconify-json/ep": "^1.1.15",
|
||||||
"@types/node": "^20.11.30",
|
"@types/node": "^20.11.30",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"sass": "^1.72.0",
|
"sass": "^1.56.0",
|
||||||
"typescript": "^5.4.3",
|
"typescript": "^5.4.3",
|
||||||
"unocss": "^0.58.6",
|
"unocss": "^0.58.6",
|
||||||
"unplugin-vue-components": "^0.26.0",
|
"unplugin-vue-components": "^0.26.0",
|
||||||
|
|
18
src/App.vue
18
src/App.vue
|
@ -1,23 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<el-config-provider namespace="ep">
|
<el-config-provider namespace="ep">
|
||||||
<BaseHeader />
|
<BaseHeader />
|
||||||
<div class="flex main-container">
|
<div class="flex main-container">
|
||||||
<BaseSide />
|
<!-- 使用 router-view 渲染匹配的路由组件 -->
|
||||||
<div w="full" py="4">
|
<router-view />
|
||||||
<Logos my="4" />
|
|
||||||
<HelloWorld msg="Hello Vue 3 + Element Plus + Vite" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-config-provider>
|
</el-config-provider>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#app {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--ep-text-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-container {
|
|
||||||
height: calc(100vh - var(--ep-menu-item-height) - 3px);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
|
@ -10,17 +10,25 @@ declare module 'vue' {
|
||||||
BaseHeader: typeof import('./components/layouts/BaseHeader.vue')['default']
|
BaseHeader: typeof import('./components/layouts/BaseHeader.vue')['default']
|
||||||
BaseSide: typeof import('./components/layouts/BaseSide.vue')['default']
|
BaseSide: typeof import('./components/layouts/BaseSide.vue')['default']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
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']
|
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
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']
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
|
ElLink: typeof import('element-plus/es')['ElLink']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
|
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
|
||||||
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
||||||
Logos: typeof import('./components/Logos.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>
|
<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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-menu class="el-menu-demo" mode="horizontal">
|
<el-menu
|
||||||
<el-menu-item index="1">Element Plus</el-menu-item>
|
:default-active="activeIndex"
|
||||||
<el-sub-menu index="2">
|
class="el-menu-demo"
|
||||||
<template #title>Workspace</template>
|
:ellipsis="false"
|
||||||
<el-menu-item index="2-1">item one</el-menu-item>
|
mode="horizontal"
|
||||||
<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)"
|
|
||||||
>
|
>
|
||||||
<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>
|
</button>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
</div>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</template>
|
</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 "~/styles/index.scss";
|
||||||
import "uno.css";
|
import "uno.css";
|
||||||
|
import router from "./router/index"; // 导入 router
|
||||||
|
import store from './store/index'
|
||||||
// If you want to use ElMessage, import it.
|
// If you want to use ElMessage, import it.
|
||||||
import "element-plus/theme-chalk/src/message.scss";
|
import "element-plus/theme-chalk/src/message.scss";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
// app.use(ElementPlus);
|
// app.use(ElementPlus);
|
||||||
|
app.use(router)
|
||||||
|
app.use(store)
|
||||||
app.mount("#app");
|
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
|
// 定义基础 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 请求封装函数
|
// 通用的 fetch 请求封装函数
|
||||||
async function request<T>(
|
async function request<T>(
|
||||||
|
@ -16,15 +37,19 @@ async function request<T>(
|
||||||
},
|
},
|
||||||
body: body ? JSON.stringify(body) : undefined,
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
};
|
};
|
||||||
|
console.log(options)
|
||||||
|
try {
|
||||||
const response = await fetch(`${BASE_URL}${endpoint}`, options);
|
const response = await fetch(`${BASE_URL}${endpoint}`, options);
|
||||||
|
const result: ApiResponse<T> = await response.json();
|
||||||
if (!response.ok) {
|
if (!result.success) {
|
||||||
const errorText = await response.text();
|
ElMessage.error(result.message)
|
||||||
throw new Error(`API Error: ${response.status} ${errorText}`);
|
}
|
||||||
|
// 返回成功的数据
|
||||||
|
return result.data as T;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('请求出错:', error);
|
||||||
|
ElMessage.error("网络开小差")
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户相关 API 封装
|
// 用户相关 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